POST
/v1/cards/applications
提交开卡申请(虚拟/实体经 form_factor 区分)
Scope
cards:write
代会员调用 · 必带 x-on-behalf-of
需 x-idempotency-key
动钱 · 扣开卡费与邮费 + 锁定首充金额(会员可用余额)
这个端点会动钱
失败处置见下方响应表。超时(504)用同一把幂等键重试——我方可能已经处理完;业务失败要换新键,同键会原样返回那次失败。
建单 + 扣开卡费(实体卡另收邮费),同步返回,不外呼上游。
真正的开卡是异步的,终局靠 webhook。
⚠ 回给你的 status 只有三档,是裁剪过的,不是我方的内部状态机。
其中 pending 的真实含义可能是「你的预付余额不够了,这张单在排队」——
我方刻意不在这里说破(会员侧连原因都看不到)。你要看队列深度走
GET /v1/merchant/pending,那是你自己的经营面。
⚠ 有两把幂等键,别混:请求头 x-idempotency-key 是开放 API 那一层
(24 小时窗口,同键回放首次结论);请求体 client_key 是发卡业务自己
那一层(落库、永久)。不传 client_key 我方会随机生成一把 ——
于是超过 24 小时之后同一份 body 重发会真的开出第二张卡。要幂等就自己传。
前置条件
- 会员 KYC 已通过 L1(产品可单独抬到 L2)
- 该会员在该供应商/介质下的持卡数未达上限(在途申请也算一个名额)
- 该会员没有未结清的罚金欠款
- 实体卡:必须已有默认邮寄地址,且收货国家在发卡与配送清单内
- 会员可用余额 ≥ 开卡费 + 邮费 + 首充额
- 你的预付余额够付这一单的批发成本(不够则这张单排队,不报原因)
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 代哪个会员调用 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
form_factor |
string | 可选 | physical = 实体卡,其余一切取值(含缺省)都按虚拟卡处理。
没有拼写校验 —— 打成 Physical 会静默开出一张虚拟卡。 |
provider |
string | 必填 | 发卡供应商代码。当前只有 photonpay(常规卡)与 zeno(共享额度卡)。认不出一律拒。 |
bin |
string | 必填 | 卡 BIN,取自 GET /v1/cards/products 的 bin。该 BIN 必须支持你要的介质与资金模式。 |
source_asset |
string | 必填 | 扣款资产代码(如 USDT)。必须在该产品的允许清单内 —— 不在清单里回 product_not_available。 |
first_topup |
string | 可选 | 首充金额,源资产口径的十进制串。省略 = 不首充。 产品配了首充下限时低于它会被拒。开卡费与首充是两笔独立的钱: 开卡失败退开卡费,而首充那笔在开卡成功后才真的充进卡。USDT 为 6 位以内:"50.00" |
address_id |
string | 可选 | 实体卡的收货地址 id。省略则用该会员的默认地址。地址会快照进单里 —— 会员日后改地址不影响已下的单。 |
client_key |
string | 可选 | 业务级幂等键(见上文两把键的区别)。强烈建议传。 |
响应
201已受理
{
"id": "cap_1b7d90c4-5e2a-4f18-83b6-0c7a4d1e9f22",
"status": "submitted",
"form_factor": "virtual"
}400
product_not_available 供应商/产品/BIN 不可用,或 source_asset 不在清单内 ·
service_unavailable 发卡这条线对你没开通,或你的资金账户被处置 ·
member_context_required 少了 x-on-behalf-of ·
idempotency_key_required / idempotency_key_invalid 幂等键缺失或不是 UUID v4
⚠ 另有一批业务拒绝目前会以 500 api_error 的形态返回(会员余额不足、
KYC 未过、持卡数已达上限、首充低于下限、有未结清罚金、收货国家不支持)——
它们在内部有明确的码,但还没登记进对外码目录。不要照着 500 无限重试:
重试同一把幂等键只会拿回同一个 500。这是我方的缺陷,已在修,
修好后这些会变成 400 且带各自的 code。409
idempotency_key_reused 同一把键换了 body 或换了端点 · idempotency_in_progress 首次请求还在处理触发的事件
调用样例
curl -X POST 'https://api.zise.com/v1/cards/applications' \
-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 '{
"form_factor": "virtual",
"provider": "photonpay",
"bin": "5240",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}'
const res = await fetch(
"https://api.zise.com/v1/cards/applications",
{
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({
"form_factor": "virtual",
"provider": "photonpay",
"bin": "5240",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}),
},
);
// 金额一律按字符串读,不要 JSON.parse 成 number
const data = await res.json();
import requests
res = requests.post(
"https://api.zise.com/v1/cards/applications",
headers={
"authorization": "Bearer $TOKEN",
"x-zise-merchant": "$MERCHANT_ID",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$(uuidgen)",
"content-type": "application/json"
},
json={
"form_factor": "virtual",
"provider": "photonpay",
"bin": "5240",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()