发卡 Open API 接口文档
面向发卡商户。能力覆盖卡产品、卡组与划款、钱包只读、开卡、卡片生命周期与消费约束、交易查询;数币充值到账通过发卡 Webhook 通知,Open API 不提供充值写接口。
接入前准备
- 联系运营开通商户 Open API,获取 ISSUING 凭证:
AccessKeyId+SecretKey+MerchantCode。 - 实现请求签名(§2)与写操作幂等 Key(§5)。
- 如需交易/绑卡/数币到账通知:在 member-portal Webhook 设置配置 HTTPS 回调 URL、订阅事件,并保存 Webhook Secret(仅创建/轮换时展示一次)。
- 数币充值地址在会员门户「数币充值」生成;到账后等待 Webhook
merchant.webhook.wallet.deposit.posted,并用钱包只读接口核对。
1. 基础约定与接入地址
| 项 | 说明 |
|---|---|
| 协议 | 生产环境使用 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 主账号一致)。 |
2. 鉴权与请求签名
除「健康检查」接口外,所有 /open/v1/... 请求必须在 HTTP 头中携带访问凭据与签名。签名算法为 HMAC-SHA256,输出为小写十六进制字符串。
2.1 待签名字符串(Canonical Request)
将以下 4 行按顺序用换行符 \n(ASCII 0x0A)连接,得到 UTF-8 字节序列后作为 HMAC 的输入消息:
- HTTP 方法:大写,如
GET、POST。 - 路径:与网关可见的 Servlet 路径一致,例如
/open/v1/cards(不含域名与查询串)。 - 查询串:对查询参数名按字典序排序;同名参数多值按值排序;键值对形如
key=value,以&连接;键与值均需 URL 编码(空格为%20)。若无查询参数,本行为空字符串(不要写?)。 - 请求体哈希:对请求体原始字节做 SHA-256,输出小写十六进制。无 body 时,对空字节序列做 SHA-256(注意与
{}的字节不同)。
HMAC输入 = UTF8( method + "\n" + path + "\n" + canonicalQuery + "\n" + bodySha256Hex )
Signature = 小写十六进制( HMAC_SHA256( UTF8(SecretKey), HMAC输入 ) )
SecretKey 为服务方在创建或轮换 API 凭据时向您下发的私钥;轮换宽限期内新旧密钥可能均可验签,以交付说明为准。
2.2 未开放路径
若请求的 URL 不在当前版本开放清单内,即使签名正确,网关也可能返回 HTTP 401 及业务错误码 40113(OPEN_API_PATH_NOT_ALLOWED),表示该路径不对商户开放。
2.3 凭证范围
| 项 | 说明 |
|---|---|
| 允许的凭证 | credential_type = ISSUING |
| 可调用路径 | 本文 /open/v1/** 发卡接口,以及 GET /open/v1/health |
| 不可调用 | /open/v1/acquiring/**(须使用 ACQUIRING 凭证,见收单文档) |
| 错误 | 凭证类型不匹配 → HTTP 403,业务码 40314(OPEN_API_CREDENTIAL_SCOPE_DENIED) |
3. 公共请求头
| 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,便于排障。 |
4. 统一响应体(成功 / 失败)
4.1 成功
HTTP 状态码一般为 200。响应 JSON 外层结构如下:
{
"code": "0",
"message": "OK",
"data": { ... 业务负载,随接口变化 ... },
"traceId": "字符串,用于与服务方对账排障"
}
data 可为 JSON 对象、数组,或含 items 的分页对象。
4.2 失败
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 | 商户编码与密钥不匹配、资源归属不符等。 |
| 403 | 40314 / OPEN_API_CREDENTIAL_SCOPE_DENIED | 凭证类型与路径不匹配(发卡须 ISSUING)。 |
| 404 | 如 CARD_NOT_FOUND、WALLET_NOT_FOUND | 资源不存在或无权限访问。 |
| 409 | 如 OPEN_API_IDEMPOTENCY_CONFLICT | 相同幂等键与已处理请求体不一致。 |
| 502 | 如 CHANNEL_TRANSACTION_FETCH_FAILED | 渠道不可用(如实时拉取约束失败且无本地快照)。 |
| 429 / 503 | 视配置 | 限流或依赖不可用。 |
5. 幂等与写操作
所有写操作(POST / PUT / PATCH / DELETE)必须携带 Idempotency-Key。相同商户 + 相同键重复提交应返回首次成功语义。开卡任务当前按 Key 重放,不校验 body 哈希。网关按商户维度限流;PATCH .../spending-constraint 另有单独频率限制。
6. 接口一览
| 方法 | 路径 | 摘要 |
|---|---|---|
| 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 为创建。
不在本清单:钱包充值/提现/手工上账写接口、数币充值地址、充值单查询、API 凭据管理、持卡人 CRUD、开卡 body 自定义 spendingConstraintJson。
数币到账通过发卡 Webhook merchant.webhook.wallet.deposit.posted 通知(§8);地址在会员门户生成。人工充值请走门户申请;API 凭据走商户门户或运营交付。7. 接口明细(路径、请求、响应 data)
下列「响应 data」均指统一外壳内 data 字段的形状。各接口含请求参数/请求体字段与响应字段表(名称、类型、含义)。写操作(POST/PATCH/DELETE)均须 Idempotency-Key 与签名头;GET 无请求体,但仍须签名头(健康检查除外)。
7.1 健康检查
签名
不要求签名相关 HTTP 头。
请求示例
GET {MERCHANT_API_BASE}/open/v1/health
(无请求头签名;无请求体)
响应 data
{ "status": "UP" }
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
status | string | 服务状态;正常为 UP。 |
7.2 已发布卡产品列表
请求示例
GET {MERCHANT_API_BASE}/open/v1/card-products
Headers: X-Access-Key-Id / X-Vcc-Timestamp / X-Vcc-Signature(见 §2)
(无请求体)
响应 data
JSON 数组,每项字段示例:
[
{
"id": 1001,
"code": "产品代码",
"name": "产品名称",
"currency": "USD",
"published": true,
"memberVisibility": "可见性枚举",
"spendingConstraintCeilingJson": "{ ... 约束上限 JSON 字符串 ... }",
"merchantCode": "M..."
}
]
响应字段(数组元素)
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 卡产品 ID,开卡时传入 cardProductId。 |
code | string | 产品编码。 |
name | string | 产品名称。 |
currency | string | 记账币种,如 USD。 |
published | boolean | 是否已发布(可开卡)。 |
memberVisibility | string | 会员可见性策略枚举。 |
spendingConstraintCeilingJson | string? | 可选。消费约束上限(JSON 字符串);PATCH 调额不得超过此上限。 |
merchantCode | string | 当前商户编码,与请求头一致。 |
7.3 卡组列表
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 省略 = 商户主会员。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/card-groups?memberUserId=2002
Headers: 签名头(§2)
(无请求体)
返回主会员名下 ACTIVE 自建卡组(memberOwned=true)。不含子池余额;余额见 §7.8 钱包列表(walletRole=CARD_GROUP)。
响应 data
[
{
"id": 9,
"code": "M32_abc...",
"name": "测试",
"memberOwned": true,
"status": "ACTIVE",
"merchantCode": "M..."
}
]
响应字段(数组元素)
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 卡组 ID(划款、开卡归属、挂卡组时使用)。 |
code | string | 卡组编码(服务端生成)。 |
name | string | 卡组名称。 |
memberOwned | boolean | 是否会员自建;本接口恒为 true。 |
status | string | 如 ACTIVE;列表仅返回可用组。 |
merchantCode | string | 商户编码。 |
本接口不含余额;子池余额见 §7.8(walletRole=CARD_GROUP)。
7.4 创建卡组
与 §7.3 共用路径,HTTP 方法为 POST。创建主会员名下新的自建卡组(服务端自动生成唯一 code)。
请求头
须带 Idempotency-Key。相同键重复提交返回同一卡组;同键不同 name 返回 409。
查询参数
memberUserId:可选。
请求示例
POST {MERCHANT_API_BASE}/open/v1/card-groups?memberUserId=2002
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
X-Access-Key-Id / X-Vcc-Timestamp / X-Vcc-Signature
Content-Type: application/json
{ "name": "营销专用卡组" }
请求体
{ "name": "营销专用卡组" }
| 字段 | 必填 | 说明 |
|---|---|---|
name | 是 | 卡组名称,1–128 字符,trim 后非空。 |
响应 data
{
"id": 12,
"code": "M32_a1b2c3...",
"name": "营销专用卡组",
"status": "ACTIVE",
"memberOwned": true,
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 新建卡组 ID。 |
code | string | 新建卡组编码。 |
name | string | 名称(与请求一致)。 |
status | string | 一般为 ACTIVE。 |
memberOwned | boolean | true。 |
merchantCode | string | 商户编码。 |
7.5 删除卡组
软删除(status=INACTIVE),列表接口不再返回。删除前须:① 无绑卡;② 全币种子池余额为 0(须先调用 §7.7 release 划回主钱包,不会自动清余额)。
请求头
须带 Idempotency-Key。
查询参数
memberUserId:可选。
请求示例
DELETE {MERCHANT_API_BASE}/open/v1/card-groups/12?memberUserId=2002
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440001
X-Access-Key-Id / X-Vcc-Timestamp / X-Vcc-Signature
(无请求体;签名时 body 哈希按空字节计算)
请求体
无。请勿发送 {}(空 JSON 与空 body 的 SHA-256 不同,会导致验签失败)。
响应 data
{
"id": 12,
"status": "INACTIVE",
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 被删除的卡组 ID。 |
status | string | 固定 INACTIVE(软删除)。 |
merchantCode | string | 商户编码。 |
| 场景 | HTTP | 说明 |
|---|---|---|
| 仍有卡片 | 400 | 卡组内仍有卡片,请先移出(PATCH .../card-group) |
| 子池有余额 | 400 | 请先 POST .../release 划回主钱包 |
7.6 划入子池(主钱包 → 卡组)
请求头
须带 Idempotency-Key。
查询参数
memberUserId:可选。
请求示例
POST {MERCHANT_API_BASE}/open/v1/card-groups/9/fund?memberUserId=2002
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440002
Content-Type: application/json
签名头(§2)
{
"currency": "USD",
"amount": "100.00"
}
请求体
{
"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..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 钱包操作 ID。 |
reference | string | 业务引用,形如 wo_{id}。 |
status | string | 如 SUCCEEDED / FAILED / PROCESSING。 |
type | string | 固定 CARD_GROUP_FUND。 |
currency | string | 币种。 |
amountMinor | number | 金额(最小货币单位,如美分)。 |
amount | string | 金额(两位小数)。 |
failureReason | string? | 失败时原因。 |
merchantCode | string | 商户编码。 |
主钱包余额不足时返回 400 WALLET_INSUFFICIENT_FUNDS。划款后可通过 §7.8 钱包、§7.10 流水确认。
7.7 划出子池(卡组 → 主钱包)
请求头
须带 Idempotency-Key。
查询参数
memberUserId:可选。
请求示例
POST {MERCHANT_API_BASE}/open/v1/card-groups/9/release?memberUserId=2002
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440003
Content-Type: application/json
签名头(§2)
{
"currency": "USD",
"amount": "50.00"
}
请求体
{
"currency": "USD",
"amount": "50.00"
}
| 字段 | 必填 | 说明 |
|---|---|---|
currency | 是 | 如 USD。 |
amount | 是 | 十进制金额字符串,须 > 0。 |
响应 data
{
"id": "8843",
"reference": "wo_8843",
"status": "SUCCEEDED",
"type": "CARD_GROUP_RELEASE",
"currency": "USD",
"amountMinor": 5000,
"amount": "50.00",
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 钱包操作 ID。 |
reference | string | 业务引用,形如 wo_{id}。 |
status | string | 如 SUCCEEDED / FAILED / PROCESSING。 |
type | string | 固定 CARD_GROUP_RELEASE。 |
currency | string | 币种。 |
amountMinor | number | 金额(最小货币单位)。 |
amount | string | 金额(两位小数)。 |
failureReason | string? | 失败时原因。 |
merchantCode | string | 商户编码。 |
子池余额不足时返回 400 WALLET_INSUFFICIENT_FUNDS。
7.8 钱包列表
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 指定则返回该用户钱包;省略则返回商户下全部(1 个 MAIN + N 个 CARD_GROUP 子池)。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/wallets?memberUserId=2002
Headers: 签名头(§2)
(无请求体)
响应 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..."
}
]
响应字段(数组元素)
| 字段 | 类型 | 说明 |
|---|---|---|
walletId | string | 钱包账户 ID(§7.9 路径参数)。 |
currency | string | 币种,如 USD。 |
availableAmountMinor | number | 可用余额(最小货币单位)。 |
availableAmount | string | 可用余额(两位小数)。 |
cardGroupId | number | 0 = 主钱包;>0 = 对应卡组子池 ID。 |
walletRole | string | MAIN 或 CARD_GROUP。 |
cardGroupName | string? | 卡组名称;主钱包为 null。 |
merchantCode | string | 商户编码。 |
7.9 单个钱包
请求示例
GET {MERCHANT_API_BASE}/open/v1/wallets/54
Headers: 签名头(§2)
(无请求体)
响应 data
{
"walletId": "54",
"currency": "USD",
"availableAmountMinor": 12122144,
"availableAmount": "121221.44",
"cardGroupId": 0,
"walletRole": "MAIN",
"cardGroupName": null,
"merchantCode": "M..."
}
响应字段
与 §7.8 列表元素相同。
7.10 资金流水(分页)
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 省略时使用商户主会员。 |
page / size | 否 | 默认 page=0、size=20。 |
sort | 否 | asc 或 desc,默认 desc。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/wallet-ledger?memberUserId=2002&page=0&size=20&sort=desc
Headers: 签名头(§2)
(无请求体)
响应 data
{
"total": 100,
"page": 0,
"size": 20,
"sort": "desc",
"merchantCode": "M...",
"items": [ { "id": "1", "amountMinor": -500, "walletAccountId": "55", "cardGroupId": "9", ... } ]
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
total | number | 总条数。 |
page | number | 当前页(从 0 起)。 |
size | number | 本页实际条数(非请求 size)。 |
sort | string | asc / desc。 |
merchantCode | string | 商户编码。 |
items | array | 流水明细,见下表。 |
响应字段(items[])
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 流水行 ID。 |
occurredAt | string | 发生时间(ISO-8601)。 |
amountMinor | number | 带符号金额(最小单位);正=入账,负=出账。 |
amount | string | 绝对值金额(两位小数)。 |
direction | string | CREDIT(入)或 DEBIT(出)。 |
balanceAfterMinor | number | 变动后余额(最小单位)。 |
balanceAfter | string | 变动后余额(两位小数)。 |
entryType | string | 分录类型(如划款、授权扣款、冲正释放等)。 |
referenceType | string? | 关联业务类型。 |
referenceId | string? | 关联业务 ID。 |
businessRef | string? | 业务引用号。 |
walletAccountId | string | 所属钱包账户 ID。 |
cardGroupId | string | 卡组 ID;主钱包为 "0"。 |
7.11 创建开卡任务
请求头
须带 Idempotency-Key。
请求示例
POST {MERCHANT_API_BASE}/open/v1/card-issuance-tasks
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440010
Content-Type: application/json
签名头(§2)
{
"cardProductId": 1001,
"memberUserId": 2002,
"platformCardGroupId": 9
}
请求体(仅允许下列字段)
{
"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": "..." }
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 开卡任务 ID(用于 §7.12 轮询)。 |
merchantCode | string | 商户编码。 |
status | string | 任务状态,如 PENDING / SUCCEEDED / FAILED。 |
channelRequestId | string? | 渠道侧请求 ID。 |
appliedFeeBreakdown | object | 开卡手续费试算/落地摘要,见下表。 |
响应字段(appliedFeeBreakdown)
| 字段 | 类型 | 说明 |
|---|---|---|
feeType | string | 固定 CARD_ISSUANCE。 |
source | string | 规则来源:MERCHANT / DEFAULT。 |
ruleId | string | 规则 ID。 |
ruleTable | string | 规则表名。 |
currency | string | 计费币种。 |
calculationMode | string | 计费模式。 |
fixedFeeMinor | string | 固定费(最小单位,字符串)。 |
ratePercent | — | 费率(若有)。 |
feeMinor | string | 实际手续费(最小单位)。 |
summary | string | 人类可读摘要。 |
merchantCode | string | 商户编码。 |
7.12 查询开卡任务
请求示例
GET {MERCHANT_API_BASE}/open/v1/card-issuance-tasks/12345
Headers: 签名头(§2)
(无请求体)
响应 data
{ "id": "...", "merchantCode": "M...", "status": "...", "channelRequestId": "..." }
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 开卡任务 ID。 |
merchantCode | string | 商户编码。 |
status | string | 任务状态。 |
channelRequestId | string? | 渠道侧请求 ID。 |
本接口不含 appliedFeeBreakdown;手续费以创建时响应为准。
7.13 卡片列表(分页)
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
page / size | 否 | 默认 0 / 20,size 最大 100。 |
cardGroupId | 否 | 不传 = 全部;0 = 未归属主钱包;>0 = 指定卡组。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/cards?page=0&size=20&cardGroupId=9
Headers: 签名头(§2)
(无请求体)
响应 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..."
}
]
}
响应字段(分页外壳)
| 字段 | 类型 | 说明 |
|---|---|---|
total | number | 总卡数。 |
page | number | 当前页。 |
size | number | 页大小。 |
merchantCode | string | 商户编码。 |
items | array | 卡片列表,见下表。 |
响应字段(items[] / 卡片详情)
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 卡账户 ID(后续卡相关接口路径参数)。 |
displayName | string? | 展示名。 |
panMasked | string? | 掩码卡号。 |
panLast4 | string? | 卡号后四位。 |
status | string | 卡状态,如 ACTIVE / FROZEN / CLOSED。 |
cardProductId | number | 卡产品 ID。 |
cardProductName | string? | 卡产品名称。 |
platformCardGroupId | number? | 所属卡组 ID;未归属为 null。 |
channelDisplayName | string | 渠道展示名。 |
channelCode | string? | 渠道编码,如 SLASH。 |
cardGroupName | string? | 卡组名称。 |
cardGroupCode | string? | 卡组编码。 |
cardGroupMemberOwned | boolean | 是否会员自建卡组。 |
cardGroupLabel | string | 人类可读卡组标签。 |
merchantCode | string | 商户编码。 |
7.14 卡片详情
请求示例
GET {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73
Headers: 签名头(§2)
(无请求体)
响应 data
{
"id": "c_2m379xtlg5p73",
"panMasked": "******0548",
"panLast4": "0548",
"status": "ACTIVE",
"cardProductId": 1001,
"platformCardGroupId": 9,
"cardProductName": "产品名",
"channelCode": "SLASH",
"cardGroupLabel": "我的卡组 · 测试",
"merchantCode": "M..."
}
与 §7.13 列表项结构一致;不包含完整 PAN/CVV。字段含义见 §7.13。
7.15 卡片敏感信息
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 若传入须与卡片归属用户一致,否则 403。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/sensitive?memberUserId=2002
Headers: 签名头(§2)
(无请求体)
响应 data
{
"cardId": "卡ID",
"channelCode": "VCC",
"pan": "完整卡号",
"cvv": "CVV",
"expMonth": 12,
"expYear": 2029,
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
cardId | string | 卡账户 ID。 |
channelCode | string | 渠道编码。 |
pan | string | 完整卡号(PCI 敏感)。 |
cvv | string | CVV(PCI 敏感)。 |
expMonth | number | 过期月(1–12)。 |
expYear | number | 过期年(四位)。 |
merchantCode | string | 商户编码。 |
7.16 读取卡消费约束
查询参数
memberUserId:可选。
适用于平台已开通消费约束能力的卡片。
请求示例
GET {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/spending-constraint?memberUserId=2002
Headers: 签名头(§2)
(无请求体)
响应 data
{
"source": "LIVE",
"spendingConstraint": {
"spendingRule": {
"utilizationLimit": {
"preset": "collective",
"timezone": "UTC",
"limitAmount": { "amountCents": 23500 }
}
}
},
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
source | string | LIVE(实时渠道)或 CACHED(缓存兜底)。 |
spendingConstraint | object? | 渠道消费约束子树(与 Slash 同构);可作为 §7.20 PATCH 参考。 |
channelError | string? | 仅 CACHED 时:渠道错误信息。 |
cachedAt | string? | 仅缓存时:缓存时间(ISO)。 |
nonRealtime | boolean? | 缓存时为 true。 |
merchantCode | string | 商户编码。 |
spendingConstraint 常见嵌套:spendingRule.utilizationLimit(周期额度)、spendingRule.transactionSizeLimit(单笔限额)、merchantCategoryCodeRule(MCC)等。
7.17 读取卡周期额度使用
查询参数
memberUserId:可选。
请求示例
GET {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/spending-utilization
Headers: 签名头(§2)
(无请求体)
响应 data
{
"cardAccountId": 25,
"supported": true,
"spentCents": 1200,
"limitCents": 50000,
"currency": "USD",
"preset": "monthly",
"periodKey": "2026-06",
"utilizationPercent": 2,
"source": "RECONCILE",
"nonRealtime": true,
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
cardAccountId | number | 卡账户数字 ID。 |
supported | boolean | 是否支持周期额度;非 SLASH 等可能为 false(仅返回基础字段)。 |
spentCents | number | 本周期已用额度(分)。 |
limitCents | number? | 本周期限额(分);无限制为 null。 |
scopeType | string? | 额度作用域类型。 |
currency | string? | 币种。 |
preset | string? | 周期:daily/weekly/monthly/yearly/collective。 |
timezone | string? | 时区,如 UTC。 |
periodKey | string? | 当前周期键,如 2026-06。 |
nextResetAt | string? | 下次重置时间(ISO)。 |
utilizationPercent | number? | 使用率 0–100。 |
source | string | 数据来源,如 CONSTRAINT / RECONCILE。 |
lastEventAt / lastReconciledAt | string? | 最近事件/对账时间。 |
nonRealtime | boolean | 通常为 true(非实时渠道直读)。 |
merchantCode | string | 商户编码。 |
7.18 开卡后挂入/移出卡组
请求头
须带 Idempotency-Key。
查询参数
memberUserId:可选。
请求示例(挂入)
PATCH {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/card-group?memberUserId=2002
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440020
Content-Type: application/json
签名头(§2)
{ "platformCardGroupId": 9 }
请求示例(移出)
PATCH {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/card-group
Headers: Idempotency-Key + 签名头
Content-Type: application/json
{ "platformCardGroupId": null }
请求体
{ "platformCardGroupId": 9 }
| 字段 | 必填 | 说明 |
|---|---|---|
platformCardGroupId | 是(键须存在) | 目标卡组 ID;null / ≤0 / 空 = 移出卡组(主钱包扣款)。 |
移出卡组(改回主钱包扣款):{ "platformCardGroupId": null }
仅可绑本人 ACTIVE 自建卡组;已开卡后不可改绑运营「平台卡组」。
响应 data
与 GET /cards/{id} 相同(含更新后的 cardGroupLabel 等);字段见 §7.13。
7.19 卡片生命周期动作
请求头
须带 Idempotency-Key。
请求示例
POST {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/actions
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440021
Content-Type: application/json
签名头(§2)
{
"action": "FREEZE",
"memberUserId": 2002
}
请求体
{
"action": "ACTIVATE | FREEZE | UNFREEZE | CLOSE",
"memberUserId": 2002
}
| 字段 | 必填 | 说明 |
|---|---|---|
action | 是 | ACTIVATE / FREEZE / UNFREEZE / CLOSE。 |
memberUserId | 否 | 传入则按该会员范围执行;省略为商户范围。 |
响应 data
{ "id": "卡ID", "merchantCode": "M...", "action": "FREEZE", "status": "FROZEN" }
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 卡账户 ID。 |
merchantCode | string | 商户编码。 |
action | string | 实际执行的动作。 |
status | string | 动作后卡状态。 |
7.20 卡消费控制部分更新
请求头
须带 Idempotency-Key(每次新请求使用新 Key)。
查询参数
memberUserId:可选。
请求体
部分更新,与 Slash PATCH /card/{id}/spending-constraint 同构。金额使用 limitAmount.amountCents(分);周期 preset 为 daily|weekly|monthly|yearly|collective。不得突破卡产品 spendingConstraintCeilingJson。配置前建议调用 §7.24、§7.22、§7.23。
请求示例(更新周期额度)
PATCH {MERCHANT_API_BASE}/open/v1/cards/c_2m379xtlg5p73/spending-constraint?memberUserId=2002
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440030
Content-Type: application/json
签名头(§2)
{
"spendingRule": {
"utilizationLimit": {
"preset": "collective",
"timezone": "UTC",
"limitAmount": {
"amountCents": 23500
}
}
}
}
请求示例(更新单笔限额)
{
"spendingRule": {
"transactionSizeLimit": {
"minimum": { "amountCents": 100 },
"maximum": { "amountCents": 50000 }
}
}
}
请求示例(MCC 限制)
{
"merchantCategoryCodeRule": {
"merchantCategoryCodes": ["5411", "5812"],
"restriction": "allowlist"
}
}
utilizationLimit 放在根级,或使用 limitCents/currency 字段——渠道会拒绝,平台返回 50021(调额渠道调用失败)。正确结构为 spendingRule.utilizationLimit.limitAmount.amountCents。请求字段(常用路径)
| 路径 | 类型 | 说明 |
|---|---|---|
spendingRule.utilizationLimit.preset | string | daily / weekly / monthly / yearly / collective。 |
spendingRule.utilizationLimit.timezone | string | 时区,建议 UTC。 |
spendingRule.utilizationLimit.limitAmount.amountCents | number | 周期额度(分)。 |
spendingRule.transactionSizeLimit.minimum.amountCents | number | 单笔最小(分)。 |
spendingRule.transactionSizeLimit.maximum.amountCents | number | 单笔最大(分)。 |
merchantCategoryCodeRule.merchantCategoryCodes | string[] | MCC 代码列表。 |
merchantCategoryCodeRule.restriction | string | allowlist / blacklist。 |
响应 data
渠道返回的消费约束 JSON(透传,结构与 Slash spending-constraint 视图一致;成功时通常含更新后的 spendingRule 等)。失败时平台返回业务错误码(如 50021),不以成功体表示失败。
7.21 交易分页查询
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
memberUserId | 否 | 省略时使用商户主会员。 |
cardId | 否 | 按卡账户 ID 筛选。 |
txnType | 否 | 交易类型筛选。 |
from / to | 否 | 发生时间范围(ISO-8601,带偏移)。 |
page / size | 否 | 默认 page=0、size=20;size 最大 200。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/transactions?memberUserId=2002&cardId=c_2m379xtlg5p73&page=0&size=20
Headers: 签名头(§2)
(无请求体)
响应 data
{
"total": 100,
"page": 0,
"pageSize": 20,
"itemCount": 20,
"merchantCode": "M...",
"items": [ { "id": "...", "amountMinor": -1200, "merchantCode": "M..." } ]
}
响应字段(分页外壳)
| 字段 | 类型 | 说明 |
|---|---|---|
total | number | 总条数。 |
page | number | 当前页。 |
pageSize | number | 有效页大小。 |
itemCount | number | 本页条数。 |
merchantCode | string | 商户编码。 |
items | array | 交易列表,见下表。 |
响应字段(items[])
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 交易记录 ID。 |
memberUserId | string | 会员用户 ID。 |
cardAccountId | string? | 卡账户 ID。 |
txnType | string | 交易类型。 |
status | string | 平台交易状态。 |
amountMinor | number | 带符号金额(最小单位)。 |
amount | string | 绝对值金额(两位小数)。 |
currency | string | 币种。 |
occurredAt | string | 发生时间(ISO)。 |
merchantReference | string? | 商户侧引用。 |
channelReference | string? | 渠道侧引用。 |
narrative | string? | 叙述/摘要。 |
merchantDescription | string? | 受理商户描述。 |
channelCode | string? | 渠道编码。 |
channelStatus | string? | 渠道状态。 |
channelDetailedStatus | string? | 渠道细状态。 |
feeSnapshotAvailable | boolean | 是否有落库手续费快照。 |
feeTotalMinorSnapshot 等 | — | 快照字段:总额、规则来源/ID/表、摘要等(有快照时出现)。 |
feeEstimateAvailable | boolean | 是否按当前规则给出预估手续费。 |
feeEstimateMinor / feeEstimate | number / string? | 预估手续费。 |
feeEstimateNote 等 | string? | 预估说明 / 与快照对比说明。 |
merchantCode | string | 商户编码(项级)。 |
无单笔交易详情接口;请用本分页 + 筛选获取。
7.22 商户类别(MCC)
查询参数
cursor:可选,游标分页。
请求示例
GET {MERCHANT_API_BASE}/open/v1/slash-catalog/merchant-categories
Headers: 签名头(§2)
(无请求体)
响应 data
{
"items": [
{ "id": "merchant_category_xxx", "name": "Grocery Stores", "code": "5411" }
],
"cursor": "next_page_token_or_null"
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
items | array | MCC 列表。 |
items[].id | string | 类别资源 ID(配置规则时可用)。 |
items[].name | string | 类别名称。 |
items[].code | string | MCC 四位码,如 5411。 |
cursor | string? | 下一页游标;无更多为 null。 |
兼容别名(建议迁移至本路径):GET /open/v1/mcc,行为相同。
7.23 受理商户搜索
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
q | 否 | 名称/ID 关键字。 |
page / size | 否 | 默认 0 / 20,最大 100。 |
ensureIds | 否 | 逗号分隔受理商户 ID,保证回显已选项。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/slash-catalog/merchants?q=AMAZON&page=0&size=20
Headers: 签名头(§2)
(无请求体)
响应 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..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
total | number | 匹配总数。 |
source | string | 数据来源,如 LOCAL_DB。 |
lastSyncAt | string? | 最近同步时间(ISO)。 |
items | array | 受理商户列表。 |
items[].id | string | 受理商户 ID(配置 merchantRule 时使用)。 |
items[].name | string | 名称。 |
items[].label | string | 展示标签。 |
merchantCode | string | 当前对接商户编码。 |
7.24 消费约束静态参考
组装 PATCH .../spending-constraint 前的 MCC/国家短列表、周期枚举等静态参考。
请求示例
GET {MERCHANT_API_BASE}/open/v1/spending-constraint/reference
Headers: 签名头(§2)
(无请求体)
响应 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..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
restrictions | string[] | 限制模式枚举:allowlist / blacklist。 |
utilizationPresets | string[] | 周期预设枚举。 |
mccShortlist | array | 常用 MCC:code + label。 |
countryShortlist | array | 常用国家:code + label。 |
fieldNotes | object | 字段填写提示(如类别 ID、受理商户 ID 来源)。 |
merchantCode | string | 商户编码。 |
7.25 查询生效手续费规则并试算
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
feeType | 是 | CARD_ISSUANCE / TRANSACTION / REFUND / MANUAL_TOP_UP / WITHDRAWAL |
baseAmountMinor | 否 | 计费基数(最小货币单位整数)。 |
请求示例
GET {MERCHANT_API_BASE}/open/v1/merchant-fee-rules/effective?feeType=TRANSACTION&baseAmountMinor=10000
Headers: 签名头(§2)
(无请求体)
响应 data
{
"feeType": "TRANSACTION",
"source": "MERCHANT",
"quotedFeeMinor": "30",
"summary": "人类可读规则摘要",
"merchantCode": "M..."
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
feeType | string | 与请求一致的费用类型。 |
source | string | MERCHANT(商户规则)或 DEFAULT(全局默认)。 |
ruleId | string | 生效规则 ID。 |
ruleTable | string | 规则表,如 fee_rule_merchant。 |
calculationMode | string | 计费模式。 |
currency | string | 计费币种。 |
fixedFeeMinor | string | 固定费(最小单位)。 |
minFeeMinor | string | 最低费。 |
ratePercent | — | 费率(若有)。 |
tierRef | — | 阶梯引用(若有)。 |
appliesTo | — | 适用范围(若有)。 |
baseAmountMinor | string | 试算基数(最小单位)。 |
foreignTransaction | boolean | 试算是否按跨境假设(默认 true)。 |
quotedFeeMinor | string | 试算出的手续费(最小单位)。 |
summary | string | 人类可读规则摘要。 |
merchantCode | string | 商户编码。 |
8. 发卡 Webhook 出站通知
本节为 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 设置 |
8.1 配置与订阅
- 登录 member-portal,进入 Webhook 设置。
- 填写回调 URL(生产须 HTTPS;禁止内网/metadata 等 SSRF 目标)。
- 订阅事件类型;保存后获得 Webhook Secret(仅创建/轮换时明文展示一次)。
- 使用「测试投递」验证连通性;「投递日志」可查询与手动重试失败记录。
配置接口位于 /api/v1/member/webhook-config(会员 JWT),不在 /open/v1 白名单。
8.2 回调 HTTP 约定
| 项 | 说明 |
|---|---|
| 方法 | POST |
| Content-Type | application/json; charset=utf-8 |
| 成功响应 | HTTP 2xx(建议 200);非 2xx 可能触发重试 |
| 超时 | 平台侧连接/读超时约 5s / 10s(以部署为准) |
8.3 签名 HTTP 头
| HTTP 头 | 说明 |
|---|---|
X-Vcc-Signature | HMAC-SHA256 签名,小写十六进制 |
X-Vcc-Timestamp | 签名时 Unix 时间戳,单位 毫秒(UTC) |
X-Vcc-Nonce | 16 字节随机数的小写 hex(32 字符) |
X-Vcc-Event-Id | 事件唯一 ID,须与 JSON 内 eventId 一致 |
8.4 验签算法
- 读取请求体原始字节,UTF-8 解码为字符串
body(勿重新序列化 JSON)。 - 读取头
X-Vcc-Timestamp、X-Vcc-Nonce、X-Vcc-Signature。 - 拼接待签名字符串(无分隔符):
payload = String(timestamp) + nonce + body - 计算:
expected = 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 防重放。8.5 事件类型
| eventType | 说明 |
|---|---|
merchant.webhook.transaction.updated | 平台交易状态变更(清算/入账等;不含实时授权) |
merchant.webhook.wallet.deposit.posted | 数币自动充值入账成功(含链上 txHash;不含人工充值) |
merchant.webhook.wallet.card_binding.verification_code | 钱包绑卡验证码就绪 |
merchant.webhook.test | 门户「测试投递」(联调) |
同一 eventId 成功投递后平台不重复推送;商户侧仍建议以 eventId 做业务去重。
8.6 事件载荷
外层 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 及幂等去重关联。 |
txnType 码值
平台统一转为大写;缺省为 CHANNEL。本事件在交易清算/入账等后续阶段推送,不含实时授权。
| 码值 | 含义 | Webhook 说明 |
|---|---|---|
CHANNEL | 渠道同步交易 | 最常见;平台同步渠道交易后的默认类型。 |
AUTHORIZATION | 授权 | 实时授权阶段不会推送本事件。 |
CAPTURE | 请款 / 捕获 | 可能出现。 |
SALE / PURCHASE | 消费 | 可能出现。 |
SETTLEMENT | 清算 | 可能出现。 |
REFUND | 退款 | 可能出现。 |
VOID | 撤销 | 可能出现。 |
REVERSAL | 冲正 | 可能出现。 |
FEE | 费用 | 可能出现。 |
其他大写字符串 | 扩展类型 | 未在上表列出时按平台原值大写推送;请兼容未知值。 |
status 码值
平台将上游状态映射为大写枚举;未命中下表时按原值大写推送。本事件不推送 AUTHORIZED(实时授权)。
| 码值 | 含义 | 说明 |
|---|---|---|
SETTLED | 已清算 / 已入账 | 交易已完成清算或入账 |
PENDING | 处理中 | 尚未终态 |
DECLINED | 已拒绝 | 交易被拒绝 |
FAILED | 失败 | 处理失败 |
CANCELLED | 已取消 | 交易已取消 |
RETURNED | 已退回 | 资金或交易已退回 |
REFUNDED | 已退款 | 已完成退款 |
REVERSED | 已冲正 | 交易已冲正 |
AUTHORIZED | 已授权 | 仅内部授权阶段;不会出现在本 Webhook 事件中 |
其他大写字符串 | 扩展状态 | 未映射时原样大写;请兼容未知值 |
data 中不含 memberUserId;请用 cardAccountId / channelReference 关联业务。实时授权阶段不会触发本事件。同一 channelReference 状态变更会复用稳定 eventId,成功投递后不会重复推送。数币充值到账 — data 示例
{
"depositId": 10086,
"txHash": "0xabc...def",
"chainId": 1,
"chainName": "Ethereum",
"chainSymbol": "ETH",
"fromAddress": "0xfrom...",
"toAddress": "0xto...",
"amountMinor": 10000,
"amount": "100.00",
"currency": "USD",
"amountRaw": "100000000",
"payCurrencyId": 2,
"payRechargeId": 778899,
"channelCode": "PAY_PROTOCOL",
"walletOperationId": "8842",
"walletId": "54",
"occurredAt": "2026-08-10T06:15:57.424Z"
}
数币充值到账 — data 字段
| 字段 | 类型 | 说明 |
|---|---|---|
depositId | number | 平台充值单 ID。 |
txHash | string | 链上交易哈希(内部 transferHash)。 |
chainId | number | Pay Protocol 链 ID。 |
chainName / chainSymbol | string? | 链名称/符号(有目录数据时返回)。 |
fromAddress / toAddress | string? | 转出/收款地址。 |
amountMinor | number | 入账金额(最小货币单位,USD 为分)。 |
amount | string | 入账金额(两位小数)。 |
currency | string | 入账币种,当前为 USD。 |
amountRaw | string? | 渠道原始金额字符串。 |
payCurrencyId | number | Pay 币种 ID。 |
payRechargeId | number | 渠道充值单 ID;与 eventId 幂等相关。 |
channelCode | string | 渠道编码,如 PAY_PROTOCOL。 |
walletOperationId | string | 钱包入账操作 ID。 |
walletId | string? | 主钱包账户 ID(若可解析)。 |
occurredAt | string | 入账完成时间(ISO-8601)。 |
GET /open/v1/wallets 与 GET /open/v1/wallet-ledger。
eventId:mwh_evt_deposit_{CHANNEL}_{payRechargeId}。
本事件走发卡 Webhook(门户 Webhook 设置),不是收单 Webhook。
绑卡验证码 — data 示例
{
"memberUserId": 32,
"inboundId": 1782206824529,
"walletId": 10,
"cardAccountId": 10001,
"bindingCode": "123456",
"expiresAt": "2026-06-23T09:22:31.285Z",
"channelCode": "VCC"
}
data.bindingCode 为完整验证码;门户投递日志 API 可能对验证码脱敏展示。8.7 重试
| 商户响应 | 平台行为 |
|---|---|
| HTTP 2xx | 标记成功,不再重试 |
| HTTP 5xx 或连接/超时 | 指数退避自动重试(默认最多 3 次) |
| HTTP 4xx | 一般不再自动重试 |
请快速返回 2xx 并异步处理业务,避免长时间阻塞导致超时重试。
8.8 验签示例
Python
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)
Node.js
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);
}
Java
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。
9. 典型对接流程
9.1 开卡并归属卡组(API 全链路)
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}— 确认cardGroupLabel
9.2 删除卡组
PATCH /cards/{id}/card-group— 移出卡组内全部卡片(platformCardGroupId: null)POST /card-groups/{id}/release— 划回全部子池余额DELETE /card-groups/{id}— 软删除
9.3 配置消费约束
GET /spending-constraint/referenceGET /slash-catalog/merchant-categoriesGET /slash-catalog/merchants?q=...GET /cards/{id}/spending-constraintPATCH /cards/{id}/spending-constraintGET /cards/{id}/spending-utilization
9.4 开卡后改挂卡组
GET /card-groupsPATCH /cards/{id}/card-group—{ "platformCardGroupId": 9 }或null移出GET /cards/{id}— 确认展示字段
9.5 数币充值到账(Webhook)
- 运营开通发卡 Open API(ISSUING 凭证)与数币充值能力;会员在门户生成链上充值地址。
- 门户 Webhook 设置:HTTPS 回调 URL + 订阅
merchant.webhook.wallet.deposit.posted,保存 Webhook Secret。 - 用户链上转入 → 平台入账成功后 POST 本事件(含
txHash、walletOperationId)。 - 商户验签并以
eventId去重;可用GET /wallets/GET /wallet-ledger核对余额与流水。
10. 联调检查清单
- 使用 ISSUING AccessKey;用 ACQUIRING Key 调发卡路径返回
40314 - 签名头齐全;无 query 时 Canonical 第三行为空字符串;无 body 时对空字节做 SHA-256
- 写操作携带
Idempotency-Key;DELETE 卡组不要发送{} - 钱包只读:
GET /wallets、GET /wallet-ledger可见入账;Open API 无充值/提现写接口 - 门户订阅数币到账事件后,入账成功可收到
merchant.webhook.wallet.deposit.posted,验签算法与 Open API 入站不同 - Webhook 须返回 HTTP 2xx;4xx 一般不自动重试,5xx/超时会退避重试
11. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-08-14 | 版式与收单商户文档对齐(侧栏目录、路径徽章、元信息卡);明确数币到账仅 Webhook、无 Open API 充值查询接口;补充联调清单与到账流程 |
| 2026-08-10 | 各接口补充请求/响应字段;新增 Webhook merchant.webhook.wallet.deposit.posted;卡消费约束 PATCH 明确 Slash 同构 body |
| 2026-06-26 | 新增卡组创建/删除及主钱包↔卡组划款 |
| 2026-06-23 | 新增商户 Webhook 出站通知(验签、事件类型) |
| 2026-06-18 | 卡组只读/绑卡、消费约束目录、约束读取与用量;GET /cards 分页;开卡支持 platformCardGroupId |