基于Django项目的Python版微信公众号支付-Native支付方式

本文详细讲解Python语言进行公众号开发时,参考开发者文档进行Native支付(模式二),并给出具体的代码:

一.开发流程

业务流程说明:

(1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。

(2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

(3)微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"

(4)商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。

(5)商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)

(6)微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。

(7)商户后台系统得到交易会话标识prepay_id(2小时内有效)。

(8)商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"

(9)微信支付系统根据交易会话标识,发起用户端授权支付流程。

(10)用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。

(11)微信支付系统验证后扣款,完成支付交易。

(12)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

(13)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

(14)未收到支付通知的情况,商户后台系统调用【查询订单API】。

(15)商户确认订单已支付后给用户发货。

二.具体代码

1.需准备的参数

import os
import qrcode
import json
import hashlib
from random import Random
import requests
from django.http import HttpResponse


notify_url = "....../wx_result_n/"  # 回调函数,完整路由,部署在服务器的话要带上域名,对应的视图为下面3中的回调函数
trade_type = 'NATIVE'  # 交易方式(扫码模式二)
APP_ID = "wx......"  # 公众账号的appid
MCH_ID = "......"  # 商户号
API_KEY = "......"  # 微信商户平台(pay.weixin.qq.com) -->账户设置 -->API安全 -->密钥设置,设置完成后把密钥复制到这里
UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"  # 该url是微信下单api
CREATE_IP = '......'  # 服务器IP
path = "/opt/data/img/"  # 服务器中存放支付二维码的路径

2.调用支付接口

def wx_pay_n(request):
    # data = json.loads(request.body)
    # print(request.body)
    # order_id = data.get("order_id", 0)

    total_price = 0.01   # 订单总价
    order_name = '商品费用'   # 订单名字
    order_detail = '商品费用'   # 订单描述
    order_id = 20200411234567    # 自定义的订单号
    data_dict = wxpay(order_id, order_name, order_detail, total_price)   # 调用统一支付接口
    # 如果请求成功
    if data_dict.get('return_code') == 'SUCCESS':
        # 业务处理
        # 二维码名字
        qrcode_name = str(order_id) + '.png'
        # 创建二维码
        img = qrcode.make(data_dict.get('code_url'))
        img_url = os.path.join(path, qrcode_name)
        img.save(img_url)
        s = {
            "code": 1000,
            "msg": "获取成功",
            "data": img_url     # 访问路径
        }
        s = json.dumps(s, ensure_ascii=False)
        return HttpResponse(s)
    s = {
                "code": 1001,
                "msg": "获取失败",
                "data": ""
            }
    s = json.dumps(s, ensure_ascii=False)
    return HttpResponse(s)

3.支付后回调接口

def wx_result_n(request):
    data_dict = trans_xml_to_dict(request.body)  # 回调数据转字典
    print('支付回调结果', data_dict)
    sign = data_dict.pop('sign')  # 取出签名
    back_sign = get_sign(data_dict, API_KEY)  # 计算签名
    # 验证签名是否与回调签名相同
    if sign == back_sign and data_dict['return_code'] == 'SUCCESS':
        order_no = data_dict['out_trade_no']
        # 处理支付成功逻辑,根据订单号修改后台数据库状态
        # 返回接收结果给微信,否则微信会每隔8分钟发送post请求
        return HttpResponse(trans_dict_to_xml({'return_code': 'SUCCESS', 'return_msg': 'OK'}))
    return HttpResponse(trans_dict_to_xml({'return_code': 'FAIL', 'return_msg': 'SIGNERROR'}))

4.工具函数

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


# 请求统一支付接口
def wxpay(order_id, order_name, order_price_detail, order_total_price):
    nonce_str = random_str()  # 拼接出随机的字符串即可,我这里是用  时间+随机数字+5个随机字母
    total_fee = int(float(order_total_price) * 100)    # 付款金额,单位是分,必须是整数
    print(total_fee)
    params = {
        'appid': APP_ID,  # APPID
        'mch_id': MCH_ID,  # 商户号
        'nonce_str': nonce_str,  # 随机字符串
        'out_trade_no': order_id,  # 订单编号,可自定义
        'total_fee': total_fee,  # 订单总金额
        'spbill_create_ip': CREATE_IP,  # 自己服务器的IP地址
        'notify_url': notify_url,  # 回调地址,微信支付成功后会回调这个url,告知商户支付结果
        'body': order_name,  # 商品描述
        'detail': order_price_detail,  # 商品描述
        'trade_type': trade_type,  # 扫码支付类型
    }

    sign = get_sign(params, API_KEY)  # 获取签名
    params['sign'] = sign  # 添加签名到参数字典
    xml = trans_dict_to_xml(params)  # 转换字典为XML
    response = requests.request('post', UFDODER_URL, data=xml.encode())  # 以POST方式向微信公众平台服务器发起请求
    data_dict = trans_xml_to_dict(response.content)  # 将请求返回的数据转为字典
    print(data_dict)
    return data_dict




def get_sign(data_dict, key):
    """
    签名函数
    :param data_dict: 需要签名的参数,格式为字典
    :param key: 密钥 ,即上面的API_KEY
    :return: 字符串
    """
    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('utf-8'))  # 将参数字符串传入
    sign = md5.hexdigest().upper()  # 完成加密并转为大写
    print(sign)
    return sign


def trans_dict_to_xml(data_dict):
    """
    定义字典转XML的函数
    :param data_dict:
    :return:
    """
    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 trans_xml_to_dict(data_xml):
    """
    定义XML转字典的函数
    :param data_xml:
    :return:
    """
    data_dict = {}
    try:
        import xml.etree.cElementTree as ET
    except ImportError:
        import xml.etree.ElementTree as ET
    root = ET.fromstring(data_xml)
    for child in root:
        data_dict[child.tag] = child.text
    return data_dict

总结

按照上面四步走,一定可以将Native方式的支付做成功,具体的业务逻辑需要自己处理一下即可,

希望能够提高大家的开发效率,如有不足请多多指教。

 

作者:E-QUAL
出处:https://www.cnblogs.com/liujiajia_me/
本文版权归作者和博客园共有,不得转载,未经作者同意参考时必须保留此段声明,且在文章页面明显位置给出原文连接。
                                            本文内容参考如下网络文献得来,用于个人学习,如有侵权,请您告知删除修改。
                                            参考链接:https://www.cnblogs.com/linhaifeng/
                                                             https://www.cnblogs.com/yuanchenqi/
                                                             https://www.cnblogs.com/Eva-J/
                                                             https://www.cnblogs.com/jin-xin/
                                                             https://www.cnblogs.com/liwenzhou/
                                                             https://www.cnblogs.com/wupeiqi/
posted @ 2020-07-19 21:17  E-QUAL  阅读(614)  评论(0编辑  收藏  举报