This section is intended for merchants that would like to create their own integration (which we assume not using the provided SDK that Indodana has provided).
Overview
Indodana provides a simple end-to-end checkout method for your e-commerce site. This checkout method allows your customer to make payment for their purchase with easy and secure verification installment process. By only inputting their mobile number and security PIN, they can finalize the purchase by choosing the tenure options provided for this service.
This is how Indodana Checkout process works:
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 sandbox & production API URLs. The sandbox API URL is located in https://sandbox01-api.indodana.com.
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 Authorizationheader in your HTTP request.
To generate the Authorization header, please refer to the following snippets of code.
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
After you have successfully created the Authorization header, then you can proceed on the next step to develop the integration according to scenario:
Get installment options
Checkout transaction
Handling transaction confirmation notification
Check transaction status
Refund / cancel transaction
Get Installment Options
This scenario occurs when the customer needs to see the available installment option for a certain transaction. The available option will be determined by amount of the transaction. This is the snippet of code that is required to request payment simulation from Indodana Merchant API.
constPromise=require('bluebird')constsuperagent=Promise.promisifyAll(require('superagent'));constpath=require('path')// Variable authorization should contain value of : // 'Bearer <api-key>:<nonce>:<signature>'var authorization =generateAuthorizationHeader()var baseUrl ="https://sandbox01-api.indodana.com/chermes/merchant"var endpoint =`${baseUrl}/v2/payment_calculation`var payload = {"amount":4500000,"items": [ {"id":"<MERCHANT-ITEM-ID>","name":"iPhone 6S","category":"<ITEM-CATEGORY>","price":4500000,"url":"http://merchant.com/phone/iphone-6s","imageUrl":"http://merchant.com/phone/iphone-6s/cover.jpg","type":"Mobile Phone","quantity":1,"parentType":"SELLER","parentId":"<MERCHANT-SELLER-ID>" } ]}superagent.post(endpoint).set('Authorization', authorization).send(payload).endAsync().then(response => {// Do something with response.bodyconsole.log(response.body) }).catch(err => {// Handle errorconsole.log(err.response.text) });
As you can see from the snippet above, the first step is always to generate an Authorization header. Once you have the Authorization signature, you will need to send a POST HTTP request with Authorization header set to the desired endpoint which hosts the payment simulation API. In our case, it is pointed to sandboxhttps://sandbox01-api.indodana.com/chermes/merchant/v2/payment_calculations. After you have executed the POST HTTP request, you will need to verify whether the request has been completed successfully or not. 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 > Get Installment Options.
Normally after you have successfully executed the get installment options request, you will need to show the available installment options to the customer on your website / mobile app.
Purchase Transaction Request
This scenario occurs after the customer successfully chose their preferred installment options. You will get the ID of the installment option such as 30_days, 3_months, 6_months, and 12_months, then you will need to perform the checkout process. This is the snippet of code that is required to perform the checkout process from Indodana Merchant API. You will need to prepare a unique merchantOrderId that Indodana will use as an identifier to notify the completion of the purchase.
Same with the installment option request, you will still need to generate an Authorization header. Once you have the Authorization signature, you will need to send a POST HTTP request with Authorization header set to the desired endpoint which hosts the payment simulation API. In our case, it is pointed to sandboxhttps://sandbox01-api.indodana.com/chermes/merchant/v2/checkout_url.
After you have successfully executed the POST HTTP request, supposedly you will get a response object containing a redirection URL (field redirectUrl). The redirection URL, when it's accessed, would contain a login page that can be used by the customer to log in to their Indodana account using their Phone Number & PIN. 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 > Purchase Transaction Checkout.
This is the screenshot of the login page that will be presented to the customer.
Please be aware that in our current workflow, we recommend the merchant to ensure that the customer would already have a Credit Limit Approval from Indodana before they access the redirection URL.
Once the customer login using their phone number and PIN, they will be presented to the transaction review page. The customer will be able to choose different tenures for their installment and finally confirm their purchase. The credit limit of the customer will be deducted once they authorized the purchase.
Handling Transaction Confirmation Notification
The next scenario that merchant needs to handle is the transaction confirmation notification. Once the customer has confirmed the purchase, Indodana will notify the merchant by calling the endpoint specified in the approvedNotificationUrl field of the Checkout Transaction Request Payload.
In this scenario, merchant will need to ensure that the endpoint that is specified in approvedNotificationUrl is valid and accessible. You will normally required to create an HTTP server that will receive the payload from Indodana and then handle the subsequent transaction flow.
Below is an example code which implements the server that you need to develop to handle the transaction confirmation. We assume that this code will be served through endpoint : https://payment-notification.merchant.com/transaction-approval-handler
It is recommended for you to be able to monitor and keep the log of the notification in your system for cross-checking / debugging whenever any issue happens.
We also encourage you to design an idempotent operation when you handle the transaction. Indodana will make sure that you acknowledge once that you have received a notification. If you fail to acknowledge, Indodana will keep sending the notification to you repeatedly until you successfully acknowledge by sending the proper response.
In order to validate that the notification is coming from Indodana, we encourage you to validate the request by checking the signature sent from Indodana in the authorization header. Please check code below for the example
constexpress=require('express');constbodyParser=require('body-parser');constcrypto=require('crypto');const_=require('lodash');// Activate JSON body parsingconstapp=express();app.use(bodyParser.json());/** * Function to validate authorization header from Indodana. * It will return true if the authorization header is valid, otherwise will return false * * @param{string} authorizationHeader */constvalidateAuthorizationHeader= (authorizationHeader) => {// Separate Auth Token from Bearer// authorizationHeader = Bearer <MERCHANT-API-KEY>:<NONCE>:<AUTHORIZATION-SIGNATURE>// authorizationValue = <MERCHANT-API-KEY>:<NONCE>:<AUTHORIZATION-SIGNATURE>constauthorizationValue=_.chain(authorizationHeader).split('Bearer').last().trim().value();// Header format: <MERCHANT-API-KEY>:<NONCE>:<AUTHORIZATION-SIGNATURE>"constauthorizationData=authorizationValue.split(':');constapiKey= authorizationData[0];constnonce= authorizationData[1];constindodanaSignature= authorizationData[2];constselfSignature=generateSelfSignature({ apiKey:'<MERCHANT_API_KEY>', apiSecret:'<MERCHANT_API_SECRET>', nonce: nonce });return selfSignature === indodanaSignature;};/** * Function to generate signature to validate Indodana signature * * @param{Object} params * @param{string} params.apiKey * @param{string} params.apiSecret * @param{number} params.nonce */constgenerateSelfSignature= ({ apiKey, apiSecret, nonce }) => {consthmac=crypto.createHmac('sha256', apiSecret);constcontent=`${apiKey}:${nonce}`;let signature =null;hmac.on('readable', () => {constdata=hmac.read();if (data) { signature =data.toString('hex'); } });hmac.write(content);hmac.end();return signature;};/** * Method to handle Indodana transaction confirmation notification * Indodana expect this method to response with status "OK" if transaction can be processed, * or "REJECT" if the transaction cannot be processed * * Indodana might hit this API multiple times, so please make sure this API is idempotent. */app.post('/transaction-confirmation-handler', (req, res) => {// Do something with the confirmed transaction// Normally you will need to mark that the transaction is paid// and proceed to the issuance / completing the transaction// This is example of the POST request that will be sent by Indodana// Headers:// {// Authorization: "Bearer <MERCHANT-API-KEY>:<NONCE>:<AUTHORIZATION-SIGNATURE>"// }//// Body:// {// "amount": 3500000.00,// "paymentType": "3_months",// "transactionStatus": "INITIATED" // Either "PAID" or "REJECTED",// "merchantOrderId": "<MERCHANT-ORDER-ID>",// "message": "Transaction status is processed",// "transactionTime": "2019-09-12T18:18:18+07:00"// "transactionId": "<INDODANA-TRANSACTION-ID>",// }// Validate request, make sure it is from IndodanaconstheadersValidationResult=validateAuthorizationHeader(req.headers.authorization);if (headersValidationResult ===true) {var notification =req.body;// Do something with the notificationconsole.log(notification);// This is the ACK response that you will need to send to Indodana// to notify Indodana that you have successfully received the transaction confirmationconstresponse= { status:'OK', message:'Confirm message from merchant if any' };returnres.send(JSON.stringify(response)); } else {constresponse= { status:'REJECT', message:'Signature does not match' };returnres.send(JSON.stringify(response)); }});
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 forwarded to Indodana
EXPIRED - Customer failed to complete the transaction
PAID - Transaction passed the fraud detection and is confirmed to-be-paid by Indodana
PROCESSED - Transaction has been verified by customer and forwarded to merchant
CANCELLED - Merchant cancelled the transactions / refund the transactions
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.
constPromise=require('bluebird')constsuperagent=Promise.promisifyAll(require('superagent'));constpath=require('path')// Variable authorization should contain value of : // 'Bearer <api-key>:<nonce>:<signature>'var authorization =generateAuthorizationHeader()var baseUrl ="https://sandbox01-api.indodana.com/chermes/merchant"var endpoint =`${baseUrl}/v1/transactions/check_status`var payload = {"merchantOrderId":"<MERCHANT-ORDER-ID>"}superagent.get(endpoint).set('Authorization', authorization).query(payload).send().endAsync().then(response => {// Do something with response.bodyconsole.log(response.body) }).catch(err => {// Handle errorconsole.log(err.response.text) });
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
constPromise=require('bluebird')constsuperagent=Promise.promisifyAll(require('superagent'));constpath=require('path')// Variable authorization should contain value of : // 'Bearer <api-key>:<nonce>:<signature>'var authorization =generateAuthorizationHeader()var baseUrl ="https://sandbox01-api.indodana.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.bodyconsole.log(response.body) }).catch(err => {// Handle errorconsole.log(err.response.text) });