微信小程序实现微信支付功能fastapi【前后端实现】
·
import datetime
import json
import os
import random
import string
import time
import OpenSSL
import base64
import requests
from urllib.parse import urlparse
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# V3版支付
class WxPayV3(object):
basedir = os.path.abspath(os.path.dirname(__file__)) # 获取当前目录
def __init__(self, ignore_resp_sign=False):
"""
:param ignore_resp_sign: 是否忽略应答验签(用于第一次缓存证书)
"""
self.app_id = 'wx14bcda174b'
self.mch_id = '1626238'
self.api_v3_key = 'Hyper_20220344566244_hyper'
self.serial_no = '4F74DF09B282ED821206A' # 证书序列号(微信支付商户平台获取)
self.ignore_resp_sign = ignore_resp_sign
self.certificates_path = self.basedir + '/certificates.json'
self.get_certificates_from_wx()
# 检查证书是否需要更新
# if not os.path.exists(self.certificates_path):
# # 如果证书文件不存在,则获取证书
# print("证书文件不存在,则获取证书")
# else:
# # 判断证书是否过期,如果过期则更新证书
# print("判断证书是否过期,如果过期则更新证书")
# with open(self.certificates_path, 'r') as f:
# content = f.read()
# print(content)
# certificates = json.loads(content)
# for certificate in certificates:
# expiry_time = certificate.get('expire_time')
# if expiry_time and datetime.datetime.now() > datetime.datetime.strptime(expiry_time,'%Y-%m-%dT%H:%M:%SZ'):
# print("证书过期,更新证书")
# self.get_certificates_from_wx() # 证书过期,更新证书
# break
# 支付签名
@staticmethod
def sign(s):
# 这里的key为示例
# api_client_key = ("-----BEGIN PRIVATE KEY-----\n"
# "MIIEvQIBADANBgkqhkiG9w0BAQEFAAIBAQDAPM45XP7PC6qe\n"
# "MIIEvQIBADANBgkqhkiG9w0BAgEAAoIBAQDAP7PC6qe\n"
# "Kkh/7UGUD/hxkPwNB2PKAms=\n"
# "-----END PRIVATE KEY-----")
current_dir = os.path.dirname(os.path.abspath(__file__))
# 构建 apiclient_key.pem 文件的完整路径
pem_file_path = os.path.join(current_dir, 'apiclient_key.pem')
# 打开文件
with open(pem_file_path, 'r') as f:
# 这里要注意的秘钥只能有三行
# -----BEGIN PRIVATE KEY-----
# ******************秘钥只能在一行,不能换行*****************
# -----END PRIVATE KEY-----
api_client_key = f.read()
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, api_client_key)
signature = base64.b64encode(OpenSSL.crypto.sign(pkey=pkey, data=s.encode(), digest='SHA256'))
return signature.decode()
# 请求签名
def _auth(self, req):
data = req.method + '\n'
parsed = urlparse(req.url)
data += parsed.path
if parsed.query:
data += "?" + parsed.query
data += '\n'
timestamp = str(int(time.time()))
data += timestamp + '\n'
nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 32))
data += nonce_str + '\n'
if req.data:
data += req.data
data += '\n'
signature = self.sign(data)
authorization = ('WECHATPAY2-SHA256-RSA2048 '
'mchid="{0}",nonce_str="{1}",'
'signature="{2}",timestamp="{3}",'
'serial_no="{4}"').format(self.mch_id,
nonce_str,
signature,
timestamp,
self.serial_no)
req.headers["Authorization"] = authorization
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
req.headers['User-Agent'] = 'requests ' + requests.__version__
r = req.prepare()
s = requests.Session()
resp = s.send(r, timeout=2)
# 验签
if not self.ignore_resp_sign:
nonce = resp.headers.get('Wechatpay-Nonce')
signature = resp.headers.get('Wechatpay-Signature')
serial = resp.headers.get('Wechatpay-Serial')
timestamp = resp.headers.get('Wechatpay-Timestamp')
body = resp.text
cer = self.get_certificate_by_serial_no(serial)
ret = self.resp_sign(timestamp=timestamp,
nonce=nonce,
body=body,
cer=cer,
signature=signature)
assert ret is None, "resp sign error"
return resp
# Native下单
def native_pay(self, total, out_trade_no, attach=None):
url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native'
notify_url = 'https://pay.weixin.com/notify'
data = dict(appid=self.app_id,
mchid=self.mch_id,
description="测试native支付",
notify_url=notify_url,
out_trade_no=out_trade_no,
amount=dict(total=total,
currency='CNY'))
if attach is not None:
data['attach'] = attach
req = requests.Request(method="POST", url=url, data=json.dumps(data))
response = self._auth(req=req)
result = response.json()
return result['code_url']
@staticmethod
def gen_out_trade_no(trade_type="jsapi"):
out_trade_no = "wx" + trade_type + datetime.datetime.now().strftime("%Y%m%d%H%M%S")
out_trade_no += str(random.randint(1000000, 9999999))
return out_trade_no
# JSAPI下单
def jsapi_pay(self, total, out_trade_no, openid, attach=None):
url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi'
notify_url = 'https://pay.weixin.com/notify'
data = dict(appid=self.app_id,
mchid=self.mch_id,
description="测试",
notify_url=notify_url,
out_trade_no=out_trade_no,
amount=dict(total=total,
currency='CNY'),
payer=dict(openid=openid))
if attach is not None:
data['attach'] = attach
req = requests.Request(method="POST", url=url, data=json.dumps(data))
response = self._auth(req=req)
result = response.json()
print("prepay_id##############",result)
return result['prepay_id']
# 查询订单
def query_order(self, out_trade_no=None, transaction_id=None):
if out_trade_no is None and transaction_id is None:
raise ValueError('Param Error')
if out_trade_no is not None:
url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{0}'.format(out_trade_no)
else:
url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/id/{0}'.format(out_trade_no)
url += '?' + 'mchid=' + self.mch_id
req = requests.Request(method="GET", url=url)
response = self._auth(req=req)
result = response.json()
return result
# 获取微信平台证书并缓存
def get_certificates_from_wx(self):
print("获取微信平台证书并缓存")
# TODO 这里实现非常简陋,鉴于V3版本证书会动态更新建议使用中控服务统一获取和安全存储证书(惰性更新也可)
url = 'https://api.mch.weixin.qq.com/v3/certificates'
req = requests.Request(method="GET", url=url)
response = self._auth(req=req)
result = response.json()
with open(self.certificates_path, 'w+') as f:
print("###########",json.dumps(result['data']))
f.write(json.dumps(result['data']))
return result
def get_certificate_by_serial_no(self, serial_no):
if not os.path.exists(self.certificates_path):
# self.get_certificates_from_wx()
raise FileNotFoundError('请先下载证书进行缓存')
with open(self.certificates_path, 'r') as f:
content = f.read()
certificates = json.loads(content)
nonce, ciphertext, associated_data = None, None, None
for certificate in certificates:
if certificate['serial_no'] == serial_no:
nonce = certificate['encrypt_certificate']['nonce']
print("$$$$$$$$$$$$$$$$$$$$$$$",nonce)
ciphertext = certificate['encrypt_certificate']['ciphertext']
print("****************************************************************", ciphertext)
associated_data = certificate['encrypt_certificate']['associated_data']
break
if ciphertext is None:
raise ValueError('certificate not found')
cer = self.decrypt_aes_gcm(nonce=nonce,
ciphertext=ciphertext,
associated_data=associated_data)
return cer
def decrypt_aes_gcm(self, nonce, ciphertext, associated_data):
aes_gcm = AESGCM(self.api_v3_key.encode())
plaintext = aes_gcm.decrypt(nonce=nonce.encode(),
associated_data=associated_data.encode(),
data=base64.b64decode(ciphertext))
return plaintext
# 应答验签
@staticmethod
def resp_sign(timestamp, nonce, body, cer, signature):
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cer)
s = '{0}\n{1}\n{2}\n'.format(timestamp, nonce, body)
signature = base64.b64decode(signature)
try:
OpenSSL.crypto.verify(cert=cert, signature=signature, data=s, digest='SHA256')
except Exception as e:
return e.__str__()
return None
# JsApi签名(未验证)
def jsapi_sign(self, prepay_id):
timestamp = int(time.time())
nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 32))
package = "prepay_id=" + prepay_id
sign_type = 'RSA'
s = "{0}\n{1}\n{2}\n{3}\n".format(self.app_id, timestamp, nonce_str, package)
pay_sign = self.sign(s)
obj = dict(app_id=self.app_id,
timestamp=timestamp,
nonce_str=nonce_str,
package=package,
sign_type=sign_type,
pay_sign=pay_sign)
return obj
路由文件
from fastapi import APIRouter,Body
from fastapi.responses import JSONResponse
from api.tencent.pay import get_order_code
from dao.models_order import orders, goods, OrderStatus, user_goods, goodStatus
from api.tencent.api import WxPayV3
router = APIRouter(prefix='/api/order', tags=['订单模块'])
appid = 'wx14ba174b'
mch_id = '1626338' # 商户号
mch_key = 'Hyper__hyper'
@router.post('/createUserGood', summary='新增用户可以使用的商品或服务权限')
async def createUserGood(body=Body(None)):
try:
userId = body.get('openId')
goodId = body.get('gid')
good = await goods.get(good_id=goodId)
await user_goods.create(user_id=userId, good_id=goodId, good_name=good.good_name)
return JSONResponse(content={"msg": "create user good success", "code": 2000})
except Exception as e:
return JSONResponse(content={"msg": e, "code": 2001})
## 序列号
@router.post('/createOrder', summary='创建订单, 并返回支付参数')
async def createOrder(body=Body(None)):
try:
userId = body.get('openId') # 用户openid
desc = body.get('desc') # 商品描述
total = body.get('total') # 总价格
name = body.get('name') # 商品名称
remark = body.get('remark') # 备注
phone = body.get('phone') # 下单人手机号
goodId = body.get('gid', None) # 商品id
tradeNo = get_order_code()
print('商户订单号', tradeNo)
order = await orders.create(
user_id=userId,
trade_no=tradeNo,
desc=desc,
total_amount=total,
good_name=name,
good_id=goodId,
phone=phone,
remark=remark
)
pay_v3 = WxPayV3()
out_trade_no = pay_v3.gen_out_trade_no(trade_type="jsapi")
print("执行prepay_id")
prepay_id = pay_v3.jsapi_pay(
total=total,
# out_trade_no=get_order_code(),
out_trade_no=out_trade_no,
openid=userId,
)
obj = pay_v3.jsapi_sign(prepay_id=prepay_id)
data = {
"orderId": str(order.order_id),
"tradeNo": str(order.trade_no),
"goodId": order.good_id,
"goodName": order.good_name,
"total": str(order.total_amount),
"status": order.status,
"params": obj
}
return JSONResponse(content={"msg": f"订单已创建,支付参数生成", "code": 2000, "payment": data})
except Exception as e:
return JSONResponse(content={"msg": e, "code": 2001})
@router.get('/updateOrderState', summary='修改订单状态')
async def updateOrderState(no, id, state):
try:
order = await orders.filter(trade_no=no, user_id=id).first()
order.status = state
await order.save()
return JSONResponse(content={"msg": "update order status success", "code": 2000})
except Exception as e:
return JSONResponse(content={"msg": e, "code": 2001})
@router.get('/getGoodList', summary='获取商品列表')
async def getGoodList(id):
try:
userId = id
if not userId:
return JSONResponse(content={"msg": "fail to request", "code": 2001})
count = await goods.all().count()
goodList = await goods.filter(sale_status=goodStatus.NORMAL)
if not goodList:
return JSONResponse(content={"msg": "no find goods", "code": 2001})
list = [
{
"gid": good.good_id,
"name": good.good_name,
"desc": good.good_desc,
"url": good.good_url,
"image": good.good_image,
"price": str(good.good_price)
}
for good in goodList
]
data = {
"list": list,
"total": count
}
return JSONResponse(content={"msg": "success", "code": 2000, "data": data})
except Exception as e:
return JSONResponse(content={"msg": e, "code": 2001})
小程序代码
const app = getApp()
const baseUrl = app.globalData.baseUrl
const mbti = require('../../../utils/mbti.js')
Page({
data: {
openId:null,
phone: null,
goodId: null,
goodName: 'MBTI人格资料',
showModal: false, // 显示控制
hideLoad: false, // 隐藏中加载
rate: 100,
total: 1, // 9.9元
isBuy:false,
goodUrl: null, //商品url
showBtn: false,
triggerPosition: 100, // 设置触发方法的滚动位置
detail: null,
type: false,
isSystemCheck:false,
contentList: [],
goods: {
list: [],
total: 0
}
},
onLoad(options) {
const mbtiName = options.mbti
const type = Boolean(options.type)
type && wx.vibrateShort()
this.getDetail(mbtiName)
const key = mbtiName.toLowerCase()
console.log(mbtiName, key, mbti);
this.setData({
contentList: mbti[key](),
openId: wx.getStorageSync('openId'),
phone: wx.getStorageSync('userInfo')?.phone,
isSystemCheck: app.globalData.isOnLine,
type
})
console.log(this.data.contentList, app.globalData.isOnLine);
},
tip: async function(){
if(!wx.getStorageSync('userInfo')) return wx.navigateTo({ url: '/pages/login/login' });
const message = `你确定需要支付${this.data.total / this.data.rate}元获得该报告吗?`
if(!this.data.isSystemCheck) return this.downLoadAndOpen()
const res = await wx.showModal({
title: `获取提示`,
content: message,
})
if (res.cancel) return
if (!res.confirm) return
this.pay()
},
getGood(){
wx.request({
url: `${baseUrl}/order/getGoodList?id=${this.data.openId}`,
success: res=>{
if(res.statusCode!==200) return
if(res.data.code!==2000) return
const { list, total } = res.data.data
const { goodName, detail } = this.data
const nameOfGood = `${detail?.mbtiName}${goodName.slice(4)}`
const goodInfo = list.find(item=>item.name===nameOfGood)
const goodId = goodInfo.gid
const price = goodInfo.price * this.data.rate
const goodUrl = goodInfo.url
this.setData({"goods.list": list, "goods.total": total, goodId, goodName:nameOfGood, total:price, goodUrl })
console.log(goodId, goodUrl, price, goodInfo);
this.getUserGoods()
}
})
},
onPageScroll(obj) {
const { scrollTop } = obj; // 获取页面垂直滚动的距离
const { triggerPosition } = this.data;
const showBtn = scrollTop >= triggerPosition? true : false
this.setData({ showBtn })
},
back(){
if(this.data.type){
wx.redirectTo({
url: '/pages/mbti/enter/enter',
})
}else{
wx.navigateBack()
}
},
toast(title='', icon='none', duration=1500){
wx.showToast({
title,
icon,
duration
})
},
downLoadAndOpen(){
if(!wx.getStorageSync('userInfo')) return wx.navigateTo({ url: '/pages/login/login' });
wx.showLoading({title:'正在打开'})
// 下载文件
wx.downloadFile({
url: this.data.goodUrl,
success: file_res=>{
const filePath = file_res.tempFilePath
// 打开文件
wx.openDocument({
filePath,
showMenu: true,
success:res=> {
console.log('打开成功文档');
wx.hideLoading()
}
})
}
})
},
// 进入官方档案详情试看
pay:async function(){
const { openId, detail, phone, goodId: gid, total, goodName } = this.data
const params = {
openId,
desc: `${detail.mbtiName}的相关资料`,
name: goodName,
remark: '',
total,
phone,
gid
}
console.log(params, '---订单参数');
wx.request({
url: `${baseUrl}/order/createOrder`,
method: 'POST',
data: params,
success: async res=> {
console.log(res);
if(res.statusCode!==200) return
if(res.data.code!==2000) return
const tradeNo = res.data.payment.tradeNo
let { timestamp,nonce_str, sign_type, pay_sign } = res.data.payment.params
wx.requestPayment({
timeStamp: timestamp + '',
nonceStr: nonce_str,
package: res.data.payment.params.package,
signType: sign_type,
paySign: pay_sign,
success: res=> {
console.log('支付成功', res);
this.toast('支付成功')
const state = 1
wx.request({
url: `${baseUrl}/order/updateOrderState?no=${tradeNo}&id=${openId}&state=${state}`,
success: data=>{
console.log(data);
wx.request({
url: `${baseUrl}/order/createUserGood`,
method: 'POST',
data: { openId, gid },
success: create_res=> {
if(create_res.statusCode!==200) return
if(create_res.data.code!==2000) return
this.setData({isBuy:true})
this.downLoadAndOpen()
}
})
}
})
},
fail: function(res) {
console.log('支付失败', res);
}
});
}
})
},
// 获取用户所拥有全部商品
getUserGoods(){
const openId = wx.getStorageSync('openId')
wx.request({
url: `${baseUrl}/user/getUserGoods?id=${openId}`,
success: res=> {
if(res.statusCode!==200) return
if(res.data.code!==2000) return
const { detail, goodId } = this.data
const { list } = res.data.data
const userGoodIds = list.map(item=>item.gid)
if(userGoodIds.includes(goodId)) this.setData({isBuy:true})
this.setData({ userGoods: list, userGoodIds, detail })
}
})
},
// 获取列表
getDetail(name){
wx.request({
url: `${baseUrl}/mbti/getMbtiDetail?name=${name}`,
success: res=> {
console.log(res);
if(res.statusCode!==200) return
if(res.data.code!==2000) return
const detail = res.data.data
this.setData({detail})
this.getGood()
}
})
},
onShareTimeline() {
return {
title: `一份来自${this.data.detail.mbtiName}的深度分析报告!`,
path: '/pages/mbti/info/info',
}
},
onShareAppMessage() {
return {
title: `一份来自${this.data.detail.mbtiName}的深度分析报告!`,
path: '/pages/mbti/info/info',
}
},
})
Cursor business 5000次高级模型请求
Cursor Pro会员帐号独享合租
欢迎联系 aigeek2024
更多推荐
所有评论(0)