电商系统对接支付渠道的解决方案

文章目录

  • 1 问题背景
  • 2 前言
  • 3 信用卡支付的交互流程
    • 3.1 收银台模式(非直连)
    • 3.2 直连模式
    • 3.3 两种模式的区别
  • 4 业务解决方案
  • 5 技术实现的细节
  • 6 如何防止支付掉单
    • 6.1 什么是掉单?
    • 6.2 原因分析
    • 6.3 如何解决
  • 7 踩过的坑

1 问题背景

前面0基础了解电商系统如何对接支付渠道详细介绍了支付环节中参与的角色以及数据流向。今天从技术实现方案讲述电商系统如何对接支付渠道的解决方案。

2 前言

  • 本篇文章会涉及到部分代码,不管是产品人员还是开发者都无需过多关注代码,只需关注整个解决方案是如何实现即可。
  • 本篇文章仅陈述出笔者在企业级开发中接触到的解决方案,不代表是行业标准,但是技术实现基本大同小异。
  • 笔者只参与过信用卡的对接,本文的解决方案基本围绕信用卡对接做例子。不管是银行卡对接、信用卡对接、电子钱包对接,前后端交互流程基本大同小异。
  • 考虑到以产品岗位和开发者的角度去做总结,笔者将先以业务解决方案给出方法论,然后再给出技术实现细节阐述对接过程中的核心要素。

3 信用卡支付的交互流程

了解具体的技术实现方案之前,先了解电商系统是如何与渠道方交互的

3.1 收银台模式(非直连)

概念解释: 收银台模式是一种非直连模式,即发起支付后,支付渠道给回来的响应报文并没有支付结果,而是给了一个收银台界面地址,需要电商系统重定向到该地址。

下面给出交互流程图:

电商系统对接支付渠道的解决方案_第1张图片

解释:

  1. 买家在C端结算页点击“支付”按钮,前端发送http请求给后端,即图中的“电商系统”。
  2. 电商系统收到请求后,组装好请求报文,发起支付请求给支付渠道。在收银台模式中,此次发起支付拿到的响应报文是没有支付结果的。
  3. 上面步骤中的”发起支付“需要使用重试机制,避免因网络抖动产生网络断开导致发起支付失败,保证客户端发起支付的请求成功。如果发送失败,则重新发送,一般会重发2-3次。
  4. 支付渠道收到请求后会返回一个响应报文给电商系统。响应报文一般会有收银台地址,或者用于展示收银台界面的一个表单(含html标签的代码,后端返回给前端做展示),或者是一个表单并且要后端填写完表单中的数据后再返回给前端展示。基本都是大同小异,核心都是前端展示收银台界面给买家看并输入信息
  5. 买家在收银台界面输入卡号等信息,点击“确认支付”。
  6. 收银台界面则会发送请求给支付渠道,这次也可以叫“发起支付”。
  7. 支付渠道处理完请求后,实时地将支付结果等信息以参数形式拼接到url后,重定向到C端结算页。并且支付渠道 会发送webhook给电商系统。
  8. C端结算页则会轮询支付结果,支付成功则重定向到支付成功页;支付失败则停留在结算页并弹出支付失败等信息。
  9. 电商系统收到webhook请求后,会处理“支付成功”、“支付失败”这两种请求。收银台模式下,支付结果一般以webhook为准,一般不会以实时返回的支付结果为准。并翻动数据库的支付状态关于幂等操作,如果第一次webhook是成功的,第二次是失败的,那么第二次的翻动状态操作将会抛异常,因为系统认为不能由支付成功翻动到支付失败。当然也可以使用Redis解决幂等问题。),然后写入Redis

3.2 直连模式

概念解释:直连模式是指发起支付后,支付渠道方返回的响应报文要么是含有3D验证的url地址,要么是支付结果。

下面给出交互图:

电商系统对接支付渠道的解决方案_第2张图片

解释:

直连模式与收银台模式的交互都基本相似,区别就在以下几方面:

  1. 买家是在C端结算页就填写信用卡卡号等信息。
  2. 发起支付后,支付渠道返回的响应报文要么含有3D验证的url地址,要么含有支付结果。(3D验证是信用卡组织为客户安全的进行网上购物所提供的一项免费服务。实际表现为在验证界面输入持卡人预设的密码
  3. 如果响应报文是含有3D验证的url地址,那么电商系统需要重定向到该地址.。买家输入预设的密码。然后验证界面发送请求给支付渠道方。
  4. 如果响应报文是含有支付结果,那么支付渠道方会重定向到C端结算页并将支付结果以参数形式拼接到url后面。并且还会发送webhook给电商系统。

3.3 两种模式的区别

收银台模式:

  • 要跳转到支付渠道
  • 在支付渠道收集卡信息
  • 不用处理3D流程(因为是支付渠道处理了)

直连模式:

  • 在自己系统(电商平台)收集卡信息
  • 非3D流程,不用跳转到支付渠道
  • 要处理3D流程

4 业务解决方案

  1. 提前寻求对方协助。包括但不限于:拿到渠道方的后端控制台访问地址并拿到收款账号,请求对方协助开通收款账号,请求对方协助配置同步地址、异步回调地址等,询问是否能关闭金额限制,询问是否能给出测试卡号等,询问对接过程中是否能做特殊处理达到调通流程的目的,等等。一切的目的都是尽可能地加快调通接口速度,能申请协助的资源尽量申请,委婉地表达能否为了调通接口而做某些特殊处理(很多成熟的支付渠道会有自己的一套风控规则,对接的过程中很容易就触发了风控导致支付失败)。

  2. 用postman按照接口文档调通流程。找渠道方拿接口文档,自己先用postman调通各个接口,包含但不限于发起支付、webhook回调、查询支付结果等。

  3. 写代码先写核心接口。核心接口有发起支付、webhook回调。有些发起支付流程是由创建订单、发起支付单这两个接口组成的。次要的主动查询订单接口可以等核心接口调通之后再慢慢加上去。

5 技术实现的细节

  1. 定义支付方式的编码。比如对接了一个名为QPay的支付渠道,对接它的信用卡方式(收银台模式),那么我定义该支付方式的编码为 QPay。如果对接它的信用卡方式(直连模式),那么我定义该支付方式的编码为QPay(Direct)。(收银台模式:发起支付后,渠道方给回来的响应报文没有支付结果)。

  2. 确定是什么支付方式。比如有银行卡、电子钱包、信用卡(收银台模式)、信用卡(直连模式)。

  3. 以订单为维度在发起支付、支付回调加分布式锁,防止重复支付。 针对同一笔订单,给它加一个分布式锁,防止重复支付。导致重复支付的情况有很多种:(1)未防重导致的重复支付。(2)掉单导致的重复支付。掉单也有内部掉单和外部掉单两种情况。内部掉单通常是未翻转支付状态。外部掉单通常是渠道方没有返回webhook消息。(3)多渠道导致重复支付。比如先打开支付宝支付界面,再用微信支付,最后回到支付宝支付。都是可以成功的。

  4. 组装请求参数。核心的请求参数有订单号同步回调地址异步回调地址最小金额单位币种。能不传的字段尽量不传。用postman调试,判断是否支持同一订单号支付多次,如果不支持,则给订单号加一个后缀,后缀可以由_下划线+时间戳组成。有些支付渠道支持参数中传同步回调地址、异步回调地址,不支持的渠道方则只能在渠道方的后台配置或者叫对方技术人员帮忙配置。通常情况下美元币种的最小金额单位就是美元,而有些支付渠道则是美分,这种情况需要给支付金额转成美分单位。

  5. 直连模式不能打印请求报文。特别是卡号等信息,不能打印。

  6. 能加密则尽量加密。加密算法可以首先问渠道方获取,对方没有再根据接口给的加密要求去自己写加密算法。有些加密算法需要将参数按照自然排序拼接起来再加密,可以使用TreeMap的key自带的排序特性

  7. 使用重试机制包裹发起支付的逻辑。避免网络抖动等原因导致请求渠道发的服务器失败,使用重试机制保证发起支付能正常。

  8. 缓存权限Token。有些支付渠道需要先获取权限Token,再在发起支付的时候一并传过去。考虑到网络会不好的原因、频繁请求权限Token会限流或被墙等原因,将Token缓存下来。如果token有国企时间,比如5min,那么缓存的过期时间设置得比他少即可,比如设置2.5min。

  9. 解析响应报文。如果是收银台模式,响应报文通常是收银台地址,或者是一个展示收银台界面的含有html标签的文本,甚至连这个html文本都需要后端填充某些交易信息。后端将这个收银台地址返回给前端做重定向,或者将html文本返回给前端做展示,用于前端做轮询支付结果。如果是直连模式,响应报文通常是3D验证地址或者支付结果。如果是3D验证地址,返回给前端做重定向。如果是支付结果,将它写入Redis。针对以上两种模式的响应报文,只要是拿不到支付结果,都得把”支付中“写入Redis,并且如果渠道方有提供主动查询支付结果的接口,则发一个带延迟的mq消息出去,按递减的频率去延迟拉取支付结果。拉取到支付结果则翻动订单的支付状态并将支付结果写入Redis。

  10. 持久化支付流水。每次发起支付后,把对方的整个响应报文持久化。

  11. webhook支付回调。写一个controller方法接收渠道方发送过来的webhook消息,只处理”支付成功“或者”支付失败“的请求。如果同一笔支付发送了多次webhook,不支持将“支付成功”翻转为“支付失败。翻转状态成功后仍要把它写入Redis。

  12. 有得验签则验签

  13. 利用对方提供的自定义参数。有些渠道方会提供自定义参数,传什么都会原样返回,我们可以借助这个参数传一些值过去,当webhook接收到这些值后,就不需要再查库去组装某些东西了。比如传个order_nosyn_urlasyn_urltoken等。

  14. 注意请求参数的风格以及请求方式。风格有下划线、驼峰风格。 请求方式有POST的表单传参、POST的JSON、甚至GET拼接参数。发起支付、webhook接收都得注意。

  15. 主动拉取支付结果。发起支付后,如果得不到支付结果,那么为保证支付状态能正确翻转,最好主动查询支付结果(虽然有些渠道方不提供该接口)。发送支付结果异步拉取延迟消息。

6 如何防止支付掉单

6.1 什么是掉单?

买家支付了钱,但是却没在电商系统看到支付成功。

6.2 原因分析

没支付成功,有可能是以下这些情况:

  • 自身原因:发起支付直接失败;翻转支付状态失败 ;前端轮询支付结果超时导致最终没有展示了支付成功页(底层原因仍然是后端翻转支付状态失败)

  • 渠道方原因 :没有发webhook;发webhook的地址错误;

6.3 如何解决

解决自身原因:

  • 使用重试机制
  • 主动拉取支付结果。有两种方式:定时任务延迟消息查询的频率要递减)。因为定时任务扫描数据库会给DB造成很大的压力,再加上如果过了几分钟还没支付成功,买家很大概率都不会再支付。因此,类比Redis架构持久化的处理方式(AOF+RDB结合),同时使用定时任务+延迟消息。这样既能在短时间内查询支付结果(延迟消息解决),也能补全历史的有异常的支付结果问题(定时任务解决)

解决渠道方原因:

  • 主动拉取支付结果。同样地,如果渠道方有查询支付结果的接口,我们就利用起来,从而解决对方没发webhook的问题。
  • 拿着流水号找对方技术人员排查定位问题。比如回调地址是否正确。

7 踩过的坑

  1. 商家原本是在域名为www.alishop.com的A电商系统做买卖,同时商家使用了QPay这家支付渠道,并且在他们的支付渠道后台配置了异步回调地址的域名为www.alishop.com。过了1个月,商家不再使用A电商系统,转而在域名为www.blibuy.com的B电商系统做买卖,但是商家忘记在支付渠道后台更新异步回调地址的域名。从而导致QPay发仍旧送webhook回调给A电商系统,而B电商系统是收不到webhook回调,导致在B电商系统上创建的订单状态无法翻转,导致顾客无法支付成功该笔订单。渠道方却一直说发webhook成功了,其实是webhook的地址是错误的。
  2. 多次支付的坑。场景:买家点击“支付”发起第一次支付,但未付款。买家再点击“支付“发起第二次支付,但未付款。此时有2个支付页面。买家现在又去第一次的支付页面付款。即买家付款的是旧的那次支付流水。商家在收单方的后台(比如Stripe的后台)看到”买家已支付成功“。由于电商平台只认可最新的流水,因此商家在电商后台没看到“买家支付成功”。这就导致了“明明买家已经支付成功了,但是电商系统这边仍然未支付或待支付的状态”。
    2.1 为什么只认可最新的流水? 如果旧的流水也认可,会出现很多漏洞。比如,第一次发起支付用优惠码,实际支付90元。第二次发起支付不适用优惠码,实际支付100元。此时该笔订单最新的需实付金额为100。买家在第一次支付的界面付款了。如果电商认可旧的流水,那么买家就是用了90元买了100元的商品,并且这是没有用优惠码的情况,这明显是不合理的。除了列举出来的这种情况,肯定还有很多漏洞。如果买家支付第二次的流水,其实也是可以支付成功的。出现多窗口多次支付的这种情况,概率会很少,基本都是要买家刻意去搞才能出现,因为前端在跳转收银台界面的时候,都是采用当前tab页跳转。
    2.2 怎么解决这种情况? 前端层面,跳转到收银台的过程采用当前tab页跳转,不要新开tab页跳转。这样买家只能刻意去弄,才能搞出多次支付的场景。后端层面,做一个补偿机制异步拉取支付结果,针对这种极少量出问题的订单,需要技术人员介入手动拉取支付结果。客户层面:需要客服或商务部的同事向客户耐心解释,以电商系统的立场阐述其中的利弊,让客户理解出现该种现象的概率极小。

你可能感兴趣的:(解决方案,支付)