Python+Django实现微信扫码支付流程

Python+Django实现微信扫码支付流程

【微信公众号支付官网】https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

【必备资料】:微信公众号、商户平台

登录https://mp.weixin.qq.com/cgi-bin/loginpage公众号,左边找到“开发”->“基本配置”,找到:

  • 开发者ID(AppID)

  • 开发者密码(AppSecret)

  • 设置IP白名单

登录https://pay.weixin.qq.com/core/home/login商户平台,找到:

  • 商户号mchID

一、实现生成二维码扫码支付

###1、创建一个Django项目,名为wechatPay

2、创建一个子应用,名为pay

3、在项目下创建static文件夹存放静态文件

4、创建templates存放html文件

点击templates文件夹右键,“Mark Directory as”–>“Template Folder”

4.1 创建报名页面buy.html

<!DOCTYPE html>
<html>
<head>
    <title>报名</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.1.0/css/bootstrap.min.css">
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/popper.js/1.12.5/umd/popper.min.js"></script>
    <script src="https://cdn.bootcss.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
</head>
<body>

<div class="jumbotron text-center">
    <h2>报名</h2>
    <p>已报名人数:&nbsp;&nbsp;8465</p>
</div>

<div class="container">
    <h2>填写信息</h2>
    <form action="{% url "to_pay" %}" method="post">
    {% csrf_token %}
        <div class="form-group">
            <label for="name">姓名</label>
            <input type="text" class="form-control" id="name" placeholder="姓名" name="name">
        </div>
        <div class="form-group">
            <label for="phone">手机号</label>
            <input type="phone" class="form-control" id="phone" placeholder="手机号" name="phone">
        </div>

        <button type="submit" class="btn btn-primary">提交</button>
    </form>
</div>

</body>
</html>

4.2 创建二维码扫码页面qrcode.html

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <title>扫码支付</title>
</head>
<style>
    .box {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%);
        text-align: center;
    }

    .conten {
        width: 100%;
    }

    body {
        background: #ccc;
    }
</style>

<body>
{% load static %}
<div class="box">
    <h2 class="content"><span>扫码支付</span></h2>
    <!--<h2>扫码支付</h2>-->
    <img src="{% static qrcode_img %}"><!--显示支付二维码-->
    
</div>
</body>

</html>

5、项目目录结构

在这里插入图片描述

6、settings.py配置

6.1 注册子应用

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    #注册子应用
    'pay',

]

6.2 添加微信支付配置

#微信支付配置
# ========支付相关配置信息===========
_APP_ID = "你的appid"  # 公众账号appid
_MCH_ID = "你的商户号"  # 商户号
_API_KEY = "你的商户号密钥"  # 微信商户平台(pay.weixin.qq.com) -->账户设置 -->API安全 -->密钥设置,设置完成后把密钥复制到这里

_UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; #url是微信下单api
_NOTIFY_URL = "支付结果回调" # 微信支付结果回调接口,需要改为你的服务器上处理结果回调的方法路径
_CREATE_IP = '你的ip'  # 发起支付请求的ip


APPEND_SLASH=False

6.3 静态文件配置

STATIC_URL = '/static/'
STATIC_ROOT = '/static/'
# 添加静态资源路由地址
STATICFILES_DIRS=[
    os.path.join(BASE_DIR,'static'),

]

7、项目下的urls.py配置

from django.conf import settings
from django.contrib import admin
from django.urls import path
from django.views.static import serve
from  pay import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('buy/', views.buy),  # 打开购买页面
    path('to_pay/', views.wxpay, name='to_pay'),  # 跳转二维码扫描页面
    path('check_wxpay/', views.check_wxpay),  # 支付结果验签
    path('static/<str:path>', serve, {'document_root': settings.STATIC_ROOT}),  # 静态文件访问配置
]

8、子应用中的views.py配置

from __future__ import unicode_literals

import os
from random import Random
import string
import time


from django.shortcuts import render
from django.http.response import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt  # 解除csrf验证

from bs4 import BeautifulSoup
from wechatPay import settings
import random
import requests
import hashlib
import qrcode

#报名首页
def buy(request):
    return render(request, 'buy.html')

# 定义字典转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>{}</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

# 发起微信支付
def wxpay(request):
    nonce_str = random_str() # 拼接出随机的字符串即可,我这里是用  时间+随机数字+5个随机字母

    total_fee = 1  #付款金额,单位是分,必须是整数
    body = 'baoming'  # 商品描述
    out_trade_no = order_num(user_id=request.POST.get('phone','12345'))  # 订单编号

    params = {
        'appid': settings._APP_ID,  # APPID
        'mch_id': settings._MCH_ID,  # 商户号
        'nonce_str':nonce_str,  # 回调地址
        'out_trade_no': out_trade_no,# 订单编号
        'total_fee':total_fee,# 订单总金额
        'spbill_create_ip':settings._CREATE_IP,# 发送请求服务器的IP地址
        'notify_url':settings._NOTIFY_URL, # 支付回调地址
        'body':body,  # 商品描述
        'trade_type':'NATIVE' #扫码支付
    }

    sign = get_sign(params,settings._API_KEY)  # 获取签名
    params['sign'] = sign  # 添加签名到参数字典
    # print(params)
    xml = trans_dict_to_xml(params)  # 转换字典为XML
    response = requests.request('post', settings._UFDODER_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('static' + '/' + qrcode_name)  #

        return render(request, 'qrcode.html', {'qrcode_img': qrcode_name})  # 为支付页面模板传入二维码图像

    return HttpResponse('交易请求失败!')

# 支付成功后回调
@csrf_exempt  # 去除csrf验证
def check_wxpay(request):
    data_dict = trans_xml_to_dict(request.body)  # 回调数据转字典
    sign = data_dict.pop('sign')  # 取出签名
    key = settings._API_KEY  # 商户交易密钥
    back_sign = get_sign(data_dict, key)  # 计算签名
    if sign == back_sign:  # 验证签名是否与回调签名相同
        '''
        检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
        '''
        print('支付成功!')
        return HttpResponse('SUCCESS')
    else:
        '''
            此处编写支付失败后的业务逻辑
        '''

        return HttpResponse('failed')


#获取签名
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

#生成订单号
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

#生成随机字符串
def random_str(randomlength=8):
    str = ''
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
    length = len(chars) - 1
    random = Random()
    for i in range(randomlength):
        str+=chars[random.randint(0, length)]
    return str

9、效果图

启动服务:

python manage.py runserver

在浏览器中输入127.0.0.1:8000/buy/

在这里插入图片描述

填写信息点击提交后会跳转到扫码支付页面。

10、注意

说明:现在微信不支持长按识别二维码支付,这个二维码只能通过微信右上角的扫一扫功能进行扫码支付。

附录

第三方依赖库:requirements.txt

beautifulsoup4==4.6.3
certifi==2018.8.24
chardet==3.0.4
colorama==0.3.9
Django==2.1.1
django-qrcode==0.3
django-wechat==0.1a1
html5lib==1.0.1
idna==2.7
image==1.5.25
lxml==4.2.5
optionaldict==0.1.1
Pillow==5.2.0
python-dateutil==2.7.3
pytz==2018.5
qrcode==6.0
requests==2.6.0
six==1.10.0
urllib3==1.23
webencodings==0.5.1
wechatpy==1.7.5
xmltodict==0.11.0

猜你喜欢

转载自blog.csdn.net/lm_is_dc/article/details/82864480