下载

首先先在项目根目录的composer.json文件的require里面添加这句话

"wechatpay/wechatpay": "^1.0",

添加之后把composer.lock文件删掉,然后在项目根目录执行composer install下载依赖

配置

新建及引入

在util里面新增一个WechatPay.php文件,然后引入这些方法

use GuzzleHttp\Exception\RequestException;
use WeChatPay\Builder;
use WeChatPay\Util\PemUtil;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;

新增两个静态属性存储配置信息

// 支付连接客户端
public static $payCli;
// 连接配置
public static $payConfig;

初始化客户端

公钥客户端
// 获取支付连接客户端
private static function getCli(): void
{
    // 商户号
    $merchantId = config('wechat.developer.pay.machid');

    // 商户私钥路径
    $merchantPrivateKeyPath = 'file://' . config('wechat.developer.pay.macPrivateKey');
    $merchantPrivateKey = Rsa::from($merchantPrivateKeyPath, Rsa::KEY_TYPE_PRIVATE);

    // 商户API证书序列号
    $merchantSerialNumber = config('wechat.developer.pay.mchsn');

    // 商户公钥id
    $platformPublicKeyId = config('wechat.developer.pay.publicsn');
    // 商户公钥路径
    $platformPublicKeyFilePath = 'file://' . config('wechat.developer.pay.macPublicKey');
    $twoPlatformPublicKeyInstance = Rsa::from($platformPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);

    // 构造一个instance
    $instance = Builder::factory([
        'mchid' => $merchantId,
        'serial' => $merchantSerialNumber,
        'privateKey' => $merchantPrivateKey,
        'certs' => [$platformPublicKeyId => $twoPlatformPublicKeyInstance]
    ]);

    // 写入静态属性
    self::$payCli = $instance;
}
平台证书客户端

如果还没有生成平台证书和获取平台证书序列号,先转到这里生成和获取:微信商户平台证书生成脚本

// 获取支付连接客户端
private static function getCli(): void
{
    // 商户号
    $merchantId = config('wechat.developer.pay.machid');

    // 商户私钥路径
    $merchantPrivateKeyPath = 'file://' . config('wechat.developer.pay.macPrivateKey');
    $merchantPrivateKey = Rsa::from($merchantPrivateKeyPath, Rsa::KEY_TYPE_PRIVATE);

    // 商户API证书序列号
    $merchantSerialNumber = config('wechat.developer.pay.mchsn');

    // 平台证书序列号:
    $platformCertificateSerial = config('wechat.developer.pay.platform');
    // 平台证书路径
    $platformCertificateFilePath  = 'file://' . config('wechat.developer.pay.platformPath');
    $wechatpayCertificate = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

    // 构造一个WechatPayMiddleware
    $instance = Builder::factory([
        'mchid' => $merchantId,
        'serial' => $merchantSerialNumber,
        'privateKey' => $merchantPrivateKey,
        'certs' => [$wechatpayCertificateSerialNumber => $wechatpayCertificate]
    ]);

    // 写入静态属性
    self::$payCli = $instance;
}

获取微信支付信息配置

// 获取配置
private static function getConfig(): array
{
    if (self::$payConfig) return self::$payConfig;

    // 应用id
    $appid = config('wechat.developer.app.appId');
    // 商家号
    $machid = config('wechat.developer.pay.machid');
    // 商户私钥路径
    $merchantPrivateKeyPath = config('wechat.developer.pay.macPrivateKey');
    // 商户私钥
    $merchantPrivateKey = PemUtil::loadPrivateKey($merchantPrivateKeyPath);
    // 回调通知地址
    $notify_url = '配置自己的微信回调接收地址';// 通知地址

    self::$payConfig = compact('appid', 'merchantPrivateKey', 'machid', 'notify_url');

    return self::$payConfig;
}

获取签名

public static function getSignStr($prepayId = ''): array
{
    if (empty($prepayId)) throw new Exception('预支付id不可为空', 400);

    // 获取配置
    extract(self::getConfig());

    // 时间戳
    $timestamp = time();
    // 随机串
    $nonce = self::getRandStr($timestamp, 33);

    // 构造签名串
    $message = $appid . "\n" . $timestamp . "\n" . $nonce . "\n" . "prepay_id={$prepayId}" . "\n";

    $timestamp = strval($timestamp);

    // 使用 SHA256 with RSA 签名
    openssl_sign($message, $raw_sign, $merchantPrivateKey, 'sha256WithRSAEncryption');
    $sign = base64_encode($raw_sign);

    return compact('nonce', 'timestamp', 'prepayId', 'sign');
}

private static function getRandStr($timestamp, $length = 32): string
{
    $str = md5($timestamp) . md5(date('Y-m-d'));
    return substr($str, 5, $length);
}

生成预支付交易单

public static function prepayment($orderId, $desc = '', $price = 0.01, $notifyUrl = '', $user = [])
{
    if (empty(self::$payCli)) self::getCli();

    // 获取配置
    extract(self::getConfig());

    // 接下来,正常使用Guzzle发起API请求
    try {
        $resp = self::$payCli->chain('v3/pay/transactions/jsapi')->post(['json' => [
            'appid' => $appid,
            'mchid' => $machid,
            'description' => $desc,
            'out_trade_no' => $orderId,
            // 通知地址 https
            'notify_url' => empty($notifyUrl) ? $notify_url : $notifyUrl,
            'amount' => [
                'total' => (int)($price * 100),
                'currency' => 'CNY'
            ],
            'payer' => [ // 添加payer字段
                'openid' => $user['wx_openid'], // 使用用户的openid来标识付款人
            ]
        ]]);

        return json_decode($resp->getBody(), true);

    } catch (RequestException $e) {

        // 进行错误处理
        $msg = $e->getMessage();
        $data = [];
        if ($e->hasResponse()) {
            $body = json_decode($e->getResponse()->getBody(), true);

            $msg = $body['message'] ?? $msg;
            $data['code'] = $e->getResponse()->getStatusCode();
            $data['reason'] = $e->getResponse()->getReasonPhrase();
            $data['body'] = $body;
        }

        return ['code' => 503, 'msg' => $msg, 'data' => $data];
    }
}

申请退款

public static function refund($order, $refund, $refundPrice = 0.01, $price = 0.01)
{
    if (empty(self::$payCli)) self::getCli();

    // 获取配置
    extract(self::getConfig());

    // 接下来,正常使用Guzzle发起API请求
    try {
        $resp = self::$payCli->chain('v3/refund/domestic/refunds')->post(['json' => [
            // 'transaction_id' => '',
            'out_trade_no' => $order['order'],
            'out_refund_no' => $refund,
            "reason" => "取消场地预定订单",
            'amount' => [
                'refund' => (int)($refundPrice * 100),
                'total' => (int)($price * 100),
                'currency' => 'CNY'
            ],
        ]]);

        return json_decode($resp->getBody(), true);
    } catch (RequestException $e) {
        // 进行错误处理
        $msg = $e->getMessage();
        $data = [];
        if ($e->hasResponse()) {
            $body = json_decode($e->getResponse()->getBody(), true);

            $msg = $body['message'] ?? $msg;
            $data['code'] = $e->getResponse()->getStatusCode();
            $data['reason'] = $e->getResponse()->getReasonPhrase();
            $data['body'] = $body;
        }

        return ['code' => 503, 'msg' => $msg, 'data' => $data];
    }
}

解析回调信息

public static function getPayResult($data)
{
    $resource = $data['resource'] ?? [];
    if (empty($resource)) throw new Exception('数据不正确', 400);

    // APIV3的秘钥
    $aesKey = config('wechat.developer.pay.AesKey');

    $aesUtil = new AesGcm();
    $payResult = $aesUtil::decrypt($resource['ciphertext'], $aesKey, $resource['nonce'], $resource['associated_data']);
    if (empty($payResult)) throw new Exception('数据不正确', 400);

    return json_decode($payResult, true);
}

使用

获取前端所需预支付参数

public function wx_prepayment(): \think\response\Json
{
    try {
        // 请求预支付
        $payParams = WechatPayUtil::prepayment(订单号, 订单名称, 金额, 回调地址, 用户信息);
        if (empty($payParams)) throw new \Exception('预支付获取失败', 503);
        if (isset($payParams['code'])) return ret($payParams);// 错误捕捉

        // 获取具体的签名信息
        $payParams = WechatPayUtil::getSignStr($payParams['prepay_id']);
        if (empty($payParams)) return ret(400);
    } catch (\Exception $e) {
        return ret(['code' => $e->getCode(), 'msg' => $e->getMessage()]);
    }

    return ret($payParams);
}

退款

$payParams = WechatPayUtil::refund(订单信息, 退款编号, 退款金额, 订单金额);

支付回调

public function wxPay(): \think\response\Json
{
    try {
        // 获取参数
        $data = $this->getWxPayParams();

        // 解密
        $parseData = WechatPayUtil::getPayResult($data);

        接下来写自己的逻辑
    } catch (Exception $e) {
        return ret(['code' => $e->getCode(), 'msg' => $e->getMessage()]);
    }

    // 返回成功
    return ret([
        'code' => 'SUCCESS',
        'message' => '成功'
    ]);
}

// 获取微信支付回调参数,并校验
private function getWxPayParams(): array
{
    $data = [];

    // 父级参数
    $parentParams = ['id', 'create_time', 'resource_type', 'event_type', 'summary'];
    foreach ($parentParams as $param) {
        $v = input('post.' . $param);
        if (empty($v)) throw new Exception('参数错误', 400);

        $data[$param] = $v;
    }

    $resource = input('post.resource');
    if (empty($resource) || !is_array($resource)) throw new Exception('参数错误', 400);

    // 子级参数
    $sunParams = ['original_type', 'algorithm', 'ciphertext', 'associated_data', 'nonce'];
    foreach ($sunParams as $sunParam) {
        if (!isset($resource[$sunParam]) || empty($resource[$sunParam]))
            throw new Exception('参数错误', 400);
        $data['resource'][$sunParam] = $resource[$sunParam];
    }
    return $data;
}

补充

前端调用

// 微信支付
weixinPay(data) {
    let param = {
        timeStamp: data.timestamp, // 时间戳必须是字符串类型
        nonceStr: data.nonce, // 随机字符串
        package: 'prepay_id=' + data.prepayId, // 统一下单接口返回的 prepay_id 参数值
        signType: "RSA", // 签名方式,默认为MD5
        paySign: data.sign, // 签名
    }
    wx.requestPayment({
        timeStamp: param.timeStamp, // 时间戳必须是字符串类型
        nonceStr: param.nonceStr, // 随机字符串
        package: param.package, // 统一下单接口返回的 prepay_id 参数值
        signType: param.signType, // 签名方式,默认为MD5
        paySign: param.paySign, // 签名
        success: async (res) => {
        
        },
        fail: (e) => {
            if (e.errCode == -100) return uni.showToast({
                title: '取消支付',
                icon: 'none'
            })
        }
    })
},
最后修改:2025 年 01 月 02 日
如果觉得我的文章对你有用,请随意赞赏