当前位置: 首页 > news >正文

消费金融系统开发回忆录

架构设计图

整个支付链路上的功能

支付系统应该有:账户管理、渠道管理、支付管理、对账管理、清算管理、结算管理        

一笔支付订单,在支付系统侧就是要记录清楚,谁发起的、对哪个商品进行支付、通过哪个渠道支付、支付时间、支付结果等

消金系统分层架构

        展示: SS          IQP

        流程:APS        TDL        TDC

                   TC         CORE      FA        MM        RMS/IRISK/RMFP

        核心:AMS        CIS       

        基础:SSP        OPR        ODX        PMS

9b0c47848d0c4ee0b0c64a3367d038b0.png

全局域划分架构

7585d960f1fd444c98bb930848387bb4.png

交互层

业务串联层

核心能力层

公共服务层

数据存储层

消金两大场景:

  • 提现
  • 消费

需要清楚账户系统的额度的变更,也就是通过中间的过程系统TDL/TDC,消金侧告知银行你可以对该笔请求进行放款,此时,银行才去真正的执行给客户打款

金钱流转动作额度发起方,一定是银行,这个都不用想,一切放款行为都是银行触发的,消金前置流程,是控制用户有没有用这笔钱的资格,后置的流程,是为了把银行的钱,和消金的账户做一个绑定。前置流程就是TDL调AMS看额度够不够用,后置流程就是银行本次给用户打了多少钱,消金的账户额度对应的是要加还是减多少额度,核算系统要生成多少钱的账单

从这个角度来看,AMS和风控系统都一样,都只是前置流程中的一个校验,AMS是检验额度够不够,风控系统是校验该笔放款是否合规

信贷三步

  • 贷前:主要是用户授信、实名注册的一个过程,这个过程顺利通过后,用户看到自己的授信额度,才到后面的动支行为
  • 贷中:就是控制用户当前能不能使用额度,也就是,提现/交易的整个过程
  • 贷后:对账、账单、还款

业务流程

定价

授信

对账

消费

二维码展示

用户打开消金app支付二维码界面,前端系统发起生成二维码的请求,请求给到TDC,TDC将二类户卡号等用户信息等给到银行,银行返回字符串二维码,TDC侧将字符串专为真正的二维码从而给到前端展示

原来最开始没进行域划分之前,功能很混乱,所有当时和前端展示有点沾边的功能,都丢在SS系统中的,比如支付二维码的生成

扫码支付

商户扫码枪扫描客户的消金app二维码支付页面,从而发起对银行的扣款请求,银行将扣款请求给到消金的支付网关TC,TC给到TDC,TDC走风控、额度扣减那套逻辑,额度扣减成功后,返回TC,TC将SUCCESS告知银行,此时,银行才从消金在上海银行的公户中,扣款打给商家的银行卡

商家扫描客户的支付二维码时,就会客户信息、消费金额信息,商家信息一起给到银行

疑问,上海银行,怎么知道每个商户的银行卡号?因为,是商家拿着自己在支付宝注册过的扫码枪扫描的用户从上海银行生成的二维码,商家肯定是提前在支付宝上注册填写过收款卡号的,或者说直接收到支付宝的余额中去

如果银行接收到了消金侧返回的SUCCESS,但是扣款失败,或者接收消金侧响应超时,就会实时发起冲正

提现

提现一定是银行管控整个流程,额度扣减都是后置处理,都是银行先把钱放出去后,银行才通知消金侧扣减额度的

用户能不能提取到这笔钱、能提取到多少钱,这个是和TDL和消金有关系的,而真金白银放款的流程其实和TDL关系不大,TDL只是一个结果的记录,比如银行把钱放出去了,放了多少钱,TDL调用AMS扣一下额度

提现流程,一定要分清楚放款到底是以文件形式,还是

文件形式,就是说别人放别人的,别人放成功后,再用文件通知你,你这边是需要有额度调整的。此时,就说明管控方是在别人那边

如果消金侧主动调用银行的打款接口,则管控方就是消金自己

自有业务

占用,是放款中的一个过程,是一个中间状态,表示该笔额度被占住了,不能用于其他场景了,是AMS为了保证同一时间,这笔额度只能用在同一个地方,防止额度用超

银行侧实际放款成功或失败,才通知AMS这边动用额度还是恢复额度

占用

用户输入借款金额,发起请求到银行,银行调占用接口到TDL,TDL内调风控通过后,调AMS额度占用(额度中间态),占用成功后通知银行允许该笔请求提现,从而银行给用户的提现卡号内打钱

动用

银行调用消金的动用接口,TDL通知AMS扣额度,通知core记账单,通知大数据等

说是占用动用两个接口,实际上在数据库中只对应了提现主表里状态节点的9个状态扭转

助贷

接入了平安普惠、支付宝、度小满、字节等

占用

比如客户在度小满发起借款请求,通过度小满的流控,某一次流控走到了消金侧,进入TDL开始风控、额度占用成功后,回调度小满的放款结果通知接收接口,告知度小满,我消金侧认可你这笔请求,你可以开始后续给客户打款的流程了。但是,度小满的动用文件,第二天才到,为了到第二天这期间,不让用户额度用超,消金需要控制额度,所以,引入了额度占用的逻辑

如果是平安普惠的优质资产助贷,则是TDL先调AMS占用,占用成功后TDL调用支付渠道网关TC,TC又调平安付开始给客户打钱。TC打钱成功后,回调通知TDL放款成功,然后TDL调AMS动用

如果没有占用,而是直接动用2w,那么客户就会看到数据不一致的中间态,用户查看额度少了2w,但是用户银行卡2w又还没有到账

美团要求10分钟返回,度小满要求2个小时返回,借呗要求2秒,所以借呗是单独一套流程,没有走MQ

如果是消金侧来控制打钱,则消金侧产出对账文件,如果是外部侧比如度小满控制打钱,则度小满来产出对账文件,消金侧来解析结账文件对账

外部调消金的uwg转发网关,进行统一加解密、金额单位转换结束后,调用支付渠道网关TC开始给客户打钱

动用

  • T+1日动用文件通知来扣额度之类的(常用,比较好追溯)
  • T日调接口

注意动用文件,与银行的对账文件的区别

消金内部系统

公共服务

综合查询平台 IQP

主要是运营人员使用的

共享支持平台SSP

单证合成

电子签名/印章

微服务网关openApi

助贷时,支付宝调用消金的系统时,首先就得通过微服务网关

业务网关 SS

统一外网的各种各样的参数风格,转换成消金内部统一的参数,比如消金内部统一的成功响应码是0000,失败是9999

这个系统慢慢的被分解,被微服务网关openApi和各个业务系统瓜分了

产品域

产品管理系统PMS

主要功能:产品配置、定价配置

每个用户,针对每个产品都有一个唯一的userId,公司内部的不同产品,不同系统之间通用的就是userId

定价查询接口,返回的是一个很大的json,由于在AMS侧需要比较定价是否超话,而直接比较大json不太方便,所以每个大json都会带一个摘要比如md5值,每次只用比较md5值就知道该定价信息是否变化过

营销域

营销管理系统 MM

曾经做过一个宜家消费满800返80的活动,还有全家消费30返回10块,不过都有名额限制,需要去抢名额

后来,这个抢名额的逻辑,被我放在了异步流程中了,异步流程中有调MM拿中奖名额,有发MQ给Core通知放款

电商平台下的营销系统

主要是发优惠券,搞促销活动

千万级别的全量用户发券,如果想争取在一两个小时内发完,就需要用分布式任务调度平台xxl-job,配合rocketmq来实现分片执行,削峰填谷

支付域

简单理解,就是真正和外界交互,进行打钱的系统

交易渠道网关 TC

定位:报盘,回盘,辅助对账

接一个渠道就是一个xxx-getway

渠道:建行,工行,平安普惠

和建行等直连成本低,其他的有手续费。就是从其他的快捷支付啥的有手续费成本高了,后来就直接和银行直连了,省钱

就相当于抢了支付宝/微信的一部分生意,可以将建行、工行的银行卡,绑定在平安消金的app上,然后通过平安消金的app来完成二维码支付

类似于微信支付的余额界面

财务域

财务系统 FA

大数据域

大数据系统 ODX 

可以理解成为数据中台,功能就是负责拉取TDC这边的支付数据,然后大屏幕准实时的展示每小时的交易笔数交易额度的变化情情况

风控域

风控系统 IRISK

二类户交易流程上每笔交易流程都会调用IRISK,来决定是否放行该笔交易

irisk主要是看是不是常用登陆地之类的监控

风控管理系统 RMS

风控规则:

客户评级评分:有客户的评级信息,一开始是ABCDE五个评级,每个评级的用户对应享受到的放款额度,放款利率都是不一样的。后来重构过一次,重构成L1到L10,10个等级了

授信出额:

调额:根据后台分析,如果准备给用户提升额度,比如提升2w但是提升额度这2w只能用于消费,那么就去调用AMS的调额接口,AMS就把小橙花二级账户的授信额度加2w,并同时把三级消费资金账户的可用额度也加2w,但是三级提现资金账户的可用额度额度不变

用户征信、是否逾期等的一些风控规则

风控系统 RMFP

d4b1fefd80cf41aeac05ff3beb768294.png

会员域

客户信息系统 CIS 

负责登陆、注册、注销、实名认证等的逻辑串联

客户号cust_no

实名服务:比如身份证信息、手机号信息,家庭住址等

卡服务:一个人可能会在消金App上绑定多张银行卡

后面有绑建行的卡,直接使用消金app扫码支付,不用通过支付宝微信就可以完成扫码支付

存用户身份信息,比如身份证、地址之类的。存消金内部自然人唯一编号 userId,供消金内部所有系统共用,方便问题追溯

存手机号,比如说后面办什么促销活动,就可以通过手机号给用户发通知短信

从技术的角度而言,所有的核心业务未必就适合于放在一个系统中处理,如贷款的账务处理和贷款的授信、审批以及贷后管理就不适合放在一个系统中,因为账务处理力求稳定和高效,而贷款的流程管理力求灵活多变,不同的功能需求更适合在不同的系统中处理。这也说明,核心业务系统未必就是一个系统,而可能是一个系统群。

银行核心系统设计目标

以客户为中心的设计

集中的客户信息能够对客户层次的所有账目和交易进行汇总。一些银行目前使用的核心业务系统正从传统的以账户为中心向以客户为中心转变,表现在客户的基础信息资料都必须在核心系统做维护(这里说的基础信息资料指能为多个系统所公用的信息,如名称、地址、身份证号码、组织机构代码等),某些系统专用的信息可以在专业的业务系统存储。

分散在各个外围业务系统的客户基础信息资料均需以核心系统客户资料为准,核心系统统一为客户分配客户号,各业务系统对客户的引用均以核心系统客户号为准。尽量避免一户多号的情况,以客户号为线索,可以查询出该客户在本行的存款、贷款、结算、理财产品购买等业务情况。

针对不同的客户提供有针对性的服务

对不同的细分客户群和层次,在服务档次和服务定价上有所区别。好的核心系统应该能帮助用户对客户进行细分,根据客户对本行的综合贡献度以及信用等级提供相应的服务,使银行有能力把更多的资源投入能给本行带来更多利润的客户上

客户号cust_no全局唯一,user_id是一个产品一个,多个user_id对应同一个cust_no

进件域

进件系统 APS

负责贷前鉴权,鉴别是否是本人,流程如下:生成流水、客户信息,跳转到第三方页面,客户输入卡号、手机号等,鉴权完成返回信息给我们

负责授信、出额、开户流程,这整个流程,是以APS作为主导来串流程,二类户交易是AMS/TDC来串流程

授信业务分为:

  • 公司授信业务:对公司法人
  • 国际贸易授信业务:
  • 个人授信业务:对个人自然人,房类、汽车类、经营类、消费类、教育类等

授信几个阶段:

调查、审查、审批、放款、贷后工作

把客户的所有情况了解清楚后,了解了客户的征信情况,评估好所有的风险,出一份调查报告

放款,是资金出银行的最后一道关口

信贷审批和审批授权

信贷审批,是指银行或金融机构对借款人提出的贷款申请进行审查、评估,并决定是否批准贷款的过程。这包括了对借款人的信用状况、还款能力、贷款用途、担保措施等多方面的综合考量。

审批授权,是指银行或金融机构在办理信贷业务时,对各级管理人员和业务经办人员授予一定范围内的信贷业务审批权限。这种授权是基于职责分工和内部控制的需要,旨在明确各岗位的权限范围、审批程序和相应责任。

授信审批大致可分为集中管理模式、分级管理模式和垂直管理模式三种模式。

用户进来注册授信时,请求通过前端打到SS,再打到APS,然后开始人脸之类的,最后是前面的很多校验都通过后才到AMS来调开户接口,调AMS来开户时,如果是新用户,那就是3层账户表一起开,授信开户,一定是针对产品开户的,不同的产品开不同的二级产品账户

账户域

账户管理系统 AMS

账户系统的5大核心功能:额度,账户状态,授信开户,定价,发票

a7c5ac9a53bc4b4891669ef9bb38ebcf.png

AMS三大业务闭环

  • 账户开户、销户
  • 账户状态冻结、解冻
  • 账户额度扣减、恢复

域划分前

AMS很庞大,AMS的业务是不清晰的,说白了就是不够“瘦”,负责交易、账单、还款,对账,整个公司内部各系统职责边界都划分的不清不楚,造成了很多因边界问题而起的矛盾

域划分后

AMS作为账户域只作为核心能力层,不负责流程的串联,不负责是否应该额度变更(包括额度扣减、额度恢复)的判断逻辑,所有的额度变更的发起都由外部系统发生。比如支付就由TDC向AMS发起扣减额度请求,还款就由CORE向AMS发生账户还款请求

AMS账户额度管控模块,只提供额度变更接口给别的系统调,至于是什么业务导致的额度变更,AMS就不去关心了。AMS账户状态管控模块也是一样,因为什么原因导致的账户冻结解冻,AMS都不关心,一切都由外部触发

消费定价更新

定时任务,定时从PMS刷新每个人的定价,因为如果PMS侧定价更新以后,AMS侧每个人的定价当月不能变化,需要等到下个月才能更新

重构交易的过程

重构前交易流程结构混乱,最关键的问题是,串交易流程的核心逻辑,不在一个类中,而是一个类串着下一个类,这样代码结构不直观

三级账户表结构

一级账户表,主账户表,AMS侧对应的主账户号是account_no,客户信息系统那边对应是cust_no

二级账户表,授信账户表,也叫产品账户表,记录小橙花等不同的产品,AMS侧对应的授信账户号是pro_credit_no字段,全公司内部通用的则是每个产品对应不同的user_id字段,

三级账户表,当前现状是有两张:消费额度管理表,提现额度管理表(有一个核心字段,已用额度,消费时是增加已用额度,还款时是降低已用额度)

430b5f0de2fd4fd2b7b589668a486abf.png

实际情况,三级资金账户表不需要两张表,只需要一张表,然后弄一个额度类型字段,提现和消费各对应一个额度类型就好

用一张三级资金账户表,提供一个amt_type额度类型字段,amt_type是CON消费资金账户、另一个amt_type是CASH提现、amt_type是ALL资金总账户额度(也就是,产品账户额度,这样二级产品账户账户中就可以不放产品额度字段了)

三级账户表,AMS侧每个三级账户对应唯一的资金账户号loan_acct_no,额度类型amt_type,总额度total_amt,已用额度used_amt,占用额度occupied_amt,账户乐观锁版本号data_version

二级账户表,可能有以下三种典型情况

小橙花,有10w的产品授信额度,其中消费和提现各5w,对应在三级资金账户表下,同一个小橙花产品下,有两个资金账户:一个amt_type是CON消费,总额度是5w、另一个amt_type是CASH提现,总额度也是5w (理想情况就是,消费和提现各种独立出额,互相不耦合)

度小满,属于助贷,有5w的产品授信额度,且只能用于提现5w,对应在三级资金账户表下,同一个度小满产品下,就只有一个资金账户:amt_type是CASH提现,总额度也是5w

借呗,属于助贷,有2w的产品授信额度,且只能用于提现2w,对应在三级资金账户表下,同一个借呗产品下,就只有一个资金账户:amt_type是CASH提现,总额度也是2w

还有一个单笔单批

健康的情况下就是,额度分开管理,也就是数据分开管理,单一职责,至于业务怎么用这些数据,业务需要用这些数据时,会加上哪些限制条件,这个是业务的事情,和数据存储的本身,一定要区分开,不要揉在一起

理想的情况是,消费和提现各种独立出额,各自都有5w额度,互相不耦合,使用消费时就只动三级表中消费账户哪条记录,使用提现时,就只动三级表中提现账户的那条记录,产品下不同的额度,分开管理,互不影响,这样我们就只需要给三级表加一个账户乐观锁版本号data_version字段,来保证某一条三级账户表记录自身的安全性,从而实现同一时刻,针对这同一天三级账户表记录,只能有一笔额度变更流程。额度变更流程,就包括对同一账户因为重复请求等原因,而同时发起了两次提现/消费,或者对某个三级账户进行提现/消费时,另外有一个流程在对这同一个三级账户记录进行还款

而当前消金小橙花,比如如果有10w的产品授信额度,其中消费消费10w,提现5w,对应在三级资金账户表下,同一个小橙花产品下,有两个资金账户:一个amt_type是CON消费,总额度是10w、另一个amt_type是CASH提现,总额度也是5w

此时,就已经把业务和数据揉在一起了,根据消金自己定的消费和提现占比1比0.5的业务需求,导致了上面的数据存储方式,导致用户消费6w后,还会影响到提现的额度只有4w了,但用户一直没有提现过,而当前还能最多提现却从5w变成了4w,但是三级提现账户表中的授信额度还是5w?

这个时候,就需要额度引擎上场了,额度引擎是公司业务规则的一个包装,

因为现在消金是把业务和数据揉在一起的,大量消费可能会影响提现额度,为了避免对小橙花的三级消费账户消费的同时,有另一个线程对该用户的三级提现账户进行提现超出该用户小橙花产品的授信总额度,所以,无论是小橙花提现还是小橙花消费时,都需要先拿一把以二级产品授信账户号为key的分布式锁。这都是因为业务的原因,导致了需要这些复杂的逻辑,实际上就应该把提现和消费额度分开,两者之间互不影响,只需要三级表各自的版本号乐观锁,来保证各自数据的安全即可

单笔单批

授信等于动支,

附加额度、临时额度

所有临时额度、附加额度、红包额度,都用单独的附加额度管理表来保存,区分不同的额度用途字段,附加额度管理表也有起止有效期字段

这张表,相当于另一张三级资金账户表,既然是三级资金账户表一定是要依附于某一个产品下的,比如我们当前的实现,是依附于小橙花产品下的

扣额度时,就要在where条件中,既判断额度用途,又判断额度有效期等条件都满足时,才允许扣额度

每个用户,针对每个产品都有一个唯一的userId,公司内部的不同产品,不同系统之间通用的就是userId。比如如果RMS根据一些风控规则,需要对某个客户的小橙花产品额度进行调额,就需要拿着该用户的小橙花userId来AMS修改该用户对应的二级账户的授信额度(同时,还会去修改三级账户表的现金额度字段值吗?)

按照业务情况,需要有额度变动的明细表的。我举的例子搞分布式,也就明细表加个状态字段而已

账户与额度的关系

账户讨论状态额度讨论有效期,这两个概念一定要区分开

账户被冻结了,其实额度是没有被冻结的,这是两个概念。也可能是额度有效期过了,但是账户状态是正常的。如果额度过期后,想让账户冻结,也要靠定时任务去刷

正常开的小橙花二级产品账户对应的额度,也是有有效期的,如果过了有效期,则需要调用AMS的账户激活接口,来更新小橙花产品额度的有效期

账户状态管控

身份证过期可能会导致账户冻结、风控系统检测到用户有高危行为也会来AMS调用账户冻结接口

账户状态管控一定是需要的,因为消金,需要控制哪些用户能用钱,哪些用户不能用钱

账户模型三层表,每层都有一个账户状态字段,从而精细化的控制每一层的账户状态

账户额度管控

额度讨论有效期,一般情况下,循环额度的有效期,都是控制产品额度的有效期,所以在二级产品表放有效期字段,一般不太会控制到三级资金额度的有效期的粒度,如果只过期提现资金账户额度,而不过期消费资金账户,那么就需要在三级资金账户中加入额度有效期字段。所以,我们要看情况,来看你的业务兼容到哪一层账户

额度变更,一定要有一张额度变更明细表,所有的额度变更,都要在这里留痕,做到快速可追溯,也就是这个入口一定要把控住

额度引擎

就是识别出当前正在使用哪个产品,并适配出当前正在使用该产品下的哪个资金账户,根据该资金账户的额度使用情况,来判断当前用户能不能在当前资金账户下使用额度,至于怎么实现的,就是三层账户结构

比如,当前用户开了三个产品,三家机构的额度,是怎么做到互不影响的?

围绕着三级账户来答,三个产品对应三个产品账户,每个产品账户下又有各自的分开管理的消费和提现资金账户,互相独立,互不影响

如果想针对某个产品,加一个红包功能,怎么加?

你们是怎么管理消费和提现,消费和提现过程中,如何保证额度不超限?

小橙花产品,对应有两个三级资金账户,一个是消费资金账户,另一个是提现资金账户,两个资金账户分开管理,然后两个资金账户有一个总的产品额度上限

小橙花产品有5w额度,如果是消费能用3w,提现能用2w,对应消费资金账户额度3w,提现资金账户2w,两个额度分开管理,互不影响

小橙花产品有5w额度,如果是消费能用5w,提现能用2w,对应消费资金账户额度5w,提现资金账户2w,两个额度耦合在了一起,互相牵制。

针对两个资金账户额度耦合在一起的情况,就需要加锁,可以加分布式锁同时锁住两个资金账户,也就是对提现和消费,统一加一把分布式锁,保证同一个产品下,提现和消费只有一个在途。也可以使用两个资金账户的版本号一起,整体组成一个乐观锁,从而保证小橙花额度使用不超过5w,如果不加锁控制,最大就可能5w的消费也出去了,2w的提现也出去了,就导致整个小橙花产品一共出去了7w,但该用户的小橙花产品额度却只有5w

三级账户表目前的现状

636702d20fa2417ba548044dc8f9dc3f.png

fc53e184096e4585b49954a145354743.png

d8ffc334ef0b4ebca7e97ce1512bbc25.png

d4b2e058ce2444c19fb08a69664ce806.png

 关键是授信额度字段

26b1a208259d46fdbfa71734bc1108f3.png

这里关键就是三个账户号,加上三个额度字段:现金额度、已用现金额度、占用现金额度

d01b7a8ee7ae483a933ecab537a71fd7.png

1e2e9aa1a739417c8bd0842ffce3535f.png

交易域

信贷放款系统 TDL

助贷

当时做过对接度小满、平安普惠,也就是说平安消金作为借贷的资金方,度小满和平安普惠是作为客户触达方

对接度小满金融时,是度小满将客户在度小满界面发起的借贷请求,流控到平安消金这端,平安消金这端经过一系列的检验,比如额度检验,风控检验等,检验通过后,调用AMS进行额度占用,占用成功后,带上该笔放款单号回调度小满的回调接口,通知度小满该笔通过,然后度小满自己调用真正的打款接口,给用户预留的收款银行卡打款。也就是这里,打款的控制者是度小满

度小满,给客户打款成功后,就可以通过调TDL接口或者以文件的形式,通知TDL去AMS进行额度动用

对接平安普惠时,打款的控制者就是平安消金自身,而不是平安普惠。依然是客户的触达方,将客户发起的放款请求给到平安消金,比如额度检验,风控检验等,检验通过后,有三个重要的步骤:步骤一,直接调用AMS进行额度动用、步骤二是带上该笔放款单号回平安普惠的回调接口,告知该笔放款成功;步骤二是调用TC的接口,TC再调用平安付的渠道,进行给客户真正的打款动作(步骤二和三调用失败,TDL侧会发起重试)

注:

说到这里,就要提一个生产问题,当时一个外包同事负责步骤三,结果调用平安付超时了没有拿到响应(实际该笔平安付那边已经执行成功放款5w的动作了),然后外包同事就进行了重试,重试时,他竟然换了一个放款单号,没有用原来的单号,结果就给客户又打了5w的钱,造成了二次放款的严重生产问题

整个TDL就是一个大异步流程,异步里面又用MQ进行了二次异步,又因为一条MQ有消费超时时间,整个放款流程网络请求过多,可能会让单条MQ消费超时,所以采用的自己发送自己消费的模式,每发送接收一次MQ只处理一个放款流程七八个业务节点中的一个

放款主表trade_loan_mgr,有一个流程状态扭转与回退的状态字段,每执行成功一个节点,流程状态就更新一步

外部支付调用异步化

72e2e774978a9cbadfa2f13b95a9e339.png

在外部支付中,经常需要服务方与第三方支付交互,获取预支付凭证,如上图所示。

这种同步调用的情况下,由于需要跨外部网络,响应的 RT 会非常长,可能会出现跨秒的情况。由于是同步调用,会阻塞整个支付链路。一旦 RT 很长且 QPS 比较大的情况下,服务会整体 hold 住,甚至会出现拒绝服务的情况

10c07efee08c36dc3f99de269fc066b6.png

因此,可以拆分获取凭证的操作,通过独立网关渠道前置服务,将获取的方式异步化,从前置网关获取内部凭证,然后由前置网关去异步调用第三方

如上,

2:三方支付收单,可以采用异步回调的方式,让三方支付系统返回一个受理凭证,三方支付系统完成真正的支付动作后,异步回调渠道网关的回调接口,当前TDL就是采用的这种回调的方式

实时交易系统 TDC 

四大功能:交易、退款、冲正、对账

6d44dfe3fff043958672c83199bba2c5.jpeg

主要有

  • 二类户交易
  • 好医生商城交易

二类户交易内部又有两个分支

  • 普通二类户交易〔非分期〕、
  • 商户分期交易

普通二类户交易CON,走普通的定价逻辑。商户分期,因为每家商户给定的利率都不一样,所以走商户分期时,需要每次交易都实时去查PMS定价系统,商户分期一般是3 6 12三期,三种选择

支付路由

商户分期POS,是针对和消金合作的商户而言的。是有一个预申请的概念,客户在勾选比如宜家的某个商品后,决定购买前如果决定使用商户分期,客户需要自己选择是3 6 12期,三种选择,选择好分多少期后,就可以生成对应的二维码,此时,就会在APS预先生成交易信息,合作商家扫描二维码后,开始真正的交易逻辑,交易请求到达TDC

交易流程,每次需要先去APS查询是否有预申请信息,如果有则走商户分期流程,否则走普通非分商户分期交易

普通交易流程,用户展示付款码或者用户扫描商家机器展示的二维码,注意,这个二维码都是银行生成的

交易信息落表、留痕

支付路由,确定是商户分期交易还是普通交易,来决定怎么查定价,普通交易的定价查本地,商户分期的因为每个商户的定价不一样,所以要传商户给PMS,来查询当前交易的定价

查CIS,查询是否为公司员工,从而采用固定定价

检验:账户状态检验、幂等检验、交易是否存在检验、各种限额检验、irisk反欺诈检验、rms用途管控检验、黑白名单检验、额度检验

检验都通过后,调AMS扣额度

扣额度成功后,更改交易状态

然后开始异步流程

调core核算系统计利息、调MM营销系统撞满800返80的活动、调大数据同步交易数据

支付流程图

ecd227e458d0426a90348b488b7b7960.png

33b8a92be2864c3e8dd6da62ff6f39f2.png

fbdc60527e704161bf7bf11c4fa31912.png

525c18191e494b62b575e89dd27718e5.png

技术点

当时用过分布式锁,用过幂等,用过延时消息,用过最终一致性,用过乐观锁版本号

延时消息

最开始的版本二类户交易,上行的交易请求发来后,如果在600ms内没有收到消金的响应,就会立马发冲正过来

但是,AMS的交易流程走完至少要两三秒,所以就把很多步骤放在异步流程里了,这其中就包括AMS扣减额度成功后,通知core开始进行针对该笔交易的记息(还有后续账单的展示)

有一次上行的冲正请求过来后,core给返回了一个没有该笔交易记录,但是core团队又在他们的数据库中,发现了这笔交易

总结起来就是,上行的冲正请求达到core的时间,比交易异步流程中通知到core的时间要快

直观的解决方案就是,让冲正请求来得慢一点,然后我就在AMS里,把上行的冲正请求,一进来就丢进MQ了,延时1min,然后自己搞了个消费者消费它,才继续走后面的冲正流程

交易流程全部同步的跑完可能需要两三秒中,所以把一大堆操作,挪到异步流程中了,同步流程中就只有必要的数据获取、风控校验、每月/每日的限额校验,额度扣减等核心步骤了

分布式锁

每月/每日的消费额度限制

当时做过一个需求,限制客户每日在某个门店的消费额度,限制客户每个月的消费总额度

以主账户号 + 商户号为key,当日消费金额为value

因为没有使用lua脚本,通过账户号 + 商户号为key查询到当日消费金额value,判断额度还没有超过当日5000的限额,就以主账户号 + 商户号为key,当日消费金额+ 当前该笔交易金额amt为新的value写入redis,这两个步骤无法保证原子性,所以就给这两个步骤,一起加了个redis分布式锁

版本号乐观锁

账户表加了一个版本号字段,TDC先查出账户额度 和当前的版本号m,然后做后续的校验逻辑,这段逻辑可能耗费几百毫秒,然后真正调用AMS扣减额度时,把版本号m传递给AMS,让AMS自己去比对库中目前的版本号是否与版本号m是否一致

核心交易逻辑

二类户交易的流程

当时的交易流程,是接口一进来,立马把所有的请求参数存一遍,交易主表trade_disburse_contract_mgr和交易事务表trade_transaction_detail

交易主表trade_manager,每比交易进来,都立马先记录一条记录,交易主表有个状态字段trade_status:初始化,支付成功,支付失败

最开始,搞了个大字段,把整个交易请求报文一股脑全丢进去,用来追溯问题的,后来被DBA劝退下架了,后来提出申请几台MongoDB专门存这种大json的非结构化数据,很合适,不过又被部门长拍死了

交易事务表trade_transaction_detail,交易/退款/撤销/冲正,等都要在这个表中添加一条记录,这个表有个关键字段 transaction_type,用来表示是TRADE REFUND REVERSED REVOKED 

交易的错误码

错误码一定要明细,0000是成功,其余的都是失败,但是失败的原因各异,可能是额度不够失败,可能是风控校验不通过失败,可能是每日限额失败,等等,不同的失败都要有各自不同的码值,这样才能实现精细化管理,问题的定位也会更清晰

二类户交易流程,黑白名单检验,是不是可以使用余弦向量比较法?

好医生商城交易

背景:平安好医生有自己的商城,在平安好医生商城购物,可以使用平安消金的消费额度

实现:整个实现采用流行的异步化的实现

好医生商城下订单的信息,是给到了APS保存,所以APS维护了订单的状态信息。

下单成功后,好医生商城侧,发生支付请求,支付请求经TC转入AMS/TDC。AMS/TDC接收到支付交易请求时,立马先完整的保存一遍交易信息,并在交易主表中记录支付状态为初始化状态

然后AMS/TDC用异步线程开启内部的后续支付处理逻辑,同时同步流程直接给好医生返回了

3秒后,消金自己的前端会发生轮训,通过支付单号来AMS/TDC查询该笔交易的支付情况

支付回调的逻辑,除了等第三方回调,还会提供主动查询结果的功能,这种是经典的分布式最终一致性解决方案

整个支付操作流程是,客户在好医生商城侧成功下订单后,点击开始支付,会首先弹出内嵌于好医生商城app执行的消金支付流程界面,客户在消金前端页面上发起真正的支付请求,此时的支付请求,才会打到AMS/TDC来。也就是说,此时的消金前端是能拿到好医生商城传过来的外部交易单号的

22年3月,交易迁移到TDC

23年2月,账户再次重构,重构逻辑未知

关于交易流程的最终一致性解决方案

蘑菇街交易创建过程中的分布式一致性方案

交易创建的一般性流程

我们把交易创建流程抽象出一系列可扩展的功能点,每个功能点都可以有多个实现(具体的实现之间有组合/互斥关系)。把各个功能点按照一定流程串起来,就完成了交易创建的过程。

面临的问题

每个功能点的实现都可能会依赖外部服务。那么如何保证各个服务之间的数据是一致的呢?比如锁定优惠券服务调用超时了,不能确定到底有没有锁券成功,该如何处理?再比如锁券成功了,但是扣减库存失败了,该如何处理?

方案选型

服务依赖过多,会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10 个服务,9 个都执行成功了,最后一个执行失败了,那么是不是前面 9 个都要回滚掉?这个成本还是非常高的。

所以在拆分大的流程为多个小的本地事务的前提下,对于非实时、非强一致性的关联业务写入,在本地事务执行成功后,我们选择发消息通知、关联事务异步化执行的方案

消息通知往往不能保证 100% 成功;且消息通知后,接收方业务是否能执行成功还是未知数。前者问题可以通过重试解决;后者可以选用事务消息来保证。

但是事务消息框架本身会给业务代码带来侵入性和复杂性,所以我们选择基于 DB 事件变化通知到 MQ 的方式做系统间解耦,通过订阅方消费 MQ 消息时的 ACK 机制,保证消息一定消费成功,达到最终一致性。由于消息可能会被重发,消息订阅方业务逻辑处理要做好幂等保证。

所以目前只剩下需要实时同步做、有强一致性要求的业务场景了。在交易创建过程中,锁券和扣减库存是这样的两个典型场景。

要保证多个系统间数据一致,乍一看,必须要引入分布式事务框架才能解决。但引入非常重的类似二阶段提交分布式事务框架会带来复杂性的急剧上升;

解决方案为:

电商领域,绝对的强一致是过于理想化的,我们可以选择准实时的最终一致性。

  1. 我们在交易创建流程中,首先创建一个不可见订单,然后在同步调用锁券和扣减库存时,针对调用异常(失败或者超时),发出废单消息到MQ
  2. 如果废单消息发送失败,本地会做时间阶梯式的异步重试;优惠券系统和库存系统收到消息后,会进行判断是否需要做业务回滚,这样就准实时地保证了多个本地事务的最终一致性

总结

  • 这里就是MQ的一个应用场景:天然的重试机制,当然都需要配合幂等消费措施
  • 蘑菇街的这个最终一致性解决方案,消金自己的交易流程,可以作为参考
  • 总结起来,很多最终一致性解决方案的根本原理是类似的,也就是将分布式事务转换为多个本地事务,然后依靠重试等方式达到最终一致性

参考链接:https://www.cnblogs.com/Vincent-yuan/p/16074577.html

转账案例的最终一致性

拿a转账b举起例子。a操作生成一个账户金额变更记录,这个记录添加一个state,标记记录未完成,事务提交,然后发送消息到b那边系统

a这边系统支持幂等,等待下一步b回调通知转账结果完成。而a在操作其他业务,其能操作的金额是过滤掉这个未完成记录后的金额部分。这种就是我说的语义锁,好像有一部分金额被锁住了,也就是冻结一个中间状态

账单域

核算系统 CORE

#贷款核心核算# 规划和实现贷款整个生命周期的现金流动。包括贷款发放、利息计算、利率调整、还款计划生成、贷款偿还或处置等,覆盖贷款的整个生命周期,核算根本要求:准!算得准并且记得准,每一笔钱不允许出现精度要求范围内的差额,一分钱不能多,一分钱不能少,每一笔帐不允许出现漏记、错记的现象

核算系统,负责贷后的相关逻辑:借据管理是基础能力。核心系统的主要职责是借据信息的存储和管理、分期计划和息费计算及交易流水管理。

比如每日跑批算利息、比如还款(还款试算)、比如账单管理,包括账单查询,账单分期

①借据。借据是用来标识客户的一次借款,每个借据都有全局唯一的借据号。借据包含各项息费金额、借款日、借据状态等基础信息。

②分期计划。借据会根据产品规则包含多个分期计划,如 3 期、6 期、

12 期、24 期、36 期等。每个分期计划都有各自息费对应的应还、已还以及分期状态等信息。

③交易流水。交易流水用来唯一标识客户的一次交易行为,如放款、还款、退款等。每一种交易行为对应一个交易码。同时交易行为对应的息费金额等信息的变化过程都会被系统记录和保留

                        

借据

借款申请对应借款单(借据),借款审批对应审批单据,额度扣减对应额度扣减记录,签署合同对应签约记录,生成还款计划对应账单,放款对应支付单

上述内容参考:DDD在有赞信贷核心系统中的实践 (qq.com)

分发路由系统

信贷放款需要资金支持,目前的信贷业务,除了使用自营资金承接外, 更多的资金来源需要依靠合作金融机构的资金池。每家平台都会接入几家甚至几十家的合作金融机构。

分发路由系统在设计时,需要.......

有的公司支付系统每天千万级交易需要尽快的收集到大数据平台参与计算,就需要用到kafka,来把每笔交易数据给到风控系统

大数据风控系统,每天都会计算产生一些结果,比如白名单、黑名单等,如果计算得出张三进入了黑名单,那就要在支付系统APP内给张三发一条站内信,这种黑名单的通知的量是比较小的,但是对可靠性要求比较高,这时就不需要用kafka,就可以选择RabbitMQ或者RocketMQ

RocketMQ天生就克服了kafka、RabbitMQ的缺点,又综合了他俩的优点,RocketMQ的一个小的缺点是RocketMQ的客户端只支持Java

相关文章:

消费金融系统开发回忆录

架构设计图 整个支付链路上的功能 支付系统应该有:账户管理、渠道管理、支付管理、对账管理、清算管理、结算管理 一笔支付订单,在支付系统侧就是要记录清楚,谁发起的、对哪个商品进行支付、通过哪个渠道支付、支付时间、支付结果等…...

org.springframework.context.ApplicationContext发送消息

1、创建消息的实体类 package com.demo;/*** 监听的实体类**/ public class EventMessage {private String name;public EventMessage(String name) {this.name name;}public String getName() {return name;}public void setName(String name) {this.name name;} }2、创建消…...

Java8-21新特性

简介 由于Java官方最近更新越来越频繁,而长期支持维护的版本LTS版每隔几年才推出一个,大规模商用的JDK只可能选择LTS版,因此这里只简单记录JDK8,11,17,21。 jdk8 Lambda表达式: Lambda表达式…...

NodeJS系列面试题

大家好,我是有用就扩散,有用就点赞。 有没有写过Koa中间件,说一下中间件原理,介绍下自己写过的中间件 koa本来就是一个轻量级框架,本身支持的功能并不多,功能都是通过中间件来实现不同的需求。开发者可以通…...

QXlsx读写excel

QXlsx读写excel 安装 QXlsx使用 qmake使用 CMake 基本用法1. 写入 Excel 文件2. 读取 Excel 文件 详细用法1. 设置单元格样式2. 合并单元格3. 创建图表4. 设置列宽和行高 完整示例 QXlsx 是一个用于在 Qt 应用中读写 Excel 文件的第三方库。它提供了丰富的 API,可以…...

昇思25天学习打卡营第13天 | mindspore 实现 ShuffleNet 图像分类

1. 背景: 使用 mindspore 学习神经网络,打卡第 13 天;主要内容也依据 mindspore 的学习记录。 2. 迁移学习介绍: mindspore 实现 ShuffleNet 图像分类; ShuffleNet 基本介绍: ShuffleNetV1 是旷视科技提…...

C语言超市管理系统UI界面

以下是部分代码。需要源码的私信 #include<easyx.h> #include<stdio.h> #include<stdlib.h>#define width 1280 #define height 840 #define font_w 35 //字体宽度 #define font_h 90 //字体高度typedef struct node {char name[100];//名字char number[1…...

BUUCTF逆向wp [MRCTF2020]Xor

第一步 查壳&#xff0c;该题是32位&#xff0c;无壳。 第二步 跟进main&#xff0c;发现反汇编不了 通过下图我们可以发现一串类似字符串的东西 第三步 我们看一下汇编 我们可以得到这些信息&#xff1a;flag的长度为27&#xff08;下面是对本条指令cmp edx 27指令的应用…...

Windows版MySQL5.7解压直用(如何卸载更换位置重新安装)

文章目录 停止mysql进程及服务迁移整个mysql文件夹删除data重启计算机重新安装 停止mysql进程及服务 net stop mysql mysqld -remove mysql迁移整个mysql文件夹 删除data 重启计算机 shutdown -r -t 0重新安装 https://blog.csdn.net/xzzteach/article/details/137723185...

详解数据结构之二叉树(堆)

详解数据结构之二叉树(堆) 树 树的概念 树是一个非线性结构的数据结构&#xff0c;它是由 n(n>0)个有限节点组成的一个具有层次关系的集合&#xff0c;它的外观形似一颗倒挂着的树&#xff0c;根朝上&#xff0c;叶朝下&#xff0c;所以称呼为树。每颗子树的根节点有且只…...

Linux----Mplayer音视频库的移植

想要播放视频音乐就得移植相关库到板子上 Mplayer移植需要依赖以下源文件&#xff1a;(从官网获取或者网上) 1、zlib-1.2.3.tar.gz &#xff1a;通用的内存空间的压缩库。 2、libpng-1.2.57.tar.gz :png格式图片的压缩或解压库 3、Jpegsrc.v9b.tar.gz : jpeg格式图片的压…...

STM32测测速---编码电机读取速度的计算

1、首先先了解一下计算的公式 速度计算&#xff1a; 轮胎每转一圈的脉冲数取决于编码器的分辨率&#xff0c;可由下面公式进行计算&#xff1a; PPR是电机的线数 以GA25-370电机为例。 图片来源&#xff1a;第四节&#xff1a;STM32定时器&#xff08;4.JGA25-370霍尔编码器…...

【已解决】服务器无法联网与更换镜像源

目录 问题描述&#xff1a; 1.修改网卡的 DNS1 和 DNS2 2.修改DNS列表 3.重启网络服务 4.切换镜像源 4.1备份原镜像源 4.2下载阿里云镜像源 4.3替换无法使用的域名 4.4刷新软件包缓存 4.5其他镜像源 5.阿里云镜像源开发者社区说明 6.阿里云DNS网址 7.DNS域名服务器…...

android11 屏蔽usb通过otg转接口外接鼠标设备

硬件平台&#xff1a;QCS6125 软件平台&#xff1a;Android11 需求&#xff1a;Android设备通过接usb转接线连接鼠标功能屏蔽。 考虑到屏蔽的层面可以从两个层面去做&#xff0c;一个是驱动层面不识别&#xff0c;一个就是Android系统层面不识别加载&#xff0c;本篇只讲后者。…...

HAL库源码移植与使用之RTC时钟

实时时钟(Real Time Clock&#xff0c;RTC)&#xff0c;本质是一个计数器&#xff0c;计数频率常为秒&#xff0c;专门用来记录时间。 普通定时器无法掉电运行&#xff01;但RTC可由VBAT备用电源供电&#xff0c;断电不断时 这里讲F1系列的RTC 可以产生三个中断信号&#xff…...

GIT命令学习 一

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…...

VS+QT 打包可执行文件.exe

切换成release版本&#xff0c;同时更改项目属性中release配置下的各个属性&#xff0c;确保匹配 重新生成解决方案&#xff0c;将生成的.exe复制到一个空白文件夹中 执行&#xff1a; cd D:\QT\5.12.10\msvc2015_64\binwindeployqt C:\Users\DELL\Desktop\serials\MainWind…...

Android笔试面试题AI答之Activity(2)

答案仅供参考&#xff0c;大部分为文心一言AI作答 目录 1. 请介绍一下Activity 生命周期&#xff1f;1. 完全生命周期2. 可见生命周期3. 前台生命周期4. 配置更改5. 特殊场景 2. 请介绍一下横竖屏切换时Activity的生命周期变化&#xff1f;1.默认行为&#xff08;未设置androi…...

来自Transformers的双向编码器表示(BERT) 通俗解释

来自Transformers的双向编码器表示&#xff08;BERT&#xff09; 目录 1. 从上下文无关到上下文敏感2. 从特定于任务到不可知任务3. BERT&#xff1a;把两个最好的结合起来4. BERT的输入表示5. 掩蔽语言模型&#xff08;Masked Language Modeling&#xff09;6. 下一句预测&am…...

代码随想录第十六天|贪心算法(2)

目录 LeetCode 134. 加油站 LeetCode 135. 分发糖果 LeetCode 860. 柠檬水找零 LeetCode 406. 根据身高重建队列 LeetCode 452. 用最少数量的箭引爆气球 LeetCode 435. 无重叠区间 LeetCode 763. 划分字母区间 LeetCode 56. 合并区间 LeetCode 738. 单调递增的数字 总…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...