支付网关与异步通知设计
支付网关
用户下单成功后,要经过收银台发起支付流程,支付网关就是用户发起支付流程的入口地址。支付网关需要接收订单的部分数据(订单号、待支付金额、商品描述信息等)和交易数据(支付方式、交易起止时间、回调地址等)以及签名,支付网关接收到收银台的支付请求后,验证并处理支付请求数据,再根据支付方式获取支付实例(比如WechatAPPPayment对象),发起支付(执行doPay)。 支付交易流水表,以下重要字段:
Name | Field | remark |
系统订单号 | order_id | 商户订单系统的真实订单号 |
商户支付单号 | trade_no | 传给第三方平台的订单号 |
支付流水号 | out_trade_no | 第三方平台返回的交易流水号 |
支付金额 | total_fee | 订单支付金额 |
支付状态 | pay_status | enum(wait、success、failed) |
同步状态 | sync_status | enum(wait、success、failed) |
支付时间 | pay_time | |
异步通知时间 | sync_time |
支付网关设计,需要注意以下几点:
- 支付网关用来接收来自订单系统的支付请求,由于考虑到系统做活动要支撑比平日多出几倍甚至几十倍的QPS,在支付网关,就要考虑用消息队列中间件来缓存请求的支付数据,再有后端消费进程数据写入db,比如使用Redis List做队列服务,Redis Hash表做缓存。不建议单条订单数据直接做为key存储至Redis,Redis实例如果keys总量太大,会导致查询性能骤降。因为无法直接对Redis Hash表中field设置过期时间,我们需要写脚本按规则去清理老数据以腾空间。
- 如果系统使用php来编程,某些商业银行直连支付,建行app支付、招行一网通支付的验签流程需要通过JavaBridge来调用银行提供的jar包,完成签名,php实例化java类库性能较差。因此,要避免每次网关支付请求的初始化过程来引入这些jar包, 需要加载的时候再实例化。
- 商户支付系统在向第三方支付平台发起支付请求时,商户订单号字段不能直接使用商户订单系统的真实订单号orderid,要重新生成一个支付单号tradeno,为什么要这样做,有以下几个原因:
- 微信支付,不是直接call支付网关,而是先请求统一下单接口,获取预支付交易会话标识,然后有这个会话标识发起支付请求。那么问题来了,如果用户在app下单选择微信app支付,获取到会话标识后,没有完成支付,然后在商户的公众号平台发现这个订单,再次支付,系统会切换到微信公众号支付,这时候统一下单接口会报错,因为该订单已经申请过会话标识了。因此,需要重新生成一个新的支付单号,再来调用统一下单接口;
- 招商银行网关支付,对商户订单号的格式要求为6位或10位数字,因此,所生成的支付单号比较特殊,要按其要求生成;
- 对于拥有账户余额模块的商户平台,收银台一般都支持第三方支付和余额抵扣组合使用,已完成订单支付。当用户在多次选择或取消余额部分抵扣,导致订单支付金额变动时,已经通过接口申请到支付凭证的,订单无法再次申请支付凭证,需要重新生成一个新的支付单号使用。
支付异步通知
支付通知,是用来接收来自银行或者第三方支付平台的订单支付结果通知,分为两种,一种是同步通知(又称前台通知),一种是异步通知(又称后台通知),简单的说,商户支付系统收到支付同步通知并且支付状态为已支付,我们需要将订单支付状态修改为支付确认中,商户支付系统收到支付异步通知并且支付状态为支付成功,我们需要将订单支付状态修改为已支付。再次强调下,商户支付系统要以异步通知的结果为准。
异步通知设计,需要注意以下几点:
- 商户支付系统对已接入的第三方支付平台提供的通知地址不要重复,商户支付系统通过判断请求的URI就清楚的知道这是来自哪个平台哪一种支付方式的支付通知,不要尝试对已接入的第三方支付平台只提供一个公用的URL,然后通过解析报文格式来判断是来自哪个平台哪一种支付方式,这样做是混乱且不可控的;
- 支付系统拿到支付通知报文之后,首先解析成统一的格式,比如array,然后要验证签名是否正确、还要验证支付金额是否一致,最后再去判断订单支付状态;
- 支付系统对所有金额(float类型)的判断要先使用BC数学函数转换为int类型,再做判断,避免因php浮点数精度问题导致金额校验失败;
- 对已通过验证的订单数据,可以将其push到支付成功队列,有独立的常驻进程(队列消费进程)去通知订单中心,为什么这样做,有以下三个原因:
- 来自第三方支付平台的异步通知,是有第三方支付平台的服务端主动发起,目的只有一个,商户支付系统拿到支付通知报文并验证通过后,要立刻对其返回正确的回执,因此我们尽量不引入除接收支付报文之外的其他逻辑。我们商户服务器在接受到通知请求后,处理好数据并将其push到队列,是相对性能开销最小且稳定的做法,可以最快速度给第三方支付平台服务器以正确回执,且第三方支付平台不受商户自身业务处理流程的影响;
- 如果在异步通知处理流程中,直接发起对商户订单系统的通知,如遇到极端情况,商户订单系统出现异常或者响应过慢,势必会影响到商户支付系统对第三方支付平台的回执,虽然部分第三方支付平台有重发机制,但基于性能以及订单到账效率考虑,我们商户方尽可能做到一次就响应成功;
- 通过消息队列这一中间件,消费进程在对商户订单系统通知支付结果的过程中,如遇异常,没有收到商户订单系统的成功应答,那么我们可以将数据push到异常处理队列中,再有异常处理队列的消费进程定时去消费这部分数据,直到消费进程收到商户订单系统的成功应答为止。
原文地址:https://blog.csdn.net/think2017/article/details/79821281
原文地址:https://www.cnblogs.com/jpfss/p/9909075.html
时间: 2024-10-02 00:01:50