从去年底开始,下决心自己写代码来搞定自已策划的微信小程序”来推鉴--投融资项目推荐服务平台“后,微信支付就成为挡在前面的一座大山。毕竟是从一个从没开发过一个程序的基本零基础,到要真正上线一个能商业运行的微信小程序DEMO,在这之间用了半年多的时间,不知跨过了多少大山:html、CSS、JavaScript、Vue、Uniapp、Python、Django、MySQL、Redis、Nginx、Git 、Docker。 在开发微信支付上花的时间算是最多的之一了。官方的指南没从小白的角度出发,写的不够清晰易懂,不得以百度了大量相关文章并加上自己理解后,完成了微信支付V3的主要流程。下面列出基本代码,希望能给有需要的人一些帮助,如有不对的地方也请大神指正。
一、引入模块
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.core.cache import cache
import requests,hashlib,datetime,json,time,random,string
from functools import wraps
#用到的关于支付的主要模块
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 XXX import settings
from XXXX.models import XXX、XXX(各数据表)
from XXX.api.utils import tokenAuthentication (定义了一个装饰器,在前端调用API时检查一下token)
二、微信支付统一下单(JSAPI支付)(微信小程序也是用这个)
@api_view(['POST'])
@tokenAuthentication
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": "xxx",
"out_trade_no": transactionNo,
"notify_url": "https://XXXXX/payNotify", #后端接收回调通知的接口
"amount": {"total": price*100, "currency": "CNY"}, #正式上线price要*100,微信金额单位为分。
"payer": {"openid": userOpenid},
}
data=json.dumps(body)
#4.定义生成签名的函数
def get_sign(sign_str):
rsa_key = RSA.importKey(open('XXX/cert/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) #获取预支付交易会话标识(prepay_id)
#11.应答签名验证
wechatpaySerial=response.headers['Wechatpay-Serial'] #获取HTTP头部中包括回调报文的证书序列号
wechatpaySignature=response.headers['Wechatpay-Signature'] #获取HTTP头部中包括回调报文的签名
wechatpayTimestamp=response.headers['Wechatpay-Timestamp'] #获取HTTP头部中包括回调报文的时间戳
wechatpayNonce=response.headers['Wechatpay-Nonce'] #获取HTTP头部中包括回调报文的随机串
#11.1.获取微信平台证书 (等于又把前面的跑一遍,实际上应是获得一次证书就存起来,不用每次都重新获取一次)
url2 = "https://api.mch.weixin.qq.com/v3/certificates"
#11.2.生成证书请求随机串
random_str2 = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
#11.3.生成证书请求时间戳
time_stamps2 = str(int(time.time()))
#11.4.生成请求证书的签名串
data2=""
sign_str2 = f"GET\n{'/v3/certificates'}\n{time_stamps2}\n{random_str2}\n{data2}\n"
#11.5.生成签名
sign2 = get_sign(sign_str2)
#11.6.生成HTTP请求头
headers2 = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'WECHATPAY2-SHA256-RSA2048 '+f'mchid="{settings.mchId}",nonce_str="{random_str2}",signature="{sign2}",timestamp="{time_stamps2}",serial_no="{settings.serialNo}"'
}
#11.7.发送请求获得证书
response2 = requests.get(url2,headers=headers2) #只需要请求头
cert=response2.json()
#11.8.证书解密
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)
nonce = cert["data"][0]['encrypt_certificate']['nonce']
ciphertext = cert["data"][0]['encrypt_certificate']['ciphertext']
associated_data = cert["data"][0]['encrypt_certificate']['associated_data']
serial_no = cert["data"][0]['serial_no']
certificate=decrypt(nonce,ciphertext,associated_data)
#11.9签名验证
if (wechatpaySerial== serial_no): #应答签名中的序列号同证书序列号应相同
print ('serial_no match')
def verify(data,signature): #验签函数
key=RSA.importKey(certificate) #这里直接用了解密后的证书,但没有去导出公钥,似乎也是可以的。怎么导公钥还没搞懂。
verifier=pkcs1_15.new(key)
hash_obj=SHA256.new(data.encode('utf8'))
return verifier.verify(hash_obj,base64.b64decode(signature))
data3=f"{wechatpayTimestamp}\n{wechatpayNonce}\n{response.text}\n"
try:
verify(data3,wechatpaySignature)
print('The signature is valid.')
#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)
except (ValueError, TypeError):
print('The signature is invalid.')
return Response ({'msg':"payfail"})
待续未完....
来推鉴微信公众号