研究了下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
from XXXX(app名称).models import XXXXX(各数据表)
@api_view(['POST'])
def payFromWx(request):
price=request.data['price']
userId=request.data['userId']
up_time=datetime.datetime.now()
transactionNo=str(up_time).replace('.', '').replace('-', '').replace(':', '').replace(' ', '')
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)
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)
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
random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
time_stamps = str(int(time.time()))
sign_str = f"POST\n{'/v3/pay/transactions/jsapi'}\n{time_stamps}\n{random_str}\n{data}\n"
sign = get_sign(sign_str)
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}"'
}
response = requests.post(url,data=data,headers=headers)
签名验证还没搞懂,有开发过的大神请指教。
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)
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)
}
})
}
完成调起支付
@api_view(['POST'])
def payNotify(request):
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()
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)
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']
url = "https://api.mch.weixin.qq.com/v3/certificates"
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
random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
time_stamps = str(int(time.time()))
data=""
sign_str = f"GET\n{'/v3/certificates'}\n{time_stamps}\n{random_str}\n{data}\n"
sign = get_sign(sign_str)
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}"'
}
response = requests.get(url,headers=headers)
cert=response.json()
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部份一大段,然并没有用。有开发过的大神请指教。
transactionlog=TransactionLogs.objects.get(transaction_no=transactionNo)
transactionlog.transaction_status_id=2
transactionlog.save()
return Response ({"code": "SUCCESS","message": "成功"})
@api_view(['POST'])
def withdraw(request):
amount=int(request.data['amount'])
userId=request.data['userId']
up_time=datetime.datetime.now()
transactionNo=str(up_time).replace('.', '').replace('-', '').replace(':', '').replace(' ', '')
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)
random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
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":"用户提现",
}
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
xml_string="{0}".format("".join(["<{0}>{1}".format(k,v) for k,v in body.items()]))
response = requests.post(url,data=xml_string.encode("utf-8"),cert=('XXX/apiclient_cert.pem','XXX/apiclient_key.pem'))
root=ET.XML(response.content.decode("utf-8"))
result={child.tag:child.text for child in root}
transactionNo=result['partner_trade_no']
transactionlog=TransactionLogs.objects.get(transaction_no=transactionNo)
transactionlog.transaction_status_id=2
transactionlog.save()
return Response ({'msg':"withdrawSuccess"})