GCash Scan User API

自动化接口参考 / Automation API Reference

https://scan-api.fengl.cc/api/public/v1
API v1.1 · Updated 2026-08-08 · Amount unit: U (USDT) · v1.0 backward compatible

开始与兼容性 / Getting Started

兼容承诺:原有五个接口、方法与必填字段不变。所有 v1.1 请求字段均为可选;旧调用方只传 access_tokenqr 时保持原行为。

自动化调用方建议为每次业务操作生成稳定的 client_request_id。网络超时后先按该字段恢复查询,再决定是否重发原始 POST;同一键与相同内容重发不会二次扣费。

Use a stable client request ID for every business operation. After an ambiguous response, recover by that ID before retrying the original POST.

认证与数据约定 / Authentication

X-API-Key: gcs_YOUR_API_KEY

Authorization: Bearer gcs_YOUR_API_KEY
  • 二选一传入认证头;Bearer 区分大小写。
  • 每个响应都有 X-Request-ID;每个 API Key 每 UTC 分钟最多 120 次请求。
  • 金额是固定两位小数的字符串,例如 "1.00";单位始终为 U
  • 时间为 UTC RFC 3339;未发生的时间和空结果返回 JSON null
  • client_request_id 最长 128 字符,匹配 ^[A-Za-z0-9][A-Za-z0-9._:@+=-]{0,127}$
  • 错误体始终包含机器码 error 和说明 message

余额与当前费用 / Balance

GET/balance
{
  "available": "25.50",
  "locked": "1.00",
  "unit": "U",
  "extraction_fee": "1.00",
  "order_fee": "1.00",
  "channels": [
    {"link_type":"gcash","link_type_label":"GCash","scan_fee":"1.00","extract_fee":"1.00"}
  ]
}

extraction_feeorder_fee 保留为默认兼容字段;按渠道计价请读取 channels 中对应 link_typescan_feeextract_fee。本地可在提交前判断余额是否足够;服务端仍会在创建事务中锁定用户余额并做最终检查。

提链任务 / Extractions

POST/extractions

Request

{
  "link_type": "upi",
  "access_token": "FULL_ACCESS_TOKEN",
  "client_request_id": "local-batch-email-hash-001",
  "email": "user@example.com",
  "metadata": {"batch_id":"20260807_001","channel":"gc-plus"},
  "callback_url": "https://hooks.example.com/gc-plus/callback",
  "callback_secret": "optional-secret"
}
字段必填约束与用途
link_typepix / upi / ideal / kakao_pay / paypal / ph_short / gcash;默认 gcash
access_token去除首尾空白后 20-8192 字符;只转发,原文不落库、不记录日志。
client_request_id幂等与本地恢复键;同一 API 用户内唯一。
email有效邮箱,最长 320 字符。
metadataJSON 对象,编码后不超过 16 KiB。
callback_url最长 2048 字符的绝对 HTTP/HTTPS URL,必须使用公网主机;回环、私网和链路本地地址会被拒绝。
callback_secret最长 512 字节,必须同时提供 URL;加密存储且永不返回。

202 First creation · 200 Replay

{
  "task_no": "01K20B2CDEFG3HJ4KM5NP6QRST",
  "link_type": "upi",
  "client_request_id": "local-batch-email-hash-001",
  "linked_order_no": null,
  "status": "QUEUED",
  "stage": "queued",
  "long_url": null,
  "copy_paste": null,
  "image_url_png": null,
  "error": null,
  "message": null,
  "fee": "1.00",
  "unit": "U",
  "idempotent_replay": false,
  "created_at": "2026-08-07T08:20:00Z",
  "started_at": "2026-08-07T08:20:01Z",
  "finished_at": null,
  "expires_at": "2026-08-07T08:30:00Z"
}

IDEMPOTENCY 服务端仅保存 link_type 与 trim 后 AT 的 SHA-256 指纹。同键同类型、同 AT 返回已有任务和 HTTP 200,不重复扣费;类型或 AT 不同返回 409 idempotency_conflict

GET/extractions/{task_no}

返回同一任务对象,但不含 idempotent_replay。成功时至少有 long_urlcopy_paste;如果提链服务宣称成功但两者都为空,任务以 no_url_returned 失败并退款。

二维码订单 / Orders

POST/orders
Content-Type: multipart/form-data

qr=@payment-qr.png
link_type=upi
client_request_id=local-batch-email-order-001
task_no=01K20B2CDEFG3HJ4KM5NP6QRST
email=user@example.com
callback_url=https://hooks.example.com/gc-plus/callback
callback_secret=optional-secret

qr 必填。link_type 允许值同提链接口,默认 gcash。支持 PNG、JPEG、WebP、GIF,最大 10 MiB。task_no 只能关联当前 API 用户、且 link_type 相同的提链任务,并且一个提链任务最多关联一个订单。未显式提供回调字段时,订单继承关联提链任务的回调配置。

201 First creation · 200 Replay

{
  "order_no": "01K20A1BCDEF2GH3JK4MNP5QRS",
  "link_type": "upi",
  "task_no": "01K20B2CDEFG3HJ4KM5NP6QRST",
  "client_request_id": "local-batch-email-order-001",
  "status": "QUEUED",
  "stage": "queued",
  "error": null,
  "message": null,
  "fee": "1.00",
  "unit": "U",
  "idempotent_replay": false,
  "queued_at": "2026-08-07T08:20:00Z",
  "claimed_at": null,
  "completed_at": null,
  "expires_at": "2026-08-07T08:30:00Z"
}

IDEMPOTENCY 指纹来自 link_type 与二维码原始字节。同键同类型、同内容返回已有订单和 HTTP 200;类型或内容不同返回 409 idempotency_conflict。重放不会上传第二份持久对象或重复冻结费用。

GET/orders/{order_no}

完成的关联订单返回 task_nostatus: "COMPLETED"stage: "scan_completed";对应提链任务会返回该订单的 linked_order_no

本地恢复查询 / Recovery

GET/extractions/by-client-request/{client_request_id}
GET/orders/by-client-request/{client_request_id}

本地断线、超时或重启后,先调用恢复接口。找到资源就继续等待或处理回调;只有收到 404 时才使用原始内容和同一个 client_request_id 重发 POST。

回调与验签 / Webhooks

  • 状态或阶段变化与 callback outbox 在同一数据库事务提交。
  • 任意 HTTP 2xx 表示接收成功;HTTP 重定向不会被跟随。
  • 首次失败后至少重试 5 次:约 1 秒、5 秒、30 秒、2 分钟、10 分钟。
  • 每个事件的所有尝试使用同一个 X-Request-ID;接收方应按它去重。
  • 提供 secret 时发送 X-Signature,值为小写十六进制 HMAC-SHA256,无 sha256= 前缀。
expected = hex_lower(
  HMAC_SHA256(callback_secret, exact_raw_json_body)
)

constant_time_compare(request.headers["X-Signature"], expected)

必须先读取原始 body 并验签,再解析 JSON。重新序列化 JSON 会改变字节序列,导致签名不匹配。业务处理应按 X-Request-ID 幂等,并尽快返回 2xx。

Extraction callback

{
  "type": "extraction.updated",
  "task_no": "01K20B2CDEFG3HJ4KM5NP6QRST",
  "client_request_id": "local-batch-email-hash-001",
  "status": "SUCCESS",
  "stage": "url_ready",
  "long_url": "https://example.com/gcash/result",
  "copy_paste": "https://example.com/gcash/result",
  "image_url_png": null,
  "error": null,
  "message": null,
  "fee": "1.00",
  "unit": "U",
  "created_at": "2026-08-07T08:20:00Z",
  "started_at": "2026-08-07T08:20:01Z",
  "finished_at": "2026-08-07T08:20:08Z",
  "expires_at": "2026-08-07T08:30:00Z"
}

Order callback

{
  "type": "order.updated",
  "order_no": "01K20A1BCDEF2GH3JK4MNP5QRS",
  "task_no": "01K20B2CDEFG3HJ4KM5NP6QRST",
  "client_request_id": "local-batch-email-order-001",
  "status": "COMPLETED",
  "stage": "scan_completed",
  "error": null,
  "message": null,
  "fee": "1.00",
  "unit": "U",
  "queued_at": "2026-08-07T08:20:00Z",
  "claimed_at": "2026-08-07T08:21:00Z",
  "completed_at": "2026-08-07T08:22:00Z",
  "expires_at": "2026-08-07T08:30:00Z"
}

状态与阶段 / Status and Stage

Extraction

StatusStage含义与资金
CREATEDcreated本地创建并冻结费用,外部提交中。
QUEUEDqueued外部已接受,费用冻结。
RUNNINGrunning提链处理中,费用冻结。
SUCCESSurl_readyURL 可用,费用扣除。
FAILEDfailed失败,费用退回。
EXPIREDexpired任务超时,费用退回。

Order

StatusStage含义与资金
QUEUEDqueued等待领取,费用冻结。
CLAIMEDclaimed工人已领取。
CLAIMEDwaiting_scan二维码已成功打开,等待扫码完成。
COMPLETEDscan_completed扫码完成,费用扣除。
CANCELLEDscan_failed扫码失败,费用退回。
EXPIREDexpired二维码或订单超时,费用退回。
REFUNDEDrefunded管理员原子退款完成。

订单 stage 枚举还保留 cancelled,用于后续显式取消流程。

稳定错误码 / Errors

{
  "error": "no_url_returned",
  "message": "Extraction completed but returned no usable URL"
}
HTTP错误码
400invalid_request, invalid_access_token, qr_required, invalid_qr, invalid_client_request_id, invalid_email, invalid_metadata, invalid_callback_url, invalid_callback_secret
401invalid_api_key
402insufficient_balance
403ip_not_allowed, user_disabled
404not_found, task_not_found
409idempotency_conflict, task_already_linked, qr_expired
415unsupported_image
429rate_limit_exceeded
500internal_error
502extraction_submit_failed
503extractor_unavailable,或二维码存储暂时不可用时的 internal_error

资源终态的 error 还可能为:no_url_returnedextraction_failedextraction_timeoutscan_failedqr_expiredorder_timeout

gc-plus 推荐链路 / Workflow

注册成功
→ GET /balance 检查 extraction_fee
→ POST /extractions(稳定 client_request_id + callback)
→ 结果不明确时按 client_request_id 恢复查询
→ 等待 SUCCESS / url_ready,打开 long_url 或 copy_paste
→ 页面出现二维码后截图
→ GET /balance 检查 order_fee
→ POST /orders(新的 client_request_id + task_no)
→ 结果不明确时按 client_request_id 恢复查询
→ 等待 COMPLETED / scan_completed
→ 刷新页面确认开通,关闭窗口,标记 gc-plus 成功

正常进度优先消费 callback;漏回调或本地重启后使用恢复 GET。GET 可安全重试;POST 仅在复用同一个 client_request_id 和原始内容时安全重试。

Telegram-bound Web workspace

The root user portal is public. Only operations require binding. POST /api/v1/web/auth/start creates a one-time Telegram deep link. Poll /api/v1/web/auth/poll/:code until the user approves the Bot callback; the response contains a JWT for the pending operation.