☰
收银核心:支付系统中承上启下的决策中枢与安全防线
2026/10/24 22:02:12 网站建设 项目流程

简介:本资源是《百图解码支付系统设计与实现》专栏第11篇的深度专题PDF,聚焦“收银核心”这一承上启下的关键应用,面向支付领域初/中级研发工程师、架构师及金融科技方向学习者,系统解决支付系统前端交互层“如何动态组装并安全受理支付方式”的核心问题。全书以手绘图解为核心表达方式,覆盖支付咨询、支付受理、风控协同、多渠道路由(快捷支付、VA、OTC、网银等)、额度校验、领域模型与系统架构集成等实战模块,内容源自作者十余年一线支付架构经验,兼顾入门理解与高阶设计参考。资源为单文件PDF,共1个5.2MB文档,图示丰富、章节清晰,适合作为支付系统学习地图中的关键拼图与团队内部技术分享材料。目前已有101人学习下载。

1. 图解收银台:为什么说它是支付系统里最易被低估、却最不能出错的“门面担当”?

你有没有遇到过这样的场景:用户在电商 App 上点击“立即支付”,页面卡在收银台加载动画长达 8 秒,最终弹出“系统繁忙,请稍后再试”;或者更糟——用户选好了微信支付,点确认后跳转失败,再返回时发现订单已超时关闭,但银行卡却莫名扣款成功?这类问题背后,90% 不是支付引擎崩了,而是收银核心在咨询或受理阶段悄悄掉了链子。这份《图解收银台:支付系统承上启下的关键应用》PDF,不是讲高大上的分布式事务或风控模型,而是聚焦一个真实、高频、且极易被架构师忽略的子系统:收银核心。它不直接扣钱,却决定用户“能不能付、付得清不清、付得顺不顺”;它不处理资金流,却承载着 70% 以上的前端支付异常归因。全文用 11 张手绘风格架构图+领域模型图+流程对比图,把“支付咨询→支付受理→结果同步”三阶段拆解到字段级逻辑,覆盖快捷支付、VA、OTC、正反扫等 8 类主流支付方式的适配边界。适合正在设计收银台模块的后端工程师、支付中台建设者,以及想穿透支付黑匣子理解“为什么明明调通了支付引擎接口,用户还是付不了”的一线研发同学。它不教你怎么写 Spring Boot,而是告诉你:当用户余额为 0 但绑了信用卡时,该不该把“信用卡支付”排在第一位?当 VA 码生成后银行回调延迟 3 分钟,轮询策略怎么设才不漏单?这些答案,就藏在每一张图的箭头粗细、虚实和标注文字里。

2. 收银核心不是“前端页面”,而是支付系统的决策中枢:从协同定位到能力边界

2.1 收银核心与支付引擎的职责切分:谁该做、谁不该碰?

很多团队在初期会把收银台简单理解为“调支付引擎接口的前端代理”,这是导致后续大量耦合和故障的根本误判。实际上,收银核心与支付引擎之间存在清晰的契约边界,这个边界不是靠文档约定,而是由数据流向和状态机驱动的。我们来看一个典型订单生命周期中的三次关键交互:

阶段主导方核心动作数据主权典型失败影响
支付咨询(用户点击“去支付”后)收银核心汇总会员余额、绑卡状态、风控白名单、渠道开关、额度余量,组装可选支付方式列表收银核心持有全部查询结果缓存,但不持久化用户看到“暂不支持该支付方式”,实际是风控规则配置错误,而非渠道不可用
支付受理(用户点击“确认支付”后)收银核心重新执行全量检查(非复用咨询结果),触发风控挑战(如短信验证),调用渠道路由服务选择通道,封装请求体发往支付引擎支付引擎仅接收标准化请求体(含唯一 trace_id),不读取任何用户资产数据订单已关闭但收银核心未重验,导致支付引擎成功扣款后被收单平台自动退款,用户投诉“钱没了货没到”
结果同步(支付引擎返回 success 后)收银核心不信任支付引擎返回值,启动对收单平台的轮询(含幂等重试、指数退避),解析收单平台回调通知,更新本地订单状态收单平台是唯一权威结果源,收银核心必须以收单平台状态为准支付引擎返回 success,但收单平台因银行清算失败回滚,若收银核心直接信了引擎,就会给用户错误发货

提示:这个表格不是理论推演,而是某跨平台系统上线首月 47 起支付客诉的根因分类统计。其中 32 起(68%)源于收银核心越界——要么把本该实时查的数据缓存太久,要么把本该由收单平台确认的结果提前落库。

2.2 收银核心的四大不可替代能力:为什么不能用“前端直连支付引擎”替代?

有些团队试图绕过收银核心,让前端 SDK 直接调用支付引擎(如微信 JSAPI、支付宝 AlipayJSBridge),理由是“减少一层转发”。这种做法在 Demo 阶段看似可行,但在生产环境必然翻车。原因在于收银核心承担着四个支付引擎根本无法也不应承担的能力:

  • 支付方式动态组装能力:同一笔订单,A 用户看到“微信支付、余额支付、信用卡”,B 用户却只看到“信用卡、VA 支付”。这不是简单的 if-else,而是基于 5 维实时数据的笛卡尔积计算:① 用户等级(影响免密额度)② 绑卡类型(借记卡/信用卡/虚拟卡)③ 当前时间(影响风控策略)④ 订单金额(触发不同渠道费率)⑤ 商户签约状态(某银行渠道仅对 VIP 商户开放)。支付引擎只认“channel_code=wechat”,而收银核心要算出“对当前用户,wechat 是否可用、是否推荐、是否需前置验证”。

  • 多源异步数据聚合能力:一次支付咨询需并行调用至少 6 个下游服务:会员中心(查余额)、卡中心(查绑卡)、风控中心(查设备指纹)、商服(查商户资质)、渠道网关(查通道开关)、营销中心(查红包)。这些服务 SLA 差异极大(风控中心 P99=200ms,营销中心 P99=1.2s),收银核心必须实现:① 设置差异化超时(风控 300ms 强制熔断,营销 1500ms 可降级)② 容忍部分服务失败(如营销未返回,仍可返回支付方式,仅不展示红包)③ 对降级结果打标(pay_methods[].is_marketing_applied = false)供前端灰度。

  • 状态强一致性保障能力:支付受理阶段的“重验”不是简单重跑一遍咨询逻辑。它必须保证:① 订单状态(未支付/已关闭/已退款)与数据库最新快照一致 ② 用户身份(登录态 token)与会话服务校验通过 ③ 所有资产数据(余额、绑卡)使用强一致性读(非缓存)④ 风控检查使用最新策略版本(避免缓存旧规则)。支付引擎没有这些上下文,也无法访问这些数据源。

  • 渠道语义翻译能力:支付引擎返回的{"code":"SUCCESS","msg":"OK"}对用户毫无意义。收银核心要把它翻译成用户能懂的语言:“支付成功,预计 1 分钟内到账” 或 “银行处理中,请 5 分钟后查看订单状态”。更关键的是,它要识别出支付引擎返回的{"code":"BANK_TIMEOUT"}实际对应收单平台的ORDER_EXPIRED,从而触发正确的业务补偿(如自动释放库存、通知用户重试)。

2.3 收银核心在整体支付架构中的位置:承上启下的三层拦截网

翻开 PDF 第 7 页的系统架构图,你会看到收银核心像一道精密的“流量过滤网”,横亘在用户请求与支付引擎之间,形成三层拦截:

  • 第一层:协议转换层
    接收前端 HTTP 请求(如POST /api/v1/pay/consult),统一鉴权(OAuth2.0 + 商户 API Key)、参数校验(金额格式、币种合法性)、日志埋点(trace_id 注入)。这一层屏蔽了前端技术栈差异(App/H5/小程序),输出标准化内部 DTO。

  • 第二层:决策引擎层
    这是真正的“智囊团”。它不写代码,而是运行一套可配置的规则引擎(PDF 中图 8 的领域模型即为此层核心)。例如:

    # 伪代码:支付方式可用性判定规则(简化版) def is_payment_method_available(user, order, method): if method == "wechat": return (user.is_wechat_bound and order.amount <= user.wechat_daily_limit and not风控.is_risk_user(user.device_id)) elif method == "va": return (order.currency == "IDR" # 仅印尼盾支持 VA and order.amount >= 10000 # VA 最低 1 万印尼盾 and bank_gateway.is_va_supported("bni")) # BNI 银行通道开启 # ... 其他 6 种支付方式规则

    规则引擎的输入来自第一层聚合的数据,输出是带权重的支付方式列表([{"code":"wechat","weight":0.9},{"code":"va","weight":0.7}])。

  • 第三层:结果仲裁层
    接收支付引擎的原始响应,但不直接透传。它要:① 校验签名(防篡改)② 映射状态码(将ENGINE_SUCCESS→PENDING,因需轮询收单平台)③ 注入业务语义(如PENDING状态下,根据渠道特性返回不同提示文案)④ 触发异步事件(如PayConsultSuccessEvent用于审计、PayMethodRecommendedEvent用于个性化推荐)。

这三层不是线性调用,而是环形反馈:第三层发现某渠道频繁超时,会动态降低其weight值;第二层规则引擎的阈值(如日限额)可由运营后台实时调整,无需重启服务;第一层的日志会沉淀为风控模型的训练数据。这种设计,让收银核心真正成为支付系统的“活”中枢,而非静态胶水层。

3. 支付咨询阶段:如何在 300ms 内完成 6 个异构服务的聚合与决策?

3.1 支付咨询的六步原子操作:从基础检查到排序推荐

PDF 第 5 页将支付咨询拆解为六个不可跳跃的步骤,每个步骤都对应一个明确的失败码和降级策略。这不是理想化的流程图,而是某支付中台 SLO 为 99.95% 的线上系统真实执行路径。我们逐条还原其技术实现要点:

  • 步骤 1:基础检查(Base Check)
    核心是三个“是否存在”校验:① 订单是否存在且状态为UNPAID(查订单库,强一致性读)② 用户是否登录且 token 有效(调会员中心/user/validate,超时 200ms)③ 商户是否签约且通道未冻结(查商服/merchant/status?mid=xxx,超时 300ms)。

    关键参数:base_check_timeout=300ms,任意一项超时或失败,直接返回ERR_ORDER_INVALID,不进入后续步骤。这是防止无效请求击穿下游的首道闸门。

  • 步骤 2:资产咨询(Asset Inquiry)
    并行调用:① 会员中心查余额(/account/balance?uid=xxx)② 卡中心查绑卡(/card/bound?uid=xxx)③ 营销中心查红包(/coupon/available?uid=xxx&order_id=yyy)。三者设置不同超时:余额(150ms,强依赖)、绑卡(250ms,弱依赖)、红包(1200ms,可降级)。

    关键设计:采用CompletableFuture.allOf()实现并行,但用orTimeout()为每个 future 单独设超时。若红包超时,asset_result.coupons = [],不影响其他资产返回。

  • 步骤 3:渠道咨询(Channel Inquiry)
    调用渠道网关/channel/available,传入{"currency":"CNY","amount":29900,"country":"CN"}。网关返回{ "wechat":true, "alipay":true, "va":false }。注意:此处va:false是因为country!="IDR",而非银行通道关闭。

    关键参数:channel_inquiry_cache_ttl=60s,因渠道开关变更频率低,但必须设置 TTL 避免脏数据。缓存 key 为channel:avail:{currency}:{amount_bucket}(金额按 100 元分桶)。

  • 步骤 4:额度咨询(Quota Inquiry)
    调用风控中心/risk/quota/check,传入{"uid":"u123","channel":"wechat","amount":29900}。风控返回{"daily_used":150000,"daily_limit":200000,"can_pay":true}。

    关键逻辑:can_pay为 false 时,该渠道直接从列表剔除,不参与后续排序。但需记录quota_rejected_channels=["wechat"]用于问题排查。

  • 步骤 5:支付方式组装(Assembly)
    将前四步结果融合,生成结构化支付方式对象:

    { "code": "wechat", "name": "微信支付", "icon": "https://cdn.xxx/wechat.png", "balance_deduction": 0, "is_default": true, "is_recommend": true, "marketing_tip": "使用微信支付立减 5 元" }

    关键规则:is_default=true仅当wechat在可用列表中且user.preferred_channel=="wechat";is_recommend=true需同时满足:①weight > 0.8②is_default || (user.is_new && channel=="wechat")(新用户首推微信)。

  • 步骤 6:支付方式排序(Sorting)
    使用加权排序算法,权重公式为:
    weight = base_weight * (1 + risk_score_factor) * (1 + marketing_factor)
    其中base_weight由渠道类型决定(微信 0.9、支付宝 0.85、VA 0.7),risk_score_factor来自风控返回的risk_score(0~1,越高权重越低),marketing_factor来自营销中心的活动权重(如双 11 期间微信权重 +0.2)。

    关键参数:sort_algorithm="weighted",禁用纯随机排序(random),因会影响营销 ROI;排序后强制置顶is_default=true的渠道。

3.2 高并发下的性能压测与瓶颈定位:为什么 300ms 是生死线?

PDF 第 7 页架构图右下角标注了“咨询接口 P99 < 300ms”,这不是拍脑袋定的指标,而是基于用户行为数据的硬约束:用户从点击“去支付”到看到收银台,心理容忍极限是 1.2 秒;其中网络传输占 400ms,前端渲染占 200ms,留给后端只有 600ms;而咨询必须在用户看到页面前完成(否则显示“加载中”),故目标定为 300ms。我们用 JMeter 对某次上线前压测结果做归因分析:

组件P99 延迟占比瓶颈原因优化方案
渠道网关180ms42%同步调用银行通道状态接口,未加缓存改为异步刷新缓存 + 本地 Guava Cache(TTL=30s)
风控中心120ms28%每次都查全量风控规则,未按用户分片引入 Redis BloomFilter 快速判断“高风险用户”再查库
会员中心65ms15%查询余额走主库,未读写分离增加从库读取,容忍 1s 延迟
卡中心45ms10%绑卡列表返回冗余字段接口改造,只返回card_type和last_four
营销中心20ms5%降级策略生效,未计入主路径保持现状,因降级后 P99 降至 5ms

关键教训:不要迷信“所有服务都优化到 50ms 就行”。实际瓶颈永远在最慢的那个环节。我们曾花两周优化卡中心到 20ms,但渠道网关仍是 180ms,整体 P99 毫无改善。后来只用 2 天给渠道网关加缓存,P99 直降 110ms。这就是“木桶效应”在支付咨询中的血泪体现。

3.3 支付咨询的避坑指南:五个让你半夜被电话叫醒的真实故障

现象 1:用户 A 在 10:00:00 看到“微信支付可用”,10:05:00 点击支付时提示“该支付方式暂不可用”

原因:支付咨询结果被缓存了 5 分钟,但用户资产(如余额)或风控状态(如设备风险分)可能秒级变化。咨询阶段的“可用”只是瞬时快照,受理阶段必须重验。
解决:严格禁止在咨询结果中缓存任何时效性数据(余额、风控分、订单状态)。咨询接口返回的available_at字段必须是当前时间戳,前端显示“该信息截至 XXXX-XX-XX XX:XX:XX 有效”。

现象 2:营销中心超时,导致整个支付咨询失败,用户无法进入收银台

原因:错误地将营销中心设为强依赖,allOf()中任一 future 失败即中断整个流程。
解决:使用CompletableFuture.anyOf()+exceptionally()为每个 future 单独兜底。营销超时则coupons=[],不影响其他资产返回,并记录marketing_unavailable=true埋点。

现象 3:VA 支付方式在咨询时显示“可用”,但用户点击后生成 VA 码失败

原因:渠道咨询只查了“VA 是否支持”,但未校验“当前银行通道是否可用”(如 BNI 银行维护)。VA 的可用性需两级校验:① 国家/币种支持(咨询阶段)② 银行通道实时状态(受理阶段生成码时)。
解决:在支付受理阶段,调用bank_gateway/va/generate前,先查bank_gateway/status?bank_code=bni,失败则返回ERR_CHANNEL_UNAVAILABLE并引导用户换渠道。

现象 4:新用户首单,微信支付排序第一,但用户实际绑的是支付宝,导致支付率下降

原因:is_default逻辑只看用户历史偏好,未结合本次订单特征。新用户无历史,preferred_channel为空,导致默认规则失效。
解决:新增规则:if user.is_new and order.amount < 5000 then default_channel = "wechat"(小额首推微信),该规则写入决策引擎配置中心,可热更新。

现象 5:咨询接口返回 500,日志显示NullPointerException,但所有下游服务都正常

原因:代码中某处用了assetResult.getCoupons().get(0).getAmount(),当coupons为空列表时,get(0)抛 NPE。未对降级后的空集合做防御性编程。
解决:所有外部数据访问必须加Optional包装或空集合校验。在咨询流程入口增加@Valid注解,对 DTO 字段做@NotNull、@Min(1)等校验,将错误拦截在最外层。

4. 支付受理阶段:为什么“重验”不是形式主义,而是支付安全的生命线?

4.1 支付受理的五步严苛流程:从二次校验到收单结果仲裁

PDF 第 6 页强调:“支付受理 ≠ 调支付引擎”,它是一套独立的状态机,其严谨性远超咨询阶段。我们拆解其五步执行逻辑,重点看每一步为何不可省略:

  • 步骤 1:全量重验(Full Re-check)
    不是简单重跑咨询逻辑,而是以受理时刻为快照,重新拉取所有数据:① 订单库查最新状态(SELECT * FROM orders WHERE id=? FOR UPDATE,加行锁防并发)② 会员中心查实时余额(/account/balance?uid=xxx&force_refresh=true)③ 风控中心查最新设备分(/risk/score?device_id=xxx&timestamp=now)。

    关键设计:FOR UPDATE锁住订单行,确保在受理过程中订单状态不被其他进程修改(如客服手动关闭订单)。若锁等待超时(300ms),直接返回ERR_ORDER_LOCKED。

  • 步骤 2:风控挑战(Risk Challenge)
    根据风控中心返回的risk_level(LOW/MEDIUM/HIGH)触发不同挑战:

    • LOW:无挑战,直接进入下一步
    • MEDIUM:要求短信验证(调短信网关/sms/send?phone=xxx)
    • HIGH:要求人脸识别(调活体检测 SDK)

    关键参数:challenge_timeout=120s,超时未完成则终止受理,释放订单锁。挑战过程不改变订单状态,仅生成challenge_id供后续关联。

  • 步骤 3:渠道路由(Channel Routing)
    对于外部渠道(如 Visa 卡支付),需从多个银行通道中选最优路径。路由策略包括:① 费率最低(fee_rate<0.6%)② 成功率最高(近 1 小时success_rate>99.5%)③ 延迟最低(p95_latency<800ms)。

    # 示例:路由决策伪代码 candidates = get_all_visa_channels() # 获取所有 Visa 通道 filtered = filter_by_fee(candidates, max_fee=0.006) ranked = sort_by_success_rate(filtered) # 按成功率降序 selected = ranked[0] # 取第一个 if selected.p95_latency > 800: log.warn("Selected channel has high latency: {}", selected.code)

    关键设计:路由结果写入route_log表,包含channel_code、reason(如"highest_success_rate")、timestamp,用于事后审计。

  • 步骤 4:提交支付引擎(Submit to Engine)
    封装标准请求体,关键字段:

    { "trace_id": "t-20240124-abc123", // 全局唯一,贯穿所有系统 "order_id": "o-20240124-xyz789", "channel_code": "visa_bni", // 经路由选出的通道 "amount": 29900, "currency": "IDR", "notify_url": "https://yourdomain.com/callback" // 支付引擎异步回调地址 }

    关键参数:submit_timeout=5s,支付引擎必须在此时间内返回ACCEPTED或明确错误。若超时,收银核心主动发起cancel请求(幂等)。

  • 步骤 5:收单结果轮询(Polling Settlement)
    支付引擎返回ACCEPTED后,绝不信任其返回值,立即启动对收单平台的轮询:

    • 初始间隔:1s(快速确认是否秒级成功)
    • 指数退避:1s → 2s → 4s → 8s → 16s
    • 最大轮询次数:12 次(覆盖 3 分钟,足够收单平台完成清算)
    • 轮询失败:若 12 次后仍无结果,标记status=PENDING,启动人工核查流程

    关键设计:轮询请求带polling_seq=1..12,收单平台可据此判断是否重复请求;每次轮询记录polling_log,含seq、http_status、response_body。

4.2 为什么必须轮询收单平台,而不是相信支付引擎?

PDF 第 6 页末尾的说明常被忽略,但它直指支付系统最脆弱的环节。我们用一个真实案例说明:

某东南亚电商上线 VA 支付,初期测试一切正常。上线后第 3 天,用户投诉“ATM 转账成功,但订单未发货”。排查发现:支付引擎调用 BNI 银行接口返回{"status":"SUCCESS"},收银核心信以为真,立即更新订单为PAID并通知仓库发货。但 BNI 银行实际在 2 分钟后因清算失败向收单平台发送REJECTED通知。收单平台收到后,自动发起资金原路退回,并将订单状态改为REFUNDED。而收银核心早已“结案”,导致用户钱退了、货发了,公司净亏。

根本原因在于:支付引擎只管“发起支付”,收单平台才管“资金是否真正到账”。二者之间存在天然的时间差和状态差。PDF 中图 6 清晰展示了这个差异:

  • 支付引擎视角:Request → Bank Gateway → Response(成功即结束)
  • 收单平台视角:Payment Request → Bank Clearing → Fund Settlement → Final Status(清算完成才算数)

因此,收银核心的轮询不是“多此一举”,而是构建最终一致性的必要手段。轮询的终点不是“支付引擎说成功”,而是“收单平台说资金已 settle”。这个认知偏差,是 80% 支付资金差错的根源。

4.3 支付受理的避坑指南:五个让资金安全悬于一线的致命错误

现象 1:用户点击支付后,页面长时间白屏,F12 查看 Network 发现submit接口超时

原因:支付引擎submit接口未设置合理超时,或收银核心未做熔断。当支付引擎因 GC 卡顿,响应时间从 200ms 涨到 15s,前端一直等待。
解决:收银核心submit调用必须设timeout=5s,超时后立即返回ERR_ENGINE_TIMEOUT,前端可提示“支付处理中,请稍后查看订单状态”,并自动跳转到订单详情页(该页会轮询结果)。

现象 2:轮询收单平台 12 次后仍无结果,订单状态卡在PENDING,客服无法处理

原因:缺少PENDING状态的兜底机制。轮询失败后,既不告警也不人工介入,订单无限期挂起。
解决:轮询结束后,若状态仍为PENDING,自动触发pending_order_alert事件,通知风控团队人工核查;同时向用户推送站内信:“您的订单支付状态待确认,我们将尽快处理,预计 2 小时内回复”。

现象 3:同一订单被多次提交,支付引擎扣款多次

原因:前端防重未做,或收银核心未校验trace_id幂等性。用户手抖连点两次“确认支付”,生成两个不同trace_id,收银核心视为两个独立请求。
解决:收银核心受理入口增加idempotent_key=md5(order_id+user_id+timestamp),存入 Redis(TTL=10min)。若idempotent_key已存在,直接返回ERR_DUPLICATE_REQUEST。

现象 4:风控挑战(短信验证)通过后,用户未在 120s 内完成支付,订单被自动取消

原因:挑战成功后未及时更新订单状态,导致re-check时发现订单已过期。
解决:风控挑战成功后,立即更新订单challenge_status=SUCCESS和challenge_expired_at=now+120s;re-check步骤增加校验if now > challenge_expired_at then return ERR_CHALLENGE_EXPIRED。

现象 5:轮询收单平台返回{"status":"PROCESSING"},收银核心误判为成功,更新订单为PAID

原因:未严格解析收单平台状态码。PROCESSING表示“银行正在处理”,不等于“资金已到账”,必须继续轮询。
解决:定义收单平台状态机:["INIT","PROCESSING","SUCCESS","FAILED","REFUNDED"],仅SUCCESS和REFUNDED为终态。轮询到PROCESSING时,继续下一轮;轮询到FAILED时,更新订单为FAILED并触发退款。

5. 支付方式领域建模:如何用一张图说清 VA、OTC、正反扫的本质差异?

5.1 八类支付方式的核心域模型:从“用户动作”到“资金流向”的抽象

PDF 第 8 页的领域模型图(图 8)是全文精华。它没有罗列技术接口,而是用 4 个维度刻画每种支付方式的本质:①用户动作(User Action)②资金流向(Fund Flow)③凭证载体(Credential)④结算周期(Settlement Cycle)。我们以 VA 和 OTC 为例,看这张图如何破除“它们都是线下转账”的误解:

支付方式用户动作资金流向凭证载体结算周期关键区别
VA(Virtual Account)用户持 VA 号去 ATM/网银转账用户 → 银行 → 收单平台 → 商户银行生成的临时账号(如8888 1234 5678 9012)T+0(银行到账即通知)凭证由银行生成,资金经银行清算,商户收款账户是银行账户
OTC(Over-the-Counter)用户持 OTC 码去 7-11 柜台交现金用户 → 7-11 → 收单平台 → 商户7-11 生成的交易码(如711-20240124-ABC123)T+1(7-11 日结后划款)凭证由第三方生成,资金经现金归集,商户收款账户是第三方备付金账户

提示:这个表格不是 PDF 原文,而是我根据图 8 的四个维度提炼的。你会发现,VA 和 OTC 的“用户动作”相似(都是线下操作),但“资金流向”和“凭证载体”完全不同——这意味着它们的系统对接方式、风控模型、对账逻辑必须完全隔离。很多团队把它们混在一个OfflinePaymentService里,结果 OT C 的现金归集延迟导致 VA 订单误判失败。

再看正扫与反扫:

支付方式用户动作资金流向凭证载体结算周期关键区别
正扫(Merchant QR)商户生成二维码,用户扫码支付用户 → 支付平台 → 商户商户侧生成的动态码(含mid、order_id)T+0(支付平台实时分账)商户掌握主动权,可控制二维码有效期(如 2 分钟)
反扫(User QR)用户生成付款码,商户扫码收款用户 → 支付平台 → 商户用户侧生成的动态码(含uid、token)T+0(同上)用户掌握主动权,商户扫码后需实时调用支付平台pay接口

本质差异在于:正扫的二维码是“支付指令”,反扫的二维码是“支付授权”。正扫时,用户扫码即扣款;反扫时,商户扫码只是获取授权,必须立刻调用pay接口才能扣款。这个差异决定了反扫必须有更强的幂等性和超时控制。

5.2 领域模型驱动的代码实现:如何让支付方式扩展成本趋近于零?

PDF 第 8 页图 8 的价值,不仅在于解释概念,更在于指导编码。我们基于该模型,设计了一个可插拔的支付方式处理器框架:

// 支付方式抽象 public interface PaymentHandler { String getChannelCode(); // 如 "va", "otc", "wechat_qr" // 用户动作:生成用户需要的操作指引 PaymentInstruction generateInstruction(Order order, User user); // 资金流向:定义资金如何流转(用于对账) FundFlow getFundFlow(); // 凭证载体:生成或验证凭证 Credential generateCredential(Order order, User user); // 结算周期:定义何时可认为资金到账 SettlementCycle getSettlementCycle(); } // VA 处理器实现 @Component public class VaPaymentHandler implements PaymentHandler { @Override public String getChannelCode() { return "va"; } @Override public PaymentInstruction generateInstruction(Order order, User user) { // 调用银行接口生成 VA 号 String vaNumber = bankGateway.generateVa(order.getId(), "bni"); return new PaymentInstruction( "请前往任意 ATM 或网银,向以下账户转账:\n" + vaNumber, "ATM转账", "bni_logo.png" ); } @Override public FundFlow getFundFlow() { return FundFlow.BANK_TO_MERCHANT; // <p> <a href="https://download.csdn.net/download/yinmo_sc/89278347" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询