通用技术 [测试小结] 微信、支付宝扫码支付

黑水 · 2017年01月12日 · 最后由 黑水 回复于 2017年01月23日 · 5858 次阅读
本帖已被设为精华帖!

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

搭建测试环境

系统可以简化为四个部分: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 请求,通知支付结果,所以需要外网能访问。
找运维帮忙做了映射,从 **.testerhome.com:5432 映射到支付网关的局域网地址 192.168.1.101:8080。
回调地址填 *
*.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、 原来通知是:微信服务端 > **.testerhome.com:5432 > 192.168.1.101:8080(支付网关)
加上 Charles 反向代理 变成:
微信服务端 > *
*.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,添加根证书到 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%2F***.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%2F***.testerhome.com:5432

bug 举例,用例设计思路

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

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

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

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

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

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

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

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

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

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

其他

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

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 13 条回复 时间 点赞

非常感谢!!! 解决了微信沙盒测试的签名问题,好大的坑啊。。。

好奇哪里看到这个新的 getsignkey API 的。

哦,看到了,在微信支付商户接入验收助手公告里面。

我的测试通过了,但在那个助手 “查询验收结果” 扫码支付里还是显示 “待验收”。我用的是统一下单 API,验证没有通过会影响 production 使用吗?

#2 楼 @kenhuang 看起来没什么影响,我也没用,只是看着用例自己测。

思寒_seveniruby 将本帖设为了精华贴 01月13日 12:19

加精理由: 分享了微信和支付宝沙箱的应用,对支付测试有帮助.

666!学习了

楼主你好,我参照你的方法在获取沙箱的商户 Key 时总是失败,获取到的返参为:

<xml>
  <content><![CDATA[{'return_code': 'FAIL', 'return_msg': '\xe8\xaf\xb7\xe7\xa1\xae\xe8\xae\xa4\xe8\xaf\xb7\xe6\xb1\x82\xe5\x8f\x82\xe6\x95\xb0\xe6\x98\xaf\xe5\x90\xa6\xe6\xad\xa3\xe7\xa1\xaemch_id'}]]></content>
</xml>

入参中 mch_id、nonce_str 都是我自己随便定义的字符串;sign 是用楼主提供的方法,使用【微信支付接口签名校验工具】生成的。如下所示:

<xml>
  <mch_id>1234567890ABCDE0123456789</mch_id>
  <nonce_str>12ABCDE01239111</nonce_str>
  <sign>428992907B084EA5FF8DF4E773853A98</sign>
</xml>

我使用 chrome 的扩展程序 postman 请求的。可以再详细解释一下操作步骤吗?

请问用 charles 抓 https 明文这块可以再讲一下吗,不太理解是怎么回事

黑水 #10 · 2017年01月22日 Author

#8 楼 @tang5188

mch_id是微信支付商户号,nonce_str 随便填不超过 32 位的字符串,sign 也随便填。

mch_id不是随便填的

黑水 #11 · 2017年01月23日 Author

#9 楼 @miao
1、不会用 Charles
2、会用 Charles 抓 HTTP 的包,不会抓 HTTPS 的
3、会抓手机上 HTTPS,不会抓 HTTPS 的
4、HTTPS 不是保密吗?为什么抓包还能看到明文?
5、……
6、……
……
你是问 1、2、3、4 呢,还是 5、6、7、8 呢?

#11 楼 @sanlengjingvv 我问的是第四个问题

微信的沙箱没有收到支付回调是怎么回事呢?但是去查询的时候订单是支付成功的

黑水 #14 · 2017年01月23日 Author

不知道你了解多少,所以给你几个关键词 Google :“SSL 双向认证 单向认证”、“信任链”、“根证书”
比如根证书的 Wiki 有这么一段:

许多应用程序会代表用户信任值得信任的根证书。例如,网页浏览器会使用它们验证 TLS 安全连接中的身份。但是,这意味着用户信任浏览器的发布商、它所信任的证书颁发机构,以及这些证书颁发机构可能颁发的所有中间证书颁发机构,相信他们忠诚地确保各证书持有人的身份和意图。这种信任关系的传递是一种普遍情况,并且是 X.509 证书链模型所设想的方式。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册