Overview
Base URL: https://{gateway-host}/api/pay
All API requests use the POST method. The request and response body are JSON format {"payload": "<AES encrypted Base64 string>"}. Communication is secured by AES encryption + RSA signature .
Security Protocol
Keys
Key Type Description
AES Key 128-bit key (Base64 encoded), provided by the platform. Used for encrypting / decrypting the request and response payload.
Merchant RSA Key Pair 2048-bit RSA. Merchant keeps the private key, uploads the public key to the platform. Used to sign requests.
Platform RSA Key Pair 2048-bit RSA. Platform keeps the private key, provides the public key to the merchant. Used to sign responses.
Headers (all required)
Header Description Example
merchantNoMerchant number M20240101001
versionAPI version 1.0
requestIdUnique request ID (idempotency) req_abc123
timestampCurrent time in ms (within 10 s, not future) 1700000000000
signRSA signature Base64 string
Content-TypeFixed application/json
Body
The request body is JSON format. The encrypted ciphertext is placed in the payload field.
{"payload": "<AES encrypted Base64 string>"}
Encryption Process
Serialize business parameters to JSON string (the "plaintext")
AES encrypt the plaintext → encrypted string (Base64)
Concatenate sign string: merchantNo|version|requestId|timestamp|<encrypted string>
RSA sign with merchant private key (SHA256withRSA) → sign (Base64)
Send request, body is {"payload": "<encrypted string>"}
AES Details
Algorithm AES/CBC/PKCS5Padding
Key 128-bit, Base64 encoded
IV First 16 bytes of the AES key
Headers
Header Description
merchantNoEcho merchant number
versionAPI version
requestIdEcho request ID
timestampResponse time in ms
codeResponse code (see below)
messageResponse message
signPlatform RSA signature
Body
The response body is JSON format. The encrypted ciphertext is in the payload field. Empty string if the request failed.
{"payload": "<AES encrypted Base64 string>"}
Decryption Process
Extract the payload value from the response body JSON
Verify signature: concatenate merchantNo|version|requestId|timestamp|code|message|<payload value>, verify with platform public key
If code is 200, AES decrypt the payload value to get business response JSON
If code is not 200, the request failed — check code and message
Response Codes
Code Message Description
200succeed Success
400payload decrypt failed AES decryption failed
401merchant not supported Merchant not found, disabled, or keys not configured
403the ip is not whitelisted IP not in whitelist
407verify sign failed RSA signature verification failed
417parameter is null or invalid Missing or invalid parameter (includes timestamp expired)
500system error Internal server error
Create Payment Order
POST /api/pay/in
Creates a collection (pay-in) order and returns a payment URL.
Request Parameters (plaintext JSON before encryption)
Field Type Required Description
outTradeNostring Yes Merchant's unique order number
amountnumber Yes Payment amount
currencystring Yes Currency code (see enum below)
payTypestring Yes Payment type (see enum below)
notifyUrlstring No Async callback URL for payment result
returnUrlstring No Redirect URL after payment
customerNamestring No Customer name
customerPhonestring No Customer phone
customerEmailstring No Customer email
cardInfoobject No Card information (see below)
cardInfo Object
Field Type Required Description
numberstring No Card number
expireMonthstring No Expiration month (e.g. "01")
expireYearstring No Expiration year (e.g. "2026")
cvvstring No CVV / CVC security code
tokenstring No Card token (for tokenized cards)
namestring No Cardholder name
payType Enum
Value Description
CREDIT_CARDCredit card payment
currency Enum
Response (decrypted payload)
Field Type Description
tradeIdstring Platform trade ID
outTradeNostring Merchant order number (echo)
tradeStatusstring Order status: CREATE / PAYING
payUrlstring Payment page URL (redirect customer here)
channelOrderNostring Channel order number
failReasonstring Failure reason (if failed)
Example
Request
Response
{
"outTradeNo": "ORD20240101001",
"amount": 500.00,
"currency": "EUR",
"payType": "CREDIT_CARD",
"notifyUrl": "https://merchant.com/notify",
"returnUrl": "https://merchant.com/return",
"customerName": "John",
"customerPhone": "9876543210",
"customerEmail": "john@example.com",
"cardInfo": {
"number": "4111111111111111",
"expireMonth": "12",
"expireYear": "2026",
"cvv": "123",
"name": "John"
}
}
{
"tradeId": "T20240101120000001",
"outTradeNo": "ORD20240101001",
"tradeStatus": "PAYING",
"payUrl": "https://pay.example.com/cashier?token=xxx",
"channelOrderNo": "CH20240101001"
}
Query Order Status
POST /api/pay/query
Query the status of an existing order. Use either tradeId or outTradeNo.
Request Parameters
Field Type Required Description
tradeIdstring Conditional Platform trade ID (one of two required)
outTradeNostring Conditional Merchant order number (one of two required)
Response
Field Type Description
tradeIdstring Platform trade ID
outTradeNostring Merchant order number
tradeStatusstring Order status (see below)
tradeAmountnumber Order amount
merchantAmountnumber Merchant settlement amount (after fee)
currencystring Currency code
Order Status Values
Status Description
CREATE Order created, not yet paid
PAYING Payment in progress
SUCCESS Payment successful
FAIL Payment failed
TIMEOUT Payment timed out
CANCEL Order cancelled
Example
Request
Response
{
"outTradeNo": "ORD20240101001"
}
{
"tradeId": "T20240101120000001",
"outTradeNo": "ORD20240101001",
"tradeStatus": "SUCCESS",
"tradeAmount": 500.00,
"merchantAmount": 485.00,
"currency": "INR"
}
Query Account Balance
POST /api/pay/balance
Query the merchant's account balances. Optionally filter by currency.
Request Parameters
Field Type Required Description
currencystring No Currency code. If omitted, returns all currencies.
Response
Field Type Description
collectionarray Pay-in account balances (funds received from customers)
paymentarray Pay-out account balances (funds for disbursement)
Each item in the array:
Field Type Description
currencystring Currency code
totalAmountnumber Total balance
frozenAmountnumber Frozen amount
availableAmountnumber Available balance
Example
Request (single currency)
Response
All currencies
{
"collection": [
{
"currency": "INR",
"totalAmount": 100000.00,
"frozenAmount": 5000.00,
"availableAmount": 95000.00
}
],
"payment": [
{
"currency": "INR",
"totalAmount": 20000.00,
"frozenAmount": 0.00,
"availableAmount": 20000.00
}
]
}
// Request
{}
// Response
{
"collection": [
{ "currency": "INR", "totalAmount": 100000.00, "frozenAmount": 5000.00, "availableAmount": 95000.00 },
{ "currency": "USDT", "totalAmount": 5000.00, "frozenAmount": 0.00, "availableAmount": 5000.00 }
],
"payment": []
}
Async Notification (Callback)
When a payment reaches a terminal state (SUCCESS / FAIL ), the platform sends an async POST notification to the notifyUrl provided during order creation.
Notification Body (JSON)
Field Type Description
tradeIdstring Platform trade ID
outTradeNostring Merchant order number
tradeStatusstring SUCCESS or FAIL
tradeAmountnumber Order amount
merchantAmountnumber Merchant settlement amount
currencystring Currency code
Rules
The platform retries failed notifications with increasing delays (configurable).
Merchant must respond with plain text SUCCESS (case-insensitive) to acknowledge.
Any other response or timeout (5 s) triggers a retry.
Example
Notification Body
Acknowledgement
// POST to notifyUrl
{
"tradeId": "T20240101120000001",
"outTradeNo": "ORD20240101001",
"tradeStatus": "SUCCESS",
"tradeAmount": 500.00,
"merchantAmount": 485.00,
"currency": "INR"
}
⚠ Important: Do not rely solely on notifications. Always call the Query Order Status API to confirm the final order status before delivering goods / services.
Code Examples
Java
PHP
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
public class OpenApiClient {
/** AES/CBC/PKCS5Padding encrypt */
public static String aesEncrypt(String plainText, String aesKeyBase64) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(aesKeyBase64);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(keyBytes, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
return Base64.getEncoder().encodeToString(
cipher.doFinal(plainText.getBytes("UTF-8")));
}
/** AES/CBC/PKCS5Padding decrypt */
public static String aesDecrypt(String cipherBase64, String aesKeyBase64) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(aesKeyBase64);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(keyBytes, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
return new String(
cipher.doFinal(Base64.getDecoder().decode(cipherBase64)), "UTF-8");
}
/** SHA256withRSA sign */
public static String rsaSign(String data, String privateKeyPem) throws Exception {
String key = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] keyBytes = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privKey);
signature.update(data.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(signature.sign());
}
/** Build and send API request */
public static void example() throws Exception {
String merchantNo = "M20240101001";
String aesKey = "your-aes-key-base64";
String merchantPK = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----";
// 1. Business parameters
String bizJson = "{\"outTradeNo\":\"ORD001\",\"amount\":500,\"currency\":\"EUR\"}";
// 2. AES encrypt
String encryptedBody = aesEncrypt(bizJson, aesKey);
// 3. Build sign string
String requestId = "req_" + System.currentTimeMillis();
String timestamp = String.valueOf(System.currentTimeMillis());
String signData = String.join("|",
merchantNo, "1.0", requestId, timestamp, encryptedBody);
// 4. RSA sign
String sign = rsaSign(signData, merchantPK);
// 5. Send HTTP POST with headers; Body is {"payload": "<encrypted string>"}
String requestBody = "{\"payload\":\"" + encryptedBody + "\"}";
// Send requestBody as the HTTP POST body
}
}
<?php
function aesEncrypt(string $plainText, string $aesKeyBase64): string {
$key = base64_decode($aesKeyBase64);
$iv = substr($key, 0, 16);
$encrypted = openssl_encrypt($plainText, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted);
}
function aesDecrypt(string $cipherBase64, string $aesKeyBase64): string {
$key = base64_decode($aesKeyBase64);
$iv = substr($key, 0, 16);
return openssl_decrypt(base64_decode($cipherBase64), 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
}
function rsaSign(string $data, string $privateKeyPem): string {
$privKey = openssl_pkey_get_private($privateKeyPem);
openssl_sign($data, $signature, $privKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
// --- Example: Create Payment Order ---
$merchantNo = 'M20240101001';
$aesKey = 'your-aes-key-base64';
$privateKey = file_get_contents('/path/to/merchant_private_key.pem');
$bizJson = json_encode([
'outTradeNo' => 'ORD001',
'amount' => 500,
'currency' => 'EUR',
'payType' => 'CREDIT_CARD',
]);
$encryptedBody = aesEncrypt($bizJson, $aesKey);
$requestId = 'req_' . time();
$timestamp = (string)(time() * 1000);
$signData = implode('|', [$merchantNo, '1.0', $requestId, $timestamp, $encryptedBody]);
$sign = rsaSign($signData, $privateKey);
$ch = curl_init('https://gateway-host/api/pay/in');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"merchantNo: $merchantNo",
'version: 1.0',
"requestId: $requestId",
"timestamp: $timestamp",
"sign: $sign",
],
CURLOPT_POSTFIELDS => json_encode(['payload' => $encryptedBody]),
]);
$response = curl_exec($ch);
curl_close($ch);