星途 · API 对接文档

项 说明
版本 v1.0(2026-11-10)
适用对象 四方平台、聚合支付、电商系统等下游接入方
依据 以当前线上代码实现为准(GatewayController / CashierController / MachiPortalApiController / NotifyService / NotifyQueue / AdminController)
接口域名 https://www.hjdgawjkdn.top(以交付单/后台「支付网关域名」为准;同机部署可用 http://127.0.0.1:8080)

0. 快速开始

  1. 在码池后台「支付通道 → 下游应用(商户)」创建应用,拿到 API 编号(apiNo)、AppID、密钥(secret,只显示一次);
  2. 调用 POST /v1/pay/unified 下单,得到 payUrl;
  3. 让用户打开 payUrl(收银台页面),填写姓名后提交(页面会调用 POST /v1/cashier/{tradeNo}/intent);
  4. 轮询 GET /v1/cashier/{tradeNo} 或 GET /v1/pay/qr/status,直到 status=SUCCESS;
  5. 码池向你的 notifyUrl 发送异步回调,验签后返回纯文本 success。

1. 通用约定

1.1 请求方式与编码

项 约定
请求体 JSON(UTF-8),Content-Type: application/json
回调请求体 application/x-www-form-urlencoded(仅回调是表单)
字符集 UTF-8
金额 整数,单位:分(100 = 1.00 元)
时间 yyyy-MM-dd HH:mm:ss(Asia/Shanghai)
签名 HMAC-SHA256,小写十六进制
大小写 字段名区分大小写(appId、outTradeNo、payerName)

⚠️ 所有 POST 接口只接受 JSON。用表单提交会返回 {"code":"5001","msg":"内部错误:Content-Type 'application/x-www-form-urlencoded' is not supported"}, 这是最常见的 415 类失败原因。

1.2 统一返回结构

{ "code": "0", "msg": "SUCCESS", "data": { } }
HTTP 状态 场景
200 业务成功或业务失败(code 非 0 时以 code 为准)
401 受保护接口未登录/Token 失效(code=9001)
403 无权限(code=9002)
404 路径不存在(code=4004)

业务错误统一 HTTP 200,必须判断 code,不能只看 HTTP 状态码。


2. 鉴权与签名

2.1 凭证

字段 来源 说明
appId 后台/交付单 应用标识,如 SF4F001
secret 创建或重置时一次性展示 用于请求签名与回调验签,不要放在请求里传输

2.2 签名算法(下单)

  1. 取参与签名的字段:amount、appId、outTradeNo、payType;
  2. 按 key 的 ASCII 升序排列;
  3. 拼成 k1=v1&k2=v2&...(剔除空值);
  4. 末尾追加 &key=<secret>;
  5. 对整串做 HMAC-SHA256,密钥为 <secret>;
  6. 输出小写十六进制,放入请求字段 sign。

示例(金额 1.23 元):

待签串:amount=123&appId=SF4F001&outTradeNo=ORD20261110001&payType=wechat&key=<secret>
sign  :HMAC-SHA256(待签串, <secret>) 的小写十六进制

sign、sign_type 不参与签名;请求里不需要传 sign_type。

Python 示例

import hmac, hashlib

def sign(app_id, out_trade_no, pay_type, amount_fen, secret):
    raw = f"amount={amount_fen}&appId={app_id}&outTradeNo={out_trade_no}&payType={pay_type}"
    return hmac.new(secret.encode(), (raw + "&key=" + secret).encode(), hashlib.sha256).hexdigest()

PHP 示例

$raw  = "amount={$amount}&appId={$appId}&outTradeNo={$outTradeNo}&payType={$payType}";
$sign = hash_hmac('sha256', $raw . '&key=' . $secret, $secret);

2.3 回调验签

与下单同一套算法,参与字段为回调中的: appId、outTradeNo、tradeNo、amount、realAmount、tradeStatus、payTime (剔除 sign、sign_type 和空值,按 key 升序 + &key=<secret>,HMAC-SHA256 小写)。


3. 接口总览

# 接口 方法 用途 鉴权
1 /v1/pay/unified POST 统一下单 appId + 签名
2 /v1/cashier/{tradeNo} GET 收银台订单信息(轮询) 无(凭单号)
3 /v1/cashier/{tradeNo}/intent POST 提交付款人姓名 → 派单出码 无(凭单号)
4 /v1/pay/qr/status GET 查单状态 无
5 /v1/pay/query GET 订单详情 无
6 /v1/pay/qr/intent POST 旧版出码(兼容) 无
7 /machi-api/auth/login POST 码商登录 账号密码
8 /machi-api/orders/{tradeNo}/confirm POST 码商确认到账 码商 Token
9 你的 notifyUrl POST 码池 → 你 的异步回调 签名
10 /admin/orders/{tradeNo}/close POST 运营关单 管理 Token

4. 统一下单

POST /v1/pay/unified

4.1 请求参数

字段 类型 必填 说明
appId string 是 应用 ID,如 SF4F001
outTradeNo string 是 商户订单号,同一 appId 下唯一,≤64 字符
payType string 是 支付方式:推荐 wechat(微信)/ alipay(支付宝);历史大写编码(如 WXPAY_NATIVE、ALIPAY_QR)仍兼容,新接入请统一用小写
amount long 是 金额(分),默认区间 100 ~ 5000000(1 ~ 50000 元,后台可调)
subject string 否 商品标题,≤128 字符
notifyUrl string 否 异步回调地址(http/https,公网可达)。不传则使用「下游应用」里配置的默认回调地址;两者都为空则不回调
sign string 是 见 §2.2

4.2 成功响应

{
  "code": "0",
  "msg": "SUCCESS",
  "data": {
    "tradeNo": "P20261110222708557265353",
    "status": "WAITING",
    "amount": 123,
    "payUrl": "https://www.hjdgawjkdn.top/cashier/P20261110222708557265353",
    "expireAt": "2026-11-10 22:57:08"
  }
}
字段 说明
tradeNo 码池订单号(后续查单/回调使用)
status 固定为 WAITING(待填名/待派单)
amount 订单金额(分)
payUrl 收银台地址,把用户引导到此页面
expireAt 下单有效期(默认 30 分钟;填名后会按收银台设置重置)

4.3 幂等响应

同一 appId + outTradeNo 且金额一致重复请求时,返回原单(不会新建):

{
  "code": "0",
  "data": {
    "tradeNo": "P20261110222708557265353",
    "status": "PAYING",
    "amount": 123,
    "payUrl": "https://www.hjdgawjkdn.top/cashier/P20261110222708557265353",
    "expireAt": "2026-11-10 22:30:09",
    "idempotent": true
  }
}
  • 幂等命中时即使原单已支付/已关闭也返回原单,不会重新下单;
  • 需要新订单请更换 outTradeNo;
  • 并发重复请求由唯一键兜底,行为一致。

4.4 常见错误

code msg 示例 原因
1001 签名错误:expect=... 签名算法/字段/密钥不一致(注意金额单位、排序、小写)
1002 参数缺失或格式错误:... 必填缺失、notifyUrl 不合法、字段超长等
1003 应用不存在或已停用 appId 错误或应用被停用
2001 商户订单号重复且参数不一致 同单号改了金额等参数
2002 单笔金额需在 1.00 ~ 50000.00 元之间 金额超限
7001 暂无可用收款码,请稍后再试 该通道有码但当前无空闲槽位
5001 下单过于频繁,请稍后再试(每分钟上限 600 单) 触发限流
5001 内部错误:Content-Type ... is not supported 用了表单/其他 Content-Type(务必 JSON)

单笔区间、每分钟限流均可在后台「系统设置 → 支付参数」调整。


5. 收银台信息(轮询)

GET /v1/cashier/{tradeNo}

无需签名,凭 tradeNo 查询,用于收银台页面与商户轮询。

5.1 响应字段

字段 说明
tradeNo 码池订单号
amountFen 订单金额(分)
realAmountFen 实际应付金额(含金额指纹,可能与订单金额差 1~99 分)
status WAITING / PAYING / SUCCESS / FAILED / CLOSED
confirmState null / WAIT_CONFIRM / CONFIRMED / CANCELLED / TIMEOUT
orderState 派单状态:PENDING / PAYING / SUCCESS / CLOSED / DEAD
dispatchAttempts 派单尝试次数
queued 已填名、排队等派单(true 时继续轮询)
dead 连续派单失败,需人工处理
qrCode 收款码内容/图片地址;未派发时为 null
payerName / payerAccount 付款人姓名/账号
expireAt 过期时间
expireInSeconds 剩余秒数(服务端计算)
expireMinutes 收银台有效期(分钟,默认 3)
subject / payType / channelCode 订单信息
bank 银行卡信息(银行卡通道时返回)
method / methods 收银台可用支付方式

5.2 示例(已派发)

{
  "code": "0",
  "data": {
    "tradeNo": "P20261110222708557265353",
    "amountFen": 123,
    "realAmountFen": 123,
    "status": "PAYING",
    "confirmState": "WAIT_CONFIRM",
    "orderState": "PAYING",
    "queued": false,
    "dead": false,
    "qrCode": "DEMO-QR-MC0001",
    "payerName": "样例用户",
    "expireAt": "2026-11-10 22:30:09",
    "expireInSeconds": 177,
    "channelCode": "wechat"
  }
}

5.3 轮询建议

阶段 建议
未填名(WAITING + queued=false) 引导用户填姓名
已填名排队(queued=true) 每 500ms~1s 轮询,直到 qrCode 非空
已派发(PAYING) 每 1~2s 轮询,直到 SUCCESS/CLOSED
dead=true 停止轮询,联系平台

收银台页面另提供 SSE:GET /v1/cashier/{tradeNo}/events(可选用)。


6. 填名出码(intent)

POST /v1/cashier/{tradeNo}/intent

把付款人姓名写入订单、进入派单队列;派单是异步的,接口立即返回 queued=true,随后轮询 §5 获取二维码。

6.1 请求

{
  "payerName": "张三",
  "payerAccount": "zhangsan",
  "methodCode": "WECHAT_QR"
}
字段 必填 说明
payerName 视支付方式 付款人姓名,1~32 个字符;支付方式 need_payer_name=1(默认)时必填
payerAccount 否 付款账号,≤64 字符
methodCode 否 支付方式编码;不传则按订单 payType 自动选择

payerName 是 UTF-8 字符串,按字符数限制 32(不是字节数)。 中文名可以直接传,不需要 URL 编码(JSON 请求体)。

6.2 响应

{
  "code": "0",
  "data": {
    "tradeNo": "P20261110222708557265353",
    "status": "WAITING",
    "queued": true,
    "orderState": "PENDING",
    "payerName": "样例用户",
    "expireInSeconds": 179,
    "qrCode": null
  }
}
  • 已派发/已支付时重复调用是幂等的:直接返回当前视图(status=PAYING/SUCCESS,可能带 qrCode),不重复入队;
  • 订单已关闭/不存在时返回 4001/4002。

6.3 错误

code 原因
2004 缺少 payerName 或超过 32 字符
4001 订单不存在
4002 当前状态不可出码(如已关闭)
5001 Content-Type 不是 application/json(表单提交会触发)

必须用 JSON。表单提交返回: {"code":"5001","msg":"内部错误:Content-Type 'application/x-www-form-urlencoded' is not supported"}


7. 派发说明(异步)

项 说明
触发 调用 intent 后置 order_state=PENDING,后台每 1 秒扫描一次队列
结果 成功 → status=PAYING、confirmState=WAIT_CONFIRM、返回收款码;失败 → 自动退避重试
退避 10s / 30s / 90s / 300s / 600s,连续 6 次失败置 order_state=DEAD
容量 每张码 max_parallel 个并发槽(当前微信 10 槽、支付宝 5 槽);满时排队,不丢单
超时 填名后按收银台有效期(默认 3 分钟)自动关单并释放

8. 查单

GET /v1/pay/qr/status?tradeNo=P2026... 或 GET /v1/pay/qr/status?outTradeNo=ORD...&appId=SF4F001

{
  "code": "0",
  "data": {
    "tradeNo": "P20261110222708557265353",
    "status": "PAYING",
    "realAmount": 123,
    "payTime": null
  }
}
字段 说明
tradeNo 码池单号
status 订单状态
realAmount 实付金额(分)
payTime 支付时间;未支付为 null

错误:1002(两个单号都没传)、4001(查无订单)。


9. 订单详情

GET /v1/pay/query?tradeNo=P2026...

返回完整订单对象(节选):

{
  "code": "0",
  "data": {
    "tradeNo": "P20261110222708557265353",
    "outTradeNo": "SMP1794320828",
    "appId": "SF4F001",
    "payType": "wechat",
    "channelCode": "wechat",
    "amountFen": 123,
    "realAmountFen": 123,
    "feeFen": 4,
    "subject": "样例",
    "status": "PAYING",
    "payerName": "样例用户",
    "notifyUrl": "http://anduzf.cc/admin-api/pay/notify/merchant",
    "expireAt": "2026-11-10 22:30:09",
    "createdAt": "2026-11-10 22:27:08"
  }
}

10. 旧版出码(兼容保留)

POST /v1/pay/qr/intent(JSON)

{ "tradeNo": "P2026...", "payerName": "张三" }

响应:

{
  "code": "0",
  "data": {
    "tradeNo": "P2026...",
    "status": "PAYING",
    "qrCode": "...",
    "realAmount": 123,
    "realAmountYuan": "1.23",
    "expireAt": "2026-11-10 22:30:09"
  }
}

新接入建议用 §6 的 /v1/cashier/{tradeNo}/intent(支持 methodCode、幂等更好)。


11. 码商确认(码商端接口)

以下接口给码商 App/工作台使用,普通商户无需调用;列出用于说明“支付确认”链路。

11.1 登录

POST /machi-api/auth/login

{ "username": "machi01", "password": "******" }

返回 data.token(Bearer Token,有效期默认 8 小时)。

11.2 确认到账

POST /machi-api/orders/{tradeNo}/confirm Header:Authorization: Bearer <码商Token>

无请求体。成功返回:

{
  "code": "0",
  "msg": "SUCCESS",
  "data": {
    "msg": "确认成功",
    "depositBalance": 490834,
    "depositFrozen": 0,
    "profitBalance": 956,
    "platform_fee_fen": 135,
    "merchant_profit_fen": 15,
    "rate_bp": 1000,
    "confirm_state": "CONFIRMED"
  }
}

注意:platform_fee_fen、merchant_profit_fen、rate_bp、confirm_state 为下划线命名(数据库字段原样返回),其余为驼峰。

错误码 原因
9001 Token 缺失/失效
9002 非码商身份
4001 订单不存在或不属于该码商
4002 状态不是 WAIT_CONFIRM(已确认/已关闭)

确认成功后:订单 status=SUCCESS、解冻押金、写分账流水,并触发 §12 回调。


12. 异步回调(码池 → 商户)

12.0 回调地址从哪来

回调地址按以下优先级取:

  1. 下单请求里的 notifyUrl(推荐,按订单指定);
  2. 下单未传时,取「下游应用」配置的默认回调地址:
    • 管理后台 → 支付通道 → 下游应用(商户)→ 新增/编辑 → 回调地址;
    • 或商户控制台 → 接入设置 → 回调地址;
  3. 两者都为空:不回调。

应用级回调地址修改后,只影响之后新建的订单;已创建订单仍用下单时的地址。

12.1 请求

  • 方式:POST
  • Content-Type:application/x-www-form-urlencoded
  • 地址:下单时传入的 notifyUrl

参数(表单):

字段 示例 说明
appId SF4F001 应用 ID
outTradeNo P202611102226432 商户订单号
tradeNo P20261110222643937265346 码池订单号
amount 100 订单金额(分)
realAmount 100 实付金额(分,可能含金额指纹)
tradeStatus TRADE_SUCCESS 交易状态(支付成功时:TRADE_SUCCESS;部分链路为 SUCCESS,建议两者都认)
payTime 2026-11-10 22:26:46 支付时间
sign_type SHA256 签名类型(不参与签名)
sign 738219f6... HMAC-SHA256 小写十六进制

原始请求体示例:

appId=SF4F001&outTradeNo=P202611102226432&tradeNo=P20261110222643937265346&amount=100
&realAmount=100&tradeStatus=TRADE_SUCCESS&payTime=2026-11-10+22%3A26%3A46
&sign_type=SHA256&sign=738219f69575ab208a821d76f6ab3167a91eeb556c12f551e869ef7f45dc54da

12.2 验签(Python)

import hmac, hashlib, urllib.parse

def verify(form: dict, secret: str) -> bool:
    params = {k: v for k, v in form.items() if k not in ("sign", "sign_type") and v}
    raw = "&".join(f"{k}={params[k]}" for k in sorted(params)) + "&key=" + secret
    expect = hmac.new(secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
    return expect == form.get("sign", "").lower()

12.3 成功判定

服务端按以下条件判定回调成功:

HTTP 200 且 响应体 trim 后(忽略大小写)等于 "success"

必须返回纯文本 success,不要返回 JSON、HTML 或空响应,否则会重试。

12.4 重试机制

项 说明
触发时机 订单确认到账后入队,异步投递
丢败重试 最多 12 次
退避间隔 15s → 30s → 1m → 2m → 5m → 10m → 30m → 1h → 2h → 6h → 12h → 24h
超限 置 DEAD,停止自动重试(需人工重投)
幂等 同一订单只入队一次;回调是 at-least-once,请按 tradeNo 去重
超时 单次请求超时 5 秒

未传 notifyUrl 时不会收到回调,且会在通知队列留下失败任务;请务必传有效地址。

12.5 回调地址要求

  • 必须是 http:// 或 https:// 公网可达地址;
  • 禁止内网/环回地址(平台有 SSRF 校验,白名单可在后台配置);
  • 建议独立接口、快速返回 success,业务处理异步化。

13. 运营关单

POST /admin/orders/{tradeNo}/close Header:Authorization: Bearer <管理后台Token>

{ "operator": "admin", "reason": "用户取消" }

行为:

订单状态 结果
码商单且 WAIT_CONFIRM 走结算释放:解冻押金、释放码位、写流水,confirmState=CANCELLED,订单 CLOSED
WAITING / PAYING(非码商待确认) 订单 CLOSED,释放金额槽/码位
已支付/已关闭 返回 4002

关单不会触发支付成功回调;商户通过轮询会看到 CLOSED。


14. 错误码总表

code 含义 常见原因
0 成功 —
1001 签名错误 签名串/密钥/金额单位/排序不对
1002 参数缺失或格式错误 必填缺失、URL 非法、字段超长
1003 应用不存在或已停用 appId 错误或应用被停用
1005 请求过于频繁 预留(当前下单限流返回 5001)
2001 商户订单号重复且参数不一致 同单号改金额/支付方式
2002 金额非法 超出单笔区间
2003 支付方式不支持 payType 不在支持列表
2004 付款人姓名非法或缺失 未传/超过 32 字符
3001 无可用通道 通道未配置/被停用
4001 订单不存在 单号错误
4002 订单状态不允许该操作 已支付/已关闭再操作
4003 任务已被处理 并发确认/重复操作
4004 接口不存在 路径错误(HTTP 404)
5001 系统繁忙/内部错误 限流、Content-Type 非 JSON、未处理异常
7001 余额不足,无法派单 通道有码但无空闲槽位
9001 未登录或登录已过期 管理/码商 Token 无效(HTTP 401)
9002 无权限访问该资源 角色不符(HTTP 403)
9003 / 9004 账号/密码错误、账号停用 登录失败

15. 状态枚举

字段 取值
status(订单) WAITING 待填名/待派单、PAYING 已派发待支付、SUCCESS 支付成功、FAILED 失败、CLOSED 已关闭
confirmState null 未派发、WAIT_CONFIRM 待确认、CONFIRMED 已确认、CANCELLED 已取消、TIMEOUT 已超时关闭
orderState PENDING 排队、PAYING 已派发、SUCCESS 成功、CLOSED 关闭、DEAD 派单失败
回调 tradeStatus TRADE_SUCCESS / SUCCESS

16. 常见失败与排查

现象 原因与处理
返回 5001 且 msg 含 Content-Type ... not supported 用表单/其他 Content-Type 调用了 JSON 接口;改 application/json
1001 签名错误:expect=... 用返回的 expect 对比自己的签名串:①金额是否用分;②字段是否按 ASCII 排序;③末尾是否 &key=secret;④HMAC-SHA256 小写
2001 同 outTradeNo 改了金额/参数;换单号或保持参数一致
2004 payerName 缺失或超 32 字符(中文按字符计)
7001 码位已被占满;稍后重试或扩码位
一直 WAITING、queued=false 还没填姓名;先调 intent
queued=true 长时间不派发 检查码位容量/通道状态;dead=true 需人工处理
收不到回调 检查 notifyUrl 是否公网可达、返回是否 success;回调任务 12 次后置 DEAD
回调验签失败 先对参数做 URL 解码再验签;剔除 sign/sign_type/空值
9001/9002 管理/码商 Token 缺失或角色不符

17. 完整示例(Python)

import hashlib, hmac, json, time, urllib.request

BASE   = "https://www.hjdgawjkdn.top"
APP_ID = "<APP_ID>"
SECRET = "<SECRET>"

def sign(out_trade_no, pay_type, amount_fen):
    raw = f"amount={amount_fen}&appId={APP_ID}&outTradeNo={out_trade_no}&payType={pay_type}"
    return hmac.new(SECRET.encode(), (raw + "&key=" + SECRET).encode(), hashlib.sha256).hexdigest()

def post(path, body):
    req = urllib.request.Request(BASE + path, data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=15) as r:
        return json.loads(r.read().decode())

out_trade_no = "ORD" + str(int(time.time()))
body = {
    "appId": APP_ID,
    "outTradeNo": out_trade_no,
    "payType": "wechat",
    "amount": 123,
    "subject": "测试商品",
    "notifyUrl": "https://your-domain.com/notify",
}
body["sign"] = sign(out_trade_no, "wechat", 123)
order = post("/v1/pay/unified", body)
print(order)                      # 把 payUrl 给用户打开

trade_no = order["data"]["tradeNo"]

# 用户填名后(通常由收银台页面完成,这里演示接口)
post(f"/v1/cashier/{trade_no}/intent", {"payerName": "张三"})

# 轮询到成功
for _ in range(60):
    with urllib.request.urlopen(f"{BASE}/v1/cashier/{trade_no}", timeout=10) as r:
        info = json.loads(r.read().decode())["data"]
    if info["status"] in ("SUCCESS", "CLOSED"):
        break
    time.sleep(1)
print(info["status"], info.get("qrCode"))

18. 话费充值 H5

话费订单创建结果会返回 landingUrl,用户打开后进入 /phone/{landingToken} 充值引导页。手机号中间四位脱敏显示;页面进入后自动开启核对窗口,不需要用户点击“开始充值”。

  • GET /v1/phone/public/{token} 订单详情与充值步骤
  • POST /v1/phone/public/{token}/start 页面自动调用,开启核对窗口
  • POST /v1/phone/public/{token}/claim 用户确认已完成充值

话费优先金额规则(管理端)

入口:话费管理 → 渠道与金额。配置保存到 sys_config,下一笔派单生效,无需重启。

  • GET /admin/phone/collect-config 读取开关、模式、金额区间、指定金额
  • POST /admin/phone/collect-config 保存配置:enabled、mode(RANGE/LIST)、minFen、maxFen、amounts(分)、windowSeconds、claimMinSeconds

当前配置为 1–200 元优先话费;号码池不可用时回退码商码池。


19. 变更记录

日期 变更
2026-11-10 v1.0 首版:下单、收银台、intent、派发、查单、码商确认、回调、幂等、关单、错误码