微信支付V3版本的Python使用方法

微信支付自推出V3版本接口以来,体验下来确实比V2有了很大的提升,但介于其官方没有提供Python版本的SDK,故在此记录通过Python3来使用微信支付V3的主要过程。

1.搞清楚自己的身份

这一步看似可以忽略,但也需要仔细,笔者之前就犯了一个错,看了不对应的文档。 微信支付的商家有两种身份模式:普通商户、服务商。

一般的大部分商户都是属于普通商户,只有品牌商等才为服务商,并且两者身份不能互换。 普通商户的API文档: 服务商API文档:

 

2.配置阶段

需要在商户端界面申请API证书和配置APIv3密钥,次过程可以参考每一个步骤右侧的查看指引按钮。

 

 

 

 

 

3.构造签名

V3版本的接口调用需要对请求进行签名,参考 根据官方的文档可以按此步骤生成请求签名。

 

复制代码
class WeixinPayUtil(object):
    @staticmethod
    def signature(private_key_path, sign_str):
        """
        生成签名值
        private_key_path 私钥路径
        sign_str 签名字符串
        https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
        """
        with open(private_key_path) as file:
            private_key = file.read()
        try:
            rsa_key = RSA.import_key(private_key)
            signer = PKCS1_v1_5.new(rsa_key)
            digest = SHA256.new(sign_str.encode('utf-8'))
            return base64.b64encode(signer.sign(digest)).decode('utf-8')
        except Exception:
            raise "WeixinPaySignIError"
复制代码

 

统一下单完整方法

复制代码
import time
import requests
import string
import random
import json
from Crypto.PublicKey import RSA
import random
import base64
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256


class WeixinPayUtil(object):
    @staticmethod
    def signature(private_key_path, sign_str):
        """
        生成签名值
        private_key_path 私钥路径
        sign_str 签名字符串
        https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
        """
        with open(private_key_path) as file:
            private_key = file.read()
        try:
            rsa_key = RSA.import_key(private_key)
            signer = PKCS1_v1_5.new(rsa_key)
            digest = SHA256.new(sign_str.encode('utf-8'))
            return base64.b64encode(signer.sign(digest)).decode('utf-8')
        except Exception:
            raise "WeixinPaySignIError"


class WeixinPay(object):
    """
    微信支付
    """
    base_url = 'https://api.mch.weixin.qq.com'

    def __init__(self, mch_id, app_id, mch_serial_no, cert_dir, notify_url):
        self.mch_id = mch_id  # 直连商户号
        self.app_id = app_id  # 微信应用ID
        self.mch_serial_no = mch_serial_no  # # 商户API证书序列号
        self.cert_dir = cert_dir  # apiclient_key.pem 地址
        self.notify_url = notify_url  # 回调地址

        self.timestamp = str(int(time.time()))
        self.nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 16))

    def _generate_request_sign(self, url_path, data, method='POST'):
        """
        生成请求签名
        """
        sign_list = [method, url_path, self.timestamp, self.nonce_str]
        if data is not None:
            sign_list.append(data)
        else:
            sign_list.append('')
        sign_str = '\n'.join(sign_list) + '\n'

        print("sign_str:", sign_str)
        return WeixinPayUtil.signature(private_key_path=self.cert_dir, sign_str=sign_str)

    def _generate_pay_sign(self, app_id, package):
        """
        生成支付签名
        """
        sign_list = [app_id, self.timestamp, self.nonce_str, package]
        sign_str = '\n'.join(sign_list) + '\n'
        return WeixinPayUtil.signature(private_key_path=self.cert_dir, sign_str=sign_str)

    def _generate_auth_header(self, signature):
        """
        生成授权请求头
        """
        return f'WECHATPAY2-SHA256-RSA2048 mchid="{self.mch_id}",nonce_str="{self.nonce_str}",' \
               f'signature="{signature}",timestamp="{self.timestamp}",serial_no="{self.mch_serial_no}"'

    def unified_order(self, order_id, openid, amount, desc, mch_id=None, notify_url=None, profit_sharing=False,
                      expire_time=None, attach=None, goods_tag=None, detail=None, scene_info=None, currency='CNY'):
        """
        统一下单
        https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
        """
        url_path = '/v3/pay/transactions/jsapi'
        url = self.base_url + url_path

        data = {
            'appid': self.app_id,
            'mchid': mch_id if mch_id is not None else self.mch_id,
            'description': desc,
            'out_trade_no': order_id,
            'notify_url': notify_url if notify_url is not None else self.notify_url,
            'settle_info': {
                'profit_sharing': profit_sharing
            },
            'amount': {
                'total': amount,
                'currency': currency
            },
            'payer': {
                'openid': openid
            }
        }

        if attach:
            data.update({'attach': attach})
        if expire_time:
            data.update({'time_expire': expire_time})
        if goods_tag:
            data.update({'goods_tag': goods_tag})
        if detail:
            data.update({'detail': detail})
        if scene_info:
            data.update({'scene_info': scene_info})

        data = json.dumps(data)
        signature = self._generate_request_sign(url_path=url_path, data=data)
        print("signature:", signature)

        print("Authorization signature:", self._generate_auth_header(signature))
        headers = {'Authorization': self._generate_auth_header(signature), 'Content-Type': 'application/json'}

        res = requests.post(url=url, data=data, headers=headers, timeout=10)

        print("res:", json.loads(res.content))

        # 支付签名
        # pay_sign = self._generate_pay_sign(app_id=self.app_id, package='prepay_id=' + res['prepay_id'])

        # return {
        #     'timestamp': self.timestamp,
        #     'nonce_str': self.nonce_str,
        #     'prepay_id': res['prepay_id'],
        #     'sign_type': 'RSA',
        #     'pay_sign': pay_sign
        # }


weixinpay = WeixinPay(mch_id="111111111", app_id="wxda22111111111111",
                      mch_serial_no="2906811111111111111111111111111111111111", cert_dir="/tmp/p1/apiclient_key.pem",
                      notify_url="http://www.baidu.com/")

weixinpay.unified_order(order_id="20220623170034664189", openid="ofJ2B1q6_pU11111111111111111", amount=100,
                        desc="test")
复制代码

 

解密

算法接口的细节,可以参考RFC 5116

大部分编程语言(较新版本)都支持了AEAD_AES_256_GCM 。开发者可以参考下列的示例,了解如何使用您的编程语言实现解密。

 

复制代码
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64

def decrypt(nonce, ciphertext, associated_data):
    key = "Your32Apiv3Key"

    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)
复制代码

 

posted on   星河赵  阅读(2595)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示