TimothyQiu's Blog

keep it simple stupid

使用支付宝「移动支付」同步通知时遇到的问题与吐槽

分类:技术

移动支付」是支付宝推出的针对手机移动支付的服务。虽然支付宝现在建议新商户转用「App 支付」接口了,但是一些较早接入的 App 仍旧在使用这个服务,比如鄙厂的智能证件照 ;-)

各大支付服务的套路其实都是一样的,无论网页还是 App:

  1. 服务器为支付所需数据签名
  2. 客户端使用签名后的数据调起支付服务
  3. 用户支付成功后,客户端获得支付结果同步通知,服务器获得异步通知回调

显然支付状态应以服务器获得的异步通知为准,不过某些情况下,客户端可能会有在先检查一下本地的支付结果有效性的需求。

前些天在 Sentry 里收到了错误报告,原因是校验客户端获得的同步通知结果时,发生了「被校内容格式异常」的错误。初以为是有人故意修改了客户端在捣乱,检查请求内容才发现,原来是支付宝(不知为何)本次发来的同步通知内容使用了不同以往的格式:

partner="2088101568358171"&seller_id="xxx@alipay.com"&out_trade_no="0819145412-6177"&subject="测试"&body="测试测试"&total_fee="0.01"&notify_url="http://notify.msp.hk/notify.htm"&service="mobile.securitypay.pay"&payment_type="1"&_input_charset="utf-8"&it_b_pay="30m"&success="true"&sign_type="RSA"&sign="hkFZr+zE9499nuqDNLZEF7W75RFFPsly876QuRSeN8WMaUgcdR00IKy5ZyBJ4eldhoJ/2zghqrD4E2G2mNjs3aE+HCLiBXrPDNdLKCZgSOIqmv46TfPTEqopYfhs+o5fZzXxt34fwdrzN4mX6S13cr3UwmEV4L3Ffir/02RBVtU=";extendInfo="doNotExit":true,"isDisplayResult":true

末尾前无古人后无来者地加了个分号,然后以奇怪的格式引入了 extendInfoisDisplayResult 两个字段。这是文档中从来没有提过会发生的事情。

原本通过文档中字段说明及例子,我们可能还觉得返回的支付结果就是用的 HTTP Query String 格式,而且支付结果去除涉及签名的 signsign_type 相关键值对后可以直接用来验证签名。收到这回这么一条回调,就都不成立了。

所幸原先为了偷懒没有以 HTTP Query String 的格式解析支付结果,而是直接用正则表达式 ^(.+)&sign_type="RSA"&sign="(.+)"$ 从中提取待签名字符串及签名本身。(偷懒之处在于:因为如果解析以后,按照支付宝的签名规则,我还得再把它们重新排序;而解析前的键值对本身已经是排过序的了。)

但不幸的是,这个正则表达式认为 sign="xxxx" 后不应该有任何多余数据。于是便有了「被校内容格式异常」。

反思及吐槽

前两天刚在知乎大言不惭地在回答里说很多程序员不肯看文档、凭直觉掩耳盗铃地写代码,结果这就立马变成自我吐槽了 ;-(

其实仔细看支付宝的文档的话,关于同步通知中的支付结果和签名,是这样说的:

result:本次操作返回的结果数据。其中:&success="true"&sign_type="RSA"&sign="xxx"之前的部分为商户的原始数据。success用来标识本次支付结果。sign="xxx"为支付宝对本次支付结果的签名(加签内容为:案例中原始数据&支付结果,……)

首先,你看文档里确实并没有保证支付结果数据始终以 sign="xxx" 结尾。我在正则表达式里作出那样的假设,是基于例子退出的假设,但这其实是个高中数学「充要条件」的问题。

其次,我那偷懒利用结果数据中「已排序」特性的操作,虽然目前并没有什么问题,但其实也是有风险的。因为文档里并没有保证它的有序性。

所以嘛,我错了,我应该好好看文档的……

以上,真是有意义的一天呐~

这篇文章没有标签

添加新评论 »