POST
/v1/cards/bind
绑定实体卡(三要素;不挂强认证)
Scope
cards:write
代会员调用 · 必带 x-on-behalf-of
需 x-idempotency-key
把一张已经寄到用户手上、还不属于任何人的实体卡认到这个会员名下。
入参是卡面三要素:卡号 / 有效期 / CVV。
不挂强认证是刻意的 —— CVV 只印在卡背面,三要素本身就是占有权证明;
再加一道验证只会让「拿到卡的人绑不上」。真正不可逆的那一步是激活,
提权加在那里。
⚠ 三要素一个字都不落库、不进日志。 CVV 只在这一次请求的内存里比对,
比完即弃(我方存的是卡号的带密钥 HMAC,不是卡号本身)。你的服务器上
同样不该留下它们 —— 转发一次就等于你也进了 PCI 的射程。
⚠ 换绑在上游是一次性的:换错人那张卡就废了,没有第二次机会,
上游也没有解绑接口。所以「一张卡只许有一次在途/成功的换绑」是
库级约束,你这一侧不要做「先查一次再提交」的重试。
⚠ 这条接口天然是卡号爆破口,所以按人限流:一小时内 5 次真实错误
(网络抖动与被限流本身不计次)。撞上之后要等窗口过去。
status 恒为 binding —— 上游换绑是异步的,终态由回调收敛。
前置条件
- 这张卡在库存里且已发出(
shipped) - 该会员名下有一张状态为
shipped或awaiting_bind的实体卡申请单 - 会员 KYC 已通过 L1(我方要拿它去上游建用卡人)
- 一小时内的失败次数未达 5 次
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 代哪个会员调用 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
pan |
string | 必填 | 完整卡号。空格与连字符会被剥掉;长度 12~19 位(不同卡组织不同,不要按 16 位校验)。 |
expiry |
string | 必填 | 有效期。MM/YY / MMYY / MM-YY / MMYYYY 都收,认不出按校验失败处理。 |
cvv |
string | 必填 | 卡背面安全码,3~4 位。恒定时间比对,比完即弃。 |
application_id |
string | 可选 | 这张卡对应的申请单 id(带不带 cap_ 前缀都收)。省略则由我方按该会员的待绑单自动找。 |
响应
201已受理,上游换绑处理中
{
"application_id": "cap_1b7d90c4-5e2a-4f18-83b6-0c7a4d1e9f22",
"masked_pan": "524012******7890",
"status": "binding"
}400
product_not_available 该 BIN 或产品不支持绑定 · invalid_request body 不是合法 JSON ·
idempotency_key_required / idempotency_key_invalid
⚠ 三要素对不上、卡号格式不对、被限流、这张卡已被别人绑走、
找不到待绑申请单、KYC 未过 —— 目前都以 500 api_error 返回
(内部有区分到「是有效期不对还是 CVV 不对」的码,未登记进对外目录)。
在修好之前,给终端用户的提示只能笼统写「卡面信息不正确」,
并且要自己数次数:连错 5 次会被锁一小时。409
idempotency_key_reused · idempotency_in_progress
调用样例
curl -X POST 'https://api.zise.com/v1/cards/bind' \
-H 'authorization: Bearer $TOKEN' \
-H 'x-zise-merchant: $MERCHANT_ID' \
-H 'x-on-behalf-of: $MEMBER_ID' \
-H 'x-idempotency-key: $(uuidgen)' \
-H 'content-type: application/json' \
-d '{
"pan": "5240121234567890",
"expiry": "08/29",
"cvv": "123"
}'
const res = await fetch(
"https://api.zise.com/v1/cards/bind",
{
method: "POST",
headers: {
"authorization": "Bearer $TOKEN",
"x-zise-merchant": "MERCHANT_ID",
"x-on-behalf-of": "MEMBER_ID",
"x-idempotency-key": "crypto.randomUUID()",
"content-type": "application/json"
},
body: JSON.stringify({
"pan": "5240121234567890",
"expiry": "08/29",
"cvv": "123"
}),
},
);
// 金额一律按字符串读,不要 JSON.parse 成 number
const data = await res.json();
import requests
res = requests.post(
"https://api.zise.com/v1/cards/bind",
headers={
"authorization": "Bearer $TOKEN",
"x-zise-merchant": "$MERCHANT_ID",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$(uuidgen)",
"content-type": "application/json"
},
json={
"pan": "5240121234567890",
"expiry": "08/29",
"cvv": "123"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()