PowerDotNet平台化软件架构设计与实现系列(15):支付平台

PowerDotNet个人项目中功能全面而强大的一个系统是支付平台。我对PowerDotNet的自信很大程度上来自于经过PowerDotNet重写后的支付、财务、结算、CRM等业务型公共服务系统的稳定运行。

使用PowerDotNet和PowerDotNetCore特别开发的业务逻辑型公共服务既有极大的业务价值,又具有很大的技术挑战,在我个人看来算是技术和业务紧密结合的典范,可按需二次开发。

个人开发支付平台的历史比较久远,投入时间较多,当然用自己的眼光看,早期的支付系统从基础组件运用到程序设计和编码实现,很多都不太令人满意,所以我一直想用PowerDotNet重写支付平台。

现在经过PowerDotNet重写后的支付平台已经达到生产级别,是PowerDotNet里最出色的稳定可靠、灵活可扩展、可维护性强且易运维的公共服务之一。

很多人可能会有疑问,PowerDotNet是不是只能开发一些相对简单的系统?就像前面介绍的基础数据平台,只有CRUD?或者像HCRM人员管理平台,只有略微复杂点的CRUD而已?

其实我认为所有业务系统都可以抽象出CRUD操作,技术为业务服务,有些系统的CRUD门槛还很高,甚至比万年不变的框架值钱且难写多了,在很多业务驱动的公司里,CRUD才是真正的核心竞争力。

个人开发生涯至今,碰到过很多业务逻辑复杂,可维护性恶劣的CRUD型业务系统,经常有各种各样的奇葩问题,让人产生一种编程是门面向巧合的玄学的感觉,优质的业务系统代码实在是凤毛麟角。

对如何写好CRUD这个问题各种方法论层出不穷,仁者见仁智者见智,至今还有很多争论,务虚和务实的选择而已,哪一种让你更喜欢更有成就感就坚持自己的选择。

你可能理解总结了很多技术原理,精通软件开发设计模式和架构,人手一个ORM和(或)代码生成器,对很多框架和中间件如数家珍娓娓道来。。。可CRUD却总写不好,这才是人间真实。

在某厂有两位高人大仙交接给我一份支付网关和银联支付代码,代码量不大,但三天两头崩可真有你的,看完代码我又笑了。魔鬼都在CRUD细节中,能让所有代码都变成魔鬼确实让人钦佩,咩哈哈。

万丈高楼平地起,让CRUD更快捷更轻松应对变化更容易扩展和维护,这是PowerDotNet出现的一个重要目标,也让我们多了一个减少出错的选择,所谓多一个选择多一条路,于我心有戚戚焉。

经过PowerDotNet和PowerDotNetCore重构优化后的支付平台命名规范优雅,分层和系统交互清晰合理,避免抽象能力逻辑能力和代码组织能力不强的开发人员陷入代码迷宫的泥潭。

重写后的支付平台,模块间耦合性大大降低,支付方式比早期支持更多样,单元测试覆盖率大幅提高,核心逻辑比原有代码量少了一半左右,关键是运行极其稳定,PowerDotNet平台基础居功至伟。

遵循PowerDotNet规范开发的CRUD型应用,不但功能强大高度复用,同时代码的可读性、可维护性和灵活性也是第一流的,让开发者更好体会CRUD的乐趣,别问我怎么知道的,我就是知道^_^。

本文介绍的支付平台可以认为是目前为止PowerDotNet的最佳实践中的最佳实践,主要是因为支付平台相关的应用比较多,不同系统和应用之间互联互通关系比较复杂,涉及的专业知识面非常广泛。

支付平台的最终目标是将公司资金的出入全部集中到平台进行统一管理,不论线上还是线下,国内还是国际,B2C还是B2B。

目前PowerDotNet实现的支付平台API接口初步统计有200多个,这还不包括未接入的支付渠道和辅助工具接口、查询统计类接口、线下业务接口、用户信息和公司虚拟货币接口。

支付平台涵盖了后端、前端和客户端的很多方面,主要包括多租户、分布式缓存、消息队列、回调通知、定时任务、配置中心、服务治理、多语言、事务、OLTP、OLAP、页面跳转、对账、证书、文件、凭证、票据、统计、风控、财务、结算、IoC、AOP、ORM、日志、安全、互操作、幂等、二维码、条码、监控、报表等。

支付平台Power.Payment目前支持B2C(线上和线下)和B2B(线上和线下)主流支付方式,和财务、结算、收银、风控、账户、CRM等都有紧密联系,是对接商户业务系统的直接而重要的桥梁。

请注意,真正完备的支付平台通常还包括收银、风控、账务、会计、清算、安全等重要子系统。本文介绍的偏重于直接涉及金钱出入的支付前置和网关部分,它们毫无疑问是支付中最重要的平台子系统。

几年前在写某司支付平台的时候,PowerDotNet还在开发和完善中,服务治理刚有雏形,不能充分利用很多开源组件功能,所以当时写的支付平台用的技术栈很弱,但是上线后也正常稳定运行,充分证明了抽象和逻辑的重要性,咩哈哈。

很惭愧,在支付系统做了一点微小的工作以后,对日常开发谈不上多上心,却坚定了我深度开发PowerDotNet的决心,人呐确实都不知道,自己多么不可预料,一个人的命运,当然要靠自我奋斗,但也要考虑到历史的进程。

经过最近几年的不断开发优化和完善,PowerDotNet基础组件和服务已经非常成熟,支付平台已经是PowerDotNet里涉及技术最全面互联互通最复杂的公共服务了。

和主流的开发框架相比,PowerDotNet开发最复杂最具挑战的业务逻辑型应用也是毫不逊色。

有了PowerDotNet,让我们撸代码更轻松更高效。

有了PowerDotNet,我们要做到业务逻辑型选手中的天花板。

有了PowerDotNet,CRUD也能兼顾设计的艺术和艺术的设计。

有了PowerDotNet,CRUD我们也是最专业的^_-。

言归正传。

支付是一个看上去容易,实际上很难深入,一旦深入又让人感觉非常简单的专业领域。

犹记得当年去某司刚接手支付系统的时候,被页面跳来跳去,界面改来改去,回调通知来通知去,接口调来调去,对账对来对去,日志和风控埋点插来插去,各种代码分支合并来合并去所支配的恐惧。

后来熟悉了之后,也许自视甚高惯了,觉得这系统除了工作量大而且过度设计性能恶劣以及代码稀烂之外(更专业的吐槽请猛击这里这里),真没多少技术含量,挑战性不强,咩哈哈哈^_^。

然后跳到下一家公司,本以为再也不会开发支付相关的东西,不到半年,一个玩票demo型的支付系统就到我手上了,看了源码后,我真想说你们还是另请高明吧,我实在也不是谦虚。。。。。。

果然天下乌鸦一般黑,没有最烂只有更烂,最后我就把demo型支付系统推倒重写了,间接考验了我的毅力和技术水平。

写支付应用,并不是拿到支付SDK和文档,写写接入逻辑拼接下报文就搞定的,这种毫不客气地说,都是demo水平,咩哈哈。

支付接入,需要抽象和提取,然后才能开发出通用高效、面面俱到、功能强大、接入灵活、稳定可追溯的平台系统。

我个人有相对丰富的支付平台的开发经验,积累的越多越能体会到公共服务设计的强大之处,下面就分享些个人经验之谈,不足或疏漏之处请注意甄别,适合自己的就是最好的。

环境准备

1、(必须).Net Framework4.5+

2、(必须)关系型数据库MySQL或SqlServer或PostgreSQL或MariaDB四选一

3、(必须)PowerDotNet数据库管理平台,主要使用DBKey功能

4、(必须)PowerDotNet配置中心Power.ConfigCenter

5、(必须)PowerDotNet注册中心Power.RegistryCenter

6、(必须)PowerDotNet缓存平台Power.Cache

7、(必须)PowerDotNet消息平台Power.Message

8、(必须)PowerDotNet文件平台Power.File

9、(必须)PowerDotNet个人用户管理平台Power.PCRM(后续详细介绍)

10、(必须)PowerDotNet人员管理平台Power.HCRM

11、(必须)PowerDotNet基础数据平台Power.BaseData

12、(必须)PowerDotNet定时任务调度平台Power.TaskSchedule

13、(可选)PowerDotNet数据同步平台Power.DataX

一、支付应用

1、应用划分

根据经验,大概可以分出下面截图这么多应用,随着前后端分离的流行,前端还可以分出SPA应用。

业务方客户端、H5和APP的支付功能主要通过支付网关接口接入,属于业务系统,不属于支付系统。

有些支付后端应用会直连银行接口,这样会产生一些额外的应用,比如安全守护进程、回调通知工具、支付凭证工具等,这里也没有列出来。

按照流行的微服务划分方法,支付服务如果按照分类进行接口拆分,还可以分出更多应用,尤其直连银行的情况下还会产生各种补偿和查询工具应用。

上图里这么多应用,只是变相证明我的微服务“理论”水平很高,其实最新版PowerDotNet将服务合并后真正开发用到的就是前面七八个应用,微服务一到实践环节就手指跟不上脑子,真的忙不过来了^_^。

在PowerDotNet微服务实践过程中,我发现自己走了一些弯路,某些系统或应用为了微服务而微服务真的是得不偿失,服务拆分粒度非常考验个人技术水平和研发经验。

PowerDotNet深受某厂SOA框架影响,潜移默化中就开发出了服务治理中心、配置中心和通用网关,除了没有直接容器部署,PowerDotNet算是最早微服务的践行者之一。

部署简单、自动伸缩、服务编排这些东西并不是Docker等容器技术出现之后才有的,在虚拟机或实体机上也可以做到,所以PowerDotNet目前还没有将全部服务改造为容器部署的动力。

PowerDotNet的设计初衷并不是为了所谓微服务,在微服务流行之前,PowerDotNet的很多设计和实现就已经存在了,说是微服务不过赶个时髦而已。

Java之父James Gosling(詹姆斯·高斯林)说过:“语言只是实现目标的工具,而不是目标本身。”,把这句话里的语言两字替换成各种方法论、框架或微服务同样适用,软件开发没有银弹,实践出真知。

当然,除了微服务,不论前端还是后端,服务还是页面,都需要花费大量的精力去设计开发完成,程序的分析设计阶段值得我们投入大量时间,所谓磨刀不误砍柴工是也。

2、应用开发

PowerDotNet和PowerDotNetCore从单体应用到SOA到微服务一路演进而来,保留了成熟开发模式的主要优点,架构清晰稳定,灵活可扩展,可按需选择合适的架构写出满足自己公司现状的应用代码。

PowerDotNet支付平台就是由我“站在巨人的肩膀上”一人独立设计开发实现的,PowerDotNet提供的平台开发工具为开发这个业务平台系统省了很多力气。比如说集成到服务治理平台的不带界面的接口:

又比如说动态可配置的支付收银台页面:

上面收银台页面只是示例参考,对用户显示的部分都可以通过后台动态配置(随手配置,不要介意图标重复),甚至样式模板都可以高度定制化,如有相似设计,英雄所见略同,咩哈哈。

当然还有常见的H5支付收银台,APP、Pad、自动售货机、一体机等专用的支付网关,这些在PowerDotNet支付平台下都开发了对应应用予以支持,本文不再赘述。

二、支付基础

我们设计的是一个支付平台,而不是只有支付功能。平台系统的最大特点就是接入的面要非常广,也就是典型的多商户或者多租户系统。

多租户系统划分的维度,常见的比如按照国家或区域划分,如中国,美国等;按照公司划分,如总公司,分公司等;按照商户划分,如内部商户,合作商户,外部商户等。

本文我们以经典的多商户系统进行分析讲解。

1、商户管理

主要功能是为接入的商户端分配编码和秘钥,安全接入签名都用到,同时还能按需动态配置显示支付方式、支付模板等,这些个性化的需求在一个复杂庞大的电商系统里随处可见。

 2、支付分类

主流的支付分类包括第三方支付、微信支付、信用卡支付、储蓄卡支付、网银支付、ApplePay、POS刷卡、公司自身的虚拟货币(如账户和积分)支付等等。

其中,信用卡支付又可以根据支付通道的不同,分为直连信用卡支付、银联通道信用卡支付等。也可以根据来源地不同,分为境内信用卡和外卡,支付的时候还需要区分DCC和EDC。

网银支付目前已经没落,很少有新的商户直接接入网银,而是主要通过支付宝通道实现网银支付。

对于特殊业务场景要考虑做到通用支持,比如常见的门店现金结算,我们还会抽象出Cash这一特殊支付分类,银企直连(银行转账),我们还可以定义BankTransfer支付分类。

担保支付是一种独特形式的支付,和信用卡预授权类似,不同公司有不同的业务形态,担保逻辑也可能不同。

3、支付方式

一个支付分类下包含一个或多个具体支付方式。

4、支付方式配置

支付系统的配置是非常多的,我是看过太多人把配置参数都放到配置文件里,或者写入配置中心,其实有经验的人都知道这不是最优选择,demo开发害死人啊。

腐烂的系统一大特征就是配置文件特别多,特别特别多的那种,尤其是还有人哼哧哼哧开发自定义的配置格式文件,真是奇哉怪也。

元数据设计大法和模板设计大法同时兼顾可维护性和可扩展性,这才是真正的最优解决方案,本文重点介绍支付平台,不细说这两种设计方法。

支付方式的配置主要包括实际支付请求的主要协议参数,还有就是回调通知URL路径等。支付方式多起来以后,各种支付方式的配置隔离非常重要。

 三、支付单

根据个人开发经验,业务系统中单据处理最大的难点通常有下面几种:

1、同一种单据,状态分类特别多,比如支付状态、退款状态、取消状态、回调通知状态等

2、同一种单据,相同状态分类下,状态类型多变,中间状态特别多,比如退款,可能有退款等待,退款申请中,退款审核中,退款处理中,退款成功,退款失败等多种状态类型

3、单据之间处理相互依赖,你中有我,我中有你,流程冗长复杂,难以保证事务性,补偿处理较难

传统的比较常见的业务订单系统和支付系统之间,通常都是通过支付请求唯一流水号和订单关联,找到支付方式和支付金额,然后将支付结果(如支付状态改为支付成功)写入业务系统中去。

这种逻辑看似简单,实际上随着系统的逻辑变得复杂,变得不易维护。比如引入退款、混合支付等逻辑,业务系统可能也不得不随着支付系统而改动。

支付单的设计思想是,将业务系统和支付系统解耦,支付的数据由支付平台来管理,业务系统通过关联的支付单号或流水号调用查询接口来查询支付状态即可。

这样业务系统只要保留支付流水号或者支付单号,自动就能获取支付状态,不用自己去维护支付逻辑。

按照不同公司的不同支付形态,支付单可以抽象为扣款支付单、退款支付单、担保支付单。

退款支付单的设计非常重要,非常考验一个人的设计水平。

除了常见的扣款、退款、冻结、解冻、预授权等指令,支付单接口还可以抽象出很多其他指令,比如代扣、分润、退分润、补差、转账、预付款等,按照实际业务需要来,支付单保留了创建更多指令的能力,便于按需扩展。

根据我多年的开发经验,直接接手过大大小小不少于三个支付系统,没有抽象出支付单的支付系统是短腿的、不完整的,有些甚至严重到拖业务后腿,这都是抽象失败的代价。

四、混合支付

不同的公司有不同的虚拟货币系统,混合支付是指公司的虚拟货币和外部的支付方式一起支付完成支付请求的一种玩法。这里面就涉及到一个很常见很典型的问题:事务性如何保证?

虚拟货币账户系统我也设计开发过,还是非常考验人的技术水平的,没有支付财务结算风控这些常识的,一般都开发不到位,尤其是应对各种财务结算报表业务变化的时候容易捉襟见肘疲于奔命。

至少个人接触到很多公司,在虚拟货币账户系统这方面的开发普遍有点吃力,不说了,说多了又要打击一波人自尊,咩哈哈。

PowerDotNet系列会有一篇文章讲讲虚拟货币系统开发。

五、大额支付

对于常见的电商业务场景而言,尤其是B2C,支付金额通常会有最大额度,比如我们常见的5万元。

但是不同的公司业务是不同的,买卖的商品金额也有巨大差异,对支付额度就有了不同要求。

以我们熟悉的汽车电商为例,车款通常金额都在5万元以上,这就对常见的支付方式有了特定的限制,必须有针对性的对接支持大额度的支付方式。

当然,在B2B业务场景下,企业和供应商之间的资金往来大于5万元再正常不过,也有银企直连、支票支付等支持大额支付。B2B付款在下面段落中单独说明。

还有一种场景也支持大额支付,就是最典型的线下POS刷卡,支持5万元以上额度的转账支付,这种支付形态非常适合先扣款,后创建支付单的场景,当然大额退款逻辑也需要开发支持。

六、B2B付款

B2C类的支付平台,主要面向终端用户,让用户付款给公司,或公司退款给用户;B2B类的支付平台,主要面向企业用户(如供应商等),公司付款给企业用户或者企业用户退款给公司。

本文介绍的支付平台主要是面向B2C形式的公共服务,而B2B形式的支付分类和支付方式远远没有B2C丰富多样,比如银企直联、支票、现金、POS刷卡等在B2B场景下屡见不鲜。

我们完全可以扩展支持B2B需要的支付分类和支付方式,做成B2C和B2B都通用的支付平台,当然也完全可以独立部署支持B2B形式的支付平台服务。

B2B和B2C类的大额银行转账付款单功能在财务平台系统里很常见,后续有空再介绍财务平台系统。

下图是支付平台开发的银企直连部分功能截图,将B2B和B2C、线上业务和线下业务统一抽象到支付平台,极大地降低了企业对接支付的难度和复杂度。

支付凭证文件处理需要按照配置模板定时自动生成并上传,在文件平台Power.File和定时任务平台Power.TaskSchedule的支持下开发难度明显下降了一个数量级。

银企直连开发中碰到的同行、跨行转账的差异,支付超时等异状,支付状态不确定状态的补偿等等问题,支付平台都有完善的解决方法,编程确实重在执行,需要不断实战积累经验。

某些公司会将相同支付平台服务独立部署多份(当然我也这么干过),支持线上和线下业务,也支持B2C和B2B独立支付,这样可以最大程度减少开发成本。

七、支付回调

1、外部支付方式回调支付平台

举例来说,比如用户使用支付宝支付完成后,支付宝会先回调通知到支付平台,支付平台改变支付单状态,然后告诉支付宝支付成功了。

从业务完成角度,从外部支付方式回调支付平台这里其实只是完成了支付功能的一个部分,因为业务方还没有拿到支付结果,还不能进行支付成功后的后续业务处理。

退款流程同扣款流程大同小异。

2、支付平台回调通知各接入商户

支付平台通过定时任务平台,定时回调通知各商户支付单已经支付完成。

退款流程同扣款流程大同小异。

通知队列需要抽象出来,系统多次回调不通过,可以人工补偿。

 支付系统里有大量的回调和补偿操作,定时任务调度平台Power.TaskSchedule真是功不可没。

八、信用卡支付

信用卡支付分类和支付方式可以单独拎出来说一整篇。

每家银行的信用卡服务接口都会有差异,我们需将最通用的指令抽象设计提取出来,以应对信用卡接口的差异。当然SDK还得一家一家对接,所以直连信用卡服务开发是一项枯燥繁琐极需耐心的工作。

信用卡预授权在很多业务场景下被广泛使用,支付平台开发的信用卡预授权相关的主要功能和通用核心指令包括:

1、预授权(PreAuth)

2、预授权撤销(PreAuthCancel)

3、预授权完成(PreAuthComplete)

4、预授权完成撤销(CompleteRevert)

5、退货或退(PreAuthRefund)

6、冲正(Reversal)

在支付分类里已经说过,其中信用卡还要区分内卡和外卡、DCC和EDC、直连还是银联等,还有部署环境的搭建,比如有无加密狗或U盾、银行信用卡客户端SDK对接等等事情需要费力操心。

九、财务对账

做过支付和财务的开发人员应该都知道,银行、第三方支付(如支付宝、财付通等)、微信、银联等都有完善的商户管理后台,业务人员(通常是财务)会定期按天或月将支付结果导出为Excel来对账。

对于常见的B2C或者B2B线下支付场景,如POS刷卡支付、支票、现金或者银企直连等,也需要定期调用银行提供的查询接口或者直接导出Excel文件并解析POS结算数据进行业务和支付数据对账。

根据个人开发经验,严格来说,对账应该划归到财务系统处理,但PowerDotNet的支付平台认为支付Excel解析以及银行、第三方支付、微信、银联、POS等的远程查询功能都应该由支付平台来完成。

这样做的最大好处是外部支付、退款、查询等功能集中统一,否则财务系统也需要配置各种支付方式的应用Id、密钥、证书等进行接口调用,通俗点讲就是让专业的人干专业的事情,保证系统“纯洁性”。

财务对账的主要目的是找出账目金额不准、或者缺少入账收支项、或者单号错误等等业务错误,通常对账都由公司财务人员来完成,解析Excel或者直接调用查询接口主要目标也是得到支付结果差异。

支付平台会定时按照时间范围或商户进行支付和退款数据解析和比较,生成差异结果并持久化保存,当然还要开发查询差异结果接口,供财务系统调用,虽然都是体力活,但系统边界更清晰更易维护。

每到财务月结的时候,通常也是开发人员人心惶惶的时候。完善的支付平台就应该有全面良好的所见即所得的对账工具,让时间宝贵的开发人员告别被大量Excel支配的恐惧,让财务对账更顺利更轻松。

支付对账功能能够很直接迅速的发现业务数据问题,当然也大大减少支付、财务结算等业务人员对各种Excel技巧的依赖,否则天天人工处理Excel,一定会等到哪天系统和人都崩溃吧^_^

十、系统交互

支付平台除了和银行、第三方支付平台、银联等外部渠道有直接的互联互通的交互关系,也和很多内部业务系统保持互通关系,整理下个人开发和对接过的几种常见互联系统。

1、订单系统

订单系统需要支付平台提供支付功能,支付平台回调支付结果给订单系统。

支付平台抽象出来的商户,基本对应不同业务的订单系统,订单系统又可以根据来源终端的不同拆分为多种终端商户,比如常见的按照PC、H5、APP、Pad等来源进行拆分。

2、风控系统

有支付则必有风控,没有风控的支付平台是不完整的,虽然银行、第三方支付、银联等支付通道也有风控,但在自己的系统里提前风控预警更高效安全。

风控系统按照支付平台的需要,可以按照配置策略动态组合风控到人、到信用卡、到订单或者到支付金额,也可以按需进行黑白名单同步或异步处理。

风控系统还支持根据特殊业务需要自定义排列组合风控规则,分为扣款前和扣款后风控,或者退款前和退款后风控,或者预授权前和预授权后风控,或者冻结前和冻结后风控等。

但是风控系统在支付系统的角色只能是辅助,主要起到锦上添花的作用,不能喧宾夺主,所以需要风控要有更高的稳定性、准确性和性能。

保证风控的稳定性和准确性,需要收集并做大量的人员数据操作和日志分析,而性能要求通常都是对风控接口设置可控的超时时间。

3、CRM

支付的最终对象说到底是对人,所以支付系统和HCRM、PCRM及ECRM也有千丝万缕的联系。

比如用户下单到支付平台支付,支付平台根据用户Id或用户名获取PCRM用户信息;或者在HCRM人员管理系统中员工提交报销申请单,财务系统审核后进行付款,这些都需要和支付平台交互。

当然支付平台和CRM的紧密程度,和CRM的设计有直接关系。后面有空介绍PCRM的时候还会说到和支付平台的关系。

4、财务系统

严格来说,支付功能是财务系统的重要组成部分,支付和财务天生紧密联系在一起。任何资金出入都需要在财务系统里进行记账和对账,当然这只是最表面浅显的功能。

在B2B和B2C业务中,线上和线下交易,转账汇款现金支票POS刷卡交易也是应有尽有,支付系统抽象的好能够让财务系统记账功能更加简单可靠。

PowerDotNet公共服务中也包括财务系统,后续有空会写出来介绍一下。

5、账户系统

常见账户系统主要管理用户的虚拟货币账户或类虚拟货币账户。虚拟货币是简化用户支付功能,提升用户体验的常见方式。虚拟货币最重要的功能包括充值、提现、支付和退款。

对于支付平台而言,我们完全可以把账户系统抽象理解为所在公司创建的支付方式,只不过这种支付方式除了支付和退款外,还有充值、提现等额外附带的功能。

6、票券系统

票券系统和账户系统有相似之处,主要的不同包括票券通常不能直接充值和提现,不支持退款,票券的用途主要针对某些支付特征而设计,比如活动订单、促销订单等等。

票券系统除了常见的优惠券、抵用券、折扣券等非支付可直接抵扣功能,还可以抽象成类虚拟货币的接口,提供给支付平台进行冻结支付扣款,个人见识过某OTA的票券系统,复杂程度让人叹为观止。

7、其他系统

其他如商品、活动等系统也和支付有些联系,就不一一写出来了。等我有空了,后续文章将重点围绕支付平台,介绍几种个人深度开发过的和支付紧密联系的公共服务平台。

支付平台可能和这么多的系统进行交互,如果设计实现不全面或者考虑不周到,很容易产生各种各样的业务逻辑问题,最典型的如幂等性缺失导致的创建重复单据或多次扣减数据等。

对于像支付平台这种类型的核心业务系统而言,正确性和稳定性是最重要最基础的需求,无论用什么技术什么语言什么框架实现,架构设计和经验都是重中之重。

PowerDotNet的支付平台许多设计和实现已经在生产环境被证明非常稳定可靠正确且性能表现不俗,对各种各样的业务边界问题也进行了精密处理,真正做到了Expect the unexpected.

根据经验和实践,异步化和串行化通常会降低业务系统间处理的复杂度,对于大中型解耦充分的系统,消息队列基本是标配,PowerDotNet的支付平台实现使用了大量异步化处理。

十一、全链路异步化

绝大多数支付场景都要求用户体验友好,低延迟高可用最快得到支付结果完成业务需求就是标配,在中小业务规模下这些业务指标并不难实现,很多公司都能做到。

在传统同步思路下,低延迟的目标能够较为轻易实现,可是对于高并发场景下,因为支付涉及的链路较长,大量的同步处理,伴随着许多IO和网络消耗,会有明显的性能问题。

虽然支付平台主要接口都预留了同步或异步参数以适配特殊的业务场景,但是支付系统全链路异步化是一个系统工程,有时候不得不要求业务端配合改造逻辑以支持异步处理。

全链路异步化,消息系统的高可用高性能设计至关重要。根据我的经验,全链路异步化做的好的支付系统,能够做到异步实现同步的效果,用户几乎完全感觉不到延迟。

如果基础设施扎实稳定,技术储备深厚,技术管理规范,消息队列就是最好的选择;当然,如果数据量不大,也没有必要一定用消息队列,数据库加定时任务也能起到消息队列的效果。

PowerDotNet支持上述两种消息策略处理模式,在配置中心配置下开关即可。任何系统如果加入外部依赖,得到好处的同时也会加大复杂度和运维难度,要按照实际情况进行技术选型。

十二、互操作

支付平台依赖很多外部SDK用于实现加解密、socket通信等功能,这些SDK的提供者可能包括公司内部的框架部门、公司外部的银行、第三方支付机构、银联等。

对接这些SDK可能经常需要做如下处理:

1、注册COM组件

regsvr32可以将 DLL文件注册为注册表中的命令组件,通过regsvr32命令行注册COM组件,可以将注册后的dll直接添加引用至.NET项目中,这种方式开发和部署都比较直接方便。

语法:regsvr32 [/u] [/s] [/n] [/i[:cmdline]] <Dllname>   

2、P/Invoke互操作

另一种是通过P/Invoke(Platform Invoke)实现从托管代码直接调用Win32 API或其他一些非托管代码,也就是互操作。

使用P/Invoke调用非托管代码的前提和主要工作就是确保托管/非托管代码之间正确的映射,包括:

(1)为使用的每个方法提供正确的声明

(2)完成方法参数、返回值的正确映射,包括基本类型、结构体、指针(函数指针)等

从代码层面来说互操作性,就是DllImport特性的使用,参考示例:

    public class CreditCardSDKEx
    {
        private const string DLLName = "CreditCardEx.dll";

        [DllImport("kernel32")]
        public static extern int LoadLibrary(string strDllName);
        [DllImport("kernel32")]
        public static extern int GetLastError();
        [DllImport(DLLName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern int Encrypt(string plainTxt, IntPtr lpVoid, ref string retStr);
        [DllImport(DLLName, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi)]
        public static extern int Decrypt(string encryptTxt, IntPtr lpVoid, ref string retStr);
  }
CreditCardEx

使用DllImport完成互操作,务必注意dll文件路径问题,尤其是Web应用程序,因为DllImport只能用字符常量,而不能使用Server.MapPath来确认物理绝对路径。

DllImport查找依赖DLL文件的顺序是:先在程序当前目录查找,找不到再到System32目录查找,还找不到就到环境变量Path所设置路径进行查找。

DllImport对托管DLL和非托管DLL处理还有些区别,整理如下:

(1)托管DLL,可直接添加引用到项目中

(2)托管DLL,通过DllImport间接使用,将托管DLL文件拷贝至应用程序根目录下,如果是web应用,拷贝至应用程序bin目录下

(3)非托管DLL 

在Asp.Net环境下,不论是WebForm还是MVC应用程序,非托管DLL 通过DllImport调用会有一个经典的路径处理问题,这也是实践得出的结果。

a、非Asp.Net应用程序,和托管DLL间接使用一样,将非托管DLL文件拷贝至应用程序根目录下即可正常调用。

b、Asp.Net应用程序,不能直接引用到项目中,放到应用bin目录下也不起作用,运行后会报错:仍然找不到该dll。这是因为Asp.Net环境下,CLR会把托管文件拷贝到一个临时目录下,然后在那里运行Web,这就是为什么把非托管DLL文件放到bin目录下仍然提示找不到该模块的原因。

解决非托管DLL在web环境下的路径问题方案有两种:

(1)[DllImport("完整的绝对路径")],这种最简单,但是不推荐,对于多台节点集群部署的应用来说,很容易埋坑。

(2)在服务器上新建一个目录,假设是(C:\Payment\WinDLL\),接着在环境变量中给Path变量添加这个目录,然后把非托管的DLL文件都拷贝到该目录下,最后[DllImport("dll名称")]即可。

注意:上面两种调用dll的方式,共同的缺点是会有兼容问题,发布应用程序的目标平台可能需要从Any调整为X86和X64平台,我自己已经不止一次在开发中遇到这种问题。

十三、其他

支付平台的其他主要功能还包括:

1、支付补偿和重试

针对支付宝、微信等的扫描、条码、声波等特殊支付方式,需要定时做补偿和重试操作,借助定时任务调度平台,这些都是没有难度的体力活,写写接口配置下补偿和重试job即可。

2、支付统计

从商户或者扣退款支付单维度进行多样性统计,财务系统对账可以参考。财务系统有空会单独写一篇文章讲讲。

 

 

3、支付数据结转与备份

自从用了DataX数据同步平台,这一块几乎不用写任何代码了,咩哈哈。

4、敏感数据脱敏处理

支付数据太敏感,尤其是信用卡CVV2和卡号后四位这些,支付好以后争取“阅后即焚”及时销毁,不要让人接触到。

在信息安全和个人信息保护越来越重视的当今社会,安全性是合格的支付平台必须要具备的基本特性。

5、银行卡号处理

包括信用卡、储蓄卡等卡号规则校验,卡号黑白名单校验,银行卡号通过卡BIN自动获取银行名称等,这些对于安全性和提升用户体验有很大帮助。

6、支付监控

根据实际业务和部署情况,抽象出支付业务正常指标,按需动态配置监控和预警参数,可进行短信、邮件、钉钉、微信等方式进行业务告警。

支付监控功能主要基于支付数据报表统计和定时结转功能,定时任务调度平台DataX数据同步平台真是帮了大忙,这也是我们要辛苦开发平台基础公共服务的原因。

7、支付接口安全

支付平台所有信息敏感的(尤其是和钱、余额、积分、资金、流水等相关的)接口必须经过严格的授权、鉴权、认证(校验token)、验签等操作,当然这些都是服务治理平台分内的事情,可以在服务治理平台后台管理系统点点按钮轻松搞定,所谓工欲善其事必先利其器,此之谓也。

8、审计日志

支付数据的后台人工变更必须有审计日志,对于支付财务这样复杂的系统,因为业务需要,红冲等后台操作可能几乎不可避免,所以必须做充足的审计准备。

posted on 2022-12-21 09:01  JeffWong  阅读(861)  评论(0编辑  收藏  举报