商户 API 对接手册

商户 API 对接手册

更新:2026-09-11

01 接入说明

开通时平台提供:环境地址、app_idapi_keyapi_secret,以及本商户已开通的支付方式清单(含各方式支持的币种与收付方向)。商户需提供:收款与出款的通知地址、服务器出口 IP(加入白名单后才能调用接口)。

方法路径用途
POST/api/v1/payin/create代收下单
GET/api/v1/payin/{payin_order_no}代收查单
POST/api/v1/payout/create代付下单
GET/api/v1/payout/{payout_order_no}代付查单
GET/api/v1/balance余额查询

1.1 通用约定

  • 请求与响应均为 JSON,UTF-8 编码;所有接口按第 2 节签名。
  • 统一响应:{"code": 0, "message": "成功", "data": {…}, "trace_id": "…", "timestamp": 1756450000}code=0 表示请求已受理,不表示支付成功,订单结果看 data.status;失败时无 data。排障时提供 trace_id
  • data 中的字段一律为字符串,无值为空串 "",不会出现 null
  • 金额为主单位十进制字符串,如 "100.00";小数位不得超过币种精度,不补零也可,平台不做四舍五入。
  • 时间为 RFC3339 格式的 UTC 时间,如 2026-09-11T08:00:00Z
  • 订单状态 status 为字符串枚举,取值见第 9.1 节;通知中的 status 只有 SUCCESSFAIL
  • merchant_order_no 是商户订单号,也是幂等键:同一订单号重发相同内容返回原订单;内容不同返回 10003,不会产生第二笔。收不到响应时用原订单号原样重发,不要换号。

02 签名

2.1 请求头

Header说明
X-App-Id平台分配的应用 ID
X-Api-Key平台分配的 API Key
X-TimestampUnix 秒级时间戳,与服务器时间差不得超过 300 秒
X-Nonce随机字符串,单次使用;每次发送(含重试)都要重新生成
X-Sign按 2.2 计算的签名,小写十六进制

2.2 签名算法

HMAC-SHA256,密钥为 api_secret,结果取小写 hex。待签名串固定 8 段,以换行符 \n 连接;某段为空时留空,分隔符不可省略:

1METHOD
2PATH
3QUERY
4APP_ID
5API_KEY
6TIMESTAMP
7NONCE
8SHA256_HEX(RAW_BODY)
  1. METHOD 大写。
  2. PATH 为实际请求路径,含路径参数的真实值(如 /api/v1/payin/PO20260829000123),不是路由模板。
  3. QUERY 为规范化查询串:参数按键升序、同键多值按值升序,键和值分别 URL 编码,空格编码为 +! ' ( ) * 必须转义,再以 k=v& 连接;无查询参数时为空串。实际请求 URL 使用同一规范化串。
  4. 最后一段是原始请求体字节的 SHA-256 小写 hex;无 body 的请求对空字节串取哈希,即 e3b0c442…7852b855。对实际发送的字节计算,不要反序列化后重新序列化。

2.3 签名示例(Python)

其他语言按同样规则实现即可。使用前替换示例域名和三项凭据;api_secret 只保存在服务端。

import hashlib, hmac, time, uuid, requests
from urllib.parse import urlencode

APP_ID = "your_app_id"
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE = "https://api.example.com"

def canonical_query(params: dict) -> str:
    if not params:
        return ""
    pairs = []
    for k in sorted(params):
        vs = params[k] if isinstance(params[k], list) else [params[k]]
        for v in sorted(map(str, vs)):
            pairs.append((str(k), v))
    return urlencode(pairs)

def sign(method: str, path: str, query: dict, body: bytes, ts: str, nonce: str) -> str:
    body_sha = hashlib.sha256(body).hexdigest()
    source = "\n".join([
        method.upper(), path, canonical_query(query),
        APP_ID, API_KEY, ts, nonce, body_sha,
    ])
    return hmac.new(API_SECRET.encode(), source.encode(), hashlib.sha256).hexdigest()

def request(method: str, path: str, query: dict | None = None, body: bytes = b""):
    ts = str(int(time.time()))
    nonce = uuid.uuid4().hex
    headers = {
        "X-App-Id": APP_ID,
        "X-Api-Key": API_KEY,
        "X-Timestamp": ts,
        "X-Nonce": nonce,
        "X-Sign": sign(method, path, query or {}, body, ts, nonce),
        "Content-Type": "application/json",
    }
    query_string = canonical_query(query or {})
    url = BASE + path + (("?" + query_string) if query_string else "")
    return requests.request(method, url, headers=headers, data=body, timeout=10, allow_redirects=False)

2.4 签名测试向量

接入时先用下面三组向量自检,逐段比对签名原文,再比最终签名。三组凭据固定为 app_id=app_demoapi_key=key_demoapi_secret=secret_demoX-Timestamp=1789000000X-Nonce=nonce_demo。签名原文中的换行是真实换行符。

A 无请求体、无查询参数
QUERY
(空)
RAW_BODY
(空)
SHA256_HEX(RAW_BODY)
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
签名原文
GET
/api/v1/balance

app_demo
key_demo
1789000000
nonce_demo
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
预期 X-Sign
21b0a67b99490232913d9bf2e1206593ab79285441465d925c26f3ff50c62912
B 有请求体
QUERY
(空)
RAW_BODY
{"merchant_order_no":"PM1","amount":"100.00","currency":"PKR","pay_method":"JAZZCASH"}
SHA256_HEX(RAW_BODY)
0bf9b9e1505c66dbab60de357ffaff19a19e282eafc4a010f08135d42a0285de
签名原文
POST
/api/v1/payin/create

app_demo
key_demo
1789000000
nonce_demo
0bf9b9e1505c66dbab60de357ffaff19a19e282eafc4a010f08135d42a0285de
预期 X-Sign
c769c86cb9289b60d5f185a55e472ea36e843451c7580d3ff060e810b1eca980
C 同键多值与空格
QUERY
probe=A&probe=a+b&x=1
RAW_BODY
(空)
SHA256_HEX(RAW_BODY)
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
签名原文
GET
/api/v1/balance
probe=A&probe=a+b&x=1
app_demo
key_demo
1789000000
nonce_demo
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
预期 X-Sign
0dce9b3f6782a672447c55becb932a90be345f3309c50e39d7c8d32346d8fea2

2.5 排查

  • 20001:A 组不过是段序或分隔符错;B 组不过是 body 哈希没按实际字节算;C 组不过是查询串规范化不一致,JavaScript 的 encodeURIComponent 默认不转义 ! ' ( ) *。三组都过仍失败,检查 api_secret 是否用错环境、PATH 是否写成了路由模板。
  • 偶发 20001 或时间戳报错:服务器时间漂移超过 300 秒,做 NTP 同步;X-Nonce 重复也会被拒。
  • 20003:出口 IP 不在白名单内,这一层在验签之前。经 NAT 或多可用区部署时出口 IP 可能不止一个,全部提供给技术支持。

03 代收下单 POST /api/v1/payin/create

3.1 请求参数

字段类型必填说明
merchant_order_nostring(≤64)商户订单号,商户内唯一,同时是幂等键
amountstring(≤32)金额,主单位字符串,如 "100.00"
currencystring币种代码,如 PKR
pay_methodstring(2–32)支付方式,如 JAZZCASH。取自开通时提供的清单
product_namestring(≤128)商品名
notify_urlstring(≤512)本单通知地址;为空则用商户默认代收通知地址
return_urlstring(≤512)付款后页面跳转地址;页面跳转不能作为支付成功凭据
payer_namestring(≤128)付款人姓名
payer_phonestring(≤32)付款人手机号,巴基斯坦为 03 开头 11 位;部分支付方式必填
payer_id_nostring(≤64)付款人证件号,巴基斯坦为 13 位 CNIC;直接扣款方式必填
payer_emailstring(≤128)付款人邮箱

3.2 响应参数

字段说明
payin_order_no平台订单号,查单用
merchant_order_no商户订单号回显
amount / currency金额与币种回显,与落库订单一致
pay_method支付方式,如 JAZZCASHEASYPAISA
status订单状态,见第 9.1 节
cashier_url平台收银台地址,可直接引导付款人打开
pay_url上游支付链接;仅 PENDING 时有值
qr_code二维码内容,用于生成付款二维码;仅 PENDING 时有值
channel_order_no支付参考编号,可能为空
fail_code / fail_reason失败码与失败说明,仅 FAIL 时有值;取值见第 9.2 节
{
  "code": 0,
  "message": "成功",
  "data": {
    "payin_order_no": "PO202609110001",
    "merchant_order_no": "M202609110001",
    "amount": "100.00",
    "currency": "PKR",
    "pay_method": "JAZZCASH",
    "pay_method": "JAZZCASH",
    "status": "PENDING",
    "cashier_url": "https://cashier.example.com/orderPage/PO202609110001?t=…",
    "pay_url": "https://pay.example.com/PO202609110001",
    "qr_code": "",
    "channel_order_no": "",
    "fail_code": "",
    "fail_reason": ""
  },
  "trace_id": "…",
  "timestamp": 1789000000
}
按 status 处理

PENDING:用 pay_urlqr_code 引导付款,或打开 cashier_urlFAIL:看 fail_code,需要再收款时用新的 merchant_order_no 下单。INIT / PROCESSING:上游尚未返回,查单获取后续状态或引导付款人打开 cashier_url,不要当作失败。

04 代收查单 GET /api/v1/payin/{payin_order_no}

路径参数为平台订单号。查单结果是订单状态的权威来源;通知可能重复、延迟或耗尽重试,收到通知后仍以查单为准。

字段说明
payin_order_no / merchant_order_no平台订单号 / 商户订单号
status订单状态,见第 9.1 节
amount / currency金额与币种
pay_method本单的支付方式
channel_order_no支付参考编号,可能为空
pay_url / qr_codePENDING 时有值
notify_url / return_url下单时传入的地址回显
expire_time订单过期时间
success_time支付成功时间,未成功为空
fail_code / fail_reasonFAIL 时有值
created_at创建时间
{
  "payin_order_no": "PO202609110001",
  "merchant_order_no": "M202609110001",
  "status": "SUCCESS",
  "amount": "100.00",
  "currency": "PKR",
  "pay_method": "JAZZCASH",
  "pay_method": "JAZZCASH",
  "channel_order_no": "JC8823717",
  "pay_url": "",
  "qr_code": "",
  "notify_url": "https://merchant.example.com/notify",
  "return_url": "",
  "expire_time": "2026-09-11T08:30:00Z",
  "success_time": "2026-09-11T08:05:12Z",
  "fail_code": "",
  "fail_reason": "",
  "created_at": "2026-09-11T08:00:00Z"
}

05 代付下单 POST /api/v1/payout/create

5.1 请求参数

字段类型必填说明
merchant_order_nostring(≤64)商户订单号,商户内唯一,同时是幂等键
amountstring(≤32)金额,主单位字符串
currencystring币种代码
pay_methodstring(2–32)支付方式。取自开通时提供的清单
receiver_namestring(≤128)收款人姓名
receiver_accountstring(≤128)收款账号:钱包代付填钱包账号(巴基斯坦为 03 开头 11 位手机号);银行代付填银行账号或 IBAN(巴基斯坦为 PK 开头 24 位)
receiver_bank_codestring(≤64)收款银行代码,银行代付必填;按开通时提供的支持银行清单填写
receiver_phonestring(≤32)收款人手机号,巴基斯坦为 03 开头 11 位;部分银行代付必填
receiver_id_nostring(≤64)收款人证件号,巴基斯坦为 13 位 CNIC;钱包代付必填
receiver_emailstring(≤128)收款人邮箱;部分支付方式必填

缺少所选支付方式要求的收款人字段时返回 10001message 会指出缺哪一项。

5.2 响应参数

字段与代收下单同构:payout_order_nomerchant_order_noamountcurrencypay_methodstatusfail_codefail_reason。受理成功时 statusINIT,出款结果通过通知和查单获取。

{
  "payout_order_no": "PT202609110001",
  "merchant_order_no": "W202609110001",
  "amount": "500.00",
  "currency": "PKR",
  "pay_method": "JAZZCASH",
  "status": "INIT",
  "fail_code": "",
  "fail_reason": ""
}
余额

下单成功后,订单金额加手续费从可用余额转入冻结余额;出款失败后解冻回可用余额,出款成功后从冻结余额扣除。可用余额不足时无法下单。

06 代付查单 GET /api/v1/payout/{payout_order_no}

字段说明
payout_order_no / merchant_order_no平台订单号 / 商户订单号
status订单状态,见第 9.1 节
amount / currency金额与币种
pay_method本单的支付方式
receiver_name / receiver_account / receiver_bank_code收款人信息回显,账号脱敏
success_time出款成功时间,未成功为空
fail_code / fail_reasonFAIL 时有值
created_at创建时间
{
  "payout_order_no": "PT202609110001",
  "merchant_order_no": "W202609110001",
  "status": "SUCCESS",
  "amount": "500.00",
  "currency": "PKR",
  "pay_method": "JAZZCASH",
  "receiver_name": "Ali Khan",
  "receiver_account": "0300****1234",
  "receiver_bank_code": "",
  "success_time": "2026-09-11T09:12:40Z",
  "fail_code": "",
  "fail_reason": "",
  "created_at": "2026-09-11T09:10:00Z"
}

PENDING_VERIFY 表示出款结果尚未确认,资金仍冻结。此时用新订单号重新下单可能造成重复打款:保留原订单,等待通知或继续查单,长时间未更新时携带平台订单号联系技术支持。

07 余额查询 GET /api/v1/balance

无请求参数,返回当前商户各币种余额。总余额 = 可用余额 + 冻结余额。

{
  "as_of": "2026-09-11T08:00:00Z",
  "balances": [
    {
      "currency": "PKR",
      "available_balance": "12345.00",
      "frozen_balance": "100.00",
      "total_balance": "12445.00"
    }
  ]
}

08 异步通知

订单进入 SUCCESSFAIL 时,平台向商户通知地址发送 POST 请求。代收优先使用下单时的 notify_url,为空则用商户默认代收通知地址;代付使用商户默认代付通知地址。地址未配置则不通知,请依赖查单。

8.1 通知报文

{
  "biz_type": "PAYIN",
  "order_no": "PO202609110001",
  "merchant_order_no": "M202609110001",
  "amount": "100.00",
  "currency": "PKR",
  "status": "SUCCESS",
  "fail_code": "",
  "fail_reason": "",
  "finished_at": "2026-09-11T08:05:12Z"
}
字段说明
biz_typePAYIN 代收,PAYOUT 代付
order_no平台订单号(payin_order_nopayout_order_no
merchant_order_no商户订单号
amount / currency金额与币种,请与商户订单核对
statusSUCCESSFAIL
fail_code / fail_reason失败时有值,见第 9.2 节
finished_at订单终态时间

8.2 验签

通知携带与商户请求相同的 5 个签名 Header,按第 2 节规则对通知地址的实际路径、查询参数和原始请求体签名。必须验签通过后再处理,可复用自己的签名代码:

import hmac, json, time
from flask import Flask, request
app = Flask(__name__)

@app.post("/notify")
def on_notify():
    body = request.get_data()                 # 原始字节,勿先解析再序列化
    ts = request.headers.get("X-Timestamp", "")
    nonce = request.headers.get("X-Nonce", "")
    got = request.headers.get("X-Sign", "")
    query = request.args.to_dict(flat=False)  # 保留同名参数的全部值
    want = sign("POST", request.path, query, body, ts, nonce)
    if not hmac.compare_digest(got, want):
        return "invalid sign", 401
    if abs(time.time() - int(ts or 0)) > 300:
        return "expired", 401
    handle_notification(json.loads(body))     # 商户实现:核对订单、金额、币种后更新本地订单
    return "success", 200

8.3 应答与重试

  • 验签并处理完成(或可靠保存待处理)后,5 秒内返回 HTTP 200,响应体为纯文本 success。其他响应或超时视为失败;通知地址不得返回重定向。
  • 失败后最多重试 3 次,间隔约 15 秒、1 分钟、5 分钟;超过 30 分钟停止。
  • 通知可能重复或乱序。按 biz_type + order_no 定位订单,同一结果重复到达不得重复处理;收到与本地不同的结果时先查单核对再更新。
  • 未收到通知不表示订单失败,以查单为准;长时间无通知时主动查单,逐步拉长间隔。

09 状态与错误码

9.1 订单状态 status

取值适用含义商户处理
INIT代收 / 代付订单已创建等待通知或查单
PENDING代收等待付款用支付链接或二维码引导付款
PROCESSING代收 / 代付上游处理中等待通知或查单,不要重复下单
SUCCESS代收 / 代付成功(终态)核对订单号、金额、币种后更新商户订单
FAIL代收 / 代付失败(终态)fail_code 决定是否用新订单号重新下单
CLOSED代收 / 代付已关闭或过期(终态)停止使用原支付链接;需要时用新订单号下单
PENDING_VERIFY代收 / 代付结果待确认既非成功也非失败,继续查单;不要重新下单,代付侧会造成重复打款

9.2 失败码 fail_code

订单为 FAIL 时有值。fail_reason 仅供展示,逻辑判断以 fail_code 为准;取值只增不改,遇到未列出的值按 UNSPECIFIED 处理。

fail_code含义能否重新下单
CHANNEL_REJECTED渠道拒绝交易可以,用新的 merchant_order_no
PAYMENT_TIMEOUT支付窗口内未完成付款可以,用新的 merchant_order_no
PENDING_MANUAL_REVIEW结果不明,平台已挂起核查不要重试,上游可能已扣款或已出账;等待后续通知或查单
CHANNEL_UNAVAILABLE暂无可用支付服务重试通常无用,联系技术支持
ORDER_CLOSED订单已关闭或取消需要时用新订单号下单
UNSPECIFIED未归类的失败先查单确认原订单结果再决定

9.3 响应码 code

code 描述本次接口调用的结果,与订单结果无关。请按 code 分支,不要解析 message 文案。

codeHTTP含义商户处理
10000500系统错误用原订单号原样重发或查单;持续出现请提供 trace_id 联系平台
10001400参数错误message 修正参数后重发
10002404资源不存在确认平台订单号及所用凭据后重查
10003409订单号冲突merchant_order_no 已用于内容不同的订单;用查单确认原订单,或换新订单号下单
10004503服务暂不可用退避后用原订单号原样重发
10005400币种小数位不受支持amount 小数位超过币种精度,平台不做四舍五入
10006400支付服务未开通pay_method 未对本商户开通,联系技术支持;重发不会改变结果
10007400支付服务暂时无法受理通常是金额超出该支付方式可受理范围;稍后原样重发,或改用其他已开通的支付方式
20001401签名验证失败按第 2.4 节测试向量自检
20003403访问被拒绝出口 IP 不在白名单内
20004403商户被冻结或不可用联系平台
20005403商户被禁用联系平台
20006429触发限流退避后重试

10 附录:支付方式与支持银行

代收当前支持巴基斯坦(PKR)的 JazzCash、Easypaisa 与二维码扫码;代付支持钱包与银行转账。本商户实际可用的支付方式、币种、下单编码与支持银行清单以开通时提供的为准,不要自行推测银行代码。

  • pay_method 只需传支付方式本身:收付方向由接口决定(/payin/create/payout/create),币种由 currency 指定,平台据此唯一确定一条支付通道。银行名称或银行代码不能替代它。建单与查单响应里回显同一个 pay_method
  • 银行代付:receiver_bank_code 按支持银行清单原样填写,receiver_account 填银行账号或 IBAN。
  • 钱包代付:receiver_account 填钱包手机号,通常还需 receiver_id_no
  • 上线前用测试凭据各完成一次代收和代付,并验证:用原订单号重发不产生第二笔;篡改通知报文后验签失败;重复通知不重复处理。验收通过后由平台单独开通生产凭据,测试凭据不得复用。
商户 API 对接手册 · 更新 2026-09-11 · 如有疑问请联系技术支持。