0


PHP对接V3版本的微信支付(APP下单)超级详细版

环境

php版本:7.2*

安装官方SDK

composer require wechatpay/wechatpay

ps: 如果因为第三方包导致提示PHP版本问题而安装不了的(参考下图 图1),可使用

--ignore-platform-reqs

配置,可以忽略。完整命令:

composer require wechatpay/wechatpay --ignore-platform-reqs

(参考下图 图2)
图1:
在这里插入图片描述
图2:
在这里插入图片描述
安装完成 官方的SDK 就不用去考虑微信的验签加密签名等问题了,只要配置好相关的配置就行。

构造一个 APIv3 客户端实例

<?php

namespace App\Http\Controllers;

use WeChatPay\Crypto\Rsa;

/**
 * 微信支付V3版本
 */
class WxPayV3Controller
{

    /**
     * 微信开发平台审核通过的应用ID
     * @var string
     */
    protected $appid = 'wxabc1111123213';

    /**
     * 商户ID
     * @var string
     */
    protected $merchantId = '1658899999';

    /**
     * 商户v3版本私钥
     * @var string
     */
    protected $merchantV3PrivateKey = 'V3e123ABC12312AAALDLSFLSDCDSLDF';

    /**
     * 「商户API私钥」文件的绝对路径
     * @var string
     */
    protected $merchantPrivatePath = 'D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem';

    /**
     * 「商户API证书」的「证书序列号」
     * @var string
     */
    protected $merchantCertificateSerial = '73021A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';

    /**
     * 「微信支付平台证书」文件的绝对路径
     * @var string
     */
    protected $platformCertificateOrPublicKeyFilePath = 'D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem';

    /**
     * 「微信支付平台证书」的「证书序列号」
     * @var string
     */
    protected $platformCertificateSerialOrPublicKeyId = '5B1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';

    /**
     * APIv3 客户端实例
     * @var \WeChatPay\BuilderChainable
     */
    protected $instance;

    public function __construct()
    {
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        // 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
        $merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);

        // 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
        // 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem
        $platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);

        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid'      => $this->merchantId,
            'serial'     => $this->merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $this->platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
            ],
        ]);

        $this->instance = $instance;
    }

参数说明

这里主要搞明白几个重要的配置参数分别去哪里获取。

$appid 微信开发平台审核通过的应用ID

获取途径:
登录 微信开放平台 输入账号密码登录,然后点击

管理后台->移动应用

点击已通过的应用进行查看。
在这里插入图片描述
查看这里的APPID即可,这就是 微信开发平台审核通过的应用ID

$appid

在这里插入图片描述

$merchantId 商户ID

获取途径:
登录 微信商户后台 输入账号密码登录,点击 右上角的登录头像 就能查看

$merchantV3PrivateKey 商户v3版本私钥

获取途径:
登录 微信商户后台 后,然后点击

API安全


如果没设置过,直接设置即可。而设置过却忘记了,那就只能重新更改了。参考下图:
在这里插入图片描述

$merchantPrivatePath 「商户API私钥」文件的绝对路径

获取途径:
登录 微信商户后台 后,然后点击

API安全


没有证书。那么就自己申请一个新的,

API安全->商户API证书

点击

申请证书

(参考下图)。
如果有证书,忘记了私钥,也可以重新申请一个新的证书,里面包含一个证书文件和一个API私钥文件。
在这里插入图片描述
具体操作步骤可参考官方文档:商户API证书获取方法及功能介绍,照着步骤来下载软件生成证书就行。里面的文件大概是这样的(参考下图):
在这里插入图片描述
其中的

apiclient_key.pen

文件就是

商户API私钥

,把这个文件放在项目的某个地方,而

$merchantPrivatePath

指的就是这个文件所在的绝对路径,不管是在Windows本地还是Linux服务器上都一样。

Windows上参考路径:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
Linux上参考路径:/www/wwwroot/www.xxx.com/cert/wx_v3/v3_apiclient_key.pem

$merchantCertificateSerial 「商户API证书」的「证书序列号」

获取途径:
登录 微信商户后台 输入账号密码登录,点击

API安全->商户API证书

点击管理证书就能查看。
在这里插入图片描述

$platformCertificateOrPublicKeyFilePath 「微信支付平台证书」文件的绝对路径

获取途径:
这个证书可以在代码里请求获取保存到项目中,也可以直接在本地先请求保存下来(博主选的是这一种)。
先参考一下官方给的文档:Certificate Downloader。博主这里主要讲一下要注意的一些地方

指令:php vendor/bin/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
参数说明:
  -m, --mchid=<merchantId>   这里就是博主代码中的 $merchantId (商户ID)
  -s, --serialno=<serialNo>  这里就是博主代码中的 $merchantCertificateSerial (「商户API证书」的「证书序列号」)
  -f, --privatekey=<privateKeyFilePath>  这里就是博主代码中的 $merchantPrivatePath(「商户API私钥」文件的绝对路径)
  -k, --key=<apiV3key>      这里就是博主代码中的 $merchantV3PrivateKey (商户v3版本私钥)
  -o, --output=[outputFilePath] 下载成功后保存证书的路径,可选参数,默认为临时文件目录夹(博主这里写的是:D:\EMin\xxx\public/)

博主的参考指令:
php vendor/bin/CertificateDownloader.php -k 'V3e123ABC12312AAALDLSFLSDCDSLDF -m 1658899999 -f D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem -s 73021A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A -o  D:\EMin\xxx\public/

Tip: 在项目的根目录上执行博主的这条参考指令即可。具体参数自己替换哈,这样就会在

 D:\EMin\xxx\public/

目录下生成一个pem文件,重命名即可。

在这里插入图片描述

$platformCertificateSerialOrPublicKeyId 微信支付平台证书」的「证书序列号」

获取途径:
登录 微信商户后台 输入账号密码登录,点击

API安全->平台证书

点击管理证书就能查看。
在这里插入图片描述

至此,微信支付最恶心的配置项已经整完了。

APP下单

博主接着上面的源码,继续写了。封装好的完整源码会放在最后

/**
 *  APP下单 
 * @param $out_trade_no string 在自己系统中唯一的订单号
 * @param $body string  商品描述
 * @param $amount number 订单金额,单位为分
 * @param $notify_url string 支付回调地址,必须是https开头。例如:https://www.xxx.com/xxx/xxx
 * @return \Psr\Http\Message\ResponseInterface
 */
public function appPay($out_trade_no, $body, $amount,$notify_url)
{
    return $this->instance
        ->chain('v3/pay/transactions/app')
        ->post(['json' => [
            'mchid'        => $this->merchantId,
            'out_trade_no' => $out_trade_no,
            'appid'        => $this->appid,
            'description'  => $body,
            'notify_url'   => $notify_url,
            'amount'       => [
                'total'    => $amount,
                'currency' => 'CNY'
            ],
        ]]);
}

参考官方文档:APP下单

生成签名

作用:用于返回给APP端 用于调起微信支付

use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;

/**
 * 生成支付签名
 * @param $result_data
 * @return array
 */
public function generateSignature($result_data)
{
    // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
    // 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
    $merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);
    $arouse_data = [
        'appId' => $this->appid,
        'timeStamp' => strval(Formatter::timestamp()),
        'nonceStr' => Formatter::nonce(),
        //'package' => 'prepay_id=' . $result_data['prepay_id'],  // JSAPI下单
        'prepay_id => $result_data['prepay_id'], // APP下单
    ];

    $arouse_data += ['paySign' => Rsa::sign(
        Formatter::joinedByLineFeed(...array_values($arouse_data)),
        $merchantPrivateKeyInstance
    ), 'signType' => 'RSA'];

    return $arouse_data;
}

Tip:博主没测JSAPI下单流程,只是看官方文档给的示例是JSAPI,没注意看文档。**APP下单不要加

prepay_id=预交易号

去加密字符串,不然APP唤醒微信时会报支付验证签名失败**

参考官方文档的部分截图:

在这里插入图片描述
参考官方文档:APP调起支付

支付回调

use Illuminate\Support\Facades\Request;
use WeChatPay\Crypto\AesGcm;

/**
 * v3版本的支付回调
 * @return false|mixed|string
 */
public function notify()
{
    //获取请求参数
    $notifyData = file_get_contents('php://input');
    
    //获取请求头用来签名验签
    $header = Request::header();

    // 转换通知的JSON文本消息为PHP Array数组
    $inBodyArray = (array)json_decode($notifyData, true);

    if ($inBodyArray['event_type'] !== 'TRANSACTION.SUCCESS') {
        return json_encode(['code' => 400,'message' => '支付失败']);
    }

    //签名验签
    $signStatus = $this->signVerify($header,$notifyData);
    if (!$signStatus) {
        return json_encode(['code' => 400,'message' => '验签失败']);
    }
    // 使用PHP7的数据解构语法,从Array中解构并赋值变量
    ['resource' => [
        'ciphertext' => $ciphertext,
        'nonce' => $nonce,
        'associated_data' => $aad
    ]] = $inBodyArray;

    // 加密文本消息解密
    $inBodyResource = AesGcm::decrypt($ciphertext,$this->merchantV3PrivateKey , $nonce, $aad);

    // 把解密后的文本转换为PHP Array数组
    $inBodyResourceArray = (array)json_decode($inBodyResource, true);
    
    //具体的业务逻辑
    return json_encode(['code' => 200]);
    
}

/**签名验签
 * @param $header 请求头
 * @param $body  请求参数
 * @return bool
 */
public function signVerify($header,$body)
{
    $inWechatpaySignature = head($header['wechatpay-signature']);// 请根据实际情况获取 微信方的签名
    $inWechatpayTimestamp = head($header['wechatpay-timestamp']);// 请根据实际情况获取 微信方的时间戳
    $inWechatpayNonce = head($header['wechatpay-nonce']);// 请根据实际情况获取  微信方的随机字符串

    // 根据通知的平台证书序列号,查询本地平台证书文件,
    $platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);

    // 检查通知时间偏移量,允许5分钟之内的偏移
    $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
    $verifiedStatus = Rsa::verify(
    // 构造验签名串
        Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $body),
        $inWechatpaySignature,
        $platformPublicKeyInstance
    );

    if ($timeOffsetStatus && $verifiedStatus) {
        return true;
    }

    return false;
}

可参考官方文档:支付通知

完整的封装类源码

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Request;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;

/**
 * 微信支付V3版本
 */
class WxPayV3Controller
{

    /**
     * 微信开发平台审核通过的应用ID
     * @var string
     */
    protected $appid = 'wxabc1111123213';

    /**
     * 商户ID
     * @var string
     */
    protected $merchantId = '1658899999';

    /**
     * 商户v3版本私钥
     * @var string
     */
    protected $merchantV3PrivateKey = 'V3e123ABC12312AAALDLSFLSDCDSLDF';

    /**
     * 「商户API私钥」文件的绝对路径
     * @var string
     */
    protected $merchantPrivatePath = 'D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem';

    /**
     * 「商户API证书」的「证书序列号」
     * @var string
     */
    protected $merchantCertificateSerial = '73021A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';

    /**
     * 「微信支付平台证书」文件的绝对路径
     * @var string
     */
    protected $platformCertificateOrPublicKeyFilePath = 'D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem';

    /**
     * 「微信支付平台证书」的「证书序列号」
     * @var string
     */
    protected $platformCertificateSerialOrPublicKeyId = '5B1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A';

    /**
     * APIv3 客户端实例
     * @var \WeChatPay\BuilderChainable
     */
    protected $instance;

    public function __construct()
    {
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        // 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
        $merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);

        // 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
        // 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_certificate_or_publickey.pem
        $platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);

        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid'      => $this->merchantId,
            'serial'     => $this->merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $this->platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
            ],
        ]);

        $this->instance = $instance;
    }
    
    /**
     *  APP下单 
     * @param $out_trade_no string 在自己系统中唯一的订单号
     * @param $body string  商品描述
     * @param $amount number 订单金额,单位为分
     * @param $notify_url string 支付回调地址,必须是https开头。例如:https://www.xxx.com/xxx/xxx
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function appPay($out_trade_no, $body, $amount,$notify_url)
    {
        return $this->instance
            ->chain('v3/pay/transactions/app')
            ->post(['json' => [
                'mchid'        => $this->merchantId,
                'out_trade_no' => $out_trade_no,
                'appid'        => $this->appid,
                'description'  => $body,
                'notify_url'   => $notify_url,
                'amount'       => [
                    'total'    => (int)$amount,
                    'currency' => 'CNY'
                ],
            ]]);
    }

    /**
     * 生成支付签名
     * @param $result_data
     * @return array
     */
    public function generateSignature($result_data)
    {
        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        // 文件路径例如:D:\EMin\xxx\cert\wx_v3\v3_apiclient_key.pem
        $merchantPrivateKeyInstance = Rsa::from('file://'.$this->merchantPrivatePath, Rsa::KEY_TYPE_PRIVATE);
        $arouse_data = [
            'appId' => $this->appid,
            'timeStamp' => strval(Formatter::timestamp()),
            'nonceStr' => Formatter::nonce(),
           'prepayId' => $result_data['prepay_id'],
        ];

        $arouse_data += ['paySign' => Rsa::sign(
            Formatter::joinedByLineFeed(...array_values($arouse_data)),
            $merchantPrivateKeyInstance
        ), 'signType' => 'RSA'];

        return $arouse_data;
    }

    /**
     * v3版本的支付回调
     * @return false|mixed|string
     */
    public function notify()
    {
        //获取请求参数
        $notifyData = file_get_contents('php://input');
        
        //获取请求头用来签名验签
        $header = Request::header();

        // 转换通知的JSON文本消息为PHP Array数组
        $inBodyArray = (array)json_decode($notifyData, true);

        if ($inBodyArray['event_type'] !== 'TRANSACTION.SUCCESS') {
            return json_encode(['code' => 400,'message' => '支付失败']);
        }

        //签名验签
        $signStatus = $this->signVerify($header,$notifyData);
        if (!$signStatus) {
            return json_encode(['code' => 400,'message' => '验签失败']);
        }
        // 使用PHP7的数据解构语法,从Array中解构并赋值变量
        ['resource' => [
            'ciphertext' => $ciphertext,
            'nonce' => $nonce,
            'associated_data' => $aad
        ]] = $inBodyArray;

        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext,$this->merchantV3PrivateKey , $nonce, $aad);

        // 把解密后的文本转换为PHP Array数组
        $inBodyResourceArray = (array)json_decode($inBodyResource, true);

        // 具体的业务逻辑
        return json_encode(['code' => 200]);
    }

    /**签名验签
     * @param $header 请求头
     * @param $body  请求参数
     * @return bool
     */
    public function signVerify($header,$body)
    {
        $inWechatpaySignature = head($header['wechatpay-signature']);// 请根据实际情况获取 微信方的签名
        $inWechatpayTimestamp = head($header['wechatpay-timestamp']);// 请根据实际情况获取 微信方的时间戳
        $inWechatpayNonce = head($header['wechatpay-nonce']);// 请根据实际情况获取 微信方的随机字符串

        // 根据通知的平台证书序列号,查询本地平台证书文件,
        $platformPublicKeyInstance = Rsa::from('file://'.$this->platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);

        // 检查通知时间偏移量,允许5分钟之内的偏移
        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串
            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $body),
            $inWechatpaySignature,
            $platformPublicKeyInstance
        );

        if ($timeOffsetStatus && $verifiedStatus) {
            return true;
        }

        return false;
    }

业务调用

<?php
    namespace App\Http\Controllers;
    
    use App\Http\Controllers\WxPayV3Controller;
    use App\Http\Controllers\Controller;
    
    class TestController extends Controller
    {
        $wxPayV3Controller = new WxPayV3Controller();
        // 生成APP下单
        $resp = $wxPayV3Controller->appPay($out_trade_no, $body, $amount,$notify_url);
        //    $out_trade_no 在自己系统中唯一的订单号
        //    $body  商品描述
        //  $amount  订单金额,单位为分
        //  $notify_url  支付回调地址,必须是https开头。例如:https://www.xxx.com/xxx/xxx
        
        $result_code = $resp->getStatusCode();
        
        if ($result_code == 200) {
            $result_data = json_decode($resp->getBody(), true);
            // 生成支付签名
            $arouse_data = $wxPayV3Controller->generateSignature($result_data);
            return json_encode(['code'  => 200 , 'msg'  =>  '成功','data' => $arouse_data ]);
        } else {
            return json_encode(['code'  => 400 , 'msg'  =>  '失败','data' => [] ]);
        }
    }

特别注意

报错请求超时

解决方案:在

C:\Windows\System32\drivers\etc\host

文件里添加

113.96.240.143 api.mch.weixin.qq.com

即可。

报错没有安全证书

具体报错:

cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://api.mch.weixin.qq.com/v3/pay/transactions/app

解决方案:
windons上 打开cmd 输入指令

curl -O https://curl.se/ca/cacert.pem

,就会在当前目录下生成一个 cacert.pem 文件,这个文件就是 CA证书。
在对应PHP版本的

ini

文件里添加

curl.cainfo="E:\xxx\crt\cacert.pem"

即可,

E:\xxx\crt\cacert.pem

为 CA证书 的绝对路径地址。

特别感谢

参考博客:
微信支付V3文档
微信支付 WeChatPay OpenAPI SDK
微信小程序支付v3版


本文转载自: https://blog.csdn.net/weixin_48195625/article/details/143465885
版权归原作者 ゛人生若只如初见° 所有, 如有侵权,请联系我们删除。

“PHP对接V3版本的微信支付(APP下单)超级详细版”的评论:

还没有评论