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.

All JSON field names use lower camel case. Enum values must be sent exactly as documented, for example CREDIT_CARD and LOCAL.


Security Protocol

Keys

Key TypeDescription
Merchant AES KeyEach merchant certificate has its own 128-bit Base64 key for request and response payload encryption.
Merchant RSA Key Pair2048-bit RSA. The merchant keeps the private key and configures the public key with its matching AES key on the platform.
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

All date-time fields in merchant API responses use yyyy-MM-dd HH:mm:ss. Date-only fields use yyyy-MM-dd, and time-only fields use HH:mm:ss.

Card Acquiring Products and Flows

Card acquiring supports hosted checkout and API integration modes.

ModeOrder Flow
CheckoutCreate order → redirect to checkoutUrl → receive notification
APIQuery dynamic fields → create order → complete returned action → receive notification

Query Dynamic Required Fields

POST/api/pay/required-fields

Queries extension fields required for the selected payment method before order creation. Checkout mode returns an empty array.

Request Parameters

FieldTypeRequiredDescription
actionstringYesPAY or PAYOUT
currencystringYesISO 4217 currency code
amountnumberYesTransaction amount, greater than zero
payTypestringYesPayment method, for example CARD or PIX
integrationModestringPay-in onlyHOSTED or DIRECT
contextobjectNoMode and query conditions; see example

context Fields

FieldTypeRequired WhenValues / Description
payoutModestringLocal payoutFixed value LOCAL
supplierstringDepends on local payout methodService type, for example PIX

Response Fields

FieldTypeDescription
actionstringRequested transaction direction
currencystringTransaction currency
payTypestringPayment method
schemaVersionstringField definition version
fieldsarray/objectField definitions to submit in order extra
{
  "action": "PAY",
  "currency": "USD",
  "amount": 100,
  "payType": "CREDIT_CARD",
  "integrationMode": "DIRECT"
}
{
  "action": "PAY",
  "currency": "USD",
  "payType": "CREDIT_CARD",
  "integrationMode": "DIRECT",
  "schemaVersion": "54c29e37dbf9431a",
  "fields": [
    {"fieldName":"senderCardNumber","path":"cardInfo.number","required":true},
    {"fieldName":"senderFirstName","path":"extra.senderFirstName","required":true}
  ]
}

integrationMode is provider-neutral. Pay-in defaults to HOSTED; query required fields before creating a DIRECT order.

Items in fields

FieldTypeDescription
fieldNamestringField name to submit in order extra
pathstringComplete path in the create-order request; use this value as authoritative
requiredbooleanWhether the field is required

Merchants should rely on path and required. Put cardInfo.number in top-level cardInfo, customerEmail at the top level, and extra.senderFirstName in extra. If several required fields are absent, create-order returns all missing paths in missingFields.


Create Card Payment

POST/api/pay/in

Creates a collection (pay-in) order and returns a payment URL.

Request Parameters (plaintext JSON before encryption)

FieldTypeRequiredDescription
userIdstringYesMerchant-side unique user identifier
outTradeNostringYesMerchant's unique order number
amountnumberYesPayment amount
currencystringYesCurrency code (see enum below)
payTypestringYesPayment type (see enum below)
integrationModestringNoHOSTED (default) or DIRECT
notifyUrlstringNoAsync callback URL for payment result
returnUrlstringNoRedirect URL after payment
customerNamestringNoCustomer name
customerPhonestringNoCustomer phone
customerEmailstringNoCustomer email
customerIpstringNoClient IP
cardInfoobjectNoCard information (see below)
extraobjectNoPayment-method extension fields

Card Acquiring Mode Parameters

ModeRequired ParametersAvailability
HostedintegrationMode=HOSTED, payType, customerIp, and returned required fieldsAvailable
DirectintegrationMode=DIRECT; query required fields and submit them by pathAvailable

integrationMode is a top-level platform-standard field: HOSTED (default) or DIRECT.

Pay-in extra Fields

FieldTypeRequired WhenDescription
countrystringCheckoutCustomer country/region
city / state / address / zipstringNoAddress details
dateOfBirthstringNoDate of birth
cancelUrlstringNoCancellation redirect URL
lang / websiteUrlstringNoCheckout display information
embedParentOriginstringEmbedded checkoutParent page Origin
appPlatform / appBundleIdstringMobile app integrationios / android and the application bundle ID
senderPaymentReference / riskTokenstringDepends on API methodReference and risk token
userAgent / acceptHeader / languagestringDepends on API methodBrowser information
colorDepth / screenHeight / screenWidth / timeZonenumberDepends on API methodBrowser color depth, screen dimensions, and UTC offset in minutes
javaEnabled / javaScriptEnabledbooleanDepends on API methodBrowser capabilities
countryOfBillingAddressstringDepends on API methodBilling country/region

For API mode, submit card data in top-level cardInfo. Submit other dynamic fields in extra using the exact names returned by Query Required Fields.

actionType is not a create-order request field. The platform returns it in the response: REDIRECT means the payer must open payUrl, while NOMORE means no additional action is required.

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)
tradeStatusstringPAYING / SUCCESS / FAIL
tradeAmount / merchantAmountnumberOrder and merchant settlement amounts
currencystringCurrency code
checkoutUrlstringPlatform checkout URL (recommended)
payUrlstringOriginal checkout URL; returned when enabled
errMsgstringMerchant-safe failure reason

The response also includes userId, payType, customer details, notification/redirect URLs, and timestamps. Its structure matches Pay-in Query and pay-in callbacks.

Example

{
  "userId": "USER10001",
  "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": "[email protected]",
  "cardInfo": {
    "number": "4111111111111111",
    "expireMonth": "12",
    "expireYear": "2026",
    "cvv": "123",
    "name": "John"
  }
}
{
  "tradeId": "T20240101120000001",
  "outTradeNo": "ORD20240101001",
  "tradeStatus": "PAYING",
  "tradeAmount": 500.00,
  "currency": "EUR",
  "checkoutUrl": "https://pay.example.com/checkout?token=opaque-token"
}

Query Card Payment

POST/api/pay/in/query

Query a pay-in order by 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
userIdstringMerchant customer identifier
payTypestringPayment method
customerName / customerPhone / customerEmailstringCustomer details
customerIpstringCustomer IP address
notifyUrlstringAsync notification URL
returnUrlstringRedirect URL after payment
payUrlstringOriginal payment URL, when enabled
checkoutUrlstringPlatform checkout URL
createTime / tradeCompleteTimestringCreation and completion times
errMsgstringMerchant-safe failure reason

Order Status Values

StatusDescription
PAYINGPayment in progress
SUCCESSPayment successful
FAILPayment failed

Example

{
  "outTradeNo": "ORD20240101001"
}
{
  "tradeId": "T20240101120000001",
  "outTradeNo": "ORD20240101001",
  "tradeStatus": "SUCCESS",
  "tradeAmount": 500.00,
  "merchantAmount": 485.00,
  "currency": "INR",
  "checkoutUrl": "https://pay.example.com/checkout?token=opaque-token"
}

Query Local Payout

POST/api/pay/out/query

Query a payout order by tradeId or outTradeNo.

Request Parameters

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

Response Parameters

FieldTypeDescription
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number
tradeStatusstringOrder status
tradeAmountnumberOrder amount
merchantAmountnumberMerchant settlement amount
feeAmountnumberMerchant fee
debitAmountnumberTotal debited amount including the fee
currencystringCurrency code
userIdstringMerchant customer identifier
payTypestringPayout method
customerNamestringCustomer name
customerPhonestringCustomer phone number
customerEmailstringCustomer email
customerIpstringCustomer IP address
notifyUrlstringAsync notification URL
utrstringTransaction reference after success
createTimestringOrder creation time
tradeCompleteTimestringOrder completion time
errMsgstringMerchant-safe failure reason

Order Status Values

StatusDescription
PAYINGPayment in progress
SUCCESSPayment successful
FAILPayment failed

Example

{
  "tradeId": "T20240101120000002"
}
{
  "tradeId": "T20240101120000002",
  "outTradeNo": "PAYOUT20240101001",
  "tradeStatus": "SUCCESS",
  "tradeAmount": 500.00,
  "merchantAmount": 485.00,
  "feeAmount": 15.00,
  "debitAmount": 515.00,
  "currency": "INR",
  "utr": "UTR202401010001"
}

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": []
}

Create Local Payout

POST/api/pay/out

Creates a payout order and returns the platform order ID and current status.

FieldTypeRequiredDescription
userIdstringYesMerchant-side unique user identifier
outTradeNostringYesUnique merchant order number
amountnumberYesAmount greater than zero
currencystringYesISO 4217 currency code
notifyUrlstringNoAsynchronous result URL
customerName / customerPhone / customerEmailstringNoBeneficiary details
bankAccount / bankCardNostringConditionalAt least one payout destination is required
bankCode / bankNamestringNoBank details
extraobjectNoPayment-method fields returned by Query Required Fields

Payout extra Fields

FieldTypeRequired WhenDescription
payoutModestringYesFixed value LOCAL
paymentTypestringLocal payoutPayment method, for example PIX
supplierstringDepends on methodService type
accountNumberstringDepends on methodReceiving account
pixKeyType / pixKeyValuestringAs returned for PIXPIX Key type and value
idCard / nationalId / addressstringAs returnedBeneficiary identity and address

Requirements vary by country, payment method, and supplier. Query dynamic required fields before order creation and submit every returned fieldName in extra.

Response Fields

FieldTypeDescription
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number
tradeStatusstringPAYING or FAIL
errMsgstringFailure reason; omitted on success
tradeAmount / merchantAmount / feeAmount / debitAmountnumberOrder, settlement, fee, and debit amounts
currencystringCurrency code
utrstringTransaction reference after success

The response also includes userId, payType, customer details, notification URL, and timestamps. Its structure matches Payout Query and payout callbacks.

{"tradeId":"T202608070001","outTradeNo":"PAYOUT20260807001","tradeStatus":"PAYING"}

Create Refund

POST/api/pay/refund

Creates a refund request for an original pay-in order.

FieldTypeRequiredDescription
tradeId / outTradeNostringOne requiredOriginal pay-in identifier
outRefundNostringYesUnique merchant refund number
amountnumberYesAmount greater than zero
reasonstringNoRefund reason
notifyUrlstringNoRefund result URL

The platform first verifies that the original payment succeeded, cumulative refunds do not exceed the original amount, and both the channel adapter and account support refunds. No refund order is created when refunds are unsupported.

Response Fields

FieldTypeDescription
refundIdstringPlatform refund ID
outRefundNostringMerchant refund number
tradeId / outTradeNostringOriginal payment identifiers
refundStatusstringPAYING / SUCCESS / FAIL
refundAmountnumberRefund amount
currencystringOriginal payment currency
notifyUrl / reasonstringNotification URL and reason
errMsgstringFailure reason
createTime / refundCompleteTimestringCreation and completion times

Query Refund

POST/api/pay/refund/query

FieldTypeRequiredDescription
tradeIdstringConditionalReturns all refunds for the original platform trade ID
outTradeNostringConditionalReturns all refunds for the original merchant order number
refundIdstringConditionalReturns the matching platform refund
outRefundNostringConditionalReturns the matching merchant refund

Exactly one query field must be provided. Response data is always an array. Original-payment queries return an empty array when no refunds exist; refund-ID queries return a single-element array.


Refund Notification

Sent when a refund reaches SUCCESS / FAIL. event is always TRADE_REFUND_ORDER_CALLBACK.

FieldTypeDescription
eventstringAlways TRADE_REFUND_ORDER_CALLBACK
refundId / outRefundNostringPlatform and merchant refund identifiers
tradeId / outTradeNostringOriginal payment identifiers
refundStatusstringSUCCESS or FAIL
refundAmountnumberRefund amount
currencystringCurrency
notifyUrl / reason / errMsgstringNotification URL, reason, and failure reason
createTime / refundCompleteTimestringCreation and completion times

Failed notifications are retried with increasing delays. Respond with plain text SUCCESS.


Card Payment Notification

When a pay-in reaches SUCCESS / FAIL, the platform sends a POST notification to its notifyUrl. event is always TRADE_IN_ORDER_CALLBACK.

Notification Parameters

FieldTypeDescription
eventstringAlways TRADE_IN_ORDER_CALLBACK
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number
tradeStatusstringSUCCESS or FAIL
tradeAmountnumberOrder amount
merchantAmountnumberMerchant settlement amount
currencystringCurrency code
userIdstringMerchant customer identifier
payTypestringPayment method
customerNamestringCustomer name
customerPhonestringCustomer phone number
customerEmailstringCustomer email
customerIpstringCustomer IP address
notifyUrlstringNotification URL
returnUrlstringRedirect URL after payment
actionTypestringNext action: REDIRECT or NOMORE
payUrlstringOriginal payment URL, when enabled
checkoutUrlstringPlatform checkout URL
createTimestringCreation time
tradeCompleteTimestringCompletion time
errMsgstringFailure reason, returned only on failure

Rules

Example

{
  "event": "TRADE_IN_ORDER_CALLBACK",
  "tradeId": "T20240101120000001",
  "outTradeNo": "ORD20240101001",
  "tradeStatus": "SUCCESS",
  "tradeAmount": 500.00,
  "merchantAmount": 485.00,
  "currency": "INR",
  "checkoutUrl": "https://pay.example.com/checkout?token=opaque-token"
}

Merchant response: SUCCESS


Local Payout Notification

When a payout reaches SUCCESS / FAIL, the platform sends a POST notification to its notifyUrl. event is always TRADE_OUT_ORDER_CALLBACK.

Notification Parameters

FieldTypeDescription
eventstringAlways TRADE_OUT_ORDER_CALLBACK
tradeIdstringPlatform trade ID
outTradeNostringMerchant order number
tradeStatusstringSUCCESS or FAIL
tradeAmountnumberOrder amount
merchantAmountnumberMerchant settlement amount
feeAmountnumberMerchant fee
debitAmountnumberTotal debited amount
currencystringCurrency code
userIdstringMerchant customer identifier
payTypestringPayout method
customerNamestringCustomer name
customerPhonestringCustomer phone number
customerEmailstringCustomer email
customerIpstringCustomer IP address
notifyUrlstringNotification URL
utrstringTransaction reference after success
createTimestringCreation time
tradeCompleteTimestringCompletion time
errMsgstringFailure reason, returned only on failure

Rules

Example

{
  "event": "TRADE_OUT_ORDER_CALLBACK",
  "tradeId": "T20240101120000002",
  "outTradeNo": "PAYOUT20240101001",
  "tradeStatus": "SUCCESS",
  "tradeAmount": 500.00,
  "merchantAmount": 485.00,
  "feeAmount": 15.00,
  "debitAmount": 515.00,
  "currency": "INR",
  "utr": "UTR202401010001"
}

Merchant response: SUCCESS


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