python - 对接微信支付(PC)和 注意点

注:本文仅提供 pc 端微信扫码支付(模式一)的示例代码。

  关于对接过程中遇到的问题总结在本文最下方。

  参考: 官方文档

       https://blog.csdn.net/lm_is_dc/article/details/83312706

一。wxpay_settings.py (配置基本参数和创建订单时必要的方法,如 随机生成字符串,加密签名,生成支付二维码等)

# encoding: utf-8

import random
import os
import time
import requests
import hashlib
from random import Random

import qrcode
from bs4 import BeautifulSoup

from appname import settings

APP_ID = ""  # 公众账号appid
MCH_ID = ""  # 商户号
API_KEY = ""  # 微信商户平台(pay.weixin.qq.com) -->账户设置 -->API安全 -->密钥设置,设置完成后把密钥复制到这里
UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"  # url是微信下单api
NOTIFY_URL = "http://xxx/get_pay/callback/"  # 微信支付结果回调接口,需要你自定义
CREATE_IP = ‘111.111.11.11‘  # 你服务器上的ip

def random_str(randomlength=8):
    """
    生成随机字符串
    :param randomlength: 字符串长度
    :return:
    """
    str = ‘‘
    chars = ‘AaBbCcDdEeFfGgHhIiJjKbkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789‘
    length = len(chars) - 1
    random = Random()

    print "randomlength : ", randomlength

    for i in range(randomlength):
        str += chars[random.randint(0, length)]
    print "str : ", str
    return str

def order_num(phone):
    """
    生成扫码付款订单号
    :param phone: 手机号
    :return:
    """
    local_time = time.strftime(‘%Y%m%d%H%M%S‘, time.localtime(time.time()))
    result = phone + ‘T‘ + local_time + random_str(5)
    return result

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(‘utf-8‘))  # 将参数字符串传入
    sign = md5.hexdigest().upper()  # 完成加密并转为大写
    return sign

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)).encode(‘utf-8‘)  # 返回XML,并转成utf-8,解决中文的问题

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 wx_pay_unifiedorde(detail):
    """
    访问微信支付统一下单接口
    :param detail:
    :return:
    """
    detail[‘sign‘] = get_sign(detail, API_KEY)
    xml = trans_dict_to_xml(detail)  # 转换字典为XML
    response = requests.request(‘post‘, UFDODER_URL, data=xml)  # 以POST方式向微信公众平台服务器发起请求
    return response.content

def pay_fail(err_msg):
    """
    微信支付失败
    :param err_msg: 失败原因
    :return:
    """
    data_dict = {‘return_msg‘: err_msg, ‘return_code‘: ‘FAIL‘}
    return trans_dict_to_xml(data_dict)

def create_qrcode(username, url):
    """
    生成扫码支付二维码
    :param username: 用户名
    :param url: 支付路由
    :return:
    """
    get_path = "%s/%s" % (settings.MEDIA_ROOT, "QRcode")    # 创建文件夹路径
    img_path = "%s/%s" % (settings.MEDIA_ROOT, "QRcode/")   # 创建文件路径

    img = qrcode.make(url)  # 创建支付二维码片
    # 你存放二维码的地址 uploads/media
    img_url = img_path + username + ‘.png‘     # 创建文件

    if not os.path.exists(get_path):
        os.makedirs(get_path)
    img.save(img_url)

    ret_url = settings.MEDIA_URL + ‘QRcode/‘  + username + ‘.png‘  # 前端展示路径

    return ret_url

二。URL

    url(r‘^create_order/$‘, CreateOrderViews.as_view()),    # 创建订单和生成二维码
    url(r‘^get_pay/$‘, Wxpay_QRccode.as_view()),    # 微信支付二维码展示页 wx
    url(r‘^get_pay/callback/$‘, Wxpay_ModelOne_pay.as_view()),    # 支付回调接口 wx

三。Views

class CreateOrderViews(APIView):
    """
        创建订单和支付二维码
    """

    # 生成订单(省略部分操作..)
    order_obj = models.Order.objects.create()

    # 调用微信支付 (下面部分参数的详细说明请看第一步骤)
    paydict = {
        ‘appid‘: APP_ID,  
        ‘mch_id‘: MCH_ID,
        ‘nonce_str‘: random_str(),
        ‘product_id‘: order_obj.id,  # 商品id,可自定义
        ‘time_stamp‘: int(time.time()),
    }
    paydict[‘sign‘] = get_sign(paydict, API_KEY)
    url = "weixin://wxpay/bizpayurl?appid=%s&mch_id=%s&nonce_str=%s&product_id=%s&time_stamp=%s&sign=%s"           % (paydict[‘appid‘], paydict[‘mch_id‘], paydict[‘nonce_str‘], paydict[‘product_id‘],
             paydict[‘time_stamp‘], paydict[‘sign‘])

    # 可以直接在微信中点击该url,如果有错误,微信会弹出提示框,如果是扫码,如果失败,什么提示都没有,不利于调试
    # 创建二维码
    img_url = create_qrcode(user_obj.username, url) # 调用生成二维码的方法(具体看第一步 wxpay_settings.py 的create_qrcode方法)
    order_obj.wx_pay_path = img_url # 将支付二维码路径存储在订单表中
    order_obj.save()    # 保存订单
    pay_url = "/order/get_pay/?order_id=%s" % (order_obj.id)
    return JSONResponse({"pay_url": pay_url})

class Wxpay_QRccode(APIView):
    """
    返回二维码图片路径接口
    """
    def get(self, request, *args, **kwargs):
        username = request.session.get(‘username‘)
        if username:
            order_id = request.GET.get("order_id")
            order_obj = Order.objects.filter(id=order_id, create_user__username=username,
                                             status=Order.STATUS_DEFAULT).first()

            return JSONResponse({"pay_img": order_obj.wx_pay_path}) # 从数据库中获取图片路径
        return JSONResponse({"error": "xxx"})

@method_decorator(csrf_exempt, name=‘dispatch‘)
class Wxpay_ModelOne_pay(APIView):
    """
    使用微信扫一扫扫描二维码,微信系统会自动回调此路由,Post请求
    """
    def post(self, request, *args, **kwargs):
        """
        扫描二维码后,微信系统回调的地址处理
        微信传来的参数格式经trans_xml_to_dict()转成字典
        {‘openid‘: ‘xxx‘,
         ‘is_subscribe‘: ‘Y‘,
         ‘mch_id‘: ‘xxx‘,
         ‘nonce_str‘: ‘xxx‘,
         ‘sign‘: ‘xxx‘,
         ‘product_id‘: ‘xxx‘,
         ‘appid‘: ‘xxx‘}

        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        try:
            data_dict = trans_xml_to_dict(request.body)  # 回调数据转字典
            sign = data_dict.pop(‘sign‘)  # 取出签名
            key = API_KEY  # 商户交易密钥
            back_sign = get_sign(data_dict, key)  # 计算签名

            if data_dict.get(‘product_id‘):     # 第一次微信扫码请求,确认是否有该订单(并确认订单状态等是否正确)
                order_obj = Order.objects.filter(id=data_dict.get(‘product_id‘), status=Order.STATUS_DEFAULT).first()
                if not order_obj:
                    return HttpResponse(pay_fail(‘交易信息有误,未找到该订单‘))

            elif data_dict.get(‘return_code‘) == "SUCCESS":     # 此处是支付成功后的回调,第二次请求(用于修改订单状态,保存openid等)
                order_obj = Order.objects.filter(vx_out_trade_no=data_dict.get(‘out_trade_no‘),
                                                 status=Order.STATUS_DEFAULT).first()
                with transaction.atomic():  # 开启事务操作
                    order_obj.status = Order.STATUS_PAYED
                    order_obj.pay_mode = Order.WEIXIN
                    order_obj.save()
                return HttpResponse("SUCCESS")  # 最终记得返回 SUCCESS,否则微信会重复访问该接口,并返回支付结果。

            else:
                return HttpResponse(pay_fail(‘支付状态返回错误!‘))

            if sign == back_sign:  # 验证签名是否与回调签名相同(第一次请求)
                params = {
                    ‘appid‘: APP_ID,  # APPID
                    ‘mch_id‘: MCH_ID,  # 商户号
                    ‘nonce_str‘: random_str(16),  # 随机字符串
                    ‘out_trade_no‘: order_num(data_dict.get(‘product_id‘)),  # 订单编号
                    ‘total_fee‘: int(Decimal(order_obj.total_fee) * 100),  # 付款金额,单位是分,必须是整数
                    ‘spbill_create_ip‘: CREATE_IP,  # 发送请求服务器的IP地址
                    ‘body‘: ‘xxx‘,  # 商品描述
                    ‘detail‘: ‘xxx‘,  # 商品详情
                    ‘notify_url‘: NOTIFY_URL,  # 微信支付结果回调接口
                    ‘trade_type‘: ‘NATIVE‘,  # 扫码支付类型
                }
                # 调用微信统一下单支付接口url
                notify_result = wx_pay_unifiedorde(params)

                if data_dict.get(‘product_id‘):
                    # 保存微信订单(用于后续修改订单状态)
                    order_obj = Order.objects.filter(id=data_dict.get(‘product_id‘),
                                                     status=Order.STATUS_DEFAULT).first()
                    order_obj.vx_out_trade_no = params.get(‘out_trade_no‘)
                    order_obj.save()

                return HttpResponse(notify_result)
            return HttpResponse(pay_fail(‘交易信息有误,请重新扫码‘))
        except Exception as e:
            return HttpResponse(pay_fail(‘程序异常‘))

四。错误总结

1. 开发中第一个错误就是扫码时的服务器繁忙(好像是叫这个错误来着。),解决方案:去微信公众号后台设置服务器地址。

2. 参数错误的话就请检查下是否和微信公众号里面的匹配。

3. 参数回调问题:NOTIFY_URL 这个参数填异步回调地址(必须是服务器地址,微信公众号后台设置),

data_dict = trans_xml_to_dict(request.body) 这个方式是可以拿到微信返回的所有回调参数,

其实这里会被请求两次,第一次是扫码请求,这里主要是给让用户看到支付前微信生成商品信息(商品价格等),第二次是扫码后的支付成功/失败的回调请求,这里主要就是对数据库的操作了(订单状态,商品属性等),data_dict.get(‘return_code‘) == "SUCCESS" ,return_code参数是只有回调才会传,所以根据这个判断需要做什么操作。

五。 本文可能有些错误的地方,欢迎指出,同时有什么问题也欢迎提出。
 

原文地址:https://www.cnblogs.com/chaoqi/p/11438869.html

时间: 2024-11-05 22:53:39

python - 对接微信支付(PC)和 注意点的相关文章

python开发微信支付学习记录(转)

前言 微信支付是由微信及财付通联合推出的移动支付创新产品.如今,随着微信支付的全面开放,相关需求也越来越多,很多开发人员进行微信支付开发及商家申请微信支付时,面临着诸多疑惑. 要想开发顺利进行,首先要对业务流程有个清晰的认识.这里以微信公众号支付为例,因此也借用微信支付官方文档中的业务流程图: 接下来来关注几个开发过程中的关键点,包括: 1.生成商户订单与调用统一下单 API 2.微信服务器交互的数据格式 3.公众号支付下网页内通过 JS-API 调起支付 4.异步通知商户支付结果(回调) 一.

Python对接支付宝支付自实现

Python对接支付宝支付自实现 # -*- coding: utf-8 -*- import base64 import json import urllib.parse from datetime import datetime import requests from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from crypt

java对接微信支付

对接微信扫码支付(模式2),前端使用velocity技术 (1)调用微信支付接口(view层)  此部分业务逻辑部分可以省略 1 @RequestMapping("/wxpay.htm") 2 public ModelAndView wxpay(HttpServletRequest request,HttpServletResponse response, String id, String type, 3 String payment_id) 4 { 5 ModelAndView m

微信支付----PC扫码支付(查询微信支付订单判断是否支付成功)

c#webapi建议前端轮番查询订单状态     #region  查询扫码订单状态         /// <summary>         /// 查询扫码支付状态         /// </summary>         /// <param name="out_trade_no">支付订单号</param>         /// <returns></returns>         [HttpGe

对接微信支付使用HMAC-SHA256使用签名算法实现方式

最近做微信押金支付对接,很多坑,心累!这里提醒一下各位: 首先,确保自己商户号进了白名单,没有需要联系客服,否则接口是调不通的,会一直提示参数错误 其次,确保接口文档是最新的,最好去官网去看,否则可能会有问题,我就是被这个坑了好久,以为拿到的接口文档是对的,结果参数一直有问题 ,最后发现是文档有问题,而且官网上文档也有问题,我已经发现好几个了,比如fee_type这个参数,明明写着不是必填,但是一定要填,否则会报签名错误之类的返回码 所以文档也有可能不及时,所以最好就是自己再三确认之后,多与客服

《小猪CMS(PigCms)多用户微信营销服务平台系统V6.1完美破解至尊版带微用户管理CRM+微信支付》

<小猪CMS(PigCms)多用户微信营销服务平台系统V6.1完美破解至尊版带微用户管理CRM+微信支付> 之前发布了不少微赢的多用户微信网站源码,今天为我的小伙伴们准备的是功能非常强悍,最新版小猪CMS(PigCms)多用户微信营销服务平台系统V6.1完美破解至尊版带微用户管理CRM,其功能非常不错的,安装也很简单.目前不少用微信管家的童鞋,估计都知道微信管家几百M的源码,实际上很多都是多余的文件,而且安装起来也是超麻烦.小猪CMS(pigcms)多用户微信营销系统也是个非常棒的选择哦. 这

商户如何接入微信支付

1. 商户接入 1.1 申请流程指引 一.申请流程图 二.申请接入步骤详细说明 (一)成为已认证的服务号 1.目前微信支付功能仅开放给已经通过微信认证的服务号(企业.商店商家.非事业单位媒体类服务号),若已经通过微信认证,且是服务号,可直接进入第二步;2.订阅号商户可先升级为服务号,此步骤约1个工作日;3.未经认证的商户可先申请微信认证.注意:请申请认证的商户主体与后续申请微信支付权限的商户主体保持一致. (二)提交申请资料,等待审核 1.登录公众号,进入:服务-服务中心-商户功能; 2.提交商

spring boot项目之微信支付功能实现详细介绍

对接微信支付功能主要有以下几个步骤, 而其第一个关键点就是获取OpenID,在这里介绍两种获取方式: 一.微信授权 微信网页授权 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域名的说明 1.在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的"开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息"的配置选项中,修改授权回调域名.请注意,这里填写的是域名(是一个字符串),

微信支付—微信H5支付「微信内部浏览器」

前言 微信支付-微信H5外部浏览器支付微信支付-微信H5内部浏览器支付「本文」微信支付-PC端扫码支付「待写」 本篇是微信支付系列的第二篇.微信H5内部浏览器支付,关于微信H5外部浏览器唤起微信APP支付,请参考上一篇文章. 开发环境:Java + SpringBoot + Vue +WxJava(开源SDK) 扫盲补充:关于微信内部浏览器支付,支付时会直接调起微信支付,不同于外部浏览器支付,内部浏览器支付首先需要获得当前支付用户对该公众号的唯一标识 openId「是否关注都是唯一的」,拿到 o