POST、DELETE /card-groups)及主钱包↔卡组子池 划款(POST .../fund、.../release);同一路径 /card-groups 以 HTTP 方法区分查询(GET)与创建(POST)。详见 §7.3–7.7。GET /cards 改为分页对象;开卡支持 platformCardGroupId;卡产品增加 spendingConstraintCeilingJson;多处 memberUserId 改为可选(省略时使用商户主会员)。| 项 | 说明 |
|---|---|
| 协议 | 生产环境使用 HTTPS;字符集 UTF-8。 |
| 数据格式 | 请求与响应体为 application/json(GET 无 body 除外)。 |
| 基础路径 | 所有开放接口均位于 {MERCHANT_API_BASE}/open/v1。将 {MERCHANT_API_BASE} 替换为服务方提供的网关根地址(不含末尾斜杠)。 |
| 示例 | https://api.example.com/open/v1/wallets |
| 终端用户 | 多数接口的 memberUserId 可选;省略时使用商户主会员账号(与 member-portal 主账号一致)。 |
除「健康检查」接口外,所有 /open/v1/... 请求必须在 HTTP 头中携带访问凭据与签名。签名算法为 HMAC-SHA256,输出为小写十六进制字符串。
将以下 4 行按顺序用换行符 \n(ASCII 0x0A)连接,得到 UTF-8 字节序列后作为 HMAC 的输入消息:
GET、POST。/open/v1/cards(不含域名与查询串)。key=value,以 & 连接;键与值均需 URL 编码(空格为 %20)。若无查询参数,本行为空字符串(不要写 ?)。{} 的字节不同)。HMAC输入 = UTF8( method + "\n" + path + "\n" + canonicalQuery + "\n" + bodySha256Hex )
Signature = 小写十六进制( HMAC_SHA256( UTF8(SecretKey), HMAC输入 ) )
SecretKey 为服务方在创建或轮换 API 凭据时向您下发的私钥;轮换宽限期内新旧密钥可能均可验签,以交付说明为准。
若请求的 URL 不在当前版本开放清单内,即使签名正确,网关也可能返回 HTTP 401 及业务错误码 40113(OPEN_API_PATH_NOT_ALLOWED),表示该路径不对商户开放。
| HTTP 头 | 必填 | 说明 |
|---|---|---|
X-Access-Key-Id | 除健康检查外必填 | 服务方分配的访问密钥 ID。 |
X-Merchant-Code | 除健康检查外必填 | 您的商户编码,须与访问密钥所属商户一致。 |
X-Vcc-Timestamp | 除健康检查外必填 | Unix 时间戳,单位为秒(UTC);与网关时间偏差须在允许窗口内。 |
X-Vcc-Signature | 除健康检查外必填 | 第 2 节所述 HMAC-SHA256 签名(小写十六进制)。 |
Idempotency-Key | 写操作必填 | 对 POST、PUT、PATCH、DELETE 必须携带;建议使用 UUID。 |
Content-Type | 有 body 时 | 使用 application/json。 |
X-Trace-Id | 可选 | 您侧生成的追踪 ID,便于排障。 |
HTTP 状态码一般为 200。响应 JSON 外层结构如下:
{
"code": "0",
"message": "OK",
"data": { ... 业务负载,随接口变化 ... },
"traceId": "字符串,用于与服务方对账排障"
}
data 可为 JSON 对象、数组,或含 items 的分页对象。
HTTP 状态码可为 4xx / 5xx。code 为业务错误码字符串,message 为人类可读说明,data 多为 null,traceId 建议落库以便工单。
| HTTP | code(示例) | 典型含义 |
|---|---|---|
| 400 | VALIDATION_ERROR 等 | 参数缺失、格式非法、业务校验失败。 |
| 401 | OPEN_API_CREDENTIAL_INVALID | 访问密钥无效或已停用。 |
| 401 | OPEN_API_SIGNATURE_INVALID | 签名不匹配或缺少签名相关头。 |
| 401 | OPEN_API_CLOCK_SKEW | 时间戳超出允许偏差。 |
| 401 | 40113 / OPEN_API_PATH_NOT_ALLOWED | 路径未对当前版本开放。 |
| 403 | FORBIDDEN | 商户编码与密钥不匹配、资源归属不符等。 |
| 404 | 如 CARD_NOT_FOUND、WALLET_NOT_FOUND | 资源不存在或无权限访问。 |
| 409 | 如 OPEN_API_IDEMPOTENCY_CONFLICT | 相同幂等键与已处理请求体不一致。 |
| 502 | 如 CHANNEL_TRANSACTION_FETCH_FAILED | 渠道不可用(如实时拉取约束失败且无本地快照)。 |
| 429 / 503 | 视配置 | 限流或依赖不可用。 |
所有写操作(POST / PUT / PATCH / DELETE)必须携带 Idempotency-Key。相同商户 + 相同键重复提交应返回首次成功语义。开卡任务当前按 Key 重放,不校验 body 哈希。网关按商户维度限流;PATCH .../spending-constraint 另有单独频率限制。
| 方法 | 路径 | 摘要 |
|---|---|---|
| GET | /open/v1/health | 健康检查(不要求签名头) |
| GET | /open/v1/card-products | 已发布卡产品列表(含消费约束上限) |
| GET | /open/v1/card-groups | 卡组列表(ACTIVE 自建;不含余额) |
| POST | /open/v1/card-groups | 创建自建卡组新 |
| DELETE | /open/v1/card-groups/{id} | 软删除卡组新 |
| POST | /open/v1/card-groups/{id}/fund | 主钱包 → 卡组子池新 |
| POST | /open/v1/card-groups/{id}/release | 卡组子池 → 主钱包新 |
| GET | /open/v1/wallets | 钱包列表(MAIN + 卡组子池) |
| GET | /open/v1/wallets/{id} | 单个钱包余额 |
| GET | /open/v1/wallet-ledger | 资金流水(分页) |
| POST | /open/v1/card-issuance-tasks | 创建开卡任务(可选归属卡组) |
| GET | /open/v1/card-issuance-tasks/{id} | 查询开卡任务 |
| GET | /open/v1/cards | 卡片列表(分页,可按卡组筛选) |
| GET | /open/v1/cards/{id} | 卡片详情(脱敏) |
| GET | /open/v1/cards/{id}/sensitive | 卡片敏感信息(完整 PAN/CVV) |
| GET | /open/v1/cards/{id}/spending-constraint | 读取当前消费约束新 |
| GET | /open/v1/cards/{id}/spending-utilization | 周期额度使用情况新 |
| PATCH | /open/v1/cards/{id}/card-group | 开卡后挂入/移出卡组新 |
| POST | /open/v1/cards/{id}/actions | 卡片生命周期动作 |
| PATCH | /open/v1/cards/{id}/spending-constraint | 卡消费控制部分更新 |
| GET | /open/v1/transactions | 交易分页查询 |
| GET | /open/v1/slash-catalog/merchant-categories | 商户类别(MCC)目录新 |
| GET | /open/v1/slash-catalog/merchants | 受理商户 ID 本地搜索新 |
| GET | /open/v1/spending-constraint/reference | 消费约束静态参考(MCC/国家短列表)新 |
| GET | /open/v1/mcc | 商户类别(别名,同 merchant-categories) |
| GET | /open/v1/merchant-fee-rules/effective | 查询当前生效手续费规则及试算 |
/open/v1/card-groups 上,GET 为查询列表、POST 为创建,请以 HTTP 方法区分。仍不在本清单内的能力:钱包充值/提现/手工上账、API 凭据管理、持卡人 CRUD、开卡 body 自定义 spendingConstraintJson。充值与手工上账请使用 member-portal;API 凭据请使用商户门户或运营交付流程。data)下列「响应 data」均指统一外壳内 data 字段的形状。
{MERCHANT_API_BASE}/open/v1/health不要求签名相关 HTTP 头。
data{ "status": "UP" }
{MERCHANT_API_BASE}/open/v1/card-productsdataJSON 数组,每项字段示例:
[
{
"id": 1001,
"code": "产品代码",
"name": "产品名称",
"currency": "USD",
"published": true,
"memberVisibility": "可见性枚举",
"spendingConstraintCeilingJson": "{ ... 约束上限 JSON 字符串 ... }",
"merchantCode": "M..."
}
]
spendingConstraintCeilingJson(可选):卡产品配置的消费约束上限;调用 PATCH .../spending-constraint 时不得突破。
{MERCHANT_API_BASE}/open/v1/card-groups| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 省略 = 商户主会员。 |
返回主会员名下 ACTIVE 自建卡组(memberOwned=true)。不含子池余额;余额见 §7.8 钱包列表(walletRole=CARD_GROUP)。
data[
{
"id": 9,
"code": "M32_abc...",
"name": "测试",
"memberOwned": true,
"status": "ACTIVE",
"merchantCode": "M..."
}
]
{MERCHANT_API_BASE}/open/v1/card-groups与 §7.3 共用路径,HTTP 方法为 POST。创建主会员名下新的自建卡组(服务端自动生成唯一 code)。
须带 Idempotency-Key。相同键重复提交返回同一卡组;同键不同 name 返回 409。
memberUserId:可选。
{ "name": "营销专用卡组" }
| 字段 | 必填 | 说明 |
|---|---|---|
name | 是 | 卡组名称,1–128 字符,trim 后非空。 |
data{
"id": 12,
"code": "M32_a1b2c3...",
"name": "营销专用卡组",
"status": "ACTIVE",
"memberOwned": true,
"merchantCode": "M..."
}
{MERCHANT_API_BASE}/open/v1/card-groups/{id}软删除(status=INACTIVE),列表接口不再返回。删除前须:① 无绑卡;② 全币种子池余额为 0(须先调用 §7.7 release 划回主钱包,不会自动清余额)。
须带 Idempotency-Key。
memberUserId:可选。
data{
"id": 12,
"status": "INACTIVE",
"merchantCode": "M..."
}
| 场景 | HTTP | 说明 |
|---|---|---|
| 仍有卡片 | 400 | 卡组内仍有卡片,请先移出(PATCH .../card-group) |
| 子池有余额 | 400 | 请先 POST .../release 划回主钱包 |
{MERCHANT_API_BASE}/open/v1/card-groups/{id}/fund须带 Idempotency-Key。
memberUserId:可选。
{
"currency": "USD",
"amount": "100.00"
}
| 字段 | 必填 | 说明 |
|---|---|---|
currency | 是 | 如 USD。 |
amount | 是 | 十进制金额字符串,须 > 0。 |
data{
"id": "8842",
"reference": "wo_8842",
"status": "SUCCEEDED",
"type": "CARD_GROUP_FUND",
"currency": "USD",
"amountMinor": 10000,
"amount": "100.00",
"merchantCode": "M..."
}
主钱包余额不足时返回 400 WALLET_INSUFFICIENT_FUNDS。划款后可通过 §7.8 钱包、§7.9 流水确认。
{MERCHANT_API_BASE}/open/v1/card-groups/{id}/release请求头、查询参数、请求体与 §7.6 相同。
data{
"id": "8843",
"reference": "wo_8843",
"status": "SUCCEEDED",
"type": "CARD_GROUP_RELEASE",
"currency": "USD",
"amountMinor": 5000,
"amount": "50.00",
"merchantCode": "M..."
}
子池余额不足时返回 400 WALLET_INSUFFICIENT_FUNDS。
{MERCHANT_API_BASE}/open/v1/wallets| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 指定则返回该用户钱包;省略则返回商户下全部(1 个 MAIN + N 个 CARD_GROUP 子池)。 |
data[
{
"walletId": "54",
"currency": "USD",
"availableAmountMinor": 12122144,
"availableAmount": "121221.44",
"cardGroupId": 0,
"walletRole": "MAIN",
"cardGroupName": null,
"merchantCode": "M..."
},
{
"walletId": "55",
"currency": "USD",
"availableAmountMinor": 100000,
"availableAmount": "1000.00",
"cardGroupId": 9,
"walletRole": "CARD_GROUP",
"cardGroupName": "测试",
"merchantCode": "M..."
}
]
{MERCHANT_API_BASE}/open/v1/wallets/{id}data与列表项相同结构(单对象)。
{MERCHANT_API_BASE}/open/v1/wallet-ledger| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 省略时使用商户主会员。 |
page / size | 否 | 默认 page=0、size=20。 |
sort | 否 | asc 或 desc,默认 desc。 |
data{
"total": 100,
"page": 0,
"size": 20,
"sort": "desc",
"merchantCode": "M...",
"items": [ { "id": "1", "amountMinor": -500, "walletAccountId": "55", "cardGroupId": "9", ... } ]
}
{MERCHANT_API_BASE}/open/v1/card-issuance-tasks须带 Idempotency-Key。
{
"cardProductId": 1001,
"memberUserId": 2002,
"platformCardGroupId": 9
}
| 字段 | 必填 | 说明 |
|---|---|---|
cardProductId | 是 | 已发布卡产品 ID。 |
memberUserId | 否 | 省略 = 商户主会员。 |
platformCardGroupId | 否 | 开卡后归属卡组;省略/null = 主钱包扣款。须为本人 ACTIVE 自建卡组;开卡前请通过 §7.6 向子池划入足够余额。 |
不接受任意 spendingConstraintJson 或其他未列字段。
data{
"id": "任务ID",
"merchantCode": "M...",
"status": "SUCCEEDED",
"channelRequestId": "...",
"appliedFeeBreakdown": { "feeType": "CARD_ISSUANCE", "feeMinor": "...", "summary": "..." }
}
{MERCHANT_API_BASE}/open/v1/card-issuance-tasks/{id}data{ "id": "...", "merchantCode": "M...", "status": "...", "channelRequestId": "..." }
{MERCHANT_API_BASE}/open/v1/cards| 参数 | 必填 | 说明 |
|---|---|---|
page / size | 否 | 默认 0 / 20,size 最大 100。 |
cardGroupId | 否 | 不传 = 全部;0 = 未归属主钱包;>0 = 指定卡组。 |
data分页对象(非数组):
{
"total": 42,
"page": 0,
"size": 20,
"merchantCode": "M...",
"items": [
{
"id": "卡账户ID",
"displayName": null,
"panMasked": "******4242",
"panLast4": "4242",
"status": "ACTIVE",
"cardProductId": 1001,
"platformCardGroupId": 9,
"cardProductName": "产品名",
"channelDisplayName": "VCC",
"channelCode": "VCC",
"cardGroupName": "测试",
"cardGroupCode": "M32_...",
"cardGroupMemberOwned": true,
"cardGroupLabel": "我的卡组 · 测试",
"merchantCode": "M..."
}
]
}
{MERCHANT_API_BASE}/open/v1/cards/{id}data与 §7.13 列表项结构一致的单对象;不包含完整 PAN/CVV。
{MERCHANT_API_BASE}/open/v1/cards/{id}/sensitive| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 若传入须与卡片归属用户一致,否则 403。 |
data{
"cardId": "卡ID",
"channelCode": "VCC",
"pan": "完整卡号",
"cvv": "CVV",
"expMonth": 12,
"expYear": 2029,
"merchantCode": "M..."
}
{MERCHANT_API_BASE}/open/v1/cards/{id}/spending-constraintmemberUserId:可选。
适用于平台已开通消费约束能力的卡片。
data{
"source": "LIVE",
"spendingConstraint": { },
"merchantCode": "M..."
}
source=LIVE 为实时拉取;渠道不可用时可能为 CACHED,并含 nonRealtime、cachedAt、channelError。
{MERCHANT_API_BASE}/open/v1/cards/{id}/spending-utilizationmemberUserId:可选。
data{
"cardAccountId": 25,
"supported": true,
"spentCents": 1200,
"limitCents": 50000,
"currency": "USD",
"preset": "monthly",
"periodKey": "2026-06",
"utilizationPercent": 2,
"source": "RECONCILE",
"nonRealtime": true,
"merchantCode": "M..."
}
{MERCHANT_API_BASE}/open/v1/cards/{id}/card-group须带 Idempotency-Key。
memberUserId:可选。
{ "platformCardGroupId": 9 }
移出卡组(改回主钱包扣款):{ "platformCardGroupId": null }
仅可绑本人 ACTIVE 自建卡组;已开卡后不可改绑运营「平台卡组」。
data与 GET /cards/{id} 相同(含更新后的 cardGroupLabel 等)。
{MERCHANT_API_BASE}/open/v1/cards/{id}/actions须带 Idempotency-Key。
{
"action": "ACTIVATE | FREEZE | UNFREEZE | CLOSE",
"memberUserId": 2002
}
memberUserId 可选。
data{ "id": "卡ID", "merchantCode": "M...", "action": "FREEZE", "status": "FROZEN" }
{MERCHANT_API_BASE}/open/v1/cards/{id}/spending-constraint须带 Idempotency-Key。
memberUserId:可选。
与平台 spending-constraint 规范对齐;配置前建议调用 §7.24、§7.22、§7.23。不得突破卡产品 spendingConstraintCeilingJson。
data更新后的消费控制 JSON。
{MERCHANT_API_BASE}/open/v1/transactions| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 省略时使用商户主会员。 |
cardId / txnType / from / to | 否 | 筛选条件。 |
page / size | 否 | 默认 page=0、size=20。 |
data{
"total": 100,
"page": 0,
"pageSize": 20,
"merchantCode": "M...",
"items": [ { "id": "...", "amountMinor": -1200, "merchantCode": "M..." } ]
}
{MERCHANT_API_BASE}/open/v1/slash-catalog/merchant-categoriescursor:可选,游标分页。
data商户类别 JSON(含 items、下一页 cursor 等)。
兼容别名(建议迁移至本路径):GET /open/v1/mcc,行为相同。
{MERCHANT_API_BASE}/open/v1/slash-catalog/merchants| 参数 | 必填 | 说明 |
|---|---|---|
q | 否 | 名称/ID 关键字。 |
page / size | 否 | 默认 0 / 20,最大 100。 |
ensureIds | 否 | 逗号分隔受理商户 ID,保证回显已选项。 |
data{
"total": 1200,
"source": "LOCAL_DB",
"lastSyncAt": "2026-06-18T10:00:00Z",
"items": [
{ "id": "ext_merchant_xxx", "name": "AMAZON", "label": "AMAZON (ext_merchant_xxx)" }
],
"merchantCode": "M..."
}
{MERCHANT_API_BASE}/open/v1/spending-constraint/reference组装 PATCH .../spending-constraint 前的 MCC/国家短列表、周期枚举等静态参考。
data{
"restrictions": ["allowlist", "blacklist"],
"utilizationPresets": ["daily", "weekly", "monthly", "yearly", "collective"],
"mccShortlist": [{ "code": "5411", "label": "5411 — 超市 / Grocery" }],
"countryShortlist": [{ "code": "US", "label": "US — 美国" }],
"fieldNotes": {
"merchantCategoryRule": "商户类别 ID,见 merchant-categories",
"merchantRule": "受理商户 ID,见 merchants 搜索"
},
"merchantCode": "M..."
}
{MERCHANT_API_BASE}/open/v1/merchant-fee-rules/effective| 参数 | 必填 | 说明 |
|---|---|---|
feeType | 是 | CARD_ISSUANCE / TRANSACTION / REFUND / MANUAL_TOP_UP / WITHDRAWAL |
baseAmountMinor | 否 | 计费基数(最小货币单位整数)。 |
data{
"feeType": "TRANSACTION",
"source": "MERCHANT",
"quotedFeeMinor": "30",
"summary": "人类可读规则摘要",
"merchantCode": "M..."
}
本节为 VCC → 商户 方向的 HTTP 回调,与 §2 Open API 入站签名 不是同一套算法,请勿使用 API SecretKey 验签 Webhook,亦勿用 Webhook Secret 调用 Open API。
| 对比项 | Open API(商户 → VCC) | Webhook(VCC → 商户) |
|---|---|---|
| 方向 | 商户请求 /open/v1/** | 平台 POST 至商户配置的回调 URL |
| 密钥 | API SecretKey | Webhook Secret(门户单独配置) |
| 签名输入 | Canonical Request(4 行) | timestamp + nonce + body 字符串拼接 |
X-Vcc-Timestamp | Unix 秒 | Unix 毫秒 |
| 配置 | member-portal → Open API 凭证 | member-portal → Webhook 设置 |
配置接口位于 /api/v1/member/webhook-config(会员 JWT),不在 /open/v1 白名单。
| 项 | 说明 |
|---|---|
| 方法 | POST |
| Content-Type | application/json; charset=utf-8 |
| 成功响应 | HTTP 2xx(建议 200);非 2xx 可能触发重试 |
| 超时 | 平台侧连接/读超时约 5s / 10s(以部署为准) |
| HTTP 头 | 说明 |
|---|---|
X-Vcc-Signature | HMAC-SHA256 签名,小写十六进制 |
X-Vcc-Timestamp | 签名时 Unix 时间戳,单位 毫秒(UTC) |
X-Vcc-Nonce | 16 字节随机数的小写 hex(32 字符) |
X-Vcc-Event-Id | 事件唯一 ID,须与 JSON 内 eventId 一致 |
body(勿重新序列化 JSON)。X-Vcc-Timestamp、X-Vcc-Nonce、X-Vcc-Signature。payload = String(timestamp) + nonce + bodyexpected = hex_lower( HMAC_SHA256( UTF8(WebhookSecret), UTF8(payload) ) )expected 与 X-Vcc-Signature。payload = timestamp + nonce + body
Signature = 小写十六进制( HMAC_SHA256( UTF8(WebhookSecret), UTF8(payload) ) )
X-Vcc-Timestamp 与当前时间偏差(如 ±5 分钟);短期缓存 X-Vcc-Nonce 防重放。| eventType | 说明 |
|---|---|
merchant.webhook.transaction.updated | 平台交易状态变更(清算/入账等;不含实时授权) |
merchant.webhook.wallet.card_binding.verification_code | 钱包绑卡验证码就绪 |
merchant.webhook.test | 门户「测试投递」(联调) |
同一 eventId 成功投递后平台不重复推送;商户侧仍建议以 eventId 做业务去重。
外层 JSON 结构:
{
"eventType": "merchant.webhook.transaction.updated",
"eventId": "mwh_evt_txn_abc123",
"occurredAt": "2026-06-23T09:15:57.424Z",
"merchantCode": "M20260618123735000001",
"data": { }
}
data 示例{
"transactionId": "txn_abc123",
"cardAccountId": 10001,
"txnType": "CHANNEL",
"status": "SETTLED",
"amountMinor": 100,
"currency": "USD",
"occurredAt": "2026-06-23T09:15:57.424Z",
"merchantReference": "your-ref-001",
"channelReference": "abc123"
}
data 字段| 字段 | 类型 | 说明 |
|---|---|---|
transactionId | string | 平台侧交易 ID,格式 txn_{channelReference}。 |
cardAccountId | number | null | 卡账户 ID;未关联到卡时可能为 null。 |
txnType | string | 交易类型(大写),见下表「txnType 码值」。 |
status | string | 交易状态(大写),见下表「status 码值」。 |
amountMinor | number | 金额,最小货币单位(如 USD 为分);符号与平台记账一致。 |
currency | string | 币种,如 USD。 |
occurredAt | string | 业务发生时间,ISO-8601 UTC。 |
merchantReference | string | null | 商户侧单号/引用(若有)。 |
channelReference | string | 平台侧渠道交易引用;与 GET /open/v1/transactions 及幂等去重关联。 |
平台统一转为大写;缺省为 CHANNEL。本事件在交易清算/入账等后续阶段推送,不含实时授权。
| 码值 | 含义 | Webhook 说明 |
|---|---|---|
CHANNEL | 渠道同步交易 | 最常见;平台同步渠道交易后的默认类型。 |
AUTHORIZATION | 授权 | 实时授权阶段不会推送本事件。 |
CAPTURE | 请款 / 捕获 | 可能出现。 |
SALE / PURCHASE | 消费 | 可能出现。 |
SETTLEMENT | 清算 | 可能出现。 |
REFUND | 退款 | 可能出现。 |
VOID | 撤销 | 可能出现。 |
REVERSAL | 冲正 | 可能出现。 |
FEE | 费用 | 可能出现。 |
其他大写字符串 | 扩展类型 | 未在上表列出时按平台原值大写推送;请兼容未知值。 |
平台将上游状态映射为大写枚举;未命中下表时按原值大写推送。本事件不推送 AUTHORIZED(实时授权)。
| 码值 | 含义 | 说明 |
|---|---|---|
SETTLED | 已清算 / 已入账 | 交易已完成清算或入账 |
PENDING | 处理中 | 尚未终态 |
DECLINED | 已拒绝 | 交易被拒绝 |
FAILED | 失败 | 处理失败 |
CANCELLED | 已取消 | 交易已取消 |
RETURNED | 已退回 | 资金或交易已退回 |
REFUNDED | 已退款 | 已完成退款 |
REVERSED | 已冲正 | 交易已冲正 |
AUTHORIZED | 已授权 | 仅内部授权阶段;不会出现在本 Webhook 事件中 |
其他大写字符串 | 扩展状态 | 未映射时原样大写;请兼容未知值 |
data 中不含 memberUserId;请用 cardAccountId / channelReference 关联业务。实时授权阶段不会触发本事件。同一 channelReference 状态变更会复用稳定 eventId,成功投递后不会重复推送。data 示例{
"memberUserId": 32,
"inboundId": 1782206824529,
"walletId": 10,
"cardAccountId": 10001,
"bindingCode": "123456",
"expiresAt": "2026-06-23T09:22:31.285Z",
"channelCode": "VCC"
}
data.bindingCode 为完整验证码;门户投递日志 API 可能对验证码脱敏展示。| 商户响应 | 平台行为 |
|---|---|
| HTTP 2xx | 标记成功,不再重试 |
| HTTP 5xx 或连接/超时 | 指数退避自动重试(默认最多 3 次) |
| HTTP 4xx | 一般不再自动重试 |
请快速返回 2xx 并异步处理业务,避免长时间阻塞导致超时重试。
import hmac, hashlib
def verify_vcc_webhook(body_utf8: str, secret: str, headers: dict) -> bool:
ts = headers.get("X-Vcc-Timestamp") or headers.get("x-vcc-timestamp")
nonce = headers.get("X-Vcc-Nonce") or headers.get("x-vcc-nonce")
sig = (headers.get("X-Vcc-Signature") or headers.get("x-vcc-signature") or "").lower()
if not ts or not nonce or not sig:
return False
payload = f"{ts}{nonce}{body_utf8 or ''}"
expected = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
import crypto from 'crypto';
function verifyVccWebhook(bodyUtf8, secret, headers) {
const ts = headers['x-vcc-timestamp'];
const nonce = headers['x-vcc-nonce'];
const sig = (headers['x-vcc-signature'] || '').toLowerCase();
if (!ts || !nonce || !sig) return false;
const payload = `${ts}${nonce}${bodyUtf8 ?? ''}`;
const expected = crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(sig, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String payload = timestamp + nonce + (bodyUtf8 == null ? "" : bodyUtf8);
byte[] expected = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
// 与 HexFormat 解析的 X-Vcc-Signature 做 MessageDigest.isEqual 比较
参考实现:仓库 open-api-client-demo/src/webhook-signature.mjs;本地全链路测试:npm run test:webhook。
POST /card-groups — 创建卡组(或 GET /card-groups 选用已有)POST /card-groups/{id}/fund — 从主钱包划入子池GET /wallets — 确认 MAIN 与 CARD_GROUP 子池余额GET /card-products — 确认 cardProductId、spendingConstraintCeilingJsonPOST /card-issuance-tasks — { "cardProductId", "platformCardGroupId" }GET /cards/{id} — 确认 cardGroupLabelPATCH /cards/{id}/card-group — 移出卡组内全部卡片(platformCardGroupId: null)POST /card-groups/{id}/release — 划回全部子池余额DELETE /card-groups/{id} — 软删除GET /spending-constraint/referenceGET /slash-catalog/merchant-categoriesGET /slash-catalog/merchants?q=...GET /cards/{id}/spending-constraintPATCH /cards/{id}/spending-constraintGET /cards/{id}/spending-utilizationGET /card-groupsPATCH /cards/{id}/card-group — { "platformCardGroupId": 9 } 或 null 移出GET /cards/{id} — 确认展示字段