前面0基础了解电商系统如何对接支付渠道详细介绍了支付环节中参与的角色以及数据流向。今天从技术实现方案讲述电商系统如何对接支付渠道的解决方案。
- 本篇文章会涉及到部分代码,不管是产品人员还是开发者都无需过多关注代码,只需关注整个解决方案是如何实现即可。
- 本篇文章仅陈述出笔者在企业级开发中接触到的解决方案,不代表是行业标准,但是技术实现基本大同小异。
- 笔者只参与过信用卡的对接,本文的解决方案基本围绕信用卡对接做例子。不管是银行卡对接、信用卡对接、电子钱包对接,前后端交互流程基本大同小异。
- 考虑到以产品岗位和开发者的角度去做总结,笔者将先以业务解决方案给出方法论,然后再给出技术实现细节阐述对接过程中的核心要素。
了解具体的技术实现方案之前,先了解电商系统是如何与渠道方交互的
概念解释: 收银台模式是一种非直连模式,即发起支付后,支付渠道给回来的响应报文并没有支付结果,而是给了一个收银台界面地址,需要电商系统重定向到该地址。
下面给出交互流程图:
解释:
概念解释:直连模式是指发起支付后,支付渠道方返回的响应报文要么是含有3D验证的url地址,要么是支付结果。
下面给出交互图:
解释:
直连模式与收银台模式的交互都基本相似,区别就在以下几方面:
收银台模式:
直连模式:
提前寻求对方协助。包括但不限于:拿到渠道方的后端控制台访问地址并拿到收款账号,请求对方协助开通收款账号,请求对方协助配置同步地址、异步回调地址等,询问是否能关闭金额限制,询问是否能给出测试卡号等,询问对接过程中是否能做特殊处理达到调通流程的目的,等等。一切的目的都是尽可能地加快调通接口速度,能申请协助的资源尽量申请,委婉地表达能否为了调通接口而做某些特殊处理(很多成熟的支付渠道会有自己的一套风控规则,对接的过程中很容易就触发了风控导致支付失败)。
用postman按照接口文档调通流程。找渠道方拿接口文档,自己先用postman调通各个接口,包含但不限于发起支付、webhook回调、查询支付结果等。
写代码先写核心接口。核心接口有发起支付、webhook回调。有些发起支付流程是由创建订单、发起支付单这两个接口组成的。次要的主动查询订单接口可以等核心接口调通之后再慢慢加上去。
定义支付方式的编码。比如对接了一个名为QPay的支付渠道,对接它的信用卡方式(收银台模式),那么我定义该支付方式的编码为 QPay
。如果对接它的信用卡方式(直连模式),那么我定义该支付方式的编码为QPay(Direct)
。(收银台模式:发起支付后,渠道方给回来的响应报文没有支付结果)。
确定是什么支付方式。比如有银行卡、电子钱包、信用卡(收银台模式)、信用卡(直连模式)。
以订单为维度在发起支付、支付回调加分布式锁,防止重复支付。 针对同一笔订单,给它加一个分布式锁,防止重复支付。导致重复支付的情况有很多种:(1)未防重导致的重复支付。(2)掉单导致的重复支付。掉单也有内部掉单和外部掉单两种情况。内部掉单通常是未翻转支付状态。外部掉单通常是渠道方没有返回webhook消息。(3)多渠道导致重复支付。比如先打开支付宝支付界面,再用微信支付,最后回到支付宝支付。都是可以成功的。
组装请求参数。核心的请求参数有订单号、同步回调地址、异步回调地址、最小金额单位、币种。能不传的字段尽量不传。用postman调试,判断是否支持同一订单号支付多次,如果不支持,则给订单号加一个后缀,后缀可以由_下划线+时间戳
组成。有些支付渠道支持参数中传同步回调地址、异步回调地址,不支持的渠道方则只能在渠道方的后台配置或者叫对方技术人员帮忙配置。通常情况下美元币种的最小金额单位就是美元
,而有些支付渠道则是美分
,这种情况需要给支付金额转成美分单位。
直连模式不能打印请求报文。特别是卡号等信息,不能打印。
能加密则尽量加密。加密算法可以首先问渠道方获取,对方没有再根据接口给的加密要求去自己写加密算法。有些加密算法需要将参数按照自然排序拼接起来再加密,可以使用TreeMap的key自带的排序特性。
使用重试机制包裹发起支付的逻辑。避免网络抖动等原因导致请求渠道发的服务器失败,使用重试机制保证发起支付能正常。
缓存权限Token。有些支付渠道需要先获取权限Token,再在发起支付的时候一并传过去。考虑到网络会不好的原因、频繁请求权限Token会限流或被墙等原因,将Token缓存下来。如果token有国企时间,比如5min,那么缓存的过期时间设置得比他少即可,比如设置2.5min。
解析响应报文。如果是收银台模式,响应报文通常是收银台地址,或者是一个展示收银台界面的含有html标签的文本,甚至连这个html文本都需要后端填充某些交易信息。后端将这个收银台地址返回给前端做重定向,或者将html文本返回给前端做展示,用于前端做轮询支付结果。如果是直连模式,响应报文通常是3D验证地址或者支付结果。如果是3D验证地址,返回给前端做重定向。如果是支付结果,将它写入Redis。针对以上两种模式的响应报文,只要是拿不到支付结果,都得把”支付中“写入Redis,并且如果渠道方有提供主动查询支付结果的接口,则发一个带延迟的mq消息出去,按递减的频率去延迟拉取支付结果。拉取到支付结果则翻动订单的支付状态并将支付结果写入Redis。
持久化支付流水。每次发起支付后,把对方的整个响应报文持久化。
webhook支付回调。写一个controller方法接收渠道方发送过来的webhook消息,只处理”支付成功“或者”支付失败“的请求。如果同一笔支付发送了多次webhook,不支持将“支付成功”翻转为“支付失败。翻转状态成功后仍要把它写入Redis。
有得验签则验签。
利用对方提供的自定义参数。有些渠道方会提供自定义参数,传什么都会原样返回,我们可以借助这个参数传一些值过去,当webhook接收到这些值后,就不需要再查库去组装某些东西了。比如传个order_no
、syn_url
、asyn_url
、token
等。
注意请求参数的风格以及请求方式。风格有下划线、驼峰风格。 请求方式有POST的表单传参、POST的JSON、甚至GET拼接参数。发起支付、webhook接收都得注意。
主动拉取支付结果。发起支付后,如果得不到支付结果,那么为保证支付状态能正确翻转,最好主动查询支付结果(虽然有些渠道方不提供该接口)。发送支付结果异步拉取延迟消息。
买家支付了钱,但是却没在电商系统看到支付成功。
没支付成功,有可能是以下这些情况:
自身原因:发起支付直接失败;翻转支付状态失败 ;前端轮询支付结果超时导致最终没有展示了支付成功页(底层原因仍然是后端翻转支付状态失败)
渠道方原因 :没有发webhook;发webhook的地址错误;
解决自身原因:
解决渠道方原因:
www.alishop.com
的A电商系统做买卖,同时商家使用了QPay
这家支付渠道,并且在他们的支付渠道后台配置了异步回调地址的域名为www.alishop.com
。过了1个月,商家不再使用A电商系统,转而在域名为www.blibuy.com
的B电商系统做买卖,但是商家忘记在支付渠道后台更新异步回调地址的域名。从而导致QPay
发仍旧送webhook回调给A电商系统,而B电商系统是收不到webhook回调,导致在B电商系统上创建的订单状态无法翻转,导致顾客无法支付成功该笔订单。渠道方却一直说发webhook成功了,其实是webhook的地址是错误的。