支付宝WAP支付接口开发
因项目需要,要增加支付宝手机网站支付功能,找了支付宝的样例代码和接口说明,折腾两天搞定,谨以此文作为这两天摸索的总结。由于公司有自己的支付接口,并不直接使用这个接口,所以晚些时候打算把测试代码整理好放到Github上。
1. 开发前准备
- 到官网了解此接口的信息,下载样例代码(只有ASP.NET和PHP)以便随时参考。
- 一个通过实名认证的企业支付宝账号,并申请开通手机WAP支付功能,我的测试账号是拿公司的,申请流程不清楚,官网有说怎么申请,各位各显神通吧。
- 公网域名和node.js环境。下面的代码大多用coffee来表达,不过本文不会贴太多代码,即使对coffee不熟悉也没什么关系。关于coffee可以参考这里。
github上有两个开源小项目(搜索 alipay
),但都没有WAP支付功能,可以拿来当参考,可以认为是示例代码的js移植版,结构很相像。我原打算在其中一个项目基础上继续开发,看了代码和接口文档后,还是决定从头开发一个。因为原有代码层次不够清晰,有点过度设计的感觉,而且支付宝的接口很简单,重写工作量不大。
吐槽下: 官网的示例代码真只是示例级(test)而已,跟产品级(production)还隔比较远,感觉还谈不上SDK。接口文档相当的坑爹,正因如此我才觉得有必要好好写篇文章总结。
2. 流程
接口开发最重要的应该是理解数据交互流程了,流程弄清了,并理解为何这么设计,开发起来也是事半功倍
首先,要准备下面几个参数:
- 企业支付宝账号的PID(也叫ParnerID)和KEY,如果使用RSA签名而不是MD5的话,还要把RSA私钥准备好
- 支付时用户看到的东西:商品名称(subject)、支付总额(total_fee)、购买数量(通常都是1吧)
- 交易后的跳转地址,交易成功后用户可以手工点击,或页面延迟自动跳转到这个地址(return_url)
- 交易状态异步通知地址,交易成功或交易关闭会把消息POST到这个地址(notify_url)
然后,看这幅流程图(不错吧,推荐下这个网站:)
1 |
Alipay WAP pay flow Browzer->+Site: 1. HTTP GET |
这个流程图基本囊括了整个交互过程,下面是说明:
- 用户点击购买按钮(或其他形式),向网站发起购买请求
- 网站创建订单,指派一个唯一订单号
- 网站把订单号、企业支付宝账号、交易金额、数量等信息,用私钥签名发送给支付宝
- 支付宝创建一个交易订单,返回一个交易令牌(token)
- 网站按照指定要求,用token和自己的私钥,构造一个重定向得到支付地址
- 网站把重定向地址返回给浏览器
- 浏览器自动重定向到该地址,即包含了token、网站签名的支付宝交易页面
- 支付宝显示当前交易金额、数量、卖家等信息
- 用户用自己的支付宝账号支付这笔金额
- 支付宝把用户支付成功(或失败)这个消息和订单号加上支付宝的签名,使用HTTP POST的方式通知网站(失败的话,会隔段时间重新发送)
- 网站处理交易后续逻辑(发货、订单状态存储之类的)
- 网站返回"success"字符串给支付宝,表示该通知已经处理,不用再重发
- 支付宝显示支付成功页面给用户(这一步和第10步是不分先后发生的)
- 支付成功页面延迟自动跳转,或用户点击“返回商户页面”,跳转到网站的支付结束页面(此时不一定成功处理支付宝发来的通知),但会在URL带上当前的订单号和状态。
可以发现,整个流程有点像OAuth(哎呀,之前那篇文章还没写完呢!),主要分三步:
一是申请支付宝交易号(获取token),这一步可以理解为,让支付宝验证网站的有效性、让网站指定该交易要支付多少钱 二是用户到支付宝页面付款,这一步可以理解为,让支付宝验证用户有效性,让用户在一个不受网站监视的环境下进行支付 三是用户付款后,处理结果页面告诉用户支付成功(同步通知),另外异步通知网站服务器该订单已支付。
支付宝的接口文档说只有两个步骤,感觉不是很好理解,三步还是比较准确的(收钱肯定要办事的嘛)。
好困,细节问题下期继续。。。
2013-07-24
3. 细节
3.1 网站向支付宝申请新订单
网站的订单系统先产生一个新订单,然后请求支付宝创建一个支付宝订单.
申请新订单的service是 alipay.wap.trade.create.direct 需要提交的关键参数包括:
3.1.1 用户在支付宝看到的订单信息:
subject: 商品名称
total_fee: 总金额
seller_account_name: 卖家支付宝账号(估计跟私钥绑定的)
merchant_url: 商品展示URL(似乎这个并非必要)
3.1.2 支付宝通知网站时将附带的信息:
out_trade_no: 该次交易对应网站的订单号(要求唯一)
call_back_url: 交易成功后,支付宝页面上“返回到商家页面”的地址(同步回调)
notify_url: 交易状态变更后,支付宝通知网站的回调地址(异步通知)
支付宝验证通过后,将返回新创建的支付宝订单号,网站可将该订单号与自己订单系统的的订单号绑定在一起。支付宝同时返回的还有该次交易的token,用于(3.2)用户支付。
3.2 用户在支付宝网站,查看订单消息,通过验证并支付
网站返回跳转到支付宝的地址,service是alipay.wap.auth.authAndExecute,包含(3.1)返回的token和网站对跳转地址的签名
这是个HTTPS页面,基本认为是安全的。当然前提是浏览器没被动手脚,安卓不少应用被捆绑广告那是常有的事,手机浏览器对HTTPS也不像PC那样有明显提示,这些也是我不怎么信任手机支付的原因。
用户跳转到支付宝页面后,可以在该页面里看到当前支付的订单的名称和金额,这些是3.1申请时由网站指定的,让用户在支付宝的页面确认一次再付款是合理的。
3.3 支付宝通知网站支付成功,网站收钱做事
这个过程是支付宝通知网站,网站处理后通知用户已到账,共包括两个并行部分:
3.3.1 异步通知
用户支付后,支付宝通过HTTP协议通知网站该订单交易结果。说白了就是支付宝悄悄地告诉网站“这个订单已经已经付款啦”
值得注意的是,异步通知有重发机制,支付宝需要得到响应为"success"才认为该通知成功被接收,否则会间隔一段时间重发,依次间隔2m,10m,10m,1h,2h,6h,15h,最多8次通知,由notify_id说明是同一个通知。8次通知都接收失败怎么办?额orz...文档没说,用那个支付宝订单号登录支付宝去查账吧。
3.3.2 同步通知
用户支付后,支付宝页面提示“支付成功”,可点击返回商家页面,也可等待一段时间自动跳回
个人认为,网站跳到这个页面后,如果仍未收到(3.3.1)异步通知,并且使用的是MD5签名,应该把状态从“待付款”调整为“等待对账”,而不应该贸然相信该通知的结果。原因是这个回调地址用户是可以知道的,MD5签名还是有被伪造的可能(4.3)。当然额外再做个token之类的理论上也行(需要放在urlpath而不是querystring)。
假如接口调用出错,通知是不会签名的。不签名的原因我怀疑是防止有人恶意收集请求-签名样本,见(4.3)。
4. 签名与加密
简单的说,签名防篡改,加密防窃听。上面的两种请求(3.1和3.2)和两个通知(3.3.1和3.3.2)都被要加签名,支付宝支持下面两类签名:
4.1 MD5: 业务数据不加密,防篡改
优点: 相对较简单(当然是相对DSA/RSA来说),计算速度快,明文更直观
缺点: 可抵赖,可能被窃听、安全性不如非对称加密
4.2 DSA/RSA: 业务数据加密,也防篡改
优点:不可抵赖,安全性较高
缺点:相对较复杂,解密速度慢
一开始我想不懂,支付宝既然支持RSA为何还要支持MD5,后来有人说RSA太慢,想想支付宝的业务量就释然了。由于每个商家的私钥都不同,并且跟商家的支付宝账号绑定,即使商家的私钥被破解了,用户支付时HTTPS协议基本可以保证用户支付的目标还是商家的账号。
4.3 使用MD5签名可能存在的风险
以下情景仅是我的推断,没有尝试过,所谓道高一尺魔高一丈,希望读者也别以身犯险。
在用户支付的步骤(3.2)和支付成功响应页面(3.3),用户可以得到一个明文请求内容和对应签名。由于网站和支付宝直接通信共用同一个密钥,一般长期不变,双方都可以对同一段数据产生签名,这就有可能抵赖的风险:
网站:“这个数据是你发过来的,上面有你的签名。”
支付宝:“不是我发的。这个数据是你伪造的,签名是你签的。”
另外,当攻击者收集到足够多的样本,是有可能破解出密钥的,继而可伪造网站或支付宝任意一方。
4.3.1 恶意消耗商家的订单号
攻击者伪造大量未使用的订单号(不少网站的订单号都是递增的纯数字,并公开给用户,且很容易推测到后面的数字),向支付宝请求订单,直到超时。由于商家对此并不知情(回调地址和通知地址均篡改掉),其他用户下单时假如商家用了被伪造过的订单号,就可能被支付宝认为提交了重复订单,结果支付失败。
4.3.2 欺骗商家已支付订单
由同步通知(3.3.2)返回的参数可以看到,网站订单标识和交易token和都是可以得到的。这样的话,关于步骤(3.1),用户不知道的参数包括notify_url和out_user_no,假如网站的用户id本身就是公开的,通知回调地址(3.3.1)被得知或同步通知(3.3.2)实现的不好,就可以通过伪造支付通知,欺骗商家订单已支付。
待续,下期补充实现代码
2013-07-28
5. 代码
5.1 签名
我只做了MD5签名,项目里没用到RSA签名,就没做那方面。按照文档说明和demo源代码,很容易就可以写出下面的签名代码:
1 |
getSign = (obj,key) -> |
支付宝发到网站的通知(3.3)的签名算法跟上面有点不一样,文档有这么段说明:
这里说要按通知的参数的原本顺序计算签名。所以我就把上面的arr.sort()
去掉然后计算签名,结果发通知发来的签名和我自己计算的不一致,纠结半天后仔细看文档的样例说明,看到下面段:
仔细跟实际接收的数据比较之后发现,文档和样例都说发来的参数顺序是(service,v,sec_id,notify_data),但我实际收到的并不是按这个顺序,只要按照文档的参数顺序重新排列再计算签名就正确了,最终通知的签名算法如下( 真是个蛋疼的大坑orz):
1 |
getNotitySign = (obj,key) -> |
文档里有说到字符编码参数
_input_charset
,我用的是utf8编码,发现不用传这个参数也可以,看来支付宝默认的字符编码就是utf8了如果使用RSA签名,需要先解密再计算签名
5.2 辅助方法
为了代码层次更清晰,我把签名、url拼接等方法抽出到一个单独模块(alipay_api.coffee):
1 |
api_url = "http://wappaygw.alipay.com/service/rest.htm" |
5.3 业务部分
5.3.1 购买(buy)
购买的逻辑对应于(2)流程图的(2,3,4,5),创建唯一请求ID,填充本次交易信息,发送到支付宝并获取token,然后拼接支付url并签名,然后重定向。
1 |
demo.buy = (info,done) -> |
其中用到的几个方法跟存储相关,我用的是MySQL:
1 |
getRequestId = (service, done) -> |
5.3.2 通知(notify)
用户支付后就等着通知了,按道理应该在TRADE_SUCCESS时处理用户支付成功的逻辑,但我实际测试发现至发送了TRADE_FINISHED事件来,所以干脆两个一并处理了,反正只会有一次成功。
1 |
xmlreader = require 'xmlreader'rr,updateResult?.affectedRows is 1 |
跟存储相关的几个方法如下:
1 |
storeTradeFinalState = (tradeId,isError,callback) -> |
6. 后记
有些公司有自己的支付平台,封装了一层,结果调用流程变成下面这样:
1 |
Alipay WAP using platform pay flow Browzer->+Site: 1. HTTP GET |
咋一看,挺方便的,Site只需要做个跳转即可,细节都被Platform隐藏起来了,切换不同的支付方式也变得很方便。
可是,这个平台的接口并不完善,这些数据都是明文并且没有要求Site做签名,于是就有一个风险。跟(2)的流程图对比,可以发现这个流程多了(3,4,14,16)四个步骤。后面两步只是个代理包装,没什么问题,问题在于步骤3和4。
可以看到,交易细节被放到了重定向url中了,即用户可以得知交易内容并篡改里面的数据。举个例子,假设重定向地址类似这样
http://payment.mysite.com/api/pay?tradeno=10000&rmb=100.00&siteid=1
由于该地址没有验签机制,所以攻击者很容易就可以发现这里面可以随意篡改数据。
方法1: 将rmb=100.00改成0.01,即将100元的支付变成1分钱,然后支付。结果Site得到通知的时候,就需要额外处理这种支付款项和要求款项不一致的情况。当然最简单就是金额不足就不退款并且支付失败了。假如没做这种判断,那就相当把100元的商品以1分钱卖出去了。
方法2: 伪造大量tradeno,然后请求,并且不支付款项。由于tradeno要求唯一,并只能使用一次,这样就相当于消耗掉了site的交易ID,并且site对此毫不知情。结果正常用户要购买时,创建的订单号对于site来说是未使用的,但对platform来说已经是使用过了,会返回支付失败。
要避免这个问题,需要在重定向的地址增加签名,签名异常的请求都抛弃掉。
【转】支付宝WAP支付接口开发的更多相关文章
-
支付宝WAP支付接口开发(Node/Coffee语言)
此博客不更新很久了, 更新的文档在这, 有兴趣到这里围观: http://neutra.github.io/2013/%E6%94%AF%E4%BB%98%E5%AE%9DWAP%E6%94%AF%E ...
-
支付宝WAP支付接口开发
支付宝WAP支付接口开发 因项目需要,要增加支付宝手机网站支付功能,找了支付宝的样例代码和接口说明,折腾两天搞定,谨以此文作为这两天摸索的总结.由于公司有自己的支付接口,并不直接使用这个接口,所以晚些 ...
-
php支付宝在线支付接口开发教程【转】
php支付宝在线支付接口开发教程 这篇文章主要为大家详细介绍了php支付宝在线支付接口开发教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 1.什么是第三方支付 所谓第三方支付,就是一些和各 ...
-
支付宝Wap支付你了解多少?
上几篇文章详细介绍了支付宝APP支付.微信APP支付 此文章来介绍下支付宝Wap支付(也叫作手机网站支付) 目录 1.创建应用并获取APPID 2.配置应用环境 3.配置沙箱环境 4.服务端实现(Ma ...
-
php微信支付接口开发程序
php微信支付接口开发程序讲解 微信支付接口现在也慢慢的像支付宝一个可以利用api接口来实现第三方网站或应用进行支付了, 下文整理了一个php微信支付接口开发程序并且己测试,有兴趣的朋友可进入参考. ...
-
JAVA微信支付接口开发——支付
微信支付接口开发--支付 这几天在做支付服务,系统接入了支付宝.微信.银联三方支付接口.个人感觉支付宝的接口开发较为简单,并且易于测试. 关于数据传输,微信是用xml,所以需要对xml进行解析. 1. ...
-
php微信支付接口开发程序(流程已通)
php微信支付接口开发程序(流程已通) 来源:未知 时间:2014-12-11 17:11 阅读数:11843 作者:xxadmin [导读] 微信支付接口现在也慢慢的像支付宝一个可以利 ...
-
H5网站接入支付宝的支付接口
写本文章的目的是为了记录工作中遇到的问题,方便以后遇到可以迅速解决问题 H5手机网站接入支付宝的支付接口,推荐使用支付宝提供的SDK来快速开发 我使用的是SDK开发 引用命名空间 using Aop. ...
-
支付宝wap支付调起客户端
https://mclient.alipay.com/home/exterfaceAssign.htm?alipay_exterface_invoke_assign_client_ip=183.15. ...
随机推荐
-
JavaScript面向对象和原型函数
对象,是javascript中非常重要的一个梗,是否能透彻的理解它直接关系到你对整个javascript体系的基础理解,说白了,javascript就是一群对象在搅..(哔!). 常用的几种对象创建模 ...
-
IIS 7 中设置文件上传大小的方法
在IIS 6.0中设置文件上传大小的方法,就是配置如下节点: <system.web> <httpRuntime maxRequestLength="1918200&quo ...
-
c# 重载运算符(+-|&;)和扩展方法
通常我们需要对class的相加,相减,相乘 等重载以适应需求, 如caml查询的时候,我们可以定义一个caml类,然后来操作这些查询. 首先,我们定义一个class为Test public class ...
-
java 页面换行处理
在taxtarea中输入的文本.如果含有回车或空格.在界面上显示的时候则不哪么正常.回车消失了,空格变短了. 如何解决这个问题呢.有2种方法. 1.使用<pre>标签 w3c对pre元素是 ...
-
HDU 1255 覆盖的面积 (线段树+扫描线+离散化)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1255 题意很清楚,就是让你求矩阵之间叠加层数大于1的矩形块的面积和. 因为n只有1000,所以我离散化 ...
-
1.Getting Started with ASP.NET MVC 5
Getting Started Start by installing and running Visual Studio Express 2013 for Web or Visual Studio ...
-
iscroll-lite.js源码注释
/*! iScroll v5.1.2 ~ (c) 2008-2014 Matteo Spinelli ~ http://cubiq.org/license */ (function (window, ...
-
《CSAPP》读书笔记
第一章 第二章 第三章 第四章 第五章 第六章 第七章 链接 可重定位目标文件 符号和符号表 符号解析 第八章 第九章 虚拟存储器 虚拟存储器 页表.页命中.缺页 地址翻译 第十章 第十一章 第十二章 ...
-
Error merging: refusing to merge unrelated histories
解决方案: git pull git pull origin master git pull origin master --allow-unrelated-histories idea提交git提交 ...
-
python 旋转数组 多种解题思路
leetcode 题目描述:给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题. 要求使用空间复杂度为 O(1) 的 ...