Pay with Sign API (One-Step)
HTTP Request
POST /v5/pay/agreement/pay-with-sign
Complete both agreement signing and deduction payment in a single API call. This API supports two modes: Sign + Pay (for first-time users) and Pay Only (using existing agreement).
Feature Description
This API supports merchants to complete both agreement signing and deduction payment in a single call. Signing is optional, merchants can choose:
- Sign + Pay Mode: Pass sign parameters, system creates agreement first, then executes deduction immediately after successful signing
- Pay Only Mode: Do not pass sign parameters, only pass existing
agreementNo, execute deduction directly based on signed agreement
Applicable Scenarios:
- First payment scenario: Complete both signing and first payment in one step when user uses the service for the first time
- Quick payment scenario: Simplify merchant integration, reduce API call count
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Merchant ID |
| agreementType | string | Yes | Sign type: CYCLE / NON_CYCLE / SINGLE |
| signParams | object | No | Sign parameters (required for first sign + payment) |
| signParams.sceneCode | string | Conditional | Scene code (required when passing signParams) |
| signParams.externalAgreementNo | string | Conditional | Merchant agreement number (required when passing signParams, unique) |
| signParams.merchantUserId | string | No | Merchant-side user ID (optional, used for merchant internal user association) |
| signParams.productCode | string | No | Product code, assigned by platform |
| signParams.signValidTime | string | No | Sign validity period, ISO8601 format |
| signParams.singleLimit | object | No | Single transaction limit configuration |
| signParams.singleLimit.amount | string | Conditional | Limit amount (required when passing singleLimit) |
| signParams.singleLimit.currency | string | Conditional | Currency code (required when passing singleLimit) |
| signParams.singleLimit.currencyType | string | Conditional | FIAT or CRYPTO (required when passing singleLimit) |
| signParams.singleLimit.chain | string | No | Chain network (optional for cryptocurrency, e.g., BSC, TRX, ARBI, LTC, SOL, ETH, TON, APTOS) |
| signParams.singleLimit.chainAddress | string | No | Chain address (required in dynamic chain settlement mode) |
| signParams.periodLimits | array | No | Period limit configuration list |
| signParams.periodLimits[].periodType | string | Conditional | Period type: DAY/WEEK/MONTH/YEAR |
| signParams.periodLimits[].amount | string | Conditional | Period limit amount |
| signParams.periodLimits[].currency | string | Conditional | Currency code |
| signParams.periodLimits[].currencyType | string | Conditional | FIAT or CRYPTO |
| signParams.periodLimits[].chain | string | No | Chain network (optional for cryptocurrency, e.g., BSC, TRX, ARBI, LTC, SOL, ETH, TON, APTOS) |
| signParams.periodLimits[].chainAddress | string | No | Chain address (required in dynamic chain settlement mode) |
| signParams.signNotifyUrl | string | No | Sign result async notification URL |
| signParams.returnUrl | string | No | Redirect URL after sign completion |
| signParams.signExpireMinutes | int | No | Sign link validity period (minutes), default 30 |
| signParams.extraParams | string | No | Extension parameters (JSON string) |
| payParams | object | Yes | Deduction payment parameters |
| payParams.agreementNo | string | Conditional | Platform agreement number (required when not passing signParams) |
| payParams.outTradeNo | string | Yes | Merchant order number (unique) |
| payParams.sceneCode | string | Yes | Scene code |
| payParams.amount | object | Yes | Deduction amount |
| payParams.amount.total | string | Yes | Amount (minimum unit) |
| payParams.amount.currency | string | Yes | Currency code |
| payParams.amount.currencyType | string | Yes | FIAT or CRYPTO |
| payParams.amount.chain | string | No | Chain network (required in dynamic chain settlement mode, e.g., BSC, TRX, ARBI, LTC, SOL, ETH, TON, APTOS) |
| payParams.amount.chainAddress | string | No | Chain address (required in dynamic chain settlement mode) |
| payParams.orderInfo | object | Yes | Order information |
| payParams.orderInfo.orderTitle | string | Yes | Order title |
| payParams.orderInfo.orderDesc | string | No | Order description |
| payParams.orderInfo.goodsName | string | No | Goods name |
| payParams.orderInfo.goodsId | string | No | Goods ID |
| payParams.orderInfo.goodsCategory | string | Yes | Goods category (4-digit code, see Appendix.MCC Industry Categories) |
| payParams.sceneInfo | object | No | Scene information |
| payParams.sceneInfo.deviceId | string | No | Device ID |
| payParams.sceneInfo.deviceIp | string | No | Device IP |
| payParams.sceneInfo.location | object | No | Location information |
| payParams.sceneInfo.location.latitude | string | No | Latitude |
| payParams.sceneInfo.location.longitude | string | No | Longitude |
| payParams.sceneInfo.location.address | string | No | Detailed address |
| payParams.payNotifyUrl | string | Yes | Payment result async notification URL |
| payParams.riskInfo | object | No | Risk control information |
| payParams.riskInfo.userIp | string | No | User IP address |
| payParams.riskInfo.deviceFingerprint | string | No | Device fingerprint |
| payParams.riskInfo.userAgent | string | No | User agent string |
Response Parameters
| Parameter | Type | Description |
|---|---|---|
| retCode | int | Response code |
| retMsg | string | Response message |
| result | object | Response data |
| result.signResult | object | Sign result (if signing was initiated) |
| result.signResult.agreementNo | string | Platform agreement number |
| result.signResult.externalAgreementNo | string | Merchant agreement number |
| result.signResult.signOrderId | string | Platform sign order number |
| result.signResult.status | string | Sign status: INIT/PENDING/SIGNED/FAILED |
| result.signResult.signTime | string | Sign success time (returned on success) |
| result.signResult.validTime | string | Agreement validity period |
| result.signResult.signUrl | string | Sign page URL (for H5 redirect) |
| result.signResult.qrCode | string | Sign QR code content (for user App scan) |
| result.signResult.qrCodeUrl | string | Sign QR code image URL |
| result.signResult.expireTime | string | Sign link/QR code expiration time |
| result.payResult | object | Deduction result |
| result.payResult.tradeNo | string | Platform transaction number |
| result.payResult.outTradeNo | string | Merchant order number |
| result.payResult.status | string | Transaction status: PROCESSING/SUCCESS/FAILED/TIMEOUT |
| result.payResult.amount | object | Merchant requested amount |
| result.payResult.amount.total | string | Amount (minimum unit) |
| result.payResult.amount.currency | string | Currency code |
| result.payResult.amount.currencyType | string | Currency type: FIAT / CRYPTO |
| result.payResult.cryptoPayment | object | User's actual cryptocurrency payment info (for fiat orders) |
| result.payResult.cryptoPayment.currency | string | Cryptocurrency currency |
| result.payResult.cryptoPayment.amount | string | Cryptocurrency amount |
| result.payResult.cryptoPayment.chain | string | Chain network (e.g., BSC, TRX, ARBI, LTC, SOL, ETH, TON, APTOS) |
| result.payResult.cryptoPayment.exchangeRate | string | Exchange rate |
| result.payResult.cryptoPayment.rateTime | string | Rate lock time |
| result.payResult.cryptoPayment.transOrderNo | string | Downstream deduction transaction number |
| result.payResult.payTime | string | Payment success time (empty before success) |
| result.payResult.failureReason | string | Failure reason (returned on failure) |
Request Example 1: Sign + Pay (First Use)
{
"merchantId": "M123456789",
"agreementType": "CYCLE",
"signParams": {
"merchantUserId": "merchant_user_123",
"sceneCode": "TAXI",
"productCode": "PROD_001",
"externalAgreementNo": "MERCHANT_AGR_001",
"signValidTime": "2026-12-23T10:30:00Z",
"singleLimit": {
"amount": "100000",
"currency": "USDT",
"currencyType": "CRYPTO",
"chain": "TRC20"
},
"periodLimits": [
{
"periodType": "DAY",
"amount": "500000",
"currency": "USDT",
"currencyType": "CRYPTO",
"chain": "TRC20"
}
],
"signNotifyUrl": "https://merchant.com/notify/sign"
},
"payParams": {
"outTradeNo": "TAXI20231223001",
"sceneCode": "TAXI",
"amount": {
"total": "2350",
"currency": "USDT",
"currencyType": "CRYPTO",
"chain": "TRC20"
},
"orderInfo": {
"orderTitle": "Taxi Fare",
"orderDesc": "Trip on December 23",
"goodsCategory": "4121"
},
"payNotifyUrl": "https://merchant.com/notify/pay",
"riskInfo": {
"userIp": "203.0.113.45",
"deviceFingerprint": "fp_abc123xyz"
}
}
}
Response Example 1: Sign + Pay (Waiting for User Scan)
{
"retCode": 20000,
"retMsg": "Success",
"result": {
"signResult": {
"agreementNo": null,
"externalAgreementNo": "MERCHANT_AGR_001",
"signOrderId": "SIGN202312230001",
"status": "INIT",
"signTime": null,
"validTime": "2026-12-23T10:30:00Z",
"signUrl": "https://pay.example.com/sign?token=xxx",
"qrCode": "https://pay.example.com/sign?token=xxx",
"qrCodeUrl": "https://pay.example.com/qr/SIGN202312230001.png",
"expireTime": "2023-12-23T11:00:00Z"
},
"payResult": {
"tradeNo": null,
"outTradeNo": "TAXI20231223001",
"status": "PENDING",
"amount": {
"total": "2350",
"currency": "USDT",
"currencyType": "CRYPTO",
"chain": "TRC20"
},
"payTime": null,
"failureReason": null
}
}
}
Notes:
signResult.status = "INIT"- Sign request created, waiting for user scanpayResult.status = "PENDING"- Payment order created, waiting for sign completion to auto-execute- Merchant should display
qrCodeUrlorsignUrlfor user to scan - Final result returned via Webhook async notification
Request Example 2: Pay Only (Using Existing Agreement)
{
"merchantId": "M123456789",
"agreementType": "CYCLE",
"payParams": {
"agreementNo": "AGR202312230001",
"outTradeNo": "TAXI20231223002",
"sceneCode": "TAXI",
"amount": {
"total": "3500",
"currency": "USDT",
"currencyType": "CRYPTO",
"chain": "TRC20"
},
"orderInfo": {
"orderTitle": "Taxi Fare",
"orderDesc": "Second trip on December 23",
"goodsCategory": "4121"
},
"payNotifyUrl": "https://merchant.com/notify/pay"
}
}
Response Example 2: Pay Only (Using Existing Agreement)
{
"retCode": 20000,
"retMsg": "Success",
"result": {
"signResult": null,
"payResult": {
"tradeNo": "PAY202312230002",
"outTradeNo": "TAXI20231223002",
"status": "PROCESSING",
"amount": {
"total": "3500",
"currency": "USDT",
"currencyType": "CRYPTO",
"chain": "TRC20"
},
"payTime": null,
"failureReason": null
}
}
}
Notes:
signResult = null- No sign initiated, using existing agreementpayResult.status = "PROCESSING"- Deduction payment processing- Final result returned via Webhook async notification
Important Notes
Async Flow
- Signing requires user scan confirmation, it's an async flow
- Sync response returns sign QR code and initial status (
INIT/PENDING) - Final result returned via Webhook async notification
Business Flow
- Call API → Returns sign QR code
- Merchant displays QR code → User scans with App
- User completes sign → System auto-triggers deduction
- Webhook notification → Returns sign and payment results
Result Processing
- Results returned separately in
signResultandpayResult - If sign fails, deduction not executed,
payResultisnull - If sign succeeds, user completes payment in app until success
Webhook Notification Strategy
- Sign result and payment result are sent as independent notifications to their respective notify URLs
- Sign result (success/rejected/failed) sent to
signNotifyUrl - Payment result (success/failed) sent to
payNotifyUrl - If user rejects sign, only sign rejected notification is sent, deduction will not be executed
- If sign fails (system error), only sign failed notification is sent, deduction will not be executed
Idempotency Guarantee
- Idempotency guaranteed through
externalAgreementNofor signing - Idempotency guaranteed through
outTradeNofor payment
Optional Signing
- When
signParamsis empty, must pass existing agreement number inpayParams.agreementNo - When using existing agreement, no scan needed, direct deduction execution