使用wechatpy实现小程序微信支付

先决条件

微信小程序的的开发文档,不仅不完善,而且零散在各个不同的页面之间,很多文档只有说明缺乏实例,而且坑众多,给开发者带了不少的麻烦,调试要花掉太多的时间。

本着别人造好了轮子,我们拿来就用的实用主义原则,这次我们找到了python的一个开源sdk:wechatpy. 使用别人的轮子也有风险,文档说明更少,摸索需要一段时间,如果最后没有调通,也是白白浪费时间,请自行评估。

这次我们的目的是使用sdk来完成微信支付的整个闭环逻辑,首选需要准备的数据如下:

  1. 微信公众号/小程序的APPID(密钥不需要)
  2. 开通了微信支付的商户号
  3. 微信支付商户号的密钥

微信支付的流程

  1. 小程序/公众号端提交给后台订单信息,后台调用微信接口创建与预付单
  2. 后台将prepay_id返回给小程序,小程序端调起支付功能
  3. 用户支付完成,小程序完成支付
  4. 微信服务器推送用户支付的消息到后台,后台修改订单的状态为完成

wechatpy创建预付单

1
2
3
4
5
6
7
8
9
10
11
pay = WeChatPay(appid=CurrentConfig.WX_APPID, api_key=CurrentConfig.MNT_KEY,
sub_appid=CurrentConfig.WX_APPID, mch_id=CurrentConfig.MNT_ID)
res = pay.order.create(
trade_type="JSAPI",
body=coupons[0].coupon_code,
total_fee=pay_amount,
notify_url=CurrentConfig.NOTIFY_URL,
user_id=members[0].open_id,
out_trade_no=order_no
)

其中WX_APPID为小程序或公众号ID,MNT_KEY为商户号密钥,MNT_ID是商户ID

out_trade_no是后台生成商户订单,需要唯一,如果不传就是微信自动生成的单号,由于我们需要在后续的微信推送的消息中处理订单,所以这里一定要写我们自己生成的订单号。

创建预付订单以后,微信会把生成的prepay_id返回:

1
prepay_id = res.get("prepay_id")

然后给小程序返回必须要的信息:

1
2
3
4
5
6
7
8
9
10
params = {
"appId": CurrentConfig.WX_APPID,
"timeStamp": timestamp,
"nonceStr": nonce_str,
"package": package,
"signType": "MD5",
}
strs = '&'.join(['{}={}'.format(key, params.get(key))
for key in sorted(params.keys()) if params.get(key)]) + "&key={}".format(CurrentConfig.MNT_KEY)
paySign = md5(strs.encode("utf-8")).hexdigest().upper()

微信的签名逻辑需要注意:

  1. 商户密钥不参与字典序排序
  2. md5后需要转大写
  3. 参与排序的字典名要与微信的文档严格保持一致

将timeStamp,package,nonceStr等返回给小程序,然后小程序调用wx.payment方法即可调起付款

微信消息推送

在统一下单的接口中有一个参数是notify_url,这个是微信支付完成后微信后台服务器给我们后台推送消息的URL接口地址,该URL不能带有参数,且必须是公网可以访问的地址。

微信推送消息是XML格式,使用wechatpy的parse_payment_result方法可以将结果转化成OrderedDict类型,且帮你做好了验签。

1
2
3
pay = WeChatPay(appid=CurrentConfig.WX_APPID, api_key=CurrentConfig.MNT_KEY,
sub_appid=CurrentConfig.WX_APPID, mch_id=CurrentConfig.MNT_ID)
data = pay.parse_payment_result(request.data)

然后就可以根据返回的结果,处理之前的订单了。
唯一需要注意的一点,微信推送消息后,需要给微信服务器返回一个消息:

1
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"