Python对接微信小程序V3接口进行支付,并使用uwsgi+nginx+django进行https部署

网上找了很多教程,但是很乱很杂,并且教程资源很少且说的详细。这里就记录一下分享给大家

共分为以下几个步骤:

一、开始前准备信息

二、使用前端code获取用户的openid

三、对接小程序v3接口下单

四、小程序支付的回调

五、安装并启动uwsgi

六、安装并启动nginx

七、配置nginx的nginx.conf文件,让部署的项目以https访问

八、后端代码

九、常见问题及小技巧

代码将在最后一起贴出,先开始流程步骤,弄明白流程,代码读起来自然简单

下面一步步的开始:

一、开始前准备信息
  1. 需要在注册小程序和在微信支付平台开始微信支付,并将在小程序后台和商户关联绑定。

  1. 接入前准备,直接参考微信支付官方文档:接入前准备

2.1 操作完成之后获取到appid,appsecret,pay_key,serial_num

appid,小程序里获取的appid

appsecret,小程序里获取的appsecret

pay_key,API密钥。需要登录微信商户平台,进入【账户中心 > API安全 】目录,设置APIV3密钥。这个密钥确保32位,自定义设置

serial_num,API证书序列号。登录微信商户平台,进入【账户中心 > API安全 】目录,申请API证书,完成申请后会有API证书序列号,完成微信支付后台配置证书后并下载该证书文件,有一个apiclient_key.pem密钥文件,之后对接支付会用到。

二、使用前端code获取用户的openid

参考官方文档: 小程序登录流程

  1. 前端使用wx.login()获取临时登录凭证code,传给后端,后端接收code并请求auth.code2Session接口获取openid

三、对接小程序v3接口下单

参考官方文档: 开发指引v3接口JSAPI下单

  1. 调用JSAPI下单接口,传递接口必要参数,获取prepay_id,并再次加密验证签名,将timeStamp,nonceStr,package,signType,paySign参数交由前端并由前端拉起支付页面。

四、小程序支付的回调
  1. 支付完成之后,微信支付会自动调用在下单配置的回调地址(这个地址必须是https的),解密并验证应答签名中的序列号是否和平台证书序列号一致,一致时就可以做项目相关的操作,比如修改订单支付状态

五、安装并启动uwsgi
  1. 直接在服务器上使用pip3 install -i https://pypi.douban.com/simple uwsgi 快速安装uwsgi,并将django的项目代码上传至服务器,创建一个uwsgi.ini文件,添加以下内容:

socket=0.0.0.0:8000  # 使用https方式,这里地址必须与nginx配置里的uwsgi_pass一致
chdir = /home/foobar/myproject/  # django项目的根目录路径
wsgi-file = myproject/wsgi.py  # django项目下的wsgi.py文件,也可以写成绝对路径
processes = 4  # 进程数
threads = 2  # 线程数
pidfile=uwsgi.pid
daemonize=uwsgi.log 
master=True
  1. 运行和停止uwsgi

2.1 在同uwsgi.ini文件的路径下运行uwsgi --ini uwsgi.ini 运行后会在uwsgi.ini生成uwsgi.pid文件,使用uwsgi --stop uwsgi.pid可停止运行。如在停止时报进程号的错误,可使用ps -ef | grep uwsgi查看uwsgi运行的进程号,复制并替换uwsgi.pid里的进程号后重新运行停止命令即可

六、安装并启动nginx

1,请到nginx官方下载nginx安装包(tar.gz格式),并上传至服务器

使用以下命令解压安装:

sudo tar -xvf nginx-1.13.7.tar.gz

cd nginx-1.13.7

sudo ./configure

sudo make

sudo make install

默认安装到/usr/local/nginx下,需要配置nginx.conf文件(下面说)

2,启动和停止nginx

/usr/local/nginx

查看版本:sudo sbin/nginx -v

启动:sudo sbin/nginx

停止:sudo sbin/nginx -s stop

重启:sudo sbin/nginx -s reload

七、配置nginx的nginx.conf文件,让部署的项目以https访问
  1. 先到域名所在服务器商(比如阿里云)搜索ssl,打开ssl控制台,按下图申请免费证书并创建证书,对域名申请签发ssl证书。

  1. 状态是已签发后,点击下载证书,这里下载要选择nginx版本的,解压后有两个.key和.pem的文件,将这两个文件上传到服务器你指定的目录下(一会配置nginx.conf指明这两个文件的路径)

  1. nginx.conf的配置


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    #以下属性中,以ssl开头的属性表示与证书配置有关。
    server {
        #配置HTTPS的默认访问端口为443。
        #如果未在此处配置HTTPS的默认访问端口,可能会造成Nginx无法启动。
        #如果您使用Nginx 1.15.0及以上版本,请使用listen 443 ssl代替listen 443和ssl on。
        listen 443 ssl;

        #填写证书绑定的域名,替换成你的域名
        server_name www.baidu.com;
        root html;
        index index.html index.htm;

        #填写证书文件名称,替换成下载的证书
        ssl_certificate /usr/local/nginx/conf/cert/9478490_baidu.com.pem;
        #填写证书私钥文件名称,替换成下载的私钥
        ssl_certificate_key /usr/local/nginx/conf/cert/9478490_baidu.com.key;

        ssl_session_timeout 5m;
        #表示使用的加密套件的类型
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        #表示使用的TLS协议的类型,您需要自行评估是否配置TLSv1.1协议。
        ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;

        ssl_prefer_server_ciphers on;
        location / {
            include uwsgi_params;
            uwsgi_pass 0.0.0.0:8000  # 必须与uwsgi.ini里的socket一致
        }
    }
    server {
    listen 80;
    #填写证书绑定的域名
    server_name www.baidu.com;  # 这里以百度的域名示例,需要替换成你申请ssl证书的域名
    #将所有HTTP请求通过rewrite指令重定向到HTTPS。
    rewrite ^(.*)$ https://$host$1;

    }
}

每次修改nginx.conf文件后都要重启nginx

八、后端代码:

1.路由urls.py

path('payment/', views.PaymentView.as_view()),  # 支付
path('pay/notify/', views.NotifyView.as_view()),  # 回调

2.views.py: 支付接口

添加购物车,订单,和商品支付,都写在了这一个支付接口里

class PaymentView(View):
    @transaction.atomic()
    def post(self, request):
        data_dict = json.loads(request.body.decode())

        code = data_dict.get('code', None)  # 前端传过来的code
        courseIds = data_dict.get("courseIds", None)
        quantitys = data_dict.get('quantitys', None)
        address = data_dict.get('address', None)
        signer_name = data_dict.get("signer_name", None)
        signer_mobile = data_dict.get("signer_mobile", None)
        school_name = data_dict.get("school_name", None)

        if not all([code, courseIds, quantitys, address, signer_name, signer_mobile, school_name]):
            return JsonResponse({'status': status.HTTP_400_BAD_REQUEST, 'msg': "参数不全"})

        valid = re.match(r"^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$", signer_mobile)
        if not valid:
            return JsonResponse({'status': status.HTTP_400_BAD_REQUEST, 'msg': "手机号码格式不正确"})

        login_info = get_login_info(code)  # 使用code获取 openid
        # session_key = login_info['session_key']
        if login_info:
            openid = login_info['openid']
            # print(openid)
        else:
            return JsonResponse({'status': status.HTTP_400_BAD_REQUEST, 'msg': "获取openid失败,请检查code"})

        sid = transaction.savepoint()

        # 当前用户openid存在就查询出来,不存在就添加
        user, valid = UserProfile.objects.get_or_create(openid=openid, defaults={"username": code})
        user_id = user.id
        # 更新用户登录时间
        user.last_login = datetime.datetime.now()
        user.save()

        totalPrice = 0  # 购物车总价

        try:
            # 添加购物车信息
            for courseId, quantity in zip(courseIds, quantitys):
                order_dict = {}
                order_dict.update({"user_id": user_id})
                order_dict.update({"course_id": courseId})
                order_dict.update({"quantity": quantity})
                order_dict.update({"checked": True})
                ShoppingCart.objects.create(**order_dict)

                course = Course.objects.get(id=courseId)
                totalPrice += course.price * quantity

            try:
                # 添加订单信息
                # 生成订单号
                orderNo = generate_orderNo()
                shop_carts = ShoppingCart.objects.filter(user_id=user_id, checked=True)
                if shop_carts.exists():
                    # 订单信息
                    order = Order.objects.create(orderNo=orderNo, user=user, payment=totalPrice, address=address,
                                                 signer_name=signer_name, signer_mobile=signer_mobile, school_name=school_name)

                    for cart in shop_carts:
                        OrderItem.objects.create(order=order, course=cart.course, currentUnitPrice=cart.course.price,
                                                 quantity=cart.quantity, totalPrice=cart.course.price * cart.quantity)
                    # 清空购物车
                    shop_carts.delete()
                else:
                    return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, 'msg': "购物车为空"})

                # 支付
                try:
                    total = int(totalPrice * 100)  # 计算总价,单位为分
                    # 3.生成统一下单的报文body
                    url = settings.wxPayUrl
                    body = {
                        "appid": settings.AppId,
                        "mchid": settings.mchid,
                        "description": "商品描述",
                        "out_trade_no": str(orderNo),
                        "notify_url": settings.wxNotifyUrl + "pay/notify/",  # 后端接收回调通知的接口
                        "amount": {"total": total, "currency": "CNY"},  # 正式上线price要*100,微信金额单位为分(必须整型)。
                        "payer": {"openid": openid},
                    }
                    data = json.dumps(body)

                    headers, random_str, time_stamps = make_headers_v3(settings.mchid, settings.serial_num, data=data,
                                                                       method='POST')

                    # 10.发送请求获得prepay_id
                    try:
                        response = requests.post(url, data=data, headers=headers)  # 获取预支付交易会话标识(prepay_id)
                        print("预支付交易会话标识", response)
                        if response.status_code == 200:
                            wechatpay_serial, wechatpay_timestamp, wechatpay_nonce, wechatpay_signature, certificate, serial_no = check_wx_cert(
                                response, settings.mchid, settings.pay_key, settings.serial_num)
                            # 11.9签名验证
                            if wechatpay_serial == serial_no:  # 应答签名中的序列号同证书序列号应相同
                                print('serial_no match')
                                try:
                                    data3 = f"{wechatpay_timestamp}\n{wechatpay_nonce}\n{response.text}\n"
                                    verify(data3, wechatpay_signature, certificate)
                                    print('The signature is valid.')
                                    # 12.生成调起支付API需要的参数并返回前端
                                    res = {
                                        'orderno': orderNo,  # 订单号
                                        'timeStamp': time_stamps,
                                        'nonceStr': random_str,
                                        'package': 'prepay_id=' + response.json()['prepay_id'],
                                        'signType': "RSA",
                                        'paySign': get_sign(
                                            f"{settings.AppId}\n{time_stamps}\n{random_str}\n{'prepay_id=' + response.json()['prepay_id']}\n"),
                                    }
                                    return JsonResponse({"status":status.HTTP_200_OK, "msg": "下单成功", "total": total, "data": res})
                                except Exception as e:
                                    logger.error(f"证书序列号验签失败{e}, {traceback.format_exc()}")
                                    return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, "msg": "下单失败"})
                            else:
                                logger.error(f"证书序列号比对失败【请求头中证书序列号:{wechatpay_serial};本地存储证书序列号:{serial_no};】")
                                return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, "msg": "调起微信支付失败!"})
                        else:
                            logger.error(f"获取预支付交易会话标识 接口报错【params:{data};headers:{headers};response:{response.text}】")
                            return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, "msg": "调起微信支付失败!"})
                    except Exception as e:
                        logger.error(f"调用微信支付接口超时【params:{data};headers:{headers};】:{e},{traceback.format_exc()}")
                        return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, "msg": "微信支付超时!"})
                except Exception as e:
                    logger.error(f"微信支付接口报错:{e},{traceback.format_exc()}, {e}")
                    return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, "msg": "微信支付接口报错!"})
            except Exception as e:
                transaction.savepoint_rollback(sid)
                return JsonResponse({'status': status.HTTP_400_BAD_REQUEST, 'msg': "生成订单失败"})
        except Exception as e:
            transaction.savepoint_rollback(sid)
            return JsonResponse({'status': status.HTTP_400_BAD_REQUEST, 'msg': "添加购物车失败"})

3.views.py: 回调接口

class NotifyView(APIView):
    def post(self, request):

        """
            支付完成之后的通知(微信官方返回的数据)
            :param request:
            :return:
            """
        try:
            # 1.获得支付通知的参数
            body = request.body
            data = bytes.decode(body, 'utf-8')
            newdata = json.loads(data)
            # newdata = {
            #     "id": "9d40acfd-13cb-5175-a5aa-6c421f794952",
            #     "create_time": "2023-01-06T15:12:49+08:00",
            #     "resource_type": "encrypt-resource",
            #     "event_type": "TRANSACTION.SUCCESS",
            #     "summary": "\xe6\x94\xaf\xe4\xbb\x98\xe6\x88\x90\xe5\x8a\x9f",
            #     "resource": {
            #         "original_type":
            #         "transaction",
            #         "algorithm": "AEAD_AES_256_GCM",
            #         "ciphertext": "UF5gLXfe8qBv9qxQsf+/Mb6as+vbIhUS8Dm25qGIJIIdXTorUUjqZH1+"
            #                       "jMQxkxma/Gn9bOxeAoQWPEuIoJ2pB328Iv90jmHTrouoP3L60mjNgGJS8d3H8i1zAPBXCpP4mgvgRANWsw4pAWj1lFM5BZr4aP+"
            #                       "pNMc5TdwreGBG3rO9sbCLXsSRfW8pVZ7IfPnhPDTOWP3P1k5ikHedcRt4/HP69oDBEe5RSsD93wO/"
            #                       "lrIwycStVHyecBaliwpVMRnNnRCXqhlalNJ3NJ6jcgy32fP1J+L90ntwGyqMmZUS71P5TN1H0iH5rXNpRY9IF3pvN+"
            #                       "lei5IS86wEoVXkmEsPcJrHaabn7rghxuZoqwuauMIiMwBLllnEmgXfAbJA4FJy+"
            #                       "OLhZPrMWMkkiNCLcL069QlvhLXYi/0V9PQVTnvtA5RLarj26s4WSqTZ2I5VGHbTqSIZvZYK3F275KEbQsemYETl18xwZ+"
            #                       "WAuSrYaSKN/pKykK37vUGtT3FeIoJup2c6M8Ghull3OcVmqCOsgvU7/pNjl1rLKEJB6t/X9avcHv+feikwQBtBmd/b2qCeSrEpM7US",
            #         "associated_data": "transaction",
            #         "nonce": "cKEdw8eV9Bh0"
            #     }
            # }
            nonce = newdata['resource']['nonce']
            ciphertext = newdata['resource']['ciphertext']
            associated_data = newdata['resource']['associated_data']

            payment = ""
            try:
                payment = decrypt(nonce, ciphertext, associated_data, settings.pay_key)
            except Exception as e:
                print(e)
            if not payment:
                return JsonResponse({"code": "FAIL", "message": "失败"}, status=400)
            payment = eval(payment.decode('utf-8'))
            # payment = {
            #     "mchid": "xxxx",
            #     "appid": "xxxx",
            #     "out_trade_no": "20231654836163523608",
            #     "transaction_id": "4200001646202301065425000524",
            #     "trade_type": "JSAPI",
            #     "trade_state": "SUCCESS",
            #     "trade_state_desc": "\xe6\x94\xaf\xe4\xbb\x98\xe6\x88\x90\xe5\x8a\x9f",
            #     "bank_type": "OTHERS",
            #     "attach": "",
            #     "success_time": "2023-01-06T15:12:49+08:00",
            #     "payer": {
            #         "openid": "xxxxx"
            #     },
            #     "amount": {
            #         "total": 1,
            #         "payer_total": 1,
            #         "currency": "CNY",
            #         "payer_currency": "CNY"
            #     }
            # }
            orderno = payment['out_trade_no']
            # logger.info(orderno)
            # zf_status = True if payment["trade_type"] == "SUCCESS" else False
            # if zf_status:
            #     money = decimal.Decimal(int(payment["amount"]["payer_total"]) / 100).quantize(decimal.Decimal("0.00"))
            # else:
            #     money = decimal.Decimal(0.0).quantize(decimal.Decimal("0.00"))
            # 7.回调报文签名验证
            # 同第一篇签名验证的代码
            wechatpay_serial, wechatpay_timestamp, wechatpay_nonce, wechatpay_signature, certificate, serial_no = check_wx_cert(
                request, settings.mchid, settings.pay_key, settings.serial_num)
            if wechatpay_serial == serial_no:  # 应答签名中的序列号同证书序列号应相同
                # 8.获得回调报文中交易号后修改已支付订单状态
                order = Order.objects.filter(orderNo=orderno, pay_status="NOTPAY").first()
                if order:
                    order.pay_status = "SUCCESS"
                    order.save()
                    return JsonResponse({"code": "SUCCESS", "message": "成功"})
                else:
                    return JsonResponse({"status": status.HTTP_400_BAD_REQUEST, "msg": "该订单不存在"})
                # 9.项目业务逻辑

            else:
                logger.error(f"证书序列号比对失败【请求头中证书序列号:{wechatpay_serial};本地存储证书序列号:{settings.serial_num};序列号{serial_no}】")
                return JsonResponse({"code": "FAIL", "message": "失败"}, status=400)
        except Exception as e:
            logger.error(f"微信回调接口报错:{e},{traceback.format_exc()}")
            return JsonResponse({"code": "FAIL", "message": "失败"}, status=400)

4. 相关函数

import time
import random

import base64
import random
import string
import time
import traceback
import datetime
import json
from decimal import Decimal
import requests
# from BaseMethods.log import log
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Cryptodome.Hash import SHA256
from sqlalchemy.util import b64encode
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import uuid


def generate_orderNo():
    return int(round(time.time() * 1000000)) + random.randint(10000000, 99999999)  # 微秒级时间戳

def get_login_info(code):
    jscode_url = settings.jscode2session
    response = requests.get(jscode_url, params={'appid': settings.AppId,
                                                'secret': settings.AppSecret,
                                                'js_code': code})
    json_response = response.json() # 把它变成json的字典
    # print(json_response)
    if json_response.get("session_key"):
        return json_response
    else:
        return False


# 获取唯一标识
# def get_uuid(utype=0):
#     """
#     唯一码
#     :param utype:
#     :return:
#     """
#     if utype == 0:
#         return uuid.uuid1()
#     elif utype == 1:
#         return str(uuid.uuid1())
#     elif utype == 2:
#         return str(uuid.uuid1().hex)
#     elif utype == 3:
#         return str((uuid.uuid5(uuid.NAMESPACE_DNS, str(uuid.uuid1()) + str(random.random()))))


# 获取当前时间
# def get_now_time(type=0):
#     """
#     :param type: 类型0-5
#     :return: yyyy-mm-dd HH:MM:SS;y-m-d H:M:S.f;y-m-d;ymdHMS;y年m月d日h时M分S秒
#     """
#     if type == 0:
#         return datetime.datetime.now()
#     elif type == 1:
#         return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#     elif type == 2:
#         return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
#     elif type == 3:
#         return datetime.datetime.now().strftime("%Y-%m-%d")
#     elif type == 4:
#         return datetime.datetime.now().strftime("%Y%m%d%H%M%S")
#     elif type == 5:
#         locale.setlocale(locale.LC_CTYPE, 'chinese')
#         timestr = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#         t = time.strptime(timestr, "%Y-%m-%d %H:%M:%S")
#         result = (time.strftime("%Y年%m月%d日%H时%M分%S秒", t))
#         return result
#     elif type == 6:
#         return datetime.datetime.now().strftime("%Y%m%d")


# 重构系统jargon类,用于处理时间格式报错问题
class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, datetime.date):
            return obj.strftime("%Y-%m-%d")
        elif isinstance(obj, Decimal):
            return float(obj)
        elif isinstance(obj, bytes):
            return str(obj, encoding='utf-8')
        elif isinstance(obj, uuid.UUID):
            return str(obj)
        elif isinstance(obj, datetime.time):
            return obj.strftime('%H:%M')
        elif isinstance(obj, datetime.timedelta):
            return str(obj)
        else:
            return json.JSONEncoder.default(self, obj)


def decrypt(nonce, ciphertext, associated_data, pay_key):
    """
    AES解密
    :param nonce:
    :param ciphertext:
    :param associated_data:
    :param pay_key:
    :return:
    """
    key = pay_key
    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = base64.b64decode(ciphertext)
    aesgcm = AESGCM(key_bytes)
    return aesgcm.decrypt(nonce_bytes, data, ad_bytes)


def order_num():
    """
    生成订单号
    :return:
    """
    # 下单时间的年月日毫秒12+随机数8位
    now_time = datetime.datetime.now()
    result = str(now_time.year) + str(now_time.month) + str(now_time.day) + str(now_time.microsecond) + str(
        random.randrange(10000000, 99999999))
    return result


def get_sign(sign_str):
    """
    定义生成签名的函数
    :param sign_str:
    :return:
    """
    try:
        with open(r'utils/cret/apiclient_key.pem') as f:
            private_key = f.read()
        rsa_key = RSA.importKey(private_key)
        signer = pkcs1_15.new(rsa_key)
        digest = SHA256.new(sign_str.encode('utf8'))
        # sign = b64encode(signer.sign(digest)).decode('utf-8')
        sign = b64encode(signer.sign(digest))
        return sign
    except Exception as e:
        print("生成签名的函数方法报错【func:get_sign;sign_str:%s】:%s ==> %s" % (sign_str, e, traceback.format_exc()))


def check_wx_cert(response, mchid, pay_key, serial_no):
    """
    微信平台证书
    :param response: 请求微信支付平台所对应的的接口返回的响应值
    :param mchid: 商户号
    :param pay_key: 商户号秘钥
    :param serial_no: 证书序列号
    :return:
    """
    wechatpay_serial, wechatpay_timestamp, wechatpay_nonce, wechatpay_signature, certificate = None, None, None, None, None
    try:
        # 11.应答签名验证
        wechatpay_serial = response.headers['Wechatpay-Serial']  # 获取HTTP头部中包括回调报文的证书序列号
        wechatpay_signature = response.headers['Wechatpay-Signature']  # 获取HTTP头部中包括回调报文的签名
        wechatpay_timestamp = response.headers['Wechatpay-Timestamp']  # 获取HTTP头部中包括回调报文的时间戳
        wechatpay_nonce = response.headers['Wechatpay-Nonce']  # 获取HTTP头部中包括回调报文的随机串
        # 11.1.获取微信平台证书 (等于又把前面的跑一遍,实际上应是获得一次证书就存起来,不用每次都重新获取一次)
        url2 = "https://api.mch.weixin.qq.com/v3/certificates"
        # 11.2.生成证书请求随机串
        random_str2 = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
        # 11.3.生成证书请求时间戳
        time_stamps2 = str(int(time.time()))
        # 11.4.生成请求证书的签名串
        data2 = ""
        sign_str2 = f"GET\n{'/v3/certificates'}\n{time_stamps2}\n{random_str2}\n{data2}\n"
        # 11.5.生成签名
        sign2 = get_sign(sign_str2)
        # 11.6.生成HTTP请求头
        headers2 = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "Authorization": 'WECHATPAY2-SHA256-RSA2048 '
                             + f'mchid="{mchid}",nonce_str="{random_str2}",signature="{sign2}",timestamp="{time_stamps2}",serial_no="{serial_no}"'
        }
        # 11.7.发送请求获得证书
        response2 = requests.get(url2, headers=headers2)  # 只需要请求头
        cert = response2.json()

        # 11.8.证书解密
        nonce = cert["data"][0]['encrypt_certificate']['nonce']
        ciphertext = cert["data"][0]['encrypt_certificate']['ciphertext']
        associated_data = cert["data"][0]['encrypt_certificate']['associated_data']
        serial_no = cert["data"][0]['serial_no']
        certificate = decrypt(nonce, ciphertext, associated_data, pay_key)
    except Exception as e:
        print(f"微信平台证书验证报错:{e};{traceback.format_exc()}")
    return wechatpay_serial, wechatpay_timestamp, wechatpay_nonce, wechatpay_signature, certificate, serial_no


def verify(check_data, signature, certificate):
    """
    验签函数
    :param check_data:
    :param signature:
    :param certificate:
    :return:
    """
    key = RSA.importKey(certificate)  # 这里直接用了解密后的证书,但没有去导出公钥,似乎也是可以的。怎么导公钥还没搞懂。
    verifier = pkcs1_15.new(key)
    hash_obj = SHA256.new(check_data.encode('utf8'))
    return verifier.verify(hash_obj, base64.b64decode(signature))


def make_headers_v3(mchid, serial_num, data='', method='GET'):
    """
    定义微信支付请求接口中请求头认证
    :param mchid: 商户ID
    :param serial_num: 证书序列号
    :param data: 请求体内容
    :param method: 请求方法
    :return: headers(请求头)
    """
    # 4.定义生成签名的函数 get_sign(sign_str)
    # 5.生成请求随机串
    random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))

    # 6.生成请求时间戳
    time_stamps = str(int(time.time()))
    # 7.生成签名串
    sign_str = f"{method}\n{'/v3/pay/transactions/jsapi'}\n{time_stamps}\n{random_str}\n{data}\n"
    print(sign_str)
    # 8.生成签名
    sign = get_sign(sign_str)
    # 9.生成HTTP请求头
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'WECHATPAY2-SHA256-RSA2048 '
                         + f'mchid="{mchid}",nonce_str="{random_str}",signature="{sign}",timestamp="{time_stamps}",serial_no="{serial_num}"'
    }
    return headers, random_str, time_stamps

5. settings.py :相关key和密钥配置

jscode2session = "https://api.weixin.qq.com/sns/jscode2session"  # 获取openid接口
grant_type = "authorization_code"  # 固定值

AppId = 'xxxxxxxxxxxx' # 小程序的密钥appid
AppSecret = '42de1fd9916e348397xxxxxxxxxxx1'   # 写自己小程序的密钥
mchid = '12345678901'
pay_key = "xxxxxxxxxxxxxxxxxxxxxxxx"  # 32位API密钥
serial_num = "63298439B7xxxxxxx3EFC29B2CA813Fxxxxxxxx"  # API 证书序列号

wxPayUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"  # 支付接口
wxNotifyUrl = "https://www.baidu.com/"  # 回调地址

到这里就已经能够使用https的方式访问网站了!~

九、常见问题及小技巧

1,常用命令

ps -aux | grep nginx  # 查看 nginx
nginx -t #查看服务器nginx生效运行的目录
ps -ef | grep uwsgi # 查看uwsgi
lsof -i:8000 # 查看端口占用情况
netstat -ap | grep 8000  # 查看端口占用情况

2,微信支付回调时必须使用https的问题

  1. 当你有服务器和域名时可以直接部署https服务,这样在支付完成后回调就可以直接使用https的地址了

  1. 当你没有服务器和域名时,需要借助第三方,可以在本地完成支付、回调的开发。功能测试完成后再在生产环境中使用

  1. 使用内网穿透的方式,并且可以远程和前端联调(这个免费)。且提供了https在本地开发的方式,方便对接微信支付。natapp 这里点击进去安装配置运行即可,核心功能就是使用网站提供的域名代理你本机的localhost。https的方式需要购买

3,pycharm与服务器连接进行远程调试

这个就直接参考另一个博主的教程吧:https://blog.csdn.net/xuanhaolaile/article/details/128293254

本文参考另两位博主

https://blog.csdn.net/qq_42142258/article/details/128653725?spm=1001.2014.3001.5506

https://zhuanlan.zhihu.com/p/402449405

最后,如果觉得有用,请给个赞吧~

猜你喜欢

转载自blog.csdn.net/qq_37140721/article/details/129725244
今日推荐