发卡宝 API v1 只有两个接口:幂等核销(consume)与对账查询(query)。
apikey 或 method=check/setUsed 的请求一律返回 410 GONE,未升级的集成请尽快切换到本页协议。| Header | 说明 |
|---|---|
X-API-Key | 商户 API Key(身份标识,重置后同步变化) |
X-Timestamp | Unix 秒级时间戳,与服务端偏差 ≤ ±300 秒;只取一次,签名与发送必须同值 |
X-Sign | 按下述规则计算的签名(小写 hex),服务端常量时间比较 |
POST: Sign = HMAC_SHA256( Secret, Timestamp + "." + 请求体原文(raw body) )
GET : Sign = HMAC_SHA256( Secret, Timestamp + "." + URL查询串原文(QUERY_STRING) )
要点:
1. POST 参与签名的是"实际发出的字节"——先 json_encode 得到 $body,签名用 $body,发送也用 $body,中间不得重排/格式化;
2. GET 的查询串自行拼接(如 http_build_query 的结果),签什么就发什么,避免 curl/代理二次编码改变顺序;
3. 签名失败返回 401 INVALID_SIGN,可用示例 Demo 对照排查。
| 规则 | 说明 |
|---|---|
clientTxnId 每单唯一 | 使用 SDK 的 FakabaoClient::newTxnId() 自动生成(格式 时间_随机),并先写入你自己的订单表再发请求;自行生成请保证随机且唯一(勿嵌卡号,单号会进日志)。 |
| 重试必须复用同一个 clientTxnId | 超时/断网属于“结果未知”:拿原单号重发 consume(命中重放语义)或调 query 确认。禁止换新单号重试同一张卡,否则失去幂等保护 |
| 重放返回同一结果 | 同单号重复请求返回 replay:true 且 pid 与首次一致,不会二次扣卡;同单号换卡号请求被拒(INVALID_PARAM) |
| 一卡一销 | 平台唯一台账;卡被别的单烧掉后,你的新单收到 ALREADY_USED |
| 接入方本地订单三态 | 推荐 pending / success / failed:发放权益与订单翻 success 在同一个本地事务内原子完成(只允许翻转一次);“结果未知”保持 pending,由你的定时任务 query 收敛 |
| 补发唯一依据 | query 返回 usedTxn == 自己的单号 才可补发;不等 = 卡经其他渠道消耗,严禁发放,转人工 |
| err | HTTP | 含义 | 接入方动作 |
|---|---|---|---|
NOT_FOUND | 404 | 卡密不存在 | 终态失败,提示换卡 |
ALREADY_USED | 409 | 卡已被核销(含被别的单烧掉) | 终态失败,提示换卡 |
REFUNDED | 409 | 该核销单已被客服退款,拒绝重放 | 终态失败 |
INVALID_PARAM | 400 | 参数缺失/非法,或单号与卡号不符 | 修正后重试(未烧卡,安全) |
INVALID_AUTH / INVALID_KEY / INVALID_SIGN / INVALID_TIMESTAMP | 401 | 鉴权头缺失 / Key不存在 / 签名错 / 时间偏差>10min | 检查 Key、Secret、服务器时钟(官方 SDK 内置自动对时自愈,无需人工干预);校时后用原单号重试 |
ACCOUNT_DISABLED / API_DISABLED / IP_DENIED | 403 | 账号或 API 被禁用 / IP 不在白名单 | 联系管理员 |
RATE_LIMITED | 429 | 触发限流 | 退避后原单号重试 |
BUSY / 5xx / 超时 / 非JSON响应 | 500 | 结果未知(不是失败!) | 订单保持 pending,返回用户“处理中”;稍后 query 对账收敛,不得判死 |
GONE | 410 | 调用了已下线的老协议 | 升级到 v1 |
行锁原子操作:校验 + 登记台账 + 标记已用一次完成,是整条链路唯一需要同步等待的调用(建议连接 3s / 总 6s 超时)。幂等:同 clientTxnId 重放返回相同结果。
| 参数 | 必填 | 说明 |
|---|---|---|
method | 是* | 固定 "consume"(*独立入口 /api/v1/consume.php 无需 method 字段) |
cardNo | 是 | 卡号(本平台 17 位:ABCDE-FGHIJ-KLMNO) |
clientTxnId | 是 | 接入方订单号,字符集 A-Za-z0-9_.:-,长度 1–64,同一商户内唯一,重试复用;推荐用 SDK newTxnId() 生成 |
POST /api/index.php
Content-Type: application/json
X-API-Key: 9ffa60a9c4aa4eb512312d680e2974dc
X-Timestamp: 1789502400
X-Sign: hmac_sha256(secret, "1789502400." + 请求体原文)
{"method":"consume","cardNo":"BQ7WK-T9U4W-NGRTJ","clientTxnId":"20260916032156_3f9a27de01c8b4f5"}
| 响应 | 示例 / 处理 |
|---|---|
| 首次核销成功 | {"code":1,"data":{"pid":38,"replay":false}} |
| 同单号重放 | {"code":1,"data":{"pid":38,"replay":true}}(pid 与首次一致) |
pid 为发卡宝商品ID(卡种),到账换算由接入方映射(例:38=30天、39=90天、40=永久)。收到映射外的未知 pid:卡已核销,绝不可判死,转人工核对发放。查询核销台账,供超时收敛、定时对账、人工补单前核对。先按 clientTxnId 查,未命中自动回落按 cardNo 查(并可追溯卡表兜底),单参数也可查,两个都带最稳。
| 参数(querystring) | 必填 | 说明 |
|---|---|---|
clientTxnId | 二选一 | 核销时携带的接入方订单号 |
cardNo | 二选一 | 卡号 |
GET /api/index.php?clientTxnId=20260916032156_3f9a27de01c8b4f5
(签名原文 = Timestamp + "." + "clientTxnId=20260916032156_3f9a27de01c8b4f5")
已核销:{"code":1,"data":{"used":true, "pid":38,"usedTxn":"20260916032156_3f9a27de01c8b4f5","usedAt":"2026-09-16 03:21:56"}}
未核销:{"code":1,"data":{"used":false,"pid":0, "usedTxn":"","usedAt":""}}
used==true && usedTxn==本单 → 允许执行本地发放(只发一次);usedTxn≠本单 → 他渠道消耗,禁止发放转人工;used==false 且订单已超时(如>30分钟)→ 可安全置失败,允许用户复用原单号重新提交。平台不提供自动回滚(卡状态单调向前)。“本地没办成”的兜底不是退卡,而是本地重试发放。
1 本地落单 pending(生成 clientTxnId,落库成功后才发请求)
2 POST consume(唯一同步阻塞,1 次 HTTP)
├─ code=1 → 3
├─ NOT_FOUND / ALREADY_USED / REFUNDED → 订单 failed,提示换卡
└─ 超时 / 429 / BUSY / 5xx / 鉴权错 → 保持 pending,返回"处理中"
3 本地事务:按 pid 发放权益 + 订单翻 success(同一事务,原子只翻转一次)
4 定时任务(每分钟):扫 pending 超时单 → GET query
├─ used=false 超30分钟 → failed(用户可复用原单号重提)
├─ used=true usedTxn==本单 → 补执行第3步(自愈)
└─ used=true usedTxn!=本单 → 转人工,禁止发放
5 客服通道:误核销人工 refund,卡恢复可用;你的历史单号再重放会收到 REFUNDED
<?php
require 'FakabaoClient.php'; // 下载见下方「官方 PHP SDK」
$c = new FakabaoClient('https://www.fakabao.net', '你的KEY', '你的SECRET');
$card = 'ABCDE-FGHIJ-KLMNO';
$txn = FakabaoClient::newTxnId(); // 自动生成,无需商户号
// TODO 先把 $txn 以 pending 写入你自己的订单表,再发起核销(重试/对账复用,禁止重新生成)
try {
$r = $c->consume($card, $txn); // ['pid'=>38,'replay'=>false]
// TODO 本地事务:按 $r['pid'] 发放权益 + 订单翻 success(同一事务,只翻转一次)
} catch (FakabaoException $e) {
if ($e->isRetryable()) {
// TIMEOUT/BUSY/RATE_LIMITED/BAD_RESPONSE/5xx:保持 pending,
// 前端文案"处理中,1分钟后自动到账",定时任务调 query 收敛
} else {
// NOT_FOUND / ALREADY_USED / REFUNDED:终态失败,订单置 failed,提示换卡
}
}
// 定时任务:对账 pending 超时单
$q = $c->query($card, $txn); // ['used','pid','usedTxn','usedAt']
if ($c->isUsedByMe($card, $txn)) { /* 唯一允许自动补发的情况 */ }
?>
已封装签名、超时语义、错误分类与补发核对,含充值闭环 + reconcile 模板。
| 问题 | 回答 |
|---|---|
| 一直 401 INVALID_SIGN? | 三点排查:① POST 签的是请求体原文(encode 后不许再改动);② GET 签的是 QUERY_STRING 原文(自行拼接,别让库改写);③ Secret 重置后未同步 |
| INVALID_TIMESTAMP? | 时钟偏差超 10 分钟。官方 SDK 内置自动对时自愈,会自动对齐服务端重发;自研接入者校准 NTP 后用原单号重试即可,不会重复扣卡 |
| consume 超时,卡到底烧没烧? | 未知,别猜。等1分钟拿原单号 query:used=false → 可安全重发;used=true 且 usedTxn=自己 → 直接补发放。也可用同一单号再 consume,命中重放返回相同结果 |
| 能回滚已核销的卡吗? | API 不提供回滚(防“退款重烧”双花)。误核销走客服人工 refund:卡恢复可用,但旧单号重放会收到 REFUNDED |
| 用户重复提交同一张卡? | 你侧落订单表并复用同一 clientTxnId;即便发了不同单号,平台一卡一销兜底,败者收 ALREADY_USED |
| 还在用老 apikey/setUsed 代码? | 老协议已 410 下线,请尽快升级;怀疑泄漏的旧 Key 请在商户中心重置作废 |