Preparation / Prerequisite for SNAP
Setup Credentials for SNAP API
Account Setup
You can skip this step if you have created a sandbox account previously.
- Contact our team ([email protected]) to start creating your account. If you're eligible, a dedicated account manager will be assigned to support you every step of the way.
- Your account manager will require ask for some basic informations to setup your merchant account.
- Verify your email.
- You can start accessing your dashboard test account.
List of Credentials for SNAP API:
Following credentials will be automatically generated for you in sandbox dashboard under Menu Settings > API Key:
- Client Key (Will be used as X-Client-Key and X-Partner-ID in request header)
- Client Secret Key (Will be used for encrypting symmetric X-Signature)
- Merchant ID (Will be used for several API request body such as Balance Inquiry)
- Public Key given by Durianpay (Will be used to verify signature from us)
Additionally you need to generate Merchant's Private & Public Key Pair from your end
- Merchant Private Key will be used for encrypting asymmetric X-Signature
- Merchant Public Key will be used by Durianpay to verify signature of request transaction from the merchant
All credentials above will need to be setup for both live and sandbox environment. Live credentials will be given only after merchant 's legal document & functionality test is approved by Durianpay.
Generate Your Private/Public Key Pair
You can refer to following steps to generate private/public key pair from your system.
Generate Private Key
openssl genrsa -out rsa_private_key.pem 2048
Generate Public Key
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
You need to generate different public-private key pair for sandbox mode and live mode
Merchant Key Exchange Procedure
In order to securely exchanging keys between you and Durianpay. Here's following procedure that will need to be adhered:
Sharing your public key to Durianpay
- Please ensure the file of your public key should only be in a compressed format (i.e. file type: .zip or .rar) and the file should be password protected.
- Email your compressed public key to [email protected]
- Email the password of the public key file to [email protected]
- Durianpay team will setup your public key in our system and notify you once it's setup.
Request Header
Each SNAP Request requires specific header value. There are 2 kind of header for requests, to generate B2B Access Token, and Transaction (disbursement, payment, etc)
B2B Access Token Header
Header | Description | Format |
---|---|---|
X-TIMESTAMP | Request timestamp ISO 8601 | datetime |
X-SIGNATURE | Security signature | Base64 encoded string |
X-CLIENT-KEY | Merchant identifier key | String |
Content-Type | Type of data being sent | MIME type |
Transaction Request Header
Each SNAP Request requires specific header value. There are 2 kind of header for requests, to generate B2B Access Token, and Transaction (disbursement, payment, etc)
Header | Description | Format |
---|---|---|
X-TIMESTAMP | Request timestamp | ISO 8601 datetime |
X-SIGNATURE | Security signature | Base64 encoded string |
X-PARTNER-ID | Partner identifier | Alphanumeric string |
X-EXTERNAL-ID | Unique messaging reference identifier generated by merchant and should be unique within the same day | Random Alphanumeric string |
CHANNEL-ID | Channel identifier | Numeric string |
Content-Type | Type of data being sent | MIME type |
Authorization | Authorization token | JWT (Started with 'Bearer ') |
Generate Signature
There are two use kind of signature to be generated, one is to obtain access token, and another is for transaction. Signature for B2B Access Token is encrypted in an asymmetrically way, while for transaction signature is encrypted in a symmetrically way.
Generate Signature To Obtain Access Token (B2B)
You can find the required structure of the string to be encrypted as below:
<X-CLIENT-KEY>|<X-TIMESTAMP> // (on ISO8601 format)
//Example: merchant_client_key|2024-05-13T14:53:06.991+07:00
which then signed using SHA256withRSA with Private/Public Key pair merchant has generated.
X-Signature: aw3o6HM68vJDLO4nxAPgK0it5nd6zik3bUgMzqLiTrIB7w1QbnCLDo/IMVjaYsbPk9s=
Generate Signature For Transaction API
You can find the required structure of the string to be encrypted as below:
<HTTP METHOD> + ":" + <RELATIVE PATH URL> + ":" + <B2B ACCESS TOKEN> + ":" + LowerCase(HexEncode(SHA-256(Minify(<HTTP BODY>)))) + ":" + <X-TIMESTAMP>
// Example: POST:/v1.0/balance-inquiry:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJEdXJpYW4gTW9uZXkiLC :f806c49e8cd175aa9dd5dd8e0a49648c885954d:2024-05-13T15:07:07+07:00
which then encrypted using HMAC-SHA512 with merchant's Secret Key that Durianpay give.
X-Signature: tnNHFw5ZCWjnHFd9UGAB5iDLuwo+89efml8F1CP8vaqut/PfNWac/XLiCkLt3zGHTppZKPxz/PPptqM2alsOFA==
Generate Access Token (B2B)
B2B Access Token will be used for all transactional APIs. The token itself will have 900 seconds (15 minutes) valid time by default.
There is no refresh access token nor the access token will be auto refreshed, so you will need to generate another access token if it has passed its expiry time
curl --location 'https://api.durianpay.id/v1.0/access-token/b2b' \
--header 'X-TIMESTAMP: 2024-05-13T15:32:36.422+07:00' \
--header 'X-SIGNATURE: KLJ9KthaIiZSzPqHUMuJRsKOZtYCqqWd5DhfWqYC7OjZ2Qlu0PvQ2wRQF5vfDWsz/qFYEexOXb47+oQBjMhAz4XRNhXBBfwoWxrGTG8iU8EYSDVfChW0NqAWjWIQolhB4UcHn5SybPFRON3rvAOIQTzuEshUA1PNAln8jOKaRmY=' \
--header 'X-CLIENT-KEY: client_id' \
--header 'Content-Type: application/json' \
--data '{
"grantType": "AUTHORIZATION_CODE"
}'
import requests
url = 'https://api.durianpay.id/v1.0/access-token/b2b'
headers = {
'X-TIMESTAMP': '2024-05-13T15:32:36.422+07:00',
'X-SIGNATURE': 'KLJ9KthaIiZSzPqHUMuJRsKOZtYCqqWd5DhfWqYC7OjZ2Qlu0PvQ2wRQF5vfDWsz/qFYEexOXb47+oQBjMhAz4XRNhXBBfwoWxrGTG8iU8EYSDVfChW0NqAWjWIQolhB4UcHn5SybPFRON3rvAOIQTzuEshUA1PNAln8jOKaRmY=',
'X-CLIENT-KEY': 'client_id',
'Content-Type': 'application/json'
}
data = {
"grantType": "AUTHORIZATION_CODE"
}
response = requests.post(url, headers=headers, json=data)
print(response.text)
OkHttpClient client = new OkHttpClient();
String url = "https://api.durianpay.id/v1.0/access-token/b2b";
String timestamp = "2024-05-13T15:32:36.422+07:00";
String signature = "KLJ9KthaIiZSzPqHUMuJRsKOZtYCqqWd5DhfWqYC7OjZ2Qlu0PvQ2wRQF5vfDWsz/qFYEexOXb47+oQBjMhAz4XRNhXBBfwoWxrGTG8iU8EYSDVfChW0NqAWjWIQolhB4UcHn5SybPFRON3rvAOIQTzuEshUA1PNAln8jOKaRmY=";
String clientKey = "client_id";
String contentType = "application/json";
Gson gson = new Gson();
String jsonBody = gson.toJson(Map.of("grantType", "AUTHORIZATION_CODE"));
RequestBody requestBody = RequestBody.create(MediaType.parse(contentType), jsonBody);
Request request = new Request.Builder()
.url(url)
.addHeader("X-TIMESTAMP", timestamp)
.addHeader("X-SIGNATURE", signature)
.addHeader("X-CLIENT-KEY", clientKey)
.addHeader("Content-Type", contentType)
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
const request = require('request');
const url = 'https://api.durianpay.id/v1.0/access-token/b2b';
const headers = {
'X-TIMESTAMP': '2024-05-13T15:32:36.422+07:00',
'X-SIGNATURE': 'KLJ9KthaIiZSzPqHUMuJRsKOZtYCqqWd5DhfWqYC7OjZ2Qlu0PvQ2wRQF5vfDWsz/qFYEexOXb47+oQBjMhAz4XRNhXBBfwoWxrGTG8iU8EYSDVfChW0NqAWjWIQolhB4UcHn5SybPFRON3rvAOIQTzuEshUA1PNAln8jOKaRmY=',
'X-CLIENT-KEY': 'client_id',
'Content-Type': 'application/json'
};
const data = {
"grantType": "AUTHORIZATION_CODE"
};
request.post({
url: url,
headers: headers,
json: data
}, (error, response, body) => {
if (error) {
console.error('Request error:', error);
} else {
console.log('Response status code:', response.statusCode);
console.log('Response body:', body);
}
});
require 'net/http'
require 'uri'
require 'json'
url = URI.parse('https://api.durianpay.id/v1.0/access-token/b2b')
headers = {
'X-TIMESTAMP' => '2024-05-13T15:32:36.422+07:00',
'X-SIGNATURE' => 'KLJ9KthaIiZSzPqHUMuJRsKOZtYCqqWd5DhfWqYC7OjZ2Qlu0PvQ2wRQF5vfDWsz/qFYEexOXb47+oQBjMhAz4XRNhXBBfwoWxrGTG8iU8EYSDVfChW0NqAWjWIQolhB4UcHn5SybPFRON3rvAOIQTzuEshUA1PNAln8jOKaRmY=',
'X-CLIENT-KEY' => 'client_id',
'Content-Type' => 'application/json'
}
data = { 'grantType' => 'AUTHORIZATION_CODE' }
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url.request_uri, headers)
request.body = data.to_json
response = http.request(request)
puts "Response code: #{response.code}"
puts "Response body: #{response.body}"
{
"responseCode": "2007300",
"responseMessage": "Successful",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJEdXJpYW4gTW9uZXkiLCJleHAiOjE3MTU1OTAwNTYsImlhdCI6MTcxNTU4OTE1NiwibWVyY2",
"tokenType": "BearerToken",
"expiresIn": "2024-05-13 08:47:36.452492159 +0000 UTC"
}
Response Definition
Format
SNAP API has its own standardized Response Code and Response Message for each of endpoint. In general, the response code will consist of 7 digits with HTTP code, Service Code, and Case Code scheme.
When doing request of balance query to https://api.durianpay.id/v1.0/balance-inquiry with the same X-External-ID twice, merchant will receive error with code 4091100 where:
- 409 is HTTP code for Conflict
- 11 is service code for balance inquiry, and
- 00 together with 409 HTTP code indicates same X-External-ID or partnerReferenceNo is being used twice.
Reference
HTTP code used is following the standard defined by RFC 9110
Service Code
Service Code is the code that indicates which service being requested depending of its relative path. For example, B2B Access Token generation has a service code of 73, Balance Inquiry has a service code of 11, etc. Please refer to the table below for list of services implemented by Durianpay.
Table of Service Code
Service Name | Service Code | Relative Path |
---|---|---|
Access Token (B2B) | 73 | /v1.0/access-token/b2b |
Balance Inquiry | 11 | /v1.0/balance-inquiry |
Account Inquiry (External) | 16 | /v1.0/account-inquiry-external |
E-Wallet Account Inquiry | 37 | /v1.0/emoney/account-inquiry |
Transfer Interbank | 18 | /v1.0/transfer-interbank |
Transfer E-Wallet | 38 | /v1.0/emoney/topup |
Transfer Status Inquiry | 36 | /v1.0/transfer/status |
Response Code List
In general, there are two types of response to a request in SNAP, Successful and Failed. Successful response is indicated with HTTP code of 2xx while 4xx and 5xx indicates error (from client side or server/Durianpay side).
Please refer to table below for list of Response Code, Response Message, and its meaning.
Successful Response
HTTP Code | Service Code | Case Code | Response Message | Description |
---|---|---|---|---|
200 | any | 00 | Successful | Successful |
202 | any | 00 | Request In Progress | Transaction still on proces |
Error Response
HTTP Code | Service Code | Case Code | Response Message | Description |
---|---|---|---|---|
400 | any | 00 | Bad Request | General request failed error, including message parsing failed. |
400 | any | 01 | Invalid Field Format {field name} | Invalid format |
400 | any | 02 | Invalid Mandatory Field {field name} | Missing or invalid format on mandatory field |
401 | any | 00 | Unauthorized [reason] | General unauthorized error (No Interface Def, API is Invalid, Oauth Failed, Verify Client Secret Fail, Client Forbidden Access API, Unknown Client, Key not Found, Invalid Signature) |
401 | any | 01 | Invalid Token (B2B) | Token found in request is invalid (Access Token Not Exist, Access Token Expiry) |
401 | any | 03 | Token Not Found (B2B) | Token not found in the system. This occurs on any API that requires token as input parameter |
403 | any | 00 | Transaction Expired | Transaction expired |
403 | any | 18 | Inactive Card/Account/Customer | Indicates inactive account |
404 | any | 01 | Transaction Not Found | Transaction not found |
404 | any | 11 | Invalid Card/Account/Customer [info]/Virtual Account | Card information may be invalid, or the card account may be blacklisted, or Virtual Account number may be invalid. |
409 | any | 00 | Conflict | Cannot use same X-EXTERNAL-ID in same day |
409 | any | 01 | Duplicate partnerReferenceNo | Transaction has previously been processed indicates the same partnerReferenceNo already created |
500 | any | 00 | General Error | General Error |
500 | any | 01 | Internal Server Error | Unknown Internal Server Failure, Please inquiry the status or retry the process again |
504 | any | 00 | Timeout | Timeout from the issue |
Updated about 2 months ago