作者简介: 刘朝宁,软件工程师,就职于京东到家后台研发部,目前主要负责京东到家首页频道页、门店页、活动页、门店列表和Feeds流等业务聚合层网关。
一、引言
依托达达的高效配送和京东到家大量优秀零售合作伙伴,共同为消费者提供生鲜蔬果、日用百货、医药健康、鲜花蛋糕、个护美妆等海量商品约 1 小时配送到家的极致服务体验。在整个服务过程中付款环节必不可少,支付系统作为电商平台的核心系统之一为电商平台、用户及用户资金所在的第三方支付机构提供交易的桥梁,重要性不言而喻,在电商平台、物流系统如此发达和智能手机普及率非常高的今天,大家也肯定在电商平台用过微信支付、支付宝、京东支付等给订单付过款,付款所使用的收银台就是支付系统的一种付款形式。本文不打算讨论一个功能全面的支付系统,本文将重点从支付系统的应用场景、架构设计和安全设计等方面讨论下解决方案。
二、背景
每一个系统的诞生背景都非常非常重要的,背景(环境)决定了一个系统的建设方向,就好比一个生存环境决定了动物优胜劣汰的方向,想要适应环境就得使出浑身解数,进化出不同的本领一样。
达达 - 京东到家支付系统 1.0 诞生的背景是在京东到家这一单一收款平台下,即仅需满足京东到家业务的 App 支付和 H5 支付,支付方式也仅有微信支付、京东支付和京东白条这些,支付场景、接入系统和支付方式都比较单一,在交互上和京东到家订单系统、 App 端等是耦合在一起的,可以说是是专为京东到家业务量身定制的,这个阶段的支付系统基本不具备扩展性,且数据模型也是趋近于定制化,外部系统接入收银台就得再建设一套接口和业务流程,针对不同的支付场景也是各种新增接口,且交互流程上没有通用性,维护成本较高。所以 1.0 版本的实现方式实际就是 2.0 版本的一个具体场景的实现。
支付系统 2.0 诞生的背景是京东到家新业务的不断发展如: VIP 会员自动续费、无人货柜,线下自助结算等,在达达和京东到家合并之后新增了更多的业务系统如达达快送,达达骑士押金等业务,不论在交互模式、流程复杂度和交互协议上难度都有所增加,我们总结了 1.0 的一些痛点如下:
1 、由于从接口入参、交互流程都是定制化的,要接入新的业务系统,不得不重开一套接口及交互流程,而且没有通用的流程流转配置模块,对系统适配只能通过各种判断,每接入一个系统都需要开发。工作量和风险都非常高,不适合大规模系统接入。
2 、由于系统内部功能耦合度比较高,所以添加任何需求都可能牵一发而动全身,比如需要新增一种支付方式,需要从上层到底层所有流程都改一遍( 2.0 只需要适配支付渠道,对上层无感知)。
3 、数据模型也需要新增很多字段,系统容量上也不适应更多的业务接入需要对数据进行分片。
所以 1.0 的系统显然无法再满足要求,那就对支付系统 2.0 的要求肯定是高扩展、简单易用和高安全性的,不论是从使用者角度还是系统本身的灵活性和可维护性都要大大增强,并且要和业务系统彻底解耦,成为一个通用平台。当时也考虑过在 1.0 基础上迭代,经过分析后发现和新做一套难度相当,所以我们重新开发了 2.0 ,并且将 1.0 的业务逐步迁移到了新平台。
三、系统设计理念
1 、设计宗旨
一个设计完善的支付系统是要能兼容公司各种战略业务,各种交易场景和各种交易方式的,对系统的可扩展性和安全性都要求非常高,所以好的系统设计一定是满足六大设计原则的,即: 单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则、开放 - 关闭原则,系统内部也一定是 高内聚、低耦合的。
支付系统 2.0 的设计主要参考了领域驱动设计( Domain-Driven Design )的战略设计部分,即界限上下文和上下文映射部分,领域驱动设计对于软件复杂的应对策略其本质是高内聚低耦合,一个很形象的隐喻:细胞质所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。使用领域驱动设计之前有两个非常重要的前提,即:领域专家和统一语言,领域专家是指对领域业务非常熟悉的一个或一些人,他们可以是产品、研发或者运营等。统一语言的概念比较宽泛,其本质主要指在实现过程中,实现团队对领域概念,专业术语及实现目标是否理解一致,一个大的工程不可能一个人来完成,所以团队的目标是否一致非常重要。其次也参考了微内核加插件的设计思想。同时领域内部大量采用了工厂、模板、策略、适配器、门面、责任链等设计模式,更好的支持了开闭原则。
那如何划分领域上下文?在划分领域之前我们首先要足够的了解需求和交互方式。
2、需求梳理
通过对需求整理和斟酌,整理出了需求的大体结构图如下:
1) 要接入支付系统的业务系统有很多,如:到家平台业务、到家会员业务、智能货柜业务、自助收银业务、达达快送业务、达达商城业务、押金业务等。
2) 使用了多种终端,如: App 、 H5 、公众号、小程序、 PC 、自助设备、智能货柜等。
3) 使用的业务场景也很多,如: App 支付、小程序支付、 H5 支付、 PC 支付、扫码支付、刷卡(被扫码)支付、刷脸支付、自动续费(自动代扣)、找人代付等。
4) 支付方式包括:微信支付、京东支付、支付宝、银联、 QQ 钱包、 Apple Pay 、云闪付甚至第三方收银台等。
5) 支付模式:前置支付方式,即用户在结算页未提交订单之前就可以选择要使用的支付方式。后置支付方式,即用户提交订单后进入付款页面选择支付方式。
6) 收款账户方面:有些业务共用一套账户有些业务使用不同账户,需要足够灵活,支持业务选择。
7) 系统灵活的配置,同类产品不同业务系统接入不需要重复开发,拿来即用。
8) 保障交易流程安全和故障监控。
3 、交互流程梳理
我们可以参考第三方支付机构的交互方案,支付系统的交互流程也是建立在第三方支付机构支付产品上的,我们以微信支付为例,下图是微信支付提供的一些支持产品。
每种支付产品都有自己相应的交互流程,我们以微信 App 支付为例,下图是 App 支付提供的交互流程,其他产品的交互流程都可以从微信支付的开发平台去找,就不一一截图了。
4 、领域上下文划分
对于领域如何划分,领域驱动设计的两位作者 EricEvans 和 Vaughn Vernon 都没有给出明确规则,只是把领域归并为三大类:核心域,通用域和支撑域,这样给我们发挥和想象的空间就比较大了。为了给大家对领域驱动设计有一个感性的认识,我们可以用一辆汽车来类比,对于一辆汽车我们很容易想到汽车是由发动机、底盘、车架、轮胎等零部件组成,这样简单的界限上下文就划分好了,同样,我们把这些汽车零部件组装到一块就是一辆汽车,组装这些零部件首先要知道各个零部件直接的关系,这个关系就是上下文映射。
外界使用比较多的划分方式是通过业务的语义边界来划分,语义边界就像汽车的轮胎、发动机、底盘和车架一样,我们通过对支付系统的需求、第三方支付产品的交互流程、支付业务语义边界和 1.0 设计的缺陷进行分析和反复斟酌,将系统抽象出了如下几个领域上下文,即:支付场景、支付渠道、对账模块、通知模块、记账模块、退款任务模块、免密签约模块、商户后台(提供支付产品的授权、账户、安全等配置)等,每个领域上下文都有自己所关注的主题内容如下:
1) 支付场景进一步抽象了第三方支付结构对 App 支付、 H5 支付、小程序支付、扫码付、刷卡付、刷脸付、免密代扣等支持产品的定义,进一步规约了该产品的使用场景,并且标准化了对外接口。外部系统想要接入支付系统只需要先在商户后台配置上适合自己的支付产品挂载上需要的支付方式和支付方式授权的账户、秘钥等信息后按产品的交互流程接入即可。
2) 支付渠道对支付方式进行了抽象,适配了各种支付方式如:微信支付、京东支付、京东白条、支付宝、 Apple Pay ,银联、各类银行卡甚至是第三方的收银台等在交互、接口和协议上的差异,对上层屏蔽了底层接口的复杂性。
3) 对账模块则主要关注系统应收实收、应退实退是否持平和一些防重幂等逻辑,简单理解就是如果应收等于实收则属于付款完成,然后告知通知模块付款完成,由通知模块告知具体外部系统,是业务流程流转的发动机。
4) 通知模块主要适配了各个接入支付系统的外部业务系统,将支付结果和退款结果通知到业务系统,并且提供了各种通知策略和各类协议的支持。
5) 对于商户后台,我们把接入的业务系统都看成一个个商户,商户后台主要关注接入系统的授权、安全秘钥,选择的支付产品,挂载的支付方式和相关第三方支付机构账户。
6) 退款任务模块是在业务系统发生退款时创建退款任务,然后退款任务会调第三方接口等发起退款,退款完成后调通知模块去通知业务系统,完成交易闭环。
7) 免密签约是自动续费、自动扣款业务场景的一种授权,把它单独出来是因为签约过程完全是独立的,只要业务系统选择了自动扣费产品之后,该模块才会生效。
5 、领域上下文映射
划分完领域上下文之后,我们就要设计上下文之间的关系,领域驱动设计给我我们提供了如下 9 种领域关系模型,不仅适用于服务内部领域也适用于微服务系统之间。
合作关系( Partnership 简称 PS ):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
共享内核( Shared Kernel 简称 SK ):两个上下文依赖部分共享的模型。
客户方 - 供应方开发( Customer-Supplier Development 简称: CSD ):上下文之间有组织的上下游依赖。
遵奉者( Conformist 简称: CF ):下游上下文只能盲目依赖上游上下文。
防腐层( Anticorruption Layer 简称: ACL ):一个上下文通过一些适配和转换与另一个上下文交互。
开放主机服务( Open Host Service 简称: OHS ):定义一种协议来让其他上下文来对本上下文进行访问。
发布语言( Published Language 简称: PL ):通常与 OHS 一起使用,用于定义开放主机的协议。
大泥球( Big Ball of Mud 简称 BBM ):混杂在一起的上下文关系,边界不清晰。
另谋他路( SeparateWay 简称: SW ):两个完全没有任何联系的上下文。
通过对业务模块之间的交互关系和对领域上下文映射的理解,支付系统的领域关系映射图如下,其中 U 表示上游, D 表示下游。
通过上下文映射关系,我们明确的限制了上下文的耦合性,从上下文映射关系图中我们能看出,虽然调用关系比较复杂,但是模块间的耦合关系非常清晰。从上下游关系我们看到从业务系统接入到支付完成通知业务系统完成了一个交易闭环。
交易流程我们也以 App 支付为例来说下(不同的支付产品交互流程也有差异),业务系统通过商户后台配置模块进行了接入授权配置后去调支付场景,支付场景会写台账(可同时包含虚拟资产如优惠券、积分等),有实收资产就会发起对账,如果应收大于实收时,说明还需要在线付款,就会去调支付渠道来发起在线付款,付款之后,支付渠道通知台账记录在线支付的实收,这时候对对账模块来说又有一笔实收,需要发起对账,如果应收等于实收会调支付通知模块发起付款成功通知,支付通知模块会告知业务系统交易完成。
如果业务系统发起的是退款业务,也会先记录台账,发起对账后生成退款任务,有退款任务调支付渠道去完成整个退款流程后告知通知模块,通知模块去通知业务系统完成退款生命周期。
6 、整体架构
在架构设计方面,我们也参考了领域驱动设计提供的一些架构模型如分层架构和六边形架构等。
分层架构如下图所示, 是一种历史悠久的架构,通过分层架构,可以将系统按不同职责组织成有序层次,由于这种划分往往比较容易界定,也算是最常见和最受欢迎的一种架构 ,也是用的比较多的架构,此处不再细说。
六边形架构如下图所示, 实际上它也是一种分层架构,只不过不是上下或左右,而是变成了内部和外部。在《实现领域驱动设计》一书中,作者将六边形架构应用到领域驱动设计的实现,六边形的内部代表了application和domain层。外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。
由于支付系统作为一个平台为了更好的易用性和灵活性需要提供多协议支持,所以我们综合了分层和多边形的架构的优势,在应用层之上添加一层协议层,同时支持私有协议和公有协议,更好的去适配业务的发展。
四、安全设计
交易安全是支付系统的核心保障,那么在确定安全方面如何实现之前我们先来穷举一些可能引起安全隐患的场景,穷举之前,我先简易描述下交易的整个环节。
如上图所示,整个支付环节就是一个深度递归,业务系统(以达达快送为例,大家也可以去体验下),用户选完起始位置、终点位置及配送信息后提单后进入收银台,用户手机端完成支付后,支付系统会收到第三方支付机构(以微信支付为例)的付款通知,然后支付系统改完本地支付和台账信息之后对账模块会发起足额对账通知到达达快送平台去改用户订单的支付状态来完成订单流转,其中达达快送系统和支付系统,支付系统和微信支付之间都是通过公网 http 协议交互的,且都采用了 https 加 md5 签名方式,但这样真的就万无一失了吗?猛一看没有任何问题,因为 https 的交互是认为安全的,接口交互全程密文,业务系统和支付系统的交互,支付系统和第三方支付机构的交互都有数据签名,改任何数据都会校验不通过,但会存在被模拟通知报文的可能,如上图所示的微信支付结果通知支付系统和支付系统通知业务系统,这两个环节,且一旦秘钥泄露(如技术破解、业务人员离职等)会增加这种风险。不过想要模拟通知报文也没那么简单,因为任何交易数据传递都有唯一的一个交易单号,这个交易单号全程不明文展示,只会传递给第三方支付机构。只能说一旦秘钥泄露存在理论上的一些安全隐患,先假设所有的数据都被泄露了,可能的安全隐患如下:
1 )用户下单后不去付款,去模拟微信支付付款成功的通知来调支付系统,且用相同的订单数据和金额。解决方式如下图所示,支付系统收到微信的付款成功通知接口鉴权验证无误后再反查一次该订单在微信的付款状态(图第二个步骤所示)确保在微信端也是付款成功才往后续流程流转。
2 )如果绕开支付系统用户下单后直接模拟支付系统的通知到达达快送系统呢?那就需要达达快送系统同样的方式反查支付系统,确认是否支付系统的付款通知,实现环环相扣,在业务流程上没有漏洞。
3 )既然支付不好绕开,我先付款后再调支付系统去退款呢?同理也要实现环环相扣,保证任何交易的通知都是信任系统的。但是第三方支付机构是不支持反查授权的,不过想要模拟支付系统调第三方支付机构进行退款也没那么容易,除了上文中说的拿不到原交易单信息和秘钥之外,退款发起还需要额外的退款证书文件去签名才能进行接口交互。
4 )要彻底保障交易安全,还要周期性的做好支付系统、业务系统及第三方支付机构的交易数据对账,保证每笔交易三方相同,尽早发现问题堵上漏洞。
五、实施过程保障
要保障项目完美落地,光有设计图纸也是不行的,还要保障实施过程的质量,一个大的工程不可能一个人能完成,所以团队理解是否一致、是否统一语言很重要,而且还要阶段性进行复盘,保证项目进度和完成质量和流程周全,为此我们也整理了一些开发过程中的部分细节如下:
1 、分布式事务一致性考虑:分布式事务对支付系统的重要性不必多说,分布式事务的概念此处也不在细说,那支付系统在与业务系统、第三方支付机构交互过程中数据的完整性如何来保证呢?我们先了解下几种分布式事务解决方案的优缺点。
1 ) XA 方案:即“全票通过方案”,要求所有的事务系统必须全部准备好,才可以进行事务处理,这种方案其实是将事务问题抛给了各个数据库本身,好处是数据一致性很高,缺点是耗费性能,所以这种方案一般用的不多。
2 ) TCC 方案:即“局部通过方案”,要求部分事务系统准备好处理事务即可,相对比 XA 方案灵活了许多,同时它的处理方式是将事务问题交给系统本身处理,需要用大量的代码控制,优点是数据一致性也很高,缺点是控制事务的逻辑代码复杂冗余,性能也很差。所以这种方案也不常用。
3 )本地消息表:这是一种基于数据库:这种方案是基于数据库表的一种方案,由各个系统分建自己的消息表,记录数据的发起及接收,并给数据做状态标记,借助 MQ ,观察消息的状态来决定事务是否需要回滚。优点是代码量少,数据可以保持最终一致性,缺点是表需要维护,且对高并发的支持不怎么好。
4 )可靠消息最终一致性方案:这是一种目前市面上比较常用的方案,其原理与上述方法类似,也需要借助 MQ ,只是不再借助数据库的消息表,而是由系统发起一条预发送消息,当系统本身的事务执行完毕后再将 MQ 中的消息变为确认消息,同样其他系统接收到 MQ 的消息后开始处理本地事务,根据处理情况决定事务是否需要回滚。相对来说优点是事务控制较为灵活,缺点是不稳定因素较多。
5 )最大努力通知方案:这个方案的大致意思就是:系统 A 本地事务执行完之后,发送个消息到 MQ ,这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口,要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B ,反复 N 次,最后还是不行就放弃。
通过对以上分布式事务解决方案分析、支付系统的业务特性和参考第三方支付机构的产品方案,我们最终选择了第五个,即:最大努力通知方案 + 人工干预(或者系统对账) + 业务报警方式,这样不至于某一个系统由于系统本身问题产生的垃圾数据一直占用支付系统资源影响正常业务,一旦这样的数据量过多甚至会导致支付系统发生雪崩等问题。
2 、接口幂等性考虑:支付系统、业务系统和第三方支付机构都是采用最终一致性方案,肯定会发生接口被重复多次调用的情况,所以接口一定是要实现幂等操作,实现方式最好使用数据唯一主键且数据一旦创建不可修改的方式来防重。
3 、系统安全性考虑:系统安全主要包含业务流程上的漏洞和接口权限方面的漏洞,关于业务流程上的漏洞会在第四章节专门讲述,此处不再详说,接口权限的安全主要在接口鉴权上,每个对外接口调用都要进行鉴权,外部系统也只有被授权和约定鉴权规则之后才能被调用。
4 、系统容量统计分析:考虑到现有业务每天、每周、每月和日峰值及业务增长比例做事先容量规划,进行分库分表设计,以便满足较长时间的使用。同时也得对业务特性进行考虑,比如有些业务如押金等需要长时间保留在线数据不能归档,有些业务则有明确的交易周期,可选择快速归档,以便腾出数据空间。
5 、命名字典:需要提供一套供数据库字段、接口、参数、方法名等命名参考的数据字典,因为不同开发人员命名风格各不相同所以尽量保持一致,同一个概念用相同的单词,减少维护成本。
6 、对外接口设计:考虑到通用性和兼容性,需要把接口参数的定义划分为环境参数、场景参数、业务定位参数和业务具体参数等几大类,环境参数如调用端的 IP 、设备号、用户信息等用于风控。场景参数是具体的操作,如支付、退款类型和支付方式等,业务定位参数如订单号、支付单号等唯一定位数据的参数,业务具体参数是具体操作数据的明细。这样的接口设计会非常清晰。另外不同的参数类型代表的功能和操作权限也不一样,所以接口的哪些功能交给调用方(业务系统)去决定,哪些需要支付系统做默认操作权限都要仔细斟酌,既要保证灵活性,又要考虑权限和兼容性。
7 、异常设计:作为一款通用的平台,需要提供一套完善的 CODE 码机制非常重要,支付系统的各种问题都能通过统一规范的 CODE 码见名知意,减少业务系统接入支付系统的问题咨询量,这就需要支付系统必须细化每个环节。
8 、文档要求:对外提供的接口文档要按第三方支付机构提供的标准一样,针对每个产品详细描述使用场景和交互流程及对应的接口规范,减少问题咨询。
9 、系统健康度监控报警:系统的健壮性是一个系统能持续不间断提供服务的必要保证,但是代码、环境、基础设施、网络、中间件等都有可能不可避免的出现问题,且一旦出现问题我们需要的一定是最短时间内定位问题和解决问题,所以系统故障报警是必不可少的,但是也不能太多,会产生疲劳,所以我们要针对性的、有目的进行监控配置,也可以对报警做优先级别处理。
六、总结
支付系统作为达达 - 京东到家的基础服务,为各类业务系统的发展都提供了强有力的支撑,随着达达 - 京东到家业务的不断发展,支付系统也会不断的演进和优化,每一次演进和优化都朝着提供稳定可靠服务的目标走出坚实的一步,为达达 - 京东到家业务的飞速发展提供强有力的保障。
七、鸣谢
在整个项目的设计开发和推进过程中,也得到了来自前端、提单、订单中心、测试等团队的大力支持和帮助。尤其特别感谢高峰、戴枫、张磊和范倩同学 ,为整个项目的推进和质量保障花费了很多心血,使项目顺利完成,并且达到了设计预期。
八、部分业务的应用场景
在到家 App 的应用
在达达快送 App 的应用
在到家线下自助收银的应用
在到家智能货柜的应用(目前业务已下线)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Spring Into HTML and CSS
Molly E. Holzschlag / Addison-Wesley Professional / 2005-5-2 / USD 34.99
The fastest route to true HTML/CSS mastery! Need to build a web site? Or update one? Or just create some effective new web content? Maybe you just need to update your skills, do the job better. Welco......一起来看看 《Spring Into HTML and CSS》 这本书的介绍吧!