019 Python实现微信小程序支付功能
正文
由于最近自己在做小程序的支付,就在这里简单介绍一下讲一下用python做小程序支付这个流程。当然在进行开发之前还是建议读一下具体的流程,清楚支付的过程。
1.支付交互流程
当然具体的参数配置可以参考官方文档https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1
2.获取openid(微信用户标识)
1 import requests
2
3 from config import APPID, SECRET
4
5
6 class OpenidUtils(object):
7
8 def __init__(self, jscode):
9 self.url = "https://api.weixin.qq.com/sns/jscode2session"
10 self.appid = APPID # 小程序id
11 self.secret = SECRET # 不要跟后面支付的key搞混
12 self.jscode = jscode # 前端传回的动态jscode
13
14 def get_openid(self):
15 # url一定要拼接,不可用传参方式
16 url = self.url + "?appid=" + self.appid + "&secret=" + self.secret + "&js_code=" + self.jscode + "&grant_type=authorization_code"
17 r = requests.get(url)
18 print(r.json())
19 openid = r.json()['openid']
20
21 return openid
3.支付请求
1 # -*- coding:utf-8 -*-
2 import requests
3 import hashlib
4 import xmltodict
5 import time
6 import random
7 import string
8 import urllib2
9 import sys
10
11
12 class WX_PayToolUtil():
13 """ 微信支付工具 """
14
15 def __init__(self, APP_ID, MCH_ID, API_KEY, NOTIFY_URL):
16 self._APP_ID = APP_ID # 小程序ID
17 self._MCH_ID = MCH_ID # # 商户号
18 self._API_KEY = API_KEY
19 self._UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" # 接口链接
20 self._NOTIFY_URL = NOTIFY_URL # 异步通知
21
22 def generate_sign(self, param):
23 '''生成签名'''
24 stringA = ''
25 ks = sorted(param.keys())
26 # 参数排序
27 for k in ks:
28 stringA += (k + '=' + param[k] + '&')
29 # 拼接商户KEY
30 stringSignTemp = stringA + "key=" + self._API_KEY
31 # md5加密,也可以用其他方式
32 hash_md5 = hashlib.md5(stringSignTemp.encode('utf8'))
33 sign = hash_md5.hexdigest().upper()
34 return sign
35
36 '''
37 # python2另外一种实现方法
38 def generate_sign(self, params):
39 ret = []
40 for k in sorted(params.keys()):
41 if (k != 'sign') and (k != '') and (params[k] is not None):
42 ret.append('%s=%s' % (k, params[k]))
43 params_str = '&'.join(ret)
44 params_str = '%(params_str)s&key=%(partner_key)s' % {'params_str': params_str, 'partner_key': key}
45
46 reload(sys)
47 sys.setdefaultencoding('utf8')
48
49 params_str = hashlib.md5(params_str.encode('utf-8')).hexdigest()
50 sign = params_str.upper()
51 return sign
52 '''
53
54 def getPayUrl(self, orderid, openid, goodsPrice, **kwargs):
55 """向微信支付端发出请求,获取url"""
56 key = self._API_KEY
57 nonce_str = ''.join(random.sample(string.letters + string.digits, 30)) # 生成随机字符串,小于32位
58 params = {
59 'appid': self._APP_ID, # 小程序ID
60 'mch_id': self._MCH_ID, # 商户号
61 'nonce_str': nonce_str, # 随机字符串
62 "body": '测试订单', # 支付说明
63 'out_trade_no': orderid, # 生成的订单号
64 'total_fee': str(goodsPrice), # 标价金额
65 'spbill_create_ip': "127.0.0.1", # 小程序不能获取客户ip,web用socekt实现
66 'notify_url': self._NOTIFY_URL,
67 'trade_type': "JSAPI", # 支付类型
68 "openid": openid, # 用户id
69 }
70 # 生成签名
71 params['sign'] = self.generate_sign(params)
72
73 # python3一种写法
74 param = {'root': params}
75 xml = xmltodict.unparse(param)
76 response = requests.post(self._UFDODER_URL, data=xml.encode('utf-8'), headers={'Content-Type': 'text/xml'})
77 # xml 2 dict
78 msg = response.text
79 xmlmsg = xmltodict.parse(msg)
80 # 4. 获取prepay_id
81 if xmlmsg['xml']['return_code'] == 'SUCCESS':
82 if xmlmsg['xml']['result_code'] == 'SUCCESS':
83 prepay_id = xmlmsg['xml']['prepay_id']
84 # 时间戳
85 timeStamp = str(int(time.time()))
86 # 5. 五个参数
87 data = {
88 "appId": self._APP_ID,
89 "nonceStr": nonce_str,
90 "package": "prepay_id=" + prepay_id,
91 "signType": 'MD5',
92 "timeStamp": timeStamp,
93 }
94 # 6. paySign签名
95 paySign = self.generate_sign(data)
96 data["paySign"] = paySign # 加入签名
97 # 7. 传给前端的签名后的参数
98 return data
99
100 # python2一种写法
101 '''
102 request_xml_str = '<xml>'
103 for key, value in params.items():
104 if isinstance(value, str):
105 request_xml_str = '%s<%s><![CDATA[%s]]></%s>' % (request_xml_str, key, value, key,)
106 else:
107 request_xml_str = '%s<%s>%s</%s>' % (request_xml_str, key, value, key,)
108 request_xml_str = '%s</xml>' % request_xml_str
109
110 # 向微信支付发出请求,并提取回传数据
111 res = urllib2.Request(self._UFDODER_URL, data=request_xml_str.encode("utf-8"))
112 res_data = urllib2.urlopen(res)
113 res_read = res_data.read()
114 doc = xmltodict.parse(res_read)
115 return_code = doc['xml']['return_code']
116 if return_code == "SUCCESS":
117 result_code = doc['xml']['result_code']
118 if result_code == "SUCCESS":
119 doc = doc['xml']
120 data = {
121 "appId": self._APP_ID,
122 "nonceStr": nonce_str,
123 "package": "prepay_id=" + doc["prepay_id"],
124 "signType": 'MD5',
125 "timeStamp": str(int(time.time())),
126 }
127 # paySign签名
128 paySign = self.generate_sign(data)
129 data["paySign"] = paySign # 加入签名
130 return data
131 else:
132 err_des = doc['xml']['err_code_des']
133 return err_des
134 else:
135 fail_des = doc['xml']['return_msg']
136 return fail_des
137 '''
当然你可能会遇到的错误有签名错误,一般的情况是你的appSecret和商户号的API密钥两个弄错了,当然如果不是还有可能是其他问题,解决方案链接https://www.cnblogs.com/wanghuijie/p/wxpay_sign_error.html。
其他的支付方式获取用户的ip地址可以通过socket.gethostbyname(socket.gethostname())方法来获取。
4.支付回调
1 # 统一下单回调处理 2 3 import xmltodict 4 5 from django.http import HttpResponse 6 7 def payback(request): 8 msg = request.body.decode('utf-8') 9 xmlmsg = xmltodict.parse(msg) 10 11 return_code = xmlmsg['xml']['return_code'] 12 13 if return_code == 'FAIL': 14 # 官方发出错误 15 return HttpResponse("""<xml><return_code><![CDATA[FAIL]]></return_code> 16 <return_msg><![CDATA[Signature_Error]]></return_msg></xml>""", 17 content_type='text/xml', status=200) 18 elif return_code == 'SUCCESS': 19 # 拿到这次支付的订单号 20 out_trade_no = xmlmsg['xml']['out_trade_no'] 21 22 # 根据需要处理业务逻辑 23 24 return HttpResponse("""<xml><return_code><![CDATA[SUCCESS]]></return_code> 25 <return_msg><![CDATA[OK]]></return_msg></xml>""", 26 content_type='text/xml', status=200)
当然微信回调的参数有很多详细可以参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8
在回调的时候可能遇到这样一个问题,支付成功以后没有调回调函数,有可能是回调地址是https然后改为http就行,遇到过这个坑,具体原因也不知道。服务器没有屏蔽https访问,https证书也没有问题,把https改为http最后就可以了。
5.安全问题
在使用的过程中商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
我在开发过程中的解决方式是在向微信支付端发起请求的时候,把订单号,金额,签名等存入数据库,然后在回调函数那里进行校验判断。在确认跟前面订单情况一样的情况下,才进行后续一系列的操作。
最后送给大家一段祝福
# _oo8oo_ # o8888888o # 88" . "88 # (| -_- |) # 0\ = /0 # ___/'==='\___ # .' \\| |# '. # / \\||| : |||# \ # / _||||| -:- |||||_ \ # | | \\\ - #/ | | # | \_| ''\---/'' |_/ | # \ .-\__ '-' __/-. / # ___'. .' /--.--\ '. .'___ # ."" '< '.___\_<|>_/___.' >' "". # | | : `- \`.:`\ _ /`:.`/ -` : | | # \ \ `-. \_ __\ /__ _/ .-` / / # =====`-.____`.___ \_____/ ___.`____.-`===== # `=---=` # # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # 强大爷保佑 永不宕机/永无bug