Skip to content

Create order ​

Call it from your server to create one platform payment order for one piece of business.

http
POST {API_BASE}/api/pay/open/createOrder
X-Pay-Api-Key: <your API KEY>
Content-Type: application/json

The caller of this endpoint is a merchant system holding an API KEY, not a browser. Merchant identity comes only from the KEY.

Request fields ​

FieldTypeRequiredDescription
merchantOrderNostringYesYour order number. The idempotency key; retries must reuse it. Max length 64
amountstringYesDecimal string in token units, e.g. "100" / "100.5". See "Amount conventions" below
chainIdnumberYesWhich chain to collect on (from your available-chain list)
tokenIdnumberYesWhich token on that chain
subjectstringNoA title, shown on the checkout page and in order lists
notifyUrlstringNoPer-order callback URL, overriding the default on your merchant profile
returnUrlstringNoWhere to send the buyer's browser after payment

On notifyUrl versus returnUrl: the former is server to server (your backend ships the goods), the latter is the jump in the buyer's browser (your "payment complete" page). Many merchants need both.

bash
curl -X POST "$API_BASE/api/pay/open/createOrder" \
  -H "X-Pay-Api-Key: $PAY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "merchantOrderNo": "SHOP-2026-0001",
    "chainId": 2,
    "tokenId": 1,
    "amount": "100.5",
    "subject": "Order SHOP-2026-0001",
    "notifyUrl": "https://your-shop.com/pay/notify",
    "returnUrl": "https://your-shop.com/pay/done"
  }'

Amount conventions ​

This is the most common mistake in the whole API. Read it carefully.

EndpointShape of amountExample
Open API create order (this page)Decimal string in token units"100" / "100.5"
Checkout online order createCashierOrderInteger string in the smallest unitFor a 6-decimal token, 100 is "100000000"

They are deliberately opposite: a merchant's server already knows a human-readable amount, while the checkout frontend works with the result of parseUnits. Send the wrong shape and it will be rejected or misinterpreted — amounts differ by 10 to the power of decimals, and the other side may not notice immediately.

decimals is a property of the token (commonly 6 for USDT) and is available in the chain and token list.

Idempotency: merchantOrderNo is the key ​

Calling again with the same merchantOrderNo does not create a new order. It returns the same payment order, with idempotentReplay set to true.

  • A retry after a network blip or timeout is safe with the same merchantOrderNo.
  • To genuinely create another order, use a new merchantOrderNo.
  • Why this matters: without an idempotency key, a retry can create two orders and collect two payments — and the platform holds no private key, so the extra payment cannot be refunded.

Response ​

json
{
  "code": 0,
  "data": {
    "order": {
      "orderNo": "PO20261011XXXXXXXXXXXX",
      "merchantOrderNo": "SHOP-2026-0001",
      "merchantNo": "M00000XXXX",
      "status": 1,
      "statusText": "待支付",
      "orderType": 1,
      "amountMinimal": "100500000",
      "payAmountMinimal": "100500123",
      "payAmountDisplay": "100.500123",
      "receivedAmountMinimal": "0",
      "chainId": 2,
      "chainKey": "tron",
      "chainType": 2,
      "tokenId": 1,
      "tokenSymbol": "USDT",
      "tokenContract": "TXxx...contract address",
      "tokenDecimals": 6,
      "payAddress": "TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "subject": "Order SHOP-2026-0001",
      "txHash": "",
      "confirmations": 0,
      "requiredConfirmations": 19,
      "expiresAt": "2026-10-11T10:30:00Z",
      "identify": {
        "mode": 1,
        "orderNo": "PO20261011XXXXXXXXXXXX",
        "calldataSuffix": "0x...order identify code..."
      },
      "notifyUrl": "https://your-shop.com/pay/notify"
    },
    "idempotentReplay": false
  },
  "msg": "下单成功"
}

Key fields ​

FieldDescription
orderNoPlatform order number. Used by the query API and the checkout page to locate this order
idempotentReplaytrue means an existing payment order was reused, not created. Read it on every retry, or you will think you opened a second order
amountMinimalThe amount you asked for, in the smallest unit
payAmountMinimalThe amount that should actually be transferred (smallest unit). In amount-suffix mode it carries an extra suffix over amountMinimal — use this one
payAmountDisplayThe same value formatted for humans, so you don't divide by decimals yourself
receivedAmountMinimalWhat actually arrived on-chain. For reconciliation; it may exceed the expected amount (the buyer overpaid) or still be 0 (not scanned yet)
payAddressWhere this order should be sent (snapshotted at creation)
tokenContractThe token contract address. Used to build the transaction — the transaction's to is the contract, not the collection address; get this backwards and no tokens move
identifyThe order identification data. Pass it through verbatim; change its case or add a prefix and the scanner will not recognise this order
notifyUrlThe callback URL actually in effect for this order. It is echoed back so you can immediately see that the URL you passed did not take effect
expiresAtThe payment validity window for this order

Use payAmountMinimal, not amountMinimal

The amount the buyer should transfer is payAmountMinimal. When it differs from amountMinimal, that extra part is the identification suffix, not an error.

::: note Response text is in Chinese statusText and msg come back in Chinese from the API. Branch on status (numeric) and code, and treat the text as human-facing only. :::

Common rejection reasons ​

When an order is rejected, the message is written for a human and is passed through verbatim (for example "amount below the minimum"). These do not consume idempotency and can be retried after a fix. See Errors for the full troubleshooting table.

Web3 payment infrastructure for global merchants