QRIS API Integration

This section is intended for QRIS partner to integrate with Indodana.

Preparation

To get started, please make sure that you have the MERCHANT_API_KEY and MERCHANT_SECRET_KEY ready.

You also need to make sure that you have access to our staging & production API URLs. The staging API URL is located in https://stg-k-api.indodanafinance.com. The production API URL is located in https://api.indodanafinance.co.id.

Here are preparation list you might required during this integration:

Authorizing Your Request

The important thing that you need to know when you want to perform a direct integration is the API authentication. By default, all of Indodana Merchant API endpoints are secured and you will need to authenticate your request. To be able to execute the API successfully, you will need to authorize yourself by presenting a valid Authorization header in your HTTP request.

To generate the Authorization header, please refer to the following snippets of code.

const crypto = require('crypto')

const apiKey = '<MERCHANT_API_KEY>'
const secret = '<MERCHANT_SECRET_KEY>'
const nonce = Math.floor(Date.now() / 1000);

const hmac = crypto.createHmac('sha256', secret);
  
var generateAuthorizationHeader = function () {
  const content = `${apiKey}:${nonce}`;
  
  var signature = null
  var authorization = ''
  hmac.on('readable', () => {
    const data = hmac.read();
    if (data) {
      signature = data.toString('hex')
      authorization = `Bearer ${apiKey}:${nonce}:${signature}`
    }
  });
  hmac.write(content);
  hmac.end();
  
  console.log('HTTP Authorization Header')
  console.log('Authorization: ' + authorization)
  
  return authorization
}
generateAuthorizationHeader()

As you can see, the content of Authorization header is generated by creating a sha256 hash of apiKey:nonce. The sha256 hash algorithm should take the MERCHANT_SECRET_KEY as the secret key. The example result of the above snippets when you try to ran the code should be as follow

HTTP Authorization Header
Authorization: Bearer sudahlahbossque:1562328997:091e834ec466df5e31b87077134e9d44ac428f515b78055673a2cfa65dbd5956

After you have successfully created the Authorization header, then you can proceed on the next step to develop the integration according to scenario:

  • Check transaction status

  • Refund / cancel transaction

Purchase Transaction Checkout

POST https://{baseUrl}/v3/checkout_url

This checkout API v3 enables QuickPay integration. You need to pass valid x-user-token on the header so user can enjoy the easy & fast checkout experience. Don't worry if you don't have the Indodana QuickPay user token for current checkout user in the first place, our checkout page already handle the flow and you need to store the token every time Indodana sends the token via callback to your system and use this token for subsequent transaction.

* means the parameter is required

Headers

Request Body

{
    "merchantUserKey": "<MERCHANT-USER-KEY>",
    "transactionDetails": {
      "merchantOrderId": "<MERCHANT-ORDER-ID>",
      "amount": 45000,
      "items": [
        {
          "id": "<MERCHANT-ITEM-ID>",
          "name": "QRIS",
          "category": "<ITEM-CATEGORY>",
          "price": 45000,
          "url": "http://merchant.com/qris",
          "imageUrl": "http://merchant.com/qris/cover.jpg",
          "type": "offline-store",
          "quantity": 1,
          "parentType": "SELLER",
          "parentId": "<MERCHANT-SELLER-ID>"
        }
      ]
    },
    "customerDetails": {
      "firstName": "First",
      "lastName": "Last",
      "email": "first.last@gmail.com",
      "phone": "081212345678"
    },
    "sellers": [
      {
        "id": "<MERCHANT-SELLER-ID>",
        "name": "Penjual iPhone",
        "url": "https://merchant.com/shop",
        "sellerIdNumber": "<MERCHANT-SELLER-ID-NUMBER>",
        "email": "seller@merchant.com",
        "officialSeller": false,
        "address": {
          "firstName": "Merchant",
          "lastName": "Seller",
          "address": "Kelapa Gading",
          "city": "Jakarta Utara",
          "postalCode": "11240",
          "phone": "081812345678",
          "countryCode": "IDN"
        }
      }
    ],
    "billingAddress": {
      "firstName": "First",
      "lastName": "Last",
      "address": "Tomang",
      "city": "Jakarta Barat",
      "postalCode": "11430",
      "phone": "081987654321",
      "countryCode": "IDN"
    },
    "shippingAddress": {
      "firstName": "First",
      "lastName": "Last",
      "address": "Tomang",
      "city": "Jakarta Barat",
      "postalCode": "11430",
      "phone": "081987654321",
      "countryCode": "IDN"
    },
    "metadata": {
      "location": {
        "lat": 6.123391,
        "lng": -107.900012
      },
      "scanQrisLocation": {
        "lat": 6.123391,
        "lng": -107.900012
      },
      "qrisContent": "<QRIS-CONTENT>",
      "transactionPurpose": "CONSUMPTIVE"
    },
    "paymentType": "3_months",
    "approvedNotificationUrl": "https://payment-notification.merchant.com/transaction-approval-handler",
    "cancellationRedirectUrl": "http://merchant.com/phone/iphone-6s",
    "backToStoreUrl": "http://merchant.com/phone/iphone-6s",
    "expirationAt": "2019-12-31T18:00:00+07:00"
}

Object Type References

For object specification of Transaction, Customer, Seller, Address, and Metadata can refer to this API reference.

QRIS Transaction Metadata Callback

To be called by Merchant Partner after a successful transaction to update Indodana's transaction metadata.

const Promise = require('bluebird')
const superagent = Promise.promisifyAll(require('superagent'));
const path = require('path')

// Variable authorization should contain value of : 
//    'Bearer <api-key>:<nonce>:<signature>'
var authorization = generateAuthorizationHeader()

var baseUrl = "https://stg-k-api.indodanafinance.com/chermes/merchant"
var endpoint = `${baseUrl}/v1/transactions/qris/metadata`
  
var payload = {
  "merchantOrderId": "<MERCHANT-ORDER-ID>",
  "paidAt": "2024-01-01T13:00:00.000+07:00",
  "acquirer": "Indodana",
  "customerPan": "123456789",
  "referenceNumber": "REF-123456789"
}

superagent
    .post(endpoint)
    .set('Authorization', authorization)
    .send(payload)
    .endAsync()
    .then(response => {
      // Do something with response.body
      console.log(response.body)
    })
    .catch(err => {
      // Handle error
      console.log(err.response.text)
    });

The sample responses (successful & error responses) that are possibly returned by the API can be found in the section of API Reference > QRIS Transaction Metadata Callback.

QRIS Check Transaction Status

In times of operating, there are several times that you may need to provide / check the status of the transaction (e.g: at the time of customer inquiry, or when you need to synchronize the state of the transaction). Indodana has provided an API endpoint for the merchants to easily check their transaction status.

There are several transaction statuses that the merchant needs to be aware of:

  • INITIATED - Transaction has been created by Indodana

  • EXPIRED - Customer failed to complete the transaction

  • PAID - Transaction passed the fraud detection and is confirmed to-be-paid by Indodana

  • REJECTED - If the transactions is identified as fraudulent transactions or in blacklisted merchants

It's really simple to check your transaction status. This is the code that will be required to perform the transaction status check.

const Promise = require('bluebird')
const superagent = Promise.promisifyAll(require('superagent'));
const path = require('path')

// Variable authorization should contain value of : 
//    'Bearer <api-key>:<nonce>:<signature>'
var authorization = generateAuthorizationHeader()

var baseUrl = "https://stg-k-api.indodanafinance.com/chermes/merchant"
var endpoint = `${baseUrl}/v1/transactions/qris/check_status`
  
var payload = {
  "merchantOrderId": "<MERCHANT-ORDER-ID>"
}

superagent
    .get(endpoint)
    .set('Authorization', authorization)
    .query(payload)
    .send()
    .endAsync()
    .then(response => {
      // Do something with response.body
      console.log(response.body)
    })
    .catch(err => {
      // Handle error
      console.log(err.response.text)
    });

The sample responses (successful & error responses) that are possibly returned by the API can be found in the section of API Reference > Sample API Request & Response > QRIS Check Transaction Status.

Purchase Transaction Cancellation / Refund

Merchant is able to void / cancel a completed transaction. Note that, there are two types of cancellation. The first is FULL CANCELLATION - when the amount that is posted in the Refund / Cancellation request is the same as the transaction amount. The second is PARTIAL CANCELLATION - when the amount that is posted in the Refund / Cancellation request is different / less than the transaction amount.

It is required for merchant to include the cancellation reason, the preferred cancellation reason that given to Indodana are:

  • Expired - when the seller does not give a response for the transaction after the predetermined time

  • Out of Stock - when the seller does not give a response for the transaction after the predetermined time

  • Items Returned - when the customer has cancelled the transaction because there was an issue with the item

  • Others - any other reason beside the three above

Full Cancellation

const Promise = require('bluebird')
const superagent = Promise.promisifyAll(require('superagent'));
const path = require('path')

// Variable authorization should contain value of : 
//    'Bearer <api-key>:<nonce>:<signature>'
var authorization = generateAuthorizationHeader()

var baseUrl = "https://stg-k-api.indodanafinance.com/chermes/merchant"
var endpoint = `${baseUrl}/v3/order_cancellation`
  
var payload = {
  "refundId":"<MERCHANT-REFUND-ID>",
  "merchantOrderId":"<MERCHANT-ORDER-ID>",
  "cancellationAmount" : 100000,
  "cancellationReason":"Out of stock",
  "cancelledBy":"<SELLER/CUSTOMER>",
  "cancellationDate":"2019-09-12T18:18:18+07:00"
}

superagent
    .post(endpoint)
    .set('Authorization', authorization)
    .send(payload)
    .endAsync()
    .then(response => {
      // Do something with response.body
      console.log(response.body)
    })
    .catch(err => {
      // Handle error
      console.log(err.response.text)
    });

The sample responses (successful & error responses) that are possibly returned by the API can be found in the section of API Reference > Sample API Request & Response > Refund / Purchase Transaction Cancellation / Refund.

Partial Cancellation

const Promise = require('bluebird')
const superagent = Promise.promisifyAll(require('superagent'));
const path = require('path')

// Variable authorization should contain value of : 
//    'Bearer <api-key>:<nonce>:<signature>'
var authorization = generateAuthorizationHeader()

var baseUrl = "https://stg-k-api.indodanafinance.com/chermes/merchant"
var endpoint = `${baseUrl}/v3/order_cancellation`
  
var payload = {
  "refundId":"<MERCHANT-REFUND-ID>",
  "merchantOrderId":"<MERCHANT-ORDER-ID>",
  "cancellationAmount" : 50000,  
  "cancellationReason":"Out of stock",
  "cancelledBy":"<SELLER/CUSTOMER>",
  "cancellationDate":"2019-09-12T18:18:18+07:00"
}

superagent
    .post(endpoint)
    .set('Authorization', authorization)
    .send(payload)
    .endAsync()
    .then(response => {
      // Do something with response.body
      console.log(response.body)
    })
    .catch(err => {
      // Handle error
      console.log(err.response.text)
    });

The sample responses (successful & error responses) that are possibly returned by the API can be found in the section of API Reference > Sample API Request & Response > Refund / Purchase Transaction Cancellation / Refund.

Last updated