微信小程序v3SDK使用-PHP版

微信小程序v3SDK使用-PHP版

  • 关于微信支付的网站
  • 下载SDK 然后放到项目中 并引用
    • 下面说一下各个参数的由来
      • appid 和 mch_id(商户号)
      • 商户私钥和商户证书
      • 平台证书获取
        • 讲解一下流程
          • 调用平台证书列表接口
          • 获取签名
          • 证书解密
    • 调用微信小程序下单
    • [调用微信小程序支付](https://github.com/wechatpay-apiv3/wechatpay-php
    • 调用支付接口返回的数据 返回给前端

关于微信支付的网站

微信支付开发文档 https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
微信支付开发SDK https://github.com/wechatpay-apiv3/wechatpay-php
微信支付官网 https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F

下载SDK 然后放到项目中 并引用

use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
// 商户号,假定为`1000100`
$merchantId = '1000100';
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';// 注意 `file://` 开头协议不能少
// 加载商户私钥
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$merchantCertificateSerial = '可以从商户平台直接获取到';// API证书不重置,商户证书序列号就是个常量
// // 也可以使用openssl命令行获取证书序列号
// // openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'
// // 或者从以下代码也可以直接加载
// // 「商户证书」,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
// $merchantCertificateFilePath = 'file:///path/to/merchant/apiclient_cert.pem';// 注意 `file://` 开头协议不能少
// // 解析「商户证书」序列号
// $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateFilePath);
// 「平台证书」,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`
$platformCertificateFilePath = 'file:///path/to/wechatpay/cert.pem';// 注意 `file://` 开头协议不能少
// 加载「平台证书」公钥
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 解析「平台证书」序列号,「平台证书」当前五年一换,缓存后就是个常量
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 工厂方法构造一个实例
$instance = Builder::factory([
    'mchid'      => $merchantId,
    'serial'     => $merchantCertificateSerial,
    'privateKey' => $merchantPrivateKeyInstance,
    'certs'      => [
        $platformCertificateSerial => $platformPublicKeyInstance,
    ],
    // APIv2密钥(32字节)--不使用APIv2可选
    // 'secret' => 'exposed_your_key_here_have_risks',// 值为占位符,如需使用APIv2请替换为实际值
    // 'merchant' => [// --不使用APIv2可选
    //     // 商户证书 文件路径 --不使用APIv2可选
    //     'cert' => $merchantCertificateFilePath,
    //     // 商户API私钥 文件路径 --不使用APIv2可选
    //     'key' => $merchantPrivateKeyFilePath,
    // ],
]);

下面说一下各个参数的由来

appid 和 mch_id(商户号)

登录微信支付官网
微信小程序v3SDK使用-PHP版_第1张图片
微信小程序v3SDK使用-PHP版_第2张图片

商户私钥和商户证书

按照教程获取这两个文件 https://kf.qq.com/faq/161222NneAJf161222U7fARv.html

平台证书获取

代码

//获取平台证书
public function aes_util()
{
    //获取平台证书列表
    $url = 'https://api.mch.weixin.qq.com/v3/certificates';
    $method = 'GET';
    $token = $this->sign($url,$method,'');
    $response = $this->curlGet($url,$token);
    //证书和回调报文解密
	$api_v3_key = '';//APIv3秘钥
	$associatedData = '';//平台列表接口返回的 associated_data
	$nonceStr = '';//平台列表接口返回的 nonce
	$ciphertext  = '';//平台列表接口返回的 ciphertext
	$AesUtil= new AesUtil($api_v3_key);
	$back=$AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);
    $back=$AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);
}

//获取签名
public function sign($url,$method,$body)
{
    $timestamp = time();
    $url_parts = parse_url($url);
    $nonce = $timestamp.rand('10000','99999');
    $serial_no = '';//API证书序列号
    $merchant_id = '';//商户号
    $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
    $message = $method."\n".
        $canonical_url."\n".
        $timestamp."\n".
        $nonce."\n".
        $body."\n";
    $apiclient_key_path = INDEX_PATH . '\certificate\apiclient_key.pem';//商户私钥文件路径
    $pkeyid = openssl_get_privatekey(file_get_contents($apiclient_key_path));
    openssl_sign($message, $raw_sign, $pkeyid, 'sha256WithRSAEncryption');
    $sign = base64_encode($raw_sign);//签名
    $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
        $merchant_id, $nonce, $timestamp, $serial_no, $sign);
    return $token;
}

function curlGet($url,$token)
{
    //初始化
    $ch = curl_init();
    //设置抓取的url
    curl_setopt($ch, CURLOPT_URL, $url);
    //设置获取的信息以文件流的形式返回,而不是直接输出。
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); // https请求 不验证hosts
    //设置header
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    $schema = 'WECHATPAY2-SHA256-RSA2048';
    $header=array(
        'Authorization:' . $schema . ' ' . $token,
        'Accept: application/json',
        'Content-Type: application/json; charset=utf-8',
        'User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
    );
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);//模拟头部信息
    //执行命令
    $output = curl_exec($ch);//返回api的json对象
    curl_close($ch); //释放curl句柄
    return json_decode($output,true);
    //1.1、组装请求携带数据(初始化)
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    $schema = 'WECHATPAY2-SHA256-RSA2048';
    $header = [
        "Content-Type" => "application/json",
        "Accept" => "application/json",
        "User-Agent" => "*/*",
        "Authorization" => $schema . ' ' . $token
    ];
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);//模拟头部信息
    //按需打开
    // $headerArray = array("Content-Type:application/json;charset=utf-8", "Accept:application/json");
    // curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);
    //1.2、发送请求
    $output = curl_exec($ch);
    //按需打开
    // $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 这一步必须在curl_close()调用之前调用
    //1.3、关闭请求通道
    curl_close($ch);
    //2、解析数据
    //2.1、解决中文乱码。【返回的字符(数据)编码格式可能是:UTF-8、GBK、GB2312、BIG5等,这里统一转换成UTF-8编码格式】
    $output = mb_convert_encoding($output, 'UTF-8', 'UTF-8,GBK,GB2312,BIG5');
    //2.2、将数据转换成数组格式。【PHP最常用的数据结构就是数组,固这里转换成数组】
    $output = json_decode($output, true);
    //3、数据传给调用者
    return $output;
}
讲解一下流程
调用平台证书列表接口

微信小程序v3SDK使用-PHP版_第3张图片
v3版本请求都需要带个头部信息 主要难点是 签名生成
如果没有带头部信息会报以下错误
微信小程序v3SDK使用-PHP版_第4张图片

获取签名
//获取签名
public function sign($url,$method,$body)
{
    $timestamp = time();
    $url_parts = parse_url($url);
    $nonce = $timestamp.rand('10000','99999');
    $serial_no = '';//API证书序列号
    $merchant_id = '';//商户号
    $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
    $message = $method."\n".
        $canonical_url."\n".
        $timestamp."\n".
        $nonce."\n".
        $body."\n";
    $apiclient_key_path = '';//商户私钥文件路径
    $pkeyid = openssl_get_privatekey(file_get_contents($apiclient_key_path));
    openssl_sign($message, $raw_sign, $pkeyid, 'sha256WithRSAEncryption');
    $sign = base64_encode($raw_sign);//签名
    $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
        $merchant_id, $nonce, $timestamp, $serial_no, $sign);
    return $token;
}

API证书序列号
微信小程序v3SDK使用-PHP版_第5张图片

证书解密

通过调取平台证书列表接口 返回的数据还得经过解密才是我们想要的数据
微信小程序v3SDK使用-PHP版_第6张图片

<?php

class AesUtil
{
    /**
     * AES key
     *
     * @var string
     */
    private $aesKey;
    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;
    /**
     * Constructor
     */
    public function __construct($aesKey)
    {
        if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
            throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
        }
        $this->aesKey = $aesKey;
    }
    /**
     * Decrypt AEAD_AES_256_GCM ciphertext
     *
     * @param string    $associatedData     AES GCM additional authentication data
     * @param string    $nonceStr           AES GCM nonce
     * @param string    $ciphertext         AES GCM cipher text
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }
        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
            \sodium_crypto_aead_aes256gcm_is_available()) {
            return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
        }
        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
            \Sodium\crypto_aead_aes256gcm_is_available()) {
            return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
        }
        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
            return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }
        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
}

引用之后调用

$api_v3_key = '';//APIv3秘钥
$associatedData = '';//平台列表接口返回的 associated_data
$nonceStr = '';//平台列表接口返回的 nonce
$ciphertext  = '';//平台列表接口返回的 ciphertext
$AesUtil= new AesUtil($api_v3_key);
$back=$AesUtil->decryptToString($associatedData,$nonceStr,$ciphertext);

APIv3秘钥

微信小程序v3SDK使用-PHP版_第7张图片
调取成功之后 返回类似下面的数据
微信小程序v3SDK使用-PHP版_第8张图片
把上面的返回的 复制 然后创建一个 .pem 文件 复制进去

平台证书列表 下面有个主意事项
微信小程序v3SDK使用-PHP版_第9张图片
可以写个定时任务 把返回的数据写入文件里

调用微信小程序下单

try {
    $resp = $instance
    ->v3->pay->transactions->native
    ->post(['json' => [
        'mchid'        => '1900006XXX',
        'out_trade_no' => 'native12177525012014070332333',
        'appid'        => 'wxdace645e0bc2cXXX',
        'description'  => 'Image形象店-深圳腾大-QQ公仔',
        'notify_url'   => 'https://weixin.qq.com/',
        'amount'       => [
            'total'    => 1,
            'currency' => 'CNY'
        ],
    ]]);

    echo $resp->getStatusCode(), PHP_EOL;
    echo $resp->getBody(), PHP_EOL;
} 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;
}

[调用微信小程序支付](https://github.com/wechatpay-apiv3/wechatpay-php

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

$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath);

$params = [
    'appId'     => 'wx8888888888888888',
    'timeStamp' => (string)Formatter::timestamp(),
    'nonceStr'  => Formatter::nonce(),
    'package'   => 'prepay_id=',//微信小程序下单返回的prepay_id 主意格式
];
$params += ['paySign' => Rsa::sign(
    Formatter::joinedByLineFeed(...array_values($params)),
    $merchantPrivateKeyInstance
), 'signType' => 'RSA'];

echo json_encode($params);

调用支付接口返回的数据 返回给前端

前端调用wx.requestPayment发起微信支付

其他的接口调用方式都一样的 按照git教程来

注:如果不想用SDK的 调用各个接口的时候 记得带上头部信息 和 请求平台证书列表一样的做法 只是 传参 不同

你可能感兴趣的:(PHP,微信小程序支付,微信小程序,php,微信)