微信小程序支付(完整版)-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.封装支付类(完整示例如下)

<?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>

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值