之前的一篇文章:mpvue1。0python3。7Django2。0。4实现微信小程序的支付功能,主要介绍了微信小程序内部支付的流程,然而实际上微信小程序有一定的局限性,也就是用户范围仅限于小程序内部生态圈,在生活中真正具有广泛性、高效性、使用方便性的支付方式还得是扫码支付,扫码的优点在于推广成本低,上至钓鱼台国宾馆,下至发廊地摊都能用,打印出来就完事了,而相比其他支付方式,现金的找零及假钞问题,信用卡的办理门槛、pos机的沉没成本,就算微信可集成的h5支付和小程序支付,奈何很多老年人根本不会用小程序和手机浏览器,更别说再进行支付操作了,所以基于二维码的扫码支付的确是非常符合国情的。 本次我们使用前后端分离项目Vue。jsDjango来集成微信的扫码支付功能,体验一下21世纪泛用性最高的支付方式,首先注册微信公众平台:https:mp。weixin。qq。com 获得开发者id和秘钥(appidappsecret) 同时确保获取微信支付接口的权限: 随后注册微信支付商户平台:https:pay。weixin。qq。com 获取微信支付的商户号(在账户信息页面): 获取微信支付接口的秘钥(账户中心api安全): 同时在产品中心开发配置页面,将支付域名配置好: 这里不像微信小程序,小程序只能允许https协议接口,而扫码支付域名既支持https也支持http,非常方便,同时注意域名必须是一个备案域名。 至此,微信支付的前置操作就搞定了,下面我们来编写后台接口wxpay。py,首先导入依赖的库和一些工具方法:importrequestsfromdjango。httpimportHttpResponse,HttpResponseRedirectimportrandomimporttimeimporthashlibimportqrcodefrombs4importBeautifulSoupdeftransxmltodict(dataxml):soupBeautifulSoup(dataxml,featuresxml)xmlsoup。find(xml)解析XMLifnotxml:return{}datadictdict(〔(item。name,item。text)foriteminxml。findall()〕)returndatadictdeftransdicttoxml(datadict):定义字典转XML的函数dataxml〔〕forkinsorted(datadict。keys()):遍历字典排序后的keyvdatadict。get(k)取出字典中key对应的valueifkdetailandnotv。startswith(!〔CDATA〔):添加XML标记v!〔CDATA〔{}〕〕。format(v)dataxml。append({key}{value}{key}。format(keyk,valuev))returnxml{}xml。format(。join(dataxml))返回XMLdefgetsign(datadict,key):签名函数,参数为签名的数据和密钥paramslistsorted(datadict。items(),keylambdae:e〔0〕,reverseFalse)参数字典倒排序为列表paramsstr。join(u{}{}。format(k,v)fork,vinparamslist)keykey组织参数字符串并在末尾添加商户交易密钥md5hashlib。md5()使用MD5加密模式md5。update(paramsstr。encode())将参数字符串传入signmd5。hexdigest()。upper()完成加密并转为大写returnsign qrcode模块用来生成二维码,bs4模块用来将微信接口返回的xml解析成json,在21世纪的第二十个年头,微信接口居然还在使用原始的xml,这种反人类行为实在不能理解。 接下来我们来编写支付逻辑,参考微信官方文档:https:pay。weixin。qq。comwikidocapinative。php?chapter65index3 业务流程说明: (1)商户后台系统根据用户选购的商品生成订单。 (2)用户确认支付后调用微信支付【统一下单API】生成预支付交易; (3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接codeurl。 (4)商户后台系统根据返回的codeurl生成二维码。 (5)用户打开微信扫一扫扫描二维码,微信客户端将扫码内容发送到微信支付系统。 (6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。 (7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。 (8)微信支付系统根据用户授权完成支付交易。 (9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。 (10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。 (11)未收到支付通知的情况,商户后台系统调用【查询订单API】。 (12)商户确认订单已支付后给用户发货。 一望而知,我们需要调用微信的统一下单接口,文档:https:pay。weixin。qq。comwikidocapinative。php?chapter91 编写逻辑:defwxpay(request):urlhttps:api。mch。weixin。qq。compayunifiedorder微信扫码支付接口key945beca8fbf7d7商户api秘钥totalfee1支付金额,单位分body123123商品描述outtradenoordersrandom。randrange(100000,999999)订单编号params{appid:wx09f,APPIDmchid:1608,商户号notifyurl:http:wxpay。v3u。cnwxback,支付域名回调地址productid:goodssrandom。randrange(100000,999999),商品编号tradetype:NATIVE,支付类型(扫码支付)spbillcreateip:114。254。176。137,发送请求服务器的IP地址totalfee:totalfee,订单总金额outtradeno:outtradeno,订单编号body:body,商品描述noncestr:ibuaiVcKdpRxkhJA字符串}signgetsign(params,key)获取签名params。setdefault(sign,sign)添加签名到参数字典xmltransdicttoxml(params)转换字典为XMLresponserequests。request(post,url,dataxml)以POST方式向微信公众平台服务器发起请求datadicttransxmltodict(response。content)将请求返回的数据转为字典print(datadict)qrcodenameouttradeno。png支付二维码图片保存路径ifdatadict。get(returncode)SUCCESS:如果请求成功imgqrcode。make(datadict。get(codeurl))创建支付二维码片img。save(。qrcodename)保存支付二维码returnHttpResponse(qrcodename) 随后配置路由:frommyapp。wxpayimportwxpayfromdjango。contrib。staticfiles。urlsimportstaticfilesurlpatterns。。。therestofyourURLconfgoeshere。。。urlpatterns〔定义超链接路由repath(staticupload(?Ppath。)39;,serve,{documentroot:staticupload}),path(wxpay,wxpay),〕 启动django服务:pythonmanage。pyrunserver 访问http:localhost:8000wxpay 没有问题,查看后台日志:{returncode:SUCCESS,returnmsg:OK,appid:wx092344a76b9979ff,mchid:1602932608,noncestr:bnJwGlXZ3eDSNgjs,sign:2D81402DABEDF75E9A58F200FE7B6775,resultcode:SUCCESS,prepayid:wx1816114416896958d6f84177bd71da0000,tradetype:NATIVE,codeurl:weixin:wxpaybizpayurl?prJgBYgTS00} 可以看到已经下单成功,不过订单状态处于预支付状态,同时检查二维码图片是否生成: 至此,后台逻辑基本搞定,下面就是如何在前端进行调用,同时让用户进行扫描操作,编写wxpay。vue组件:templatecenterh1扫码支付h1centeraformitem生成二维码abuttonaformitemimg:imgq01。71396。comcbal4283ab8cfbac54df。jpgimgq01。71396。comcbal4283ab8cfbac54df。jpgaformitemtemplate 当用户点击按钮之后,旋即请求后端支付接口,将接口生成的二维码返回给前端,效果是这样的: 随后使用微信扫一扫功能进行扫码支付,需要注意的是,该二维码有效期只有五分钟,所以最好加上刷新功能。 支付成功之后,我们还需要对交易进行确认,所以根据微信官方文档,调用统一查询接口: https:pay。weixin。qq。comwikidocapinative。php?chapter92,根据接口文档编写逻辑:defwxcheck(request):统一订单查询接口urlhttps:api。mch。weixin。qq。compayorderqueryouttradenoorder537236支付后的商户订单号key945bd7商户api密钥params{appid:wx0ff,APPIDmchid:1608,商户号outtradeno:outtradeno,订单编号noncestr:ibuaiVcKdpRxkhJA随机字符串}signgetsign(params,key)获取签名params。setdefault(sign,sign)添加签名到参数字典xmltransdicttoxml(params)转换字典为XMLresponserequests。request(post,url,dataxml)以POST方式向微信公众平台服务器发起请求datadicttransxmltodict(response。content)将请求返回的数据转为字典print(datadict)returnHttpResponse(ok) 这里需要注意的是,查询的订单编号可以使商户自己的订单编号,也可以是微信订单号,二者必取其一: 访问接口http:localhost:8000wxcheck 返回结果:{returncode:SUCCESS,returnmsg:OK,appid:wx092344a76b9979ff,mchid:1602932608,noncestr:BVoaDmxxADkpSFEl,sign:23A86EB406B743E0C2C61C7E78DC9373,resultcode:SUCCESS,openid:oy9q36f9Dpeokj9FWyN3j0znpIqE,issubscribe:N,tradetype:NATIVE,banktype:OTHERS,totalfee:1,feetype:CNY,transactionid:4200000806202012174121934231,outtradeno:order537236,attach:,timeend:20201217231553,tradestate:SUCCESS,cashfee:1,tradestatedesc:支付成功,cashfeetype:CNY} 可以看到没有问题,但是由于涉及金钱业务,为了养成良好的测试习惯,最好登录商户后台再次确认: 结语:至此,整个微信扫码支付流程全部跑通,流程上比微信小程序支付逻辑要简单一些,同时由于不需要在线用户的openid,所以像微信小程序获取不到openid这样的大坑并不存在,后续会分享一些关于微信扫码订单退款的逻辑,搞笑的是,统一下单和查询接口没有并发限制,而申请退款居然有qps上的限制,所以退款流程应该会需要消息队列的介入。