如何集成 Canonical
了解 Canonical PSS 驱动如何通过实现标准入金 API 合约,将支付服务提供商连接到 B2CORE,包括 B2CORE 端配置、webhook、轮询以及入金状态生命周期。
Canonical 驱动可让您通过 PSS 将您选择的支付服务提供商 (PSP) 连接到 B2CORE,即使 B2CORE 尚未为该提供商提供专用驱动。
为什么使用 Canonical 驱动
B2CORE 中的大多数支付系统都依赖于专门为某个提供商构建的专用驱动。Canonical 驱动采用不同的方法:它定义了一个统一的标准 API 合约——Canonical Deposit API,任何提供商都可以实现该合约。无论合约背后使用的是哪个提供商,B2CORE 都会以统一的方式处理身份验证、入金启动流程、轮询、webhook 和状态生命周期。
当您希望进行以下操作时,Canonical 驱动非常有用:
- 连接没有 B2CORE 专用驱动的首选或内部 PSP,而无需等待定制开发。
- 让您的提供商实现一份已文档化且稳定的合约,而非定制集成,以缩短上市时间。
- 在 B2CORE 管理其端入金工作流的同时,完全掌控提供商端。
Canonical 驱动目前支持入金流程。若要通过您的提供商提供入金服务,该提供商必须实现下方 OpenAPI 规范中描述的 Canonical Deposit API,并遵循本页面中的行为要求。
OpenAPI 规范
Canonical Deposit API 定义于以下 OpenAPI 规范中,其中涵盖身份验证、请求和响应架构、端点及状态码。请下载该文件,以查看您的提供商必须实现的完整合约:
canonical-deposit-api.yaml
本页面的其余部分说明了该规范无法表达的行为要求、B2CORE 端配置和设计决策。请结合阅读两者,以全面了解集成方式。
B2CORE 驱动凭据
以下字段配置于 B2CORE 端(Back Office),用于向 PSP API 进行身份验证。有关完整的 JWT 令牌生成和验证详情,请参阅 OpenAPI 规范。
| 字段 | 描述 |
|---|---|
| API 基础 URL | PSP API 的 HTTPS 基础 URL。 |
| App ID | 唯一商户标识符。在 JWT 中用作 sub 声明。 |
| App secret | 用于 HMAC-SHA256 签名的密钥。采用 Base64 URL 编码,长度为 32 字节(43 个字符)。绝不会在请求中发送——仅用于签署令牌。 |
B2CORE 驱动配置字段
以下字段配置于 B2CORE 端(Back Office),用于控制驱动行为。它们不属于面向 PSP 的 API。
全局参数(globalParam1、globalParam2、globalParam3)
三个配置级字符串字段,会随 每个已认证请求 发送到 PSP。
- 对于
POST端点,它们包含在 JSON 请求正文中。 - 对于
GET端点,它们作为查询参数包含在请求中。
这些字段表示 PSP 特定的值,例如商户 ID、渠道或项目 ID。其确切语义取决于 PSP 的实现。B2CORE 管理员会在配置设置期间填写这些字段。
必填字段(只读)
类型: 多选
选择哪些用户信息字段会在支付表单中显示为 只读(不可编辑)。
所选字段必须已在用户的 B2CORE 资料中配置并保存。如果资料中缺少任一所选字段,支付表单生成将因缺少字段错误而失败——用户必须先在其 B2CORE 资料中填写数据才能继续。
所选字段以及“必填字段(可编辑)”中的字段共同决定了在 POST /api/v1/deposits 请求中发送到 PSP 的 startDepositUserInfo 对象内哪些用户数据会被有效填充。未在任一列表中选择的字段会在表单中隐藏,并且可能以空值发送。
必填字段(可编辑)
类型: 多选
选择哪些用户信息字段会在支付表单中显示为 可编辑。用户可以直接在支付表单中填写或修改这些字段。与“必填字段(只读)”不同,这些字段无需预先配置在用户的 B2CORE 资料中。
优先级规则: 如果某字段同时在“必填字段(只读)”和“必填字段(可编辑)”中被选中,则它将显示为 只读。只读设置始终具有更高优先级。
电子邮件行为: 无论电子邮件是否在任一列表中被选中,它始终会显示在支付表单中,并始终在 startDepositUserInfo 中发送。配置仅控制其显示方式:
| 电子邮件选中位置 | 行为 |
|---|---|
| 两个列表均未选中 | 显示为可编辑字段 |
| 必填字段(可编辑) | 显示为可编辑字段 |
| 必填字段(只读) | 显示为只读字段(值来自 B2CORE 资料) |
| 两个列表均选中 | 显示为只读字段(只读优先) |
默认同步截止时间
类型: 选择
默认值: 4h
入金创建后,B2CORE 轮询入金状态的最长持续时间。超过此截止时间后:
StatusSyncInProgress→ 入金将移至unexpected。StatusSyncUnexpected→ 入金将移至unexpected。
unexpected 状态需要管理员通过 LifecycleService 进行手动调查。
启动时可安全失败
类型: 布尔值(当前硬编码为 yes,无可选项)
确定当启动请求遇到意外错误时,是否可以安全地将入金标记为 failed(终态)。
yes— 如果 PSP 在入金启动期间返回错误,并且 B2CORE 确信该入金未在 PSP 端创建(例如,B2CORE 从未收到重定向 URL),则可以安全地将入金移至failed。客户没有损失任何资金。no— 即使发生错误,入金也会以未经验证的外部 ID 移至in_progress并进行轮询,因为 PSP 可能已在发生错误的情况下创建了入金。
目前始终为 yes。 典型情况是:如果没有重定向 URL,客户无法完成 PSP 支付页面上的操作,因此入金无法成功。
轮询前等待 webhook
类型: 布尔值(当前硬编码为 yes,无可选项)
控制 B2CORE 在开始轮询 GET /api/v1/deposits/{externalID} 前是否等待 webhook 通知。
| 值 | 行为 |
|---|---|
yes | 入金启动后,B2CORE 会在开始轮询前最多等待 5 分钟接收 webhook。如果 5 分钟内未收到 webhook,B2CORE 将继续进行标准轮询。 |
no | B2CORE 会根据标准退避计划立即开始轮询。 |
原因: 许多 PSP 会在入金状态变更时快速发送 webhook 通知。在轮询前等待 webhook 可减少不必要的 API 调用数量,从而有助于遵守速率限制。5 分钟超时可确保即使 webhook 延迟或丢失,流程仍会继续。
测试配置流程
当管理员在 B2CORE 中点击 测试配置 时:
1. B2CORE generates a one-time JWT signed with appSecret
2. B2CORE → POST /api/v1/configuration/test (with Bearer JWT + globalParams)
3. If response status = "available" → test result: "available"
4. If response status = "failed" → test result: "failed" (with error from PSP)
5. If unexpected error (5xx, timeout, and similar) → test result: "unexpected"测试配置方法是唯一一个预期任何凭据问题返回的并非 401 Unauthorized,而是带有响应正文的 200 OK 的方法。
code 和 description 会显示给 B2CORE 管理员,因此请返回清晰且不包含敏感信息的数据。
Webhook 系统设计
两个 webhook 渠道
B2CORE 支持每笔入金使用 两个 webhook 渠道:
| 渠道 | 注册方式 | 描述 |
|---|---|---|
| 自动 | 通过 POST /api/v1/deposits 请求中的 notificationURL | 始终启用。B2CORE 生成该 URL 并将其传递给 PSP。 |
| 管理员可配置 | 由管理员在 B2CORE Back Office 中设置 | 可选。管理员可以配置一个独立的 webhook URL,供 PSP 向其发送通知(例如,在 PSP 管理面板中注册)。 |
两个渠道均会进入相同的 B2CORE webhook 处理程序 → driver_transit 存储 → 轮询器优化管道。
Webhook 作为轮询优化
webhook 不是事实来源。它是一种用于减少不必要轮询请求的优化机制。
PSP → B2CORE webhook handler → driver_transit (key-value store) → poller其工作方式如下:
- 当 webhook 到达时,B2CORE 会在
driver_transit中存储一个以externalID为键的标记。 - 轮询器会在发出 API 调用前检查
driver_transit:- 如果存在对应
externalID的 webhook 标记,B2CORE 会立即调用GET /api/v1/deposits/{externalID}。 - 如果不存在标记且经过时间少于 5 分钟,B2CORE 会等待(参阅轮询前等待 webhook)。
- 如果不存在标记且已超过 5 分钟,B2CORE 会继续执行标准轮询。
- 如果存在对应
- 当入金达到终态(成功或失败)时,
driver_transit条目会被删除。
Webhook 负载
webhook 负载极简(请参阅 OpenAPI 规范中 POST /api/v1/deposits 下的 webhook 回调):
{
"externalID": "550e8400-e29b-41d4-a716-446655440000",
"status": "success"
}负载包含以下字段:
externalID— 与POST /api/v1/deposits请求中的 UUID 相匹配。status—"success"、"failed"或"unprocessable"之一。
仅当入金转换至终态时,才应发送 webhook。
入金启动流程
启动请求
B2CORE 通过调用 POST /api/v1/deposits 发起入金。
请求包含 returnURL 字段——这是一个 B2CORE 前端页面 URL,用于在用户完成 PSP 页面交互后将其重定向回 B2CORE。这不是 webhook——它仅用于浏览器重定向。
有关所有其他详细信息,请参阅 OpenAPI 规范。
启动结果映射
PSP 响应会按如下方式映射为 B2CORE 操作:
| PSP 响应 | B2CORE 操作 |
|---|---|
| PSP 返回重定向 URL | 移至 in_progress |
| 具有规范违规的 2xx 响应(例如,没有重定向 URL) | 移至 failed |
| HTTP 4xx / 5xx / 超时 / 网络错误 | 移至 failed |
所有失败场景都会产生 failed(而非 unexpected),因为“启动时可安全失败”为 yes(参阅启动时可安全失败):如果没有有效的重定向 URL,最终用户无法与 PSP 支付页面交互,因此不会损失资金。
重定向流程
启动成功后,B2CORE 会收到重定向 URL,并将最终用户发送至 PSP 支付页面:
sequenceDiagram
participant B as B2CORE
participant P as PSP
participant U as End user
B->>P: POST /api/v1/deposits
P-->>B: 200 OK<br/>action.type: "redirect"<br/>action.redirect.url: "..."
B->>U: Redirect end user to PSP payment page
U->>P: Open PSP payment page
U->>P: Complete payment
P-->>U: Redirect to returnURL
U->>B: Land on B2CORE "in progress" pagereturnURL会将用户带回 B2CORE 前端页面,该页面会指示入金正在处理中。- 启动后,B2CORE 会开始轮询和 webhook 流程(参阅轮询和状态同步)。
- 当前,
"redirect"是唯一受支持的操作类型。
轮询和状态同步
轮询退避计划
B2CORE 使用渐进式退避轮询 GET /api/v1/deposits/{externalID}:
| 自入金启动后的时间 | 轮询间隔 |
|---|---|
| 0–15 分钟 | 每 1 分钟一次 |
| 15–60 分钟 | 每 3 分钟一次 |
| 1–3 小时 | 每 5 分钟一次 |
| 3–5 小时 | 每 10 分钟一次 |
| 5 小时以上 | 每 15 分钟一次 |
截止时间处理
默认截止时间:入金创建后 4 小时(可配置,参阅默认同步截止时间)。
超过截止时间后,中间状态将按如下方式处理:
| 最后一次轮询结果 | B2CORE 操作 |
|---|---|
inProgress | 移至 unexpected |
| 网络错误、5xx、解析错误等 | 移至 unexpected |
unexpected 状态会停止自动轮询,并要求管理员通过 B2CORE 的 LifecycleService 进行手动操作。
Webhook 等待逻辑
当“轮询前等待 webhook”为 yes(当前默认值,参阅轮询前等待 webhook)时:
flowchart TD
A["t=0min: deposit created, redirect URL returned<br/>Poller scheduled but WAITS for webhook"] --> B{Webhook received<br/>before t=5min?}
B -- yes --> C["Immediately poll<br/>GET /api/v1/deposits/{externalID}"]
B -- "no (t=5min elapsed)" --> D["Begin standard polling<br/>(1-minute intervals initially)"]
C --> E[Continue with standard<br/>polling backoff]
D --> E
E --> F[Continue until terminal status<br/>or deadline]轮询结果映射
来自 GET /api/v1/deposits/{externalID} 的每个轮询结果都会映射为 B2CORE 内部操作:
| PSP 响应状态 | B2CORE 操作 |
|---|---|
"inProgress" + 未超过截止时间 | 继续轮询(重试) |
"inProgress" + 已超过截止时间 | 移至 unexpected |
"success" | 移至 success,停止轮询。保存 finalAmount 和 finalCurrencyCode |
"failed" | 移至 failed,停止轮询。保存 reason |
"unprocessable" | 移至 unexpected,停止轮询。保存 reason。需要管理员调查 |
| HTTP 5xx / 超时 / 解析错误 | 将其视为一次 unexpected 轮询尝试。如果尚未超过截止时间,则继续轮询 |
带有 X-Safe-To-Fail-After-Seconds 的 HTTP 404 | 继续轮询。在指定时间加上安全边际(5 分钟)后,如果仍为 404,则以原因“deposit redirect URL is expired”移至 failed |
| 不带该请求头的 HTTP 404 | 继续轮询。等待入金出现在 PSP 端,或等待超过同步截止时间 |
入金状态生命周期
本节说明 B2CORE 入金状态。有关 PSP 响应状态到 B2CORE 状态的映射,请参阅启动结果映射和轮询结果映射。
完整状态图
flowchart TD
created[created]
post["POST /api/v1/deposits"]
failed1[failed]
inProgress1[in_progress]
unexpected1[unexpected]
poll["GET /api/v1/deposits/{externalID}<br/>(polling)"]
retryIP["(in_progress)<br/>(retry)"]
retryUX["(unexpected)<br/>(retry)"]
success[success]
failed2[failed]
unprocessable["(unprocessable)<br/>(stop polling)"]
inProgress2[in_progress]
created --> post
post --> failed1
post --> inProgress1
inProgress1 --> poll
poll --> retryIP
poll --> retryUX
poll --> success
poll --> failed2
poll --> unprocessable
retryIP --> inProgress2
retryUX --> inProgress2
unprocessable --> unexpected1
unexpected1 -->|Manual recovery| inProgress2从 unexpected 状态恢复
管理员可以通过 LifecycleService 执行以下手动转换:
| 转换 | 使用时机 |
|---|---|
unexpected → in_progress | 重试轮询(例如,在 PSP 中断问题解决后) |
unexpected → success | 仅当最后一次轮询尝试为 unprocessable 且管理员确认成功时 |
unexpected → failed | 管理员确认入金失败 |
入金状态定义
下表定义了每种 B2CORE 入金状态:
| 状态 | 业务含义 |
|---|---|
in_progress | 入金正在处理中。涵盖所有中间状态:待处理的银行转账、正在进行的 3DS 验证、等待 PSP 端人工审核、客户与 PSP 支付页面交互等。这是非终态——B2CORE 会继续轮询。 |
success | 经纪商已收到客户资金。PSP 已确认资金已入账。finalAmount 和 finalCurrencyCode 字段反映实际收到的金额和币种,可能因费用或兑换而与初始请求不同。 |
failed | 入金未完成。客户未损失任何资金,经纪商也未收到任何资金。示例包括:银行卡被拒绝、银行转账被拒绝、用户在 PSP 页面取消或重定向链接已过期。 |
unexpected | 无法自动解决该入金,需要通过 B2CORE 由管理员手动操作(参阅从 unexpected 状态恢复);自动轮询已停止。当 in_progress 状态下截止时间超时,或 PSP 报告 unprocessable 时会进入该状态。 |
入金创建时机
PSP 会遵循两种入金创建模式之一:
| 模式 | 行为 | 对轮询的影响 |
|---|---|---|
| 立即创建 | PSP 在 POST /api/v1/deposits 时创建入金记录。 | GET /api/v1/deposits/{externalID} 会在启动后立即返回结果。 |
| 延迟创建 | PSP 仅在最终用户完成 PSP 支付页面后创建入金记录。 | 在用户完成页面前,GET /api/v1/deposits/{externalID} 返回 404 Not Found。 |
对于延迟创建,PSP 应当在 404 响应中返回 X-Safe-To-Fail-After-Seconds 请求头。该请求头会告知 B2CORE 重定向链接的有效时长。在 redirect_time + header_value + safety_margin 后,如果入金仍返回 404,B2CORE 会将其标记为 failed,原因为 "deposit redirect URL is expired"。
如果该请求头不存在,B2CORE 会继续轮询,直至入金出现或超过同步截止时间(默认 4 小时);此时入金将移至 unexpected。
行为要求
分布式追踪
B2CORE 向 PSP 发出的所有 HTTP 请求均包含符合 W3C Trace Context 规范的标准追踪请求头:
traceparent— 包含追踪 ID、父跨度 ID 和追踪标志。tracestate— 供应商特定的追踪数据。
PSP 实现应将这些请求头传播至其下游服务,以实现端到端可观测性。
PSP 支付页面币种锁定
当入金启动响应包含重定向至 PSP 支付页面时:
- PSP 页面不得允许最终用户将
currencyCode更改为任何类似、等值或替代币种。 - PSP 页面上显示的币种必须与入金启动请求中发送的币种完全一致。
- 如果 PSP 页面允许选择币种,则该币种必须预先选定并锁定。
IP 白名单建议
尽管 API 规范中并未要求,仍强烈建议 PSP 实现在其端配置 IP 白名单。这可在 JWT 身份验证之外提供额外的安全层,将 API 访问限制为已知的 B2CORE IP 地址。
如需在您的环境中启用 Canonical 驱动,请联系您的客户经理。请具体说明您计划如何使用 Canonical 驱动以及使用目的,以便我们相应地安排访问权限。
最后更新于