Implementing Recurring Payments


This guide provides how to implement Iamport’s regular payment feature.The billing key can be acquired from Iamport through REST API or JavsScript library. After that, you can request the payment through REST API and schedule a recurring payments.
STEP1Acquiring a Billing Key
This step describes how to pass customer’s card information to the card company and acquire the billing key corresponding to the card.

What is Billing Key?

It is an encryption key for payments that enable re-payment at any time, such as pay-as-you-go or recurring payments for the subscription. Since the merchant cannot own the customer’s card information, the billing key for the card has to be acquired from the card company. The issued billing key is stored at this point, and the payment can be requested with the billing key at the desired time. To acquire the billing key, card number, card expiration date, date of birth (or business registration number), and the first 2 digits of the password are sent to the card company.
There are two main methods to acquire a billing key according to PG companies.
  • A. Acquiring a billing key through REST API(NICE and JTNet)
  • B. Acquiring a billing key from a general payment window (JTNet, Inicis, Danal-Credit card, Danal-Mobile and Mobilians)

A. Acquiring a billing key through REST API

In the case of recurring payments through NICE, the billing key is acquired through the REST API provided by Iamport. When a merchant server sends card information to the Iamport server through Iamport REST API, the Iamport server calls PG’s API. The Iamport server here is responsible for passing the card information to the PG company. The card information is not recorded during this process.There are pros and cons in this method:
  • Pros: The merchant can customize the card information input field to the desired format.
  • Cons: The secure process must be established to pass the card information, and the terms of use and privacy policy must be provided.
Through the following process, the procedure for acquiring a billing key through REST API can be implemented.
  1. Fill out the card information field
  2. Create API endpoint and extract card information
  3. Acquire a billing key through REST API
1Fill out the card information field
Fill out the input field to provide the card information. To acquire the billing key, the form should ask users to provide the card number, card expiration date, date of birth, and the first 2 digits of the password. In the case of a corporate card (except the card issued to a employee with his or her name on it), enter the 10 digits business number in the birth field.
  <form action="https://www.myservice.com/subscription/issue-billing" method="post">
    <div>
        <label for="card_number">Card number XXXX-XXXX-XXXX-XXXX</label>
        <input id="card_number" type="text" name="card_number">
    </div>
    <div>
        <label for="expiry">Card expiration date YYYY-MM</label>
        <input id="expiry" type="text" name="expiry">
    </div>
    <div>
        <label for="birth">Date of birth YYMMDD</label>
        <input id="birth" type="text" name="birth">
    </div>
    <div>
        <label for="pwd_2digit">First 2 digits of card password XX</label>
        <input id="pwd_2digit" type="text" name="pwd_2digit">
    </div>
    <input hidden type="text" value="johndoe_0001_1234" name="customer_uid">
    <input type="submit" value="Pay">
  </form>
The form was created for users to provide the card number, card expiration date, date of birth, and the first 2 digits of the password. The POST request for /subscription/issue-billing is generated to pass the contents of the form when clicking on the pay button. The value pointing to user’s card (customer_uid) is also passed together at this point.

customer_uid property

The billing key, an encryption key for payment acquired using customer’s card information, is stored in the Iamport server after the issuance. For security reasons, the merchant cannot access the billing key directly, so the value specified in customer_uid attribute is used as a unique key and stored in the Iamport server. In other words, the customer_uid value corresponds 1:1 to the billing key (per card).

If you are registering multiple cards from a customer, you should configure one customer_uid for each card. For example, if the customer’s unique key is johndoe_0001 and the last 4 digits of the card number is 1234, the value of the customer_uid attribute can be specified as johndoe_0001_1234 to point to a specific card for a specific customer.
2Create API endpoint and extract card information
Create API endpoint to receive card information and extract the card information included in the request.
  // Handle POST request to "/subscription/issue-billing"
  app.post("/subscriptions/issue-billing", async (req, res) => {
    try {
      const {
        card_number, // Card number
        expiry, // Card expiration date
        birth, // Date of birth
        pwd_2digit, // First 2 digits of card password
        customer_uid, // The id that corresponds 1:1 to the card (billing key)
      } = req.body; // Extract the card information from the request body
      ...
    } catch (e) {
      res.status(400).send(e);
    }
  });
The code above is an API that handles POST requests to /subscription/issue-billing. card_number, expiry, birth, pwd_2digit, customer_uid were extracted from the HTTP request body.
3Acquire a billing key through REST API
Create HTTP request to https:\//api.iamport.kr/subscribe/customers/{customer_uid} to acquire a billing key.
  // Handle POST request to "/subscription/issue-billing"
  app.post("/subscriptions/issue-billing", async (req, res) => {
    try {
      const {
        card_number, // Card number
        expiry, // Card expiration date
        birth, // Date of birth
        pwd_2digit, // First 2 digits of card password
        customer_uid, // The id that corresponds 1:1 to the card (billing key)
      } = req.body; // Extract the card information from the request 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
      ...
      // Request a billing key
      const issueBilling = await axios({
        url: \`https://api.iamport.kr/subscribe/customers/\${customer_uid}\`,
        method: "post",
        headers: { "Authorization": access_token }, // Append the authentication token to Authorization header
        data: {
          card_number, // Card number
          expiry, // Card expiration date
          birth, // Date of birth
          pwd_2digit, // First 2 digits of card password
        }
      });
      ...
      const { code, message } = issueBilling.data;
      if (code === 0) { // Billing key was issued successfully
        res.send({ status: "success", message: "Billing has successfully issued" });
      } else { // Failed to acquire a billing key
        res.send({ status: "failed", message });
      }
    } catch (e) {
      res.status(400).send(e);
    }
  });
In the code above, an authentication token was issued using the REST API Key and REST API Secret issued on Iamport account to retrieve payment information. The authentication 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 response to the request, it stores the access_token, which is the authentication token included in the response data. Then, customer_uid was binded to https:\//api.iamport.kr/subscribe/customers/${customer_uid}, the issued authentication token access_token was specified in Authorization, and requested as POST method with the card information including card number, expiry, birth and pwd 2digit to acquire the billing key;.

If the return value of code property in the response is 0, the issuance of the billing key is successful. Generate appropriate server responses based on the results.

Payment upon issuance

You can use Iamport REST API, /subscribe/payments/onetime, to request the billing key and the payment at the same time.

B. Acquiring a billing key from a general payment window

In the case of recurring payments through JTNet, Inicis, Danal-Credit card, Danal-Mobile and Mobilians, the billing key is acquired from the general payment window. The payment process is performed by the customer entering the card information in the general payment window provided by PG.There are pros and cons in this method:.
  • Pros: Since card information is transmitted directly to the PG company without going through the server of the merchant or Iamport, no additional security process, such as encryption of data and communication channel, is required.
  • Cons: Since card information is input in the general payment window provided by PG, the billing key is issued only through the web browser interface, and the card information input field cannot be customized.
To acquire the billing key, use the same integration method as IMP.request_pay(param, callback) in iamport.payment.js. You can find how to install the code in Implementing General Payments page. It can be implemented through the following steps.
  1. Call payment window
  2. Request a payment
1Call 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. The billing key is issued in this step.
  // Call IMP.request_pay(param, callback)
  IMP.request_pay({ // param
    pg: "html5_inicis",
    pay_method: "card", // Only "card" is supported
    merchant_uid: "issue_billingkey_monthly_0001", // The order number to acquire the billing key
    customer_uid: "johndoe_0001_1234", // The id that corresponds 1:1 to the card (billing key)
    name: "The initial authenticated payment",
    amount: 0, // Set to 0 to acquire only the billing key
    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) {
      // Billing key was issued successfully
    } else {
      // Failed to acquire a billing key
    }
  });
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.

There are requirements for the properties of param attribute when calling IMP.request_pay to acquire the billing key.
  • pay_method property should be set to card: Set it to card because recurring payments can only be made with credit/debit card.
  • amount property should be set to 0: Set it to 0 because this is not an actual payment but this is to issue the billing key.
  • customer_uid attribute property: As a value corresponding 1:1 to the billing key, a unique customer number should be used because it will attempt recurring payments with the value specified in the attribute.

Mobile web environment

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, when using one of the PG services, additional work is required, such as specifying m_redirect_url. More details can be found in Implementing General Payments > Step 7. Support mobile web environment

Differences in the configuration across PG services

Since the policy of the param required by the PG differs between the companies, be sure to refer to the examples provided by each PG. You can find this in the examples for each PG.
2Pass customer_uid and order information
Then, if the billing key is successfully issued in the client, pass customer_uid to the server. The server can request the payment later on with the customer_uid.
  // Call IMP.request_pay(param, callback)
  IMP.request_pay({ // param
    /* ...code omitted here... */
  }, function (rsp) { // callback
    if (rsp.success) {
      // Billing key was issued successfully
      // HTTP request using jQuery
      jQuery.ajax({
        url: "https://www.myservice.com/billings/", // URL of the web service
        method: "POST",
        headers: { "Content-Type": "application/json" },
        data: {
          customer_uid: "johndoe_0001_1234", // The id that corresponds 1:1 to the card (billing key)
        }
      });
    } else {
      // Failed to acquire a billing key
    }
  });
Create an API endpoint on the server side that receives customer_uid from the client.
  // Handle POST request for "/billings"
  app.post("/billings", async (req, res) => {
    try {
      const { customer_uid } = req.body; // Extract customer_uid from req body
        ...
    } catch (e) {
      res.status(400).send(e);
    }
  });
The client passed the customer_uid that corresponds 1:1 to the billing key. The API Endpoint was created on the server side to receive that value. The payment will be requested later with customer_uid along with the order information.
STEP2Request a payment
Create HTTP request to https:\//api.iamport.kr/subscribe/payments/again to attempt the payment.
  // The controller to handle POST request for "/billings"
  app.post("/billings", async (req, res) => {
    try {
      const { customer_uid } = req.body; // Extract customer_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
      ...
      // Request (re)payment
      const paymentResult = await axios({
        url: \`https://api.iamport.kr/subscribe/payments/again\`,
        method: "post",
        headers: { "Authorization": access_token }, // Append the authentication token to Authorization header
        data: {
          customer_uid,
          merchant_uid: "order_monthly_0001", // The order number for the newly created (re)payment
          amount: 8900,
          name: "Recurring payments for monthly subscription"
        }
      });
      ...
      const { code, message } = paymentResult;
      if (code === 0) { // Successful communication with the card company (additional check is required to confirm whether the payment was successful)
        if ( paymentResult.status === "paid" ) { // The payment was approved
          res.send({ ... });
        } else { // Failed to approve the payment (e.g. Customer card limit exceeded, suspended card, insufficient balance, etc.)
          // paymentResult.status: failed is returned
          res.send({ ... });
        }
        res.send({ ... });
      } else { // The payment was not approved by the card company (paymentResult is null)
        res.send({ ... });
      }
    } catch (e) {
      res.status(400).send(e);
    }
  });
First, customer_uid corresponding to the billing key was extracted.

Then, the authentication token was issued using the REST API key and REST API Secret issued on Iamport account for the authentication. The authentication 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 access token access_token was specified in Authorization for https:\//api.iamport.kr/payments/again, and the payment request was made as POST method with the values including customer_uid, merchant_uid, amount, and name.
STEP3Scheduling a Payment
If a customer enters card to purchases a recurring payments product with a issued billing key, the payment request has to be made regularly going forward. At this point, you can use /subscribe/payments/schedule of Iamport’s REST API, to schedule payments at specific time in the further.
1Schedule a payment
Create request to the Iamport REST API, /subscribe/payments/schedule, to schedule the payment. The customer number corresponding to the billing key is assigned to the customer_uid property, and the schedules property is specified with the order and scheduling information. Multiple order information can be included in one request as the schedules attribute is an array type.

The merchant_uid, schedule_at, and amount properties must be specified in each item of schedules.
  // Schedule a payment
  axios({
    url: \`https://api.iamport.kr/subscribe/payments/schedule\`,
    method: "post",
    headers: { "Authorization": access_token }, // Append the authentication token to Authorization header
    data: {
      customer_uid: "johndoe_0001_1234", // The id that corresponds 1:1 to the card (billing key)
      schedules: [
        {
          merchant_uid: "order_monthly_0001", // order number
          schedule_at: 1519862400, // Time of payment attempt in Unix timestamp. e.g. The 1st of the next month
          amount: 8900,
          name: "Recurring payments for monthly subscription",
          buyer_name: "John Doe",
          buyer_tel: "01012345678",
          buyer_email: "johndoe@gmail.com"
        }
      ]
    }
  });
In the example code above, the request was made to /subscribe/payments/schedule with POST method. The customer number was specified in customer_uid property and an order was specified in the schedules property in the request body.

In order element, the unique order number was specified in the merchant_uid property and the payment attempt time was specified in the scheduled_at property in Unix timestamp. Also, the amount of payment request was specified in the amount property, and customer information was specified.

/subscribe/payments/schedule

You can learn more about the properties of the request for /subscribe/payments/schedule, in Iamport API documentation.
2Synchronize the payment results
When the payment is attempted at scheduled time, Iamport Webhook is called to pass imp_uid and merchant_uid to specified callback URL. The merchant server inquires the payment information from the Iamport server with the received imp_uid, and trigger the defined logic based on the payment status.

Iamport Webhook

After the reservation payment is attempted using the Iamport Webhook, you can check whether the payment was successful, save the payment information in the database based on the result, and schedule the next payment. You can take a closer look at the concept of Iamport Webhook and how to set the callback URL in the Iamport Webhook documentation.
Create an endpoint to handle the POST request generated when Iamport Webhook is called.
  // Handle POST request for "/iamport-callback/schedule"
  app.post("/iamport-callback/schedule", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = req.body;
      ...
    } catch (e) {
      res.status(400).send(e);
    }
  });
An endpoint was created to handle POST request for /iamport-webhook/iamport-callback. imp_uid and merchant_uid were extracted from the HTTP request body.

Then, payment information was retrieved with imp_uid. If the payment is successfully made, the payment information is stored in the database. Node.js
  // Handle POST request for "/iamport-callback/schedule"
  app.post("/iamport-callback/schedule", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = 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
      const { status } = paymentData;
      if (status === "paid) { // Payment complete
        // Store the payment information in the database
        await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Mongoose
        ...
      } else {
        // Reattempt the payment
      }
    } 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 for the authentication. 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.

Then, the payment status is checked through the payment status of the retrieved payment information, and when the payment is completed, the payment data is synchronized to the merchant’s database.
STEP4Implementing Recurring Payments
/subscribe/payments/schedule allows you to schedule a payment attempt at desired time, it is possible to implement recurring payments using Iamport Webhook. Whenever the scheduled payment is attempted, Webhook is called, and the merchant server receives payment information through the callback url and confirms that the payment was successful to schedule the next payment. In this way, the recurring payments can be implemented.

Following STEP 3, schedule a new payment after confirming that the payment is successful.
  // Handle POST request for "/iamport-callback/schedule"
  app.post("/iamport-callback/schedule", async (req, res) => {
    try {
      const { imp_uid, merchant_uid } = 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
      const { status } = paymentData;
      if (status === "paid) { // Payment complete
        // Store the payment information in the database
        await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Mongoose
        ...
        // Schedule a new payment
        axios({
          url: \`https://api.iamport.kr/subscribe/payments/schedule\`,
          method: "post",
          headers: { "Authorization": access_token }, // Append the authentication token to Authorization header
          data: {
            customer_uid: "johndoe_0001_1234", // The id that corresponds 1:1 to the card (billing key)
            schedules: [
              {
                merchant_uid: "order_monthly_0001", // order number
                schedule_at: 1519516800, // Time of payment attempt in Unix timestamp. e.g. The 1st of the next month
                amount: 8900,
                name: "Recurring payments for monthly subscription",
                ...
              }
            ]
          }
        });
      } else {
        // Reattempt the payment
      }
    } catch (e) {
      res.status(400).send(e);
    }
  });
In the example code above, after it was confirmed that the payment was made (status === "paid"), the payment result was stored in the database, and the time to attempt the next payment was specified in the scheduled_at attribute in Unix timestamp.