改 / 重置 PIN(忘了旧 PIN 走 CVV 那一支)
代会员调用 · 必带
x-on-behalf-of
初始 PIN 在激活那一步就设过了(POST /v1/cards/{id}/activate 收 pin)。
这一条补的是事后:用户忘了 PIN,或在 ATM 上试错到被锁 ——
在此之前没有任何恢复路径,那张卡的线下能力(ATM、chip & pin)
从此报废,唯一出路是走补卡。
两种用法二选一:
- 改:带
current_pin+new_pin; - 重置:带
cvv+new_pin(忘了旧 PIN 时用)。
⚠⚠ PIN 与 CVV 一个字都不许存在你的服务器上。 它们只在这一次
请求里存在,我方不落库、不进日志,你也不该落。这条线上唯一允许
长期保存卡面信息的地方是没有 —— 要展示卡密只有
POST /v1/cards/{id}/secure-session 那条托管页路径。
⚠ 每一次调用都要强认证(不是首次才要)。理由是 cvv 那一支
天然是一台 CVV 判定器:转发给上游,对就成功、错就失败,而 CVV
只有 1000 种取值。所以爆破一次的成本被定成「一次终端用户亲自完成的
因子校验」,外加 5 次/小时/会员的限流(超了回 429 rate_limited,
按会员计 —— 换一张卡重来不会重置计数)。
⚠ 我方不校验 PIN 的位数。 位数由上游与卡组织决定,我方猜一个
(比如「必须 4 位」)会当天挡住一批合法用户;填错的那一侧上游本来
就会明确拒绝。这里只挡明显不可能的形态(空、非数字)。
⚠ invalid_fields 同时覆盖「CVV/旧 PIN 不对」(上游判的),
与「你少传了一个字段」(我方判的,带 fields 数组)。
按有没有 fields 区分这两种。
前置条件
- 卡在这个会员名下
- 该供应商支持 PIN 管理
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string | 必填 | 卡 id |
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-idempotency-key |
string | 必填 | |
x-step-up |
string | 可选 | 终端用户在我方托管屏完成强认证后拿到的 challenge_id |
x-on-behalf-of |
string | 必填 | 代哪个会员调用 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
new_pin |
string | 必填 | 新 PIN,纯数字。不要在你这边存下来。 |
current_pin |
string | 可选 | 旧 PIN(「改」那一支)。与 cvv 二选一。 |
cvv |
string | 可选 | 卡背三位码(「重置」那一支,忘了旧 PIN 时用)。与 current_pin 二选一。 |
响应
200上游已确认
{
"id": "crd_9f2c1b7a-3d51-4a2e-9c08-6b1f0d4e77aa",
"updated": true
}400
step_up_required 需要强认证(终端用户完成后用同一把幂等键、
同一份 body 重发并带 x-step-up)·
invalid_fields 少传字段(带 fields)或 CVV/旧 PIN 不对(不带 fields)·
state_invalid 卡片状态不允许 ·
product_not_available 供应商不可用404
not_found 卡不存在或不在这个会员名下429
rate_limited 超过 5 次/小时/会员。退避,别继续试。请求
curl -X POST 'https://api.zise.com/v1/cards/{id}/pin' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-on-behalf-of: $MEMBER_ID' \
-H 'content-type: application/json' \
-d '{
"cvv": "123",
"new_pin": "4821"
}'const res = await fetch("https://api.zise.com/v1/cards/{id}/pin", {
method: "POST",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"content-type": "application/json",
},
body: JSON.stringify({
"cvv": "123",
"new_pin": "4821"
}),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();import requests
res = requests.post(
"https://api.zise.com/v1/cards/{id}/pin",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"content-type": "application/json",
},
json={
"cvv": "123",
"new_pin": "4821"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()req, _ := http.NewRequest("POST", "https://api.zise.com/v1/cards/{id}/pin",
strings.NewReader(`{
"cvv": "123",
"new_pin": "4821"
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// 金额字段用 string 接,不要 float64HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.zise.com/v1/cards/{id}/pin"))
.header("x-auth-token", "Bearer $TOKEN")
.header("x-on-behalf-of", "$MEMBER_ID")
.header("content-type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"cvv": "123",
"new_pin": "4821"
}
"""))
.build();
// 金额字段用 String / BigDecimal,不要 double$ch = curl_init('https://api.zise.com/v1/cards/{id}/pin');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-auth-token: Bearer $TOKEN',
'x-on-behalf-of: $MEMBER_ID',
'content-type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"cvv": "123",
"new_pin": "4821"
}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
200
{
"id": "crd_9f2c1b7a-3d51-4a2e-9c08-6b1f0d4e77aa",
"updated": true
}