Z Zise Developers

补/换卡申请(余额不在新旧卡之间直接转

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"
}
400state_invalid 原卡状态不允许补卡 · invalid_request body 不是合法 JSON · idempotency_key_required / idempotency_key_invalid「非安全类但原卡已激活」「已有在途补卡单」「reason 不在枚举内」 目前都以 500 api_error 返回(前两种内部有码未登记, 第三种是缺了一道白名单校验)。
404not_found 卡不存在或不在这个会员名下
请求
curl -X POST 'https://api.zise.com/v1/cards/{id}/replacements' \
  -H 'x-auth-token: Bearer $TOKEN' \
  -H 'x-on-behalf-of: $MEMBER_ID' \
  -H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
  -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: {
    "x-auth-token": "Bearer $TOKEN",
    "x-on-behalf-of": "$MEMBER_ID",
    "x-idempotency-key": "$IDEMPOTENCY_KEY",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    "reason": "pan_leaked",
    "note": "用户报告卡号在钓鱼站被输入"
  }),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();
import requests

res = requests.post(
    "https://api.zise.com/v1/cards/{id}/replacements",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-on-behalf-of": "$MEMBER_ID",
        "x-idempotency-key": "$IDEMPOTENCY_KEY",
        "content-type": "application/json",
    },
    json={
      "reason": "pan_leaked",
      "note": "用户报告卡号在钓鱼站被输入"
    },
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zise.com/v1/cards/{id}/replacements",
    strings.NewReader(`{
  "reason": "pan_leaked",
  "note": "用户报告卡号在钓鱼站被输入"
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
req.Header.Set("x-idempotency-key", "$IDEMPOTENCY_KEY")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// 金额字段用 string 接,不要 float64
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.zise.com/v1/cards/{id}/replacements"))
    .header("x-auth-token", "Bearer $TOKEN")
    .header("x-on-behalf-of", "$MEMBER_ID")
    .header("x-idempotency-key", "$IDEMPOTENCY_KEY")
    .header("content-type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "reason": "pan_leaked",
  "note": "用户报告卡号在钓鱼站被输入"
}
"""))
    .build();
// 金额字段用 String / BigDecimal,不要 double
$ch = curl_init('https://api.zise.com/v1/cards/{id}/replacements');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
    'x-on-behalf-of: $MEMBER_ID',
    'x-idempotency-key: $IDEMPOTENCY_KEY',
    'content-type: application/json',
  ],
  CURLOPT_POSTFIELDS => <<<'JSON'
{
  "reason": "pan_leaked",
  "note": "用户报告卡号在钓鱼站被输入"
}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
201
{
  "id": "crp_8e4a2f16-b073-4c95-a2d8-3f6e1c07b94a",
  "status": "requested"
}