公司开发了自己的支付网关,第一版刚上线,有微信、支付宝扫码支付 (商户提供二维码,买家扫码),做了些笔记。
微信和支付宝都有多种支付方式,也有新老版本,我看的对应文档是这两个:
微信商户平台 - 扫码支付
蚂蚁金服开放平台 - 当面付 - 扫码支付
系统可以简化为四个部分: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 元的商品,分别用支付宝和微信买一次。
因为测的时候还没上线,所以比较自由。这里测试可能还要关注:上线流程,支付账户信息的保护,支付网关有没有记录来源帮助运营财务区分测试数据(或者有公司就不允许产生测试数据)之类的问题。
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¬ify_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¬ify_url=http%3A%2F%2F***.testerhome.com:5432
位置:
设置-支付账户
测试结果:
填写信息前后有空格,不能支付
期望结果:
保存时去除前后空格
这种是 Web 测试的通用方法能发现的。
商品有促销,支付网关和服务端订单价格不一致
步骤:
商品 A 价格 10 元,买家购买时使用 10% 折扣券
测试结果:
支付网关和服务端订单价格不一致
这种是通过画业务流程路径图,然后遍历发现的。
用户支付成功,支付网关处理超时,没有发货
步骤:
1、 App 购买商品,获得二维码
2、 通过代理阻挡支付网关给服务端的 Response
3、 用户完成支付
4、 等待支付时间超时
测试结果:
订单成为结束状态
期望结果:
服务端应该继续向支付网关查询
和微信验收用例 3 的思路一样,网络请求会失败、超时、会重发,不幂等。
退出支付界面后再完成支付,没有发货
步骤:
1、App 选择商品,买家用支付宝或微信扫码但不输入密码
2、App 退出支付界面
3、买家输入支付密码完成支付
测试结果:
支付成功,没有发货
开发文档有写,第一次看不明白。系统熟悉之后再看文档就想到测试场景了。
以下情况需要调用关单接口:商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
微信沙箱挺好用的,不用打开微信,等 5 秒就会变成已支付状态并向回调 URL 通知,方便把验收用例自动化。
用了两星期,支付宝沙箱坏了 5 次,有时候看不了账单,有时候不能扫码,有时候不返回二维码……
想要稳定丰富场景的自动化、测性能还是需要 mock 不少东西。