Implementing General Payments


This guide provides instructions on how to install Iamport general payment feature on the website and store payment result information in the database.We will first add a payment feature to a client by inserting Iamport JavaScript library. We will then write a logic to pass an inquiry key to the server after the customer’s payment process is complete. The server retrieves the payment information from the Iamport server, verifies the payment forgery, and stores it in the database of the service.
STEP1Add Iamport library
Add <script> lines below to the payment HTML page. Since Iamport library is based on jQuery, jQuery 1.0 or higher must be included.
  <!-- jQuery -->
  <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
  <!-- iamport.payment.js -->
  <script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.8.js"></script>
STEP2Identify merchant
Put [Merchant ID](/implementation/account-info?lang=en) into the argument of IMP object init method and call it from the payment page of the website.
  var IMP = window.IMP; // Can be skipped.
  IMP.init("imp00000000"); // Replace "imp00000000" with the issued "Merchant ID".
In the code, a random number is used for the argument of init() function. Check Merchant ID in your Admin Dashboard and use it as the argument of the function.
STEP3Add code to call payment window
Call IMP.request_pay(param, callback). Include attributes and values that are required for the payment request, in the first argument of the function param.
  // Call IMP.request_pay(param, callback)
  IMP.request_pay({ // param
    pg: "html5_inicis",
    pay_method: "card",
    merchant_uid: "ORD20180131-0000011",
    name: "Norway swivel chair",
    amount: 64900,
    buyer_email: "johndoe@gmail.com",
    buyer_name: "John Doe",
    buyer_tel: "010-4242-4242",
    buyer_addr: "Shinsa-dong, Gangnam-gu, Seoul",
    buyer_postcode: "01181"
  }, function (rsp) { // callback
    if (rsp.success) {
        ...,
        // Logic on successful payment,
        ...
    } else {
        ...,
        // Logic on payment failure,
        ...
    }
  });
When IMP.request_pay(param, callback) is called, the payment module window of the designated PG company appears in the PC environment. On the other hand, some PG companies provide a different process that redirects to PG’s website in a mobile environment.

The first argument, param object, contains the information required for the payment request. Specify appropriate values for the attributes, such as payment method (pay_method), product name (name), and the product price (amount). Learn more about the attributes of param.

Create an order number (merchant_uid)

Before calling IMP.request_pay, it is recommended to forward order information to your server (INSERT order information into the database) and assign the server-generated order number to the merchant_uid attribute of param. This is because order information must be retrieved from your server for reliable verification in the step of verifying the payment forgery after the payment is completed.
The second argument, callback, is a function executed after the customer completes the payment. The argument rsp passed to the callback function contains whether the payment was successful as well as the payment and error information. After the customer completes the payment process, you can check the payment result through rsp and write the code you want to execute in the callback. Learn more about the arguments of rsp which are passed to the callback.

It is common to start a payment process by clicking a button on the order page. As shown in the example below, you can write code to call IMP.request_pay(param, callback) function by clicking the payment button.
  <button onclick="requestPay()">Pay</button>
  ...
  <script>
    function requestPay() {
      // Call IMP.request_pay(param, callback)
      IMP.request_pay({ // param
          pg: "html5_inicis",
          pay_method: "card",
          merchant_uid: "ORD20180131-0000011",
          name: "Norway swivel chair",
          amount: 64900,
          buyer_email: "johndoe@gmail.com",
          buyer_name: "John Doe",
          buyer_tel: "010-4242-4242",
          buyer_addr: "Shinsa-dong, Gangnam-gu, Seoul",
          buyer_postcode: "01181"
      }, function (rsp) { // callback
          if (rsp.success) {
              ...,
              // Logic on successful payment,
              ...
          } else {
              ...,
              // Logic on payment failure,
              ...
          }
      });
    }
  </script>
When onclick event occurs for the button, the payment process can be started by calling IMP.request_pay(param, callback) function.
STEP4Pass query parameters to a merchant server
If the payment is successful (rsp.success: true) in the callback, write a logic to pass the imp_uid and merchant_uid of rsp to the server.
  IMP.request_pay({
    /* ...code omitted here... */
  }, function (rsp) { // callback
    if (rsp.success) { // Successful payment: Successful payment approval or issuance of a virtual account
      // HTTP request using jQuery
      jQuery.ajax({
          url: "https://www.myservice.com/payments/complete", // Merchant server
          method: "POST",
          headers: { "Content-Type": "application/json" },
          data: {
              imp_uid: rsp.imp_uid,
              merchant_uid: rsp.merchant_uid
          }
      }).done(function (data) {
        // Logic executed on successful response from the merchant server through the payment API
      })
    } else {
      alert("Payment failed. Error: "+ rsp.error_msg);
    }
  });
If you pass imp_uid (transaction unique number) to the merchant server, you can query the payment information from the Iamport server with imp_uid. Also, the order information can be queried from the merchant database with merchant_uid, which is the order number managed by the merchant. With queried information, you can verify the payment forgery and store it in the database of the service.

Reasons to query the transaction information on the server side

To query transaction information, the token (access_token) has to be issued with the REST API Key and REST API Secret issued from the Admin Dashboard, and then the token must be included in the API request for the payment information. When the token issuance process is performed on the client, the REST API key and REST API Secret are exposed which is not secure. Therefore, the process of issuing the token and querying the transaction information must be performed on the server side.
STEP5Transaction verification and data synchronization on the server
When payment is completed, the server verifies if there was a forgery in the payment amount of the transaction and synchronizes the data by storing the transaction data in the database of your service.
1Create API endpoint
First, create an API endpoint that receives imp_uid from the server.
  app.use(bodyParser.json());
  // Handles the POST request for "/payments/complete"
  app.post("/payments/complete", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = req.body; // Extract imp_uid and merchant_uid from req body
    } catch (e) {
      res.status(400).send(e);
    }
  });
The code above is API to handles the POST request for /payments/complete. imp_uid was extracted from the HTTP request body.
2Query payment Information
Next, the access token is issued from https:\//api.iamport.kr/users/getToken using the [REST API Key](/implementation/account-info?lang=en) and [REST API Secret](/implementation/account-info?lang=en). Then, the access token and the extracted imp_uid are used to search for payment information through a request to https:\//api.iamport.kr/payments/$imp_uid.

REST API Access Token

All the requests to Iamport REST API that access private resources must include an access token. You can learn more about issuing and using access tokens in the REST API Access Token document.
  app.use(bodyParser.json());
  ...
  // Handles the POST request for "/payments/complete"
  app.post("/payments/complete", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = req.body; // Extract imp_uid and merchant_uid from req body
      ...
      // Acquire access token
      const getToken = await axios({
        url: "https://api.iamport.kr/users/getToken",
        method: "post", // POST method
        headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
        data: {
          imp_key: "imp_apikey", // REST API key
          imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW3ZGseDA6bkA5lamv9OqDMnxyeB9wqOsuO9W3Mx9YSJ4dTqJ3f" // REST API Secret
        }
      });
      const { access_token } = getToken.data.response; // Authentication token
      ...
      // Query payment information from Iamport with imp_uid
      const getPaymentData = await axios({
        url: \`https://api.iamport.kr/payments/\${imp_uid}\`, // pass imp_uid
        method: "get", // GET method
        headers: { "Authorization": access_token } // Append the authentication token to Authorization header
      });
      const paymentData = getPaymentData.data.response; // Query result
      ...
    } catch (e) {
      res.status(400).send(e);
    }
  });
In the code above, an access token was issued using the REST API Key and REST API Secret issued for Iamport account to retrieve payment information. The access token request was sent to https:\//api.iamport.kr/users/getToken using POST method, with application/json as Content-Type, imp_key: REST API key, and imp_secret: REST API Secret in the body.

When the server responses to the request, it stores the access_token, which is the authentication token included in the response data. Then, the URL https:\//api.iamport.kr/payments/{imp_uid} was built with imp_uid, the issued access token access_token was added to Authorization, and finally the payment information was queried by sending a request using GET method.

HTTP request to Iamport REST API

To use Iamport REST API, you need to be careful about the HTTP request specification for each API.

The requests must be generated following the requirement for each API, including the request method (GET or POST), header(Authorization: access_token, Content-Type: application/json), and request parameters.

You can find the details of each API in Iamport REST API documentation.

If you are not familiar with HTTP requests, you can check out the link below.
3Synchronize data after the verification of payment forgery.
It verifies the payment forgery by comparing the transaction amount with the amount that should have been originally paid, and stores the payment information in the database.

Reasons to verify the payment forgery

Since payment requests are made in the client environment, the request amount of payment can be forged by manipulating the script in the client. Therefore, after the payment is completed, the actual transaction amount and the amount that supposed to be paid must be compared.

For example, when you pay for a 100,000 won product, you need to create a payment request by setting the amount: 100000. However, an attacker can manipulate the script to set the property to a value lower than the actual amount, and then create a payment request. Since script manipulation on the client side has a technical feature that cannot be prevented by nature, the verification of payment forgery must be done on the server side after the payment.
The amount that should have been paid is retrieved from the merchant database and compared to the actual amount of the payment retrieved from Iamport. After that, the payment information is stored in the database and an appropriate response is generated based on the payment status.
  app.use(bodyParser.json());
  ...
  // Handles the POST request for "/payments/complete"
  app.post("/payments/complete", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = req.body; // Extract imp_uid and merchant_uid from req body
      // Acquire access token
      /* ...code omitted here... */
      // Query payment information from Iamport with imp_uid
      /* ...code omitted here... */
      const paymentData = getPaymentData.data.response; // Query result
      ...
      // Query the amount to be paid from the database
      const order = await Orders.findById(paymentData.merchant_uid);
      const amountToBePaid = order.amount; // Amount to be paid 
      ...
      // Verify payment
      const { amount, status } = paymentData;
      if (amount === amountToBePaid) { // Match the payment amount payment amount === amount to be paid
        await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Store the payment information in the database
        ...
        switch (status) {
          case "ready": // Issue a virtual account
            // Store the virtual account issue information in database
            const { vbank_num, vbank_date, vbank_name } = paymentData;
            await Users.findByIdAndUpdate("/* Customer id */", { $set: { vbank_num, vbank_date, vbank_name }});
            // Send text message for issuance of virtual account
            SMS.send({ text: \`Virtual account has been successfully issued. Account information \${vbank_num} \${vbank_date} \${vbank_name}\`});
            res.send({ status: "vbankIssued", message: "Virtual account issued successfully" });
            break;
          case "paid": // Payment complete
            res.send({ status: "success", message: "Payment succeeded" });
            break;
        }
      } else { // Payment amount mismatch. Payment is forged.
        throw { status: "forgery", message: "Forged payment attempted" };
      }
    } catch (e) {
      res.status(400).send(e);
    }
  });
Following the previous example, the amount that should have been originally paid was queried from the database of the service. In the code above, the order information was queried from the order history table/collection using the unique order number specified in merchart_uid to find out the amount that should have been paid.

Then, the actual amount paid was compared to the amount that should have been paid, amountToBePaid, to check for forgery in payment. If the payment amounts are matched, the payment information is stored in the database and an appropriate response is sent based on the payment status.

merchant_uid in database

The example assumed that the order information was stored in the store’s database before the payment process starts, that is, before calling IMP.request_pay(param, callback). This assumption starts with the order number management process commonly used in commerce apps.

First, when the user enters order information and tries to make a payment, the order information is delivered to the server of the store. The server stores the received order information in the store’s database, and then sends the unique order number (created when the database is saved) to the client. The client requests payment by specifying the received payment unique number in merchant_uid of the param attribute. A reliable verification can be done when such process is implemented.

Reasons for storing in the database of the service

To enable customers who have completed payment to access your service and check payment details, the transaction details is stored in your service database.

For example, when a customer accesses the order history page of the service after the payment, the customer’s payment information must be displayed. Since this payment information must be stored in the service database, Iamport retrieves transaction data and stores it in the database of my service to synchronize data.
STEP6Handle server response
Coming back to the client side, we will write a response logic to handle the request in STEP 4.
  IMP.request_pay({
    /* ...code omitted here... */
  }, function (rsp) { // callback
    if (rsp.success) { // Successful payment: Successful payment approval or issuance of a virtual account
        // HTTP request using jQuery
        jQuery.ajax({
          /* ...code omitted here... */
        }).done(function(data) { // Handle response
          switch(data.status) {
            case: "vbankIssued":
              // Logic for a virtual account
              break;
            case: "success":
              // Logic for a successful payment
              break;
          }
        });
    } else {
      alert("Payment failed. Error: " +  rsp.error_msg);
    }
  });
STEP7Set up Webhook and data synchronization
To synchronize the payment status of the Iamport server to the merchant database and to make up for network instability, you need to create a callback URL and write a logic to use Iamport Webhook.

Iamport webhook is called to deliver imp_uid and merchant_uid to the specified callback URL, when: the payment by credit card is completed, the customer pay into the issued virtual account, and the payment is refunded in Iamport Admin Dashboard. The merchant server inquires the payment information from the Iamport server with the received imp_uid and synchronizes the data based on the status.

Iamport Webhook

With Iamport Webhook, you can make up for network instability due to a sudden browser malfunction, and synchronize data at the time of issuing or transferring to a virtual account. You can take a closer look at the concept of Iamport Webhook and how to set the callback URL in the Iamport Webhook documentation.
1Create callback URL endpoint
Create an endpoint to handle the POST request generated when Iamport Webhook is called.
  app.use(bodyParser.json());
  ...
  // Handle POST request for "/iamport-webhook"
  app.post("/iamport-webhook", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = req.body; // Extract imp_uid and merchant_uid from req body
    } catch (e) {
      res.status(400).send(e);
    }
  })
An endpoint was created to handle POST request for /iamport-webhook, and imp_uid and merchant_uid were extracted from the HTTP request body.
2Query payment Information
Next, the access token is issued from https:\//api.iamport.kr/users/getToken using the REST API Key and REST API Secret. Then, the access token and the extracted imp_uid are used to search for payment information through a request to https:\//api.iamport.kr/payments/$imp_uid.
  app.use(bodyParser.json());
  ...
  // Handle POST request for "/iamport-webhook"
  app.post("/iamport-webhook", async (req, res) => {
    try {
        const { imp_uid, merchant_uid } = req.body; // Extract imp_uid and merchant_uid from req body
        // Acquire access token
        const getToken = await axios({
          url: "https://api.iamport.kr/users/getToken",
          method: "post", // POST method
          headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
          data: {
            imp_key: "imp_apikey", // REST API key
            imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW3ZGseDA6bkA5lamv9OqDMnxyeB9wqOsuO9W3Mx9YSJ4dTqJ3f" // REST API Secret
          }
        });
        const { access_token } = getToken.data.response; // Authentication token
        ...
        // Query payment information from Iamport with imp_uid
        const getPaymentData = await axios({
          url: \`https://api.iamport.kr/payments/\${imp_uid}\`, // pass imp_uid
          method: "get", // GET method
          headers: { "Authorization": access_token } // Append the authentication token to Authorization header
        });
        const paymentData = getPaymentData.data.response; // Query result
        ...
    } catch (e) {
      res.status(400).send(e);
    }
  });
In the code above, an access token was issued using the REST API Key and REST API Secret issued for Iamport account to retrieve payment information. The access token request was sent to https:\//api.iamport.kr/users/getToken using POST method, with application/json as Content-Type, imp_key: REST API key, and imp_secret: REST API Secret in the body.

When the server responses to the request, it stores the access_token, which is the access token included in the response data. Then, the URL https:\//api.iamport.kr/payments/{imp_uid} was built with imp_uid, the issued access token access_token was added to Authorization, and finally the payment information was queried by sending a request using GET method.
3Synchronize data after the verification of payment forgery
As described in STEP 5, it verifies the payment forgery by comparing the transaction amount with the amount that should have been originally paid, and stores the payment information in the database.

The amount that should have been paid is retrieved from the merchant database and compared to the actual amount of the payment retrieved from Iamport. After that, the payment information is stored in the database and an appropriate response is generated based on the payment status.
  app.use(bodyParser.json());
  ...
  // Handle POST request for "/iamport-webhook"
  app.post("/iamport-webhook", async (req, res) => {
    try {
        const { imp_uid, merchant_uid } = req.body; // Extract imp_uid and merchant_uid from req body
        // Acquire access token
        /* ...code omitted here... */
        // Query payment information from Iamport with imp_uid
        /* ...code omitted here... */
        const paymentData = getPaymentData.data.response; // Query result
        ...
        // Query the amount to be paid from the database
        const order = await Orders.findById(paymentData.merchant_uid);
        const amountToBePaid = order.amount; // Amount to be paid
        ...
        // Verify payment
        const { amount, status } = paymentData;
        if (amount === amountToBePaid) { // Match the payment amount payment amount === amount to be paid
          await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Store the payment information in the database
          switch (status) {
            case "ready": // Issue a virtual account
              // Store the virtual account issue information in database
              const { vbank_num, vbank_date, vbank_name } = paymentData;
              await Users.findByIdAndUpdate("/* Customer id */", { $set: { vbank_num, vbank_date, vbank_name }});
              // Send text message for issuance of virtual account
              SMS.send({ text: \`Virtual account has been successfully issued. Account information \${vbank_num} \${vbank_date} \${vbank_name}\`});
              res.send({ status: "vbankIssued", message: "Virtual account issued successfully" });
              break;
            case "paid": // Payment complete
              res.send({ status: "success", message: "Payment succeeded" });
              break;
          }
        } else { // Payment amount mismatch. Payment is forged
          throw { status: "forgery", message: "Forged payment attempted" };
        }
    } catch (e) {
      res.status(400).send(e);
    }
  });
Following the previous example, the amount that should have been originally paid was queried from the database of the service. In the code above, the order information was queried from the order history table/collection using the unique order number specified in merchart_uid to find out the amount that should have been paid.

Then, the actual amount paid was compared to the amount that should have been paid, amountToBePaid, to check for forgery in payment. If the payment amounts are matched, the payment information is stored in the database and an appropriate response is sent based on the payment status.
STEP8Support mobile web environment
This step is only required when KG Inicis, LG U+, NHN KCP, NICE, JTNet, KICC are used as PG services.

Unlike PC, KG Inicis, LG U+, NHN KCP, NICE, JTNet, and KICC provide a payment process for mobile web environments that redirects to the website of each PG service. Therefore, even if IMP.request_pay(param, callback) is called, callback is not executed after payment is completed.

Why callback is not executed?

If the browser’s URL is changed, the callback specified in IMP.request_pay is released from memory, so callback cannot be executed when payment is completed.
In response, Iamport provides the ability to redirect to a specific url after the payment is completed. Here, you can specify the url you want to redirect to the m_redirect_url attribute of the param.
1Set up redirection
When calling IMP.request_pay(param, callback), specify the url to be redirected after payment is completed in the m_redirect_url attribute of param, which is the first parameter of the function.
  // Call IMP.request_pay(param, callback)
  IMP.request_pay({
      /* ...code omitted here... */,
      m_redirect_url: "https://www.myservice.com/payments/complete/mobile"
  }, /* callback */); // callback is not executed

The above code is just an example. Please change myservice.com to your real web server domain.
After the payment is completed, a GET request will be created. At this time imp_uid, merchant_uid, imp_success, error_code and error_msg are added to the query string of the url.

What does 'payment completed' exactly mean?

Payment completed means all the following cases.

  1. Payment finished(payment status: paid, imp_success: true)
  2. Payment failure(payment status: failed, imp_success: false)
  3. Payment process could not even started because of PG module setting problems.
  4. Customers stopped process themselves with clicking X or cancel buttons.
  5. Payment process failed by wrong password, max out etc.
  6. Virtual bank issed(payment status: ready, imp_success: true)
The following examples are redirected URLs when you set m_redirect_url as https:\//myservice.com/payments/complete.
  curl https://myservice.com/payments/complete?imp_uid=iamport_unique_id&merchant_uid=unique_order_number&imp_success=true
You can write the logic to process the GET request for the URL, and perform transaction verification and data synchronization with the received imp_uid and merchant_uid.
2Precautions for imp_success
imp_success parameter means that a payment process has ended without any problems. But do not judge the success of the payment by the imp_success value because the amount could be forged.

If imp_success is true, you have to pass payment results(imp_uid, merchant_uid) to your web server(POST request) and check if the amount is forged. Unless, you need to write your own code to show the reason of failure to customers.

For some PGs, success query parameter is passed, not imp_success. And for real-time account transfer of KG Inicis, no parameter which is substitute for imp_success is passed. So keep in mind with that.
3Create API and write server logic
Write the code that performs the server logic such as verification of forgery in payment amount and storing in the database, which was introduced in STEP 5.
  app.use(bodyParser.json());
  ...
  // Handles the GET request for "/payments/complete/mobile/"
  app.get("/payments/complete/mobile/", async (req, res) => {
    try {
        const { imp_uid, merchant_uid } = req.query; // Extract imp_uid and merchant_uid from req query
        // Acquire access token
        const getToken = await axios({
          url: "https://api.iamport.kr/users/getToken",
          method: "post", // POST method
          headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
          data: {
            imp_key: "imp_apikey", // REST API key
            imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW3ZGseDA6bkA5lamv9OqDMnxyeB9wqOsuO9W3Mx9YSJ4dTqJ3f" // REST API Secret
          }
        });
        const { access_token } = getToken.data.response; // Authentication token
        ...
        // Query payment information from Iamport with imp_uid
        const getPaymentData = await axios({
          url: \`https://api.iamport.kr/payments/\${imp_uid}\`, // pass imp_uid
          method: "get", // GET method
          headers: { "Authorization": access_token } // Append the authentication token to Authorization header
        });
        const paymentData = getPaymentData.data.response; // Query result
        ...
        // Query the amount to be paid from the database
        const order = await Orders.findById(paymentData.merchant_uid);
        const amountToBePaid = order.amount; // Amount to be paid
        ...
        // Verify payment
        const { amount, status } = paymentData;
        if (amount === amountToBePaid) { // Match the payment amount payment amount === amount to be paid
          await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Store the payment information in the database
          switch (status) {
            case "ready": // Issue a virtual account
              // Store the virtual account issue information in database
              const { vbank_num, vbank_date, vbank_name } = paymentData;
              await Users.findByIdAndUpdate("/* Customer id */", { $set: { vbank_num, vbank_date, vbank_name }});
              // Send text message for issuance of virtual account
              SMS.send({ text: \`Virtual account has been successfully issued. Account information \${vbank_num} \${vbank_date} \${vbank_name}\`});
              res.send({ status: "vbankIssued", message: "Virtual account issued successfully" });
              break;
            case "paid": // Payment complete
              res.send({ status: "success", message: "Payment succeeded" });
              break;
          }
        } else { // Payment amount mismatch. Payment is forged
          throw { status: "forgery", message: "Forged payment attempted" };
        }
      } catch (e) {
        res.status(400).send(e);
      }
  });
The code above is the API that handles the GET request for /payments/complete/mobile. imp_uid and merchant_uid were extracted from the query string in the HTTP request URL. After acquiring the transaction information through the Iamport REST API using the extracted data, logic such as transaction verification and storing in the database were performed to generate an appropriate response.