Refunding Payments


This guide describes how to integrate payment refund into your website.

STEP1Request refund
client-side

Request a refund to the server with the required refund information. In the case of virtual account refund, you need to specify additional parameters for the refund desposit account information.

Request a refund in the onclick event of the Request Refund button as follows:
<button onclick="cancelPay()">Request Refund</button>
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script><!-- jQuery CDN --->
<script>
  function cancelPay() {
    jQuery.ajax({
      "url": "{URL to receive refund request}", // Example: http://www.myservice.com/payments/cancel
      "type": "POST",
      "contentType": "application/json",
      "data": JSON.stringify({
        "merchant_uid": "{Order ID}", // Example: ORD20180131-0000011
            "cancel_request_amount": 2000, // Refund amount
            "reason": "Testing payment refund" // Reason for the refund
            "refund_holder": "Jone Doe", // [required for virtual account refund] refund account holder's name
            "refund_bank": "88" // [required for virtual account refund] refund account bank code (e.g. Shinhan Bank is 88 for KG Inicis)
            "refund_account": "56211105948400" // [required for virtual account refund] refund account number
      }),
      "dataType": "json"
    });
  }
</script>
STEP2Get payment information
server-side

Assume that there is a Payments table that stores payment information as follows:
  /* ... model/payments.js ... */
  var mongoose = require('mongoose');
  var Schema = mongoose.Schema;
  ...
  var PaymentsSchema = new Schema({
    imp_uid: String, // i'mport unique ID (unique key for refund)
    merchant_uid: String, // order ID (used to query payment information)
    amount: { type: Number, default: 0 }, // Payment amount (for calculating refundable amount)
    cancel_amount: { type: Number, default: 0 }, // Total amount refunded (for calculating refundable amount)
    ...
  });
  ...
  module.exports = mongoose.model('Payments', PaymentsSchema);
Query the payment information of the order from the Payments table using the order ID (merchant_uid) received from the client.
  /* ... Omitted ... */
  var Payments = require('./models/payments');
  app.post('/payments/cancel', async (req, res, next) => {
    try {
      /* Get access token */
      /* ... Omitted ... */
      /* Get payment information */
      const { body } = req;
      const { merchant_uid } = body; // Order ID from the client
      Payments.find({ merchant_uid }, async function(err, payment) { 
        if (err) {
          return res.json(err);
        }
        const paymentData = payment[0]; // Save payment information
        /* Call i'mport REST API to request refund */
        ...
      });
    } catch (error) {
      res.status(400).send(error);
    }
  });
STEP3Request refund to i'mport server
server-side

To request a refund, you must first get a REST API access token.

Use the access token and payment/refund information to call the REST API (POST https: ///api.iamport.kr/payments/cancel) to request a refund.

Guidelines for setting parameters for refund request:

  • Refund unique key (imp_uid or merchant_id)

    Set imp_uid or merchant_uid as the unique key that identifies the transaction to be refunded. The value of imp_uid takes precedence, and if an invalid imp_uid value is entered, the refund request will fail regardless of the merchant_uid value.
  • Refund amount (amount)

    Enter the requested refund amount. If amount is not specified, the full amount will be refunded.

    Refunding mobile micropayments

    • For mobile micropayments, only full (not partial) refunds are allowed according to the policy of mobile phone companies.
    • If a refund is requested on a different month from when the payment is made, a full refund is not allowed. For example, a payment made on January 31st is not refundable on February 1st.
  • Refundable amount (checksum)

    Enter the refundable amount. For example, the checksum of a product that costs 10,000 won is 10,000. If this payment has been partially refunded for 1,000 won in the past, the checksum is 9000 for the subsequent refund.

    The checksum is used to check whether the refundable amount is the same between the server and the i'mport server. If they do not match, the refund request will fail. If the checksumis not specified, the verification is not peformed.

    Reason for entering checksum

    checksum is not required, but it is recommended for comparing the refundable amount between the server and the i'mport server.

    For example, consider the case when a partial refund request of 1,000 won for a 10,000 won payment was completed on the i'mport server, but not on the server due to a server or database error. In this case, the checksum of the i'mport server is changed to 9000, but that of the merchant server remains 10,000. If you attempt to make a refund request with checksum(10000), the request will fail because the value does not match the checksum(9000) of the i'mport server.
Request for a refund as follows:
  /* ... Omitted ... */
  app.post('/payments/cancel', async (req, res, next) => {
    try {
      /* Get access token */
      /* ... Omitted ... */
      /* Get payment information */
      const { body } = req;
      const { merchant_uid, reason, cancel_request_amount } = body; // Order ID from the client, reason for refund, refund amount
      Payments.find({ merchant_uid }, async function(err, payment) { 
        /* ... Omitted ... */
        const paymentData = payment[0]; // Save payment information
        const { imp_uid, amount, cancel_amount } = paymentData; // Get imp_uid, amount(paid amount), cancel_amount(total refund amount) from payment information
        const cancelableAmount = amount - cancel_amount; // Refundable amount (= paid amount - total refund amount)
        if (cancelableAmount <= 0) { // If refundable amount is 0
          return res.status(400).json({ message: "This order has already been fully refunded." });
        }
        ...
        /* Call i'mport REST API to request refund */
        const getCancelData = await axios({
          url: "{URL to receive refund request}", // Example: http://www.myservice.com/payments/cancel
          method: "post",
          headers: {
            "Content-Type": "application/json",
            "Authorization": access_token // Access token from i'mport server
          },
          data: {
            reason, // Reason for refund from client
            imp_uid, // Unique key for refund
            amount: cancel_request_amount, // Requested refund amount
            checksum: cancelableAmount // [Recommended] refundable amount
          }
        });
        const { response } = getCancelData.data; // Refund result
        /* Save refund result */
        ...
      });
    } catch (error) {
      res.status(400).send(error);
    }
  });

Additional parameters for virtual account refund

For a virtual account refund, you must sign up for the Virtual Account Special Service provided by each PG.

Virtual account special service

Unlike credit cards, fees for payment/refund are not subject to refund for virtual account refund.

For example, for a 10,000 won payment, the merchant:
  • receives 9700 won (10,000 won - virtual account issuance fee of 300 won) from the PG at the time of payment.
  • pays 10,300 won (10,000 won to be refunded + 300 won remittance fee for the refund account) to the PG company when the payment is refunded.

To prevent confusion that may arise regarding fees for virtual account refund, PGs provide virtual account refunds only to merchants who are signed up for the Virtual Account Special Service.
Since a virtual account transaction involves a one-way payment method, the target account for refund is unknown. Hence, you must specify the following refund account information in addition to the refund amount.
  • refund_holder: Refund account holder's name
  • refund_account: Refund account number
  • refund_bank: Refund account bank code

Virtual account bank code varies by PG

The virtual account bank code differs depending on the PG even for the same bank. Please check the Bank Codes list (required for virtual account refund) at the bottom of the i'mport API Reference guide.

Currently, we are in the process of converting the virtual account bank code into the standard 3-digit code provided by the Korea Financial Telecommunications and Clearings Institute.
Request for a refund of virtual account payment as follows:
  /* ... Omitted ... */
  app.post('/payments/cancel', async (req, res, next) => {
    try {
      /* Get access token */
      /* ... Omitted ... */
      /* Get payment information */
      const { body } = req;
      const { merchant_uid, reason, cancel_request_amount, refund_holder, refund_bank, refund_account } = body; // Order ID from the client, reason for refund, refund amount, virtual accoun info (account holder, account number, bank code
      Payments.find({ merchant_uid }, async function(err, payment) { 
        /* ... Omitted ... */
        const paymentData = payment[0]; // Save payment information
        const { imp_uid, amount, cancel_amount } = paymentData; // Get imp_uid, amount(paid amount), cancel_amount(total refund amount) from payment information
        const cancelableAmount = amount - cancel_amount; // Refundable amount (= paid amount - total refund amount)
        if (cancelableAmount <= 0) { // If refundable amount is 0
          return res.status(400).json({ message: "This order has already been fully refunded." });
        }
        ...
        /* Call i'mport REST API to request refund */
        const getCancelData = await axios({
          url: "https://api.iamport.kr/payments/cancel",
          method: "post",
          headers: {
            "Content-Type": "application/json",
            "Authorization": access_token // Access token from i'mport server
          },
          data: {
            reason, // Reason for refund from client
            imp_uid, // Unique key for refund
            amount: cancel_request_amount, // Requested refund amount
            checksum: cancelableAmount, // [Recommended] refundable amount
            refund_holder, // [required when refunding virtual account payment] refund account holder's name
            refund_bank, // [required for virtual account refund] refund account bank code (e.g. Shinhan Bank is 88 for KG Inicis)
            refund_account // [required for virtual account refund] refund account number
          }
        });
        const { response } = getCancelData.data; // Refund result
        /* Save refund result */
        ...
      });
    } catch (error) {
      res.status(400).send(error);
    }
  });
If the virtual account refund request is successful, the refund amount will be deposited (from PG) into the specified refund account usually in about one working day.
STEP4Save refund result
server-side

After the refund process is complete, save the result in the databse.

Determining the success of a refund request

Even if a response code of 200 (OK) is returned for the REST API (POST https://api.iamport.kr/payments/cancel) request, a non-zero code in the response body means that the refund has failed. You can check the reason for the failure in the message of the body.
  /* ... Omitted ... */
  app.post('/payments/cancel', async (req, res, next) => {
    try {
      /* Get access token */
      /* ... Omitted ... */
      /* Get payment information */
      Payments.find({ merchant_uid }, async function(err, payment) { 
        /* ... Omitted ... */
        /* Call i'mport REST API to request refund */
        /* ... Omitted ... */
        const { response } = getCancelData.data; // Refund result
        /* Save refund result */
        const { merchant_uid } = response; // Get order info from refund response
        Payments.findOneAndUpdate({ merchant_uid }, response, { new: true }, function(err, payment) { // Get and update payment information for the order
          if (err) {
            return res.json(err);
          }
          res.json(payment); // Return refund result to the client
        });
      });
    } catch (error) {
      res.status(400).send(error);
    }
  });
STEP5Handle response from server
client-side

On the page, add the logic to handle the response from the server as follows:
<button onclick="cancelPay()">Request Refund</button>
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script><!-- jQuery CDN --->
<script>
  function cancelPay() {
    jQuery.ajax({
      /* ... Omitted ... */
    }).done(function(result) { // If refund is successful 
      alert("Refund successful");
    }).fail(function(error) { // If refund fails
      alert("Refund failed");
    });
  }
</script>