LinkQueue 用户 API v1
本文档描述 LinkQueue 的公开用户 API。API 与 /kakaoscan 和 /kakaopay 操作页面并存,使用同一 CDK USDT 钱包、任务队列、商家在线状态、退款规则和 30 分钟有效期。
接入地址
生产基础地址(部署包含 API v1 的 0.14.0 或更高版本后生效):
https://kakao.whitexfox.cn/api/v1
本地开发地址:
http://127.0.0.1:3000/api/v1
/api/v1 下的所有响应均为 JSON,并包含以下响应头:
X-LinkQueue-API-Version: 1
Cache-Control: private, no-store, max-age=0
API 面向服务端调用,不开放跨域浏览器凭证共享。不要把 CDK 放进公开前端代码、URL、日志或分析平台。
浏览器扩展验收接口不属于 /api/v1,位于 /api/user/tasks/{taskId}/verification/*。这些端点为不携带 Cookie 的扩展请求开放 CORS,但每次操作仍必须提交完整 CDK,并由服务端校验该 CDK 对任务的所有权。
浏览器扩展与手动验收
| 方法 | 路径 | 用途 |
|---|---|---|
POST | /api/user/tasks/{taskId}/verification/challenge | 用 CDK 申请五分钟有效的一次性 challenge |
POST | /api/user/tasks/{taskId}/verification/evidence | multipart 提交截图或插件状态证据 |
POST | /api/user/tasks/{taskId}/verification/status | 用 CDK 读取当前验收状态 |
POST | /api/v1/tasks/{taskId}/appeal | 商家确认扫码满 60 秒后,提交图片任务未到账申诉 |
POST | /api/merchant/tasks/{taskId}/verification/scan | 商家启动验收窗(链接 60 秒,插件图片 120 秒) |
GET | /api/merchant/tasks/{taskId}/verification/status | 任务所属商家读取验收与证据摘要 |
GET | /api/merchant/tasks/{taskId}/verification/evidence/{evidenceId} | 任务所属商家读取二维码截图 |
challenge 请求体为 {"cdk":"LQ-..."}。challenge 绑定任务与 CDK、使用后立即失效,每个任务最多签发 10 次;它不扣减钱包或扫码次数。证据接口接收 cdk、challenge、source、kind、JSON 字符串 metadata,二维码证据还需一个 image 文件。source=manual 只允许 kind=qr_capture,支持真实 JPEG/PNG/WebP、最大 5 MiB、宽高 120-8192 像素;同任务相同 SHA-256 图片拒绝重复提交。
插件证据类型为 qr_capture、checkout_callback 和 account_subscription。回调证据只要求 provider=adyen、returnObserved=true、回跳 URL 哈希和观测时间;扫码端被视为可信,不强制校验回调域名、路径来源或网络请求完成状态,同一验收会话仍只能提交一次。新建图片任务的插件会在强制刷新二维码页后读取 ChatGPT 会话 AT,并使用 /availability 返回的 P-256 公钥封装为 ECDH/AES-GCM 密文,服务器只保存密文。商家声明扫描后,服务端 worker 每 15 秒解密一次并请求 ChatGPT /backend-api/me 与 /backend-api/subscriptions;只有服务端确认 AT 有效、账号指纹一致且订阅已激活时,才会设置账号已验证。只要 AT 有效、账号指纹一致且订阅仍未激活,服务端就记录 valid_no_subscription。扫码记录满 60 秒后,CDK 持有者可以调用 POST /api/v1/tasks/{taskId}/appeal,申诉会立即触发一次轮询;满足上述三项条件时自动写入唯一 complaint_refund 流水并退回实际扫码扣费。Plus 已激活、AT 失效、网络异常或账号不一致的申诉保留为待复核。没有服务端 AT 的旧链接任务不会仅凭客户端 account_subscription JSON 完成自动验收;AT 密文不会返回到客户端。
浏览器扩展运行在客户设备上,无法提供硬件级远程证明。以上机制可阻止 CDK 越权、challenge 重放、跨任务误传和常见重复数据,但不能证明被完全控制的客户端没有修改扩展或模拟请求。争议订单应保留原始证据,并以支付处理方或平台服务端可验证的交易结果为最高等级依据。
CDK 鉴权
除商家在线状态接口外,每个请求都必须携带 CDK Bearer 凭证:
Authorization: Bearer LQ-ABCD-EFGH-JKLM-NPQR
一个 CDK 只能查看和操作自己的任务。CDK 等同于密码,泄露后持有者可以查看对应任务记录并消耗 USDT 余额。
用户自主合并 CDK
/kakaoscan 与 /kakaopay 都提供用户自主合并入口。当前页面中已验证的 CDK 作为保留目标,用户必须同时证明持有每一个完整来源 CDK:
POST /api/user/cdks/merge
Content-Type: application/json
{
"targetCdk": "LQ-ABCD-EFGH-JKLM-NPQR",
"sourceCdks": [
"LQ-1111-2222-3333-4444",
"LQ-5555-6666-7777-8888"
]
}
一次可合并 1 至 99 个来源 CDK。目标和来源必须全部有效、未过期、属于同一发卡分组,并且不能存在等待、派发、处理中的提链任务或已经提链但尚未进入扫码队列的任务。合并在一个数据库事务中完成:来源剩余 USDT、扫码任务、提链记录、投诉归属和兼容计数转入目标;来源 CDK 永久变为 revoked,但数据库记录、原发卡分组和原创建管理员归属都不会删除或改写。管理页面会将目标和来源标记为“用户自主合并”。
成功响应包含 mergeId、targetPrefix、sourcePrefixes、sourceCount、transferredBalanceUsdt、targetBalanceUsdt、movedTaskCount、movedExtractionCount、movedComplaintCount 和 submissionsUsed。接口响应为 private, no-store;每个客户端 IP 最多 10 次/5 分钟,同一目标 CDK + IP 最多 5 次/5 分钟。合并不可撤销,客户端不得自动重试状态不明的合并请求,应先重新查询目标和来源状态。
接口概览
| 方法 | 路径 | 鉴权 | 用途 |
|---|---|---|---|
GET | /availability | 无 | 查询在线商家和可用扫码栏目 |
GET | /account | Bearer CDK | 查询 USDT 余额和任务历史 |
POST | /tasks | Bearer CDK | 一次提交 1 至 50 条链接(链接栏目) |
POST | /image-tasks | Bearer CDK | multipart 上传 1 至 50 张图片(图片栏目) |
POST | /tasks/{taskId}/cancel | Bearer CDK | 取消仍在排队的任务并退回该单扫码费用 |
POST | /tasks/{taskId}/complaints | Bearer CDK | 对已扫描订单提交投诉和图片证据 |
查询商家在线状态
GET /api/v1/availability
请求示例:
curl -sS https://kakao.whitexfox.cn/api/v1/availability
成功响应,HTTP 200:
{
"online": true,
"onlineCount": 2,
"capabilities": {
"imageDeliveryVerification": {
"enabled": true,
"windowSeconds": 120
}
},
"defaultScanService": "kakao-scan",
"scanServices": [
{
"slug": "kakao-scan",
"nameZh": "Kakao扫码",
"nameEn": "Kakao Scan",
"isDefault": true,
"scanPriceUsdt": "0.800000",
"submissionType": "link",
"noticeZh": "",
"noticeEn": "",
"complaintsEnabled": true,
"placeholderUrl": "https://pay.nicepay.co.kr/v1/checkout/pay/merchant-token/payment-token",
"onlineMerchantCount": 2,
"available": true
}
]
}
capabilities.imageDeliveryVerification 明确表示服务器支持插件图片任务的双信号到账核验及等待窗口;capabilities.accessTokenDeliveryVerification 返回 AT 申诉等待时间、轮询间隔和一次性传输公钥。插件必须在截图和提交前检查两个能力,并要求创建响应包含任务验收会话。scanServices 只包含后台启用的栏目。用户页面左侧栏目名称来自 nameZh / nameEn,每个栏目有独立 scanPriceUsdt;available 表示该栏目至少有一名获授权且在线的商家。submissionType 为 link 或 image,noticeZh / noticeEn 是用户端显眼提醒,complaintsEnabled 控制该栏目是否允许新投诉。服务端仍会在提交事务中再次校验,避免商家在状态查询后离线或权限变化造成竞态。后台在 /admin/scan-pages 管理栏目和商家权限,初始默认栏目为 Kakao扫码。管理员可以逐栏目启用路径规则匹配;开启时执行路径模板与查询参数策略,关闭时只接受配置的精确 HTTPS 域名并允许该域名下任意路径和查询参数。现有栏目升级后默认保持开启、使用链接、允许投诉且不显示提醒。ideal 栏目匹配 https://pm-redirects.stripe.com/authorize/acct_.../(结尾 / 可省略),不接受其他域名、非 acct_ 路径或查询参数;栏目名称、价格、提交类型、提醒、投诉开关和商家权限继续由管理员配置。
查询钱包与任务
GET /api/v1/account?offset=0&limit=100
Authorization: Bearer <CDK>
查询参数:
| 参数 | 类型 | 默认值 | 范围 | 说明 |
|---|---|---|---|---|
offset | integer | 0 | 0 至 100000 | 从第几条任务开始 |
limit | integer | 100 | 1 至 100 | 本页最多返回多少条任务 |
任务按创建时间倒序返回。继续分页时使用响应中的 page.nextOffset,直到 page.hasMore 为 false。
请求示例:
curl -sS \
-H "Authorization: Bearer LQ-ABCD-EFGH-JKLM-NPQR" \
"https://kakao.whitexfox.cn/api/v1/account?offset=0&limit=100"
成功响应,HTTP 200:
{
"cdk": {
"prefix": "LQ-ABCD-EFGH-JKLM",
"initial": 3,
"capacity": 3,
"remaining": 2,
"consumed": 1,
"status": "active",
"expiresAt": null,
"submissionLimit": 6,
"submissionsUsed": 1,
"submissionRemaining": 5,
"balanceUsdt": "1.600000",
"scanPriceUsdt": "0.800000"
},
"tasks": [
{
"id": "a7642aae-8e6a-4d88-a06d-366a4d98a07f",
"url": "https://pay.nicepay.co.kr/v1/checkout/pay/.../...",
"status": "queued",
"cdkPrefix": "LQ-ABCD-EFGH-JKLM",
"createdAt": "2026-08-01T03:00:00.000Z",
"claimedAt": null,
"resolvedAt": null,
"refunded": false,
"queuePosition": 4,
"merchantName": null,
"complaint": null
}
],
"page": {
"total": 1,
"nextOffset": 1,
"hasMore": false
}
}
balanceUsdt 是权威可用余额,scanPriceUsdt 是当前单条扫码价格。提交时按当前价格扣款并把金额快照写入任务;取消、失效、超时或投诉成立时只返还该任务记录的扫码费用。initial、capacity、remaining、submissionLimit 和 submissionRemaining 是旧版客户端兼容字段,不再限制已充值钱包的提交能力;新客户端不应使用它们判断余额。
CDK 的发卡分组(卡网售卖或管理员手动发卡)和创建管理员归属只在认证后的 /admin/cdks 管理页面中展示。后台会分别统计两个分组内有效且未过期 CDK 的 USDT 余额。公开用户 API 不返回发卡分组、管理员名称、登录账号或内部用户 ID。
商家领取后的支付二维码、二维码检查状态和批量结单属于登录后的内部商家工作台,不属于公开 /api/v1 合约,也不会出现在 CDK 用户任务 JSON 中。商家工作台会保存领取时提取的二维码,并在页面可见且仍有待处理任务时每 1 分钟检查一次;检测为失效只改变商家端提示,不会自动选择 scanned 或 invalid,也不会自动退款或计入结算。公开 API 调用方仍应只依据任务的正式 status 和 refunded 字段处理业务结果。
每个商家最多同时持有 15 条未处理订单。只要当前持有量低于 15,商家就可以继续补领空余数量,不需要先处理完全部订单。自动打开支付页面是浏览器端的尽力操作;弹窗被拦截时订单仍会成功认领并保留在商家工作台中。
批量提交任务
POST /api/v1/tasks
Authorization: Bearer <CDK>
Content-Type: application/json
请求体:
{
"scanService": "kakao-scan",
"urls": [
"https://pay.nicepay.co.kr/v1/checkout/pay/VVQwMDA4OTg2Zw==/fpmandate_example1",
"https://pay.nicepay.co.kr/v1/checkout/pay/VVQwMDA4OTg2Zw==/fpmandate_example2"
]
}
要求:
- 每次提交
1至50条链接。 scanService可选;省略时使用当前默认栏目。指定后,所有链接必须符合该栏目的精确 HTTPS 域名。栏目启用路径规则匹配时,模板中的*匹配一个路径段,**匹配多个路径段,并按配置决定是否允许查询参数;关闭路径规则匹配时,该精确域名下的任意路径和查询参数均可提交。- 如果栏目返回
submissionType=image,请改用下方的/image-tasks;链接接口会返回IMAGE_SUBMISSION_REQUIRED。图片接口字段为scanService和重复的images文件字段,每张必须是真实 JPEG、PNG 或 WebP,单张不超过 5 MiB,每批最多 50 张。图片私有保存,只有领取任务的商家可以读取,任务终态后一小时由清理任务删除原图。 - iDEAL 重定向链接应指定
"scanService": "ideal",并使用https://pm-redirects.stripe.com/authorize/acct_.../格式。 - 同一批次不能包含重复链接。
- 必须至少有一名商家在线。
- CDK 当前 USDT 余额必须覆盖
当前扫码单价 × 本批链接数。 - 整个批次位于一个数据库事务中:全部成功或全部失败,失败时不会扣除部分额度。
批量提交图片任务
POST /api/v1/image-tasks
Authorization: Bearer <CDK>
Content-Type: multipart/form-data
表单字段:scanService(必须指定为 submissionType=image 的栏目)和一个或多个 images 文件字段。服务端会验证 MIME 与文件头,拒绝伪造扩展名;单批最多 50 张、单张最多 5 MiB、请求体最多 16 MiB。扩展可额外提交 verification JSON 字符串,字段为 64 位十六进制 accountFingerprint、受支持的 pageOrigin 域名、ISO capturedAt 和 accessTokenEnvelope(ECDH-P256-AES-256-GCM 的临时公钥、IV、密文及 keyId)。此时必须且只能上传一张图片,服务端会在同一事务创建图片任务、二维码证据与验收会话。成功响应与 /tasks 相同,任务的 inputType 为 image,url 是内部占位符。
请求示例:
curl -sS \
-X POST \
-H "Authorization: Bearer LQ-ABCD-EFGH-JKLM-NPQR" \
-F "scanService=gcashscan" \
-F "images=@gcash-qr.png;type=image/png" \
-F 'verification={"accountFingerprint":"<64-hex-sha256>","pageOrigin":"m.gcash.com","capturedAt":"2026-08-08T12:00:00.000Z","accessTokenEnvelope":{"version":1,"algorithm":"ECDH-P256-AES-256-GCM","keyId":"<16-hex>","ephemeralPublicKey":"<base64url>","iv":"<base64url>","ciphertext":"<base64url>"}}' \
https://kakao.whitexfox.cn/api/v1/image-tasks
成功响应,HTTP 201:
{
"tasks": [
{
"id": "a7642aae-8e6a-4d88-a06d-366a4d98a07f",
"url": "image://6f35d1ab-1f8e-4e51-b077-98d91d0f89f8",
"status": "queued",
"inputType": "image",
"cdkPrefix": "LQ-ABCD-EFGH-JKLM",
"createdAt": "2026-08-01T03:00:00.000Z",
"claimedAt": null,
"resolvedAt": null,
"refunded": false,
"queuePosition": 4,
"merchantName": null,
"verification": {
"state": "waiting_scan",
"attemptCount": 1,
"verificationDeadline": null,
"callbackSeen": false,
"accountVerified": false,
"failureReason": null
},
"complaint": null
}
],
"remaining": 2,
"balanceUsdt": "1.600000",
"prefix": "LQ-ABCD-EFGH-JKLM",
"submissionsUsed": 1,
"submissionRemaining": 5
}
响应任务会包含 scanServiceSlug、scanServiceNameZh 和 scanServiceNameEn 快照字段;scanService 价格会在 tasks 的扫码扣费快照中固定,之后后台改价不会影响已提交任务。用户页面和 API 共用同一套栏目规则。
接口当前不接受 Idempotency-Key。如果客户端在提交后超时,不能直接重复上传;应先调用 /account 检查最近返回的完整图片任务 ID,避免重复创建和扣费。
取消排队任务
POST /api/v1/tasks/{taskId}/cancel
Authorization: Bearer <CDK>
请求不需要 JSON 请求体。
curl -sS \
-X POST \
-H "Authorization: Bearer LQ-ABCD-EFGH-JKLM-NPQR" \
https://kakao.whitexfox.cn/api/v1/tasks/a7642aae-8e6a-4d88-a06d-366a4d98a07f/cancel
成功响应,HTTP 200:
{
"task": {
"id": "a7642aae-8e6a-4d88-a06d-366a4d98a07f",
"url": "https://pay.nicepay.co.kr/v1/checkout/pay/VVQwMDA4OTg2Zw==/fpmandate_example1",
"status": "cancelled",
"cdkPrefix": "LQ-ABCD-EFGH-JKLM",
"createdAt": "2026-08-01T03:00:00.000Z",
"claimedAt": null,
"resolvedAt": "2026-08-01T03:02:00.000Z",
"refunded": true,
"queuePosition": null,
"merchantName": null,
"complaint": null
},
"remaining": 3,
"balanceUsdt": "2.400000"
}
只有 queued 状态可以取消。任务被商家领取、处理或超过 30 分钟后不能取消。重复取消已经取消的同一任务是安全的,不会重复退款。
投诉已扫描订单
POST /api/v1/tasks/{taskId}/complaints
Authorization: Bearer <CDK>
Content-Type: multipart/form-data
只允许投诉属于该 CDK、状态为 scanned 且尚未退款的订单。每个任务只能提交一次投诉。表单字段如下:
| 字段 | 类型 | 要求 |
|---|---|---|
reason | text | 投诉说明,去除首尾空格后 10 至 1000 个字符 |
images | file,可重复 | 1 至 3 张真实 JPEG、PNG 或 WebP 图片,每张最多 5 MiB |
服务端会同时检查声明 MIME 类型和文件魔数,修改扩展名或伪造 Content-Type 的文件不会被接受。
curl -sS \
-X POST \
-H "Authorization: Bearer LQ-ABCD-EFGH-JKLM-NPQR" \
-F "reason=商家标记已扫描,但实际页面未完成处理" \
-F "images=@evidence-1.png;type=image/png" \
-F "images=@evidence-2.jpg;type=image/jpeg" \
https://kakao.whitexfox.cn/api/v1/tasks/a7642aae-8e6a-4d88-a06d-366a4d98a07f/complaints
成功响应,HTTP 201:
{
"complaint": {
"id": "28f7a3dc-d5df-4694-a547-a57fbb13a239",
"status": "pending",
"reason": "商家标记已扫描,但实际页面未完成处理",
"adminNote": "",
"createdAt": "2026-08-01T06:00:00.000Z",
"reviewedAt": null
},
"attachments": [
{
"id": "f42ce667-b6cf-4ad4-b1fa-9df88420dde3",
"originalName": "evidence-1.png",
"mimeType": "image/png",
"byteSize": 48213
}
]
}
投诉提交成功后,任务状态立即变为 complained,但在投诉仍为 pending 时继续计入商家的已扫描与未结算数量。之后调用 /account 可在任务的 complaint 字段看到 pending、approved 或 rejected。approved 表示管理员或该订单所属商家确认投诉:返还该任务记录的扫码费用,同时从商家已扫描及可结算数量中扣除 1 条;rejected 表示驳回且不返还,任务恢复为 scanned。已经产生的历史结算记录不会被回写。
任务状态
| 状态 | 含义 | 钱包处理 |
|---|---|---|
queued | 等待商家领取 | 提交时已扣除,可由用户取消 |
claimed | 商家已领取 | 不可取消 |
scanned | 商家标记已扫描 | 不退款 |
complained | 用户已投诉,等待处理或投诉成立 | 暂不计入商家结算;投诉成立时退款一次 |
invalid | 商家标记已失效 | 返还该单记录的扫码费用 |
unclear | 商家标记图片不清晰 | 返还该单记录的扫码费用,不计入商家扫描 |
cancelled | 用户在排队时取消 | 退款一次 |
expired | 提交 30 分钟后仍未完成 | 返还该单记录的扫码费用 |
以 refunded 字段判断任务是否已经退回扫码费用,不要只根据状态推断。
错误响应
所有错误使用同一结构:
{
"error": "当前没有商家在线,暂时无法提交任务",
"code": "NO_MERCHANT_ONLINE"
}
程序逻辑应判断稳定的 code,不要解析可能调整的中文 error。
| HTTP | code | 说明 |
|---|---|---|
400 | INVALID_INPUT | JSON 结构、参数类型/范围、UUID 或用户合并来源数量不符合要求 |
400 | INVALID_URL | 不是允许的 Nicepay HTTPS 链接 |
400 | SCAN_URL_RULE_MISMATCH | 链接不符合所选扫码栏目的 HTTPS 域名、路径或查询参数规则 |
400 | DUPLICATE_URL | 同一批次包含重复链接 |
400 | INVALID_JSON | JSON 语法无效 |
401 | CDK_REQUIRED | 缺少 Authorization 头 |
401 | INVALID_AUTHORIZATION | Authorization 不是 Bearer 格式 |
401 | INVALID_CDK | CDK 格式不正确 |
404 | CDK_NOT_FOUND | CDK 不存在 |
400 | DUPLICATE_SOURCE_CDK | 合并来源 CDK 存在重复项 |
400 | TARGET_IN_SOURCE_CDKS | 目标 CDK 同时出现在来源列表中 |
409 | INVALID_MERGE_TARGET | 合并目标不是有效、未合并的 CDK |
409 | SOURCE_CDK_REVOKED | 合并来源已经删除或被合并 |
409 | SOURCE_CDK_INACTIVE | 用户合并来源不是有效状态 |
409 | SOURCE_CDK_EXPIRED | 用户合并来源已经过期 |
409 | CDK_MERGE_CHANNEL_MISMATCH | 目标与来源不属于同一发卡分组 |
409 | CDK_HAS_ACTIVE_EXTRACTIONS | 参与合并的 CDK 仍有活动提链任务 |
409 | CDK_MERGE_LIMIT_EXCEEDED | 合并后的累计金额或任务计数超出系统上限 |
404 | TASK_NOT_FOUND | 任务不存在或不属于该 CDK |
404 | EXTRACT_JOB_NOT_FOUND | 提链任务不存在或不属于该 CDK |
409 | CDK_DISABLED | CDK 已停用或撤销 |
409 | CDK_EXPIRED | CDK 已过期 |
409 | INSUFFICIENT_USDT | 当前 USDT 余额不足 |
409 | NO_MERCHANT_ONLINE | 当前没有在线商家 |
404 | SCAN_SERVICE_NOT_FOUND | 指定扫码栏目不存在 |
409 | SCAN_SERVICE_DISABLED | 指定扫码栏目已停用 |
409 | IMAGE_SUBMISSION_REQUIRED | 当前栏目要求图片上传 |
409 | LINK_SUBMISSION_REQUIRED | 当前栏目要求链接提交 |
400 | INVALID_SCAN_IMAGE | 图片格式或文件魔数不合法 |
413 | SCAN_IMAGE_TOO_LARGE | 单张图片超过 5 MiB |
409 | COMPLAINTS_DISABLED | 当前扫码栏目已关闭投诉 |
409 | URL_UNAVAILABLE | 至少一条 URL 已在有效任务中 |
409 | TASK_NOT_CANCELLABLE | 任务已被领取或处理 |
409 | EXTRACT_JOB_NOT_CANCELLABLE | 提链任务或关联扫码任务已进入不可取消状态 |
409 | TASK_EXPIRED | 任务超过 30 分钟 |
409 | TASK_NOT_COMPLAINABLE | 任务不是未退款的已扫描订单 |
409 | COMPLAINT_EXISTS | 该任务已经提交过投诉 |
400 | INVALID_COMPLAINT_REASON | 投诉说明长度不符合要求 |
400 | INVALID_COMPLAINT_IMAGES | 未上传 1 至 3 张图片 |
400 | INVALID_COMPLAINT_IMAGE | 图片格式或文件魔数不合法 |
413 | COMPLAINT_IMAGE_TOO_LARGE | 单张图片超过 5 MiB |
413 | BODY_TOO_LARGE | JSON 请求体超过限制 |
415 | MULTIPART_REQUIRED | 投诉接口未使用 multipart/form-data |
415 | JSON_REQUIRED | 提交接口未使用 application/json |
415 | CONTENT_ENCODING_UNSUPPORTED | 请求体使用了不支持的压缩编码 |
429 | RATE_LIMITED | 触发限流,按 Retry-After 等待 |
500 | INTERNAL_ERROR | 服务端暂时无法处理请求 |
限流
限流窗口为 5 分钟,同时按客户端 IP 和 CDK 维度保护接口:
| 接口 | 每客户端 IP | 每 CDK + 客户端 IP |
|---|---|---|
GET /availability | 600 | - |
GET /account | 1500 | 120 |
POST /tasks | 600 | 600 |
POST /tasks/{id}/cancel | 1000 | 120 |
POST /tasks/{id}/complaints | 300 | 20 |
超过限制时返回 HTTP 429 和 Retry-After 秒数。反向代理可能设置更严格的瞬时流量限制,因此批量客户端应平滑发送请求,不要在同一毫秒同时启动全部任务。
KakaoPay 提链 + 扫码页面
网页入口为 /kakaopay,与 /kakaoscan 共用匿名 CDK 认证,但钱包按 USDT 定点金额扣费。数据库初始价格为提链成功 0.100000 USDT、提链失败 0.050000 USDT、扫码 0.800000 USDT,管理员可以在后台分别修改;提交提链任务时会锁定当时的三项价格,处理中再次改价不会影响该任务。实际响应中的 prices 字段仍表示当前配置:
{
"extractionPrice": "0.100000",
"extractionFailurePrice": "0.050000",
"scanPrice": "0.800000"
}
提链所需的上游 CDK 由管理员在 /admin/extraction-pool 的“提链池管理”页面维护,调用方不需要也不能在用户 API 中传入上游 CDK。渠道 1 对接现有 masi.cc.cd 异步任务接口,使用 KSCAN-* CDK;渠道 2 对接 tilian.kuyaoapi.com,使用 KAKAO-* Bearer Key、Idempotency-Key 和 NDJSON 流式终态。管理员分别保存和同步两个渠道的凭据,并在两者中激活一个供新任务使用。任务入队时会固化当时的激活渠道;后续切换只影响新任务,已经排队或处理中的任务继续使用原渠道及原凭据。完整上游 CDK 仅在服务端加密保存,管理页和 API 都只显示掩码。
这组接口供页面或服务端集成使用,CDK 放在 X-CDK 请求头中,不要放入 URL:
| 方法 | 路径 | 作用 |
|---|---|---|
POST | /api/kakaopay/extract | 提交单条 accessToken,或提交 1 至 300 条 accessTokens;AT 加密后先进入持久队列 |
GET | /api/kakaopay/extract | X-CDK 查询提链历史和汇总,支持 offset 与 limit(最大 300) |
GET | /api/kakaopay/extract/{job_id} | X-CDK 轮询提链任务;成功后有在线商家会自动进入扫码队列 |
DELETE | /api/kakaopay/extract/{job_id} | X-CDK 取消等待、派发、处理中的提链任务,或取消已提链但扫码仍在排队的任务 |
提链任务先以 waiting 加密落库,再依次变为 dispatching、processing 和最终状态。调度器只会为任务选择与其渠道快照一致的凭据。每条已配置且可用的上游 CDK 都有独立的 30 条本地并发上限:1 条 CDK 最多同时处理 30 条,5 条 CDK 最多同时处理 150 条;上游实时额度仍是硬边界。渠道 1 创建任务后持续轮询,渠道 2 的每个本地任务使用稳定幂等键并从 NDJSON 流读取对应 Token 的终态。完成或失败后 worker 会按全局先进先出顺序自动补入后续任务,因此 300 条突发请求会按两个渠道各自已接受任务及其凭据容量运行,其余任务保持可见排队。processing 默认超过 15 分钟仍没有终态时会变为 failed,错误码为 EXTRACT_TIMEOUT,并按任务快照扣除一次提链失败费。
worker 默认每个 tick 最多认领或启动 300 条任务(KAKAO_EXTRACT_DISPATCH_BATCH_MAX=300),同时最多发出 50 个创建请求(KAKAO_EXTRACT_DISPATCH_CONCURRENCY=50);每轮最多选取 300 条处理中任务进行轮询(KAKAO_EXTRACT_POLL_BATCH_MAX=300),同时轮询请求限制为 50(KAKAO_EXTRACT_POLL_CONCURRENCY=50)。这些参数限制单次调度和网络负载,不是提链任务的总并发上限;总在途容量由“可用上游 CDK 数量 x 每条 30”及各 CDK 的实时 available_uses 共同决定。
等待和处理中任务不会提前扣费。每条任务按“提链成功价 + 扫码价”和“提链失败价”中的较大值计入 reservedUsdt,并从 availableBalanceUsdt 扣除,防止直接扫码或管理员扣减挪用已承诺资金。上游成功返回有效 Nicepay 链接时只扣提链成功费,成功进入扫码队列时再扣扫码费;上游明确失败、派发异常、派发中断或处理超时进入 failed 时只扣一次任务快照中的失败费。用户主动取消进入 cancelled,不扣失败费并释放未使用承诺。取消已经提链但仍在扫码队列中的任务会取消对应扫码单、释放 URL 并只退回该单记录的扫码费用;已经交付的提链费用不退。扫码任务被商家领取后不能再取消。上游目前没有取消接口,因此取消 dispatching 或 processing 任务会立即停止本站跟踪并释放本地承诺,但远端操作可能继续自行结束;上游 CDK 的实时 pending_uses 仍会限制新任务,防止本地超发。接口返回 balanceUsdt、reservedUsdt、availableBalanceUsdt、extractionCharged、extractionFailureCharged、scanCharged、queuePosition 和掩码后的 atMask,不会返回完整 AT。
批量请求示例:
{
"cdk": "LQ-ABCD-EFGH-JKLM-NPQR",
"accessTokens": ["AT_VALUE_1", "AT_VALUE_2"]
}
接口按一个批次返回 batchId、accepted 与 jobs。同一批次包含重复 AT、超过 300 条、钱包可用余额不足或没有在线商家时,整个批次不会部分入队。POST 仍按同一 IP 每 5 分钟 60 次、同一 CDK + IP 每 5 分钟 30 次限制,但一次批量请求只占用一次请求计数。收到应用限流 429 时响应包含 Retry-After;上游暂时无槽位不会丢弃已经入库的任务,任务保持 waiting 并由 worker 自动重试。
批量客户端建议
- 每个最终用户分配独立 CDK,不要让多个用户共享一个 CDK。
- 客户端总并发建议控制在 50 个连接以内;通常使用 10 至 20 个工作线程即可。
- 同一个 CDK 同时只发送一个提交请求;不同 CDK 可以并行。服务端会串行锁定同一 CDK,提高同一 CDK 的线程数只会增加等待。
- 查询任务状态时每个 CDK 间隔 15 至 30 秒,并加入随机抖动。
- 先查询一次
/availability,没有在线商家时暂停提交。 - 收到
429时严格遵守Retry-After,不要立即重试。 - 网络超时后的提交必须先查历史去重;取消接口可以对同一任务安全重试。
- 记录任务 ID、URL、状态和时间,但不要记录完整 CDK 或 Authorization 头。
Python 批量示例
下面示例按 CDK 并行提交,每个 CDK 的 urls 最多 50 条,并处理 HTTP 429。requests 可安装在调用方自己的 Conda 环境中。
from concurrent.futures import ThreadPoolExecutor, as_completed
import random
import time
import requests
BASE_URL = "https://kakao.whitexfox.cn/api/v1"
def api_request(method, path, cdk=None, json=None, attempts=4):
headers = {"Authorization": f"Bearer {cdk}"} if cdk else {}
for attempt in range(attempts):
response = requests.request(
method,
f"{BASE_URL}{path}",
headers=headers,
json=json,
timeout=(5, 30),
)
if response.status_code != 429:
response.raise_for_status()
return response.json()
retry_after = int(response.headers.get("Retry-After", "5"))
time.sleep(retry_after + random.random())
raise RuntimeError("API rate limit retry exhausted")
def submit_for_user(item):
cdk = item["cdk"]
result = api_request("POST", "/tasks", cdk, {"urls": item["urls"]})
return cdk[-4:], [task["id"] for task in result["tasks"]]
jobs = [
{"cdk": "LQ-ABCD-EFGH-JKLM-NPQR", "urls": ["https://pay.nicepay.co.kr/v1/checkout/pay/.../..."]},
{"cdk": "LQ-2345-6789-ABCD-EFGH", "urls": ["https://pay.nicepay.co.kr/v1/checkout/pay/.../..."]},
]
availability = api_request("GET", "/availability")
if not availability["online"]:
raise RuntimeError("No merchant is currently online")
with ThreadPoolExecutor(max_workers=min(20, len(jobs))) as executor:
futures = [executor.submit(submit_for_user, item) for item in jobs]
for future in as_completed(futures):
masked_cdk, task_ids = future.result()
print(masked_cdk, task_ids)
生产代码还应捕获非 2xx 响应并按 code 分类处理。提交请求发生连接中断或读取超时时,先调用 /account 检查 URL 是否已存在,再决定是否重试。