在生活具有广泛性、高效性、使用方便性的支付方式是扫码支付,扫码的优点在于推广成本低,上至钓鱼台国宾馆,下至发廊地摊都能用,打印出来就完事了,而相比其他支付方式,现金的找零及假钞问题,信用卡的办理门槛、pos机的沉没成本,就算微信可集成的h5支付和小程序支付,奈何很多老年人根本不会用小程序和手机浏览器,更别说再进行支付操作了,所以基于二维码的扫码支付的确是非常符合国情的。
本次我们使用前后端分离项目Vue.js+Tronado来集成微信的扫码支付功能,体验一下21世纪泛用性最高的支付方式,首先注册微信公众平台:https://mp.weixin.qq.com
同时确保获取微信支付接口的权限:
随后注册微信支付商户平台:https://pay.weixin.qq.com/
获取微信支付的商户号(在账户信息页面):
获取微信支付接口的秘钥(账户中心->api安全):
同时在产品中心->开发配置页面,将支付域名配置好:
这里不像微信小程序,小程序只能允许https协议接口,而扫码支付域名既支持https也支持http,非常方便,同时注意域名必须是一个备案域名。
至此,微信支付的前置操作就搞定了,下面我们来编写后台接口wx_pay.py,首先导入依赖的库和一些工具方法:
import requests
from django.http import HttpResponse, HttpResponseRedirect
import random
import time
import hashlib
import qrcode
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
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
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
qrcode模块用来生成二维码,bs4模块用来将微信接口返回的xml解析成json,在21世纪的第二十个年头,微信接口居然还在使用原始的xml,这种反人类行为实在不能理解。
接下来我们来编写支付逻辑,参考微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5&index=3
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。
一望而知,我们需要调用微信的统一下单接口,文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
编写逻辑:
import requests
import random
import time
import hashlib
import qrcode
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
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
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
class Zhifubao:
def pay(self):
print ('支付宝')
class Weixing():
def pay(self):
print ('微信')
def wx_pay(self,total_fee):
url = 'https://api.mch.weixin.qq.com/pay/unifiedorder' # 微信扫码支付接口
key = '945bec9df3614cffb74e39aba8fbf7d7' # 商户api秘钥
total_fee = total_fee # 支付金额,单位分
body = '123123' # 商品描述
out_trade_no = 'order_%s' % random.randrange(100000, 999999) # 订单编号
params = {
'appid': 'wx092344a76b9979ff', # APPID
'mch_id': '1602932608', # 商户号
'notify_url': 'http://wxpay.v3u.cn/wx_back/', # 支付域名回调地址
'product_id': 'goods_%s' % random.randrange(100000, 999999), # 商品编号
'trade_type': 'NATIVE', # 支付类型(扫码支付)
'spbill_create_ip': '114.254.176.137', # 发送请求服务器的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) # 将请求返回的数据转为字典
print(data_dict)
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 qrcode_name
class Factory: #工厂
def way(self,name):
if name == 'zhifubao':
return Zhifubao()
if name == 'weixin':
return Weixing()
# fa = Factory()
# d = fa.way('weixin')
# print(d.wx_pay(1))
随后配置路由:
启动Tornado服务:
python main.py
至此,后台逻辑基本搞定,下面就是如何在前端进行调用,同时让用户进行扫描操作,编写weixin_pay.vue组件:
<template>
<div>
<center><h1>扫码支付</h1></center>
<van-field v-model="money" label="金额" />
<van-button color='orange' @click='submit'>生成二维码</van-button>
<van-image :src="src" v-if="src"/>
</div>
</template>
<script>
export default {
data() {
return {
money:"1",
src:"",
};
},
methods: {
submit:function(){
this.axios.get('https://localhost/weixin/',{params:{'money':this.money}}).then((result) =>{
console.log(result.data);
this.src = "https://localhost/static/"+result.data.image
});
},
},
};
</script>
随后使用微信扫一扫功能进行扫码支付,需要注意的是,该二维码有效期只有五分钟,所以最好加上刷新功能。
支付成功之后,我们还需要对交易进行确认,所以根据微信官方文档,调用统一查询接口:
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2,根据接口文档编写逻辑:
def wx_check(self,out_trade_no):
# 统一订单查询接口
url = "https://api.mch.weixin.qq.com/pay/orderquery"
out_trade_no = out_trade_no # 支付后的商户订单号
key = '945bec9df3614cffb74e39aba8fbf7d7' # 商户api密钥
params = {
'appid': 'wx092344a76b9979ff', # APPID
'mch_id': '1602932608', # 商户号
'out_trade_no': out_trade_no, # 订单编号
'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) # 将请求返回的数据转为字典
print(data_dict)
return ('ok')
这里需要注意的是,查询的订单编号可以使商户自己的订单编号,也可以是微信订单号,二者必取其一:
结语:至此,整个微信扫码支付流程全部跑通