python实现微信扫码支付完整流程

Posted Dream_it_possible!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python实现微信扫码支付完整流程相关的知识,希望对你有一定的参考价值。

文章目录

微信官方开发文档:
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1

一、支付前要做的准备

我们需要准备的东西:

  1. 微信开放平台申请应用的appid , 商户号mch_id, 秘钥API_KEY, 这些信息会作为收款的凭据。

  2. 安装依赖:
    python的版本是: 3.7, django的版本是: 2.0.13

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple lxml
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django-qrcode==0.3
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django-wechat==0.1a1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple html5lib==1.0.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple idna==2.7
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple image==1.5.25
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple optionaldict==0.1.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytz==2018.5
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple qrcode==6.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple six==1.10.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple urllib3==1.23
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple webencodings==0.5.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple wechatpy==1.7.5
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xmltodict==0.11.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple bs4
  1. 统一支付接口: https://api.mch.weixin.qq.com/pay/unifiedorder
    ,此接口必须采用post方式调用,必须需要传的参数解释如下:
字段名变量名类型示例值描述
公众账号IDappidString(32)wxd678efh567hg6787微信支付分配的公众账号ID(企业号corpid即为此appId)
商户号mch_idString(32)1230000109微信支付分配的商户号
随机字符串nonce_strString(32)5K8264ILTKCH16CQ2502SI8ZNMTM67VS随机字符串,长度要求在32位以内。推荐随机数生成算法
签名signString(32)C380BEC2BFD727A4B6845133519F3AD6通过签名算法计算得出的签名值,详见签名生成算法
商品描述bodyString(128)腾讯充值中心-QQ会员充值商品简单描述,该字段请按照规范传递,具体请见参数规定
商户订单号out_trade_noString(32)20150806125346商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-
标价金额total_feeInt88订单总金额,单位为分,详见支付金额
终端IPspbill_create_ipString(64)123.12.12.123支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
通知地址notify_urlString(256)http://www.weixin.qq.com/wxpay/pay.php异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
交易类型trade_typeString(16)JSAPIJSAPI -JSAPI支付NATIVE -Native支付APP -APP支付
注: 我们是扫码模式的支付,因此要设置trade_type='NATIVE ’
  1. 支付接口参数样例:
<xml>
   <appid>wx2421b1c4370ec43b</appid>
   <attach>支付测试</attach>
   <body>JSAPI支付测试</body>
   <mch_id>10000100</mch_id>
   <detail><![CDATA[ "goods_detail":[  "goods_id":"iphone6s_16G", "wxpay_goods_id":"1001", "goods_name":"iPhone6s 16G", "quantity":1, "price":528800, "goods_category":"123456", "body":"苹果手机" ,  "goods_id":"iphone6s_32G", "wxpay_goods_id":"1002", "goods_name":"iPhone6s 32G", "quantity":1, "price":608800, "goods_category":"123789", "body":"苹果手机"  ] ]]></detail>
   <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str><notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
   <openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
   <out_trade_no>1415659990</out_trade_no>
   <spbill_create_ip>14.23.150.211</spbill_create_ip>
   <total_fee>1</total_fee>
   <trade_type>NATIVE</trade_type>
   <sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>
  1. 在python中用字典进行处理需要的参数,在发起支付时,给参数转换为XML格式的,调用成功后,再将返回的XML格式的结果转换为字典处理即可,可用如下代码进行转换。
# 定义字典转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 version="1.0" encoding="UTF-8" ></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

二、发起微信支付

   知道我们需要哪些东西后,就可以通过如下的方式来实现返回二维码,生成的二维码通过base64加密,然后返回给前端。

1.生成支付二维码

   生成二维码的同时,我们应该先在系统内生成订单记录,为后续通过云服务器回调通知做准备。

from __future__ import unicode_literals
from random import Random
import io
from my_app.pay.produce_order import UpdateOrder
from django.shortcuts import render
from django.http.response import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt  # 解除csrf验证
from my_app.models import MetaSettings, StudentBugCourseRecord, LoggerRecord
from my_app.BaseView import BaseView
from bs4 import BeautifulSoup
import base64
import random
import requests
import hashlib
import qrcode
import time
from HttpResult import Result

def wxpay(request, datas):
    meta = MetaSettings.objects.using(BaseView.get_partner(None, request)).values_list("wechat_appid",
                                                                                       "wechat_merchant_id",
                                                                                       "wechat_secret_key").first()
    r = Result()
    try:
        if "未设置" in meta:
            raise Exception("请维护微信信息!")
        _APP_ID = meta[0]
        _MCH_ID = meta[1]
        _API_KEY = meta[2]
        _CREATE_IP = "192.168.124.8"
        _NOTIFY_URL = request.get_host() + "/app/wechat/notify"
        _PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"
        order_number = datas["order_number"]
        nonce_str = order_number  # 拼接出随机的字符串即可,我这里是用  时间+随机数字+5个随机字母
        total_fee = int((datas["total_pay"] * 100))  # 付款金额,单位是分,必须是整数
        subject = "购买课程"  # 商品描述
        out_trade_no = order_num()  # 订单编号
        params = 
            "appid": _APP_ID,  # APPID
            "mch_id": _MCH_ID,  # 商户号
            "nonce_str": nonce_str,
            "out_trade_no": out_trade_no,  # 订单编号
            "total_fee": total_fee,  # 订单总金额
            "spbill_create_ip": _CREATE_IP,  # 发送请求服务器的IP地址
            "notify_url": _NOTIFY_URL,  # 支付结果通知地址
            "body": subject,  # 商品描述
            "trade_type": 'NATIVE'  # 扫码支付
        

        sign = get_sign(params, _API_KEY)  # 获取签名
        params['sign'] = sign  # 添加签名到参数字典
        request.session[order_number] = params
        # print(params)
        xml = trans_dict_to_xml(params)  # 转换字典为XML
        data = xml.encode("utf-8")
        response = requests.request('post', _PAY_URL, data=data)  # 以POST方式向微信公众平台服务器发起请求
        content = response.content
        data_dict = trans_xml_to_dict(content)  # 将请求返回的数据转为字典
        # qrcode_name = out_trade_no + '.png'  # 支付二维码图片保存路径

        if data_dict.get('return_code') == 'SUCCESS':  # 如果请求成功
            datas["wx_params"] = params
            # 创建订单信息
            UpdateOrder.pay_course(request, datas)
            img = qrcode.make(data_dict.get('code_url'))  # 创建支付二维码
            # img_path = 'edu_saas_app/static/' + qrcode_name
            # img.save(img_path)  #
            buf = io.BytesIO()
            img.save(buf, format='PNG')
            image_stream = buf.getvalue()
            heximage = base64.b64encode(image_stream)
            # return render(request, 'qrcode.html', 'qrcode_img': qrcode_name)  # 为支付页面模板传入二维码图像
            return 'code': 0, 'qrcode_img': str(heximage, "utf-8")
        else:
            raise Exception("请求失败!", data_dict.get('return_code'))
    except Exception as e:
        r.error(e)
        return r.message

注: 在发送post请求前,需要将xml进行utf-8转码,否则得到的二维码扫出来后的中文是乱码。

2.签名加密方式,将Key 和参数进行混合加密

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

3.out_trade_no为微信平台那边需要的订单号

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

4.nonce_str 为我们商户提供的32位以内的随机字符串

import datetime

def generate_number(length):
    start = int("1" + "0" * (length - 1))
    end = int("9" * length)
    return random.randrange(start, end)

 def get_order_number():
        now = datetime.datetime.now()
        str_time = now.strftime('%Y%m%d%H%M%S')
        # 在加一个4位的随机数
        rand_str = generate_number(8)
        return str_time + str(rand_str)

发起支付时需要注意点:

  1. 必须的参数一定要传,不传会报错。
params = 
            "appid": _APP_ID,  # APPID
            "mch_id": _MCH_ID,  # 商户号
            "nonce_str": nonce_str,     
            "out_trade_no": out_trade_no,  # 订单编号
            "total_fee": total_fee,  # 订单总金额
            "spbill_create_ip": _CREATE_IP,  # 发送请求服务器的IP地址
            "notify_url": _NOTIFY_URL,  # 支付结果通知地址
            "body": subject,  # 商品描述
            "trade_type": 'NATIVE'  # 扫码支付
        
  1. total_fee的单位是分,不能为0,否则会报错。

  2. 发起请求的方式为POST, 传给微信的body必须是XML格式的,不能接收其他格式的body参数。

      xml = trans_dict_to_xml(params)  # 转换字典为XML
        data = xml.encode("utf-8")
        response = requests.request('post', _PAY_URL, data=data)  # 以POST方式向微信公众平台服务器发起请求

三、前端回调提示用户支付成功

  用户付钱以后,需要知道的第一件事就是自己有没有支付成功,主要方式有2种:

  1. 前端使用ajax的方式进行定时回调。
  2. 前端调用后端的接口,后端在一定的时间内给予返回。

在这里介绍第一种回调方式,前端持续间隔时间调用该接口,这个结构并不会影响到用户支付成功,而是给前端一个响应,让用户知道是否成功。
建议不采用第二种方式进行回调,因为后端会一直循环查询,比较占用资源,也容易出问题,比如出现死循环问题。
需要的参数格式还是XML格式:

<xml>
   <appid>wx2421b1c4370ec43b</appid>
   <mch_id>10000100</mch_id>
   <nonce_str>ec2316275641faa3aacf3cc599e8730f</nonce_str>
   <transaction_id>1008450740201411110005820873</transaction_id>
   <sign>FDD167FAA73459FD921B144BAF4F4CA2</sign>
</xml>

完整代码

# 查询订单信息
def check_wxpay(request):
    q_url = "https://api.mch.weixin.qq.com/pay/orderquery"

    meta = MetaSettings.objects.using(BaseView.get_partner(None, request)).values_list("wechat_appid",
                                                                                       "wechat_merchant_id",
                                                                                       "wechat_secret_key").first()

    _APP_ID = meta[0]
    _MCH_ID = meta[1]
    _API_KEY = meta[2]
    # 从session中拿出订单信息
    order_number = request.GET.get("order_id")
    data_dict = request.session[order_number]
    data_dict = dict(data_dict)
    data_dict.pop("body")
    data_dict.pop("notify_url")
    data_dict.pop("total_fee")
    data_dict.pop("trade_type")
    data_dict.pop("spbill_create_ip")
    data_dict.pop("sign")
    sign = get_sign(data_dict, _API_KEY)  # 获取签名
    data_dict['sign'] = sign  # 添加签名到参数字典
    xml = trans_dict_to_xml(data_dict)  # 转换字典为XML
    response = requests.request('post', q_url, data=xml.encode("utf-8"))  # 以POST方式向微信公众平台服务器发起请求
    content = response.content
    data_dict = trans_xml_to_dict(content)
    trade_state = data_dict["trade_state"]
    # SUCCESS支付成功
    # REFUND 转入退款
    # NOTPAY
    # CLOSED 已关闭
    # REVOKED—已撤销(付款码支付)
    # USERPAYING--用户支付中(付款码支付)
    # PAYERROR--支付失败(其他原因,如银行返回失败)
    if "SUCCESS" == trade_state:
        del request.session[order_number]
        return JsonResponse("code": 0, "msg": "支付成功!")
    elif "PAYERROR" == trade_state:
        return JsonResponse("code": -1, "msg": "支付失败!")
    elif "NOTPAY" == trade_state:
        return JsonResponse("code": -2, "msg": "未支付!")

四、 云服务平台微信通知

  前端回调不能保证用户是否支付成功,因此不能使用前端回调来确定用户这单是否支付成功,微信给出的解决方案是: 支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
  1. 自己定义通知接口: /notify ,另外微信会给商户传如下的参数:
支付成功的话,微信给商户返回的return_code为SUCCESS, 另外会带有其他的一些信息参数,如下:
  2. 用户就可以根据return_code来做对应的支付成功或者失败处理逻辑!
  3. 处理完成后,商户需要给微信返回一个xml,来确认商户收到了消息,避免微信重复发送消息。

<xml>
  <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
  <attach><![CDATA[支付测试]]></attach>
  <bank_type><![CDATA[CFT]]></bank_type>
  <fee_type><![CDATA[CNY]]></fee_type>
  <is_subscribe><![CDATA[Y]]></is_subscribe>
  <mch_id><![CDATA[10000100]]></mch_id>
  <nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
  <openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
  <out_trade_no><![CDATA[1409811653]]></out_trade_no>
  <result_code><![CDATA[SUCCESS]]></result_code>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <sign关于微信扫码支付的流程

微信扫码登录流程

微信扫码登录实现

手机微信如何扫码付款

微信扫码支付功能---用户扫码支付成功,微信异步回调商户接口

java实现微信支付之扫码支付