微信支付V3版本的Python使用方法
微信支付自推出V3版本接口以来,体验下来确实比V2有了很大的提升,但介于其官方没有提供Python版本的SDK,故在此记录通过Python3来使用微信支付V3的主要过程。
1.搞清楚自己的身份
这一步看似可以忽略,但也需要仔细,笔者之前就犯了一个错,看了不对应的文档。 微信支付的商家有两种身份模式:普通商户、服务商。
一般的大部分商户都是属于普通商户,只有品牌商等才为服务商,并且两者身份不能互换。 普通商户的API文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml 服务商API文档:https://pay.weixin.qq.com/wiki/
2.配置阶段
需要在商户端界面申请API证书和配置APIv3密钥,次过程可以参考每一个步骤右侧的查看指引按钮。
3.构造签名
V3版本的接口调用需要对请求进行签名,参考https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml 根据官方的文档可以按此步骤生成请求签名。
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)
分类:
Pytorch
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了