开题
现在网络支付方式越来越发达,已经不可避免地开始与各类 IT 系统集成。在财务制度比较规范的地方,这类入账自然需要统一管理,资金不能随意流动。如果由各个业务系统自己去申请对接网络支付接口,自然是很难实现统一管理的,而且也麻烦。
因此,就需要一个统一的企业级支付平台作为中间层,来负责各业务系统与网络支付的对接。这样,不仅方便了财务数据的统一提取、账户对账,也使企业支付平台可以代表所有的业务系统与网络支付接口进行交互,业务系统不会接触到企业的网络支付总密钥,较为安全可控。
这样的例子其实我们天天见。例如 12306 的车票支付、教育部考试中心的考试费支付,都采用了中间层,来接入多个网络支付平台。
此时,企业级支付平台的角色可以认为是一个代理,协助业务系统完成支付流,并保存相关数据。目前这类“代理”的架构并没有标准,所以实现起来会各有小差别。如何去定义企业级支付平台(下称企业层)和业务系统各自的角色,就会决定架构的实现方式,相应就会影响到支付业务的实现情况。
WePay 的实现思路
下面介绍一款已经在运行中的企业支付平台 WePay,其目前向上对接了微信企业号的微信支付接口,远期规划可加入其他网络支付的支持。
WePay 会给每个业务系统分配一个“子商户”,对应会有 ID 和密钥,也就意味着业务系统通过 WePay 实现的子商户进行支付请求,WePay 自己处理网络支付接口的密钥,应用系统只能借助 WePay 获取网络支付的结果信息。
上图的流程目前其实工作得非常好。应用系统需要完成的工作很简单,生成应用端的订单信息并保存订单号,然后把订单信息一股脑前台 POST 给 WePay,WePay 的网页会帮应用系统实现支付过程,应用系统只管收支付结果就可以了。
在这里,WePay 的角色并不只是“代理”那么简单了,因为要帮应用系统实现支付流程,所以中间还得控制支付过程。我就暂且叫它做“富代理”吧。
为了更好地说明纯粹的“代理”是个什么情况,我假设了新的流程。这里业务系统会把 WePay 当作是网络支付的服务器,所以业务系统传给 WePay 的 ID 可以是自编号的子商户,WePay 把请求递交给网络支付的时候再用上网络支付统一的密钥。
代理与否,利弊明显。实际上现在基本上都是“富代理”的解决方案。
- 纯代理
- 业务系统需要自己处理支付过程(图中,浏览器端中间的蓝栈页面均由应用服务器实现);
- 不同系统的技术良莠不齐,支付过程的体验和安全性会有差别,不利于企业对支付过程进行标准化;
- 但如果业务系统已经做了现有的网络支付对接(目前网络支付有大量的 SDK),技术上,纯代理模式下业务系统会更容易接入企业层系统。
- 富代理
- 支付过程由 WePay 实现(图中,浏览器端中间的蓝栈页面均由 WePay 实现);
- 如果应用是从头开发的,接入的工作量就会比直接接入网络支付少(即便人家有 SDK 了还得自己实现前端呢);
- 支付过程均由企业层实现,用户体验和安全性可以由企业层的统一实现来保证。
“富代理”角色可以多做这点判断
企业层需要做的事情多了,这样的角色定位下,每单支付业务系统该给企业层传多少参数呢?
根据内部公开资料得知,目前业务系统在创建支付订单时,需要传以下这些参数给这款 WePay 系统。我做了下分类:
-
请求身份和请求校验
-
spbillCreateIp
发起订单的 IP -
tenant
租户代码(由 WePay 分配) -
timestamp
订单生成的时间戳 -
sign
所有参数加上密钥后的签名值
-
-
订单内容(必须)
tradeType
交易类型-
totalFee
支付金额(单位为分) -
tenantTradeNumber
租户订单号(业务系统内部唯一) -
redirectUrl
前端跳转确认地址
-
订单内容(增强)
-
productBody
,productDetail
交易内容和详情 -
tenantUserCode
,tenantUserName
企业应用中用户的用户名、姓名
-
实际上这一整套接口的参数内容,多是直接借鉴微信支付的参数来着的,反正就是转发参数,用现成的参数名也没啥问题。
但注意到 tradeType
这个参数了吗?实际上这个参数是微信支付用的。微信那边目前支持的值有 JSAPI
(微信内浏览器)、NATIVE
(用户扫码付款)、APP
(手机应用)、MWEB
(微信外手机浏览器)、MICROPAY
*(商户条码付款),而 WePay 实现了 JSAPI
和 NATIVE
,根据业务系统发的参数来返回给浏览器不同的界面。
那么,这个参数真的要靠业务系统来给定吗?
- 大多数情况下,业务系统需要根据客户端来提供不同的界面来适配,比如根据 UA 有没有 micromessenger,判断提供 PC 版还是微信版。业务系统很多时候似乎知道客户端属性了,让业务系统来指定这个类型,下不同的单,似乎顺理成章。
- 然而,当今的网页技术早已可以实现一套前端模板,通吃 PC 移动(Bootstrap 框架 推了很多不会写响应式设计的人一把)。因此,有的业务系统根本没必要去判断客户端是不是微信(而提供不同的界面)。这时候让业务系统来下单,业务系统肯定下一个 PC 版的单了,除非非常考究,才会特意适配一下微信。
- 此外前文提到,企业层在支付流程中,会协助应用系统实现面向用户的支付交互流程(前端)。而显然,
JSAPI
、NATIVE
、MWEB
这三种模式,只是触发支付的方式不同,并不会影响到支付业务的核心内容和最终结果。这种情况下,显然没有必要让应用去选择下单模式,而应该让企业层来统一判断,从而继续降低业务系统的集成难度。JSAPI
创建订单的时候需要OPENID
,可能有的业务会靠这个参数来限制只能某人支付这一订单。实际上这个需求并非必要,很多时候反而会造成业务规则不统一(支付页面如果可以分享,会导致OPENID
不同而支付失败;不限制的话,虽然可能会有退款退不到账户本人的问题,但很多业务直接上NATIVE
模式,谁扫码都行,完全没考虑这一点),确实有业务需要的话也可以保留,就是 WePay 多判断一下而已。
- 更重要的是,这三种模式是高度与特定的网络支付端口耦合的。如果日后需要接入其它的网络支付,较好的办法是业务系统不用管网络支付端口的具体的支付模式参数,而靠 WePay 去给用户选择,否则业务系统全得再改一遍,多没意思。
结论就是,这个 tradeType
应当做一下整合。鉴于JSAPI
、NATIVE
、MWEB
这三种模式本质上都是网页支付,可以在 WePay 端整合为一个名字(如 HTML
),再由 WePay 进行选择,而不应该让业务系统来选。如果业务系统还发了这三个串进来,就统一定向到新的 HTML
模式就好。至于 APP
和 MICROPAY
,现在这个系统似乎没有用到,这些方式就预留着空位就好。
支付中间层如何判断支付模式?
既然提出了这个解决方案,自然也得考虑,没有业务系统的任何数据,支付层能不能高效率地自行判断支付模式呢?当然是可以的。
支付中间层最麻烦的事情,莫过于在开始让用户支付前,就得先跟网络支付开好订单。而开订单的时候,就必须决定具体的支付模式。
有种稳妥的方法,就是通过前端来检查微信的 JSAPI 能不能用、有没有手机(微信),再来按需异步开订单。不过这样当然太麻烦了,所以靠粗暴的后端 User Agent 检查,就可以实现绝大多数情况下的正确及时判断了。
所以是:
- UA 判断是否包含了
micromessenger
,是的话,默认在微信环境下,开 JSAPI 订单- 调 JSAPI 需要获取
OPENID
,企业号的接口无论微信用户是否有关注企业号,都会返回一个OPENID
,而且鉴于支付是低频操作,这个时候让
WePay 跳一次 OAuth 其实也并不太影响响应时间(OAuth 有两种方案,一种是 WePay 自己跟微信拿数据然后 Cookie 缓存,另一种是跟企业号应用主域名拿数据,由于企业号域名通常用户都有登陆,都带 Cookie,可能可以节省 OAuth 的开销)
- 调 JSAPI 需要获取
- 其他情况全部走 NATIVE 订单;或者,如果有闲心兼容下 MWEB(手机浏览器在这个模式下可以直接拉起微信,体验会更好),就再 UA 判断是否包含 Android 或 iPhone / iPad 串,有的话就进 MWEB,没有就进 NATIVE 扫码去
- 要做到 100% 靠谱,就得考虑在 WePay 端提供切换模式的功能了,不过一切换模式,就得关闭订单,还得重新生成订单号给网络支付用,是挺烦的,所以没必要做
EOF
说明:本文资料均来自公网公开资料,图是自己用这个画的,并没有透露什么机密。终于可以安心学习了 = =
本文采用知识共享“署名-非商业性使用 4.0”许可协议授权,如需额外授权请与本人联系。