发卡 Open API 接口文档

面向发卡商户。能力覆盖卡产品、卡组与划款、钱包只读、开卡、卡片生命周期与消费约束、交易查询;数币充值到账通过发卡 Webhook 通知,Open API 不提供充值写接口。

文档版本
2026-08-14
服务前缀
/open/v1/**(不含 /open/v1/acquiring/**)
凭证类型
必须使用 credential_type = ISSUING 的 AccessKey
Base URL
由交付环境提供,形如 https://{host}(下文路径相对该 Host)
金额约定
带 Minor / Cents 后缀为最小货币单位整数;两位小数字符串为展示金额
重要:本接口仅接受 ISSUING 凭证。使用 ACQUIRING 凭证调用发卡路径返回 40314。 收单能力见独立文档 收单 Open API 商户接口文档。

接入前准备

  1. 联系运营开通商户 Open API,获取 ISSUING 凭证:AccessKeyId + SecretKey + MerchantCode。
  2. 实现请求签名(§2)与写操作幂等 Key(§5)。
  3. 如需交易/绑卡/数币到账通知:在 member-portal Webhook 设置配置 HTTPS 回调 URL、订阅事件,并保存 Webhook Secret(仅创建/轮换时展示一次)。
  4. 数币充值地址在会员门户「数币充值」生成;到账后等待 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 的输入消息:

  1. HTTP 方法:大写,如 GET、POST。
  2. 路径:与网关可见的 Servlet 路径一致,例如 /open/v1/cards(不含域名与查询串)。
  3. 查询串:对查询参数名按字典序排序;同名参数多值按值排序;键值对形如 key=value,以 & 连接;键与值均需 URL 编码(空格为 %20)。若无查询参数,本行为空字符串(不要写 ?)。
  4. 请求体哈希:对请求体原始字节做 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 建议落库以便工单。

HTTPcode(示例)典型含义
400VALIDATION_ERROR 等参数缺失、格式非法、业务校验失败。
401OPEN_API_CREDENTIAL_INVALID访问密钥无效或已停用。
401OPEN_API_SIGNATURE_INVALID签名不匹配或缺少签名相关头。
401OPEN_API_CLOCK_SKEW时间戳超出允许偏差。
40140113 / OPEN_API_PATH_NOT_ALLOWED路径未对当前版本开放。
403FORBIDDEN商户编码与密钥不匹配、资源归属不符等。
40340314 / 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 无请求体,但仍须签名头(健康检查除外)。

请求体有无一览:有 JSON body → §7.4 创建卡组、§7.6 划入、§7.7 划出、§7.11 开卡、§7.18 挂卡组、§7.19 生命周期、§7.20 调额;无 body → 全部 GET,以及 §7.5 删除卡组。

7.1 健康检查

GET /open/v1/health

签名

不要求签名相关 HTTP 头。

请求示例

GET {MERCHANT_API_BASE}/open/v1/health
(无请求头签名;无请求体)

响应 data

{ "status": "UP" }

响应字段

字段类型说明
statusstring服务状态;正常为 UP。

7.2 已发布卡产品列表

GET /open/v1/card-products

请求示例

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..."
  }
]

响应字段(数组元素)

字段类型说明
idnumber卡产品 ID,开卡时传入 cardProductId。
codestring产品编码。
namestring产品名称。
currencystring记账币种,如 USD。
publishedboolean是否已发布(可开卡)。
memberVisibilitystring会员可见性策略枚举。
spendingConstraintCeilingJsonstring?可选。消费约束上限(JSON 字符串);PATCH 调额不得超过此上限。
merchantCodestring当前商户编码,与请求头一致。

7.3 卡组列表

GET /open/v1/card-groups

查询参数

参数必填说明
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..."
  }
]

响应字段(数组元素)

字段类型说明
idnumber卡组 ID(划款、开卡归属、挂卡组时使用)。
codestring卡组编码(服务端生成)。
namestring卡组名称。
memberOwnedboolean是否会员自建;本接口恒为 true。
statusstring如 ACTIVE;列表仅返回可用组。
merchantCodestring商户编码。

本接口不含余额;子池余额见 §7.8(walletRole=CARD_GROUP)。

7.4 创建卡组

POST /open/v1/card-groups

与 §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..."
}

响应字段

字段类型说明
idnumber新建卡组 ID。
codestring新建卡组编码。
namestring名称(与请求一致)。
statusstring一般为 ACTIVE。
memberOwnedbooleantrue。
merchantCodestring商户编码。

7.5 删除卡组

DELETE /open/v1/card-groups/{id}

软删除(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..."
}

响应字段

字段类型说明
idnumber被删除的卡组 ID。
statusstring固定 INACTIVE(软删除)。
merchantCodestring商户编码。
场景HTTP说明
仍有卡片400卡组内仍有卡片,请先移出(PATCH .../card-group)
子池有余额400请先 POST .../release 划回主钱包

7.6 划入子池(主钱包 → 卡组)

POST /open/v1/card-groups/{id}/fund

请求头

须带 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..."
}

响应字段

字段类型说明
idstring钱包操作 ID。
referencestring业务引用,形如 wo_{id}。
statusstring如 SUCCEEDED / FAILED / PROCESSING。
typestring固定 CARD_GROUP_FUND。
currencystring币种。
amountMinornumber金额(最小货币单位,如美分)。
amountstring金额(两位小数)。
failureReasonstring?失败时原因。
merchantCodestring商户编码。

主钱包余额不足时返回 400 WALLET_INSUFFICIENT_FUNDS。划款后可通过 §7.8 钱包、§7.10 流水确认。

7.7 划出子池(卡组 → 主钱包)

POST /open/v1/card-groups/{id}/release

请求头

须带 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..."
}

响应字段

字段类型说明
idstring钱包操作 ID。
referencestring业务引用,形如 wo_{id}。
statusstring如 SUCCEEDED / FAILED / PROCESSING。
typestring固定 CARD_GROUP_RELEASE。
currencystring币种。
amountMinornumber金额(最小货币单位)。
amountstring金额(两位小数)。
failureReasonstring?失败时原因。
merchantCodestring商户编码。

子池余额不足时返回 400 WALLET_INSUFFICIENT_FUNDS。

7.8 钱包列表

GET /open/v1/wallets

查询参数

参数必填说明
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..."
  }
]

响应字段(数组元素)

字段类型说明
walletIdstring钱包账户 ID(§7.9 路径参数)。
currencystring币种,如 USD。
availableAmountMinornumber可用余额(最小货币单位)。
availableAmountstring可用余额(两位小数)。
cardGroupIdnumber0 = 主钱包;>0 = 对应卡组子池 ID。
walletRolestringMAIN 或 CARD_GROUP。
cardGroupNamestring?卡组名称;主钱包为 null。
merchantCodestring商户编码。

7.9 单个钱包

GET /open/v1/wallets/{id}

请求示例

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 资金流水(分页)

GET /open/v1/wallet-ledger

查询参数

参数必填说明
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", ... } ]
}

响应字段

字段类型说明
totalnumber总条数。
pagenumber当前页(从 0 起)。
sizenumber本页实际条数(非请求 size)。
sortstringasc / desc。
merchantCodestring商户编码。
itemsarray流水明细,见下表。

响应字段(items[])

字段类型说明
idstring流水行 ID。
occurredAtstring发生时间(ISO-8601)。
amountMinornumber带符号金额(最小单位);正=入账,负=出账。
amountstring绝对值金额(两位小数)。
directionstringCREDIT(入)或 DEBIT(出)。
balanceAfterMinornumber变动后余额(最小单位)。
balanceAfterstring变动后余额(两位小数)。
entryTypestring分录类型(如划款、授权扣款、冲正释放等)。
referenceTypestring?关联业务类型。
referenceIdstring?关联业务 ID。
businessRefstring?业务引用号。
walletAccountIdstring所属钱包账户 ID。
cardGroupIdstring卡组 ID;主钱包为 "0"。

7.11 创建开卡任务

POST /open/v1/card-issuance-tasks

请求头

须带 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": "..." }
}

响应字段

字段类型说明
idstring开卡任务 ID(用于 §7.12 轮询)。
merchantCodestring商户编码。
statusstring任务状态,如 PENDING / SUCCEEDED / FAILED。
channelRequestIdstring?渠道侧请求 ID。
appliedFeeBreakdownobject开卡手续费试算/落地摘要,见下表。

响应字段(appliedFeeBreakdown)

字段类型说明
feeTypestring固定 CARD_ISSUANCE。
sourcestring规则来源:MERCHANT / DEFAULT。
ruleIdstring规则 ID。
ruleTablestring规则表名。
currencystring计费币种。
calculationModestring计费模式。
fixedFeeMinorstring固定费(最小单位,字符串)。
ratePercent—费率(若有)。
feeMinorstring实际手续费(最小单位)。
summarystring人类可读摘要。
merchantCodestring商户编码。

7.12 查询开卡任务

GET /open/v1/card-issuance-tasks/{id}

请求示例

GET {MERCHANT_API_BASE}/open/v1/card-issuance-tasks/12345
Headers: 签名头(§2)
(无请求体)

响应 data

{ "id": "...", "merchantCode": "M...", "status": "...", "channelRequestId": "..." }

响应字段

字段类型说明
idstring开卡任务 ID。
merchantCodestring商户编码。
statusstring任务状态。
channelRequestIdstring?渠道侧请求 ID。

本接口不含 appliedFeeBreakdown;手续费以创建时响应为准。

7.13 卡片列表(分页)

GET /open/v1/cards

查询参数

参数必填说明
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..."
    }
  ]
}

响应字段(分页外壳)

字段类型说明
totalnumber总卡数。
pagenumber当前页。
sizenumber页大小。
merchantCodestring商户编码。
itemsarray卡片列表,见下表。

响应字段(items[] / 卡片详情)

字段类型说明
idstring卡账户 ID(后续卡相关接口路径参数)。
displayNamestring?展示名。
panMaskedstring?掩码卡号。
panLast4string?卡号后四位。
statusstring卡状态,如 ACTIVE / FROZEN / CLOSED。
cardProductIdnumber卡产品 ID。
cardProductNamestring?卡产品名称。
platformCardGroupIdnumber?所属卡组 ID;未归属为 null。
channelDisplayNamestring渠道展示名。
channelCodestring?渠道编码,如 SLASH。
cardGroupNamestring?卡组名称。
cardGroupCodestring?卡组编码。
cardGroupMemberOwnedboolean是否会员自建卡组。
cardGroupLabelstring人类可读卡组标签。
merchantCodestring商户编码。

7.14 卡片详情

GET /open/v1/cards/{id}

请求示例

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 卡片敏感信息

GET /open/v1/cards/{id}/sensitive

查询参数

参数必填说明
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..."
}

响应字段

字段类型说明
cardIdstring卡账户 ID。
channelCodestring渠道编码。
panstring完整卡号(PCI 敏感)。
cvvstringCVV(PCI 敏感)。
expMonthnumber过期月(1–12)。
expYearnumber过期年(四位)。
merchantCodestring商户编码。
该接口返回完整 PAN/CVV,请仅在必要场景调用,并确保传输与存储符合 PCI 相关要求。

7.16 读取卡消费约束

GET /open/v1/cards/{id}/spending-constraint

查询参数

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..."
}

响应字段

字段类型说明
sourcestringLIVE(实时渠道)或 CACHED(缓存兜底)。
spendingConstraintobject?渠道消费约束子树(与 Slash 同构);可作为 §7.20 PATCH 参考。
channelErrorstring?仅 CACHED 时:渠道错误信息。
cachedAtstring?仅缓存时:缓存时间(ISO)。
nonRealtimeboolean?缓存时为 true。
merchantCodestring商户编码。

spendingConstraint 常见嵌套:spendingRule.utilizationLimit(周期额度)、spendingRule.transactionSizeLimit(单笔限额)、merchantCategoryCodeRule(MCC)等。

7.17 读取卡周期额度使用

GET /open/v1/cards/{id}/spending-utilization

查询参数

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..."
}

响应字段

字段类型说明
cardAccountIdnumber卡账户数字 ID。
supportedboolean是否支持周期额度;非 SLASH 等可能为 false(仅返回基础字段)。
spentCentsnumber本周期已用额度(分)。
limitCentsnumber?本周期限额(分);无限制为 null。
scopeTypestring?额度作用域类型。
currencystring?币种。
presetstring?周期:daily/weekly/monthly/yearly/collective。
timezonestring?时区,如 UTC。
periodKeystring?当前周期键,如 2026-06。
nextResetAtstring?下次重置时间(ISO)。
utilizationPercentnumber?使用率 0–100。
sourcestring数据来源,如 CONSTRAINT / RECONCILE。
lastEventAt / lastReconciledAtstring?最近事件/对账时间。
nonRealtimeboolean通常为 true(非实时渠道直读)。
merchantCodestring商户编码。

7.18 开卡后挂入/移出卡组

PATCH /open/v1/cards/{id}/card-group

请求头

须带 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 卡片生命周期动作

POST /open/v1/cards/{id}/actions

请求头

须带 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" }

响应字段

字段类型说明
idstring卡账户 ID。
merchantCodestring商户编码。
actionstring实际执行的动作。
statusstring动作后卡状态。

7.20 卡消费控制部分更新

PATCH /open/v1/cards/{id}/spending-constraint

请求头

须带 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.presetstringdaily / weekly / monthly / yearly / collective。
spendingRule.utilizationLimit.timezonestring时区,建议 UTC。
spendingRule.utilizationLimit.limitAmount.amountCentsnumber周期额度(分)。
spendingRule.transactionSizeLimit.minimum.amountCentsnumber单笔最小(分)。
spendingRule.transactionSizeLimit.maximum.amountCentsnumber单笔最大(分)。
merchantCategoryCodeRule.merchantCategoryCodesstring[]MCC 代码列表。
merchantCategoryCodeRule.restrictionstringallowlist / blacklist。

响应 data

渠道返回的消费约束 JSON(透传,结构与 Slash spending-constraint 视图一致;成功时通常含更新后的 spendingRule 等)。失败时平台返回业务错误码(如 50021),不以成功体表示失败。

7.21 交易分页查询

GET /open/v1/transactions

查询参数

参数必填说明
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..." } ]
}

响应字段(分页外壳)

字段类型说明
totalnumber总条数。
pagenumber当前页。
pageSizenumber有效页大小。
itemCountnumber本页条数。
merchantCodestring商户编码。
itemsarray交易列表,见下表。

响应字段(items[])

字段类型说明
idstring交易记录 ID。
memberUserIdstring会员用户 ID。
cardAccountIdstring?卡账户 ID。
txnTypestring交易类型。
statusstring平台交易状态。
amountMinornumber带符号金额(最小单位)。
amountstring绝对值金额(两位小数)。
currencystring币种。
occurredAtstring发生时间(ISO)。
merchantReferencestring?商户侧引用。
channelReferencestring?渠道侧引用。
narrativestring?叙述/摘要。
merchantDescriptionstring?受理商户描述。
channelCodestring?渠道编码。
channelStatusstring?渠道状态。
channelDetailedStatusstring?渠道细状态。
feeSnapshotAvailableboolean是否有落库手续费快照。
feeTotalMinorSnapshot 等—快照字段:总额、规则来源/ID/表、摘要等(有快照时出现)。
feeEstimateAvailableboolean是否按当前规则给出预估手续费。
feeEstimateMinor / feeEstimatenumber / string?预估手续费。
feeEstimateNote 等string?预估说明 / 与快照对比说明。
merchantCodestring商户编码(项级)。

无单笔交易详情接口;请用本分页 + 筛选获取。

7.22 商户类别(MCC)

GET /open/v1/slash-catalog/merchant-categories

查询参数

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"
}

响应字段

字段类型说明
itemsarrayMCC 列表。
items[].idstring类别资源 ID(配置规则时可用)。
items[].namestring类别名称。
items[].codestringMCC 四位码,如 5411。
cursorstring?下一页游标;无更多为 null。

兼容别名(建议迁移至本路径):GET /open/v1/mcc,行为相同。

7.23 受理商户搜索

GET /open/v1/slash-catalog/merchants

查询参数

参数必填说明
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..."
}

响应字段

字段类型说明
totalnumber匹配总数。
sourcestring数据来源,如 LOCAL_DB。
lastSyncAtstring?最近同步时间(ISO)。
itemsarray受理商户列表。
items[].idstring受理商户 ID(配置 merchantRule 时使用)。
items[].namestring名称。
items[].labelstring展示标签。
merchantCodestring当前对接商户编码。

7.24 消费约束静态参考

GET /open/v1/spending-constraint/reference

组装 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..."
}

响应字段

字段类型说明
restrictionsstring[]限制模式枚举:allowlist / blacklist。
utilizationPresetsstring[]周期预设枚举。
mccShortlistarray常用 MCC:code + label。
countryShortlistarray常用国家:code + label。
fieldNotesobject字段填写提示(如类别 ID、受理商户 ID 来源)。
merchantCodestring商户编码。

7.25 查询生效手续费规则并试算

GET /open/v1/merchant-fee-rules/effective

查询参数

参数必填说明
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..."
}

响应字段

字段类型说明
feeTypestring与请求一致的费用类型。
sourcestringMERCHANT(商户规则)或 DEFAULT(全局默认)。
ruleIdstring生效规则 ID。
ruleTablestring规则表,如 fee_rule_merchant。
calculationModestring计费模式。
currencystring计费币种。
fixedFeeMinorstring固定费(最小单位)。
minFeeMinorstring最低费。
ratePercent—费率(若有)。
tierRef—阶梯引用(若有)。
appliesTo—适用范围(若有)。
baseAmountMinorstring试算基数(最小单位)。
foreignTransactionboolean试算是否按跨境假设(默认 true)。
quotedFeeMinorstring试算出的手续费(最小单位)。
summarystring人类可读规则摘要。
merchantCodestring商户编码。

8. 发卡 Webhook 出站通知

本节为 VCC → 商户 方向的 HTTP 回调,与 §2 Open API 入站签名 不是同一套算法,请勿使用 API SecretKey 验签 Webhook,亦勿用 Webhook Secret 调用 Open API。

对比项Open API(商户 → VCC)Webhook(VCC → 商户)
方向商户请求 /open/v1/**平台 POST 至商户配置的回调 URL
密钥API SecretKeyWebhook Secret(门户单独配置)
签名输入Canonical Request(4 行)timestamp + nonce + body 字符串拼接
X-Vcc-TimestampUnix 秒Unix 毫秒
配置member-portal → Open API 凭证member-portal → Webhook 设置

8.1 配置与订阅

  1. 登录 member-portal,进入 Webhook 设置。
  2. 填写回调 URL(生产须 HTTPS;禁止内网/metadata 等 SSRF 目标)。
  3. 订阅事件类型;保存后获得 Webhook Secret(仅创建/轮换时明文展示一次)。
  4. 使用「测试投递」验证连通性;「投递日志」可查询与手动重试失败记录。

配置接口位于 /api/v1/member/webhook-config(会员 JWT),不在 /open/v1 白名单。

8.2 回调 HTTP 约定

项说明
方法POST
Content-Typeapplication/json; charset=utf-8
成功响应HTTP 2xx(建议 200);非 2xx 可能触发重试
超时平台侧连接/读超时约 5s / 10s(以部署为准)

8.3 签名 HTTP 头

HTTP 头说明
X-Vcc-SignatureHMAC-SHA256 签名,小写十六进制
X-Vcc-Timestamp签名时 Unix 时间戳,单位 毫秒(UTC)
X-Vcc-Nonce16 字节随机数的小写 hex(32 字符)
X-Vcc-Event-Id事件唯一 ID,须与 JSON 内 eventId 一致

8.4 验签算法

  1. 读取请求体原始字节,UTF-8 解码为字符串 body(勿重新序列化 JSON)。
  2. 读取头 X-Vcc-Timestamp、X-Vcc-Nonce、X-Vcc-Signature。
  3. 拼接待签名字符串(无分隔符):payload = String(timestamp) + nonce + body
  4. 计算:expected = hex_lower( HMAC_SHA256( UTF8(WebhookSecret), UTF8(payload) ) )
  5. 恒定时间比较 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 字段

字段类型说明
transactionIdstring平台侧交易 ID,格式 txn_{channelReference}。
cardAccountIdnumber | null卡账户 ID;未关联到卡时可能为 null。
txnTypestring交易类型(大写),见下表「txnType 码值」。
statusstring交易状态(大写),见下表「status 码值」。
amountMinornumber金额,最小货币单位(如 USD 为分);符号与平台记账一致。
currencystring币种,如 USD。
occurredAtstring业务发生时间,ISO-8601 UTC。
merchantReferencestring | null商户侧单号/引用(若有)。
channelReferencestring平台侧渠道交易引用;与 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 字段

字段类型说明
depositIdnumber平台充值单 ID。
txHashstring链上交易哈希(内部 transferHash)。
chainIdnumberPay Protocol 链 ID。
chainName / chainSymbolstring?链名称/符号(有目录数据时返回)。
fromAddress / toAddressstring?转出/收款地址。
amountMinornumber入账金额(最小货币单位,USD 为分)。
amountstring入账金额(两位小数)。
currencystring入账币种,当前为 USD。
amountRawstring?渠道原始金额字符串。
payCurrencyIdnumberPay 币种 ID。
payRechargeIdnumber渠道充值单 ID;与 eventId 幂等相关。
channelCodestring渠道编码,如 PAY_PROTOCOL。
walletOperationIdstring钱包入账操作 ID。
walletIdstring?主钱包账户 ID(若可解析)。
occurredAtstring入账完成时间(ISO-8601)。
到账通知边界:Open API 没有数币充值地址 / 充值单查询接口。 链上到账并入账成功后,仅通过本事件通知。补偿查询请用 GET /open/v1/wallets 与 GET /open/v1/wallet-ledger。
仅在数币自动充值入账成功(POSTED)后推送;KYT 中风险待审、高风险拒绝时不推送。 未配置 Webhook、未订阅本事件或配置已禁用时,入账仍完成但不回调。 门户「人工充值申请」(银行转账审批)不触发本事件。 数币链路若经 Ops 审核后仍走同一入账,会推送。 稳定 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"
}
Webhook 回调 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 全链路)

  1. POST /card-groups — 创建卡组(或 GET /card-groups 选用已有)
  2. POST /card-groups/{id}/fund — 从主钱包划入子池
  3. GET /wallets — 确认 MAIN 与 CARD_GROUP 子池余额
  4. GET /card-products — 确认 cardProductId、spendingConstraintCeilingJson
  5. POST /card-issuance-tasks — { "cardProductId", "platformCardGroupId" }
  6. GET /cards/{id} — 确认 cardGroupLabel

9.2 删除卡组

  1. PATCH /cards/{id}/card-group — 移出卡组内全部卡片(platformCardGroupId: null)
  2. POST /card-groups/{id}/release — 划回全部子池余额
  3. DELETE /card-groups/{id} — 软删除

9.3 配置消费约束

  1. GET /spending-constraint/reference
  2. GET /slash-catalog/merchant-categories
  3. GET /slash-catalog/merchants?q=...
  4. GET /cards/{id}/spending-constraint
  5. PATCH /cards/{id}/spending-constraint
  6. GET /cards/{id}/spending-utilization

9.4 开卡后改挂卡组

  1. GET /card-groups
  2. PATCH /cards/{id}/card-group — { "platformCardGroupId": 9 } 或 null 移出
  3. GET /cards/{id} — 确认展示字段

9.5 数币充值到账(Webhook)

  1. 运营开通发卡 Open API(ISSUING 凭证)与数币充值能力;会员在门户生成链上充值地址。
  2. 门户 Webhook 设置:HTTPS 回调 URL + 订阅 merchant.webhook.wallet.deposit.posted,保存 Webhook Secret。
  3. 用户链上转入 → 平台入账成功后 POST 本事件(含 txHash、walletOperationId)。
  4. 商户验签并以 eventId 去重;可用 GET /wallets / GET /wallet-ledger 核对余额与流水。

10. 联调检查清单

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