Merchant API

v1.0

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 TypeDescription
AES Key128-bit key (Base64 encoded), provided by the platform. Used for encrypting / decrypting the request and response payload.
Merchant RSA Key Pair2048-bit RSA. Merchant keeps the private key, uploads the public key to the platform. Used to sign requests.
Platform RSA Key Pair2048-bit RSA. Platform keeps the private key, provides the public key to the merchant. Used to sign responses.

Request Format

Headers (all required)

HeaderDescriptionExample
merchantNoMerchant numberM20240101001
versionAPI version1.0
requestIdUnique request ID (idempotency)req_abc123
timestampCurrent time in ms (within 10 s, not future)1700000000000
signRSA signatureBase64 string
Content-TypeFixedapplication/json

Body

The request body is JSON format. The encrypted ciphertext is placed in the payload field.

{"payload": "<AES encrypted Base64 string>"}

Encryption Process

  1. Serialize business parameters to JSON string (the "plaintext")
  2. AES encrypt the plaintext → encrypted string (Base64)
  3. Concatenate sign string: merchantNo|version|requestId|timestamp|<encrypted string>
  4. RSA sign with merchant private key (SHA256withRSA) → sign (Base64)
  5. Send request, body is {"payload": "<encrypted string>"}

AES Details

AlgorithmAES/CBC/PKCS5Padding
Key128-bit, Base64 encoded
IVFirst 16 bytes of the AES key

Response Format

Headers

HeaderDescription
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

  1. Extract the payload value from the response body JSON
  2. Verify signature: concatenate merchantNo|version|requestId|timestamp|code|message|<payload value>, verify with platform public key
  3. If code is 200, AES decrypt the payload value to get business response JSON
  4. If code is not 200, the request failed — check code and message

Response Codes

CodeMessageDescription
200succeedSuccess
400payload decrypt failedAES decryption failed
401merchant not supportedMerchant not found, disabled, or keys not configured
403the ip is not whitelistedIP not in whitelist
407verify sign failedRSA signature verification failed
417parameter is null or invalidMissing or invalid parameter (includes timestamp expired)
500system errorInternal 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)

FieldTypeRequiredDescription
outTradeNostringYesMerchant's unique order number
amountnumberYesPayment amount
currencystringYesCurrency code (see enum below)
payTypestringYesPayment type (see enum below)
notifyUrlstringNoAsync callback URL for payment result
returnUrlstringNoRedirect URL after payment
customerNamestringNoCustomer name
customerPhonestringNoCustomer phone
customerEmailstringNoCustomer email
cardInfoobjectNoCard information (see below)

cardInfo Object

FieldTypeRequiredDescription
numberstringNoCard number
expireMonthstringNoExpiration month (e.g. "01")
expireYearstringNoExpiration year (e.g. "2026")
cvvstringNoCVV / CVC security code
tokenstringNoCard token (for tokenized cards)
namestringNoCardholder name

payType Enum

ValueDescription
CREDIT_CARDCredit card payment

currency Enum

ValueDescription
EUREuro

Response (decrypted payload)

FieldTypeDescription
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number (echo)
tradeStatusstringOrder status: CREATE / PAYING
payUrlstringPayment page URL (redirect customer here)
channelOrderNostringChannel order number
failReasonstringFailure reason (if failed)

Example

{
  "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

FieldTypeRequiredDescription
tradeIdstringConditionalPlatform trade ID (one of two required)
outTradeNostringConditionalMerchant order number (one of two required)

Response

FieldTypeDescription
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number
tradeStatusstringOrder status (see below)
tradeAmountnumberOrder amount
merchantAmountnumberMerchant settlement amount (after fee)
currencystringCurrency code

Order Status Values

StatusDescription
CREATEOrder created, not yet paid
PAYINGPayment in progress
SUCCESSPayment successful
FAILPayment failed
TIMEOUTPayment timed out
CANCELOrder cancelled

Example

{
  "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

FieldTypeRequiredDescription
currencystringNoCurrency code. If omitted, returns all currencies.

Response

FieldTypeDescription
collectionarrayPay-in account balances (funds received from customers)
paymentarrayPay-out account balances (funds for disbursement)

Each item in the array:

FieldTypeDescription
currencystringCurrency code
totalAmountnumberTotal balance
frozenAmountnumberFrozen amount
availableAmountnumberAvailable balance

Example

{
  "currency": "INR"
}
{
  "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)

FieldTypeDescription
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number
tradeStatusstringSUCCESS or FAIL
tradeAmountnumberOrder amount
merchantAmountnumberMerchant settlement amount
currencystringCurrency code

Rules

Example

// POST to notifyUrl
{
  "tradeId": "T20240101120000001",
  "outTradeNo": "ORD20240101001",
  "tradeStatus": "SUCCESS",
  "tradeAmount": 500.00,
  "merchantAmount": 485.00,
  "currency": "INR"
}
SUCCESS
⚠ 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

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);