CDK-Scan 对外 API 对接文档
版本 v3.1(对外版) | 出具日期 2026-10-29 | Base URL https://jmlscan.cloud-ip.cc(TLS) 范围 本文档只覆盖对接方需要的接口(对外业务 / 公开只读 / 账号侧)。 供货商端、管理端等内部接口不在本文档内,也不对外提供。
验证状态:路由面(401/404 探针枚举)、限流阈值与运行时参数已于 2026-10-29 全量重新实测并据此更正(§3.4.1 单价、§8 频率限制、§4 路由清单)。
本文档与旧版(/api-doc)的差异
| # | 差异 | 说明 |
|---|---|---|
| 1 | 移除内部接口 | 旧页面曾公开列出管理端内部接口(含物理删除类操作)。本页面为公网免鉴权访问,这些内容已全部移除,只保留对接方需要的对外接口。 |
| 2 | 补全字段级说明 | 旧页面只有"路径 + 一行描述",无请求头表、字段表、校验规则、状态枚举、错误码。本文档全部补齐。 |
| 3 | 错误示例为真实报文 | 下文所有错误响应均为本实例实测原文,非手写。 |
| 4 | 明确对外接口只有 2 个 | 探针实测:对接方业务接口仅 POST/GET /api/v1/client/orders,其余 client/* 路径均为 404。 |
| 5 | 参数标注"以实时接口为准" | 单价等运行时可变的字段,请读 GET /api/v1/public/scan/config,不要硬编码。 |
0. 一页速览
| 项目 | 内容 |
|---|---|
| 是否已有对外接口 | ✅ 已有,无需新开发 |
| Base URL | https://jmlscan.cloud-ip.cc(TLS)兼容入口 https://jmlscan.cloud-ip.cc(明文,不建议用于生产) |
| 对接方式 | 两条路,按需选一条(详见 §3.0)<br>A. CDK 直连 —— 只凭 CDK,不用注册 / 邀请码 / API Key<br>B. 账号 API Key —— 需注册(邀请码制)+ 建 Key,可查订单 / 有账户体系 |
| 方式 A 端点 | POST /api/v1/public/scan/orders 凭证 = 请求体 cdk_codes 频率 1800 次/分(约 30 次/秒) |
| 方式 B 端点 | POST /api/v1/client/orders(提交)、GET /api/v1/client/orders(查询) 凭证 = 请求头 X-API-Key 频率 60 次/分 |
| 开户方式(仅方式 B 需要) | 邀请码制:需联系站长获取 invite_code(见 §2.1) |
| 🔴 AccessToken(AT) | 必填,每条链接对应一个 AT,见 §3.1 |
| 🔴 允许的支付链接 | 仅 pay.nicepay.co.kr/* 与 payments.stripe.com/upi/instructions/* |
| 单价 | 0.5 / 条(实时值读 GET /api/v1/public/scan/config) |
| 单批上限 | 200 条 |
| 幂等 | 强制 Idempotency-Key 请求头(8–80 字符) |
| 频率限制 | 对外业务 /client/* 60 次/分钟;认证接口 10 次/分钟;公开只读接口 1800 次/分钟(均按来源 IP)。详见 §8 |
| 订单超时回收 | claimed / processing 超 8 分钟未推进 → 自动取消并退费 |
| 回调 | ❌ 不支持,需轮询 |
| 验证状态 | 2026-09-23 全量实测通过,测试数据零残留 |
1. 基础约定
1.1 协议与地址
| 项 | 值 |
|---|---|
| 对接地址 | https://jmlscan.cloud-ip.cc(TLS,推荐)兼容入口 https://jmlscan.cloud-ip.cc(明文,不建议传输 API Key) |
| 传输协议 | HTTP |
| 数据格式 | JSON,UTF-8 |
Content-Type | application/json |
| 请求体上限 | 1 MiB(超出 → body 读取失败) |
⚠️ `scan.fishmail.vip` 是另一套独立部署实例,不是本文档对接对象。 所有可变参数请运行时从
GET /api/v1/public/scan/config读取,不要硬编码。
1.2 统一响应信封
成功
{ "code": "OK", "data": { }, "message": "success" }
失败(无 data 字段)
{ "code": "AT_REQUIRED", "message": "每个支付链接都必须提供对应的 AccessToken(AT)" }
判断依据:HTTP 状态码 + `code` 字段。
code == "OK"为成功,其余为业务错误。
1.3 时间与金额格式
| 类型 | 格式 | 示例 |
|---|---|---|
| 时间 | RFC3339 / UTC,秒级 | "2026-09-23T13:20:31Z" |
| 时间(空值) | null,或空字符串 ""(管理端部分字段) | "completed_at": null |
| 金额 | 字符串十进制,最多 4 位小数 | "0.5"、"1.0" |
⚠️ 金额是字符串,请用
Decimal/BigDecimal解析,勿直接转浮点。
1.4 请求追踪
可选请求头 X-Request-ID(≤80 字符)。服务端原样回显;不传则生成 32 位十六进制串。
报障时请提供该值,可据此精确定位日志。
1.5 响应安全头(实测)
X-Request-ID: <32位hex>
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: same-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'
1.6 CORS
允许来源由服务端白名单控制(当前仅放行站长自用来源,第三方网页前端调用需先与站长确认加入白名单)。
允许的请求头:
Authorization, Content-Type, Idempotency-Key, X-API-Key, X-Request-ID
允许方法:GET, POST, OPTIONS。暴露响应头:X-Request-ID。预检返回 204。
💡 服务端到服务端调用不受 CORS 限制,推荐对接方走服务端调用。
2. 鉴权体系
| 凭证 | 携带方式 | 用途 | 有效期 |
|---|---|---|---|
| API Key | X-API-Key: csk_xxx | 对接方业务调用(提交/查询订单) | 长期,撤销即失效 |
| Bearer Token | Authorization: Bearer <token> | 账号管理(建 Key、查余额、兑换 CDK) | 30 天(expires_in ≈ 2591999 秒) |
对接方业务代码只需 API Key;Bearer Token 仅开户/运维使用。
2.1 开户流程
Step 1|获取邀请码并注册
🔴 `invite_code` 为必填(v1.2 预告的"免邀请码"未上线)。
POST /api/v1/auth/register
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
account | string | ✅ | 3–32 位,仅字母/数字/下划线 |
password | string | ✅ | 6–72 字节 |
invite_code | string | ✅ | 站长下发的邀请码 |
curl -X POST https://jmlscan.cloud-ip.cc/api/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"account":"your_account","password":"YourPass123456","invite_code":"CDK-XXXXXXXX"}'
成功 201:
{
"code": "OK",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_at": "2026-10-23T13:13:38Z",
"expires_in": 2591999,
"user": { "id": 414, "account": "your_account", "role": "user", "created_at": "2026-09-23T13:13:38Z" }
},
"message": "success"
}
相关错误
| HTTP | code | 触发 |
|---|---|---|
| 400 | INVALID_REQUEST | 请求体不是合法 JSON |
| 400 | INVALID_ACCOUNT | 账号格式不符 |
| 400 | INVALID_PASSWORD | 密码长度不符 |
| 400 | INVITE_CODE_REQUIRED | 未传邀请码(实测线上行为) |
| 400 | INVALID_INVITE_CODE | 邀请码无效/过期 |
| 403 | REGISTRATION_CLOSED | 当前暂停注册(受 registration_open 开关控制) |
| 409 | ACCOUNT_EXISTS | 账号已存在 |
⚠️ 区分两个开关:
registration_open = true表示"注册未暂停";它不代表"可以不传邀请码"。两者独立。
Step 2|创建 API Key
POST /api/v1/user/api-keys(Bearer)
curl -X POST https://jmlscan.cloud-ip.cc/api/v1/user/api-keys \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"my-integration"}'
成功 201(`secret` 仅此一次返回,请立即保存):
{
"code": "OK",
"data": {
"id": 22,
"name": "my-integration",
"prefix": "csk_OkiZ7JfxdN2c…",
"secret": "csk_OkiZ7JfxdN2clWZ27bO4nICIwv5HkjRfQbqFKX7LCrQ",
"last_used_at": "",
"created_at": "2026-09-23T13:20:31Z"
},
"message": "success"
}
每个账号最多 5 个 key(
max_api_keys = 5),超出 →409 API_KEY_LIMIT。撤回:
POST /api/v1/user/api-keys/:id/revoke→{"revoked": true}
Step 3|兑换 CDK 充值
POST /api/v1/user/cdks/redeem(Bearer)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
codes | string[] | ✅ | 1–200 个充值码 |
{
"code": "OK",
"data": {
"redeemed_count": 2,
"credited_amount": "1.0",
"balance": "1.0",
"results": [
{ "code": "CDK-XXXXXXXXXXXXXXXX", "status": "redeemed", "message": "兑换成功", "amount": "0.5" }
]
}
}
错误:400 CDK_REQUIRED(空数组)、400 TOO_MANY_CDKS(>200)、409 WALLET_NOT_FOUND。
Step 4|查询余额
GET /api/v1/user/overview(Bearer)→ {"balance":"1.0","frozen_balance":"0","unit_price":"0.5","cdk_purchase_url":"https://..."}
3. 接口详情
3.0 先看这个:两种提交方式,怎么选
对接方有两条路,凭证不同、扣费方式不同:
| 方式 A:CDK 直连(最省事) | 方式 B:账号 API Key | |
|---|---|---|
| 端点 | POST /api/v1/public/scan/orders | POST /api/v1/client/orders |
| 凭证 | CDK 本身(放在请求体 cdk_codes 里) | 请求头 X-API-Key: csk_... |
| 要不要注册账号 | ❌ 不用 | ✅ 要(且注册需邀请码,见 §2.1) |
| 要不要邀请码 | ❌ 不用 | ✅ 要 |
| 扣费来源 | 扣 CDK 的剩余次数 | 扣 账号余额(余额由 CDK 兑换而来) |
| 频率限制 | 1800 次/分钟(约 30 次/秒) | 60 次/分钟 |
| 查订单 | GET /api/v1/public/scan/orders/:receipt(凭回执号,免登录) | GET /api/v1/client/orders(按账号列出) |
| 余额查询 | 查 CDK 剩余次数:POST /api/v1/public/scan/cdks/query | GET /api/v1/user/overview |
| 适合 | 一次性/低量对接、不想开户 | 长期、高频、需要账号级账务 |
✅ 只想"拿卡密直接提交",用方式 A 就够 —— 不需要注册、不需要邀请码、不需要 API Key。
ℹ️ 两种方式的限流都不算严:方式 A 为 1800 次/分钟(约 30 次/秒),方式 B 为 60 次/分钟(均按来源 IP)。
方式 A 反而更宽松,方式 B 胜在能查订单、有账户与余额体系。选型请按业务需要,不必为了「更高频率」去注册。
方式 A:POST /api/v1/public/scan/orders(CDK 直连)
鉴权:无(不需要任何请求头凭证) 频率:1800 次/分钟(约 30 次/秒)
| 请求头 | 必填 | 说明 |
|---|---|---|
Content-Type | ✅ | application/json |
Idempotency-Key | ✅ | 8–80 字符。同一批重试必须复用同一个值 |
X-API-Key / Authorization | ❌ | 不需要 |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
cdk_codes | string[] | ✅ | 1–200 个。这些就是本次提交的"钱" |
links | string[] | ✅ | 1–max_batch(当前 200)条,须匹配白名单(同 §3.1.2) |
ats | string[] | ✅ | 必须与 links 等长、一一对应(规则同 §3.1.3) |
curl -X POST https://jmlscan.cloud-ip.cc/api/v1/public/scan/orders \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: my-order-20260927-0001' \
-d '{
"cdk_codes": ["CDK-XXXXX-XXXXX-XXXXX-XXXXX"],
"links": ["https://pay.nicepay.co.kr/v1/payment/abc123"],
"ats": ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
}'
成功响应:结构与 §3.1.4 相同(batch_no / receipt / orders[] 等)。 差别:本入口扣的是 CDK 次数,响应里的 balance / frozen_balance 反映的是该 CDK 所属账号(无绑定账号时为 0);请以 `remaining_uses` 判断卡内剩余次数。
提交后请保存返回的 `receipt`(`R` 前缀) —— 后续凭它免登录查询订单状态。
错误码(均为本实例实测原文)
| HTTP | code | 触发 | 实测报文 |
|---|---|---|---|
| 400 | INVALID_REQUEST | 请求体不是对象/非法 JSON | 请求格式错误 |
| 400 | INVALID_PAYMENT_LINK | 存在不在白名单的链接 | 存在无效的支付链接 |
| 400 | INVALID_LINK_COUNT | links 空或超上限 | 每次必须提交 1-200 条链接 |
| 400 | AT_REQUIRED | 未提供 ats | 每个支付链接都必须提供对应的 AccessToken(AT) |
| 400 | AT_COUNT_MISMATCH | ats 与 links 数量不等 | AccessToken 行数必须与支付链接一一对应 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失或长度非 8–80 | Idempotency-Key 长度必须为 8-80 个字符 |
| 400 | INVALID_CDK_COUNT | cdk_codes 空或超 200 | 每次必须提交 1-200 个卡密 |
| 400 | INVALID_CDK | 卡密不存在 / 格式错 | 卡密不存在或格式错误 |
| 409 | CDK_UNAVAILABLE | 卡密已用尽 / 禁用 / 过期 | — |
| 409 | CDK_USES_INSUFFICIENT | 卡内剩余次数 < 链接数 | — |
| 409 | DUPLICATE_LINK | 链接已被提交过 | — |
| 429 | RATE_LIMITED | 超 1800 次/分钟(方式 A) | 请求过于频繁,请稍后再试 |
★ 实测确认的校验顺序(排查问题时可据此定位卡在哪一步):
请求体结构 → links 白名单 → links 数量 → ats 数量
→ Idempotency-Key 长度 → cdk_codes 数量 → CDK 有效性
→ (之后)CDK 剩余次数 / 链接占用 / 派单
💡 例:同时写错链接和漏传幂等键时,会先报
INVALID_PAYMENT_LINK,不会报幂等键问题。
3.1 提交支付链接(核心接口)
POST /api/v1/client/orders 鉴权:`X-API-Key` 频率:60 次/分钟
POST /api/v1/user/orders 为同逻辑的 Bearer 版本,契约完全一致。
3.1.1 请求
| 请求头 | 必填 | 说明 |
|---|---|---|
X-API-Key | ✅ | csk_... |
Content-Type | ✅ | application/json |
Idempotency-Key | ✅ | 8–80 字符,同批重试必须复用同一值 |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
links | string[] | ✅ | 1–max_batch(当前 200)条,须匹配白名单 |
ats | string[] | ✅ | 必须与 `links` 等长、一一对应 |
curl -X POST https://jmlscan.cloud-ip.cc/api/v1/client/orders \
-H "X-API-Key: csk_xxx" \
-H "Idempotency-Key: order-20260923-0001" \
-H 'Content-Type: application/json' \
-d '{
"links": ["https://pay.nicepay.co.kr/v1/payment/abc123"],
"ats": ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."]
}'
3.1.2 🔴 支付链接白名单(实测)
服务端正则:
^(?:https?://(?:www\.)?pay\.nicepay\.co\.kr/.+|https?://payments\.stripe\.com/upi/instructions/.+)$
| 链接 | 是否通过 |
|---|---|
https://pay.nicepay.co.kr/v1/payment/xxx | ✅ |
https://payments.stripe.com/upi/instructions/xxx | ✅ |
https://www.pay.nicepay.co.kr/... | ✅ |
https://checkout.stripe.com/c/pay/cs_live_xxx | 🚫 400 INVALID_PAYMENT_LINK |
| 其它任意域名 | 🚫 400 INVALID_PAYMENT_LINK |
3.1.3 🔴 AccessToken(AT)提交规则
| 场景 | 结果(实测) |
|---|---|
完全不传 ats | 400 AT_REQUIRED |
ats 数量 ≠ links 数量 | 400 AT_COUNT_MISMATCH |
| 每条链接配一个 AT(1:1) | ✅ 通过校验,进入余额/CDK 扣减 |
ats 中夹空字符串 | 按缺失处理,整批拒绝 |
AT 是 ChatGPT 的
access_token(JWT,形如eyJhbGciOi...)。一个链接对应一个 AT,不可批量复用。AT 会在订单被工人「完成」时自动验活:确定失效(invalid/expired/missing)= 支付成功 = 可结算;仍存活 = 支付失败 = 不结算。
3.1.4 响应(成功 201)
{
"code": "OK",
"data": {
"batch_no": "B20260923MONSTERXXXXXXXXX",
"order_count": 1,
"total_amount": "0.5",
"balance": "0.5",
"frozen_balance": "0.5",
"idempotent": false,
"receipt": "R20260923DCVVXKWRDR6GB3",
"remaining_uses": 9,
"orders": [
{
"order_no": "O20260923LGUUI2BG7L62D42Q",
"batch_id": 301,
"source": "client",
"fulfillment_mode": "supplier",
"customer_id": 414,
"supplier_id": null,
"link": "https://pay.nicepay.co.kr/v1/payment/abc123",
"status": "pending",
"unit_price": "0.5",
"charge_amount": "0.5",
"supplier_amount": "0.6",
"result": "",
"failure_reason": "",
"has_at": true,
"at_email": "",
"at_status": "",
"at_probe_status": "",
"at_probe_error": "",
"at_proxy_used": "",
"at_probe_count": 0,
"at_checked_at": null,
"settled_on_dead_at": false,
"claimed_at": null,
"started_at": null,
"completed_at": null,
"created_at": "2026-09-23T13:25:00Z"
}
]
},
"message": "success"
}
字段说明
| 字段 | 说明 |
|---|---|
batch_no | 批次号(前缀 B) |
receipt | ★ 回执号(前缀 `R`),用于后续免登录查询,请保存 |
balance / frozen_balance | 提交后余额 / 冻结额(仅提交响应有) |
remaining_uses | CDK 提交后的剩余次数 |
idempotent | true 表示命中幂等,未新建订单 |
orders[].at_email | 从 AT 解析出的邮箱(初始为空,验活后才回填) |
orders[].settled_on_dead_at | true = 因 AT 确认失效而结算 |
3.1.5 提交相关错误码(实测)
| HTTP | code | 触发条件 |
|---|---|---|
| 400 | INVALID_REQUEST | 请求体非法 JSON |
| 400 | INVALID_LINK_COUNT | links 为空或超上限 |
| 400 | INVALID_PAYMENT_LINK | 存在不在白名单的链接 |
| 400 | AT_REQUIRED | 未提供 `ats` |
| 400 | AT_COUNT_MISMATCH | ats 与 links 数量不等 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失或长度非 8–80 |
| 400 | INVALID_CDK | 卡密不存在/格式错误 |
| 400 | DUPLICATE_CDK | 本次提交中卡密重复 |
| 409 | INSUFFICIENT_BALANCE | 余额(含 CDK 折算)不足 |
| 409 | DUPLICATE_LINK | 链接已提交过(含本批内重复) |
| 409 | CDK_UNAVAILABLE | 卡密已用尽/禁用/过期 |
| 409 | CDK_USES_INSUFFICIENT | 卡密剩余次数 < 链接数 |
| 409 | BALANCE_INCONSISTENT | 冻结余额异常(需联系管理员) |
| 429 | RATE_LIMITED | 超频(响应含 Retry-After) |
3.2 查询我的订单
GET /api/v1/client/orders?page=1&page_size=20(X-API-Key)
| 查询参数 | 默认 | 上限 |
|---|---|---|
page | 1 | — |
page_size | 20 | 100(超出被截断) |
响应:
{
"code": "OK",
"data": {
"items": [ { /* 同 3.1.4 的 order 结构 */ } ],
"pagination": { "page": 1, "page_size": 20, "total": 0, "total_pages": 0 }
},
"message": "success"
}
⚠️ 本接口无搜索/筛选参数,只有
page/page_size。需按订单号精查请用下方回执查询或在工作台操作。
3.3 凭回执号查询提交结果(免登录)
GET /api/v1/public/scan/orders/:receipt 无需鉴权 频率:1800 次/分钟(约 30 次/秒)
:receipt 必须为提交时返回的 `R` 前缀回执号。
| 传入 | 结果(实测) |
|---|---|
R20260923...(正确回执) | ✅ 200,返回批次 + 订单列表 |
B20260923...(批次号) | 🚫 404 ORDER_NOT_FOUND |
O20260923...(订单号) | 🚫 404 ORDER_NOT_FOUND |
| 任意不存在值 | 🚫 404 ORDER_NOT_FOUND |
响应结构同 §3.1.4,但以下字段不返回(因其仅提交时计算):
| 字段 | 提交响应 | 回执查询 |
|---|---|---|
balance | ✅ | ❌ 无 |
frozen_balance | ✅ | ❌ 无 |
remaining_uses | ✅ | ❌ 无 |
idempotent | ✅ | ❌ 无 |
batch_no / receipt / orders[] | ✅ | ✅ |
💡 这是 v1.2 文档未区分之处。回执查询只反映订单状态,不反映余额;查余额请用
GET /user/overview。
3.4 公开只读接口(无需鉴权)
3.4.1 提交配置
GET /api/v1/public/scan/config
{
"code": "OK",
"data": {
"unit_price": "0.5",
"max_batch": 200,
"available": true,
"accepting_supplier_count": 12,
"cdk_purchase_url": "https://wzyp.cn/shop/66666SMS",
"announcement": "<中文公告>",
"announcements": { "zh": "...", "en": "...", "ja": "...", "hi": "..." },
"countdown_seconds": 480,
"at_required": true
},
"message": "success"
}
🔴 `at_required: true` — 对接方应在启动时读取此字段做自检;若为
true而本地仍未传ats,所有提交将被400拒绝。
3.4.2 提交统计
GET /api/v1/public/scan/stats → {"total_orders":2241,"completed_orders":1246}(数值随业务变化,仅示意)
3.4.3 查询 CDK 余额(免登录)
POST /api/v1/public/scan/cdks/query 频率:1800 次/分钟(约 30 次/秒)
| 字段 | 类型 | 必填 |
|---|---|---|
cdk_codes | string[] | ✅ 1–200 个 |
{
"code": "OK",
"data": {
"cards": [
{
"code": "CDK-*****-*****-*****-NXOMT",
"total_uses": 5,
"remaining_uses": 3,
"status": "active",
"merged_into": ""
}
],
"total_remaining_uses": 3
}
}
返回的 code 为掩码形式。status:active | redeemed | disabled。
merged_into:该卡若已被合并到新卡,此处为新卡的掩码;正常卡为空字符串。
合并后源卡的 status 会变为 disabled(表示已被吸收,并非作废),请按 merged_into 提示用户改用新卡。
错误:400 INVALID_CDK_COUNT(数量不在 1–200)、400 INVALID_CDK(卡密无效)。
3.4.4 合并 CDK(免登录)
POST /api/v1/public/scan/cdks/merge 频率:1800 次/分钟(约 30 次/秒)
把多张小额卡合并为一张。要求 2–20 个 CDK,且各项属性一致。
| 字段 | 类型 | 必填 |
|---|---|---|
cdk_codes | string[] | ✅ 2–20 个 |
成功 201:
{
"code": "OK",
"data": {
"cdk": "CDK-XXXXXXXXXXXXXXXXXXXX",
"masked_code": "CDK-*****-*****-*****-ABCDE",
"remaining_uses": 8,
"merged_count": 3,
"source_cdks": ["CDK-*****-...", "CDK-*****-...", "CDK-*****-..."]
},
"message": "success"
}
错误:400 INVALID_CDK_MERGE(数量不在 2–20)、409 CDK_MERGE_FAILED(属性不一致等)。
⚠️ 合并后源卡进入
disabled状态(表示已被合并吸收,并非失效作废)。
3.4.5 探活
GET /health → {"code":"OK","data":{"status":"up"},"message":"success"}
3.4.6 按 CDK 查历史记录(免登录)
POST /api/v1/public/scan/cdks/history 频率:60 次/分钟
凭 CDK 卡密查询「这张卡以前提交过哪些单」。卡被合并后,用旧卡或新卡查询都会返回整条合并链上的完整记录, 不会因为合并而丢历史(这是 2026-10-29 修复的行为:此前只返回被查询那一张卡自己的记录,旧卡会漏掉大部分订单)。
| 字段 | 类型 | 必填 |
|---|---|---|
cdk_codes | string[] | ✅ 1–200 个 |
响应:data.cards[] 与请求按下标一一对应,data.total_cards 为条数。每项包含:
| 字段 | 说明 |
|---|---|
code | 卡号掩码(你查询的那张卡) |
amount | 单次面额 |
total_uses / remaining_uses | 总次数 / 剩余次数 |
status | active | redeemed | disabled |
merged_into | 已合并时为新卡的掩码,否则空字符串 |
accounts[] | 历史订单列表:order_no、status、created_at、source_card(该单来自链上哪张卡的掩码) |
error | 仅在卡号格式非法时出现,值为 INVALID_FORMAT |
⚠️ 本接口无需登录,凭证就是 CDK 卡密本身。请勿把卡密提交给非官方地址;返回内容含历史订单信息,请自行妥善保管。
错误:400 INVALID_CDK_COUNT(数量不在 1–200);单张卡格式非法不会整体报错,而是在对应下标返回 error: INVALID_FORMAT。
3.4.7 按回执查接单人(免登录)
GET /api/v1/public/scan/receipt-supplier?receipt=<回执号> 频率:60 次/分钟
凭回执号查询该批次由哪位接单人处理,用于顾客侧展示「你的单被谁接了」。
| 字段 | 说明 |
|---|---|
data.receipt | 回执号 |
data.batch_no | 该回执对应的批次号 |
data.suppliers[] | { id, account } 列表。一个批次可能由多人分抢,故为数组;按 id 分组统计各自成功/失败数 |
data.supplier | 仅向后兼容保留的单值(等于 suppliers[0],无人接单时为 null)。新对接请只用 suppliers |
错误:400 INVALID_RECEIPT(回执号格式非法)、404 RECEIPT_NOT_FOUND(回执不存在)、503 DB_ERROR(查询失败,可重试)。
3.5 工作台(/user/)接口(Bearer)
| 接口 | 方法 | 说明 |
|---|---|---|
/api/v1/user/overview | GET | 余额 / 单价 / CDK 购买地址 |
/api/v1/user/orders | POST | 提交订单(与 `/client/orders` 同契约,需 {links, ats}) |
/api/v1/user/orders | GET | 我的订单(page / page_size ≤100) |
/api/v1/user/order-batches | GET | 我的批次列表(含 batch_no / input_count / order_count / total_amount) |
/api/v1/user/orders/:order_no/cancel | POST | 取消订单(pending/claimed/processing 可取消,自动退费) |
/api/v1/user/ledger | GET | 余额流水 |
/api/v1/user/withdrawals | POST / GET | 申请 / 查询提现 |
/api/v1/user/cdks/redeem | POST | CDK 充值 |
/api/v1/user/api-keys | GET / POST | 列出 / 创建 API Key |
/api/v1/user/api-keys/:id/revoke | POST | 撤销 API Key |
/api/v1/user/order-availability | GET | 接单可用性 |
提现请求体(POST /user/withdrawals):{"amount":"1.0","method":"...","account":"..."}
4. 完整路由清单(实测枚举)
以下为 2026-10-29 对该实例逐条探测得到的完整路由面(404 者不在表中)。
4.1 公开(无鉴权)
GET /health
GET /api/v1/public/scan/config
GET /api/v1/public/scan/stats
GET /api/v1/public/scan/receipt-supplier 60 次/分
POST /api/v1/public/scan/orders 1800 次/分
GET /api/v1/public/scan/orders/:receipt 1800 次/分
POST /api/v1/public/scan/cdks/query 1800 次/分
POST /api/v1/public/scan/cdks/merge 1800 次/分
POST /api/v1/public/scan/cdks/history 60 次/分
4.2 认证
POST /api/v1/auth/register 10 次/分
POST /api/v1/auth/login 10 次/分
GET /api/v1/auth/me
POST /api/v1/auth/logout
POST /api/v1/auth/change-credentials
4.3 用户(Bearer,role ∈ {user, admin})
GET /api/v1/user/overview
GET /api/v1/user/ledger
POST /api/v1/user/cdks/redeem
GET /api/v1/user/withdrawals
POST /api/v1/user/withdrawals
GET /api/v1/user/order-availability
GET /api/v1/user/api-keys
POST /api/v1/user/api-keys
POST /api/v1/user/api-keys/:id/revoke
POST /api/v1/user/orders
GET /api/v1/user/orders
GET /api/v1/user/order-batches
POST /api/v1/user/orders/:order_no/cancel
4.4 对外业务(API Key)
POST /api/v1/client/orders 60 次/分
GET /api/v1/client/orders 60 次/分
4.5 静态页面
| 路径 | 内容 |
|---|---|
/ | 工作台(免登录提交页) |
/scan/ | 免登录提交页(多语言 + CDK 融合面板) |
/user/ | 工作台(与 / 同一文件) |
/admin/ | 管理后台 |
/supplier/ | 供货商端 |
/pay/ | 支付/CDK 管理页(含 CDK 融合入口) |
/api-doc | 本 API 文档页 |
5. 订单状态机
pending ──claim──▶ claimed ──start──▶ processing ──complete(AT判死)──▶ completed
│ │ │
│ │ └──fail──▶ failed
│ │
└────cancel────────┴────────cancel──────┴──────────────▶ cancelled
| 状态 | 含义 | 可否取消 |
|---|---|---|
pending | 待接单 | ✅ |
claimed | 已被工人接单 | ✅ |
processing | 工人处理中 | ✅ |
completed | 已完成(AT 确认失效 + 已结算) | ❌ |
failed | 失败(AT 仍存活 / 工人标记失败) | ❌ |
cancelled | 已取消(自动退费) | ❌ |
超时回收:claimed / processing 超过 480 秒未推进 → 自动取消并退费(order_timeout_minutes = 8)。
5.1 AT 状态枚举(8 值)
| 值 | 含义 | 是否算「确定失效」 | 可否结算 |
|---|---|---|---|
missing | 无 AT(本地判定) | ✅ | ✅ |
invalid | JWT 不可解析 / HTTP 401 | ✅ | ✅ |
expired | JWT exp 已过期(本地判定) | ✅ | ✅ |
normal | AT 正常(200/204 或越过认证层的 4xx) | ❌(=存活) | 🚫 |
review | HTTP 403,人工复核,不认定死亡 | ❌ | 🚫 |
rate_limited | HTTP 429 | ❌ | 🚫 |
network | 超时/连接失败/未知 | ❌ | 🚫 |
validation_error | 程序解析异常 | ❌ | 🚫 |
只有 `missing` / `invalid` / `expired` 三者算「确定失效」,才触发结算。其余状态订单保持
processing供重试,对应错误码409 AT_INDETERMINATE。
6. 关键机制说明
6.1 幂等
Idempotency-Key强制,8–80 字符- 同
customer_id+ 同 key 重复提交 → 返回首次结果,idempotent: true,不重复建单、不重复扣费 - 重试必须复用同一 key;换 key 视为新提交
重试建议:网络超时/5xx → 用同 key 重试(最多 3 次,指数退避 1s/2s/4s);收到 429 → 按 Retry-After 等待;收到 4xx 业务错误 → 不要重试,修正参数。
6.2 CDK 计费
- CDK 按「次数」计费,1 条链接消耗 1 次
- 提交时原子扣减;订单被取消 → 自动返还(
cdk_usages.refunded_uses增加) - 某张卡剩余次数不足时,会自动跨卡凑足(多卡联合扣减)
- 卡状态:
active(可用)|redeemed(已用尽)|disabled(已合并到其它卡,非作废)
6.3 AT 验活链路(含本期修复)
工人点「完成」
↓
事务外:读取订单快照
↓
AT 验活(双层)
├─ 本地层:JWT 解析 + exp 检查(不联网)→ missing / invalid / expired
└─ 在线层:GET 探针端点
↓ 经站内本地中继 http://站内中继(Chrome TLS 指纹)
↓ 转发至 https://chatgpt.com/backend-api/me
├─ 401 → invalid(确定失效 → 可结算)
├─ 200/204 → normal(存活 → 不结算)
├─ 403 → review(不认定死亡)
├─ 429 → rate_limited
└─ 超时/连接失败 → network
↓
判定:确定失效 → 进事务加行锁 → settleFrozen + CreditOrder → completed
仍存活 → 409 AT_STILL_ALIVE,保持 processing
不确定 → 409 AT_INDETERMINATE,保持 processing
本期变更:Cloudflare 自 2026-09-23 起对
chatgpt.com启用 TLS/JA3 指纹挑战,Go 标准库net/http的 TLS 指纹不像浏览器 → 一律403,导致所有 AT 验活失效(换 IP 无效,实测新老代理池与直连全部 403)。修复方式:站内新增本地中继(
curl_cffi的 Chrome 指纹),AT_PROBE_ENDPOINT指向 站内中继端点。对对接方接口契约零影响 —— 但若验活大面积返回
review,请勿反复调用complete,应联系站长排查中继状态。
6.4 结算口径(重要)
- 归属账户 = `COALESCE(上级 parent_id, 自己)`(即老板账号承接其名下工人的收益)
- 恒等式:
balance + frozen_balance + total_withdrawn == total_earned - 一 CDK 多账号提交时,只结算 1 单,不追讨工人余额
- 冻结余额在提交时产生;订单终结(completed / failed / cancelled)时解冻
7. 错误码全表
7.1 通用 / 鉴权
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_REQUEST | 请求格式错误 |
| 401 | UNAUTHORIZED | 未提供凭证 |
| 401 | INVALID_TOKEN | Token 失效,请重新登录 |
| 401 | INVALID_API_KEY | API Key 无效或已撤销 |
| 403 | FORBIDDEN | 无权访问该资源 |
| 403 | USER_DISABLED | 账号已被禁用 |
| 404 | NOT_FOUND | 资源不存在 |
| 429 | RATE_LIMITED | 请求过于频繁(含 Retry-After) |
| 500 | INTERNAL_ERROR | 服务端内部错误 |
7.2 注册 / 登录
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_ACCOUNT | 账号格式不符(3–32 位字母/数字/下划线) |
| 400 | INVALID_PASSWORD | 密码长度不符 |
| 400 | INVITE_CODE_REQUIRED | 未传邀请码 |
| 400 | INVALID_INVITE_CODE | 邀请码无效或过期 |
| 400 | CURRENT_CREDENTIALS_INVALID | 当前用户名或旧密码错误 |
| 401 | INVALID_CREDENTIALS | 账号或密码错误 |
| 403 | REGISTRATION_CLOSED | 当前已暂停注册 |
| 409 | ACCOUNT_EXISTS | 账号已存在 |
7.3 提交订单
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_LINK_COUNT | 链接数不在 1–max_batch |
| 400 | INVALID_PAYMENT_LINK | 链接不在白名单 |
| 400 | AT_REQUIRED | 未提供 `ats` |
| 400 | AT_COUNT_MISMATCH | ats 与 links 数量不等 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失/长度错误 |
| 400 | INVALID_CDK | 卡密不存在或格式错误 |
| 400 | DUPLICATE_CDK | 本批中卡密重复 |
| 400 | CDK_REQUIRED | 未提供充值码 |
| 400 | TOO_MANY_CDKS | 充值码超 200 |
| 400 | INVALID_CDK_COUNT | 卡密数不在 1–200 |
| 400 | INVALID_SUPPLIER_AMOUNT | 供货商金额格式错误(管理端) |
| 409 | INSUFFICIENT_BALANCE | 余额不足 |
| 409 | DUPLICATE_LINK | 链接已提交过 |
| 409 | CDK_UNAVAILABLE | 卡密已用尽/禁用/过期 |
| 409 | CDK_USES_INSUFFICIENT | 卡密剩余次数不足 |
| 409 | CDK_MERGE_FAILED | CDK 合并失败 |
| 409 | CDK_REFUND_UNAVAILABLE | 卡密次数返还失败 |
| 409 | SUPPLIER_NOT_ACCEPTING | 未开启接单 |
| 409 | BALANCE_INCONSISTENT | 冻结余额异常 |
7.4 订单查询 / 操作
| HTTP | code | 说明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 订单/回执不存在 |
| 404 | AT_NOT_FOUND | 该订单未保存 AT |
| 409 | ORDER_NOT_AVAILABLE | 订单已被其他供货商接走 |
| 409 | INVALID_ORDER_STATUS | 当前状态不允许此操作 |
| 403 | FORBIDDEN | 无权操作该订单 |
| 409 | AT_STILL_ALIVE | AT 仍存活,判定支付失败,不予结算 |
| 409 | AT_INDETERMINATE | AT 状态无法确认,请稍后重试 |
| 400 | FAILURE_REASON_REQUIRED | 未填写失败原因 |
7.5 提现
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_AMOUNT | 提现金额格式错误 |
| 400 | INVALID_WITHDRAWAL | 提现信息错误 |
| 409 | WALLET_NOT_FOUND | 余额账户不存在 |
| 409 | SUPPLIER_ACCOUNT_NOT_FOUND | 供货商资金账户不存在 |
| 409 | WITHDRAWAL_REVIEWED | 提现单已处理 |
| 404 | WITHDRAWAL_NOT_FOUND | 提现单不存在 |
| 422 | INSUFFICIENT_BALANCE | 可用余额不足(用户端) |
8. 频率限制
| 范围 | 实际限制 |
|---|---|
POST/GET /api/v1/client/orders(含 /client/*) | 60 次/分钟(按来源 IP) |
/api/v1/auth/login、register、change-credentials | 10 次/分钟 |
POST /api/v1/public/scan/orders | 1800 次/分钟(约 30 次/秒) |
POST /api/v1/public/scan/cdks/query | 1800 次/分钟(约 30 次/秒) |
POST /api/v1/public/scan/cdks/merge | 1800 次/分钟(约 30 次/秒) |
GET /api/v1/public/scan/orders/:receipt | 1800 次/分钟(约 30 次/秒) |
GET /api/v1/public/scan/receipt-supplier | 60 次/分钟 |
POST /api/v1/public/scan/cdks/history | 60 次/分钟 |
其余 /api/v1/user/*、/api/v1/admin/*、/api/v1/supplier/* | 1200 次/分钟(约 20 次/秒) |
⚠️ 同一请求可能同时命中路径桶与全局桶,取最严的那一条生效。表中为 2026-10-29 实测的稳态速率。
超限响应 429,附 Retry-After: <秒>。
⚠️ 服务端判定客户端 IP 时信任
TRUSTED_PROXIES白名单;伪造 `X-Forwarded-For` 无效。
9. 联调 Checklist
- [ ] 已向站长获取
invite_code - [ ] 注册成功并保存
access_token - [ ] 创建 API Key 并保存
secret(仅一次可见) - [ ] 兑换 CDK,确认
GET /user/overview余额 > 0 - [ ] 读取
GET /public/scan/config,确认at_required与unit_price - [ ] 构造
{links, ats},两者长度一致 - [ ] 链接确认命中白名单(nicepay 或 stripe upi)
- [ ] 每次提交带唯一
Idempotency-Key(8–80 字符) - [ ] 重试必须复用同一
Idempotency-Key - [ ] 保存返回的
receipt,用于后续查询 - [ ] 轮询间隔建议 ≥ 5 秒,避免触发限流
10. cURL 全流程
BASE=https://jmlscan.cloud-ip.cc
# 1) 注册(invite_code 必填)
curl -s -X POST $BASE/api/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"account":"my_account","password":"MyPass123456","invite_code":"CDK-XXXXXXXX"}'
# 2) 创建 API Key
TOKEN=<上一步返回的 access_token>
curl -s -X POST $BASE/api/v1/user/api-keys \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"prod"}'
# 3) 兑换 CDK
curl -s -X POST $BASE/api/v1/user/cdks/redeem \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"codes":["CDK-XXXXXXXXXXXXXXXX"]}'
# 4) 提交(★ ats 必填且与 links 一一对应)
KEY=csk_xxxxxxxx
curl -s -X POST $BASE/api/v1/client/orders \
-H "X-API-Key: $KEY" \
-H "Idempotency-Key: mybatch-20260923-001" \
-H 'Content-Type: application/json' \
-d '{
"links": ["https://pay.nicepay.co.kr/v1/payment/abc123"],
"ats": ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."]
}'
# 5) 查询(按回执号,免鉴权)
curl -s $BASE/api/v1/public/scan/orders/R20260923XXXXXXXXXXXXXX
# 6) 查询(按账号,API Key)
curl -s "$BASE/api/v1/client/orders?page=1&page_size=20" -H "X-API-Key: $KEY"
11. Python 示例(标准库,无第三方依赖)
import json, time, urllib.request, urllib.error
BASE = "https://jmlscan.cloud-ip.cc"
def call(method, path, body=None, token=None, api_key=None, headers=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, method=method)
req.add_header("Content-Type", "application/json")
if token:
req.add_header("Authorization", "Bearer " + token)
if api_key:
req.add_header("X-API-Key", api_key)
for k, v in (headers or {}).items():
req.add_header(k, v)
try:
with urllib.request.urlopen(req, timeout=30) as r:
return r.status, json.loads(r.read().decode())
except urllib.error.HTTPError as e:
return e.code, json.loads(e.read().decode())
# 1) 提交(每条链接配一个 AT)
def submit(api_key, links, ats, idem_key):
assert len(links) == len(ats), "links 与 ats 必须一一对应"
status, data = call("POST", "/api/v1/client/orders",
{"links": links, "ats": ats},
api_key=api_key,
headers={"Idempotency-Key": idem_key})
if status != 201:
raise RuntimeError(f"提交失败 {status}: {data}")
return data["data"]["receipt"]
# 2) 轮询(按回执号,免鉴权)
def poll(receipt, interval=5, timeout=480):
deadline = time.time() + timeout
while time.time() < deadline:
status, data = call("GET", f"/api/v1/public/scan/orders/{receipt}")
if status != 200:
raise RuntimeError(f"查询失败 {status}: {data}")
orders = data["data"]["orders"]
if all(o["status"] in ("completed", "failed", "cancelled") for o in orders):
return orders
time.sleep(interval)
raise TimeoutError("轮询超时")
if __name__ == "__main__":
API_KEY = "csk_xxxxxxxx"
links = ["https://pay.nicepay.co.kr/v1/payment/abc123"]
ats = ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."]
receipt = submit(API_KEY, links, ats, "mybatch-20260923-001")
print("receipt:", receipt)
for o in poll(receipt):
print(o["order_no"], o["status"], "at_status=", o.get("at_status"))
12. 运行时参数(2026-10-29 实测)
以下值全部来自 `GET /api/v1/public/scan/config`,会随运营调整而变。
| 字段 | 2026-10-29 实测 | 说明 |
|---|---|---|
unit_price | "0.5" | 每条链接的扣费(字符串,请用 Decimal 解析) |
max_batch | 200 | 单批链接上限 |
at_required | true | AT 强制:为 true 时必须传 ats |
available | true | 当前是否可接单 |
accepting_supplier_count | 12 | 在线接单人数(动态) |
countdown_seconds | 480 | 工人接单倒计时(秒) |
cdk_purchase_url | https://wzyp.cn/shop/66666SMS | CDK 购买地址 |
announcement | (中文文本) | 站点公告 |
announcements | {zh,en,ja,hi} | 多语言公告字典 |
🔴 以上任何一项都不要硬编码。 单价会调、接单人数会变、
available会翻转。建议:每次提交前读一次 `/config`;
available == false时暂缓提交,避免无谓的 409。
账号级固定限制(不会变,可写死):
| 项 | 值 |
|---|---|
| 每账号 API Key 上限 | 5 |
Idempotency-Key 长度 | 8–80 字符 |
| 单批链接 / 卡密上限 | 200 |
| CDK 合并数量 | 2–20 |
| 订单超时回收 | 8 分钟 |
13. 快速排障
| 现象 | 原因与处理 |
|---|---|
400 AT_REQUIRED | 未传 ats。读 GET /public/scan/config 的 at_required 确认;补齐 AT |
400 AT_COUNT_MISMATCH | ats 与 links 长度不等,逐条对齐 |
400 INVITE_CODE_REQUIRED | 注册未带邀请码;联系站长获取(此项并未放开) |
400 INVALID_PAYMENT_LINK | 链接不在白名单;仅接受 pay.nicepay.co.kr/* 或 payments.stripe.com/upi/instructions/* |
400 INVALID_IDEMPOTENCY_KEY | 幂等键缺失或长度不在 8–80 |
409 INSUFFICIENT_BALANCE | 余额/CDK 折算不足;兑换 CDK 或减少条数 |
409 AT_STILL_ALIVE | AT 仍存活 = 支付未成功,属正常拒结算;确认该 AT 对应的支付是否真的失败 |
409 AT_INDETERMINATE | 验活网络异常/限流/403;稍后重试,勿频繁调用 |
404 ORDER_NOT_FOUND | 回执查询传了批次号/订单号;必须传 `R` 前缀回执号 |
400 INVALID_REQUEST(供货商) | 开关接单传了 accepting;应传 `enabled` |
409 SUPPLIER_NOT_ACCEPTING | 未开启接单;先 POST /supplier/accepting {"enabled":true} |
429 RATE_LIMITED | 超频;按 Retry-After 等待 |
| 探活请求被直接断开 | 若用脚本探活,请带浏览器风格 User-Agent;服务端对异常 UA 会直接断开 |
14. 当前不支持的能力
| 能力 | 状态 |
|---|---|
| 订单状态回调 / Webhook | ❌ 需轮询 |
| 订单搜索 / 高级筛选参数 | ❌ 仅 page / page_size |
| HTTPS | ❌ 当前为 HTTP(如需请与站长确认) |
| 批量取消订单 | ❌ 单条取消 |
| 自助修改单价/超时 | ❌ 管理端设置 |
| 免邀请码注册 | ❌ 未上线(v1.2 预告作废) |
附录 A:本文档的验证方法
为避免"凭记忆写文档",本文档所有条目经三重交叉验证:
1. 路由枚举:对该实例逐条发起真实请求(含不存在路径作对照), 以 HTTP 状态码区分「存在」(401/400/409/200)与「不存在」(404),得到 §4 的路由面。
探针必须带浏览器 UA —— 否则可能被网关直接断连,把全部路由误报成"不存在"。
2. 真实 E2E:注册账号 → 登录 → 建 Key → 提交 → 查询全链路跑通,逐项记录真实响应体。 本文档所有错误响应均为实际触发的报文原文,非手写推测。 3. 字段交叉核对:以线上实测行为为准,与服务端实现逐字段比对。 凡"实现与实测冲突",一律以实测为准并在此登记。
2026-09-27 复核结果(相对 09-23 版):
| 检查项 | 结果 |
|---|---|
| 对外路由面 | ✅ 无变化(对接方业务接口仍为 2 个) |
| 公开只读接口 | ✅ 全部可用 |
| 错误码 | ✅ 关键错误报文重新抓取,与 09-23 一致 |
| 运行时参数 | ⚠️ 与 09-23 不同(见 §10),再次印证不要硬编码 |
由此发现并纠正的历史错误:
| 项 | 旧文档描述 | 实测真相 |
|---|---|---|
| 注册邀请码 | "已改为可留空,勿传" | 必填,不传 400 INVITE_CODE_REQUIRED |
| 支付链接白名单 | 仅 nicepay | nicepay + Stripe UPI 双前缀 |
supplier/accepting 字段名 | 未记录 | `enabled`(非 accepting) |
| 回执查询响应 | 与提交响应同构 | 不含 balance / frozen_balance / remaining_uses |
| 对外 client 接口数量 | 一度被写成 6~8 个 | 实测只有 2 个,其余 client/* 均 404 |
2026-10-29 复核(v3.0 → v3.1):对全站重新做了路由探针与配置读取,更正与新增如下。
| 检查项 | v3.0 记载 | 2026-10-29 实测 | 处理 |
|---|---|---|---|
unit_price | "0.8" | "0.5" | 已更正 §3.4.1 / §12 / 速览 |
supplier_unit_price | "0.6" | 该字段不存在 | 已从 §3.4.1 删除 |
accepting_supplier_count | 14 / 18 | 12(动态) | 已更新并标注动态 |
/client/* 限流 | 120 次/分 | 60 次/分 | 已更正 §8 / §4.4 |
/auth/login|register|change-credentials | 20 次/分 | 10 次/分 | 已更正 §8 / §4.2 |
/public/scan/orders 等公开接口 | 10–120 次/分 | 1800 次/分 | 已更正 §8 / §4.1 |
POST /public/scan/cdks/history | 未收录 | 存在,60 次/分 | 已新增 §3.4.6 |
GET /public/scan/receipt-supplier | 未收录 | 存在,60 次/分 | 已新增 §3.4.7 |
/public/scan/cdks/query 响应 | 4 个字段 | 新增 merged_into(掩码) | 已补 §3.4.3 |
| Base URL | https://jmlscan.cloud-ip.cc | 另有 https://jmlscan.cloud-ip.cc | 已改为推荐 HTTPS |
未变更项:max_batch=200、at_required=true、available=true、countdown_seconds=480、cdk_purchase_url、
§4 已列的 27 条路由全部可达,/user/withdrawals 的 {amount, method, account} 字段名无误。
Gopay 通道(独立大类)
📌 本大类是对上文的补充,上文 UPI 通道的内容一字未改。 两套通道在卡密前缀、支付链接、接口路径、订单标记上完全分开:UPI 的卡进不了 Gopay 通道,Gopay 的链接也进不了 UPI 通道。 选哪套只看一件事:你手上拿到的是哪种卡密。
G0. 两套通道对照(先看这张表)
| 维度 | UPI 通道(§1–§14) | Gopay 通道(本大类) |
|---|---|---|
| 卡密前缀 | CDK-XXXXX-XXXXX-XXXXX-XXXXX(27 字符) | GOPAY-XXXXX-XXXXX-XXXXX-XXXXX(29 字符) |
| 支付链接 | payments.stripe.com/upi/instructions/*pay.nicepay.co.kr/* | app.midtrans.com/snap/v4/redirection/<uuid> |
| 二维码 | 有(Stripe 页面内展示) | ❌ 没有,只有跳转链接 |
| 接口前缀 | /api/v1/public/scan/* | /api/v1/public/gopay/* |
| 查卡余量 | POST /public/scan/cdks/query(§3.4.3) | POST /public/gopay/cdks/query(G1.1) |
| 提交订单 | POST /public/scan/orders(§3.0 方式 A) | POST /public/gopay/orders(G1.2) |
| 凭回执查询 | GET /public/scan/orders/:receipt(§3.3) | GET /public/gopay/orders/:receipt(G1.3) |
| 订单标记 | pay_method = "upi" | pay_method = "gopay" |
| 链接有效期 | 按平台规则(不单独限制) | 15 分钟,到点自动判定(见 G3) |
| 卡密合并 | ✅ 支持(2–20 张,§3.4.4) | ❌ 不支持 |
| 鉴权 | 无(凭证 = 卡密本身) | 无(凭证 = 卡密本身) |
| 计费方式 | 扣卡密剩余次数,1 条链接 = 1 次 | 同 |
| 单价 | 读 unit_price(当前 "0.5") | 同一 unit_price,不要硬编码 |
⚠️ `/api/v1/public/gopay/*` 是独立服务,与 UPI 通道不是同一套代码路径。 因此它们的错误码集合、响应字段有细微差异,本文已逐项标注;不要把 UPI 的排障经验直接套到 Gopay 上(最容易踩的是:Gopay 查卡响应没有
merged_into)。
G1. 三个公开接口
G1.0 路由一览
POST /api/v1/public/gopay/cdks/query 1800 次/分
POST /api/v1/public/gopay/orders 1800 次/分
GET /api/v1/public/gopay/orders/:receipt 1800 次/分
只有这三条。 /api/v1/public/gopay/ 下的其它任何路径一律 404 NOT_FOUND(响应体 {"code":"NOT_FOUND","message":"接口不存在"})。
频率限制与 UPI 公开接口一致:1800 次/分钟(约 30 次/秒,按来源 IP)。超限 429。详见 §8。
响应信封与 §1.2 完全相同:成功 {"code":"OK","data":{...},"message":"success"};失败 {"code":"...","message":"..."}(无 data)。
G1.1 查询 Gopay 卡余量
POST /api/v1/public/gopay/cdks/query 无需鉴权 频率:1800 次/分钟
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
cdk_codes | string[] | ✅ | 1–200 个,必须全部是 GOPAY- 开头的卡 |
curl -X POST https://jmlscan.cloud-ip.cc/api/v1/public/gopay/cdks/query \
-H 'Content-Type: application/json' \
-d '{"cdk_codes":["GOPAY-XXXXX-XXXXX-XXXXX-XXXXX"]}'
成功 200
{
"code": "OK",
"data": {
"cards": [
{
"code": "GOPAY-*****-*****-*****-NXOMT",
"total_uses": 5,
"remaining_uses": 3,
"status": "active"
}
],
"total_remaining_uses": 3
},
"message": "success"
}
| 字段 | 说明 |
|---|---|
cards[] | 与请求 cdk_codes 顺序无关(按卡哈希排序返回),请以 code 匹配 |
cards[].code | 卡号掩码,前缀一定是 GOPAY- |
cards[].total_uses / remaining_uses | 总次数 / 剩余次数(整数) |
cards[].status | active|redeemed(已用尽)|disabled(被禁用) |
total_remaining_uses | 本次查询所有卡的剩余次数合计 |
⚠️ 与 UPI 查卡(§3.4.3)的唯一差异:本接口不返回 `merged_into`。 Gopay 卡不支持合并,所以无需判断「已合并到新卡」。若你的代码复用了 UPI 的解析逻辑,请把
merged_into当可选字段处理。
错误
| HTTP | code | 触发 | 报文 |
|---|---|---|---|
| 400 | INVALID_REQUEST | 请求体不是合法 JSON | 请求格式错误 |
| 400 | INVALID_CDK_COUNT | 数量不在 1–200 | 每次必须查询 1-200 个卡密 |
| 400 | INVALID_CDK | 格式不对 / 卡不存在 / 不是 Gopay 专用卡 | 卡密格式不正确(Gopay 卡应为 GOPAY-XXXXX-XXXXX-XXXXX-XXXXX) · 卡密不存在 · 该卡密不是 Gopay 专用卡,请在 UPI 通道使用 |
| 400 | DUPLICATE_CDK | 本批内卡密重复 | 卡密重复 |
G1.2 提交支付链接(核心接口)
POST /api/v1/public/gopay/orders 无需鉴权 频率:1800 次/分钟 成功返回 201
| 请求头 | 必填 | 说明 |
|---|---|---|
Content-Type | ✅ | application/json |
Idempotency-Key | ✅ | 8–80 字符。同一批重试必须复用同一个值 |
X-API-Key / Authorization | ❌ | 不需要(Gopay 通道只走卡密直连) |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
cdk_codes | string[] | ✅ | 1–200 个,必须全部是 GOPAY- 卡。这就是本次提交的「钱」 |
links | string[] | ✅ | 1–max_batch(当前 200)条,必须全部是 Midtrans 链接(见 G3) |
ats | string[] | ⚠️ 强烈建议 | 与 links 等长、一一对应。详见下方说明 |
curl -X POST https://jmlscan.cloud-ip.cc/api/v1/public/gopay/orders \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: gopay-20261103-0001' \
-d '{
"cdk_codes": ["GOPAY-XXXXX-XXXXX-XXXXX-XXXXX"],
"links": ["https://app.midtrans.com/snap/v4/redirection/0f9dff28-55ed-4eba-b712-c2eb5a404c74"],
"ats": ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
}'
🔴 关于 `ats`:业务上必须传,且一条链接一个 AT。 服务端不会因为
ats为空而拒绝建单(与 UPI 通道的400 AT_REQUIRED不同), 但没有 AT 的订单无法做 G3 的到期判定,会一直停在处理中、只能人工处理。 请照 §3.1.3 的规则准备 AT,不要依赖这条「能过」。
成功 201
{
"code": "OK",
"data": {
"batch_no": "B20261103ABCDEFGHIJKLMNOP",
"order_count": 1,
"total_amount": "0.5",
"balance": "0",
"frozen_balance": "0",
"idempotent": false,
"receipt": "R20261103ABCDEFGHIJKLMN",
"remaining_uses": 4,
"orders": [
{
"order_no": "O20261103QRSTUVWXYZABCDEF",
"batch_id": 321,
"source": "guest",
"fulfillment_mode": "supplier",
"customer_id": null,
"supplier_id": null,
"link": "https://app.midtrans.com/snap/v4/redirection/0f9dff28-55ed-4eba-b712-c2eb5a404c74",
"status": "pending",
"unit_price": "0.5",
"charge_amount": "0.5",
"supplier_amount": "0.7",
"result": "",
"failure_reason": "",
"has_at": true,
"at_email": "[email protected]",
"at_status": "",
"at_probe_status": "",
"at_probe_error": "",
"at_proxy_used": "",
"at_probe_count": 0,
"at_checked_at": null,
"settled_on_dead_at": false,
"claimed_at": null,
"started_at": null,
"completed_at": null,
"created_at": "2026-11-03T12:30:00Z",
"link_expires_at": "2026-11-03T12:45:00Z",
"pay_method": "gopay"
}
]
},
"message": "success"
}
| 字段 | 说明 |
|---|---|
batch_no | 批次号(前缀 B) |
receipt | ★ 回执号(前缀 `R`),用于免登录查询,请保存 |
balance / frozen_balance | 恒为 `"0"`。Gopay 通道按卡密次数计费,没有账户余额概念;请以 remaining_uses 判断卡内剩余 |
remaining_uses | 本批扣卡后,所选卡片的剩余次数合计 |
idempotent | true = 命中幂等,未新建订单、未重复扣卡 |
orders[].pay_method | ★ 恒为 "gopay",可用于区分通道 |
orders[].link_expires_at | ★ Gopay 专有:链接失效时间 = created_at + 15 分钟(RFC3339)。可直接用来做倒计时 |
orders[].customer_id | null(卡密直连无账号) |
orders[].source | guest |
orders[].supplier_amount | 接单人侧的结算额(与你无关,仅供排查时辨认) |
orders[].at_email | 从 AT 解析出的邮箱,提交时即回填(UPI 通道是验活后才回填) |
错误码(Gopay 通道实测原文)
| HTTP | code | 触发 | 报文 |
|---|---|---|---|
| 400 | INVALID_REQUEST | 请求体不是合法 JSON | 请求格式错误 |
| 400 | NO_LINKS | links 全为空 / 全是空串 | 请至少提交 1 条支付链接 |
| 400 | INVALID_LINK_COUNT | 链接数超 max_batch | 每次必须提交 1-200 条链接 |
| 400 | INVALID_PAYMENT_LINK | 含非 Midtrans 链接(Stripe / Nicepay / 其它域名都算) | Gopay 通道只接受 Midtrans 链接(app.midtrans.com/snap/v4/redirection/...),当前链接识别为:upi |
| 400 | DUPLICATE_LINK | 本批内链接重复 | 存在重复的支付链接 |
| 400 | AT_COUNT_MISMATCH | ats 非空但与 links 数量不等 | AccessToken 行数必须与支付链接一一对应 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失或长度非 8–80 | Idempotency-Key 长度必须为 8-80 个字符 |
| 400 | INVALID_CDK_COUNT | cdk_codes 空或 >200 | 每次必须提交 1-200 个卡密 |
| 400 | INVALID_CDK | 非 GOPAY- 格式 / 卡不存在 / 不是 Gopay 卡 | 卡密格式不正确(Gopay 卡应为 GOPAY-XXXXX-XXXXX-XXXXX-XXXXX) · 卡密不存在或不是 Gopay 专用卡 |
| 400 | DUPLICATE_CDK | 本批内卡密重复 | 卡密重复 |
| 400 | UNAVAILABLE_CDK | 卡已用尽 / 被禁用 / 已过期 | 卡密不可用或次数不足 |
| 400 | CDK_USES_INSUFFICIENT | 卡内剩余次数 < 链接数 | 卡密剩余次数不足 |
| 409 | DUPLICATE_LINK | 该链接历史上已被提交过(跨批次去重) | 存在已提交过的支付链接 |
| 409 | NO_ACCEPTING_SUPPLIERS | 当前无人可接单,或在途单已达接单容量 | 当前没有可接单的供货商,请稍后再试 |
| 429 | — | 超 1800 次/分钟 | 由网关返回,含 Retry-After |
| 500 | INTERNAL_ERROR | 服务异常 / 平台单价未配置 | 服务异常 |
★ 校验顺序(排查时据此定位卡在哪一步)
请求体结构 → links 去重/非空 → links 数量 → 链接白名单(必须 midtrans)
→ ats 数量 → Idempotency-Key 长度(网关层最前)
→ cdk_codes 数量 → 卡密格式 → 卡密重复
→ (之后)卡密存在性/通道归属 → 卡密可用性/次数 → 链接占用 → 派单
💡 注意与 UPI 的差异:幂等键在校验链最前端(网关直接判),所以同时写错链接和漏传幂等键时,Gopay 通道先报
INVALID_IDEMPOTENCY_KEY,而 UPI 通道先报INVALID_PAYMENT_LINK。
G1.3 凭回执号查询(免登录)
GET /api/v1/public/gopay/orders/:receipt 无需鉴权 频率:1800 次/分钟
响应结构与 G1.2 完全一致(含 orders[] 与 link_expires_at),差异仅在于**不反映本次扣卡结果**:
| 字段 | 提交响应 | 回执查询 |
|---|---|---|
batch_no / receipt / orders[] | ✅ | ✅ |
remaining_uses | ✅ 本次扣卡后余量 | ⚠️ 恒为 0(无意义,别用) |
balance / frozen_balance | "0" | "0" |
idempotent | ✅ | 恒为 false |
🔴 回执是通道绑定的。 拿 UPI 批次的回执来查会返回
404 ORDER_NOT_FOUND,反之亦然。两个通道的回执不能互换。
错误
| HTTP | code | 触发 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 回执不存在,或该回执属于 UPI 批次 |
G2. 卡密格式(GOPAY-)
GOPAY-AAAAA-BBBBB-CCCCC-DDDDD
└──┬─┘ └──────┬───────────┘
│ └─ 20 位 base32 主体(RFC4648 标准字母表 A–Z2–7),5-5-5-5 分组
└─ 固定前缀 "GOPAY-",共 29 字符
| 项 | 说明 |
|---|---|
| 总长度 | 29 字符(GOPAY- 6 + 20 位主体 + 3 个连字符) |
| 字符集 | 大写字母 A–Z 与数字 2–7(不含 0、1、8、9) |
| 大小写 | 服务端统一转大写处理,小写输入也能识别;但请统一用大写 |
| 获取方式 | ① 管理端「生成卡密」选 Gopay 类型;② 站长把已有 CDK 卡批量标记为 Gopay |
| 能否合并 | ❌ 不支持。Gopay 卡没有合并功能 |
🔴 前缀即通道。
GOPAY-卡只能进 Gopay 通道,CDK-卡只能进 UPI 通道,互相提交一律400 INVALID_CDK。这是刻意设计的硬隔离,不是 bug。
G3. 链接格式与 15 分钟有效期(★ Gopay 最重要的一条)
G3.1 链接白名单
服务端正则:
^https?://app\.midtrans\.com/snap/v4/redirection/[0-9a-fA-F-]{8,}(?:[?#].*)?$
| 链接 | 是否通过 |
|---|---|
https://app.midtrans.com/snap/v4/redirection/0f9dff28-55ed-4eba-b712-c2eb5a404c74 | ✅ |
https://app.midtrans.com/snap/v4/redirection/<uuid>?lang=id | ✅(允许 ?/# 后缀) |
https://payments.stripe.com/upi/instructions/xxx | 🚫 400 INVALID_PAYMENT_LINK |
https://pay.nicepay.co.kr/v1/payment/xxx | 🚫 400 INVALID_PAYMENT_LINK |
https://app.midtrans.com/snap/v1/xxx(非 v4/redirection) | 🚫 400 INVALID_PAYMENT_LINK |
| 其它任意域名 | 🚫 400 INVALID_PAYMENT_LINK |
Gopay 没有二维码,只有链接。 不要试图把二维码内容(
qr...之类的字符串)当链接提交,会被判为INVALID_PAYMENT_LINK。
G3.2 🔴 15 分钟有效期与到期自动判定
Midtrans 支付链接自生成起只有 15 分钟有效,过期后链接即失效——供货商再操作也没有意义,AT 也不会因为付款而失效。因此:
- 提交响应里的
orders[].link_expires_at=created_at + 15 分钟,可直接用于倒计时; - 请务必在 15 分钟内完成支付,不要卡点提交;
- 15 分钟到点后,系统会自动再探一次 AT 并给出结论(扫描周期 60 秒)。
订单创建(pending / claimed / processing)
↓ 等待 15 分钟(每 60 秒扫一轮在途 Gopay 单)
再探一次 AT(经站内中继访问 chatgpt.com,Chrome 指纹)
├─ AT 仍存活(HTTP 200/204)→ 判 failed,failure_reason 注明「链接已过期,AT 仍存活」
├─ AT 已失效(HTTP 401) → 不动作 → 说明付款确实发生过,交由正常流程结算
└─ 判定不了(403 / 超时) → 不动作 → 下一轮再试,绝不误判
| 判定结果 | 订单状态 | 说明 |
|---|---|---|
| AT 仍存活 | failed | 到点后 AT 还能用 = 没人真正付过款 → 判失败。failure_reason = Gopay 链接已过期(15 分钟),AT 仍存活,判定支付未到账 |
| AT 已失效 | 不变 | AT 失效是付款成功的信号(同 §3.1.3 / §5.1),交给人正常完成 |
| 判定不了 | 不变 | 403(Cloudflare 挑战 / 账号 review)、超时、网络异常都不算失败,只会下一轮重试 |
| 无 AT | 不变 | 没有判据,不猜。这类单只能人工处理 —— 所以请务必传 ats |
⚠️ 判失败 ≠ 退款。 15 分钟到点判失败属「未到账」情形,卡密次数不自动返还(与站内公告的未到账规则一致)。 若你确认该笔实际已到账(例如 AT 失效但状态没走到 completed),请凭
receipt联系站长人工复核,不要重复提交同一链接(会被409 DUPLICATE_LINK拦住)。
G4. 错误码速查(Gopay 专属差异)
下表只列与 UPI 通道不同或 Gopay 特有的项;未列出的沿用 §7。
| code | UPI 通道 | Gopay 通道 |
|---|---|---|
AT_REQUIRED | 有(未传 ats → 400) | ❌ 没有(不传也能过,但无 AT 无法判定,见 G3.2) |
NO_LINKS | — | ✅ Gopay 特有(links 全空) |
UNAVAILABLE_CDK | 有 | 有,但返回 400(UPI 是 409) |
CDK_USES_INSUFFICIENT | 409 | 400 |
NO_ACCEPTING_SUPPLIERS | —(用 SUPPLIER_NOT_ACCEPTING) | ✅ 409,Gopay 特有 |
INVALID_PAYMENT_LINK | 只认 Stripe/Nicepay | 只认 Midtrans,报文里会回显识别结果 |
merged_into(字段) | 查卡响应有 | ❌ 没有 |
link_expires_at(字段) | ❌ 没有 | ✅ Gopay 专有 |
pay_method(字段) | "upi" | "gopay" |
⚠️ 同一份代码接两个通道时,务必按 `code` 分支处理,不要按 HTTP 状态码猜。 例如
CDK_USES_INSUFFICIENT在 UPI 是 409、在 Gopay 是 400 —— 按状态码写重试逻辑会错。
G5. cURL 全流程
BASE=https://jmlscan.cloud-ip.cc
CARD=GOPAY-XXXXX-XXXXX-XXXXX-XXXXX
LINK=https://app.midtrans.com/snap/v4/redirection/0f9dff28-55ed-4eba-b712-c2eb5a404c74
# 0) 读实时参数(单价 / 单批上限)
curl -s $BASE/api/v1/public/scan/config
# 1) 查卡余量(确认是 Gopay 卡且次数够)
curl -s -X POST $BASE/api/v1/public/gopay/cdks/query \
-H 'Content-Type: application/json' \
-d "{\"cdk_codes\":[\"$CARD\"]}"
# 2) 提交(★ ats 与 links 一一对应)
curl -s -X POST $BASE/api/v1/public/gopay/orders \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: gopay-20261103-0001' \
-d "{
\"cdk_codes\": [\"$CARD\"],
\"links\": [\"$LINK\"],
\"ats\": [\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"]
}"
# 3) 凭回执查询(免鉴权,★ 必须 R 前缀)
curl -s $BASE/api/v1/public/gopay/orders/R20261103XXXXXXXXXXXXXX
G6. Python 示例(标准库,无第三方依赖)
import json, time, urllib.request, urllib.error
BASE = "https://jmlscan.cloud-ip.cc"
def call(method, path, body=None, headers=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, method=method)
req.add_header("Content-Type", "application/json")
for k, v in (headers or {}).items():
req.add_header(k, v)
try:
with urllib.request.urlopen(req, timeout=30) as r:
return r.status, json.loads(r.read().decode())
except urllib.error.HTTPError as e:
return e.code, json.loads(e.read().decode())
# 1) 提交(每条链接配一个 AT)
def submit_gopay(cdk_codes, links, ats, idem_key):
assert len(links) == len(ats), "links 与 ats 必须一一对应"
status, data = call("POST", "/api/v1/public/gopay/orders",
{"cdk_codes": cdk_codes, "links": links, "ats": ats},
headers={"Idempotency-Key": idem_key})
if status != 201:
raise RuntimeError("提交失败 %s: %s" % (status, data))
return data["data"]["receipt"]
# 2) 轮询(按回执号,免鉴权)
# ★ 终止条件只有 completed / failed / cancelled
# ★ failed 有两种来源:供货商判失败,或 15 分钟到点 AT 仍存活
def poll(receipt, interval=5, timeout=1200):
deadline = time.time() + timeout
while time.time() < deadline:
status, data = call("GET", "/api/v1/public/gopay/orders/" + receipt)
if status != 200:
raise RuntimeError("查询失败 %s: %s" % (status, data))
orders = data["data"]["orders"]
if all(o["status"] in ("completed", "failed", "cancelled") for o in orders):
return orders
time.sleep(interval)
raise TimeoutError("轮询超时")
if __name__ == "__main__":
receipt = submit_gopay(
["GOPAY-XXXXX-XXXXX-XXXXX-XXXXX"],
["https://app.midtrans.com/snap/v4/redirection/0f9dff28-55ed-4eba-b712-c2eb5a404c74"],
["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."],
"gopay-20261103-0001",
)
print("receipt:", receipt)
for o in poll(receipt):
print(o["order_no"], o["status"], "pay_method=", o["pay_method"],
"expires_at=", o["link_expires_at"], "reason=", o["failure_reason"])
G7. 两通道边界矩阵(防误用)
| 你做的事 | 结果 |
|---|---|
GOPAY- 卡查 Gopay 通道 | ✅ 正常 |
GOPAY- 卡查 UPI 通道(/scan/cdks/query) | 🚫 400 INVALID_CDK(格式不符) |
CDK- 卡查 Gopay 通道 | 🚫 400 INVALID_CDK(格式不符,提示应为 GOPAY-) |
| Midtrans 链接提交到 Gopay 通道 | ✅ 正常 |
| Midtrans 链接提交到 UPI 通道 | 🚫 400 INVALID_PAYMENT_LINK |
| Stripe / Nicepay 链接提交到 Gopay 通道 | 🚫 400 INVALID_PAYMENT_LINK(报文会写「当前链接识别为:upi」) |
| 一批里混两种链接 | 🚫 提交到哪个通道都会被拒(Gopay 侧直接 INVALID_PAYMENT_LINK) |
| 用 UPI 回执查 Gopay 通道 | 🚫 404 ORDER_NOT_FOUND |
| 用 Gopay 回执查 UPI 通道 | 🚫 404 ORDER_NOT_FOUND |
同一个 Idempotency-Key 重试 | ✅ 返回首次结果,idempotent: true,不重复扣卡 |
换 Idempotency-Key 重复提交同一链接 | 🚫 409 DUPLICATE_LINK |
G8. 联调 Checklist(Gopay 专用)
- [ ] 确认拿到的是
GOPAY-开头的卡(不是CDK-) - [ ] 先跑一次
POST /public/gopay/cdks/query,确认status=active且remaining_uses够用 - [ ] 链接确认是
https://app.midtrans.com/snap/v4/redirection/...(不是 Stripe / Nicepay) - [ ]
ats与links等长、一一对应,一条链接一个 AT - [ ] 每次提交带唯一
Idempotency-Key(8–80 字符),重试复用同一值 - [ ] 保存返回的
receipt(R前缀)与link_expires_at - [ ] 在
link_expires_at前完成支付(15 分钟窗口,别卡点) - [ ] 轮询间隔 ≥ 5 秒,终止条件为
completed/failed/cancelled - [ ] 读到
failed时先看failure_reason:是否含「链接已过期(15 分钟),AT 仍存活」 - [ ] 同一链接不要换幂等键重复提交(会
409 DUPLICATE_LINK)
G9. 快速排障(Gopay)
| 现象 | 原因与处理 |
|---|---|
400 INVALID_PAYMENT_LINK | 链接不是 Midtrans。看报文末句「当前链接识别为:xxx」——upi = 你拿 Stripe/Nicepay 链接来了;unknown = 域名/路径不匹配 |
400 INVALID_CDK(提示应为 GOPAY-) | 卡是 CDK- 的,或长度/字符集不对(必须是 29 字符、A–Z2–7) |
400 INVALID_CDK(「卡密不存在或不是 Gopay 专用卡」) | 卡不存在,或这张卡还没被标记成 Gopay 类型 → 找站长确认 |
400 UNAVAILABLE_CDK / CDK_USES_INSUFFICIENT | 卡用尽/禁用/过期,或剩余次数少于链接数。先跑 G1.1 查余量 |
409 NO_ACCEPTING_SUPPLIERS | 当前没有在线接单人,或在途单已满。稍后重试,换幂等键重试前请先确认上一批是否已建单 |
409 DUPLICATE_LINK | 这条链接已经被提交过(含你自己上一批)。换链接,不要换幂等键硬重试 |
404 ORDER_NOT_FOUND(回执查不到) | ① 传了批次号 B... 或订单号 O...(必须 R...);② 这个回执属于 UPI 批次 |
订单 20 分钟了还停在 processing | 说明既没被供货商完成,也没走到期判定。多半是没传 AT(无判据不动作)→ 联系站长人工处理 |
订单变 failed,reason 含「AT 仍存活」 | 到点后 AT 还能用 = 没付成功。这是预期行为,不是系统故障。请检查该笔是否真的扣款 |
订单变 failed,但客户说已付款 | 凭 receipt 找站长复核(可查链接 + AT 轨迹)。不要重复提交同一链接 |
📎 本大类与上文的关系:Gopay 通道只覆盖卡密直连这一条路(不支持 API Key / 账号余额 / 批次列表)。需要账号体系、余额、批次管理、提现等能力,请继续用 UPI 通道(§2 / §3.1 / §3.2 / §3.5)。
*文档结束 | 正文 v3.1(对外版)| 2026-10-29 | 增补「Gopay 通道」大类 | 2026-11-03 | Base URL https://jmlscan.cloud-ip.cc*