当前位置: 首页 > news >正文

微信小程序支付(完整版)-ThinkPHP/Uniapp

技术说明

1.前端:uniapp、vue3

2.接口:PHP8、ThinkPHP8、MySQL8.0

3.微信支付- PHP,官方示例文档

4.示例代码的模型及业务自己进行调整,不要一味的复制粘贴!!!

流程说明

1.小程序调用接口--获取拉起支付所用参数,生成订单

2.拉起微信支付

3.支付完成-更改订单状态

参数说明

1.appid - 小程序id

2.mchid -- 商户号ID

3.certificate_serial -- 证书序列号

4.api_v3_key -- 支付密钥(v3)

5.apiclient_key.pem -- 商户API私钥文件,根据微信支付下载器下载即可

6.cert.pem -- 微信支付平台证书文件(注意:此文件必须是手动下载的,具体下载方式下方有说明!!!

其他说明

1.本示例采用微信支付sdk

2.实际情况根据业务进行调整;

3.通知回调(未能正确返回)

4.其他没毛病。

项目示例

1.安装微信支付 wechatpay -- sdk

composer require wechatpay/wechatpay

2.下载微信支付平台证书文件

(1)下载微信支付平台证书下载器

(2)进行详情页(微信支付平台证书下载器)

(3)下载CertificateDownloader.php,点击下方红框,直接下载文件就行,文件位置随便放,只要能用php命令运行就行

(4)下载证书,直接复制下面命令,改参数即可。

        -k 支付密钥(上方参数4)

        -m 商户号(上方参数2)

        -f 商户密钥(上方参数5,需要完整路径)

        -s 证书序列号(上方参数3)

        -o 生成证书地址(需要本地完整路径)

php -f ./CertificateDownloader.php --  -k 4202c8***** -m 16***** -f /****/apiclient_key.pem -s 25***** -o /*****/cert/

3.封装支付类(完整示例如下)

<?phpnamespace app\common\controller;use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;/*** @note 微信支付操作*/
class WechatPay
{protected string $spAppid;  //  小程序appidprotected string $spAppSecret;  //  小程序密钥protected string $merchantId;  //  商户号protected string $certificateSerial;  //  证书序列号protected string $apiV3Key;  //  APIv3密钥protected object $instance;  //  实例protected string $merchantPrivateKeyFilePath;public function __construct(){$this->spAppid = config('wechat.sp.appid');$this->spAppSecret = config('wechat.sp.secret');$this->merchantId = config('wechat.pay.mchid');$this->certificateSerial = config('wechat.pay.certificate_serial');$this->apiV3Key = config('wechat.pay.api_v3_key');// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名$this->merchantPrivateKeyFilePath = root_path() . 'wxcert/apiclient_key.pem';if (!file_exists($this->merchantPrivateKeyFilePath)) throw new \Exception('商户API私钥文件不存在');$merchantPrivateKeyFilePath = 'file://' . $this->merchantPrivateKeyFilePath;$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名$platformCertificateFilePath = root_path() . 'wxcert/cert.pem';if (!file_exists($platformCertificateFilePath)) throw new \Exception('微信支付平台证书文件不存在');$platformCertificateFilePath = 'file://' . $platformCertificateFilePath;$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);// 从「微信支付平台证书」中获取「证书序列号」$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);// 构造一个 APIv3 客户端实例$this->instance = Builder::factory(['mchid' => $this->merchantId,   //  商户号'serial' => $this->certificateSerial,   //「商户API证书」的「证书序列号」'privateKey' => $merchantPrivateKeyInstance,'certs' => [$platformCertificateSerial => $platformPublicKeyInstance,],]);}/*** @note 获取微信支付预交易订单* @param string $openid 用户openid* @param string $out_trade_no 订单号* @param string $notify_url 回调地址* @param float $price 价格* @param string $desc 描述*/public function spPrepayId(string $openid, string $out_trade_no, string $notify_url, float $price = 0.01, string $desc = '订单'){$prepay_id = '';try {$resp = $this->instance->chain('/v3/pay/transactions/jsapi')->post(['json' => ['mchid' => $this->merchantId,'out_trade_no' => $out_trade_no,'appid' => $this->spAppid,'description' => $desc,'notify_url' => $notify_url,'amount' => ['total' => $price * 100,'currency' => 'CNY'],'payer' => ['openid' => $openid]]]);$res = json_decode($resp->getBody());$prepay_id = $res->prepay_id;} catch (\Exception $e) {// 进行错误处理echo $e->getMessage(), PHP_EOL;;if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {$r = $e->getResponse();echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;}echo $e->getTraceAsString(), PHP_EOL;}return $prepay_id;}/*** @note 生成签名* @param string $prepay_id 预交易订单* @param string $nonceStr 随机字符串* @param string $timeStamp 时间戳* @return string*/public function makeSign(string $prepay_id, string $nonceStr, string $timeStamp): string{if (!file_exists($this->merchantPrivateKeyFilePath)) return '';$merchantPrivateKeyFilePath = 'file://' . $this->merchantPrivateKeyFilePath;$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);$params = ['appId' => $this->spAppid,'timeStamp' => $timeStamp,'nonceStr' => $nonceStr,'package' => 'prepay_id=' . $prepay_id,];$params += ['paySign' => Rsa::sign(Formatter::joinedByLineFeed(...array_values($params)),$merchantPrivateKeyInstance), 'signType' => 'RSA'];return $params['paySign'] ?? '';}/*** @note 回调通知,参数解密* @param string $inWechatpaySignature 微信支付平台签名* @param string $inWechatpayTimestamp 微信支付平台时间戳* @param string $inWechatpayNonce 微信支付平台随机串* @param string $inBody 通知内容* @param string $inWechatpaySerial 平台证书序列号* @param string $inRequestID 请求ID* @return array*/public function notifyDecrypt(string $inWechatpaySignature, string $inWechatpayTimestamp, string $inWechatpayNonce, string $inBody, string $inWechatpaySerial, string $inRequestID = ''): array{// 根据通知的平台证书序列号,查询本地平台证书文件,$platformCertificateFilePath = root_path() . 'wxcert/cert.pem';if (!file_exists($platformCertificateFilePath)) throw new \Exception('微信支付平台证书文件不存在');$platformCertificateFilePath = 'file://' . $platformCertificateFilePath;$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);// 检查通知时间偏移量,允许5分钟之内的偏移$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);$verifiedStatus = Rsa::verify(// 构造验签名串Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),$inWechatpaySignature,$platformPublicKeyInstance);if ($timeOffsetStatus && $verifiedStatus) {// 转换通知的JSON文本消息为PHP Array数组$inBodyArray = (array)json_decode($inBody, true);// 使用PHP7的数据解构语法,从Array中解构并赋值变量['resource' => ['ciphertext' => $ciphertext,'nonce' => $nonce,'associated_data' => $aad]] = $inBodyArray;// 加密文本消息解密$inBodyResource = AesGcm::decrypt($ciphertext, $this->apiV3Key, $nonce, $aad);// 把解密后的文本转换为PHP Array数组return (array)json_decode($inBodyResource, true);}return [];}/*** @note 加密消息解密*/public function decryptMsg($encryptedData, $iv, $sessionKey): array|string{$pc = new WxBizDataCrypt($this->spAppid, $sessionKey);$errCode = $pc->decryptData($encryptedData, $iv, $data);if ($errCode == 0) {return $data;}return [];}}

4.封装接口(完整示例如下)

<?phpnamespace app\api\controller\sp;use think\response\Json;class Activity 
{/*** @note 生成订单*/public function prepayId(): void{$activityId = $this->request->post('ac_id/d', 1);if (empty($activityId)) $this->error('赛事错误,请重试!');$openid = $this->request->post('openid/s', '');if (empty($openid)) $this->error('支付用户获取失败,请重试!');$model = new ActivityModel();$activity = $model->findOrEmpty($activityId)->toArray();if (empty($activity)) $this->error('get Err');if ($activity['status'] != 1) $this->error('get Err!');//  订单信息$orderInfo = ['activity_id' => $activityId,'openid' => $openid,'number' => 'order' . date('YmdHis') . rand(1000, 9999),'money' => $activity['price'],'type' => 1,'status' => 0];//  生成订单$pay = new WechatPay();$notify_url = env('domain') . 'index.php/api/sp.Activity/notify';$prepayId = $pay->spPrepayId($openid, $orderInfo['number'], $notify_url);if (empty($prepayId)) $this->error('订单生成失败,请重试!');$orderInfo['prepay_id'] = $prepayId;$order = new Order();$order->save($orderInfo);$timeStamp = (string)time();$orderInfo['timeStamp'] = $timeStamp;$nonceStr = getRandStr(32);$orderInfo['nonceStr'] = $nonceStr;$orderInfo['package'] = 'prepay_id=' . $prepayId;$orderInfo['paySign'] = $pay->makeSign($prepayId, $nonceStr, $timeStamp);$this->success('get Success', ['order' => $orderInfo]);}/*** @note 支付回调*/public function notify(): Json{$inWechatpaySignature = request()->header('Wechatpay-Signature', ''); // header中获取签名$inWechatpayTimestamp = request()->header('Wechatpay-Timestamp', ''); // header中获取时间戳$inWechatpaySerial = request()->header('Wechatpay-Serial', ''); // header中获取证书序列号$inWechatpayNonce = request()->header('Wechatpay-Nonce', ''); // header中获取随机字符串$inRequestID = request()->header('Request-ID', ''); // 请根据实际情况获取$inBody = file_get_contents('php://input'); // 请根据实际情况获取,例如: file_get_contents('php://input');$pay = new WechatPay();$res = $pay->notifyDecrypt($inWechatpaySignature, $inWechatpayTimestamp, $inWechatpayNonce, $inBody, $inWechatpaySerial, $inRequestID);if (!empty($res)) {//  进行订单数据修改$order = new Order();//  查询订单数据$orderInfo = $order->where('number', $res['out_trade_no'])->find();if (!empty($orderInfo)){$result = $order->where('id',$orderInfo['id'])->save(['transaction_id' => $res['transaction_id'],'status' => $res['trade_state'] == 'SUCCESS' ? 1 : 0,'trade_type' => $res['trade_type'],'trade_state_desc' => $res['trade_state_desc'],'bank_type' => $res['bank_type'],'success_time' => $res['success_time']]);cache(':order_' . $res['out_trade_no'], $result, 3600);}return json(['code' => 'SUCCESS']);}return json(['message' => '失败', 'code' => 'FAIL']);}}

5.uniapp示例

<template><view class="box"><view><up-button text="立即支付" type="primary" @click="toPay"></up-button>    </view><up-toast ref="uToastRef"></up-toast></view>
</template><script setup>import {onLoad} from '@dcloudio/uni-app'import {ref,} from 'vue';import {getPrepayId} from '@/utils/api/order.js'const uToastRef = ref(null)//	点击支付const toPay = () => {getPrepayId({openid: ''}).then((res) => {if (res.code == 1) {const order = res.data.orderuni.requestPayment({provider: 'wxpay',timeStamp: order.timeStamp, //	时间戳nonceStr: order.nonceStr, //	随机字符串,长度为32个字符以下package: order.package, //		统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***signType: 'RSA', //	签名算法,应与后台下单时的值一致paySign: order.paySign, //	签名success: function(res) {console.log('success:' + JSON.stringify(res));},fail: function(err) {console.log('fail:' + JSON.stringify(err),);}});} else {uToastRef.value.error(res.msg)}})}
</script><style lang="scss">.box {width: 100%;}
</style>

相关文章:

微信小程序支付(完整版)-ThinkPHP/Uniapp

技术说明 1.前端&#xff1a;uniapp、vue3 2.接口&#xff1a;PHP8、ThinkPHP8、MySQL8.0 3.微信支付- PHP&#xff0c;官方示例文档 4.示例代码的模型及业务自己进行调整&#xff0c;不要一味的复制粘贴&#xff01;&#xff01;&#xff01; 流程说明 1.小程序调用接口…...

同时安装多个nodejs版本可切换使用,或者用nvm管理、切换nodejs版本(两个详细方法)

目录 一.使用nvm的方法&#xff1a; 1.卸载nodejs 2.前往官网下载nvm 3.安装nvm 4.查看安装是否完成 5.配置路径和淘宝镜像 6.查看和安装各个版本的nodejs 7.nvm的常用命令 二.不使用nvm&#xff0c;安装多个版本&#xff1a; 1.安装不同版本的nodejs 2.解压到你想放…...

马化腾用了一年多的时间,告诉所有人,视频号小店是新风口!

大家好&#xff0c;我是电商笨笨熊 当腾讯说出自己要做电商的时候&#xff0c;所有人都在说&#xff0c;根本不可能&#xff1b; 甚至在视频号小店正式推出之后&#xff0c;依旧有人说&#xff0c;腾讯做电商就是笑话&#xff1b; 一个“抄”过来的项目&#xff0c;毫无特色…...

代码随想录算法训练营第36期DAY19

DAY19 104二叉树的最大深度 根节点的高度就是最大深度。 非递归法&#xff1a; /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) …...

C#图像:1.图像区域分割与提取

&#xff08;1&#xff09;创建一个名为SplitImage的窗体的应用程序&#xff0c;将窗体改名为FormSplitImage。 &#xff08;2&#xff09;创建一个名为ImageProcessingLibrary的类库程序&#xff0c;为该工程添加名为ImageProcessing的静态类 &#xff08;3&#xff09;为Imag…...

炸弹使用技巧

掼蛋掼蛋&#xff0c;打的就是炸弹。炸弹是指掼蛋中由4-8张相同牌点的牌组成的牌型&#xff0c;需要注意的是&#xff1a;每局牌中都有两张红桃的牌型为逢人配&#xff0c;可以配除了大小王以外的任意牌&#xff0c;因此掼蛋中牌数最多的炸弹可以达到10张。 两副扑克牌中&#…...

SpringAop详解

文章目录 一、Spring自定义注解1、什么是注解&#x1f468;‍&#x1f3eb;2、注解的目的或作用&#x1f49e;3、JDK内置注解&#x1f4ab; 【内置元注解 一共八个固定注解】4、元注解 &#x1f3af;5、自定义注解&#x1f4f8;5、Java反射API和类加载过程51、什么是反射基本原…...

对XYctf的一些总结

对XYctf的一些总结 WEB 1.http请求头字段 此次比赛中出现的&#xff1a; X-Forwarded-For/Client-ip&#xff1a;修改来源ip via&#xff1a;修改代理服务器 还有一些常见的字段&#xff1a; GET&#xff1a;此方法用于请求指定的资源。GET请求应该安全且幂等&#xff0c…...

Visual Studio和Visual Studio Code适用于哪些编程语言

Visual Studio和Visual Studio Code都适用于多种编程语言&#xff0c;它们的适用编程语言如下&#xff1a; Visual Studio适用于&#xff1a; C#Visual Basic .NETF#CJavaScriptTypeScriptPythonHTML/CSSJava&#xff08;通过插件支持&#xff09; Visual Studio Code适用于…...

缓存菜品操作

一&#xff1a;问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大。 二&#xff1a;实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; 每个分…...

达梦数据库常用命令整理

1.数据库自身信息 1.1 查询实例信息 SQL> select name inst_name from v$instance;行号 INST_NAME ---------- --------- 1 DMSERVER已用时间: 11.211(毫秒). 执行号:15.1.2 查询数据库当前状态 SQL> select status$ from v$instance;行号 STATUS$ -…...

Vue 组件的三大组成部分

Vue 组件通常由三大组成部分构成&#xff1a;模板&#xff08;Template&#xff09;、脚本&#xff08;Script&#xff09;、样式&#xff08;Style&#xff09; 模板部分是组件的 HTML 结构&#xff0c;它定义了组件的外观和布局。Vue 使用基于 HTML 的模板语法来声明组件的模…...

MoneyPrinter中的文字转声音国内替换方案

背景&#xff1a; 在进行MoneyPrinter项目国内环境搭建中&#xff0c;发现框架本身的TikTok文字转语音部分的代码已经不能用了&#xff0c;最好是能够找到国内网站的替换方案。 实现&#xff1a; 感谢网站&#xff1a;https://www.text-to-speech.cn/ 代码&#xff1a; # -*…...

消除试卷手写笔迹的软件免费的有哪些?这几款都不错

消除试卷手写笔迹的软件免费的有哪些&#xff1f;在数字化学习的浪潮中&#xff0c;学生们越来越频繁地利用电子设备来完成学习任务。然而&#xff0c;当纸质试卷需要被数字化并再次利用时&#xff0c;如何高效地消除手写笔迹便成为了一个有待解决的问题。那么&#xff0c;今天…...

智能创作时代:AI 如何重塑内容生成游戏规则

文章目录 前言一&#xff1a;自动化内容生成文章生成视频制作音频创作 二&#xff1a;内容分发与推广智能推荐系统社交媒体优化 三&#xff1a;内容分析与优化数据分析用户反馈质量控制 结语 前言 在数字化时代的浪潮中&#xff0c;内容生产与消费已成为信息传播的核心。随着人…...

大数据------JavaWeb------Tomcat(完整知识点汇总)

Web服务器——Tomcat Web服务器定义 它是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让Web开发更便捷 Web服务器主要功能 封装HTTP协议操作&#xff0c;简化开发将Web项目部署到…...

LMDeploy笔记

随谈模型部署 模型部署包含的内容很多&#xff0c;来聊聊。 访存bottleneck 首先&#xff0c;基于transformer的计算是访存密集型任务。 so? 过去&#xff0c;我们表达模型的性能&#xff0c;通常会用ops&#xff0c;macs这些指标,也计算量来衡量模型的推理时间&#xff…...

Unity 状态机

文章目录 前言一、状态机二、应用1、场景切换2、人物行为切换3、宝箱、机关切换4、AI 三、人物行为总结 前言 提到Unity状态机&#xff0c;接触不久的开发者会想到Unity的动画状态机&#xff0c;而对于老油条来说&#xff0c;可能会回忆起自己实现的动画状态机。当然&#xff…...

一毛钱不到的FH8208C单节锂离子和锂聚合物电池一体保护芯片

前言 目前市场上电池保护板&#xff0c;多为分体方案&#xff0c;多数场合使用没有问题&#xff0c;部分场合对空间有进一步要求&#xff0c;或者你不想用那么多器件&#xff0c;想精简一些&#xff0c;那么这个芯片就很合适&#xff0c;对于充电电池来说&#xff0c;应在使用…...

python数据可视化:显示两个变量间的关系散点图scatterplot()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 python数据可视化&#xff1a; 显示两个变量间的关系 散点图 scatterplot() [太阳]选择题 请问关于以下代码表述错误的选项是&#xff1f; import seaborn as sns import matplotlib.pyplot …...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合

作者&#xff1a;来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布&#xff0c;Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明&#xff0c;Elastic 作为 …...

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、基础概念 1. 哈希核心思想&#xff1a; 哈希函数的作用&#xff1a;通过此函数建立一个Key与存储位置之间的映射关系。理想目标&#xff1a;实现…...

node.js的初步学习

那什么是node.js呢&#xff1f; 和JavaScript又是什么关系呢&#xff1f; node.js 提供了 JavaScript的运行环境。当JavaScript作为后端开发语言来说&#xff0c; 需要在node.js的环境上进行当JavaScript作为前端开发语言来说&#xff0c;需要在浏览器的环境上进行 Node.js 可…...