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

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐