MerchantCenter 商户开放 API 对接指南
文中的 <...> 均为需要替换的占位值。
接入前请确认以下信息:
- 平台 Base URL 由平台提供。
- 商户号由平台提供。
- API Key 在商户后台生成和查看。
- 调用接口使用的固定公网出口 IP 需要提前登记。
接入凭据准备
- API Key 未生成前,开放接口无法通过鉴权。
- API Key 可在商户后台完成验证后生成和查看。
- 后台平时显示 API Key 脱敏值,完成验证后可临时查看完整值。
- API Key 重置成功后,旧 Key 立即失效。
- 商户后台的验证信息不随接口请求发送,也不参与订单签名。
1. 接口清单
| 业务 | 方法 | Path |
|---|---|---|
| 查询平台商户余额 | GET | /open/mc/merchantBalance/query |
| 创建代收订单 | POST | /open/mc/merchantPayin/create |
| 查询代收订单 | GET | /open/mc/merchantPayin/query |
| 提交代收 UTR | POST | /open/mc/merchantPayin/submitUtr |
| 代收补单 | POST | /open/mc/merchantPayin/makeup |
| 查询 UPI 是否已存在 | GET | /open/mc/merchantPayin/upiQuery |
| 查询 UTR 是否已存在 | GET | /open/mc/merchantPayin/utrQuery |
| 创建代付订单 | POST | /open/mc/merchantPayout/create |
| 查询代付订单 | GET | /open/mc/merchantPayout/query |
2. 通用请求与响应
2.1 请求 Header
所有商户请求和平台异步通知均使用以下五个认证 Header:
| Header | 必填 | 说明 |
|---|---|---|
x-merchant-no | 是 | 平台分配的商户号 |
x-timestamp | 是 | Unix 秒级时间戳;默认允许与平台服务器时间误差不超过 300 秒 |
x-nonce | 是 | 本次请求唯一随机串,16~64 位,只允许字母、数字、_、- |
x-sign-version | 是 | 固定为 v2;缺失或其他版本均拒绝 |
x-sign | 是 | HMAC-SHA256 签名,64 位小写十六进制字符串 |
POST 请求还应携带:
Content-Type: application/json
请求签名要求:
- 每一次 HTTP 请求都必须生成新的 timestamp、nonce 和 sign。
- 创建请求、查询请求和重复查询均不得复用 nonce。
- nonce、HTTP method、URL path 或 payload 发生变化时,必须重新计算 sign。
- 调用方服务器时间与平台服务器时间的偏差不得超过 300 秒。
2.2 来源 IP 策略
| 接口范围 | IP 白名单要求 |
|---|---|
| 全部开放接口 | 强制要求。商户必须提前登记固定公网出口 IP;来源 IP 不匹配时请求将被拒绝 |
来源 IP 要求:
- 商户通过代理或 NAT 访问时,应登记请求实际使用的固定公网出口 IP。
- 出口 IP 变更前,应先联系平台更新白名单。
2.3 字段规则
| 规则 | 说明 |
|---|---|
| 金额 | 使用字符串,必须大于 0,固定两位小数,例如 "500.00" |
| 可选字段 | 无值时建议省略;请求体或通知体可以携带 null |
| GET 参数 | 使用 URL query;同一个 key 只传一次 |
| 订单定位 | orderNo 与 merchantOrderNo 二选一,只能传一个 |
| 未知字段 | 未在对应接口字段表中声明的字段会被拒绝 |
2.4 通用响应
成功:
{
"code": 1000,
"message": "success",
"data": {}
}
失败:
{
"code": 40100,
"message": "Request parameter is invalid",
"data": null
}
创建订单在业务受理前失败时,data 仍会保留商户订单号和失败状态:
{
"code": 40302,
"message": "Service is temporarily unavailable",
"data": {
"merchantOrderNo": "ORDER-EXAMPLE-001",
"status": "FAILED"
}
}
只有创建成功并返回过 orderNo 的订单,后续查询和异步通知才会携带平台订单号。
3. 签名与验签
3.1 签名参数
| 请求方向/方法 | 参与 payload SHA-256 的内容 |
|---|---|
商户 POST 请求平台 | JSON Body 参数按 3.2 节删除空值、排序后的稳定 JSON |
商户 GET 请求平台 | URL query 参数按 3.2 节删除空值、排序后的稳定 JSON |
平台 POST 通知商户 | 通知 JSON 参数按 3.2 节删除空值、排序后的稳定 JSON |
path 规则:
- path 只使用 URL pathname。
- 例如:
https://api.example.com/open/mc/merchantPayin/query?orderNo=1的签名 path 为/open/mc/merchantPayin/query。 - Base URL、host 和 query 原始字符串不直接进入签名原文。
- GET 的 query 业务值通过稳定 JSON 摘要参与签名。
3.2 参数清理与排序
GET、POST 和平台通知使用同一规则:删除空值参数,按字段名字典序递归排序,再序列化为稳定 JSON。该处理只用于签名,不改变实际发送的 JSON。
| 类型 | 规则 |
|---|---|
| 对象 | 删除空值字段后,所有层级的 key 按字典序升序排列 |
| 数组 | 保持原顺序;元素递归清理,空值元素不参与签名 |
| 有效标量 | 字符串、数字、布尔值使用标准 JSON;0、false、"0" 必须参与 |
| 空值 | null、undefined、空字符串、纯空白字符串、非有限数字不参与签名 |
| 重复 query key | 只取第一个值;调用方应避免重复 key |
| 输出 | UTF-8,不含缩进、额外空格或换行 |
示例 GET query 对象:
{
"merchantOrderNo": "MP001",
"amount": "500.00",
"currency": "INR",
"utr": null
}
参与摘要计算的稳定 JSON:
{"amount":"500.00","currency":"INR","merchantOrderNo":"MP001"}
只要有效业务参数相同,JSON 字段原始顺序、空格和换行不会改变签名;字段名称或有效值发生变化时必须重新计算签名。
3.3 八行签名原文
先计算:
payloadSha256 = hex_lower(SHA256(payloadBytes))
再使用单个换行符 \n 拼接以下八行,末尾不增加换行:
MCV2-HMAC-SHA256
v2
<UPPERCASE_HTTP_METHOD>
<URL_PATHNAME>
<MERCHANT_NO>
<TIMESTAMP_UNIX_SECONDS>
<NONCE>
<PAYLOAD_SHA256>
用商户后台生成并安全保存的 API Key 对 canonical text 的 UTF-8 字节计算 HMAC-SHA256,并输出小写十六进制字符串:
sign = hex_lower(HMAC_SHA256(apiKey, canonicalText))
签名适用范围:
- 商户请求平台和平台异步通知商户均使用上述签名原文。
- 商户请求必须携带第 2.1 节列出的五个认证 Header。
- nonce 必须符合格式要求,并且不能重复使用。
- 平台异步通知携带相同的认证 Header,验签算法与商户请求一致。
4. 查询平台商户余额
GET /open/mc/merchantBalance/query
GET /open/mc/merchantBalance/query?currency=INR
Query:
| 字段 | 必填 | 说明 |
|---|---|---|
currency | 否 | 币种,例如 INR;不传返回该商户已有的全部币种账户 |
成功响应 data:
{
"merchantNo": "<MERCHANT_NO>",
"balances": [
{
"currency": "INR",
"balance": "1000.00",
"availableBalance": "900.00",
"frozenBalance": "100.00"
}
]
}
余额返回说明:
- 指定币种但尚无账户记录时,平台返回该币种的三个
"0.00",避免把空数组误判为接口异常。 - 该接口返回商户在本平台的资金账户余额。
5. 创建代收订单
POST /open/mc/merchantPayin/create
Content-Type: application/json
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchantOrderNo | string | 是 | 商户唯一订单号,1~128 字符 |
amount | string | 是 | 大于 0 的金额字符串,最多 2 位小数 |
currency | string | 是 | 当前使用 INR |
payinInterfaceStyle | string | 否 | standard / extended;默认 extended |
notifyUrl | string | 否 | 订单终态通知地址 |
returnUrl | string | 否 | HTTPS 跳转地址;平台确认订单支付成功后用于返回商户页面,最长 2048 字符 |
attach | string | 否 | 商户透传字段,最长 512 字符;通知时原样返回 |
请求示例:
{
"merchantOrderNo": "MP-EXAMPLE-001",
"amount": "500.00",
"currency": "INR",
"payinInterfaceStyle": "extended",
"notifyUrl": "https://merchant.example.com/mc/payin/notify",
"returnUrl": "https://merchant.example.com/payment/result",
"attach": "optional"
}
returnUrl 使用说明:
- 仅支持完整的
https://地址,不支持 HTTP 或 URL 中的账号密码;支持查询参数和单页应用 Hash 地址(例如#/payment/result)。 - 当前打开的收银台观察到订单从支付中变为支付成功时自动跳转一次;重新打开已成功订单时不自动跳转,可点击页面上的“RETURN TO MERCHANT”手动返回。
- UTR 提交成功仅代表平台已保存 UTR,不代表订单支付成功,也不会立即触发跳转;未传该字段时保持在收银台页面。
- 相同商户订单号重复建单仍会返回幂等冲突,不会更新首次订单的跳转地址。
成功响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
orderNo | string | 平台订单号 |
merchantOrderNo | string | 商户订单号 |
amount | string | 订单金额,固定返回两位小数 |
currency | string | 订单币种 |
status | string | 创建成功固定为 CREATED |
payUrl | string / null | 可直接公开访问的完整收银台 URL,无需追加 token 或其他参数 |
payinInterfaceStyle | string | 实际采用的接口样式:standard 或 extended |
merchantFee | string / null | 商户手续费,固定返回两位小数 |
utr | string / null | UTR;创建时通常为 null |
payee_upi | string / null | 收款 UPI / VPA |
cash_params | object / null | 扩展收银参数 |
payinInterfaceStyle 使用说明:
standard:使用payUrl展示支付页面;cash_params固定为null。extended:可使用cash_params中的支付信息构建自定义收银页面;无法提供扩展参数时为null。
cash_params 非空时包含:
| 字段 | 类型 | 说明 |
|---|---|---|
payee_upi | string | 收款 UPI / VPA |
remark | string | 支付备注 |
links.qr | string / null | UPI 二维码内容或支付链接 |
links.paytm | string / null | Paytm 唤醒链接 |
links.phonepe_ios | string / null | PhonePe iOS 唤醒链接 |
links.phonepe_android | string / null | PhonePe Android 唤醒链接 |
支付信息使用要求:
- 优先直接使用接口返回的
payUrl或cash_params.links,不要自行拼接或修改支付链接。 payUrl可直接交给付款人打开;订单过期后页面仍会展示订单信息和过期状态,但付款操作会被禁用。- 付款人已完成转账但订单页面已经过期时,仍可在该页面补交 UTR。
- 顶层
amount是订单金额;返回的支付链接中可能包含本次实际支付金额,两者可能存在小额差异。 - 使用自定义收银页面时,只展示非空的支付方式和链接。
重复请求规则:
- 同一商户下,
merchantOrderNo必须唯一。 - 同一
merchantOrderNo重复提交时返回40102,不会返回原订单数据。 - 每次 HTTP 重试都必须使用新的
x-nonce。
6. 查询代收订单
GET /open/mc/merchantPayin/query?orderNo=<ORDER_NO>
GET /open/mc/merchantPayin/query?merchantOrderNo=<MERCHANT_ORDER_NO>
Query 严格二选一:
| 字段 | 说明 |
|---|---|
orderNo | 平台订单号,最长 64 字符 |
merchantOrderNo | 商户订单号,最长 128 字符 |
成功响应在创建响应字段基础上增加:
| 字段 | 说明 |
|---|---|
createTime | UTC ISO 8601 创建时间 |
updateTime | UTC ISO 8601 更新时间 |
代收查询状态:
| 状态 | 说明 |
|---|---|
PENDING | 待处理 |
PAYING | 处理中 |
SUCCESS | 终态成功 |
REJECTED | 终态驳回 |
FAILED | 建单失败 |
EXPIRED | 已过期 |
CLOSED | 已关闭 |
7. 提交代收 UTR
POST /open/mc/merchantPayin/submitUtr
Content-Type: application/json
请求字段:
| 字段 | 必填 | 说明 |
|---|---|---|
orderNo | 是 | MC 平台代收订单号;本接口不接受商户订单号 |
utr | 是 | 12~32 位字母或数字;包含空白、连接符或其他符号时直接返回参数错误,合法值统一转为大写 |
请求示例:
{
"orderNo": "<ORDER_NO>",
"utr": "UTR123456789"
}
成功响应 data:
{
"orderNo": "<ORDER_NO>",
"merchantOrderNo": "MP-EXAMPLE-001",
"utr": "UTR123456789",
"status": "PAYING",
"verifyStatus": "SUBMITTED"
}
UTR 提交说明:
- 提交成功表示平台已经持久化 UTR,且本次 UTR 提交已经被支付服务接受。
- 提交成功不代表订单支付成功。
- 相同 UTR 重复提交幂等成功。
- 同一订单提交不同 UTR 返回参数错误。
- 最终结果以订单查询或异步通知为准。
8. 代收补单
POST /open/mc/merchantPayin/makeup
Content-Type: application/json
请求字段:
| 字段 | 必填 | 说明 |
|---|---|---|
orderNo | 是 | MC 平台代收订单号;本接口不接受商户订单号 |
utr | 是 | 12~32 位字母或数字;包含空白、连接符或其他符号时直接返回参数错误,合法值统一转为大写 |
请求示例:
{
"orderNo": "<ORDER_NO>",
"utr": "UTR123456789"
}
成功响应 data:
{
"orderNo": "<ORDER_NO>",
"merchantOrderNo": "MP-EXAMPLE-001",
"utr": "UTR123456789",
"status": "SUCCESS",
"result": "SUCCESS"
}
补单结果明确失败时返回:
{
"code": 40306,
"message": "Makeup failed",
"data": {
"orderNo": "<ORDER_NO>",
"utr": "UTR123456789",
"result": "FAILED"
}
}
补单仍在处理时返回:
{
"code": 1000,
"message": "处理中,请查询订单",
"data": {
"orderNo": "<ORDER_NO>",
"merchantOrderNo": "MP-EXAMPLE-001",
"utr": "UTR123456789",
"status": "PAYING",
"result": "PROCESSING"
}
}
补单暂时无法完成时返回:
{
"code": 40307,
"message": "Makeup exception, please contact operations",
"data": {
"orderNo": "<ORDER_NO>",
"utr": "UTR123456789",
"result": "EXCEPTION"
}
}
补单返回规则:
- 订单已有 UTR 时不能再次补单。
SUCCESS:补单成功。PROCESSING:处理中,请查询订单。FAILED:补单明确失败,本次提交的 UTR 会被删除。EXCEPTION:补单结果尚未明确,本次提交的 UTR 会保留;请查询订单或联系运营核实。PROCESSING或EXCEPTION时,平台不会自动重复提交补单,请查询订单确认最终状态。- 非人工终态订单补单成功后,订单状态更新为
SUCCESS,并按正常订单终态发送异步通知。 - 已人工终态的订单补单成功后,订单状态保持不变,并按当前终态信息发送异步通知。
- 订单最终状态以订单查询和异步通知为准。
9. 查询 UPI 是否已存在
GET /open/mc/merchantPayin/upiQuery?upi=receiver%40upi
Query:
| 字段 | 必填 | 说明 |
|---|---|---|
upi | 是 | UPI ID,例如 receiver@upi |
成功响应:
{
"code": 1000,
"message": "success",
"data": {
"status": "YES"
}
}
返回值说明:
status=YES:该 UPI 已存在。status=NO:该 UPI 不存在。
调用限制:默认同一商户 10 秒内只允许查询一次 UPI;过于频繁返回 40309。
10. 查询 UTR 是否已存在
GET /open/mc/merchantPayin/utrQuery?utr=UTR123456789
Query:
| 字段 | 必填 | 说明 |
|---|---|---|
utr | 是 | 单个 UTR,12~32 位字母或数字;包含空白、连字符或其他符号时直接返回 40100,合法值统一转为大写 |
成功响应:
{
"code": 1000,
"message": "success",
"data": {
"status": "YES",
"result": "CURRENT_MERCHANT",
"orderNo": "PP20260817174707288670"
}
}
返回值说明:
status=YES:已经找到该 UTR;具体情况必须继续读取result。status=NO:未找到该 UTR,或该 UTR 尚未支付;此时result固定为NOT_FOUND。result=AVAILABLE_FOR_MAKEUP:已找到且确认支付,但尚未关联订单,可以补单。这是唯一可以补单的查询结果。result=CURRENT_MERCHANT:已关联当前商户订单,并额外返回平台订单号orderNo。result=OTHER_MERCHANT:已关联订单,但不属于当前商户;不返回订单号。result=UTR_ORDER_ABNORMAL:已找到 UTR,但其订单状态异常;不可补单。- UTR 格式错误返回
40100,data=null。 40302:当前服务繁忙,请稍后再试(英文响应文案:Service is busy. Please try again later.)。
status 与 result 的合法组合如下;除 CURRENT_MERCHANT 外均不返回 orderNo:
status | result | 含义 | 是否可补单 |
|---|---|---|---|
YES | AVAILABLE_FOR_MAKEUP | 已支付,尚未关联订单 | 是 |
YES | CURRENT_MERCHANT | 已关联当前商户订单 | 否 |
YES | OTHER_MERCHANT | 已被其他商户领取 | 否 |
YES | UTR_ORDER_ABNORMAL | UTR 订单异常 | 否 |
NO | NOT_FOUND | 不存在或未支付 | 否 |
调用限制:默认同一商户 10 秒内只允许查询一次 UTR;过于频繁返回 40309。
11. 创建代付订单
POST /open/mc/merchantPayout/create
Content-Type: application/json
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchantOrderNo | string | 是 | 商户唯一订单号 |
amount | string | 是 | 两位小数金额字符串 |
currency | string | 是 | 当前使用 INR |
accountName | string | 是 | 收款人姓名 |
accountNo | string | 是 | UPI ID 或银行账号 |
payMethod | string | 是 | UPI / BANK |
platformName | string | BANK 必填 | 银行名称 |
ifscOrBankCode | string | BANK 必填 | IFSC/银行代码 |
notifyUrl | string | 否 | 终态通知地址 |
attach | string | 否 | 商户透传字段,最长 512 字符 |
UPI 示例:
{
"merchantOrderNo": "PP-UPI-EXAMPLE-001",
"amount": "500.00",
"currency": "INR",
"accountName": "Test User",
"accountNo": "receiver@upi",
"payMethod": "UPI",
"notifyUrl": "https://merchant.example.com/mc/payout/notify"
}
BANK 示例:
{
"merchantOrderNo": "PP-BANK-EXAMPLE-001",
"amount": "500.00",
"currency": "INR",
"accountName": "Test User",
"accountNo": "000000001234",
"payMethod": "BANK",
"platformName": "Test Bank",
"ifscOrBankCode": "TEST0000001",
"notifyUrl": "https://merchant.example.com/mc/payout/notify",
"attach": "optional"
}
成功响应 data:
{
"orderNo": "<ORDER_NO>",
"merchantOrderNo": "PP-UPI-EXAMPLE-001",
"amount": "500.00",
"merchantFee": "5.00",
"currency": "INR",
"status": "CREATED",
"freezeAmount": "505.00",
"utr": null
}
CREATED 只表示平台已受理并完成当前建单步骤,不表示收款人已到账。最终结果以代付查询或验签后的异步通知为准。
重复请求规则:
- 同一商户下,
merchantOrderNo必须唯一。 - 同一
merchantOrderNo重复提交时返回40102,不会返回原订单数据。 - 每次 HTTP 重试都必须使用新的
x-nonce。
12. 查询代付订单
GET /open/mc/merchantPayout/query?orderNo=<ORDER_NO>
GET /open/mc/merchantPayout/query?merchantOrderNo=<MERCHANT_ORDER_NO>
Query 严格二选一。
成功响应 data:
{
"orderNo": "<ORDER_NO>",
"merchantOrderNo": "PP-UPI-EXAMPLE-001",
"amount": "500.00",
"merchantFee": "5.00",
"currency": "INR",
"status": "PAYING",
"freezeAmount": "505.00",
"utr": null,
"createTime": "2026-07-24T08:00:00.000Z",
"updateTime": "2026-07-24T08:00:05.000Z"
}
utr 返回规则:
- 普通代付订单返回单个 UTR。
- 拆单父单成功时,返回所有成功子单的有效 UTR,多个 UTR 使用
-连接,例如UTR123456789-UTR987654321。 - 暂无有效 UTR 时返回
null。接收方应按可变长度字符串保存该字段。
代付查询状态:
| 状态 | 说明 |
|---|---|
PENDING | 待平台处理 |
PAYING | 平台处理中 |
SUCCESS | 终态成功 |
REJECTED | 终态驳回 |
FAILED | 平台建单失败 |
13. 异步通知
异步通知说明:
- 创建订单传入
notifyUrl后,订单进入可通知状态时,平台发送 HTTP POST JSON。 - 通知使用第 2、3 节的五个 Header 和同一套签名规则。
- 每次通知均生成新的 timestamp、nonce 和 sign。
13.1 代收通知字段
| 字段 | 类型 | 说明 |
|---|---|---|
orderNo | string | 平台订单号 |
merchantOrderNo | string | 商户订单号 |
status | string | SUCCESS / REJECTED / EXPIRED |
amount | string | 订单金额 |
currency | string | 币种 |
utr | string/null | UTR;没有时仍发送 null |
attach | string | 创建时传入才返回 |
代收成功通知示例:
{
"orderNo": "PI202607240001",
"merchantOrderNo": "MP-EXAMPLE-001",
"status": "SUCCESS",
"amount": "500.00",
"currency": "INR",
"utr": null,
"attach": "optional"
}
13.2 代付通知字段
| 字段 | 类型 | 说明 |
|---|---|---|
orderNo | string | 平台订单号 |
merchantOrderNo | string | 商户订单号 |
status | string | SUCCESS / REJECTED |
amount | string | 订单金额 |
merchantFee | string/null | 商户手续费 |
currency | string | 币种 |
utr | string/null | 普通订单为单个 UTR;拆单父单成功时为成功子单 UTR 的 - 连接值;没有时仍发送 null |
attach | string | 创建时传入才返回 |
代付拆单成功通知示例:
{
"orderNo": "PO202608210001",
"merchantOrderNo": "PP-SPLIT-EXAMPLE-001",
"status": "SUCCESS",
"amount": "1000.00",
"merchantFee": "10.00",
"currency": "INR",
"utr": "UTR123456789-UTR987654321"
}
代付失败通知示例:
{
"orderNo": "PO202607240001",
"merchantOrderNo": "PP-UPI-EXAMPLE-001",
"status": "REJECTED",
"amount": "500.00",
"merchantFee": "5.00",
"currency": "INR",
"utr": null
}
通知只包含本节声明的商户业务字段。
13.3 商户 ACK
平台收到以下任一响应即认为通知成功:
- HTTP 2xx,纯文本去除首尾空白后不区分大小写等于
success。 - HTTP 2xx,JSON 的
code为数字1000或字符串"1000"。
示例:
success
{
"code": 1000,
"message": "success"
}
14. 公共业务码
| code | 说明 |
|---|---|
1000 | 成功 |
40000 | 未分类请求失败 |
40001 | 鉴权 Header 缺失或非法 |
40002 | timestamp 非法或过期 |
40003 | 签名错误 |
40004 | 商户不存在、停用或 API 停用 |
40007 | nonce 非法或已使用 |
40100 | 请求参数错误 |
40101 | 金额错误 |
40102 | 商户订单号重复或幂等冲突 |
40201 | 当前商户下订单不存在 |
40301 | 余额或资金操作失败 |
40302 | 当前服务暂不可用 |
40304 | 当前订单不支持此操作 |
40305 | UTR 未被接受 |
40306 | 补单失败;订单已有的 SUCCESS 状态不会被覆盖 |
40307 | 补单暂时无法完成;请查询订单当前状态并联系运营处理 |
40308 | UTR 提交暂时无法完成;请查询订单当前状态后再决定是否重试 |
40309 | UPI 或 UTR 查询过于频繁 |
调用方应按 code 分类处理,不要解析 message 作为程序分支。
15. Node.js 签名示例
以下示例同时适用于请求签名和通知验签:
import crypto from 'node:crypto';
function clean(value) {
if (
value === undefined ||
value === null ||
(typeof value === 'string' && value.trim() === '') ||
(typeof value === 'number' && !Number.isFinite(value))
) {
return undefined;
}
if (Array.isArray(value)) {
return value.map(clean).filter(item => item !== undefined);
}
if (typeof value === 'object') {
return Object.keys(value)
.sort()
.reduce((result, key) => {
const item = clean(value[key]);
if (item !== undefined) result[key] = item;
return result;
}, {});
}
return value;
}
export function sign({
method,
path,
merchantNo,
timestamp,
nonce,
payload,
apiKey,
}) {
const upperMethod = method.toUpperCase();
const payloadBytes = JSON.stringify(clean(payload) ?? {});
const payloadSha256 = crypto
.createHash('sha256')
.update(payloadBytes, 'utf8')
.digest('hex');
const canonicalText = [
'MCV2-HMAC-SHA256',
'v2',
upperMethod,
path,
merchantNo,
String(timestamp),
nonce,
payloadSha256,
].join('\n');
return crypto
.createHmac('sha256', apiKey)
.update(canonicalText, 'utf8')
.digest('hex');
}
示例使用说明:
- GET 请求把 query 对象作为
payload。 - POST 请求和通知把解析后的 JSON 对象作为
payload。 - payload 先删除空值并按字段名递归排序,再计算签名。
- Header 同时发送
x-sign-version: v2。
16. 上线前检查
- 接口的方法和路径与第 1 节一致。
- 生产 Base URL 使用 HTTPS。
- 调用方服务器已同步时间,和平台服务器的偏差保持在 300 秒以内。
- 全部开放接口使用的固定公网出口 IP 已登记到平台白名单。
- 每次 HTTP 请求和通知尝试使用新的 timestamp、nonce、sign,并发送
x-sign-version: v2。 - method、pathname、merchantNo、timestamp、nonce 和 payload SHA-256 均进入签名。
- GET、POST 和通知都对删除空值、递归排序后的稳定 JSON 计算摘要。
- 金额统一使用两位小数字符串。
- 重复商户订单号返回
40102,不会返回原订单数据。 - 通知接口返回第 13.3 节声明的 ACK 格式。
- 商户实现只依赖本文声明的业务字段和状态。