简介:《图解收银台:支付系统承上启下的关键应用》是《百图解码支付系统设计与实现》系列中的第11篇,聚焦收银核心如何协同支付引擎完成咨询、受理与风控校验。整份资源为1个PDF文件,约5.2MB,已有101人学习。内容以大量手绘图示展开,先说明收银核心在支付系统中的位置,再拆解支付咨询的基础检查、资产/渠道/额度咨询、支付方式组装与排序,支付受理阶段的重复校验、风控、渠道路由、结果轮询等关键环节;同时给出收银核心系统架构、领域模型,以及快捷支付、代扣/协议支付、卡支付、网银支付、VA支付、OTC支付等常见支付方式的使用场景。适合支付系统入门学员、支付研发工程师和架构师快速建立收银台设计全貌,也可作为培训机构的图解参考,帮助将承上启下的收银台设计经验直接应用于日常研发。
1. 收银台:为什么说它是支付系统里承上启下的门面
做支付系统的人聊架构,一般先聊支付引擎、账务、清结算这些“重”的域,收银台往往一笔带过。但实际上,用户能感知的支付体验几乎全部压在这一层:哪些支付方式能展示、顺序怎么排、点击支付后多久能反馈结果。那份图解收银台的 PDF 我拆完以后最大的感受是——收银台不是“一个页面”,而是一台“判断引擎”。它的核心职责不是 UI,而是在支付产生前把所有“能不能付、怎么付、按什么规则付”的决策做完,然后把一个已经可执行的支付请求交给支付引擎。换句话说,支付引擎管钱怎么扣,收银台管这次扣款允不允许发生。这篇笔记围绕这份 PDF 里的收银核心设计、支付咨询与支付受理两条链路展开,适合正在做支付研发、想理解支付系统架构设计的从业者,也适合准备从单体业务系统转向支付域的工程师。
2. 支付咨询:用户能看到哪些支付方式,是怎么算出来的
收银台第一次被调用,通常发生在用户点“去支付”那一刻。系统要回答的问题只有一个:当前这笔订单,这次交易,允许用哪些支付方式。PDF 里把这个阶段定义为支付咨询,整个咨询过程不是查一次配置就完事,而是基础检查、资产咨询、渠道咨询、额度咨询四层过滤之后,再做组装和排序。下面把这条链路拆开讲。
2.1 咨询链路全景:四层过滤决定“有哪些支付方式”
咨询阶段在收银核心里的位置,相当于一扇门:所有可用的支付方式先被筛一遍,筛完的才可能出现在用户面前。四层检查各有各的信息来源,任何一层没过,对应的支付方式就要被剔除或者置灰。
| 检查层 | 主要检查内容 | 数据来源 |
|---|---|---|
| 基础检查 | 订单状态、用户状态、商户状态 | 订单中心、会员中心、商户平台 |
| 资产咨询 | 绑卡信息、账户余额、可用营销(满减/红包) | 卡中心、账户中心、营销系统 |
| 渠道咨询 | 币种、金额区间、渠道开关 | 渠道网关配置 |
| 额度咨询 | 单笔限额、日累计限额、月累计限额 | 限额中心或各渠道返回 |
实操中这四层是串行还是并行,要看公司的基础设施。我一般会建议:基础检查必须前置且串行,因为这层不过,后面三层就不用查了;资产、渠道、额度这三层可以并行拉取,再汇总做交集。并行能省一次用户可感知的等待,咨询接口的耗时也能从几百毫秒压到一百毫秒上下。
有一个容易被忽略的点:渠道咨询里的“渠道开关”不只是渠道本身的开关,还要看商户维度。同一个渠道,A 商户开了,B 商户没开,那就不能在 B 的收银台上出现。所以渠道开关往往是一个二维配置:渠道维度 + 商户维度。PDF 里讲支付咨询时用的是“币种、金额、渠道开关”,实际落地时我会在渠道层多带一个商户号参数,用于命中商户维度的开关。
2.2 可支付检查:最容易漏掉的是“订单已被支付”
基础检查里有三个动作:可支付检查、用户检查、商户检查。用户和商户的检查比较好理解,无非是账号状态、商户权限;可支付检查看起来也简单——订单是否有效、是否过期。但这里藏着一个非常典型的坑:订单在收银台渲染完,用户犹豫了几分钟,这期间订单在另一个端已经被支付了。此时用户回来点“确认支付”,如果收银台只看订单基本状态,很可能让用户发起第二次扣款。
常见的做法是把可支付检查细化成三个维度:订单状态、支付单状态、交易状态。订单状态回答“这笔业务订单还能不能付”,支付单状态回答“这笔订单有没有生成过支付单、支付单是什么状态”,交易状态回答“扣款动作有没有发生、扣到哪一步”。只有订单可支付、支付单不存在或为失败态、交易不处于处理中,才允许进入下一步。这套三维检查在咨询阶段和受理阶段都要跑,后面讲受理阶段会再提到。
除状态之外,可支付检查还应该包括一个容易被业务侧忽略的点:金额一致性。收银台渲染给用户看的金额,和提交支付时订单中心返回的金额,必须是一致的。如果中间发生过改价、优惠变动而收银台没有感知,用户看到的和实际扣的就不是一个数。金额校验放在咨询阶段做的原因是提前暴露问题,而不是等到扣款失败再让用户重试。
2.3 支付方式组装与排序:把渠道翻译成用户能理解的东西
四层过滤做完,得到的其实是一堆“渠道 + 资产”的原始组合,还不能直接展示给用户。组装工作要干的事情是:把这些组合翻译成用户熟悉的支付方式。比如某张银行卡可以被快捷支付使用,那组装出来的是“快捷支付—XX 银行储蓄卡(尾号 1234)”;再比如余额 + 满减营销可用,那组装出来的是“余额支付(可用红包抵 5 元)”。组装层还决定了支付方式的展示名称、图标、附加说明文案,这块做得好不好直接影响支付转化率。
组装完成之后是排序。PDF 里提到排序要“考虑用户体验,又要考虑营销策略”,这句话展开来其实是几套规则在打架:用户最近用过什么(体验优先)、商户主推哪个渠道(商业优先)、哪个渠道当下有满减/红包(营销优先)、哪个渠道费率低(成本优先)。四类目标不可能同时满足,所以排序通常要做成分数制,而不是简单的 if-else 判断。
我一般会这样设计:每个支付方式有一个基础权重(来自用户最近支付行为统计),加上商户推荐权重、营销活动权重、成本权重,最后按总分倒序。权重参数放到配置中心,运营可以随时调整。这里有个血泪经验:排序规则千万不要写死在代码里。曾有一个团队把“钱包支付永远排第一”写死在支付方式组装逻辑里,结果某次钱包渠道要做大促降级,只能临时发版,等发完版活动都结束两天了。
3. 支付受理:点击“确认支付”之后,链路里到底发生了什么
用户选中一个支付方式,点“确认支付”,收银台就从咨询模式切到受理模式。PDF 把受理阶段定义为“支付请求真正走向支付引擎前的最后一道关卡”,它在两个层面和咨询阶段完全不同:第一,咨询阶段的输出可以被缓存、被延迟消费,受理阶段的每一步都必须以当前实时数据为准;第二,咨询阶段只做计算,受理阶段要真正产生一个支付动作。
3.1 为什么受理阶段要把支付咨询的活全部重做一遍
PDF 里明确写了一句:在支付咨询阶段的动作要全部再做一遍。这句话很多人会理解为“同一个方法调两次”,但实际上它的含义是:以提交时点的数据为准,重新做一次可支付检查、用户检查、商户检查、资产检查、渠道检查和额度检查。
原因很现实。用户在收银台页面停留的时间可能从几秒到几分钟,甚至更久。这期间账户余额可能被另一笔消费扣减,订单可能被另一个端支付,营销活动可能达到总预算上限,渠道限额可能因为其他用户的高频交易而达到日累计上限。如果受理阶段直接信任咨询阶段的结果,等于用一个已经过期的快照去做一笔真实扣款的决策,这在支付系统里是不可接受的。
落地做法是把咨询阶段的校验逻辑抽成一个共享的校验执行器,咨询阶段以“宽松模式”运行(例如渠道额度可以做缓存读取),受理阶段以“强制模式”运行(所有校验实时查库,且校验失败直接中断)。我当时接这套设计时踩过一次坑:早期为了省事,受理阶段直接调咨询接口,结果用户在提交时又触发了一次完整渲染,响应时间翻倍,而且部分校验对象有差异(咨询按用户维度,受理按支付单维度),导致漏检。后来改成共享校验器 + 模式参数,才把两条链路的逻辑收口。
3.2 风控、渠道路由与提交支付引擎:顺序不能乱
重做完校验之后,受理阶段还有三个关键动作:调风控、走渠道路由、提交支付引擎。这三个动作的先后顺序是硬约束:风控必须在支付提交之前,路由必须在支付提交之前,且风控结果要作为路由选择的输入之一。
风控的作用在 PDF 里有句很形象的描述——“偷偷做个背影调查,以确保你不是在洗钱”。落到工程上,收银核心会组装一次风控请求,带上用户、商户、订单、支付方式、设备指纹等上下文,风控系统返回建议动作。常见返回值是“通过/拒绝/人工审核”。收银核心对“人工审核”的处理一般是转异步,不阻断支付,但会记录标记;对“拒绝”则直接拦截,返回用户可读的失败原因。
渠道路由这一步,只有当支付方式是外部渠道卡支付时才需要。比如用户在收银台上选了一张卡,但这张卡背后可能有多条可以处理这笔交易的渠道,有的是银行直连,有的是三方机构代付,成本和成功率都不一样。渠道路由的输入通常包括:卡 BIN、金额、币种、风控标签、渠道历史成功率;输出是一条具体的渠道编码。路由完成后,收银核心把这些参数组装成支付请求,提交给支付引擎,支付引擎开始执行真正的扣款动作。
3.3 轮询收单平台而不是支付引擎:一个容易想错的细节
提交支付引擎之后,收银核心还需要拿到支付结果并返回给用户。PDF 在这里特别强调了一句话:轮询结果是从收单平台拿,而不是从支付引擎拿。我一开始没有想明白这个细节,后来在梳理支付时序时被点醒。
原因是:收单平台和支付引擎各自维护一套订单状态,而且它们的生命周期并不完全一致。举 PDF 里的例子:用户提交支付,支付引擎层面扣款成功,但同一时间收单平台因为订单超时把订单关闭了。此时如果收银核心去向支付引擎要结果,拿到的可能是“成功”,但真正和用户订单挂钩的收单平台订单却是关闭状态,就会出现「提示用户支付成功,但实际业务订单没有落账,过一会儿还要追着用户退款」的事故。
所有支付产品都有类似逻辑:标准收银台、前置收银台、扫码付,最终都要以“收单平台侧的状态”为用户可见的终态;支付引擎的成功,只是支付链路某一环的成功,不是业务订单的成功。所以收银核心在受理阶段结束前,会从收单平台轮询最终结果,如果发现收单侧订单已关闭而支付侧扣款已发生,就会触发资金退回,并向用户展示那句经典的提示:“订单已关闭,如果已经扣款,支付款项预计在15个工作日内原路退回。”
4. 收银核心的系统架构:产品形态、服务边界与领域模型
通读完支付咨询和支付受理两条链路,再看收银核心的整体系统架构会清晰很多。收银核心在支付系统里的位置,可以从三个层面看:收单产品形态决定了它怎么暴露能力;核心服务与外部依赖决定了它怎么协作;领域模型决定了它的内部数据边界。
4.1 三种收单产品形态:标准收银台、前置收银台与扫码付
收银核心对外提供的产品能力,PDF 里归纳为三类:标准收银台、前置收银台、扫码付。三者面向的接入方和使用体验完全不同。
| 产品形态 | 实现方式 | 用户体验 | 典型适用场景 |
|---|---|---|---|
| 标准收银台 | 用户跳转到支付平台页面完成支付 | 统一支付平台 UI,信任感强 | 外部商户、平台聚合场景 |
| 前置收银台 | 支付能力内嵌到商户页面直接完成 | 不跳转,转化率高,但商户需自建 UI | 平台内商家、对转化率敏感的电商 |
| 扫码付 | 通过二维码/条形码完成支付 | 线上+线下通用,支持正扫反扫 | 线下门店、PC 端扫码支付 |
标准收银台适合快速接入,代价是用户会跳出商户页面,部分用户在看到跳转时会流失。前置收银台把支付方式渲染、支付确认都放在商户自己的页面,用户体验更连贯,但商户需要自己维护一套收银 UI,并且要承接支付方式的展示排序逻辑——这就是为什么 PDF 会强调收银核心是“承上启下”,上游是商户页面,下游是支付引擎。扫码付则独立于页面存在,正扫反扫的差异在第 5 章会展开讲。
4.2 核心服务与外部依赖:收银核心不是全知全能的
PDF 里给出了收银核心的服务边界:核心服务是支付咨询、支付受理、风控挑战并支付;外部依赖主要是会员、商服、卡中心、风控、渠道网关、支付引擎。这个边界划分很关键——收银核心自己并不持有账户余额,也不知道用户绑了多少张卡,更不执行实际扣款,它只做组装、校验、编排。
依赖关系上,收银核心属于“被多方依赖、也依赖多方”的中间层。向上服务商户口碑好的收银台页面,向下编排支付引擎和渠道网关,横向还要拉会员、卡中心、风控的数据做决策。正因为它处于这样一个位置,它的稳定性要求很高,同时它对下游的故障也要有降级预案。比如卡中心超时,收银核心应该跳过卡支付展示而不是让整个收银台不可用;再比如风控超时,按照默认放行策略还是默认拦截策略,需要在架构层面提前定,不能临时拍脑袋。
商服(商户服务)在依赖列表里的作用是提供商户信息与商户维度配置。收银核心在咨询阶段拿到的“可支付方式”,很大程度上由商服系统里商户开通的产品、渠道、限额决定。可以理解为:商服决定商户的“支付能力账号”,收银核心决定这次交易怎么用这个能力。
4.3 领域模型:订单、支付单与收单结果的边界
领域模型这块,PDF 里给出了收银核心的领域设计思路,核心是区分订单、支付单、收单结果三个概念。业务订单是用户在商家的订单,属于业务域;收银核心在受理时会产生一笔支付请求,对应支付单;支付请求提交到收单平台后,收单平台有自己的一套收单单据,叫收单记录。三者是 1:N 的关系:一个订单可以发起多次支付尝试,一次支付尝试对应一个支付单,支付单在收单平台侧又可能对应多条收单流水(比如一次重试)。
理解这三层边界,是排查很多收银台问题的钥匙。比如用户反馈“我付了两次钱”,很可能就是一个订单生成了两个支付单,每个支付单都在收单平台创建了记录。排查时需要先确定看到的是哪个域的数据,再顺着支付单找到收单记录,去看哪一笔是成功的、哪一笔需要退款。如果一开始就把订单、支付单、收单结果混在一个状态模型里,这类问题基本无从下手。
支付方式在领域模型里也是一个独立实体,而不是订单上的一个字段。它包含支付方式编码、资产类型、渠道编码、限额信息、开关状态等属性,并且与营销组合可以有聚合关系。这样做的好处是:当用户选择“余额支付 + 满减”时,收银核心可以把一笔支付拆成一个基础支付方式实例和一个营销优惠实例,在提交支付引擎之前组装成带有优惠分摊信息的请求,后续对账和退款也能追溯。
5. 避坑:收银台从咨询到受理,我踩过的四个典型问题
收银台逻辑不复杂,但它是支付链路里最容易出“看起来正常、实际状态错乱”的地方。下面这四条是拆这套设计时最有感触的真实问题,按“现象 → 原因 → 解决”记录。
5.1 订单已被支付,收银台还在正常渲染“去支付”
现象:用户在 App 上打开待支付订单,收银台显示可支付,点击支付后系统提示“订单不存在”;后台一查,这笔订单在另一个端已经支付完成。
原因:可支付检查只查了订单主表状态,没有查支付单状态。同一笔业务订单在咨询和受理之间被并发支付,收银核心只看到了订单“初始态”。
解决:把可支付检查改为“订单状态 + 支付单状态 + 交易状态”三重校验。订单初始态但支付单已存在成功记录时,直接跳转结果页;支付单处理中时,收银台返回“支付处理中,请稍后查询结果”,不让用户重复提交。
5.2 受理阶段重复调用咨询逻辑,接口超时翻车
现象:收银台确认支付按钮点下去,转圈很久后提示“系统繁忙”;日志里看到受理接口调用了和咨询几乎一样的一大串下游,总耗时超过网关超时阈值。
原因:为了“省事”,受理阶段直接复用了咨询接口,导致资产、渠道、额度全部重新渲染一遍,下游调用量翻倍,性能退化严重。
解决:共享校验器,但用模式参数区分。咨询阶段走“渲染模式”,允许缓存读取、允许部分降级;受理阶段走“校验模式”,只跑必要的实时校验,不渲染支付方式列表。两类模式的差异点在配置里明确列出,避免再次混用。
5.3 轮询结果取错数据源,用户被重复扣款
现象:用户支付时提示“支付超时”,查后台发现支付引擎已经扣款成功,但收银台展示的却是失败,用户再次发起支付导致重复扣款。
原因:轮询结果直接查支付引擎的交易状态。支付的扣款动作虽然成功,但收单侧订单已经关闭,收银核心按支付引擎的成功去处理,又把失败结果返回给用户。
解决:终态一律以收单平台为准。收银核心轮询收单平台拿到“已关闭/已支付”的结果后,再反向和支付引擎的成功状态做比对,若出现“引擎成功、收单关闭”,自动触发资金退回,并只向用户展示收单平台的结论。
5.4 支付方式排序规则写死在代码里,活动降级要发版
现象:某次支付渠道做费率调整,运营希望把排名靠前的钱包支付临时撤下,结果只能提需求排期改代码,错过了整个活动窗口。
原因:排序逻辑里的“钱包永远第一”是写死的,没有做成运营可调整的配置。
解决:排序改为权重配置化。每个支付方式由基础权重、商户推荐权重、营销权重、成本权重构成,统一存配置中心,线上可实时调整。发版只负责权重计算逻辑,具体数值由运营配置,加一个“排序预览”功能,运营改完先看效果再发布。
6. 用一个完整订单实例验证链路:收银台的兜底与自检
前面几章把咨询、受理、架构和常见坑都过了一遍。最后一个技巧,分享我怎么验证一套收银台设计是否闭环:不直接测页面,而是拿一个业务订单,按状态机从头到尾推一遍,把每一步期望看到的日志关键词和返回码写下来,照着核对。
以“订单创建 → 收银台渲染 → 发起支付 → 支付成功 → 订单关闭”为例,我一般会推演这条路径上的状态合法变换,并把关键节点整理成一张自检表。
| 节点 | 期望看到的日志 | 关键返回码/状态 |
|---|---|---|
| 收银台渲染 | query_pay_methods 带订单号与用户号 | 返回可用支付方式列表 |
| 发起支付 | create_payment 生成支付单 | 支付单状态=INIT |
| 提交支付引擎 | submit_to_engine 带渠道编码 | 引擎返回受理成功 |
| 收单结果轮询 | poll_settlement 拿到收单结果 | 收单状态=SUCCESS |
| 订单终态 | order_status=PAID | 触发后续发货流程 |
在验证的同时,我还会用一段小型的状态合法变换检查来做每日自检,相当于给收银核心的支付单流转加一道“协议护栏”。简化后的代码如下,逻辑上只允许从当前状态跳转到合法下一状态。
VALID_TRANSITIONS = { "INIT": {"PROCESSING", "FAIL"}, "PROCESSING": {"SUCCESS", "FAIL", "CLOSED"}, "SUCCESS": {"REFUNDING", "CLOSED"}, "REFUNDING": {"REFUNDED"}, "FAIL": {"INIT", "CLOSED"}, } def assert_transition(domain: str, current: str, target: str) -> bool: if target not in VALID_TRANSITIONS.get(current, set()): raise ValueError(f"{domain} 状态非法: {current} -> {target}") return True这段代码对收银核心而言足够小,但它卡住了两类最危险的状态跳变:一是支付单从 PROCESSING 直接跳到 SUCCESS 之外的状态却被标记为成功,二是 FAIL 状态的支付单被再次提交到支付引擎。状态机校验放在收银核心的领域服务入口,每次状态变更都强制执行,比事后对账多一道防线。
从那以后,我每次接一个新的支付渠道或者改一次收银台的排序逻辑,都强制走一遍这份状态自检表,把日志关键词对着过一遍再放行上线。这套习惯救过我几次,尤其是那些“表面成功、实际未到账”的隐蔽问题,靠页面手工测试很难发现,靠状态机一推就能暴露出来。希望帮到你。
本文还有配套的精品资源,点击获取