玩转APP支付
原文地址:http://52sox.com/python-play-...
近期公司的APP打算上线,需要集成支付的功能。由于采用的是Python进行开发,因此无法直接使用官方提供的SDK。虽然也有一些集成的第3方可以使用,比如ping++、beecloud。
但是由于提供的时间比较充裕,于是就自己实现了1个。在这个过程中,难免遇到一些坑,但是很快就解决了。
适用范围
首先为了避免耽误大家的时间,这里我们只实现了微信支付及支付宝的移动支付。对于微信公众支付及支付宝的其他支付场景是不适用的。
个人建议及使用的库
在正式讲述APP支付之前,我有如下的建议:
Python版本>=2.7.9,由于Python版本2.7.9为1个bug修复版本,在这个版本中使用新的SSL模块,修复了之前HTTP客户端模块(比如urllib2,httplib)不对服务器证书进行校验的问题,详情请查看PEP 476。
使用lxml,而不是标准库中的XML库,主要在于标准库中的XML模块无法检验恶意构造的数据,详情请查看Warning。
使用pycrypto库用于支付宝RSA签名,版本>=2.61。这里使用的是pycrypto,是因为安装比较方便,另外因为版本2.61之前在某种情况下,使用fork会出现随机数不安全的问题,详情请查看CVE-2013-1445。
职责
下面我们需要理清我们要做的事情,避免不必要的工作。主要是如下2个方面:
服务端负责生成订单及签名,及接受支付异步通知
客户端负责使用服务端传来的订单信息调用支付接口,及根据SDK同步返回的支付结果展示结果页。
另外,私钥必须放在服务端,签名过程也必须放在服务端。
支付方式比较
共同点
在这2种支付方式中,我们需要对签名的信息(URL键值对,例如key1=value1&key2=valu2...)按照ASCII编码顺序进行排序后再进行签名,并且采用POST方式进行提交。
不同点
在微信中,签名的方式采用的是md5,而支付宝采用的RSA。
在微信支付中,提交和返回数据都为XML格式,其根节点为xml。而在支付宝中,采用的是使用表单提交的方式来进行。
由于微信支付采用的是XML格式,因此字符编码采用的是UTF-8,而支付宝需要指定参数_input_charset来指定编码,官方建议我们采用UTF-8。
下面我们正式进行APP支付流程的说明,在这个过程中,我们需要阅读官方提供的文档。这里我们从微信开始,因为相比支付宝,微信的支付调用更为简单些。
微信
在进行模块代码编写之前,我们来看看官方提供的流程图。换句话说,在我们调用统一下单接口后,我们需要给APP客户端返回prepayid及生成的签名,另外还有APP端调起支付接口中的其他字段。
统一下单
这里,假设我们统一下单时请求参数如下:
appid=wx2421b1c4370ec43b&attach=支付测试&body=APP支付测试&mch_id=10000100&nonce_str=1add1a30ac87aa2db72f57a2375d8fec¬ify_url=http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php&out_trade_no=1415659990&spbill_create_ip=14.23.150.211&total_fee=1&trade_type=APP
而我们的商户号假设为1900000109,那么我们需要将商户号与之前的请求参数拼接在一起:
data = 'appid=wx2421b1c4370ec43b&attach=支付测试&body=APP支付测试&mch_id=10000100&nonce_str=1add1a30ac87aa2db72f57a2375d8fec¬ify_url=http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php&out_trade_no=1415659990&spbill_create_ip=14.23.150.211&total_fee=1&trade_type=APP&key=1900000109'>>> from hashlib import md5>>> md5(data).hexdigest().upper()'F3D12D07612100A7F0DA652E97A766FA'
这里我们拼接后的参数进行MD5加密后将其转换为大写字母,这样就得到我们需要的签名了。因此,在请求统一下单时,我们需要传递如下的字符串:
wx2421b1c4370ec43b 支付测试 APP支付测试 10000100 1add1a30ac87aa2db72f57a2375d8fec http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php 1415659990 14.23.150.211 1 APP F3D12D07612100A7F0DA652E97A766FA
关于签名校验,微信官方提供了1个校验工具,当在请求返回的err_code出现SIGNERROR时可以使用这个工具来辅助我们进行校验。
返回给客户端APP
当我们成功请求统一下单接口后,返回的结果可能如下所示:
接下来,我们需要取出返回结果中的prepay_id参数,然后按照调起支付接口中组装请求参数,假设我们得到如下的请求参数:
appid=wx2421b1c4370ec43b&noncestr=5K8264ILTKCH16CQ2&package=Sign=WXPay&partnerid=1900000109&prepayid=wx201411101639507cbf6ffd8b0779950874×tamp=1412000000
那么进行签名后将得到字符串0586C6E4A2AA6D297F4046362D878BAC。那么我们返回给客户端APP的字段主要有prepayid、noncestr、timestamp、sign。
异步回调
当用户成功完成支付后,微信会将相关支付信息推送到在统一下单时提交的notify_url指定的url地址中。在这一步,我们主要要做的是检验信息,比如签名是否正确、支付金额是否相同,可以在这个过程中修改订单的支付状态。
如果检验通过后,我们需要给微信返回类似如下的参数:
在这一步可能遇到的问题是无法接收到微信推送过来的参数,由于这里公司采用的是Flask,因此采用如下的方式来进行接收:
from flask import request...@app.route('/notify')def notify(): req = request.stream.read() ...
在这里,我采用的是从原始流中进行直接读取操作。
说完了微信,我们来看下支付宝的情况。
支付宝
这里我采用UTF-8编码进行处理,并查看如下的功能流程,让我们对支付流程有1个了解。
准备
在正式开始支付宝的支付之前,我们先来说下基础的一些内容,首先是要使用的私钥要是PKCS8格式的。然后是需要传递给支付宝的参数,其中基本参数partner、_input_charset、sign、sign_type、service这些属于基本参数,是必须要传递的参数。关于需要传递参数的内容请查阅参数。
支付待签名字符串生成
关于支付请求参数,我们可以查看下面的链接请求参数。
在支付宝APP支付中,我们需要请求参数中需要剔除sign_type、sign这2个参数,并在签名之前需要对字符串进行UTF-8的urlencode,即待签名字符串如果有中文则必须未转义形式显示,例如:
_input_charset="utf-8"¬ify_url="http://notify.msp.hk/notify.htm"&out_trade_no="0819145412-6177"&partner="2088101568338364"&seller_id="xxx@alipay.com"&service="mobile.securitypay.pay"&subject="测试"&payment_type="1"&total_fee="0.01"
在这里,我们对请求的参数进行了排序,然后请求的参数的数值需要添加双引号。之后,我们需要对上面的字符串进行签名处理,这里我们假设我们的私钥如下:
-----BEGIN RSA PRIVATE KEY-----MIICXAIBAAKBgQDQ3/XlPY/IFw8FISXKHVRLICPSEPmWCauMtKPoAc9M6szlCjG+YqtxaigPwVdRqoG3m24uMgz36qXyANvXMB3X7e6t6g1DoI3wxy5aNNlE0Dlu0BIHrcLUFsSZgCTuAvOori2oGVp6StXz0Wg5kacICnf6GNHCM1B2IgshEQte2wIDAQABAoGAMkbmanKiDFi4jdSHwxnCM38eAC+D1ECpoWnN1kexPWN7RFpq1NftSpRx5jD0srynEqoAIHB9vKMnpJPeVvLHC8ZvtZyehQPTvdaqdeORcZUhaYHYBWgiCCr/6fgW00yxR+UrYZFY6DEHbHkXgXqtEFzoVYIVwI6a90F/xFQ8hpECQQDoypOny/zUvocchTQ/JuqsmZXKNZgU+1c/3Kflz7RDpi9e94yR9eaBSLBTDEkngJkJD5/riTzC0O4AHb/2+5vzAkEA5bL5lgoCWyyVlvy/PBbZ2Ilcf+vMyvtyDBWklW9xrXEy53W+G4QqNSatTzNHN2VNEqFz2/3xNIbFlMpHzU3zeQJBAJS3thTgkKko/xANWQ9vQUT66WLBUmM1HsxBn1GFm9gL9v9ojnlA6v10/pBPrPx7f0j2nmfOyO58o0+XseeLXlkCQB55k2GTrGJaVPJ2UAzx3y86cjpKl54qpCP0TyTAZ22igiVxWqqd61en7QCABifUWdhp8UwzsefNJbOq7sHPYMkCQACbuh1TKx9AlZz1kPoAagBsZofx4cb5QnHpmIzREbRdaydfoaqR5BKpjJXky4tyBDeyp50s96UUd/eEYDC8RV4=-----END RSA PRIVATE KEY-----
在RSA签名就验证签名中,我们需要确保公钥和私钥都包含在BEGIN和END之间,且不需要进行将其放在1行中。
然后我们使用如下的方式进行签名操作:
from Crypto.Signature import PKCS1_v1_5from Crypto.Hash import SHAfrom Crypto.PublicKey import RSAfrom base64 import b64encodemessage = '_input_charset="utf-8"¬ify_url="http://notify.msp.hk/notify.htm"&out_trade_no="0819145412-6177"&partner="2088101568338364"&seller_id="xxx@alipay.com"&service="mobile.securitypay.pay"&subject="测试"&payment_type="1"&total_fee="0.01"'key = RSA.importKey(open('rsa_private_key.pem').read())h = SHA.new()h.update(message)signer = PKCS1_v1_5.new(key)signature = signer.sign(h)print b64encode(signature)
这样我们将得到签名:
FDW1YrI/FeX841orIDZ+rYyacSyDtWs4d+GPNpEMbWd38TpmePLagEIzAd8DDB3TlLxwyiA/IgGYIiLPQOk8qdIdp3AkjWHEMPmRbULZx2bMVNJlJy/yunOAbJRIJhP3I1Ip/nCFRVvBmBE3I8Mt95UQtYhtLkx+fZbuXmpCckQ=
在这里,官方所说的是SHAWithRSA函数对应于PKCS1_V1_5标准外加SHA1加密方式,需要主要的是这里生成的私钥的长度是1024位。
然后我们对参数字符串进行拼接将得到:
_input_charset="utf-8"¬ify_url="http://notify.msp.hk/notify.htm"&out_trade_no="0819145412-6177"&partner="2088101568338364"&seller_id="xxx@alipay.com"&service="mobile.securitypay.pay"&subject="测试"&payment_type="1"&total_fee="0.01"&sign="FDW1YrI/FeX841orIDZ+rYyacSyDtWs4d+GPNpEMbWd38TpmePLagEIzAd8DDB3TlLxwyiA/IgGYIiLPQOk8qdIdp3AkjWHEMPmRbULZx2bMVNJlJy/yunOAbJRIJhP3I1Ip/nCFRVvBmBE3I8Mt95UQtYhtLkx+fZbuXmpCckQ="&sign_type="RSA"
我们将生成的这串字符串返回给客户端APP调用即可。
异步回调
与微信一样,当用户成功支付后,支付宝会主动以POST方式将数据推送给你提交的notify_url中的URL。在这里,我们需要以表单的形式来接收传递过来的参数。
在此之前,我们说下一些关于通知的内容:
通知触发条件:支付宝只有在交易成功、支付成功以及交易创建是会触发通知,对于交易关闭时不触发通知的,换句话说在这些情况下会主动推送消息给你。
通知交易状态:主要有4种状态,TRADE_SUCCESS,TRADE_FINISHED、TRADE_CLOSED,WAIT_BUYER_PAY,分别对应交易成功、交易完成、交易关闭和等待买家付款。
而支付宝会传递过来的参数,我们可以查看服务器异步通知参数。
在异步回调中,我们需要完成如下2个验证的工作:
验证签名
验证是否是支付宝发来的通知
对于第2个验证,我们需要拼装成如下的URL:
https://mapi.alipay.com/gateway.do?service=notify_verify&partner=2088002396712354¬ify_id=RqPnCoPT3K9%252Fvwbh3I%252BFioE227%252BPfNMl8jwyZqMIiXQWxhOCmQ5MQO%252FWd93rvCB%252BaiGg
然后我们进行GET请求,而结果会返回1个true或false的字符串。
对于第1种验证,假设我们有如下的字符串:
discount=0.00&payment_type=8&subject=测试&trade_no=2013082244524842&buyer_email=dlwdgl@gmail.com&gmt_create=2013-08-22 14:45:23¬ify_type=trade_status_sync&quantity=1&out_trade_no=082215222612710&seller_id=2088501624816263¬ify_time=2013-08-22 14:45:24&body=测试测试&trade_status=TRADE_SUCCESS&is_total_fee_adjust=N&total_fee=1.00&gmt_payment=2013-08-22 14:45:24&seller_email=xxx@alipay.com&price=1.00&buyer_id=2088602315385429¬ify_id=64ce1b6ab92d00ede0ee56ade98fdf2f4c&use_coupon=N&sign_type=RSA&sign=1glihU9DPWee+UJ82u3+mw3Bdnr9u01at0M/xJnPsGuHh+JA5bk3zbWaoWhU6GmLab3dIM4JNdktTcEUI9/FBGhgfLO39BKX/eBCFQ3bXAmIZn4l26fiwoO613BptT44GTEtnPiQ6+tnLsGlVSrFZaLB9FVhrGfipH2SWJcnwYs=
我们剔除了sign和sign_type参数后,按照ASCII顺序进行排序,我们将得到如下的字符串:
body=测试测试&buyer_email=dlwdgl@gmail.com&buyer_id=2088602315385429&discount=0.00&gmt_create=2013-08-22 14:45:23&gmt_payment=2013-08-22 14:45:24&is_total_fee_adjust=N¬ify_time=2013-08-22 14:45:24¬ify_type=trade_status_sync&out_trade_no=082215222612710&payment_type=8&price=1.00&quantity=1&seller_email=alipayrisk18@alipay.com&seller_id=2088501624816263&subject=测试&total_fee=1.00&trade_no=2013082244524842&trade_status=TRADE_SUCCESS&use_coupon=N
然后我们进行如下的验证签名:
from Crypto.Signature import PKCS1_v1_5from Crypto.Hash import SHAfrom Crypto.PublicKey import RSAfrom base64 import b64decodesign = '1glihU9DPWee+UJ82u3+mw3Bdnr9u01at0M/xJnPsGuHh+JA5bk3zbWaoWhU6GmLab3dIM4JNdktTcEUI9/FBGhgfLO39BKX/eBCFQ3bXAmIZn4l26fiwoO613BptT44GTEtnPiQ6+tnLsGlVSrFZaLB9FVhrGfipH2SWJcnwYs='msg = 'body=测试测试&buyer_email=dlwdgl@gmail.com&buyer_id=2088602315385429&discount=0.00&gmt_create=2013-08-22 14:45:23&gmt_payment=2013-08-22 14:45:24&is_total_fee_adjust=N¬ify_time=2013-08-22 14:45:24¬ify_type=trade_status_sync&out_trade_no=082215222612710&payment_type=8&price=1.00&quantity=1&seller_email=alipayrisk18@alipay.com&seller_id=2088501624816263&subject测试&total_fee=1.00&trade_no=2013082244524842&trade_status=TRADE_SUCCESS&use_coupon=N'key = RSA.importKey(open('alipay_public_key.pem').read())sign = b64decode(sign)h = SHA.new(msg)verifier = PKCS1_v1_5.new(key)print verifier.verify(h,sign)
在这里,我们读取支付宝的公钥,然后对签名进行base64编码解密,然后进行比对操作,其结果为1个布尔值。
最后,如果2个检验都通过,我们需要返回给支付宝1个字符串success即可。
参考文章:
https://doc.open.alipay.com/d...
https://doc.open.alipay.com/d...
关键字:Python, 微信
版权声明
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!