查询卡片信息
默认返回卡的状态、余额、脱敏卡号。
当请求体传 with_sensitive=true 时,同一接口会在响应里额外返回 完整 PAN / CVV / 到期日 / 持卡人姓名,授权方式二选一:
- 用户二次验证(默认可用,自助) —— 与 Web 端查看 CVV 完全一致:按账户配置的交易认证方式提交
email_code/pin/two_fa_code/sms_code。验证码通过/openapi/card/sensitive/send_code获取,无需管理员审批。 - 凭证免验证特权(需审批) —— 面向全自动 server-to-server 场景,凭证可被授予免二次验证特权。默认关闭 —— 详见下文 敏感卡片信息。
常用于:
- 异步开卡后轮询,直到
status=2 (active) - 业务侧定期同步余额
- 排查 webhook 是否漏投递
- 在自有收银台 / 钱包 UI 中展示 PAN / CVV(两条授权路径均可)
端点
| 项 | 值 |
|---|---|
| Method | POST |
| Path | /api/v1/openapi/card/info |
| 鉴权 | HMAC |
| 幂等键 | 不需要 |
请求字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
card_id | string | ✅ | card_<id> 格式 |
with_sensitive | bool | 可选 | 传 true 表示请求完整 PAN / CVV / 到期日 / 持卡人姓名。默认 false。需卡片为 active,且通过两条授权路径之一。 |
email_code | string | 条件必填 | 邮箱验证码(账户交易认证方式含邮箱时必填)。通过 /openapi/card/sensitive/send_code 发送。免验证特权凭证无需传。 |
sms_code | string | 条件必填 | 短信验证码(认证方式含短信时必填)。 |
pin | string | 条件必填 | 交易 PIN 码(认证方式含 PIN 时必填;用户在 Web 端设置)。 |
two_fa_code | string | 条件必填 | 2FA / Google Authenticator 动态码(认证方式含 2FA 时必填)。 |
请求示例 — 默认(仅状态与余额)
{ "card_id": "card_12345" }请求示例 — 请求敏感字段(用户二次验证路径)
{
"card_id": "card_12345",
"with_sensitive": true,
"email_code": "482913"
}请求示例 — 请求敏感字段(免验证特权凭证)
{
"card_id": "card_12345",
"with_sensitive": true
}响应字段
始终返回
| 字段 | 类型 | 说明 |
|---|---|---|
card_id | string | 回显 |
card_type | string | 全小写:virtual_l / virtual_p / virtual_v / virtual_r / virtual_g / virtual_a |
card_brand | string | 卡组织 |
currency | string | 卡内币种 |
status | int | 1=pending 2=active 3=failed 4=closing 5=closed 6=frozen |
status_desc | string | 英文描述 |
masked_card_no | string | 脱敏卡号(前 6 后 4),如 424242******1234 |
last_four | string | 卡号末 4 位 |
balance | string (decimal) | 卡内可用余额 |
frozen_balance | string (decimal) | 卡内冻结余额 |
activated_at | string nullable | 激活时间(成功后才有) |
created_at | string | 创建时间 |
仅当 with_sensitive=true、授权通过、且 status=2 时返回
| 字段 | 类型 | 说明 |
|---|---|---|
card_number | string | 完整 PAN(无空格 / 无横线) |
cvv | string | 卡安全码(Visa / Mastercard 为 3 位) |
expiry_date | string | 到期日,格式 MM/YY |
first_name | string | 持卡人名(开卡时提供) |
last_name | string | 持卡人姓 |
任一前提不满足时,这些字段会整体省略(不存在、不为 null 也不为空串)。
响应示例 — 默认
{
"code": 200,
"message": "成功",
"data": {
"card_id": "card_12345",
"card_type": "virtual_v",
"card_brand": "VISA",
"currency": "USD",
"status": 2,
"status_desc": "active",
"masked_card_no": "424242******1234",
"last_four": "1234",
"balance": "85.50",
"frozen_balance": "0",
"activated_at": "2026-04-29T11:00:30Z",
"created_at": "2026-04-29T11:00:12Z"
}
}响应示例 — 含敏感字段
{
"code": 200,
"message": "成功",
"data": {
"card_id": "card_12345",
"card_type": "virtual_v",
"card_brand": "VISA",
"currency": "USD",
"status": 2,
"status_desc": "active",
"masked_card_no": "424242******1234",
"last_four": "1234",
"balance": "85.50",
"frozen_balance": "0",
"activated_at": "2026-04-29T11:00:30Z",
"created_at": "2026-04-29T11:00:12Z",
"card_number": "4242420000001234",
"cvv": "123",
"expiry_date": "12/28",
"first_name": "JOHN",
"last_name": "DOE"
}
}状态码(status)
| 值 | 含义 | 说明 |
|---|---|---|
| 1 | pending | 开卡中 |
| 2 | active | 已激活,可用 |
| 3 | failed | 开卡失败(终态) |
| 4 | closing | 销卡中 |
| 5 | closed | 已销卡(终态) |
| 6 | frozen | 已冻结(用户 / 风控) |
敏感卡片信息
敏感字段(card_number / cvv / expiry_date / first_name / last_name)受两条授权路径之一保护:
| 路径 | 适用对象 | 每次请求需要 | 开通方式 |
|---|---|---|---|
| A. 用户二次验证 | 所有凭证(默认) | 按账户交易认证配置提交 email_code / pin / two_fa_code / sms_code —— 仅在信任窗口外需要(见下) | 无需开通 —— 自助可用 |
| B. 凭证免验证特权 | 审批通过的对接方 | 无(不需要验证码) | 需管理员审批,默认关闭 |
路径 A 与 Web 端使用同一套交易认证配置(安全设置 → 交易认证)、同一验证用途、同样的一次性验证码。路径 B 面向无人值守的 server-to-server 自动化 —— 该场景下无法每次请求都由人完成二次验证。
信任窗口 —— 验证一次,窗口内免验证
通过本接口成功完成一次二次验证后,账户进入信任窗口(时长由平台管理员配置,默认 30 分钟,0 = 关闭)。窗口生效期间,再次对自己名下的卡发起 with_sensitive=true 请求无需携带任何验证字段即可成功 —— 一次人工验证后即可程序化批量获取多张卡的 PAN/CVV(例如把卡分发给下游用户的场景)。
- 窗口为固定窗口,不滑动:从验证成功时刻起算,窗口内的免验请求不会续期;到期需重新验证。
- 窗口按账户维度生效,覆盖该凭证名下所有卡。
- 窗口生效期间
send_code返回grace_active=true且不发码 —— 对接方可低成本探测当前是否需要验证码。注意:窗口外调用会实际发码并占用 60 秒冷却,请勿把它当作无副作用的状态探针轮询。 - 管理员封禁始终凌驾于信任窗口之上。
路径 A — 用户二次验证(默认)
第一步:发送验证码
| 项 | 值 |
|---|---|
| Method | POST |
| Path | /api/v1/openapi/card/sensitive/send_code |
| 鉴权 | HMAC |
| 幂等键 | 不需要 |
| 请求体 | {}(空 JSON 对象) |
| 冷却 | 同一账户 60 秒内限 1 次 → 超频返回 429 + Retry-After 头 |
验证用途由服务端固定(get_card_sensitive)—— 本接口不能用于触发转账、提现等其他操作的验证码,其他用途的验证码在这里也不被接受。
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
require_email | bool | 第二步需提交 email_code |
require_sms | bool | 第二步需提交 sms_code |
require_pin | bool | 第二步需提交 pin(不发码 —— PIN 由用户本人持有) |
require_2fa | bool | 第二步需提交 two_fa_code(不发码 —— 由认证器 App 生成) |
email_sent | bool | 6 位验证码已发送至账户绑定邮箱 |
sms_sent | bool | 6 位验证码已发送至账户绑定手机 |
grace_active | bool | 信任窗口生效中:不发码、不消耗冷却 —— 跳过第二步的验证字段直接调 /card/info |
如果账户只使用 PIN 和/或 2FA,则不会发送任何验证码(email_sent=false, sms_sent=false)且不消耗冷却窗口 —— 直接携带本地因子进入第二步。
第二步:携带验证码调用 /card/info
传 with_sensitive=true,并附上第一步响应中标记为需要的所有字段。验证码特性:
- 一次性 —— 验证通过即消费;每次验证都需重新发码(信任窗口生效期间无需验证)
- 限时 —— 10 分钟内有效
- 防爆破 —— 连续输错会临时锁定验证(返回
429)
验证成功即开启信任窗口 —— 窗口内的后续获取完全无需验证字段。
路径 B — 凭证免验证特权(需审批)
默认关闭 — 需显式审批后开通
免验证特权对每一份 API 凭证默认关闭,且受平台级总开关额外保护。如需开通,请联系你的客户经理或提交工单。开通时需要:
- 说明业务用途(如无法引入人工二次验证的无人值守自动化)
- 确认你的环境符合 PCI-DSS 对 PAN 存储 / 展示的要求
- 提供你的
AppID(cp_xxxx...),授权将下发到正确的凭证
凭证开关与平台开关均为 ON 时,with_sensitive=true 无需任何验证码字段即可成功。免验证特权凭证仍可携带验证码字段 —— 会被忽略。
访问前提
下列四个条件必须同时满足,敏感字段才会下发:
- 请求 — 调用方显式传
with_sensitive: true - 授权 — 有效的用户二次验证凭据(路径 A)或凭证具备免验证特权(路径 B)
- 卡状态 — 卡必须处于
status=2 (active);pending/failed/closing/closed/frozen一律拒绝 - 无管理员封禁 — 平台管理员可按卡或按账户维度强制关闭敏感信息获取。封禁同时压制两条授权路径(验证码正确、免验证特权凭证均无效),返回
403 card_sensitive_access_banned;账户维度封禁同时拦截send_code。如认为封禁有误请联系客服。
任一不满足,响应为 403(验证被锁定时为 429)加对应的 message_key。字段不会部分返回 —— 单次请求是"全有或全无"。
合规与处理建议
把返回的 PAN 视为高敏感 PCI 数据
- 不要日志:完整
card_number/cvv不要写到应用日志或 APM - 不要落库:PAN / CVV 不要持久化到自有数据库或分析数仓
- 仅通过 TLS 透传到你的前端,渲染后立即丢弃
- 非展示状态下务必脱敏;优先使用
masked_card_no - 一旦怀疑凭证泄漏,立即在用户后台 Reset OpenAPI Secret
运维说明
- 免验证特权(路径 B)可被回收。一旦被回收,后续不带验证码的
with_sensitive=true请求将在 ~5 分钟内(缓存传播窗口)失效 —— 之后自动落入路径 A,返回对应的*_requiredkey。 - 这些
403错误码与鉴权中间件返回的401不同 ——403表示鉴权通过但二次验证缺失/未通过,或卡状态拒绝敏感访问。 - 此接口绝不会通过 webhook payload 推送敏感字段;敏感字段只能通过本同步接口获取。
典型错误
| HTTP | message_key | 说明 |
|---|---|---|
| 400 | openapi_invalid_card_id | 卡 ID 错误 |
| 400 | openapi_card_type_not_supported | 对非虚拟卡类型请求敏感字段 |
| 401 | openapi_invalid_credentials | 鉴权失败 |
| 403 | email_code_required / sms_code_required / pin_required / 2fa_code_required | 传了 with_sensitive=true 但缺对应的二次验证字段(且凭证无免验证特权)。先调用 send_code 再重试。 |
| 403 | invalid_email_code / invalid_sms_code / invalid_pin / invalid_2fa_code | 提交的二次验证凭据错误或已过期 |
| 403 | email_not_verified / phone_not_verified / no_usable_auth_method | 账户对应验证渠道不可用 —— 请在 Web 端完善安全设置 |
| 403 | card_sensitive_access_banned | 该卡或该账户的敏感信息获取已被管理员强制关闭,任何授权路径均无效 |
| 403 | openapi_sensitive_card_only_active | 卡不在 status=2 (active),敏感字段拒绝下发 |
| 404 | card_not_found | 卡不存在 / 不属于当前账号 |
| 429 | too_many_requests | 验证连续失败被临时锁定(或 PIN 锁定);请遵循 Retry-After 头 |
| 500 | openapi_get_card_info_failed | 服务端异常 |
| 500 | openapi_internal_error | 加载凭证上下文失败 |
/card/sensitive/send_code 的错误:
| HTTP | message_key | 说明 |
|---|---|---|
| 401 | openapi_invalid_credentials | 鉴权失败 |
| 403 | card_sensitive_access_banned | 该账户的敏感信息获取已被管理员强制关闭,不发送验证码 |
| 429 | too_many_requests | 发码冷却中(同一账户 60 秒 1 次);请遵循 Retry-After 头 |
| 500 | send_code_failed | 发送失败 —— 冷却已释放,可立即重试 |
推荐轮询策略
import time
def wait_card_active(card_id, max_minutes=10):
"""异步开卡后轮询,直到 active 或失败"""
deadline = time.time() + max_minutes * 60
backoff = 2
while time.time() < deadline:
info = call("/api/v1/openapi/card/info", {"card_id": card_id})
status = info['data']['status']
if status == 2:
return info['data'] # active
if status in (3, 5):
raise CardOpenFailed(info['data'])
time.sleep(backoff)
backoff = min(backoff * 2, 30) # 指数退避,最多 30 秒
raise TimeoutError(f"Card {card_id} not active in {max_minutes} min")
def fetch_pan_for_display(card_id, email_code=None, sms_code=None, pin=None, two_fa_code=None):
"""一次性拿敏感字段渲染前端,绝不落库。
路径 A(默认):先 POST /openapi/card/sensitive/send_code,向账户所有人
收集所需二次验证凭据后传入本函数。
路径 B(免验证特权凭证):不传任何验证码直接调用。
"""
body = {"card_id": card_id, "with_sensitive": True}
for k, v in {"email_code": email_code, "sms_code": sms_code,
"pin": pin, "two_fa_code": two_fa_code}.items():
if v:
body[k] = v
resp = call("/api/v1/openapi/card/info", body)
if resp.get('code') == 403:
key = resp.get('message_key')
if key and key.endswith('_required'):
raise PermissionError(f"缺少二次验证凭据:{key} —— "
"请先调用 /openapi/card/sensitive/send_code")
if key and key.startswith('invalid_'):
raise PermissionError(f"二次验证未通过:{key}")
if key == 'openapi_sensitive_card_only_active':
raise RuntimeError("卡未激活,敏感字段暂不可获取")
if resp.get('code') == 429:
raise RuntimeError("验证已锁定,请按 Retry-After 时间后重试")
if resp.get('code') != 200:
raise RuntimeError(f"获取卡信息失败:{resp.get('message_key')}")
return resp['data'] # 含 card_number / cvv / expiry_date / first_name / last_name
def request_sensitive_code():
"""路径 A 第一步:触发邮箱/短信验证码,并获知需要哪些验证因子"""
resp = call("/api/v1/openapi/card/sensitive/send_code", {})
if resp.get('code') != 200:
raise RuntimeError(f"发码失败:{resp.get('message_key')}")
return resp['data'] # require_email / require_sms / require_pin / require_2fa / email_sent / sms_sent优先用 webhook
能用 webhook 就用 webhook。card/info 轮询是 fallback —— 仅用于 webhook 接收器临时不可用、或对账场景。