这一篇教程,我们继续了解关于微信公众平台扫码支付接口的调用以及回调验签。
本教程是在沙箱环境下完成,需要准备可用的商户号和沙箱交易密钥。
其中,商户号必须是真实的商户号,沙箱交易密钥通过真实商户交易密钥获取。
提示:获取沙箱交易密钥请参考《微信公众平台开发基本配置Token验证与接口请求验签》
作者使用的商户号和沙箱密钥不便公开,大家可以尝试在网上搜索一下,万一能找到一个呢?(注意潜台词)
接下来我们先看一下扫码支付的过程:
- 组织订单数据发起请求
- 返回二维码链接
- 用户扫描二维码支付(沙箱环境不需要扫码,会在5秒钟后自动返回回调数据)
- 返回回调数据
- 回调数据验签
参考上方的过程,我们完成项目核心代码的编写。
在编写核心代码之前,我们先做一些准备工作。
1、创建页面模板
需要两个页面,提交购买商品的页面和显示支付二维码的页面。
示例代码:(buy.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>结算</title>
</head>
<body>
<form action="../to_pay/" method="get">
<p>商品:Python 入门视频</p>
<input type="hidden" name="goods_name" value="Python 入门视频">
<p>价格:¥1.01</p>
<input type="hidden" name="price" value="1.01">
数量:<input type="number" name="quantity" value="1">
<p><input type="submit"></p>
<p>注意:数量与价格计算后的总金额只能是1.01或者1.02以及验收实例中的金额,否则交易失败!</p>
</form>
</body>
</html>
示例代码:(qrcode.html)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>扫码支付</title> </head> <body> {% load static %} <img src="{% static qrcode_img %}"><!--显示支付二维码--> </body> </html>
2、添加URL配置
示例代码:(urls.py)
from django.urls import path from website import views as site_view from django.views.static import serve from myproject import settings urlpatterns = [ path('weixin/', site_view.check_token), # 用于Token验证 path('buy/', site_view.buy), # 打开购买页面 path('to_pay/', site_view.wxpay), # 跳转二维码扫描页面 path('check_wxpay/', site_view.check_wxpay), # 支付结果验签 path('static/<path:path>', serve, {'document_root': settings.STATIC_ROOT}), # 静态文件访问配置 ]
3、添加打开购买页面的视图函数
示例代码:(views.py)
from django.shortcuts import render def buy(request): return render(request, 'buy.html')
4、创建“static”文件夹
项目根目录下创建“static”文件夹用于保存支付二维码图片。
示例代码:(settings.py)
STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static')
5、安装需要使用的库。
pip install python-wechat
# 微信库
pip install beautifulsoup4
# 解析XML的库
pip install qrcode
# 生成二维码图片的库
接下来,我们在视图(views.py)中进行核心代码的编写。
因为不管是发送请求数据还是接收回调数据,数据都是以XML格式进行发送。
而我们对数据进行读取的时候,通过字典读取比较方便,所以先完成两个格式转换的函数。
1、XML转Dict的函数
示例代码:
from bs4 import BeautifulSoup
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
2、Dict转XML的函数
示例代码:
def trans_dict_to_xml(data_dict): # 定义字典转XML的函数 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>{}</xml>'.format(''.join(data_xml)) # 返回XML
3、编写签名函数
不管是请求还是回调都需要签名以及对签名进行验证,我们可以把签名函数独立出来。
验签规则可以参考:《微信公众平台开发基本配置Token验证与接口请求验签》
示例代码:
import hashlib
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
4、编写发起支付请求并生成支付二维码的函数
向微信公众平台服务器发起请求时会返回数据,包含请求状态等信息,如果请求成功会返回二维码链接“code_url”,通过这个链接生成支付二维码图片,供用户扫描支付。
示例代码:
from django.http.response import HttpResponse from myproject import settings import random import requests import qrcode
def wxpay(request): url = 'https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder' # 微信扫码支付接口 key = 'c4966d17d71********b62450d3069097' # 沙箱交易密钥 total_fee = int(float(request.GET['price']) * int(request.GET['quantity']) * 100) # 订单总金额(注意是分不是元) body = request.GET.get('goods_name').encode() # 商品描述 out_trade_no = 'order_%s' % random.randrange(100000, 999999) # 订单编号 params = { 'appid': 'wx77d670a870709aab', # 沙箱环境的APPID 'mch_id': '14******32', # 商户号 'notify_url': 'http://d0a10b0e.ngrok.io/check_wxpay/', # 回调地址 'product_id': 'goods_%s' % random.randrange(100000, 999999), # 商品编号 'trade_type': 'NATIVE', # 支付类型(扫码支付) 'spbill_create_ip': '1.203.76.199', # 发送请求服务器的IP地址 'total_fee': total_fee, # 订单总金额 'out_trade_no': out_trade_no, # 订单编号 'body': body, # 商品描述 'nonce_str': 'ibuaiVcKdpRxkhJA' # 字符串 } sign = get_sign(params, key) # 获取签名 params.setdefault('sign', sign) # 添加签名到参数字典 xml = trans_dict_to_xml(params) # 转换字典为XML response = requests.request('post', url, data=xml) # 以POST方式向微信公众平台服务器发起请求 data_dict = trans_xml_to_dict(response.content) # 将请求返回的数据转为字典 qrcode_name = out_trade_no + '.png' # 支付二维码图片保存路径 if data_dict.get('return_code') == 'SUCCESS': # 如果请求成功 img = qrcode.make(data_dict.get('code_url')) # 创建支付二维码片 img.save(settings.STATIC_ROOT + '/' + qrcode_name) # 保存支付二维码 return render(request, 'qrcode.html', {'qrcode_img': qrcode_name}) # 为支付页面模板传入二维码图像 return HttpResponse('交易请求失败!')
5、编写回调数据验签函数
示例代码:
from django.views.decorators.csrf import csrf_exempt # 解除csrf验证
@csrf_exempt # 去除csrf验证 def check_wxpay(request): data_dict = trans_xml_to_dict(request.body) # 回调数据转字典 sign = data_dict.pop('sign') # 取出签名 key = 'c4966d17**************50d3069097' # 商户交易密钥 back_sign = get_sign(data_dict, key) # 计算签名 if sign == back_sign: # 验证签名是否与回调签名相同 ''' 检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。 ''' print('支付成功!') return HttpResponse('SUCCESS') else: ''' 此处编写支付失败后的业务逻辑 ''' return HttpResponse('')
提示:微信和支付宝不一样,回调会在30分钟内反复发送,无需返回数据;所以,每次回调都要对业务状态进行判断,如果已经处理过就不再处理。
启动开发服务器,进行测试。
如果支付二维码图片没有正常显示,将“settings.py”文件中的“DEBUG”项改为“False”。
项目源代码下载:【点此下载】