Python3实现微信公众平台扫码支付二维码生成与回调验签

2024年04月15日 django Python51

这一篇教程,我们继续了解关于微信公众平台扫码支付接口的调用以及回调验签。

本教程是在沙箱环境下完成,需要准备可用的商户号和沙箱交易密钥。

其中,商户号必须是真实的商户号,沙箱交易密钥通过真实商户交易密钥获取。

提示:获取沙箱交易密钥请参考《微信公众平台开发基本配置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><strong>注意:数量与价格计算后的总金额只能是1.01或者1.02以及验收实例中的金额,否则交易失败!</strong></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{%%20static%20qrcode_img%20%}"><!--显示支付二维码-->
</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”。

项目源代码下载:【点此下载

本文链接:http://so.lmcjl.com/news/2158/

展开阅读全文