支付系统设计
本文主要是从支付架构、支付流程分析、支付核心逻辑、支付基础服务、支付安全、压力测试6个方面来详细讲述支付系统架构设计。
支付系统框架
- 架构的定义:架构一定是基于业务功能来展开的,主要是制定技术规范、框架,指导系统落地,好的架构是需要不断演变和进化而来的,不是一开始就设计完美的。
- 技术方面核心考量主要是:安全、稳定、可扩展、可维护。
- 构建架构时需要关注的点:目标客户是谁、主要场景有哪些、流程是怎样的、模型、职责有哪些、边界在哪里以及设计。其中比较难以理解的点是困难及模型这两块。
- 架构与业务需求的关系:架构的产生来自于业务需求,业务需求进一步抽象形成架构,架构指导后续研发,研发最终成果解决业务需求的问题。整体是一个正向循环的关系。
支付系统的角色
支付系统是每个电商系统都必备的模块之一,也是众多模块中最核心的功能,如果支付出现问题,那么意味着会直接影响到产品收益,事故严重程度高。
支付中心系统对内为各个业务线提供统一的支付、退款等服务,对外对接三方支付或银行服务实现资金的流转。如下图:
大部分公司基本都是这样的架构,主要有以下几方面的优点:
- 形成统一支付服务,降低业务线接入成本及重复研发成本。
- 更好更快的支持创新业务,为公司业务快速发展提供条件。
- 更利于构建安全,稳定,可扩展的支付系统。
- 利于核心支付数据的沉淀和统一利用。
支付中心(payment core)模块
支付核心系统指用户执行支付的核心流程,包括:
- 支付中心模块是按照支付场景来为业务方提供支付服务。
- 这个模块一般位于支付网关(payment gateway)之后,支付渠道(payment channel/method)之前。
- 它根据支付能力将不同的支付渠道封装成统一的接口,通过支付网关来对外提供服务。
所以,从服务的角度,支付中心本身也是一个代理模式的服务,它透过支付网关响应业务方请求,进行一些统一处理后,分发到不同的支付渠道去执行; 支付渠道调用银行、第三方支付等渠道提供的接口来执行支付操作,最终落地资金转移。 最后支付中心将执行结果做处理后,再回传给业务方。
支持支付核心系统所提供的功能的其他子系统
- 基础服务系统提供支撑线上支付系统运行的基础业务功能:
- 客户信息管理 (user):包括对用户、商户的实名身份、基本信息、协议的管理;
- 卡券管理 (promotion): 对优惠券、代金券、折扣券的制作、发放、使用流程的管理;
- 支付渠道管理 (payment channel/method): 通道接口、配置参数、费用、限额以及 QOS 的管理;
- 账户和账务系统 (settlement): 管理账户信息以及交易流水、记账凭证等。这里的账务一般指对接线上系统的账务,采用单边账的记账方式,内部账记录在会计核算系统中。
- 订单系统 (order/payment): 一般订单系统可以独立于业务系统来实现的,这里的订单,主要指支付订单。
- 资金系统指围绕财务会计而产生的后台资金核实、调度和管理的系统,包括:
- 会计核算 (accounting):提供会计科目、内部账务、试算平衡、日切、流水登记、核算和归档的功能。
- 资金管理 (fund management):管理公司在各个支付渠道的头寸,在余额不足时进行打款。对第三方支付公司,还需要对备付金进行管理。
- 清算分润:对于有分润需求的业务,还需要提供清分清算、对账处理和计费分润功能。
- 风控系统(risk)是支付系统必备的基础功能,所有的支付行为必须做风险评估并采取对应的措施;
- 信用系统(credit)是在风控基础上发展的高级功能,京东的白条,蚂蚁花呗等,都是成功的案例。
支付产品类型
支付产品是由支付系统对支付渠道进行封装而对业务方提供的支付能力。整体上来说,可以提供如下支付产品:
-
快捷支付: 用户在完成绑卡之后,在支付的时候,不需要再输入卡或者身份信息,仅需要输入支付密码就可以完成支付。 对于小额度的支付,甚至可以开通小额免密,直接完成支付。 这种支付方式不会打断用户的体验,是目前主要的在线支付方式。 一般快捷支付产品是通过封装银行或者第三方支付平台提供的快捷支付接口或者代付接口来实现的。
-
网银支付: 用户在支付的时候,需要跳转到银行网银页面来完成支付。 在网银页面,需要输入用户的卡号和身份信息。这种支付方式会中断用户当前的体验,一般仅用于PC Web上的支付。 网银支付是封装银行提供的网银支付来实现。
-
账户余额支付: 也称为余额(balance)支付、钱包(wallet)支付等。 指为用户建立本地账户,如支付宝余额,ShopeePay Wallet,GrabPay Wallet支持充值,之后可以使用这个钱包账户来完成支付。
-
信用支付: 如京东的白条,蚂蚁花呗,ShopeePay Later等,指使用信用账户进行透支,类似信用卡支付。
-
虚币支付: 不少公司会有自己的虚拟币,比如京豆、Q币、Shopee Coins等。这些虚币也可以作为一种支付方式。
-
第三方平台支付: 使用微信、支付宝、Apple Pay、Google Pay等第三方支付平台来完成支付。 使用时,一般需要用户预先安装支付平台系统(手机上),注册并登录到第三方支付平台,并且已经在该平台上完成绑卡等操作。 在国内,由于微信、支付宝已经被大量使用,用户也产生对这些平台的信任,平台支付往往是电商公司的主要支付方式。 东南亚地区,支付还处于混战阶段,没有主流的支付平台,各个国家的情况还不一样,所以这里的需要对接的支付平台较多。
-
理财产品支付 使用余额宝、基金、股票等理财产品来完成支付。
-
国际卡(外卡)支付: 对于由海外支付的需求,还需要提供外卡支付支持。 国内不少支付渠道都能支持外卡支付,如支付宝全球购等。
-
协议支付: 协议支付也称代收或者代扣,代收指渠道授权商户可以从用户的银行账户中扣款,一般用于定期扣款,不用于日常消费。 比如水电煤气、有线电视费。协议支付是通过封装银行、第三方支付提供的代扣或者快捷接口来实现。
-
话费/公交卡支付: 对于有包月小额类型的支付,手机话费也是一个不错的选择。目前也有一些平台可以支持话费支付,比如联动优势等。 在新加坡,ez-link公交卡也是一种支付方式,可以用来支付公交、地铁、麦当劳、超市。
-
组合支付: 组合支付是指将多种支付方式组合起来,一起完成一笔订单的支付。
支付流程
用户支付一般分为这几个大步骤:
- 用户在业务订单确认页,唤起收银台(checkout)页面;
- 用户在收银台页面选择支付方式,确认发起支付;
- 显示第三方支付页面,用户输入密码,完成真实支付操作;
- 支付系统处理用户支付结果,并通知给用户及各个相关系统。
下面详细说下整个流程:
- 第1步,用户进入业务客户端checkout,选择支付渠道;
- 第2步,业务客户端发送支付要素,到业务服务端;
- 第3步,业务服务端通过支付网关,向支付中心发起支付请求;也可以由前端直接发起。
- 第4步,支付中心发起支付请求到渠道侧;
- 第5步,返回支付链接/凭证到客户端
- 渠道返回支付链接给支付中心;
- 支付中心返回支付链接到业务服务端 (Optional,也可以直接返回到前端);
- 业务服务端返回支付链接到业务客户端;
- 第6步,客户端根据支付链接,唤起第三方支付页面;
- 第7步,用户进入第三方渠道页面(如支付宝、微信支付)完成支付;
- 第8-10步,支付结果处理:
- 一般渠道是采用异步通知方法来通知业务,但是有些业务是在第7步支付完成之后,在C端会同步通知支付成功。如果以此结果来判断支付是否成功,其实是不严谨会出问题的,应当调用渠道的支付接口来进行核查,然后以渠道返回的结果为准。
- 客户端拿到三方返回的结果,确认用户已经支付,则分配定时任务轮询查询(注意超时时间)后台支付结果,拿到终态之后跳转到相应页面。
- 三方系统支付成功后会通知支付中心结果,支付中心做好自身逻辑处理,异步通知业务订单系统,然后返回三方系统通知结果。
- 如果长时间未收到三方支付结果的通知,为了防止掉单,支付中心可发起主动查询来获取支付最终结果,以保证支付结果的及时更新。
- 当然业务线订单系统为了防止支付系统出现异步通知问题,也可以定时轮询支付中心的支付状态,防止掉单。
对接支付渠道的问题
接口文档升级、变更能及时得到通知
挑战:支付渠道升级接口,客户端怎么保证前向兼容
渠道行为不一样,怎么保障用户体验的一致
同一业务在不同渠道表现不一样
- 如还款功能,有的渠道不支持退款,有的渠道不支持部分还款;
支付渠道的异常
- 如支付渠道维护中,短时间内不可用,需要给用户一个友好的提示。
- 支付渠道挂了,或者网络不通;需要及时熔断。
- 支付渠道没有回调或者回调失败。
三方支付系统支付成功后99%的情况下都会回调通知我们,但也难免有意外,比如三方延迟回调或者三方系统宕机,为了保证支付结果的实时性,三方支付也要求我们不能完全依赖于回调接口,所以我们需要定时的调用主动查询接口来查询三方的支付结果。这里我们可以使用的 MQ 延时队列 或者定时任务实现。
- 调用三方支付创单成功后,发送<支付主动查询>延时MQ消息。
- 消费消息,判断支付状态是否到达终态,如果到达终态,则返回处理成功,否则调用三方支付查询接口,如果支付成功则处理成功业务,返回处理成功。
- 如果客户未支付则判断是否达到最大的重试次数,如果达到最大重试次数则停止<支付主动查询>的重试,否则解析重试规则,发送下一轮的延时消息。
支付订单超时关闭问题
如果用户长时间没有支付,一般都会有一个超时时间(如上图业务收银台的支付剩余时间),到达这个超时时间支付单会自动关闭。实现此需求有很多方式,比如:
- 定时轮询DB,取出达到超时时间且在支付中的数据,然后执行关闭逻辑。
缺点:1. 存在延迟,取决于定时任务的频率。2. 影响数据库性能。
- 基于内存的延时队列(DelayQueue)和时间轮算法
这两种的算法的共同缺点是:
-
- 数据易丢失,由于数据存储在内存中,服务重启后数据全部消失。
-
- 有内存限制,如果数据量过大,会出现OOM异常。
支付系统需要保证数据最终一致,通常不会采用内存队列的方式来实现关闭订单的功能。
- MQ延时队列
如RabbitMQ支持消息延时处理。 比如支付单30分钟过期,在支付单创建成功后发送延迟消息,消费者在30分钟后会拉取到该消息然后执行关闭逻辑。
支付结果通知上游容错
在回调通知上游业务系统支付结果时,可能会回调失败,比如网络异常或上游系统发生短时故障,如果发生这种情况我们单靠简单的重试是无法完全解决问题的。
为了尽可能的通知成功,我们需要针对没有通知成功的数据,每隔一段时间通知一次,那这个需求和我们上一个问题差不多,所以可以复用我们的延时重试队列。
上游业务的要求
- 清晰的 API 、SDK 文档;
- 安全;
- 所有应用接口统一标准的异步通知;
- 保证出口 IP 稳定(安全)。
架构设计要点
- 提供规范的 API、SDK;
- 安全(通讯安全、数据安全);
- 稳定;
- 异步通知统一;
- 各渠道的异常;
- 及时了解渠道接口调整。
支付核心逻辑
在系统里我们设计了诸如支付、转账、汇款、红包等产品功能。 这些功能都是通过调用支付中心的接口来实现的。 支付成功之后,我们会把订单信息同步到wallet(或者账务)系统。
在前期设计时需要设计好账务的生成规则,例如;用户发一笔多人红包,这是一笔红包的请求会生成多笔账务(fund_movment),对不同领红包的人进行区分,这样方便管理和维护。
支付网关
此处特指API网关,支付网关的功能
支付网关的内容:
- 唯一的请求入口;
- 统一的身份认证、签名
- 加解密
- 流控/熔断
- 协议转换;如HTTP转换为RPC;
- API 发布管理;API 调用计费;API 的监控、报警分析;API 聚合;
上述内容除了必要意外,其他不放在网关层做,也是为了更好的用户体验。 全局的限流最好不要放到业务流程中做,会影响用户的体验。业务层可以有自己业务的限流。
支付逻辑
主要是根据请求的参数进行静态检验和业务逻辑校验,避免系统异常。
- 适配渠道的参数校验:长度、类型、格式;
- 订单的支付状态:是否支付;
- 订单的有效期等等。
要点:
- 一般商户是不需要做支付路由,大部分都是指定了最终的某个支付渠道。但也有些没有指定了某个最终的渠道,比如银行卡的支付可以选择哪个第三方支付来完成支付,还有微信线上线下的封装,这个时候就涉及到支付路由规则配置。
- 费率 - fee:单笔费率、总额费率、阶梯费率;
- 营销活动 - promotion :固定时间单笔优惠、单笔满减、单笔这款、直接补贴;
- 额度限制 - limit:单笔额度、时间范围内总额度;
- 服务指标 - SLA:失败率、平均响应时间、异常率、TPS;
- 特殊配置:特殊要求(比如某渠道能快速结算)。
支付网关的目的之一——省钱。
风控 & 反欺诈
要点:梳理清楚业务风险,分析风险原因,制定风险防范规则。
风控数据来源
内部数据:
- 用(商)户信息
- 交易数据
- 账户数据
- 黑名单
- 设备(device_fingerprint)、位置信息(GPS)
- 日志数据
外部数据:
- 第三方购买,如tongdun
- 央行征信
- 芝麻信用
- 合作数据
风控流程
事前:
- 入网审核
- 风险评估
- 单笔限额设置
- 单日限额设置
- 频次设置
事中:
- 实时分析
- 多维度判断
- 拒绝
- 拦截 – 进一步验证– 人工介入
- 延迟操作(例如用户大额提现,需要时间段进行复核)
事后:
- 数据分析
- 巡查、警告
- 降低评级
- 升级防范措施
- 逻辑完善
- 反馈至事前、事中规则中
账务系统
- 账务生成
- 内部对账
- 原始账单下载
- 生成标准账单
- 对账
- 差错处理
账务生成后首先进行内部对账,一直后进行原始账单下载,再生成标准账单,进行对账之后进行差错处理。
内部对账流程:
- 订阅交易信息;
- 根据交易事件查询生成账务的规则。
交易事件:支付、退款、转账等等。
- 根据规则生成账务明细;
- 将账务明细落地。
对账流程实现 - 方式之一
- 内部对账
- 账单下载;
- 标准化账单;
- 双向对账;
- 差错处理。
内部对账:
- 保证账务和交易信息配对;通常一条交易信息可以有多条账务信息
- 上游业务订单和支付系统订单匹配
渠道账单下载:
- 下载;
- 下载方式:FTP、HTTP、S3;
- 文件格式:CSV、XML、Excel;
- 下载时间:每日、每周、每月、实时;有些渠道工作日才生成账单,周末不生产账单,这个时候就需要考虑到这个问题。
- 异常处理:立即重试;重试次数;重试间隔;重试失败后通知;重试失败后人工介入。
- 账单标准化(对账字段统一);
- 落地标准化账单。
正向对账:
- 获取核对文件;
- 以账务系统为准来逐笔比对(如以amount字段为准进行比对);
- 数据一致标记成功,数据不一致标记差错。
反向对账:
- 以渠道账单为准来逐笔比对;
- 数据一致标记成功,数据不一致标记差错。
差错处理
- 本地丢失:渠道账单的数据未在账务中查找到。
- 渠道丢失:账务中的数据未在渠道账单中查找到。
- 数据差错:账务与渠道某些对账字段未能对上。
此处需要注意的是,针对差错都需要向渠道查询每笔订单信息再次确认,同时,有些渠道的交易成功时间本来就是有错误的。 一般来说是件不会差错很大,一般出现在跨日交易中,例如:当天交易无账单,先标记为差错,第二天再改正。
支付基础服务
Webhook
公共推送服务
主动查询
异步延时调用
场景:
- 订单创建成功的时候会向服务推送主动查询信息,如果订单支付成功会通知服务取消后续的主动查询,否则在过期时间点向渠道主动查询订单是否支付目的是避免渠道异步通知服务的异常。
- 退款创建成功的时候会向服务推送主动查询信息,该服务会在一定的时间范围内多次查询渠道直到有明确的结果返回(有些渠道没有异步通知)。
- 转账也是类似的逻辑,但某些渠道只提供重试的功能,要注意幂等性。
补偿:
- 协调保证各模块间数据的一致性;
- 一般会跟重试、回滚、兜底来协调使用;
- 使用条件:系统异常、业务异常;
- 补偿失败报警人工干预。
链路监控
展示信息:应用、URL、调用方、调用时间、调用次数、调用失败次数、本地平均耗时、总平均耗时、调用失败平均耗时 、错误率、依赖度。
关注:Cache、DB & SQL、Nginx & HTTP、TCP & RPC
支付安全
数据安全
- 防窃听、防越权防抵赖、防破坏、防篡改、防重放、防泄漏。
使用范围:网络、系统、应用、业务等。
数据安全要点
- 加密通讯(防窃听)
- 双向签名(防抵赖、防篡改)
- 敏感数据加密存储(防泄漏)
- 密钥管理(通过认证接口获取,只允许加载到内存,不允许直接写入配置文件)
- 权限控制(防越权-非法访问)
- 数据的完整性(放篡改- 数据被恶意修改、非法篡改)
其他
- 内部接口认证。
- 避免内部代码未经审核发布到托管平台!!!
- 数据异常分析
- 安全机构合作
- 内部安全演练
注意点
- 使用 HTTPS 加密传输;
- 传输的数据使用签名;
- 提交的数据是符合规则并且是不存在或者是未支付的;
- 支付成功以服务端异步通知为准。
全链路压测
全链路压测(Full Chain Stress Test)是支付全系统每个环节都参加的实战演习,主要对零点峰值流量进行评估,以及对承压能力进行测试,是大促前为系统查缺补漏的重要一环。
要实现全链路压测,涉及的产品很多,包括网络、应用、中间件、数据库、安全、数据等都需要改造,涉及的应用范围很广。
采集压测数据
为了模拟尽可能真实,全链路压测以线上数据为数据源,进行采样、过滤和脱敏,作为全链路压测的基础数据,数据量与线上数据保持同一个数量级,在数据库上进行区间隔离。
数据安全
为了保证安全,可以根据user_id进行隔离,压测用户集跟线上用户id不重合,防止出现意外,修改线上用户数据。
正常用户应该访问不了测试数据,测试账户也访问不了正常数据,防止数据错乱。 在实际大流量压测开始前,应进行小规模验证,确保数据读写是符合预期的。
压测流量平台
在早期,需要通过Jenkins手动触发压测流量;有了压测流量平台后,大大简化了压测的工作量。
流量平台是全链路压测的CPU,主要由两大部件构成:
- 全链路压测操控中心,进行压测的case、压测流量大小配置以及对压测集群的管控。
- 压测引擎,由控制台统一管控,最好部署在外网CDN集群,进行登录、session同步,能发送各种协议(如rpc/http)的压测请求、状态统计。
我们基于locust构建了内部的压测引擎。基于Grafana进行压测流量监控。
业务改造
全链路压测有不少地方需要在业务系统针对压测流量进行相应改造。
- 比如如何区分压测请求和,和正常的请求
- 如何跟上下游、中间件交互
影子库 - shadow db
全链路压测的链路有读有写,并且在线上进行,为了不污染到线上的正常数据, 对于数据库,可以新建一个完全一样的影子库来进行数据的隔离;通过db name前缀加以区分:
- payment_db:真实线上用户请求写payment_db
- shadow_payment_db: 压测请求写shadow_payment_db
内部自研的ORM能自动检测压测参数,读写shadow db。研发只需要添加好对应的shadow db配置。
中间件 - 区分压测请求
全链路压测的流量通过在链路上带上特定的压测参数进行区分,如果缺乏相应中间件的支持,在调用到下游系统的时候,压测标志就丢失了。 因此需要所有中间件的协议都支持对压测流量的识别,使得压测标识能够随着调用传递下去,使得下游的应用、基础中间件和存储都能够识别压测流量。
- 对于Http请求,可以在header中携带压测信息。
- 对于Golang RPC请求,通常在ctx中携带压测信息。
- 对于缓存,我们没有用单读的缓存集群,业务使用cache key前缀来区分处理压测的请求:
- cache key:
user_info:user_id
- shadow cache key:
shadow:user_info:user_id
- cache key:
链路验证
链路验证往往会耗费压测项目组大量的时间和精力,电商业务具备复杂的业务特性,有上百条链路,每一条链路都需要确保能够让全链路压测引擎跑通。