文中咱们从严谨的角度一步步聊到支付如何演变成独立的系统。内容包括:系统演进过程、接口设计、数据库设计以及代码如何组织的示例。如有不足之处,欢迎讨论共同窗习。git
我记得最开始工做的时候,全部的功能:加购物车/下单/支付 等逻辑都是放在一个项目里。若是一个新的项目须要某个功能,就把这个部分的功能包拷贝到新的项目。数据库也原封不动的拷贝过来,稍微根据需求改改。github
这就是所谓的 单体应用 时代,随着公司产品线开始多元,每条产品线都须要用到支付服务。若是支付模块调整了代码,那么就会到处改动、到处测试。另外一方面公司的交易数据割裂在不一样的系统中,没法有效汇总统一分析、管理。数据库
这时就到了系统演进的时候,咱们把每一个产品线的支付模块抽离成统一的服务。对本身公司内部提供统一的API使用,能够对这些API进一步包装成对应的SDK,供内部业务线快速接入。这里服务使用HTTP或者是RPC协议均可以根据公司实际状况决定。不过若是考虑到将来给第三方使用,建议使用HTTP协议,json
系统的演变过程:数据结构
总结下,将支付单独抽离成服务后,带来好处以下:app
若是咱们接手该需求,须要为公司从零搭建支付系统。咱们该从哪些方面入手?这样的系统到底须要具有什么样的能力呢?异步
首先支付系统咱们能够理解成是一个适配器。他须要把不少第三方的接口进行统一的整合封装后,对内部提供统一的接口,减小内部接入的成本。作为一个最基本的支付系统。须要对内提供以下接口出来:数据库设计
/gopay
/refund
/notify/支付渠道/商户交易号
/return/支付渠道/商户交易号
/query/trade
/query/refund
/query/bill
/query/settle
一个基础的支付系统,上面8个接口是确定须要提供的(这里忽略某些支付中的转帐、绑卡等接口)。如今咱们来基于这些接口看看都有哪些系统会用到。学习
下面按照系统维度,介绍下这些接口如何使用,以及内部的一些逻辑。测试
通常支付网关会提供两种方式让应用系统接入:
下面为了讲清楚设计思路,咱们按照 网关模式 进行讲解。
对于应用系统它须要可以请求支付,也就是调用 gopay
接口。这个接口会处理商户的数据,完成后会调用第三方网关接口,并将返回结果统一处理后返回给应用方。
这里须要注意,第三方针对支付接口根据个人经验大体有如下状况:
这里因为第三方返回结构的不统一,咱们须要统一处理成统一格式,返回给商户端。我推荐使用json格式。
{
"errno":0,
"msg":"ok",
"data":{
}
}
复制代码
咱们把全部的变化封装在 data 结构中。举个例子,若是返回的一个url。只须要应用程序发起 GET 请求。咱们能够这样返回:
{
"errno":0,
"msg":"ok",
"data":{
"url":"xxxxx",
"method":"GET"
}
}
复制代码
若是是返回的结构,须要应用程序直接发起 POST 请求。咱们能够这样返回:
{
"errno":1,
"msg":"ok",
"data":{
"from":"<form action="xxx" method="POST">xxxxx</form>",
"method":"POST"
}
}
复制代码
这里的 form 字段,生成了一个form表单,应用程序拿到后可直接显示而后自动提交。固然封装成 from表单这一步也能够放在商户端进行。
上面的数据格式仅仅是一个参考。你们可根据本身的需求进行调整。
通常应用系统除了会调用发起支付的接口外,可能还须要调用 支付结果查询接口。固然大多数状况下不须要调用,应用系统对交易的状态只应该依赖本身的系统状态。
对于对帐,通常分为两个类型:交易对帐 与 结算对帐
交易对帐的核心点是:检查每一笔交易是否正确。它主要目的是看咱们系统中的每一笔交易与第三方的每一笔交易是否一致。
这个检查逻辑很简单,对两份帐单数据进行比较。它主要是使用 /query/bill
接口,拿到在第三方那边完成的交易数据。而后跟我方的交易成功数据进行比较。检查是否存在偏差。
这个逻辑很是简单,可是有几点须要你们注意:
针对这些状况都须要有对应的处理手段进行处理。在个人经验中上面的状况都有过遇到。
金额不对:主要是因为第三方的问题,多是系统升级故障、多是帐单接口金额错误;
第三方无交易数据: 多是拉去的帐单时间维度问题(好比存在时差),这种时区问题须要本身跟第三方确认找到对应的时间差。也多是被攻击,有人冒充第三方异步通知(说明系统校验机制又问题或者密钥泄漏了)。
本身系统无交易数据: 这种缘由多是第三方通知未发出或者未正确处理致使的。
上面这些问题的处理绝大部份均可以依赖 query/trade
query/refund
来完成自动化处理。
那么有了上面的 交易对帐 为何还须要 结算对帐 呢?这个系统又是干吗的?先来看下结算的含义。
结算,就是第三方网关在固定时间点,将T+x或其它约定时间的金额,汇款到公司帐号。
下面咱们假设结算周期是: T+1。结算对帐主要使用到的接口是 /query/settle
,这个接口获取的主要内容是:每一笔结算的款项都是由哪些笔交易组成(交易成功与退款数据)。以及本次结算扣除多少手续费用。
它的逻辑其实也很简单。咱们先从本身的系统按照 T+1 的结算周期,计算出对方应该汇款给咱们多少金额。而后与刚刚接口获取到的数据金额比较:
银行收款金额 + 手续费 = 我方系统计算的金额
这一步检查经过后,说明金额没有问题。接下来须要检查本次结算下的每一笔订单是否一致。
结算系统是 强依赖 对帐系统的。若是对帐发现异常,那么结算金额确定会出现异常。另外结算须要注意的一些问题是:
针对上面的问题,你们根据本身的业务需求须要作一些方案来进行自动化处理。
财务系统有不少内部业务,我这里只聊与支付系统相关的。(固然上面的对帐系统也能够算是财务范畴)。
财务系统与支付主要的一个关系点在于校验交易、以及退款。这里校验交易可使用 query/trade
query/refund
这两个接口来完成。这个逻辑过程就不须要说了。下面重点说下退款。
我看到不少的系统退款是直接放在了应用里边,用户申请退款直接就调用退款接口进行退款。这样的风险很是高。支付系统的关于资金流向的接口必定要慎重,不能过多的直接暴露给外部,带来风险。
退款的功能应该是放到财务系统来作。这样能够走内部的审批流程(是否须要根据业务来),而且在财务系统中能够进行更多检查来以为是否当即进行退款,或者进入等待、拒绝等流程。
针对第三方主要使用到的其实就是异步通知与同步通知两个接口。这一部分的逻辑其实很是简单。就是根据第三方的通知完成交易状态的变动。以及通知到本身对应的应用系统。
这部分比较复杂的是,第三方的通知数据结构不统1、通知的类型不统一。好比:有的退款是同步返回结果、有的是异步返回结果。这里如何设计会在后面的 系统设计 中给出答案。
第一部份的内容就到此结束了。若是有什么疑问欢迎到咱们GitHub主页留言。
GitHub: https://github.com/skr-shop