POST
/v1/cards/{id}/replacements
补/换卡申请(余额不在新旧卡之间直接转)
Scope
cards:write
代会员调用 · 必带 x-on-behalf-of
需 x-idempotency-key
只建单,不动钱、不外呼。冻结原卡 / 退余额 / 注销 / 发新卡由我方的
执行器按状态推进。
⚠ 余额绝不在新旧卡之间直接转 —— 上游没有卡间转账接口。唯一正确的
路径是「原卡 → 退回可用余额 → 用户自行充值新卡」。做成「自动转」会在
两个上游动作之间留一个钱不在任何一张卡上的窗口,那个窗口里出任何错
都只能人工平账。
⚠ 收不收费按「是谁的原因」分,不按「麻不麻烦」分。
安全类(pan_leaked / stolen / pin_locked / damaged /
user_request / expiring)= 原卡冻结 → 退余额 → 注销为 replaced,
收补卡费与邮费;
非安全类(lost_in_transit / not_received / production_error /
name_error)= 原卡从未到达用户手上、库存行作废,两样都不收。
⚠ 非安全类要求原卡从未激活:对一张 active 的卡报「未收到」会被拒
(走那条路会跳过退余额那一步)。
⚠ 一张卡同时只许有一个在途补卡(库级约束)。并发提交第二次会被拒,
不会静默成功。
⚠ 原卡的终态是 replaced 不是 closed —— 两者刻意分开:
closed 的含义是「用户自己销的」,覆盖掉就把来源证据抹了。
前置条件
- 原卡不在
closed/replaced/expired - 非安全类场景:原卡状态不是
active - 这张卡没有其它在途的补卡单
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string | 必填 | 原卡 id |
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 代哪个会员调用 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason |
"pan_leaked" | "stolen" | "pin_locked" | "damaged" | "user_request" | "expiring" | "lost_in_transit" | "not_received" | "production_error" | "name_error" | 必填 | 补卡场景。⚠ 这一枚举目前没有在这个端点上做白名单校验 ——
传一个不在表里的值会撞库级约束并以 500 api_error 返回。
请严格按这十个值发。 |
note |
string | 可选 | 备注,落我方留痕(截断到 200 字符)。 |
响应
201已受理。
status 恒为 requested,后续推进由我方执行器完成。{
"id": "crp_8e4a2f16-b073-4c95-a2d8-3f6e1c07b94a",
"status": "requested"
}400
state_invalid 原卡状态不允许补卡 · invalid_request body 不是合法 JSON ·
idempotency_key_required / idempotency_key_invalid
⚠ 「非安全类但原卡已激活」「已有在途补卡单」「reason 不在枚举内」
目前都以 500 api_error 返回(前两种内部有码未登记,
第三种是缺了一道白名单校验)。404
not_found 卡不存在或不在这个会员名下
调用样例
curl -X POST 'https://api.zise.com/v1/cards/{id}/replacements' \
-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 '{
"reason": "pan_leaked",
"note": "用户报告卡号在钓鱼站被输入"
}'
const res = await fetch(
"https://api.zise.com/v1/cards/{id}/replacements",
{
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({
"reason": "pan_leaked",
"note": "用户报告卡号在钓鱼站被输入"
}),
},
);
// 金额一律按字符串读,不要 JSON.parse 成 number
const data = await res.json();
import requests
res = requests.post(
"https://api.zise.com/v1/cards/{id}/replacements",
headers={
"authorization": "Bearer $TOKEN",
"x-zise-merchant": "$MERCHANT_ID",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$(uuidgen)",
"content-type": "application/json"
},
json={
"reason": "pan_leaked",
"note": "用户报告卡号在钓鱼站被输入"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()