支付系统-收银台系统总结

支付系统-帐户系统总结

他人:支付业务与技术架构学习总结(6)——对账系统的设计 

原则:

    支付系统思考思路: 先考虑正常case, 先考虑并发[重复支付, 押金退款,提现], 再考虑事务中断.[重复支付]

    能幂等先幂等(需要采用数据库事务), 不能幂等优先选择资金安全的方案. [退款和支付的不同处理]

    避免事务中断,尽量采用面向对象嵌套法,每个内部子模块获取自己的数据,开头都需要对需要判断子模块实体的状态,外部系统写操作放在最后.

    对账对出事务中断的部分: 修改状态后人工修复.

    角色,用例,模块,依赖, 抽象,导致. [优惠模块为什么放置到订单之前,又双向依赖订单,而不是放置再支付中的原因]

    从业务抽死剥茧出头疼地并发场景.

    架构重构,千万不要盲目抽到新的模块. 没想明白之前,循环依赖会搞死你,虽然可以依赖倒置的概念,但maven不支持循环依赖. 测试驱动重构.

 看得懂,讲明白下面的这些文章就出师了:

Aggregate Framework是为方便开发人员运用DDD和CQRS思想来构建复杂的、可扩展的Java企业应用系统而提供的Java技术框架。该框架提供了Aggregate、Repository、Domain Event等构建块的实现;

 

 

  tcc github源代码 https://github.com/search?utf8=%E2%9C%93&q=TCC&type=

TCC是一个理念,其由Atomikos公司的创始人提出,如果想了解其具体内容直接到其官网下载个白皮书看下就好了,任何时候都是看官方文档才能更准确的获知答案。不过TCC只是分布式事务中的一个选项,且并非最优选项,这里有篇文章介绍https://github.com/QNJR-GROUP...

https://github.com/QNJR-GROUP/EasyTransaction 含多种模式 和 复合

https://github.com/prontera/spring-cloud-rest-tcc

try 就是冻结. 冻结资金,冻结券,或者直接修改订单状态. confirm时啥都不做. tcc三个步骤都需要保证幂等,否则问题很大. 如何做到自动幂等?

wsba oasis

通过几天的资料查找,对解决分布式事务的方法有两阶段提交、支付宝分享的TCCtry-confirm-cancel)和基于消息的最终一致解决方案,其中第一条和第二条虽然也能解决问题,但普遍对第三种基于消息队列的最终一致解决方案推荐多比较高,所以第一条和第二条可以参考使用。 from 分布式事务方案整合

订单处理:本地事务

资金账户加款、积分账户增加积分:TCC型事务(或两阶段提交型事务),实时性要求比较高,数据必须可靠。

会计记账:异步确保型事务(基于可靠消息的最终一致性,可以异步,但数据绝对不能丢,而且一定要记账成功)

看看别人对资金安全的总结 http://www.infoq.com/cn/presentations/correctness-ensure-of-funds-in-internet-financialsystem

商户通知:最大努力通知型事务(按规律进行通知,不保证数据一定能通知成功,但会提供可查询操作接口进行核对)

业务 方案1 方案2_黑ma
支付

订单支付

押金支付

出行卡支付

月卡充值

充值支付

 bill引擎,回调各业务实体.

 getFee 时新建实体,还是 success 时新建实体

 

 支付和费用变更

 费用不一致就退回.

 问题,券不可退.

 多退,少补的原则. 记录费用详情. (拆分同一个费用项 id)

 退款和打款  

退款记录再退款表

打款记录在提现表

退款和打款都记录在订单表.

打款依赖提现表.

提现表有两种类型: 司机提现, 退款打款.

费用,优惠记录

静态字段标识. 券 id.

后来优惠越来越多,开始意识到需要抽象出 支付表: 含支付类型,支付 id (券 id)等

抽象出支付表

会计

无会计统计

基于帐户地变动去进行会计统计. [ ]

记住: 借代表增加,贷代表减少.

业务收入

1、原借款时:借:其他应收款   2200
                贷:库存现金  2200
2、报销差旅费时:借:管理费用—差旅费   2000
                     库存现金  200
                    贷:其他应收款       2200

 

结合会计

  • 记账
    • 复式记账.
    • 会计凭证和账单.
      •  司乘分离的差额. 需要记录. 不能放在 pay 表里.
      •  多个收据对应一笔帐户变动
  • 何时冻结费用变更
    • 方案一 尝试支付后不可更改费用. 冻结费用最早进行.
      • 缺点明显,一旦尝试支付过就不能变更费用.
    • 方案二 冻结费用在网关层回调业务方. 先冻结,然后修改支付成功.
      • 缺点:方案复杂,解决万分之一的问题
    • 方案三 不冻结. 修改费用对主体状态的变更(还需支付,退款) 和 支付成功通知对主体状态的变更 需要并发加锁
      •  如果支付通知的费用和 现有费用不一致, 状态仍然为待支付. 判断期间费用不可变更
      •  变更费用后,仍需支付.那么就还需要支付. 如果需要退款.那么就退款.
  • 支付和退款退的那部分费用是什么?
    • 需要和费用模块联动.费用历史.
      • 方案一  费用模块用version, 每次变更保存新的费用项和增加 version. 费用模块保存 versionList
      • 方案二  支付保存费用项 list, 每次自己做计算.
      • 方案三  不用关心支付的费用是什么? 只关心总金额. 缺点是不满足有时候退款,需要根据费用来确定渠道退款顺序.(押金等)
  • 如何重复支付判断?
    • 方案1: 实体支付成功,我方未成功. 即重复支付.
    • 考虑并发情况.
      • 两笔支付都修改实体为支付成功,
  • 如何支付溢出判断?
    • 支付成功时,
    • 锁定实体.
    • 计算已支付金额..
    • 修改自己状态. 此时其他流水无法修改自己状态. 已经分布式锁住了唯一健. 有并发竞争的复杂 case. 表面上只改一个整体的状态,但实际上是对计算逻辑有竞争.
    • 是否支付溢出,那么就退款
    • 释放实体
    • 第二笔锁定实体. 计算已支付金额和已退款金额.
  • 代码能处理正常业务逻辑, 但是能否处理事务中断后的重试? 如何面对 mq 或者 dubbo 的重试? 
    • 要做到幂等(见下面条目)
    • 如果做不到幂等,那么就选一种没有资金损失的方案?
    • 案例1 重复支付判断
      • 原有逻辑
        • 实体未支付, 属于正常支付成功case. 修改实体为已支付,修改账单未已支付
        • 实体已支付, 属于重复支付case ,退款. 计算下多支付了多少钱, 多付了哪些费用项, 把多的进行退款.
      • 问题: 假设后续流程出错,整个流程重试, 实体已支付 ,认为属于重复支付 case,贸然退款就有问题,出现资金损失.
      • 解决方案1: 将自账单和实体统一起来判断.
          • 先判断:  判断实体已支付,我方已支付. 略过. 实体已支付,我方未支付. 重复支付. 实体已支付,我方支付. 已经修改过状态
      • 缺点: 假设正常case下修改实体为已支付后, 事务中断. 然后被 dubbo 或者 mq 重试.
      • 问题: 实体已支付,账单未支付. 变成重复支付,退款.资金损失.
      • 解决方案2:  重试最好导致的问题是本来要退款的变成了不退款. 案例思考: 先改账单支付,再改实体支付. 事务中断, 刚好此时实体被另外一笔支付支付. 然后被 dubbo 重试.
      • 问题: 账单已支付,实体已支付. 略过进行下一步. 本来要被退款的被忽略了. 乘客自己会来打电话,或者对账系统能对出来. 支付的金额(减去退款) !=订单的总费用
    • 案例2 帐户加款
  • 幂等. 只有做到了幂等,才能进行重试. 但是有些情况下,重试是mq,或者 dubbo 自动控制的, 如果没有肯定出现问题,选择一种不资损的方案.
    • 方案一: 流水+事务 [能够抵抗微服务拆分,分库]
    • 方案二: 状态前置判断法,
      • 1.设置事务: 内部操作流水+状态修改处于一个事务中,写外部系统无法,可以放在最后?
        • 缺点:不能够抵抗微服务拆分,分库. 例如:重复支付判断?
      • 2.不设置事务: 相信内部操作流水+状态修改处于一个系统,会尽可能得处于一个事务中. (要使用面向对象编程法: 形参传入最上层的 beanWrapper,内部包含了所有对象,所有外部数据都先获取好,组装好. (更新除外) 注意: 不要按需获取,避免调用外部挂掉. ) 否则中间随意调用外部系统,写操作除外,可以放在最后?
        • 缺点:肯定会出现事务中断,要选择一个没有资金损失的方案.
    • 双实体幂等控制(多对1操作) 都在同一个系统和数据库下,相信极大情况下都是一个事务内的. 可以通过对多和1的 双状态判断来进行幂等控制. [ 例如: 多笔账单对实体的支付成功逻辑, 基本上不会在实体侧保存一份支付成功流水,因为同一个数据库. 但如果微服务拆分后就不一样了,微服务拆分引发的幂等重试, 所以这种最好是幂等略过的方式 ,这样虽然数据会不一致,但是不会引发问题, 重复退款等.]
      •  多方支付成功,1方支付成功. 略过 .
      •  多方支付未成功,1方支付成功. 说明已经成功 (不考虑中断在中间的情况, 因为不采用分布式事务,也无从考证)
      • 多方
    • 双实体幂等控制(多对1操作变更)幂等判断, 两个实体在不同的系统中,必须要求1方记录多方的操作流水号. 例如 多笔支付重复支付和幂等判断 , 帐户的变更幂等判断.
  • 回调时金额不一致的处理. (有了支付溢出专题讨论后,这里就简单很多了)
    • 极端场景描述:
      •    乘客需支付10元
      •    唤起 app 尝试支付10元.未回调
      •    改费用,改成了9元.
      •    支付9元
      •    10元回调回来.
      •    9元回调回来.
    • 两个方案
      • 方案一,先设置为支付成功,再由业务方来确定是否全额退款,还是部分退款.
        • 如果是全额退款的. 就会照成问题. 和正常支付成功且部分退款的如何区分? 本质上不用区分.就需要看下未退款金额即可.

          业务上需要判断是否已支付. 不能单纯地看成功支付的笔数,而是要看总金额.

        • 如果是采用多退少补的方式. 如何明确退款对应的费用? 费用 version_from version_to. 押金多少都不动.已账单为主.
            •    乘客需支付10元
            •    唤起 app 尝试支付10元.未回调
            •    改费用,改成了9元.
            •    支付9元
            •    10元回调回来. ( 设置实体为支付成功,设置为支付成功,并且退1元. )
            •    9元回调回来.  ( 设置为成功,设置实体支付成功失败, 全额退款. 不退.)
      • 方案二 及早询问业务方是否费用变更. 同时冻结费用(越早冻结越好),如果变更如何应对.
        •  这样就会出现
          • 多退
            •  如果是多退部分,支付成功并部分退款
            •  如果是多退全部,支付关闭并全额退款. 这部分退款不需要对应着费用. 做一个标记,是全额原路退,还是业务退款.
          • 少补
            •  少的部分需要重新交. 交的那部分费用是什么,用 费用项表示. 负数
          • 少退
            • 如果是少退全部, 支付关闭并全额退款. 这部分退款不需要对应着费用. 做一个标记,是全额原路退,还是业务退款.
  • 支付有哪些抽象概念?
    • 费用:
    • 优惠 (最开始单一的券,就简单的用字段描述,后面发现优惠多了,只能用关联表):
      • 权益
      • 包月
      • 积分
  • 哪些是应收款项
    • 优惠的部分
  • 哪些是显性成本:
    • 优惠的金额. 首单减免,包月.
    • 优惠券
    • 权益折算成的减免时间,最终和原时间的差价
  • 哪些是隐形成本:
  • 哪些是隐形收入:
    • 包月部分
    • 余额部分
  • 哪些是用户关心的支付.
    • 余额+线上支付等
  • 关闭全额退款还是二次支付?
  • 费用是可变的,会导致实体状态是并发修改的? 回调流程,何时冻结? 是否需要提早冻结,层次很多的情况下.最底层网关层回调时是否就需要冻结?
    • 像滴滴, 黑马一层(可变,有并发),收银台一层(可变,有并发,一旦并发,就看谁先锁住,回调先锁住,状态无法回溯的话,那么就生成新的实体.最好是如此.),phoenix 一层

1.  抽象不同的阶段. ( 引擎模式,更让人思考, 每个业务增加一个流程点,  越上游收集越好,例子是 回调回来费用变更.)

   不同的阶段,分类主体不同. 比如计费阶段,根据业务分. 渠道阶段,根据渠道分.

2. pay 表 只保存乘客支付金额和优惠总额. 1 对多 优惠表

3. 优惠表里保存优惠类型,支付帐户,总金额.

4. 差额表里保存差额凭证 

 

业务和统一收银台的交互 ,和 业务和 phoenix 帐户系统的交互没什么区别. 但是区别在数据上,都利用了签名,利用客户端 以及 签名的把两个服务端的交互解耦掉了.(微信就没解耦,微信和网络影响服务器资源)

区别: 1. 统一收银台不仅传递了支付金额,还传递了各项费用. 面向的 orderId. 一个 orderId 对应多笔支付. 内部封装掉.

 

这种做法比较巧妙. 比较适合用在通用组件上.(需要回调业务方. 客户端回调,解耦服务端)

 

表结构设计:

  函数依赖要存储.因为可能会变. 老的订单会有问题. 卢peng 说不要存储.

 

重构:  支付宝鲁肃:支付宝全局架构重构实践 http://www.infoq.com/cn/presentations/cl-refactor-system-arch/

CAP theorem 理论在 多副本存储 和 分布式存储中理解

微服务架构的分布式事务解决方案 龙果学院 含源代码

 

posted @ 2017-09-08 14:42  fei33423  阅读(6505)  评论(0编辑  收藏  举报