scribble

sanlengjingvv

Blog GitHub

12 Jan 2017
微信、支付宝扫码支付测试小结

公司开发了自己的支付网关,第一版刚上线,有微信、支付宝扫码支付(商户提供二维码,买家扫码),做了些笔记。
微信和支付宝都有多种支付方式,也有新老版本,我看的对应文档是这两个:
微信商户平台-扫码支付
蚂蚁金服开放平台-当面付-扫码支付

搭建测试环境

系统可以简化为四个部分:App<=>服务端<=>支付网关<=>微信服务端,通过 HTTPS 通信。
1、 本机启动服务端和支付网关两个 Docker 容器。
2、 在服务端配置文件填写支付网关的 IP 和端口。
3、 打包连接本机服务端的 App。
4、 填写支付网关使用的支付账户信息,长这样:

微信支付商户号:1234567890  
微信 APPID :abcd1ccc22c333aaa  
微信 API 密钥(商户 Key ):lmnopqabcd123456efghijkrstuvwxyz

支付宝卖家 ID: 2012345678901234
支付宝 APPID: 2010101010101010

支付宝 RSA 私钥:
-----BEGIN RSA PRIVATE KEY-----
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFab
-----END RSA PRIVATE KEY-----

支付宝 RSA 公钥:
-----BEGIN PUBLIC KEY-----
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/ABC+DEFabcdef/A
abcdef/ABC+DEFabcdef/ABC
-----END PUBLIC KEY-----

5、 在支付网关填写支付宝和微信的回调地址:
微信和支付宝会向回调地址发送 HTTP 请求,通知支付结果,所以需要外网能访问。
找运维帮忙做了映射,从 vpn.testerhome.com:5432 映射到支付网关的局域网地址 192.168.1.101:8080。
回调地址填 vpn.testerhome.com:5432。
6、 在服务端后台创建一个 0.01 元的商品,分别用支付宝和微信买一次。

因为测的时候还没上线,所以比较自由。这里测试可能还要关注:上线流程,支付账户信息的保护,支付网关有没有记录来源帮助运营财务区分测试数据(或者有公司就不允许产生测试数据)之类的问题。

使用微信支付仿真测试环境( sandbox )

1、 在支付网关配置里修改微信 URL

例如,刷卡支付URL:https://api.mch.weixin.qq.com/pay/micropay
变更为:https://api.mch.weixin.qq.com/sandboxnew/pay/micropay

2、 获取微信沙箱 API 密钥(商户 Key ),沙箱 2017 年 1 月 6 号升级前不需要这步,使用者都用同一个沙箱密钥,目前( 11 号)微信开发文档还没更新是错的。
2.1、 在微信支付接口签名校验工具里,“XML 源串”填写:

<_xml>
  <mch_id>1234567890</mch_id>
  <nonce_str>sQsUNUqeKIo</nonce_str>
  <sign>A</sign>
</_xml>

mch_id是微信支付商户号,nonce_str随便填不超过 32 位的字符串,sign也随便填。
“商户 Key ”填前面搭建测试环境时候用到的:

微信 API 密钥(商户 Key ):lmnopqabcd123456efghijkrstuvwxyz

点击“校验签名”,得到“新 sign 值”

#4.md5校验结果:
原sign值:A
新sign值:FC28C21A2D038E85D14A4F5C73BA3817

2.2、 用 Postman 发送 HTTP POST 请求到 https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey ,Body 是:

<_xml>
  <mch_id>1234567890</mch_id>
  <nonce_str>sQsUNUqeKIo</nonce_str>
  <sign>FC28C21A2D038E85D14A4F5C73BA3817</sign>
</_xml>

<sign>...</sign>是上一步得到的“新 sign 值”,Response 是:

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <return_msg><![CDATA[ok]]></return_msg>
  <sandbox_signkey><![CDATA[zyxwvutsrkjihgfe654321dcbaqponml]]></sandbox_signkey>
</xml>

3、 在支付网关里把微信 API 密钥(商户 Key )改成 zyxwvutsrkjihgfe654321dcbaqponml 。
4、 在服务端后台创建一个 3.01 元的商品,在 App 上下单,出现二维码后不用扫,5 秒后微信服务端会通知商户服务端支付完成。

执行微信验收用例

微信提供了扫码支付验收用例,执行一遍看看。
执行用例 3,发现支付网关有 Bug。
执行用例 6,发现微信沙箱有 Bug……

用例6【扫码-异常】订单金额 3.33 元,用户支付成功,微信支付通知签名非法

执行用例后订单状态是“已支付”,开始以为支付网关有问题,在开发帮助下加 log 得到微信回调通知的请求,发现在[微信支付接口签名校验工具]能校验通过。(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1)。
沙箱支付回调通知签名并没有非法,那测试要怎么模拟这个场景呢?加 log 的时候明白了些微信支付部分的代码,验证微信回调是这里:

@http.route(
    ['/weixin_callback'],
    type='http')
def weixin_callback():
    request = request.httprequest.data
    is_valid = callback_verify(request)

验证前把 sign 修改掉:

@http.route(
    ['/weixin_callback'],
    type='http')
def weixin_callback():
    request = request.httprequest.data
    request.replace("<sign>.*</sign>","<sign>Wrong</sign>")
    is_valid = callback_verify(request)

改完重启服务,执行用例后订单状态仍然是“已支付”,就提了 Bug。
但写这段代码的开发不在,被指派修 Bug 的开发怀疑我改的不对,我自己也怀疑。所以还是找在盒子外改的方法。

抓包

1、 原来通知是:微信服务端 > vpn.testerhome.com:5432 > 192.168.1.101:8080(支付网关)
加上 Charles 反向代理 变成:
微信服务端 > vpn.testerhome.com:5432 > 192.168.1.101:8080( Charles ) > 192.168.1.101:8081(支付网关)
微信服务端有多个 IP ,所以在 Access Control Setting 添加 0.0.0.0/0 。
2、 还要抓支付网关发向微信服务端的请求,因为只能打开一个 Charles ,所以需要换个代理工具,或者在局域网内另一台电脑用 Charles。
我是用了另一台电脑( 192.168.1.102 )打开 Charles 监听 8888 端口。指定要抓 HTTPS 的域名端口:api.mch.weixin.qq.com:443
3、 将 Charles 根证书 放到支付网关所在的容器,执行命令:

cat /dockerVolumn/charles-ssl-proxying-certificate.pem >> /etc/pki/tls/cert.pem
export http_proxy=192.168.1.102:8888
export HTTP_PROXY=192.168.1.102:8888
export https_proxy=192.168.1.102:8888
export HTTPS_PROXY=192.168.1.102:8888
curl -v https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery 

4、 Charles 弹窗询问是否允许连接,点 allow ,然后能得到 HTTPS 明文了。
5、 但是重启支付网关购买商品出现SSL: CERTIFICATE_VERIFY_FAILED错误,查资料发现 Python 的 Request 库默认使用 [Mozilla trust store] (https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt),添加根证书到 linux 信任列表没有用,需要这样指定:

export REQUESTS CA_BUNDLE=/dockerVolumn/charles-ssl-proxying-certificate.pem
# 验证一下
python
>>> import requests
>>> requests.get('https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery').text

6、 重启支付网关购买商品,能看到明文。在反向代理给 weixin_callback 加断点,在正向代理给 orderquery 加断点,修改 weixin_callback request 里的 sign ,阻挡 orderquery 的 response。
支付仍然能成功,开发觉得这样可以证明程序有问题,就去调查原因了。
7、 如果要测“签名正确,其他信息错误”的情况,需要通过微信支付接口签名校验工具计算修改后内容的 sign 值。

支付宝沙箱、验收

1、 根据支付宝沙箱环境使用说明设置后得到沙箱环境的支付宝卖家 ID、APPID、RSA 私钥、RSA 公钥,Android 支付宝钱包沙箱版的安装包、登录用户名密码、支付密码。
2、 在支付网关把支付账户信息改成沙箱环境的。
3、 在支付网关配置里修改支付宝 URL

https://openapi.alipay.com/gateway.do 修改为 https://openapi.alipaydev.com/gateway.do

4、 另一台手机上安装 Android 支付宝钱包沙箱版。
5、 重启支付网关,下单,用支付宝钱包沙箱版扫码支付。
6、 参考微信的验收用例,通过 Chrales 断点模拟重发、超时、签名错误情况。
7、 看到支付宝有云验收功能,看了一下发现是给“买家提供二维码,商户扫码”这种情况提供的,就没继续试。
8、 参考微信验收用例 7,测试“签名正确,关键信息不一致”的情况,因为没有支付宝私钥(当然没有啦)。得自己生成一对公钥私钥,公钥填写到支付网关的账户信息中,用私钥对构造的请求签名,Postman 直接发请求给支付网关。支付宝的签名验签工具一直调不通,先用了开发已经实现的程序,还需要研究。

发现问题了,如果按照支付宝文档把charset=UTF-8放在请求 body 里,像下面的请求,支付宝发给回调地址的请求仍然是 GBK 编码的。

POST /gateway.do HTTP/1.1
Host: openapi.alipaydev.com
Content-Type: application/x-www-form-urlencoded

charset=UTF-8&method=alipay.trade.precreate&notify_url=http%3A%2F%2Fvpn.testerhome.com:5432

需要把charset=UTF-8放在请求 header 里,支付宝发给回调地址的请求才是 UTF-8 编码的。

POST /gateway.do HTTP/1.1
Host: openapi.alipaydev.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8

charset=UTF-8&method=alipay.trade.precreate&notify_url=http%3A%2F%2Fvpn.testerhome.com:5432

bug 举例,用例设计思路

位置:  
设置-支付账户  
测试结果:
填写信息前后有空格,不能支付  
期望结果: 
保存时去除前后空格  

这种是 Web 测试的通用方法能发现的。

商品有促销,支付网关和服务端订单价格不一致
步骤:  
商品 A 价格 10 元,买家购买时使用 10% 折扣券
测试结果:
支付网关和服务端订单价格不一致

这种是通过画业务流程路径图,然后遍历发现的。

用户支付成功,支付网关处理超时,没有发货
步骤:  
1、 App 购买商品,获得二维码
2、 通过代理阻挡支付网关给服务端的 Response 
3、 用户完成支付
4、 等待支付时间超时
测试结果:
订单成为结束状态
期望结果: 
服务端应该继续向支付网关查询

和微信验收用例 3 的思路一样,网络请求会失败、超时、会重发,不幂等。

退出支付界面后再完成支付,没有发货
步骤:  
1、App 选择商品,买家用支付宝或微信扫码但不输入密码    
2、App 退出支付界面
3、买家输入支付密码完成支付  

测试结果:  
支付成功,没有发货  

开发文档有写,第一次看不明白。系统熟悉之后再看文档就想到测试场景了。

以下情况需要调用关单接口:商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。

其他

微信沙箱挺好用的,不用打开微信,等 5 秒就会变成已支付状态并向回调 URL 通知,方便把验收用例自动化。
用了两星期,支付宝沙箱坏了 5 次,有时候看不了账单,有时候不能扫码,有时候不返回二维码……
想要稳定丰富场景的自动化、测性能还是需要 mock 不少东西。


Til next time,
黑水 at 19:20

scribble

Blog GitHub