python实现微信扫码支付完整流程
Posted Dream_it_possible!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python实现微信扫码支付完整流程相关的知识,希望对你有一定的参考价值。
文章目录
微信官方开发文档:
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1
一、支付前要做的准备
我们需要准备的东西:
-
微信开放平台申请应用的appid , 商户号mch_id, 秘钥API_KEY, 这些信息会作为收款的凭据。
-
安装依赖:
python的版本是: 3.7, django的版本是: 2.0.13
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple lxml
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django-qrcode==0.3
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django-wechat==0.1a1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple html5lib==1.0.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple idna==2.7
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple image==1.5.25
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple optionaldict==0.1.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytz==2018.5
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple qrcode==6.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple six==1.10.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple urllib3==1.23
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple webencodings==0.5.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple wechatpy==1.7.5
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xmltodict==0.11.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple bs4
- 统一支付接口: https://api.mch.weixin.qq.com/pay/unifiedorder
,此接口必须采用post方式调用,必须需要传的参数解释如下:
字段名 | 变量名 | 类型 | 示例值 | 描述 |
---|---|---|---|---|
公众账号ID | appid | String(32) | wxd678efh567hg6787 | 微信支付分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | String(32) | 1230000109 | 微信支付分配的商户号 |
随机字符串 | nonce_str | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
商品描述 | body | String(128) | 腾讯充值中心-QQ会员充值 | 商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商户订单号 | out_trade_no | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_- |
标价金额 | total_fee | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | String(64) | 123.12.12.123 | 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP |
通知地址 | notify_url | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | String(16) | JSAPI | JSAPI -JSAPI支付NATIVE -Native支付APP -APP支付 |
注: 我们是扫码模式的支付,因此要设置trade_type='NATIVE ’ |
- 支付接口参数样例:
<xml>
<appid>wx2421b1c4370ec43b</appid>
<attach>支付测试</attach>
<body>JSAPI支付测试</body>
<mch_id>10000100</mch_id>
<detail><![CDATA[ "goods_detail":[ "goods_id":"iphone6s_16G", "wxpay_goods_id":"1001", "goods_name":"iPhone6s 16G", "quantity":1, "price":528800, "goods_category":"123456", "body":"苹果手机" , "goods_id":"iphone6s_32G", "wxpay_goods_id":"1002", "goods_name":"iPhone6s 32G", "quantity":1, "price":608800, "goods_category":"123789", "body":"苹果手机" ] ]]></detail>
<nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str><notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
<openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
<out_trade_no>1415659990</out_trade_no>
<spbill_create_ip>14.23.150.211</spbill_create_ip>
<total_fee>1</total_fee>
<trade_type>NATIVE</trade_type>
<sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>
- 在python中用字典进行处理需要的参数,在发起支付时,给参数转换为XML格式的,调用成功后,再将返回的XML格式的结果转换为字典处理即可,可用如下代码进行转换。
# 定义字典转XML的函数
def trans_dict_to_xml(data_dict):
data_xml = []
for k in sorted(data_dict.keys()): # 遍历字典排序后的key
v = data_dict.get(k) # 取出字典中key对应的value
if k == 'detail' and not v.startswith('<![CDATA['): # 添加XML标记
v = '<![CDATA[]]>'.format(v)
data_xml.append('<key>value</key>'.format(key=k, value=v))
return '<xml version="1.0" encoding="UTF-8" ></xml>'.format(''.join(data_xml)) # 返回XML
# 定义XML转字典的函数
def trans_xml_to_dict(data_xml):
soup = BeautifulSoup(data_xml, features='xml')
xml = soup.find('xml') # 解析XML
if not xml:
return
data_dict = dict([(item.name, item.text) for item in xml.find_all()])
return data_dict
二、发起微信支付
知道我们需要哪些东西后,就可以通过如下的方式来实现返回二维码,生成的二维码通过base64加密,然后返回给前端。
1.生成支付二维码
生成二维码的同时,我们应该先在系统内生成订单记录,为后续通过云服务器回调通知做准备。
from __future__ import unicode_literals
from random import Random
import io
from my_app.pay.produce_order import UpdateOrder
from django.shortcuts import render
from django.http.response import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt # 解除csrf验证
from my_app.models import MetaSettings, StudentBugCourseRecord, LoggerRecord
from my_app.BaseView import BaseView
from bs4 import BeautifulSoup
import base64
import random
import requests
import hashlib
import qrcode
import time
from HttpResult import Result
def wxpay(request, datas):
meta = MetaSettings.objects.using(BaseView.get_partner(None, request)).values_list("wechat_appid",
"wechat_merchant_id",
"wechat_secret_key").first()
r = Result()
try:
if "未设置" in meta:
raise Exception("请维护微信信息!")
_APP_ID = meta[0]
_MCH_ID = meta[1]
_API_KEY = meta[2]
_CREATE_IP = "192.168.124.8"
_NOTIFY_URL = request.get_host() + "/app/wechat/notify"
_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"
order_number = datas["order_number"]
nonce_str = order_number # 拼接出随机的字符串即可,我这里是用 时间+随机数字+5个随机字母
total_fee = int((datas["total_pay"] * 100)) # 付款金额,单位是分,必须是整数
subject = "购买课程" # 商品描述
out_trade_no = order_num() # 订单编号
params =
"appid": _APP_ID, # APPID
"mch_id": _MCH_ID, # 商户号
"nonce_str": nonce_str,
"out_trade_no": out_trade_no, # 订单编号
"total_fee": total_fee, # 订单总金额
"spbill_create_ip": _CREATE_IP, # 发送请求服务器的IP地址
"notify_url": _NOTIFY_URL, # 支付结果通知地址
"body": subject, # 商品描述
"trade_type": 'NATIVE' # 扫码支付
sign = get_sign(params, _API_KEY) # 获取签名
params['sign'] = sign # 添加签名到参数字典
request.session[order_number] = params
# print(params)
xml = trans_dict_to_xml(params) # 转换字典为XML
data = xml.encode("utf-8")
response = requests.request('post', _PAY_URL, data=data) # 以POST方式向微信公众平台服务器发起请求
content = response.content
data_dict = trans_xml_to_dict(content) # 将请求返回的数据转为字典
# qrcode_name = out_trade_no + '.png' # 支付二维码图片保存路径
if data_dict.get('return_code') == 'SUCCESS': # 如果请求成功
datas["wx_params"] = params
# 创建订单信息
UpdateOrder.pay_course(request, datas)
img = qrcode.make(data_dict.get('code_url')) # 创建支付二维码
# img_path = 'edu_saas_app/static/' + qrcode_name
# img.save(img_path) #
buf = io.BytesIO()
img.save(buf, format='PNG')
image_stream = buf.getvalue()
heximage = base64.b64encode(image_stream)
# return render(request, 'qrcode.html', 'qrcode_img': qrcode_name) # 为支付页面模板传入二维码图像
return 'code': 0, 'qrcode_img': str(heximage, "utf-8")
else:
raise Exception("请求失败!", data_dict.get('return_code'))
except Exception as e:
r.error(e)
return r.message
注: 在发送post请求前,需要将xml进行utf-8转码,否则得到的二维码扫出来后的中文是乱码。
2.签名加密方式,将Key 和参数进行混合加密
def get_sign(data_dict, key): # 签名函数,参数为签名的数据和密钥
params_list = sorted(data_dict.items(), key=lambda e: e[0], reverse=False) # 参数字典倒排序为列表
params_str = "&".join(u"=".format(k, v) for k, v in params_list) + '&key=' + key
# 组织参数字符串并在末尾添加商户交易密钥
md5 = hashlib.md5() # 使用MD5加密模式
md5.update(params_str.encode()) # 将参数字符串传入
sign = md5.hexdigest().upper() # 完成加密并转为大写
return sign
3.out_trade_no为微信平台那边需要的订单号
def order_num(package_id=12345, user_id=56789):
# 商品id后2位+下单时间的年月日12+用户2后四位+随机数4位
local_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))[2:]
result = str(package_id)[-2:] + local_time + str(user_id)[-2:] + str(random.randint(1000, 9999))
return result
4.nonce_str 为我们商户提供的32位以内的随机字符串
import datetime
def generate_number(length):
start = int("1" + "0" * (length - 1))
end = int("9" * length)
return random.randrange(start, end)
def get_order_number():
now = datetime.datetime.now()
str_time = now.strftime('%Y%m%d%H%M%S')
# 在加一个4位的随机数
rand_str = generate_number(8)
return str_time + str(rand_str)
发起支付时需要注意点:
- 必须的参数一定要传,不传会报错。
params =
"appid": _APP_ID, # APPID
"mch_id": _MCH_ID, # 商户号
"nonce_str": nonce_str,
"out_trade_no": out_trade_no, # 订单编号
"total_fee": total_fee, # 订单总金额
"spbill_create_ip": _CREATE_IP, # 发送请求服务器的IP地址
"notify_url": _NOTIFY_URL, # 支付结果通知地址
"body": subject, # 商品描述
"trade_type": 'NATIVE' # 扫码支付
-
total_fee的单位是分,不能为0,否则会报错。
-
发起请求的方式为POST, 传给微信的body必须是XML格式的,不能接收其他格式的body参数。
xml = trans_dict_to_xml(params) # 转换字典为XML
data = xml.encode("utf-8")
response = requests.request('post', _PAY_URL, data=data) # 以POST方式向微信公众平台服务器发起请求
三、前端回调提示用户支付成功
用户付钱以后,需要知道的第一件事就是自己有没有支付成功,主要方式有2种:
- 前端使用ajax的方式进行定时回调。
- 前端调用后端的接口,后端在一定的时间内给予返回。
在这里介绍第一种回调方式,前端持续间隔时间调用该接口,这个结构并不会影响到用户支付成功,而是给前端一个响应,让用户知道是否成功。
建议不采用第二种方式进行回调,因为后端会一直循环查询,比较占用资源,也容易出问题,比如出现死循环问题。
需要的参数格式还是XML格式:
<xml>
<appid>wx2421b1c4370ec43b</appid>
<mch_id>10000100</mch_id>
<nonce_str>ec2316275641faa3aacf3cc599e8730f</nonce_str>
<transaction_id>1008450740201411110005820873</transaction_id>
<sign>FDD167FAA73459FD921B144BAF4F4CA2</sign>
</xml>
完整代码
# 查询订单信息
def check_wxpay(request):
q_url = "https://api.mch.weixin.qq.com/pay/orderquery"
meta = MetaSettings.objects.using(BaseView.get_partner(None, request)).values_list("wechat_appid",
"wechat_merchant_id",
"wechat_secret_key").first()
_APP_ID = meta[0]
_MCH_ID = meta[1]
_API_KEY = meta[2]
# 从session中拿出订单信息
order_number = request.GET.get("order_id")
data_dict = request.session[order_number]
data_dict = dict(data_dict)
data_dict.pop("body")
data_dict.pop("notify_url")
data_dict.pop("total_fee")
data_dict.pop("trade_type")
data_dict.pop("spbill_create_ip")
data_dict.pop("sign")
sign = get_sign(data_dict, _API_KEY) # 获取签名
data_dict['sign'] = sign # 添加签名到参数字典
xml = trans_dict_to_xml(data_dict) # 转换字典为XML
response = requests.request('post', q_url, data=xml.encode("utf-8")) # 以POST方式向微信公众平台服务器发起请求
content = response.content
data_dict = trans_xml_to_dict(content)
trade_state = data_dict["trade_state"]
# SUCCESS支付成功
# REFUND 转入退款
# NOTPAY
# CLOSED 已关闭
# REVOKED—已撤销(付款码支付)
# USERPAYING--用户支付中(付款码支付)
# PAYERROR--支付失败(其他原因,如银行返回失败)
if "SUCCESS" == trade_state:
del request.session[order_number]
return JsonResponse("code": 0, "msg": "支付成功!")
elif "PAYERROR" == trade_state:
return JsonResponse("code": -1, "msg": "支付失败!")
elif "NOTPAY" == trade_state:
return JsonResponse("code": -2, "msg": "未支付!")
四、 云服务平台微信通知
前端回调不能保证用户是否支付成功,因此不能使用前端回调来确定用户这单是否支付成功,微信给出的解决方案是: 支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
1. 自己定义通知接口: /notify ,另外微信会给商户传如下的参数:
支付成功的话,微信给商户返回的return_code为SUCCESS, 另外会带有其他的一些信息参数,如下:
2. 用户就可以根据return_code来做对应的支付成功或者失败处理逻辑!
3. 处理完成后,商户需要给微信返回一个xml,来确认商户收到了消息,避免微信重复发送消息。
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<attach><![CDATA[支付测试]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign关于微信扫码支付的流程