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>已报名人数: 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