申请虚拟卡
异步开卡接口。请求成功只代表"已入队",开卡完成结果通过 card.opened / card.open_failed webhook 推送,或通过 card/info 轮询。
端点
| 项 | 值 |
|---|---|
| Method | POST |
| Path | /api/v1/openapi/card/apply |
| 鉴权 | HMAC |
| 幂等键 | 必填 Idempotency-Key 头 |
请求字段
不要传 card_type
本接口不需要传 card_type。服务器从 header_id 自动推导,并校验是否在虚拟卡范围内。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
header_id | string | ✅ | hdr_<id> 格式(列卡头) |
package_id | string | ✅ | pkg_<id> 格式(列卡配置) |
first_name | string | ⚠️ | 持卡人名(看 header.require_phone/email) |
last_name | string | ⚠️ | 持卡人姓 |
phone_code | string | ⚠️ | 国家区号(如 86 / 1) |
phone | string | ⚠️ | 手机号(不含区号) |
email | string | ⚠️ | 邮箱 |
use_bound_email | bool | 否 | 默认 false,使用请求中传的 email。传 true 时回退到账号绑定的邮箱。 |
use_bound_phone | bool | 否 | 默认 false,使用请求中传的 phone + phone_code。传 true 时回退到账号绑定的手机号。 |
first_deposit_amount | string | 否 | 自定义首充总额,非负整数(支付/充值资产单位,如 USDT)。空 = 用配置底额。必须 ≥ 套餐的 min_first_deposit(列卡配置);该最低值高于 base 时本字段必填(留空会被 first_recharge_below_min 拒绝)。超出 base 的部分按充值口径收手续费,并与开卡费一并冻结。可用首充预览精确试算。 |
持卡人字段是否必填
header.require_phone == true时需要phone_code+phone(或显式传use_bound_phone == true让服务端读账号绑定的手机号)header.require_email == true时需要email(或显式传use_bound_email == true让服务端读账号绑定的邮箱)first_name/last_name多数情况下需要(virtual_g/virtual_a始终必填,见下文)- OpenAPI 与 H5 用户端的默认值相反:本接口的
use_bound_phone/use_bound_email默认为false—— 服务端使用你传的值。商户的终端用户一般没有在我们这边绑定过手机号/邮箱,回退到"绑定值"会失败。只有当你确实需要使用账号绑定值时才显式传true。
自定义首充
默认按配置底额开卡。如需加充,传 first_deposit_amount(非负整数,且 ≥ base)。底额按 1:1 免费进卡;超出底额的超额按充值口径收手续费,扣费后按 1:1 进卡(USDT/USD,不走汇率),发给提供方的首充取整为整数。开卡费 + 完整计算出的冻结额在开卡时一并从钱包冻结。
套餐还可能设置高于底额的首充最低金额(列卡配置的 min_first_deposit,预览里的 min_first_deposit_amount)。此时 first_deposit_amount 必填且须 ≥ 该最低值,仍然只有底额部分免手续费(例:底额 10、最低 500 → 至少首充 500,10 免费、490 收费)。
开卡前先预览
校验逻辑(格式 / >= base / 最大充值 / 充值限额 / 余额)与首充预览共用。先调预览即可向客户展示精确的 total_freeze / first_recharge_card——数值与本接口 1:1 一致。非法金额在本接口同样被拒,错误 key 与预览的 invalid_reason 相同(见典型错误)。
G 卡(virtual_g)特殊约束
申请 virtual_g 时,服务端会在 header 配置之外强制以下约束:
| 约束 | 规则 | 失败 message_key |
|---|---|---|
| 邮箱 | 始终必填(服务端强制覆盖 header 设置) | bank_card_email_required |
| 手机号 | phone_code + phone 始终必填(服务端强制覆盖 header 设置) | bank_card_phone_required |
| 持卡人姓名 | first_name + last_name 拼接后必须匹配 ^[A-Za-z]+(?: [A-Za-z]+)*$,总长度 ≤ 40 字符(仅 ASCII 字母 + 单个空格分隔;不含数字、符号、非 ASCII 字符) | virtual_g_name_invalid |
| 生日与账单地址 | 不要传 —— 服务端自动生成 | virtual_g_required_fields_missing(仅当服务端自动填充失败时返回) |
申请前先校验姓名
真实世界的持卡人姓名常带变音符号、连字符或撇号——这些字符会被提供方拒绝。请在前端引导终端用户输入只含 ASCII 字母 + 单空格分隔的姓名。
A 卡(virtual_a)特殊约束
virtual_a(A 卡)与 V / R / G 同一提供方体系,遵循提供方自己的开卡协议。无论 header.require_* 配置如何,服务端都会强制以下规则 —— 列卡头返回的已是生效值(A 卡卡头恒为 require_email=true、require_phone=false):
| 约束 | 规则 | 失败 message_key |
|---|---|---|
| 邮箱 | 始终必填 —— 传 email(账号已绑定邮箱时也可 use_bound_email=true) | bank_card_email_required |
| 手机号 | 不收集 —— phone_code / phone / use_bound_phone 会被忽略,提供方协议没有手机号字段 | — |
| 持卡人姓名 | first_name 与 last_name 均必填且不能为空白(纯空格会被拒绝);在冻结任何资金之前校验 | virtual_a_required_fields_missing |
| 生日与账单地址 | 不要传 —— 服务端自动生成 | virtual_a_required_fields_missing(仅当服务端自动填充失败时返回) |
其余能力(自定义首充、幂等、card.opened / card.open_failed 异步结果、充值、冻结 / 解冻、销卡、交易明细、敏感卡信息)与其他虚拟卡类型完全一致。
请求示例
{
"header_id": "hdr_5",
"package_id": "pkg_12",
"first_name": "John",
"last_name": "Doe"
}请求头必须含:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
card_id | string | card_<id> 格式(用于后续查询) |
status | int | 1=pending 2=active 3=failed 4=closing 5=closed 6=frozen |
status_desc | string | 英文描述 |
created_at | string | RFC3339 时间 |
响应示例(成功入队)
{
"code": 200,
"message": "成功",
"data": {
"card_id": "card_12345",
"status": 1,
"status_desc": "pending",
"created_at": "2026-04-29T11:00:12Z"
}
}status=1 不代表卡已可用
返回 status=1 (pending) 只是"已入队"。等 card.opened webhook 到达,或轮询 card/info 看到 status=2 (active) 才能用。
后续流程
典型错误
| HTTP | message_key | 说明 |
|---|---|---|
| 400 | insufficient_balance | 余额不足(开卡费 + 首充) |
| 400 | invalid_first_deposit_amount | first_deposit_amount 不是纯非负整数(小数 / 符号 / 科学计数法 / 位数过多) |
| 400 | first_recharge_below_base | first_deposit_amount 低于配置底额 |
| 400 | first_recharge_below_min | first_deposit_amount(或留空的默认底额)低于套餐首充最低金额 min_first_deposit |
| 400 | first_recharge_exceeds_max | 超额超过 max_recharge_amount |
| 400 | first_recharge_limit_exceeded | 超额超过账号充值限额 |
| 400 | first_recharge_asset_mismatch | 开卡费资产 ≠ 充值资产;该配置不支持自定义超额 |
| 400 | first_recharge_excess_too_small | 扣费 + 取整后到卡为 0,请加大金额 |
| 400 | kyc_required | 请先完成实名认证 |
| 400 | kyc_not_approved | 账号 KYC 未通过 |
| 400 | bank_card_email_required | 卡类型需要邮箱;请传 email 或显式设 use_bound_email=true(仅当账号已绑定时) |
| 400 | bank_card_phone_required | 卡类型需要手机号;请传 phone_code+phone 或显式设 use_bound_phone=true(仅当账号已绑定时) |
| 400 | openapi_card_type_not_supported | header 对应的卡类型不在虚拟卡范围 |
| 400 | account_manager_bind_required | 客户经理管控卡头,而账号未绑定客户经理 —— header_id 只能用 /card_headers/list 返回的值 |
| 400 | account_manager_unavailable | 账号绑定的客户经理已失效;请联系平台客服 |
| 400 | account_manager_open_disabled | 客户经理未对该账号放开此卡头 / 套餐;请只使用列表接口返回的卡头与套餐(列表已不含未放开 / 被关闭的) |
| 400 | virtual_g_name_invalid | (G 卡)持卡人姓名不满足 ASCII 字母 / 单空格 / ≤40 字符规则 |
| 400 | virtual_g_required_fields_missing | (G 卡)服务端无法组装完整的开卡请求(罕见,通常是配置或地址池缺失) |
| 400 | virtual_a_required_fields_missing | (A 卡)first_name / last_name 缺失或为空白,或服务端无法组装完整的开卡请求(卡头 / 地址池问题) |
| 400 | openapi_invalid_header_id | header_id 缺前缀或不存在 |
| 400 | openapi_invalid_package_id | package_id 缺前缀 / 不属于该 header |
| 400 | openapi_idempotency_key_required | 缺幂等键 |
| 400 | openapi_idempotency_key_too_long | 幂等键超 128 字符 |
| 400 | openapi_idempotency_key_invalid_chars | 幂等键含非法字符 |
| 401 | openapi_invalid_credentials | 鉴权失败 |
| 409 | openapi_idempotency_key_conflict | 同 key 不同 body |
| 500 | openapi_apply_card_failed | 服务端异常 |
注意事项
- 开卡费 + 首充会在请求时一并扣款 / 冻结。传自定义
first_deposit_amount时,超额手续费也会一并冻结(见自定义首充)。失败的开卡会自动退还 - 异步任务最长可能跑几分钟。不要因为客户端 30 秒内没收到 webhook 就重试 apply(重试需带相同
Idempotency-Key) card_id一旦返回就永久指向这次请求的卡(即使最终失败)