提交开卡申请(虚拟/实体经 form_factor 区分)
代会员调用 · 必带
x-on-behalf-of
需 x-idempotency-key
动钱 · 默认扣会员可用余额;funding_source=merchant 时只扣商户预付大账户
这个端点会动钱
失败处置见下方响应表。超时(504)用同一把幂等键重试——我方可能已经处理完;业务失败要换新键,同键会原样返回那次失败。
建单 + 扣开卡费(实体卡另收邮费),同步返回,不外呼上游。真正的开卡是异步的,终局靠 webhook。
新申请在商户预付不足时返回 service_unavailable,不会创建待资金申请。201 表示已受理:人工审核产品先返回 pending_review,自动审核产品先返回submitted。用返回的 id 查询申请详情,issued 才表示已出卡。
⚠ 有两把幂等键,别混:请求头 x-idempotency-key 是开放 API 那一层(24 小时窗口,同键回放首次结论);请求体 client_key 是发卡业务自己那一层(落库、永久)。不传 client_key 我方会随机生成一把 ——于是超过 24 小时之后同一份 body 重发会真的开出第二张卡。要幂等就自己传。
前置条件
- 本人 KYC 已满足产品要求,或使用有效快捷 KYC 绑定(L0 亦可;要求 L2 的卡产品不能使用快捷资料绕过认证)。显式选择时,报价与申请传同一
quick_kyc_binding_id;不改变本人认证等级 - 该会员在该供应商/介质下的持卡数未达上限(在途申请也算一个名额)
- 该会员没有未结清的罚金欠款
- 实体卡:必须已有本人邮寄地址;国家限制仅校验本次开卡使用的 KYC,邮寄国家只影响邮费
- 默认模式:会员可用余额 ≥ 开卡费 + 邮费 + 首充额
- 商户代付模式:仅 merchant_hosted 商户可用;开卡费与首充都从预付扣
- 你的预付余额够付这一单的批发成本(不够返回
service_unavailable,不建单)
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 代哪个会员调用 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
quick_kyc_binding_id |
string | 可选 | 当前商户、当前会员已绑定快捷 KYC 的 binding.id;L0、L1、L2 会员均可显式选择,用此资料开符合条件的卡产品。不覆盖本人实名或提升认证等级,也不能绕过产品的 L2 要求。报价时传同一编号。 |
quick_kyc_email |
string | 可选 | 无真实 L1 且需要自动分配新快捷 KYC 时必填,必须从未用于任何 KYC。已有绑定复用无需填写。 |
form_factor |
string | 可选 | physical = 实体卡,其余一切取值(含缺省)都按虚拟卡处理。
没有拼写校验 —— 打成 Physical 会静默开出一张虚拟卡。
提供 product_id 时以产品介质为准;显式传入不一致的介质会返回 product_not_available。 |
product_id |
string | 可选 | 产品标识,取自 GET /v1/card/products 的 id(cpd_…)。
推荐用它下单 —— BIN 与发行安排由它唯一确定
(含介质)。
认不出(或不属于你)一律回 product_not_available。 |
bin |
string | 可选 | 卡 BIN,取自 GET /v1/card/products 的 bin。
只在不传 product_id 时使用;该 BIN 必须支持你要的介质与资金模式。 |
source_asset |
string | 必填 | 扣款资产代码(如 USDT)。必须在该产品的允许清单内 —— 不在清单里回 product_not_available。 |
first_topup |
string | 可选 | 首充金额,源资产口径的十进制串。省略 = 不首充。
产品配了首充下限时低于它会被拒。开卡费与首充是两笔独立的钱:
开卡失败退开卡费,而首充那笔在开卡成功后才真的充进卡,
到账另发 card.topup.credited(与事后自己充一笔同一条事件)。USDT 为 6 位以内:"50.00" |
funding_source |
"member" | "merchant" | 可选 | 开卡费用付款方。member 保持既有会员自付行为;merchant
从商户预付大账户扣批发成本(开卡费与首充都算),不扣会员钱包。
商户代付仅适用于 merchant_hosted 商户。 |
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 首次请求还在处理会触发的事件
card.application.approvedcard.application.rejectedcard.application.submittedcard.application.awaiting_bindcard.topup.credited
绿 = 终局且是好消息 · 红 = 终局且要处置 · 紫 = 中间态。点进去看事件体与验签。
请求
curl -X POST 'https://api.zinfra.vip/v1/cards/applications' \
-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 '{
"form_factor": "virtual",
"product_id": "cpd_1042",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}'const res = await fetch("https://api.zinfra.vip/v1/cards/applications", {
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({
"form_factor": "virtual",
"product_id": "cpd_1042",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();import requests
res = requests.post(
"https://api.zinfra.vip/v1/cards/applications",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
json={
"form_factor": "virtual",
"product_id": "cpd_1042",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/cards/applications",
strings.NewReader(`{
"form_factor": "virtual",
"product_id": "cpd_1042",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}`))
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 接,不要 float64HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.zinfra.vip/v1/cards/applications"))
.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("""
{
"form_factor": "virtual",
"product_id": "cpd_1042",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}
"""))
.build();
// 金额字段用 String / BigDecimal,不要 double$ch = curl_init('https://api.zinfra.vip/v1/cards/applications');
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'
{
"form_factor": "virtual",
"product_id": "cpd_1042",
"source_asset": "USDT",
"first_topup": "50.00",
"client_key": "6d0f3a2e-8b41-4c77-9a10-2f5c7e91b3d4"
}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
201
{
"id": "cap_1b7d90c4-5e2a-4f18-83b6-0c7a4d1e9f22",
"status": "submitted",
"form_factor": "virtual"
}