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

news2024/11/24 3:42:16
技术说明

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.封装支付类(完整示例如下)

<?php

namespace 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;  //  小程序appid
    protected 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.封装接口(完整示例如下)

<?php

namespace 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.order
				uni.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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1660804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

资源管理游戏模版进入The Sandbox

我们非常高兴地向您介绍 Game Maker 的最新模板&#xff1a;资源管理游戏&#xff01; 这一全新的模板让您能够深入身临其境的游戏体验中&#xff0c;同时掌握令人兴奋的新机制。通过揭开模板的神秘面纱&#xff0c;您可以锤炼您的游戏设计技能。 什么是资源管理游戏&#xff1…

winpcap无法安装提示新版本已经安装-window11解决办法

winpcap无法安装提示新版本已经安装-window11解决办法 问题解决办法 问题 安装ensp的时候跳出来这个问题&#xff0c;说自己的winpcap没安装&#xff0c;建议安装 但当自己去安装一个winpcap的时候&#xff0c;它又跳出来这个&#xff01; WinPcap 4.1.3 Setup A newer versi…

电脑设置在哪里打开?Window与Mac双系统操作指南

随着科技的不断发展&#xff0c;电脑已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;对于许多初学者来说&#xff0c;如何找到并熟悉电脑的设置界面可能是一个挑战。特别是对于那些同时使用Windows和Mac双系统的用户来说&#xff0c;更是需要一篇详尽的指南来…

android进阶-AIDL

参考&#xff1a;Android进阶——AIDL详解_android aidl-CSDN博客 AIDL&#xff08;Android 接口定义语言&#xff09;&#xff0c;可以使用它定义客户端与服务端进程间通信&#xff08;IPC&#xff09;的编程接口&#xff0c;在 Android 中&#xff0c;进程之间无法共享内存&…

latex algorithm2e 库学习总结

案例1 \documentclass{article}\usepackage{xeCJK} \usepackage[]{algorithm2e} %\usepackage{ctex} % 中文包\begin{document}\renewcommand{\algorithmcfname}{算法} % 把标题设置为“算法” \begin{algorithm…

数据库管理-第184期 23ai:干掉MongoDB的不一定是另一个JSON数据库(20240507)

数据库管理184期 2024-05-07 数据库管理-第184期 23ai:干掉MongoDB的不一定是另一个JSON数据库&#xff08;20240507&#xff09;1 JSON需求2 关系型表设计3 JSON关系型二元性视图3 查询视图总结 数据库管理-第184期 23ai:干掉MongoDB的不一定是另一个JSON数据库&#xff08;20…

雪花算法生成全局Id,看这篇就够了

分布式id 雪花算法能够生成一个64位long类型数据&#xff0c;适合做分布式系统的全局标识符&#xff0c;或者分库分表中&#xff0c;同类型数据表的主键 原理探究 雪花算法&#xff1a;以一台服务器为对象&#xff0c;在一毫秒时间内&#xff0c;生成一个自增的long数据特点…

Ps 滤镜:视频

Ps菜单&#xff1a;滤镜/视频 Filter/Video “视频”滤镜子菜单中包含了“NTSC 颜色”和“逐行”两个滤镜。 这两个滤镜都是针对视频和电视播放的特定需求设计的。 “逐行”滤镜主要解决交错视频的视觉问题&#xff0c;而“NTSC 颜色”滤镜则确保色彩在电视播放时的兼容性和准确…

springboot+vue+mysql老年大学会员管理系统+PPT+论文+讲解+售后

现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本老粘大学会员管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…

HTML4(二)

文章目录 1 开发者文档2 基本标签2.1 排版标签2.2 语义化标签2.3 行内元素与块级元素2.4 文本标签2.5 常用标签补充 3 图片标签4 超链接标签4.1 跳转页面4.2 跳转文件4.3 跳转锚点4.4 唤起指定应用 5 列表5.1 有序列表5.2 无序列表5.3 自定义列表 6 表格6.1 基本结构6.2 表格标…

如何查看页面对应的Selenium定位参数

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

IO 5.10

在一个进程中&#xff0c;创建一个子线程。 主线程负责&#xff1a;向文件中写入数据 子线程负责&#xff1a;从文件中读取数据 要求使用线程的同步逻辑&#xff0c;保证一定在主线程向文件中写入数据成功之后&#xff0c;子线程才开始运行&#xff0c;去读取文件中的数据#incl…

bean在java中什么意思?这篇文章带你详细了解

bean在java中什么意思&#xff1f;这篇文章带你详细了解 在Java的世界里&#xff0c;你可能会经常听到“Bean”这个词。它听起来像咖啡豆&#xff0c;但实际上与咖啡无关。那么&#xff0c;Java Bean到底是什么呢&#xff1f; 简单来说&#xff0c;Bean是一种特殊的Java类&…

麒麟kylin-v10系统,虚拟机kvm的使用

kvm的使用 虚拟机新建 点击选择对应的iso文件 选择相应的系统 &#xff08;注意&#xff0c;如果这里没有相应的系统比如&#xff1a;windows&#xff0c;可以直接选择Generic default这是通用默认的意思&#xff09; 选择cpu 完成即可 等待安装完毕 网络设置-ssh连接 虚拟…

2024.5.10

数据库删除操作 //删除操作的槽 void Widget::on_delbt_clicked() {QString sql;if(ui->idedit->text()NULL){sql "drop Stu";}else{sql QString("delete from Stu where id %1").arg(ui->idedit->text());}QSqlQuery query;if(!query.exe…

看懂这4点,保证让你顺利申请小程序地理位置接口

小程序地理位置接口有什么功能&#xff1f; 通常情况下&#xff0c;我们在开发小程序时&#xff0c;可能会用到获取用户地理位置信息的功能。小程序开发者开放平台的新规定指出&#xff0c;如果没有申请开通微信小程序地理位置接口&#xff08;getLocation&#xff09;&#xf…

OSPF虚链路

原理概述 通常情况下&#xff0c;一个OSPF网络的每个非骨干区域都必须与骨干区域通过ABR路由器直接连接&#xff0c;非骨干区域之间的通信都需要通过骨干区域进行中转。但在现实中&#xff0c;可能会因为各种条件限制&#xff0c;导致非骨干区域和骨干区域无法直接连接&#x…

Mysql的关联查询以及语句

一、mysql的连接查询 1、等值连接 这里是三张表的等值连接 select rp.role_id,rp.permission_id from role_permission rp, role r, permission p where rp.role_idr.id and rp.permission_idp.id 2、内连接&#xff1a; 角色&#xff1a;系统管理员 是否拥有权限&#xf…

【JVM】垃圾回收机制(Garbage Collection)

目录 一、什么是垃圾回收&#xff1f; 二、为什么要有垃圾回收机制&#xff08;GC&#xff09;&#xff1f; 三、垃圾回收主要回收的内存区域 四、死亡对象的判断算法 a&#xff09;引用计数算法 b&#xff09;可达性分析算法 五、垃圾回收算法 a&#xff09;标记-清除…

ICode国际青少年编程竞赛- Python-3级训练场-if else语句

ICode国际青少年编程竞赛- Python-3级训练场-if else语句 1、 for i in range(9):if Flyer[i].x < Dev.x:Flyer[i].step(Dev.x - Flyer[i].x)else:Flyer[i].step(Flyer[i].x - Dev.x) Dev.step(Dev.y - Item.y)2、 for i in range(6):if Flyer[i].x < Dev.x:# 满足条…