跳到内容

聚合支付平台

受众:开发者、平台管理员 摘要:Payment Platform 模块用于承载商户入驻、商户应用、支付通道路由和服务商账号基线。当前阶段已提供商户入驻后台 contract,并落地开放支付下单、查询、关闭、退款、webhook outbox、quote-aware 订单账本快照、结算批次读侧、结算释放入余额、通道路由、分账规则快照、对账差异、风控冻结、服务商账单导入 / 拉取和商户控制台契约。

模块边界

spring-open-module-payment-platform 是独立业务模块,不放进 Mall、Cashier 或 Open Platform 内部:

  • API 前缀:/api/v1/payment-platform/**
  • 管理 API 前缀:/api/v1/admin/payment-platform/**
  • 商户控制台 API 前缀:/api/v1/payment-platform/merchant-console/**
  • 开放支付 API 前缀:/api/v1/open/payment/**
  • 配置前缀:spring.open.payment-platform.*
  • 数据库表前缀:payment_platform_
  • 权限码前缀:payment-platform:*

Payment Platform 面向平台型收单场景,负责管理商户、商户应用、聚合收款通道和服务商账号。真实支付能力仍通过 Cashier / Payment starter / Money 底座承接;Open Platform 只提供第三方应用、签名、scope 和调用日志,不直接保存支付平台商户配置。

当前状态

  • 模块已进入 Maven reactor,并由 spring-open-application 显式装配。
  • 模块随 Maven 依赖默认启用,入驻、商户控制台和开放支付运行时默认可用;不需要聚合支付时移除应用侧 Maven 引用,或显式设置 spring.open.payment-platform.enabled=false
  • 真实收款仍受商户审核状态、商户应用状态、通道健康、服务商账号和 Payment Provider 配置约束;默认平台手续费率仍为 0,模块不会隐式收费。
  • 首段迁移已建立四张基础表:商户主体、商户应用、支付通道、服务商账号。
  • 迁移已种子化后台权限和五个菜单入口:商户入驻、支付通道、结算风控、服务商账单、Webhook 投递。
  • 商户入驻后台 contract 已落地:分页、详情、审核通过 / 驳回、交易状态调整和商户应用绑定。
  • Admin 商户入驻页已接入 /payment-platform/merchants,可消费真实后端接口执行审核、交易开关和 Open Platform 应用绑定。
  • 商户入驻字段 Schema 已落地:后台可通过 /api/v1/admin/payment-platform/merchants/onboarding-schema 获取字段定义,Admin 商户详情抽屉复用 Schema 动态字段组件只读渲染商户资料,后续自助入驻表单可复用同一字段契约。
  • 支付通道后台 contract 已落地:分页、详情、创建、修改、删除、健康状态更新和服务商账号绑定。
  • 开放支付单 contract 已落地:第三方应用经 Open Platform 鉴权和 scope 校验后,可以创建、查询、关闭商户支付单。
  • 开放支付下单已接入通道路由:按可用通道的状态、健康状态、资产、金额区间和优先级选择 Provider,并把通道 / 服务商账号快照写入 Cashier attributes。
  • 开放退款 contract 已落地:商户退款号在同一商户应用内幂等,平台会校验可退余额、同步 Cashier 支付单退款快照,并写入退款单映射。
  • 商户 webhook outbox 已落地:支付成功、订单关闭和退款成功会写入 payment_platform_webhook_delivery 待投递记录,记录事件、目标 URL、payload、headers、重试次数和下一次重试时间。
  • 开放支付单会保存 Money 账本口径快照:结算资产、原始应收、平台手续费率、平台手续费金额、商户应收净额和 Cashier quote 摘要;稳定支付资产编码下,手续费和商户应收按实际支付资产金额计算。
  • 开放支付 API 已支持 quote:商户创建订单时可声明定价资产、允许支付资产和首选支付资产,也可按商户订单号创建 / 查询当前有效报价。
  • 结算批次首段 contract 已落地:平台可把已支付、未退款且超过结算延迟的订单生成待结算批次和明细,商户开放应用可查询自己的结算批次和明细。
  • 分账规则首段已落地:后台可按平台、商户或商户应用配置结算分账规则,结算批次生成时会按当时启用规则写入不可变分账计划快照;分账计划只做结算读侧和运营审计,不在 Payment Platform 自建余额,商户资金外流统一走 Money 余额提现。
  • 结算对账风控已落地:后台可查看结算批次,标记对账结果,冻结 / 释放异常批次,并在冻结期间阻断出款。
  • 服务商账单导入 / 拉取已落地:后台可按 CSV 导入服务商结算账单,也可通过 Provider 拉取 contract 拉取账单内容;两种入口都会批量匹配平台结算批次,金额一致时自动标记对账成功,金额差异或缺失批次时进入差异 / 人工复核并冻结未入账批次。
  • Webhook 投递后台已落地:后台可分页查看商户回调投递状态、失败原因和重试窗口,并可将失败或待处理记录重新放回 Runtime Outbox。
  • 商户控制台已落地:当前登录用户可查看自己关联商户、商户应用、支付订单、退款单、结算批次和回调投递日志;PC 用户中心新增 /account/payment-platform 入口。
  • Provider 退款执行已接入 Payment starter:开放退款会调用 Cashier 订单选定 Provider,商户请求金额保持订单定价资产口径,Provider 请求默认按 quote 等比例换算为原支付资产金额;Provider 返回已退款时同步累计退款并写入 refund.succeeded webhook,返回 pending / processing / failed 时只保存退款单和服务商快照,后续 Cashier 退款回调可补偿完成。结算释放成功会把商户应收写入商户负责人 Money 余额;后台结算对账以余额入账完成状态和服务商账单金额为准。商户 Webhook 已接入 Runtime 通用投递器,开放支付 API 签名示例、Webhook 验签示例和 SDK smoke 已提供。
  • Payment mock provider 可配置支付查询和退款状态,用于本地稳定复现真实 Provider 的补偿、退款处理中 / 失败等生产边界;结算资金事实只通过 Money 余额账本写入,资金外流统一走 Money 提现。

配置

yaml
spring:
  open:
    payment-platform:
      enabled: true
      merchant-onboarding-enabled: true
      trading-enabled: true
      default-settlement-delay-days: 1
      default-platform-fee-rate: 0
      webhook-timeout-seconds: 5
      max-webhook-retry-count: 5
      statement-pull:
        default-provider: mock
        default-window-days: 1

默认值语义:

  • enabled=true:缺省启用聚合支付平台开放侧业务处理;显式设为 false 时关闭开放支付、商户控制台和 webhook consumer,后台运营 API 仍可用于配置和排障。
  • merchant-onboarding-enabled=true:缺省开放商户入驻提交入口;需要先内部建商户时可显式关闭。
  • trading-enabled=true:缺省允许已通过审核且交易状态开启的商户应用创建真实支付交易;没有有效通道、服务商账号或 Provider 配置时仍不会完成真实收款。
  • default-settlement-delay-days=1:后续结算规则未配置时使用保守延迟。
  • default-platform-fee-rate=0:默认平台手续费率。未配置费率规则时用于订单快照,0.006 表示 0.6%;默认 0 避免模块启用后隐式收费。
  • webhook-timeout-seconds=5max-webhook-retry-count=5:商户 webhook outbox 的安全默认值;Runtime Outbox 入队会沿用这组重试配置。
  • statement-pull.default-provider=mock:后台手动拉取未指定 Provider 时的默认拉账 Provider。内置 mock 只用于开发、CI 和 smoke,真实服务商需要实现 Provider 并配置真实凭据。
  • statement-pull.default-window-days=1:后台手动拉取未指定账期起止时间时的默认回看天数,最小按 1 天处理。

上述配置已由 PaymentPlatformServiceProvider 声明到 lifecycle 配置 schema,后台配置中心可发现聚合支付启停、商户入驻、交易开关、默认结算规则、webhook 投递安全默认值和服务商账单拉取默认值。

数据模型

当前用途
payment_platform_merchant商户主体、联系人、审核状态、交易状态、结算延迟和结算资产基线
payment_platform_merchant_app商户应用、开放平台应用绑定、webhook、IP 白名单和密钥引用
payment_platform_channel聚合收款通道、支付方式、路由优先级、费率和健康状态
payment_platform_provider_account服务商账号、服务商侧商户号、子商户号和密钥引用
payment_platform_payment_order商户开放订单号与 Cashier 支付单映射、状态快照、通知地址、追踪标识、结算资产和手续费 / 商户应收账本快照
payment_platform_refund_order商户退款号与平台支付单映射、退款金额、状态、原因、服务商退款号 / 状态 / 消息 / 结果和幂等快照
payment_platform_webhook_delivery商户 webhook 待投递 / 重试记录、事件 ID、payload、headers、响应摘要和追踪标识
payment_platform_settlement_batch按商户应用生成的结算批次、结算资产、订单数、平台手续费、应结算金额、结算状态、对账状态、风控状态和出款重试摘要
payment_platform_settlement_item结算批次内的支付订单明细、商户订单号、平台订单号、金额快照、状态和追踪标识
payment_platform_settlement_rule多收款方分账规则,支持平台、商户和商户应用范围、百分比 / 固定金额 / 剩余金额、优先级和启停状态
payment_platform_settlement_split_item结算批次生成时的分账计划快照,记录规则、收款方、分账金额、状态和追踪标识
payment_platform_provider_statement_batch服务商账单导入批次、来源 Provider、账单日期、导入人、导入行数、匹配 / 差异 / 人工复核统计和状态摘要
payment_platform_provider_statement_item服务商账单行明细、结算批次号、服务商金额、币种、交易号 / 转账号、匹配状态、差异金额和处理消息

密钥只保存 secret_refsecret_masked,不在业务表保存明文密钥。金额相关字段沿用 Money / Cashier 的资产编码边界,首段只预留 asset_code、最小 / 最大金额和费率字段。

后台入口

首批权限码:

权限码说明
payment-platform:merchant:view查看聚合支付平台商户资料和应用绑定
payment-platform:merchant:review审核聚合支付平台商户入驻资料
payment-platform:channel:view查看聚合支付通道和服务商账号
payment-platform:channel:manage管理聚合支付通道和服务商账号
payment-platform:settlement:view查看聚合支付结算批次、对账状态和风控状态
payment-platform:settlement:payout执行和重试聚合支付结算出款
payment-platform:settlement:review将异常聚合支付结算批次转入人工复核
payment-platform:settlement-rule:manage管理聚合支付结算分账规则
payment-platform:statement:view查看服务商账单导入批次和批次明细
payment-platform:statement:import导入服务商账单文件并触发批量对账
payment-platform:statement:pull通过 Provider 拉取服务商账单并触发批量对账
payment-platform:webhook-delivery:view查看聚合支付 Webhook 投递状态、重试时间和错误日志
payment-platform:webhook-delivery:retry将失败或待处理的聚合支付 Webhook 投递重新放回 Runtime Outbox

首批菜单入口:

  • /payment-platform/merchants:商户入驻。
  • /payment-platform/channels:支付通道。
  • /payment-platform/settlements:结算风控。
  • /payment-platform/statements:服务商账单。
  • /payment-platform/webhooks:Webhook 投递。

商户入驻后台入口已具备真实操作能力:

方法路径说明
GET/api/v1/admin/payment-platform/merchants/onboarding-schema查询商户入驻字段 Schema,供后台审核详情和后续自助入驻表单复用
GET/api/v1/admin/payment-platform/merchants分页查询商户,可按关键字、审核状态、交易状态、风控等级和联系人用户过滤
GET/api/v1/admin/payment-platform/merchants/{id}查看商户详情和商户应用绑定
POST/api/v1/admin/payment-platform/merchants/{id}/approve通过待审核商户
POST/api/v1/admin/payment-platform/merchants/{id}/reject驳回待审核商户,并关闭交易与商户应用
POST/api/v1/admin/payment-platform/merchants/{id}/trading-status调整交易状态,只有已通过商户可开启交易
POST/api/v1/admin/payment-platform/merchants/{id}/appsappCode 创建或更新商户应用绑定

商户应用启用要求商户已审核通过且交易状态为 enabled;交易停用或挂起时会同步关闭该商户应用,避免应用绕过商户交易开关。

支付通道后台入口已具备真实操作能力:

方法路径说明
GET/api/v1/admin/payment-platform/channels分页查询通道,可按关键字、服务商、支付方式、启停状态、健康状态和资产过滤
GET/api/v1/admin/payment-platform/channels/{id}查看通道详情和服务商账号
POST/api/v1/admin/payment-platform/channels创建通道,设置服务商、支付方式、启停状态、优先级、金额区间、资产和健康状态
PUT/api/v1/admin/payment-platform/channels/{id}修改通道配置
PUT/api/v1/admin/payment-platform/channels/{id}/health更新通道健康状态并刷新最近检查时间
POST/api/v1/admin/payment-platform/channels/{id}/provider-accountsaccountCode 创建或更新服务商账号
DELETE/api/v1/admin/payment-platform/channels/{id}删除通道,并同步删除该通道下的服务商账号

结算后台入口已具备查询、入账释放、对账和风控操作能力:

方法路径说明
GET/api/v1/admin/payment-platform/settlements分页查询结算批次,可按批次号、商户、商户应用、结算状态、对账状态和风控状态过滤
GET/api/v1/admin/payment-platform/settlements/{settlementNo}查看结算批次详情和批次明细
POST/api/v1/admin/payment-platform/settlements/{settlementNo}/payoutpending / failed / manual_review 批次释放结算,把商户应收写入商户负责人 Money 余额
POST/api/v1/admin/payment-platform/settlements/{settlementNo}/retry-payoutfailed / manual_review 批次重新执行余额入账释放
POST/api/v1/admin/payment-platform/settlements/{settlementNo}/manual-review将异常批次转入人工复核,清空下次自动重试时间
POST/api/v1/admin/payment-platform/settlements/{settlementNo}/reconciliation标记对账结果。matched 记录对账成功,difference 会冻结结算并进入人工复核
POST/api/v1/admin/payment-platform/settlements/{settlementNo}/risk/freeze人工冻结异常结算批次,冻结期间禁止出款
POST/api/v1/admin/payment-platform/settlements/{settlementNo}/risk/release释放风控冻结;若批次仍处于人工复核,会恢复为待入账

分账规则后台入口与结算风控页面同屏展示,用于维护结算批次生成时的分账计划:

方法路径说明
GET/api/v1/admin/payment-platform/settlement-rules分页查询分账规则,可按关键字、商户、商户应用和启停状态过滤
GET/api/v1/admin/payment-platform/settlement-rules/{id}查看分账规则详情和收款方配置
POST/api/v1/admin/payment-platform/settlement-rules创建分账规则,设置适用范围、收款方、分账方式和优先级
PUT/api/v1/admin/payment-platform/settlement-rules/{id}修改分账规则。历史结算批次的分账快照不会重算
DELETE/api/v1/admin/payment-platform/settlement-rules/{id}软删除分账规则,后续批次不再匹配

分账规则支持三类分配方式:

  • percentage:按批次净结算金额乘以比例计算分账金额,比例按小数保存,例如 0.1000 表示 10%。
  • fixed:固定金额分账;当固定金额大于批次可分配金额时按剩余可分配金额封顶。
  • remainder:按前序规则扣减后的剩余金额分账,同一批次通常只配置一个兜底规则。

结算批次生成时会按优先级读取当时启用且匹配商户 / 商户应用的规则,写入 payment_platform_settlement_split_item 快照。后续规则修改只影响新批次,不影响历史批次;出款状态变更会同步分账计划状态,便于运营在后台查看每个批次的分账计划与实际出款阶段。

服务商账单后台入口用于导入或拉取服务商侧账单,并按结算批次号批量对账:

方法路径说明
GET/api/v1/admin/payment-platform/provider-statements分页查询服务商账单导入批次,可按关键字、Provider、账单状态和账单日期过滤
GET/api/v1/admin/payment-platform/provider-statements/{statementNo}查看账单批次详情和账单行明细
POST/api/v1/admin/payment-platform/provider-statements/import导入 CSV 账单内容并触发批量对账;需要 payment-platform:statement:import 权限
POST/api/v1/admin/payment-platform/provider-statements/pull通过 Provider 拉取账单内容并触发批量对账;需要 payment-platform:statement:pull 权限

拉取请求可以显式传入 providerproviderAccountIdperiodStartperiodEndstatementNoremark。未传 Provider 时读取配置中心 spring.open.payment-platform.statement-pull.default-provider;未传账期起始时间时按 spring.open.payment-platform.statement-pull.default-window-days 计算默认窗口。内置 mock Provider 会把本地结算批次转换成 CSV 内容,用于开发和 CI 验证拉账 contract;真实服务商 SDK、凭据托管、调度频率和失败补偿需要在具备真实服务商账号后按部署环境验收。

CSV 字段约定如下,首行表头可省略:

text
settlementNo,providerAmount,currency,transactionId,providerTransferNo,message
  • settlementNo 必填,对应平台结算批次号。
  • providerAmount 必填,按服务商账单金额和平台结算净额比对。
  • currency 为空时使用平台结算资产币种;不一致时进入差异。
  • transactionIdproviderTransferNomessage 用于排障和审计,不参与金额计算。
  • 行金额一致时,平台结算批次会标记为 matched
  • 行金额差异、币种不一致、批次不存在或金额格式错误时,账单行进入 difference / manual_review;未入账批次会同步转人工复核并冻结风险,已入账批次只冻结风险,不回滚 Money 余额流水。

Webhook 投递后台入口用于查看商户回调是否成功送达,并支持把失败或待处理记录重新放回投递队列:

方法路径说明
GET/api/v1/admin/payment-platform/webhook-deliveries分页查询 Webhook 投递,可按关键字、商户、商户应用、事件类型、状态和 Trace 过滤
POST/api/v1/admin/payment-platform/webhook-deliveries/{id}/retry将失败或待处理的投递重新放回 Runtime Outbox

开放支付 API

开放支付 API 走 Open Platform 访问令牌、签名、scope、限流和调用日志。Payment Platform 只校验商户和商户应用状态,并把支付单落到 Cashier;它不直接保存支付服务商密钥,也不重写 Payment starter 的 Provider 逻辑。

方法路径Scope说明
POST/api/v1/open/payment/ordersopen:payment:order:create创建商户支付单。merchantOrderNo 在同一商户应用内幂等,重复请求返回既有映射
GET/api/v1/open/payment/orders/{merchantOrderNo}open:payment:order:read查询商户支付单和 Cashier 支付单状态快照
POST/api/v1/open/payment/orders/{merchantOrderNo}/closeopen:payment:order:close关闭 pending / processing 状态支付单,并同步关闭 Cashier 订单
POST/api/v1/open/payment/orders/{merchantOrderNo}/quotesopen:payment:quote:create创建或复用当前有效报价,返回定价资产、支付资产、汇率、有效期和报价编号
GET/api/v1/open/payment/orders/{merchantOrderNo}/quotes/activeopen:payment:quote:read查询当前有效报价;没有有效报价时返回 null
POST/api/v1/open/payment/orders/{merchantOrderNo}/refundsopen:payment:refund:create创建商户退款请求。merchantRefundNo 在同一商户应用内幂等,重复请求返回既有退款单
GET/api/v1/open/payment/orders/{merchantOrderNo}/refunds/{merchantRefundNo}open:payment:refund:read查询商户退款单状态快照
GET/api/v1/open/payment/settlementsopen:payment:settlement:read查询当前商户应用的结算批次
GET/api/v1/open/payment/settlements/{settlementNo}open:payment:settlement:read查询当前商户应用的结算批次和订单明细

创建支付单的关键字段:

字段说明
merchantOrderNo商户侧订单号;同一商户应用内必须唯一
subject / body支付主题和说明
amount / currency支付金额和币种;币种默认 CNY
priceAssetCode定价资产编码;为空时沿用 currency,支持 CNYUSDTfiat:CNYcrypto:USDT 等输入并归一为稳定资产编码
allowedPayAssetCodes允许支付资产编码列表;为空表示不限制,报价创建时会校验支付资产是否在列表内
preferredPayAssetCode首选支付资产编码;创建订单后会自动创建 / 复用一条 Cashier quote,必须命中 allowedPayAssetCodes
provider / scene支付服务商和业务场景;provider 可显式指定,也可留空交由通道路由选择
payerId / notifyUrl / traceId支付人、商户通知地址和追踪标识

开放报价规则:

  • priceAssetCode 是商户商品或服务的定价资产编码;currency 保持支付渠道友好的币种字段,后端会把两者归一为稳定 AssetCode
  • 报价请求的 payAssetCode 为空时默认使用定价资产,跨资产支付会通过 Cashier quote 接入 Money 汇率 Provider。
  • allowedPayAssetCodes 只做商户订单级支付资产准入,不保存汇率,不替代 Money 资产目录启停和 Provider 可用性判断。
  • quote 响应包含 quoteNobaseAmountbaseSymbolpayAmountpaySymbolraterateProviderrateSnapshotAtexpiresAtstatustraceId
  • 支持的典型组合包括法币定价 / 法币支付、法币定价 / Web3 Token 支付、Token 定价 / 法币支付、Token 定价 / Token 支付;同资产支付也生成 rate=1 的 quote,便于 webhook、退款和结算统一审计。

用户侧收银台体验:

  • App / PC 统一收银台会读取 Cashier 订单 attributes 中的 allowedPayAssetCodespreferredPayAssetCode,展示可选支付资产、应付金额、定价金额、汇率和有效期。
  • 用户切换支付资产会重新创建 quote;点击刷新会按当前资产组合刷新 quote。过期 quote 不允许提交支付,需要用户明确刷新。
  • 支付提交会携带 quoteNopayAssetCode,Cashier 会在调用 Payment Provider 前校验 quote 归属、状态、过期时间和支付资产。
  • Admin Cashier 支付订单详情会展示 quote 快照;Payment Platform 订单和 webhook 继续以 ledgerSnapshotJson / ledgerSnapshot 暴露商户侧审计字段。

通道路由规则:

  • 通道必须为 enabled,健康状态必须为 healthyunknown
  • 通道 assetCode 必须匹配订单币种对应的资产编码,例如 CNY 对应 fiat:CNY
  • minAmount / maxAmount 为空时不限制;有值时订单金额必须落在区间内。
  • 多个通道可用时按 priority 升序、id 升序选择。
  • 调用方显式传 provider 时,只在该服务商通道内选择;如果该服务商已经配置通道但全部不可用,则创建订单失败并返回通道路由不可用错误。
  • 调用方显式传 provider 且后台尚未配置该服务商通道时,为兼容历史接入,会继续按该 provider 创建 Cashier 订单。
  • 被选中的通道、支付方式、健康状态和启用的服务商账号编码会写入 Cashier 订单 attributes,供后续 Provider 适配、对账和审计使用。

创建退款的关键字段:

字段说明
merchantRefundNo商户侧退款号;同一商户应用内必须唯一
amount本次退款金额;不可超过支付单剩余可退金额
reason / traceId / extraJson退款原因、追踪标识和商户扩展 JSON

开放退款会通过 PaymentManager.refund(...) 调用 Cashier 订单上的 Provider,退款请求携带商户 / 商户应用 / 平台订单 / Cashier 订单 / 退款单、Provider、原交易号、金额、币种、通知地址、traceId 和商户扩展 JSON。若 Cashier 支付单 attributes 带有 quote 快照,Provider 请求金额会按 refundAmount / baseAmount * payAmount 计算,币种使用原支付资产;attributes 同步携带 quoteNo、定价资产、支付资产、汇率和 Provider 退款金额。Provider 返回 refunded / success 时会累计 Cashier 与平台支付单退款金额、写入退款成功时间,并生成 refund.succeeded webhook;返回 pending / processing 时只保留退款单和 Provider 快照,等待 Cashier 退款 handler 补偿完成;返回 failed 时记录失败状态和服务商消息,不推进订单退款金额。

退款失败不会修改 Cashier / Payment Platform 已退款金额,也不会改写订单结算账本快照或发送 refund.succeeded webhook;后续如果服务商通过异步回调确认退款成功,仍由 Cashier 退款 handler 按同一平台订单补偿完成。

支付单返回值包含商户订单号、平台支付单号、金额、币种、结算资产编码、原始应收金额、平台手续费率、平台手续费金额、商户应收净额、账本快照 JSON、Provider、Scene、状态、交易号、支付人、支付成功时间、已退款金额、最近退款时间、通知地址、追踪标识和关闭时间。账本快照 JSON 会在存在 quote 时包含 quoteNobaseAmountbaseSymbolpayAmountpaySymbolraterateProviderrateSnapshotAt。退款返回值包含商户订单号、商户退款号、退款金额、币种、状态、Provider、服务商退款号、服务商退款状态、服务商消息、交易号、退款原因、追踪标识和退款成功时间。

开发者签名示例

开放支付 API 复用 Open Platform HMAC 签名规则。商户服务端必须先拿到已启用的 Open Platform 应用、明文 AppSecret、已授权的支付 scope 和已启用的 Payment Platform 商户应用,再从自己的后端发起调用;浏览器、小程序或移动 App 不应持有长期 AppSecret

固定 smoke 夹具:

text
appKey = app_payment_platform_sample
appSecret = secret_payment_platform_sample
method = POST
path = /api/v1/open/payment/orders
query =
timestamp = 1779379200000
nonce = nonce-payment-sdk-001

请求体必须按实际发送的原始 JSON 字节参与 SHA-256;下面是一份和测试夹具一致的单行 JSON:

json
{"merchantOrderNo":"merchant-order-sdk-001","subject":"Payment Platform SDK sample order","body":"Smoke request for open payment API","amount":19.90,"currency":"CNY","provider":"mock","scene":"web","payerId":"user-10001","notifyUrl":"https://merchant.example.com/webhooks/payment","traceId":"trace-sdk-001"}

对应 body sha256

text
3158dcf8585aa259a89e2e976d17ee78437b9f2d4f133ea206c31ffe467b8682

canonical string:

text
POST
/api/v1/open/payment/orders

1779379200000
nonce-payment-sdk-001
3158dcf8585aa259a89e2e976d17ee78437b9f2d4f133ea206c31ffe467b8682

签名 key 为 SHA-256(AppSecret),再对 canonical string 做 HMAC-SHA256,hex 输出:

text
c8409877fa5ba8a1149c2a3e7d3783de736a0e1ac3a6ce3e7cdcf53e30fa6aec

查询当前有效报价的固定 smoke 夹具:

text
method = GET
path = /api/v1/open/payment/orders/merchant-order-sdk-001/quotes/active
query =
timestamp = 1779379202000
nonce = nonce-payment-sdk-quote-001
body sha256 = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
signature = b9b4268c6f2bae2d5cd8ff5c9ca637087022f6d10657f0ff01ee7018def54c99

最小调用示例:

bash
curl -X POST 'http://localhost:8000/api/v1/open/payment/orders' \
  -H 'Content-Type: application/json' \
  -H 'X-Open-App-Key: app_payment_platform_sample' \
  -H 'X-Open-Timestamp: 1779379200000' \
  -H 'X-Open-Nonce: nonce-payment-sdk-001' \
  -H 'X-Open-Signature-Method: HMAC-SHA256' \
  -H 'X-Open-Signature: c8409877fa5ba8a1149c2a3e7d3783de736a0e1ac3a6ce3e7cdcf53e30fa6aec' \
  -H 'X-Open-Idempotency-Key: merchant-order-sdk-001' \
  --data '{"merchantOrderNo":"merchant-order-sdk-001","subject":"Payment Platform SDK sample order","body":"Smoke request for open payment API","amount":19.90,"currency":"CNY","provider":"mock","scene":"web","payerId":"user-10001","notifyUrl":"https://merchant.example.com/webhooks/payment","traceId":"trace-sdk-001"}'

Java / Node SDK 封装可直接复用 Open Platform 签名示例SHA-256(AppSecret) + canonical string + HMAC-SHA256 规则。仓库内 PaymentPlatformOpenApiSdkSmokeTest 会锁定上面的下单 / quote 查询 body hash、canonical string、signature 和支付 scope,避免文档示例和真实签名工具漂移。

商户控制台 API

商户控制台走当前登录用户身份,只返回 payment_platform_merchant.contact_user_id 等于当前用户 ID 的商户数据。该 API 是商户自助读侧,不暴露管理员审核字段以外的敏感操作,也不返回密钥明文、secretRef、webhook payload 或 headers。

方法路径说明
GET/api/v1/payment-platform/merchant-console/overview查询当前用户商户、商户应用、应用密钥摘要和最近订单 / 退款 / 结算 / 回调
GET/api/v1/payment-platform/merchant-console/orders分页查询当前用户商户支付订单,支持 merchantIdmerchantAppIdstatuskeyword
GET/api/v1/payment-platform/merchant-console/refunds分页查询当前用户商户退款单,支持相同过滤条件
GET/api/v1/payment-platform/merchant-console/settlements分页查询当前用户商户结算批次,支持相同过滤条件
GET/api/v1/payment-platform/merchant-console/webhooks分页查询当前用户商户回调投递日志,支持相同过滤条件

商户应用返回 openPlatformAppKeysecretMasked 便于开发者核对接入信息;密钥引用和明文密钥只留在 Secret / Open Platform 侧。若请求传入不属于当前用户的 merchantIdmerchantAppId,接口返回空分页,避免通过过滤条件探测其他商户数据。

结算批次

结算批次把订单级账本快照汇总成待结算批次,并可由后台释放到商户负责人 Money 余额、失败重试、人工复核、对账差异标记和风控冻结 / 释放。Payment Platform 只保存结算快照、释放状态、对账状态和风控状态;商户资金外流统一走 Money 余额提现,不在业务模块内自建复杂出账模型。

生成规则:

  • 商户应用必须启用,商户必须已审核通过。
  • 支付单必须是 success 状态。
  • 支付单必须未进入其他结算批次。
  • 支付单必须没有退款金额。
  • 支付成功时间必须早于商户 settlementDelayDays 或模块默认 default-settlement-delay-days 计算出的结算截止时间。
  • 单个批次只聚合同一结算资产订单,避免 CNY、USDT 等不同支付资产在同一批次混合出款。

结算状态:

状态说明
pending已生成待结算批次,等待后续余额入账释放
payouting正在执行余额入账释放
manual_review人工复核中,暂停自动重试
paid商户应收已入商户负责人 Money 余额,批次和明细已回写完成
failed余额入账失败,记录失败摘要并设置下次重试时间

对账状态:

状态说明
pending待对账
matched对账一致
difference存在差异,会同步冻结批次并进入人工复核
manual_review对账结果需要人工处理

风控状态:

状态说明
normal未触发风控
frozen已冻结,禁止释放和重试释放
released冻结已释放,可继续待入账流程

开放查询返回批次号、商户、商户应用、结算资产、原始应收总额、平台手续费总额、商户应结算净额、订单数、状态、对账状态、风控状态、重试摘要、生成时间、入账时间和明细列表。后台释放结算时按批次号生成 Money 幂等键,将商户原始应收写入 RECHARGE,将平台手续费写入 ADJUST_OUT;两笔账本写入成功后批次 / 明细 / 订单结算状态同步变为 paid,账本异常进入 failed 并设置下次重试时间。当对账结果为 difference 或后台人工冻结时,批次进入 manual_reviewfrozen;冻结期间 payout / retry-payout 会返回风控冻结错误,释放冻结后人工复核批次会恢复为 pending

Money 账本保持两笔最小语义:释放结算时写入商户原始应收 RECHARGE 和平台手续费 ADJUST_OUT;幂等键为 payment-platform:settlement:<action>:<settlementNo>。若批次内订单 quote 汇率快照一致,Money 账本命令会携带 exchangeRaterateProviderrateSnapshotAt,便于后续按定价资产和支付资产追溯。后台标记对账时可打开 Provider 核验开关;已入账批次自动 matched,未入账批次进入 difference 并冻结风险。服务商账单导入使用同一对账状态机,可批量把账单行匹配结果回写到结算批次。已 paid 批次若账单导入出现差异,只冻结风险并保留已完成入账状态,不回滚已写入的 Money 余额流水;后续人工处理通过风控复核和补偿流水闭环。

商户 Webhook

Payment Platform 会先写模块内 webhook delivery 记录,再写入 Runtime Outbox。Runtime Webhook 投递器启用后会异步发起 HTTP 请求;写入时优先使用订单 notifyUrl,为空时回退到商户应用默认 webhookUrl

事件触发时机
payment.succeededCashier 支付成功 handler 同步平台支付单后写入
payment.closed开放支付关闭接口同步关闭 Cashier 订单后写入
refund.succeededProvider 退款已完成,或 Cashier 退款 handler 补偿完成退款快照后写入

payment_platform_webhook_delivery 会记录 eventIdeventType、目标 URL、JSON payload、基础 headers、attemptCountmaxAttemptsnextRetryAt。payload 会同步携带 settlementSymbolgrossAmountplatformFeeRateplatformFeeAmountmerchantReceivableAmountledgerSnapshot,存在 quote 时 ledgerSnapshot 会包含定价资产、支付资产、汇率和报价编号,便于商户侧按平台账本口径核对订单。Runtime Outbox 使用 consumer=payment-platform-webhook 触发投递;成功后回写 succeeded、HTTP 状态和最后投递时间,失败后回写 failed、失败摘要和下一次重试时间。

投递请求会保留兼容业务 header:

http
X-SpringOpen-Event-Id: payment-platform-payment-succeeded-sdk-001
X-SpringOpen-Event-Type: payment.succeeded
X-SpringOpen-Signature-Version: v1

Runtime 通用投递器会补齐标准 header:

http
X-Spring-Open-Webhook-Event-Id: payment-platform-payment-succeeded-sdk-001
X-Spring-Open-Webhook-Event-Type: payment.succeeded
X-Spring-Open-Webhook-Aggregate-Type: payment-platform.webhook-delivery
X-Spring-Open-Webhook-Timestamp: 1779379201000
X-Spring-Open-Webhook-Body-Sha256: 0d366cd3aeec0b1265536630f4976137bfe03a0a24823be35c242c98e330b37f
X-Spring-Open-Webhook-Signature: sha256=38544000cf0f65ef38d7e50a88e9b5b0588727094483e0ebc9b4b898a69c1a2b

Runtime Webhook 验签规则:

text
bodyHash = SHA-256(rawRequestBody)
signingPayload = method + "\n" + path + "\n" + query + "\n" + timestamp + "\n" + eventId + "\n" + bodyHash
signature = "sha256=" + HMAC-SHA256(signingPayload, webhookSecret)

固定验签夹具:

text
webhookSecret = webhook_secret_payment_platform_sample
bodyHash = 0d366cd3aeec0b1265536630f4976137bfe03a0a24823be35c242c98e330b37f
signingPayload = POST\n/webhooks/payment\n\n1779379201000\npayment-platform-payment-succeeded-sdk-001\n0d366cd3aeec0b1265536630f4976137bfe03a0a24823be35c242c98e330b37f
signature = sha256=38544000cf0f65ef38d7e50a88e9b5b0588727094483e0ebc9b4b898a69c1a2b

商户侧验签必须使用 HTTP 原始请求体字节,不要重新格式化 JSON。生产环境还应拒绝超出时间窗口的 timestamp、未知事件 ID、body hash 或签名不匹配的请求。Payment Platform 不在业务表保存 webhook 明文密钥;需要签名时通过 spring.open.outbox.webhook.endpoints.payment-platform.secret 或默认 Runtime Webhook endpoint secret 配置。

应用装配与验证

装配护栏由 PaymentPlatformApplicationAssemblyTest 覆盖:

  • spring-open-modules/pom.xml 必须包含 spring-open-module-payment-platform
  • spring-open-application/pom.xml 必须显式依赖 spring-open-module-payment-platform
  • 配置前缀、Admin API 前缀和默认安全配置保持稳定。
  • 首批权限码、结算查看 / 出款权限码、开放支付 API 前缀、商户控制台 API 前缀和开放支付 scope 保持稳定,避免菜单 / API contract 漂移。

迁移护栏由 PaymentPlatformMigrationTest 覆盖,校验表、权限、菜单、Open Platform 应用绑定字段、密钥引用字段、开放支付单表、订单账本快照字段、退款单表、webhook delivery 表、结算批次 / 明细表、服务商账单导入表、结算对账风控字段、开放支付 scope 和可移植 Liquibase 写法。

后续功能簇

  1. 真实服务商定时拉账验收:当前已具备 Provider 拉取 contract、mock Provider、后台手动拉取入口和批量对账复用链路;真实服务商账单拉取需要按 Provider 凭据、频率、账期窗口和失败补偿接入定时任务,不能在无真实服务商账号时伪装完成。
  2. 分账规则:需要多收款方结算时再新增规则配置,仍复用 Money 账本和统一余额提现,不在 Payment Platform 自建余额体系。

Released under the Apache License 2.0.