python的django环境下微信支付v3的主要流程环节代码。也还有验签部份的缺漏处的请教大神。
发布于 4 年前 作者 xuexiuying 529 次浏览 来自 分享
研究了下python的django环境下微信支付v3的主要开发环节,并成功实现微信支付和微信企业提现。里面还是有很多细节,官方的指南没从小白的角度出发,写的不够清晰。希望给有用的人一些帮助。

虽然可用,但也还有支付验签环节没完全搞懂,请教有开发过的大神。

直接上代码:

from rest_framework.decorators import api_view
from rest_framework.response import Response
import requests,hashlib,datetime,json,time,random,string

#用到的关于支付的主要模块
from Cryptodome.PublicKey import RSA
from base64 import b64encode
from Cryptodome.Signature import pkcs1_15
from Cryptodome.Hash import SHA256
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
from xml.etree import ElementTree as ET

from XXXX(app名称) import settings  #各类用到的参数存在settings.py 中
from XXXX(app名称).models import XXXXX(各数据表)

#微信支付统一下单
[@api_view](/user/api_view)(['POST'])
def payFromWx(request):    
    price=request.data['price']    
    userId=request.data['userId']    
    up_time=datetime.datetime.now()
    #1.以交易日期生成交易号
    transactionNo=str(up_time).replace('.''').replace('-''').replace(':''').replace(' ''')
    #2.生成新交易记录
    newTransaction=TransactionLogs.objects.create(
        transaction_no =transactionNo,
        transaction_status_id=1,
        user_id =userId,
        transaction_type_id=1,
        transaction_amount=price,
        created_at=up_time)
    #3.生成统一下单的报文body    
    user=Users.objects.get(user_id=userId)
    userOpenid=user.openid
    url='https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi'
    body={
        "appid": settings.AppId, 
        "mchid": settings.mchId,
        "description""购买",
        "out_trade_no": transactionNo,   
        "notify_url""https://api.XXX.com/api/payNotify", 
        "amount": {"total": price*100"currency""CNY"}, 
        "payer": {"openid":userOpenid },                 
    }    
    data=json.dumps(body)
    #4.定义生成签名的函数
    def get_sign(sign_str):
        rsa_key = RSA.importKey(open('XXXX/apiclient_key.pem').read())(#加载证书)
        signer = pkcs1_15.new(rsa_key)
        digest = SHA256.new(sign_str.encode('utf8'))
        sign = b64encode(signer.sign(digest)).decode('utf-8')
        return sign
    #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"POST\n{'/v3/pay/transactions/jsapi'}\n{time_stamps}\n{random_str}\n{data}\n"
    #8.生成签名
    sign = get_sign(sign_str)
    #9.生成HTTP请求头
    headers = {
        'Content-Type''application/json',
        'Accept''application/json', 
        'User-Agent''*/*',
        'Authorization':'WECHATPAY2-SHA256-RSA2048 '+f'mchid="{settings.mchId}",nonce_str="{random_str}",signature="{sign}",timestamp="{time_stamps}",serial_no="{settings.serialNo}"'       
    }
    #10.发送请求获得prepay_id
    response = requests.post(url,data=data,headers=headers) #要加请求头
    #11.应答签名验证
        签名验证还没搞懂,有开发过的大神请指教。

    #12.生成调起支付API需要的参数并返回前端
    res = {
        'timeStamp':time_stamps,
        'nonceStr':random_str,
        'package':'prepay_id='+response.json()['prepay_id'],            
        'paySign':get_sign(f"{settings.AppId}\n{time_stamps}\n{random_str}\n{'prepay_id='+response.json()['prepay_id']}\n"),
    }
    return Response (res)

#前端js
async payFromWx () {
	const payInfo =await this.$myRequest({
		url:'/api/payFromWx',
		method:'POST',
		data:{
			price:this.price,
			userId:this.userInfo.userId,										
			},
		})
	wx.requestPayment({
		timeStamp: payInfo.data.timeStamp,
		nonceStr: payInfo.data.nonceStr,
		package: payInfo.data.package,
		signType: "RSA",
		paySign: payInfo.data.paySign,
		success (res) {
			uni.showToast({
				title: '微信支付成功,预计5分钟内生效',
				icon: 'none'
			})
			this.getUserInfo()					
			},
			fail (res) {
			 console.log('fail', res)
			}
		})			
}	
完成调起支付

#自己的服务器notify_url收到支付通知后的处理
[@api_view](/user/api_view)(['POST'])
def payNotify(request):
    #1.获得支付通知的参数
    body=request.data    
    nonce=request.data['resource']['nonce']
    ciphertext=request.data['resource']['ciphertext']
    associated_data=request.data['resource']['associated_data']
    up_time=datetime.datetime.now()
    #2.获得支付通知HTTP头参数
    Wechatpay_Serial=request.META['HTTP_WECHATPAY_SERIAL']
    Wechatpay_Timestamp=request.META['HTTP_WECHATPAY_TIMESTAMP']
    Wechatpay_Nonce=request.META['HTTP_WECHATPAY_NONCE']
    Wechatpay_Signature=request.META['HTTP_WECHATPAY_SIGNATURE']
    print('Wechatpay_Signature',Wechatpay_Signature)   
    #6.回调报文解密后取得定单号
    def decrypt(nonce, ciphertext, associated_data):
        key = settings.apiV3Key
        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)
    payment=decrypt(nonce, ciphertext, associated_data)
    payment=eval(payment.decode('utf-8'))
    transactionNo=payment['out_trade_no']
    #7.回调报文签名验证
    #7.1.获取平台证书
    url = "https://api.mch.weixin.qq.com/v3/certificates"
    #7.2.定义生成签名的函数
    def get_sign(sign_str):
        rsa_key = RSA.importKey(open('ltjian/static/apiclient_key.pem').read())
        signer = pkcs1_15.new(rsa_key)
        digest = SHA256.new(sign_str.encode('utf8'))
        sign = b64encode(signer.sign(digest)).decode('utf-8')
        return sign   
    #7.3.生成请求随机串
    random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
    #7.4.生成请求时间戳
    time_stamps = str(int(time.time()))
    #7.5.生成签名串
    data=""
    sign_str = f"GET\n{'/v3/certificates'}\n{time_stamps}\n{random_str}\n{data}\n"
    #7.6.生成签名
    sign = get_sign(sign_str)
    #7.7.生成HTTP请求头
    headers = {
        'Content-Type''application/json',
        'Accept''application/json',         
        'Authorization''WECHATPAY2-SHA256-RSA2048 ' + f'mchid="{settings.mchId}",nonce_str="{random_str}",signature="{sign}",timestamp="{time_stamps}",serial_no="{settings.serialNo}"'       
    }
    #7.8.发送请求获得证书
    response = requests.get(url,headers=headers)
    cert=response.json()
    #7.9.证书解密
    nonce = cert["data"][0]['encrypt_certificate']['nonce']
    ciphertext = cert["data"][0]['encrypt_certificate']['ciphertext']
    associated_data = cert["data"][0]['encrypt_certificate']['associated_data']
    certificate=decrypt(nonce, ciphertext, associated_data)
    print('certificate',certificate)   
    #7.10.签名验证
    签名验证还没搞懂,所以整了第7部份一大段,然并没有用。有开发过的大神请指教。

    #8.获得回调报文中交易号后修改已支付订单状态    
    transactionlog=TransactionLogs.objects.get(transaction_no=transactionNo)
    transactionlog.transaction_status_id=2
    transactionlog.save()

    return Response ({"code""SUCCESS","message""成功"})#指定格式,返回给微信服务器

#企业付款至零钱 还是V2版
[@api_view](/user/api_view)(['POST'])
def withdraw(request):
    amount=int(request.data['amount'])    
    userId=request.data['userId']
    up_time=datetime.datetime.now()
    #1.以交易日期生成交易号
    transactionNo=str(up_time).replace('.''').replace('-''').replace(':''').replace(' ''')
    #2.生成新交易记录
    newTransaction=TransactionLogs.objects.create(
        transaction_no =transactionNo,
        transaction_status_id=1,
        user_id =userId,
        transaction_type_id=2,
        transaction_amount=amount,
        created_at =up_time)
    #3.生成请求随机串
    random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))    
    #4.生成企业付款的报文body    
    user=Users.objects.get(user_id=userId)
    userOpenid=user.openid
    userNickName=user.user_wxNickName
    url='https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'
    body={
        "mch_appid":settings.AppId, 
        "mchid":settings.mchId,
        "nonce_str":random_str,
        "partner_trade_no":transactionNo,
        "openid":userOpenid,
        "check_name":"NO_CHECK",
        "re_user_name":userNickName,
        "amount":amount*100, 
        "desc":"用户提现",        
    }   
    #4.生成签名   
    stringSignTemp="&".join(["{0}={1}".format(k,body[k]) for k in sorted(body)]+["{0}={1}".format("key",settings.apiKey)])   
    sign=hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
    body["sign"]=sign        
    #5.转成XML
    xml_string="{0}".format("".join(["<{0}>{1}".format(k,v) for k,v in body.items()]))   
    #6.发送请求 要加上两个下载的证书   
    response = requests.post(url,data=xml_string.encode("utf-8"),cert=('XXX/apiclient_cert.pem','XXX/apiclient_key.pem'))
    #7.把response从xml转换为字典
    root=ET.XML(response.content.decode("utf-8"))
    result={child.tag:child.text for child in root}
    #8.修改订单状态
    transactionNo=result['partner_trade_no']
    transactionlog=TransactionLogs.objects.get(transaction_no=transactionNo)
    transactionlog.transaction_status_id=2
    transactionlog.save()    
    return Response ({'msg':"withdrawSuccess"}) 

2 回复

创建订单为啥要和支付放在一个函数里,如果调起不成功,岂不是点一次创建一条订单数据,建议调起支付前创建好订单,再传数据调起支付

验签的时候,获取头部信息,文档有说明。商户先从应答中获取以下信息。

HTTP头Wechatpay-Timestamp 中的应答时间戳。 HTTP头Wechatpay-Nonce 中的应答随机串 应答主体(response Body)可以打印出头部信息进行获取,根据文档,对数据进行公钥验签解密

回到顶部