微信APP支付V3版本签名 && APP下单接口Python的django框架实现

问题背景

最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程、踩坑过程与最终具体实现。

微信支付APP下单流程

根据微信官方文档: 开发指引-APP支付 | 微信支付商户平台文档中心

开发指引-APP支付 | 微信支付商户平台文档中心

下单流程:

和支付宝不同,微信多了一个预付单的概念,这里把APP下单实际分为四大部分,其中包含请求微信后端需要的首次签名和需要返回给APP的二次支付信息签名--这里踩一个小坑,流程图中并没把第二次签名支付信息需要返回给APP的步骤画出来(即下面的步骤6.5),因而一开始误以为只需要返回prepay_id给客户端,导致校验失败。
一. 对应步骤1~4,APP 请求业务后端,业务后台进行V3签名后,请求微信后端生成预付单prepay_id
二. 对应步骤5~6.5,业务后端收到微信后端返回prepay_id,将支付相关参数打包进行二次签名后返回给APP,这里相比流程图多了一个6.5--即业务后端返回签名支付信息到APP
三. 对应步骤7~18,APP收到业务后端返回签名支付信息后调起SDK发起支付请求,收到同步消息结果通知

实现方法:

这是我实际的商品购买的业务代码,用户需要登录授权之后,在下单是把用户的openid和商品价格商品描述发送给后端。

需要安装

wechatpayv3
pip install wechatpayv3

我封装好的支付方法: Pay.py

import json
from wechatpayv3 import WeChatPay, WeChatPayType
from utils.pay.config import *
class Pay():
    def __init__(self):
        """
        :param wechatpay_type: 微信支付类型,示例值:WeChatPayType.MINIPROG
        :param mchid: 直连商户号,示例值:'1230000109'
        :param private_key: 商户证书私钥,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...'
        :param cert_serial_no: 商户证书序列号,示例值:'444F4864EA9B34415...'
        :param appid: 应用ID,示例值:'wxd678efh567hg6787'
        :param apiv3_key: 商户APIv3密钥,示例值:'a12d3924fd499edac8a5efc...'
        :param notify_url: 通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php'
        :param cert_dir: 平台证书存放目录,示例值:'/server/cert'
        :param partner_mode: 接入模式,默认False为直连商户模式,True为服务商模式
        :param proxy: 代理设置,示例值:{"https": "http://10.10.1.10:1080"},没有就是None
        :param timeout: 超时时间,示例值:(10, 30), 10为建立连接的最大超时时间,30为读取响应的最大超时实践
        """
        self.wxpay = WeChatPay(
            wechatpay_type=WeChatPayType.MINIPROG,
            mchid=MCHID,
            private_key=PRIVATE_KEY,
            cert_serial_no=CERT_SERIAL_NO,
            apiv3_key=APIV3_KEY,
            appid=APPID,
            notify_url=NOTIFY_URL,
            cert_dir=CERT_DIR,
            partner_mode=PARTNER_MODE,
            proxy=PROXY,
            timeout=TIMEOUT,
        )
    def pay(self,openid,price,nonce_str,description):
        """
        :param openid: 微信用户的唯一标识
        :param price: 该订单的价格
        :param nonce_str: 请求随机串nonce_str,与签名使用的随机字符串值仙童
        :param description: 商户证书序列号,示例值:'444F4864EA9B34415...'

        return prepay_id : 【预支付交易会话标识】 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
        """
        code, message = self.wxpay.pay(
            description=description,
            out_trade_no=nonce_str,
            amount={'total': price},
            payer={'openid': openid}
        )
        # print('code: %s, message: %s' % (code, message))
        message = json.loads(message)
        print(message)
        return message["prepay_id"]

    def sign(self,prepay_id, timeStamp, nonce_str):
        """
        :param prepay_id: 预支付交易会话标识
        :param price: 该订单的价格
        :param nonce_str: 请求随机串nonce_str,与签名使用的随机字符串值仙童
        :param description: 商户证书序列号,示例值:'444F4864EA9B34415...'

        return prepay_id : 【预支付交易会话标识】 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
        """

        # :微信支付订单采用RSAwithSHA256算法时,示例值: ['wx888', '1414561699', '5K8264ILTKCH16CQ2502S....','prepay_id=wx201410272009395522657....']
        sign = [APPID, timeStamp, nonce_str, f'prepay_id={prepay_id}']
        # print(self.wxpay.sign(sign))
        paySign = self.wxpay.sign(sign)

        return {
            "timeStamp": timeStamp,
            "nonceStr": nonce_str,
            "package": f"prepay_id={prepay_id}",
            "signType": "RSA",
            "paySign": paySign
        }
    def decrypt_callback(self,headers, body):
        """
        :param headers:
        :param body:

        return prepay_id
        """
        return self.wxpay.decrypt_callback(headers, body)


在pay方法中需要构造对微信官方的请求:

JSAPI下单(可以看里面官方需要的必须数据:)

sign方法中是对应答prepay_id做签名处理:

如何生成请求签名(官方)

并返回前端需要的对接官方接口:wx.requestPayment(Object object)的请求数据:

decrypt_callback方法是对回调的通知接口,用于接收微信官方的响应,判断支付是否成功

django框架下实现代码:

config.py配置文件代码:

# 微信支付商户号(直连模式)或服务商商户号(服务商模式,即sp_mchid)
MCHID = '***************'

# 商户证书私钥
with open('utils/zhi_cert/apiclient_key.pem') as f:
    PRIVATE_KEY = f.read()

# 商户证书序列号
CERT_SERIAL_NO = '***************'

# API v3密钥, https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml
APIV3_KEY = '***************'

# APPID,应用ID或服务商模式下的sp_appid
APPID = '***************'
# 写自己小程序的密钥
AppSecret = "***************"

# 回调地址,也可以在调用接口的时候覆盖
NOTIFY_URL = 'http://63m4gj.natappfree.cc/front-lp/wechatnotify'
# 微信支付平台证书缓存目录,初始调试的时候可以设为None,首次使用确保此目录为空目录。
CERT_DIR = './zhijiao/cert'
# 接入模式:False=直连商户模式,True=服务商模式。
PARTNER_MODE = False
# 代理设置,None或者{"https": "http://10.10.1.10:1080"},详细格式参见[https://requests.readthedocs.io/en/latest/user/advanced/#proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies)
PROXY = None

# 请求超时时间配置
TIMEOUT = (10, 30) # 建立连接最大超时时间是10s,读取响应的最大超时时间是30s

然后接口代码实现与前端请求响应代码:url.py

from django.urls import path
from appFrontLP.views import *
from django.conf.urls.static import static
urlpatterns=[
    path("wechatnotify", WeChatNotifyView.as_view()),#微信支付回调通知
    path('wechatpay', WeChatPayView.as_view()),#支付

]

视图函数view.py方法

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

        openid = request.data.get('openid')
        price = int(request.data.get('price'))
        description = request.data.get('description')
        nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
        current_time = datetime.now()
        current_date = int(datetime.timestamp(current_time))
        pay = Pay()
        #下单
        prepay_id = pay.pay(openid,price,nonce_str,description)
        #吊起支付
        order = pay.sign(prepay_id,f"{current_date}",nonce_str)
        return Response(order)

class WeChatNotifyView(APIView,Cursor):
    def post(self, request):
        headers = {
            'Wechatpay-Signature': request.META.get('HTTP_WECHATPAY_SIGNATURE'),
            'Wechatpay-Timestamp': request.META.get('HTTP_WECHATPAY_TIMESTAMP'),
            'Wechatpay-Nonce': request.META.get('HTTP_WECHATPAY_NONCE'),
            'Wechatpay-Serial': request.META.get('HTTP_WECHATPAY_SERIAL')
        }
        result = Pay().decrypt_callback(headers=headers, body=request.body)
        .................
        return Response({"status": 200})

下面是前端uniapp的代码:

	// 表单提交
	const formSub = async () => {
		uni.showLoading({
			title:'支付中',
			icon:'none'
		})
		// 实际支付等于 总价(all_cost)- 折扣金额(deduction) = 实际支付(actual_pay)
		orderData.value.actual_pay = orderData.value.all_cost - orderData.value.deduction
		console.log('订单表单',orderData.value);
		const res = await postApi.api('/order', orderData.value);
		id.value = res.data.order_id
		if (res.data.status == 200) {
			// 表单
			const Form = {
				openid: orderData.value.openid,
				price: orderData.value.actual_pay,
				description: '测试',
				recharge_type: '1'
			}
			
			console.log('支付表单',Form);
			const payRes = await postApi.api('/wechatpay', Form)
			console.log('支付参数', payRes);
			uni.hideLoading()
			if(payRes.statusCode == 200){
				uni.requestPayment({
					provider: 'wxpay',
					// orderInfo: '1',
					timeStamp: String(payRes.data.timeStamp), // 时间戳
					nonceStr: payRes.data.nonceStr, // 随机字符串
					package: payRes.data.package,
					signType: payRes.data.signType, // 签名算法
					paySign: payRes.data.paySign, // 签名
					success: async(res) => {
						uni.showLoading({
							title:'订单提交中',
							icon:'none'
						})
						console.log(res);
						
						const wechatnotifyForm = {
							order_id:id.value,
							count:orderData.value.count
						}
						console.log('回调表单',wechatnotifyForm);
						const payRes = await postApi.api('/wechatnotify',wechatnotifyForm )
						console.log('回调成功',payRes);
						// 成功带参跳转ok
						uni.hideLoading()
						uni.navigateTo({
							url:'/pages/myself/sonMoreGoods?orderId=' + id.value
						})
					},
					fail: (err) => {
						console.log(err);
						// 失败带参跳转fail cancel
						uni.showToast({
							title:'支付失败',
							icon:'none'
						})
						uni.navigateTo({
							url:'/pages/myself/sonMoreGoods?orderId=' + id.value
						})
					},
				})
			}else{
				uni.showToast({
					title: '订单异常',
					icon: 'none'
				})
			}
		} else {
			uni.showToast({
				title: '订单异常',
				icon: 'none'
			})
		}

	};

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现微信支付v3的django库有很多,其中比较常用的是wechatpy和weixin-pay,下面分别介绍一下。 1. wechatpy wechatpy是一个Python微信开发库,支持微信公众号、小程序、企业号等平台的开发,同时也支持微信支付v3。使用wechatpy可以方便地实现微信支付v3接口调用和签名验证等功能。 首先,需要在微信商户平台上创建商户号,并获取商户证书,包括apiclient_key.pem、apiclient_cert.pem、wechat_pay_cert.pem三个文件。将这三个文件放置在django项目的某个目录下,然后在settings.py文件中添加以下配置: ``` WECHAT_PAY = { 'appid': '微信公众平台appid', 'mch_id': '商户号', 'mch_key': '商户支付密钥', 'apiclient_key_path': 'apiclient_key.pem证书路径', 'apiclient_cert_path': 'apiclient_cert.pem证书路径', 'wechat_pay_cert_path': 'wechat_pay_cert.pem证书路径', 'notify_url': '微信支付结果通知地址', } ``` 接下来,可以在views.py中实现微信支付的业务逻辑,比如创建微信支付订单、查询订单状态等。具体实现方式可以参考wechatpy的官方文档。 2. weixin-pay weixin-pay是另一个支持微信支付v3Python库,使用方法与wechatpy类似。使用weixin-pay需要在微信商户平台上创建商户号,并获取商户密钥和证书。将商户证书放置在django项目的某个目录下,然后在settings.py文件中添加以下配置: ``` WEIXIN_PAY = { 'appid': '微信公众平台appid', 'mch_id': '商户号', 'mch_key': '商户支付密钥', 'cert_path': 'apiclient_cert.pem证书路径', 'key_path': 'apiclient_key.pem证书路径', 'notify_url': '微信支付结果通知地址', } ``` 接下来,可以在views.py中实现微信支付的业务逻辑,具体实现方式可以参考weixin-pay的官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值