PHP微信支付之navicate支付(tp6)

版本:v3

适用语言:PHP(建议7.2以上,本人8.0)、tp6

官方文档:

微信支付开发者文档

说明:微信支付有多种实现方式,此处我使用两种方式来实现,一种是使用官方sdk改写(建议使用),一种是直接调用接口。

  • 目录结构
    1. cert目录:存放证书目录(如果是Linux服务器,需赋予创建文件权限)
    2. config目录:配置文件目录
    3. controller目录:
      1. Demo.php 请求示例demo
      2. AesUtil.php 解密方法
      3. GetCert.php 下载微信支付证书方法
      4. Notify.php 微信支付成功回调示例
      5. Wechat.php 微信支付下单示例
    4. route目录:路由

二、代码说明

  1. 直接调用

说明:直接调用比较简单,url地址填写navicate下单地址即可,请求参数安装文档填写必须参数即可,此处需注意:下单接口文档并没有提及请求header的内容,但是此处是必须要添加请求header!!!具体header组成如下图代码所示。特别是'Authorization在文章下面我写有生成的该参数值的方法。

/**
 *
 * navicate支付,直接调用
 */
public function navicate2()
{
    $uri = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native';
    $data = [
        "amount" => [
            "total" => 1,
            "currency" => "CNY",
        ],
        "mchid" => "商户号",
        "description" => "微信支付测试",
        "notify_url" => "回调地址",
        "out_trade_no" => "订单号",
        "appid" => "appid",
    ];
    $header = [
        'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="商户号",nonce_str="随机字符串",timestamp="时间戳",serial_no="证书序列表",signature="签名"',  //  身份认证信息
        'Content-Type:application/json',
        'Accept:application/json',
        'User-Agent:' . $_SERVER['HTTP_USER_AGENT']
    ];
    $res = curlPost($uri, $data, 2, $header);
    halt($res);
}

  1. Sdk使用(我用的是开发者版本的sdk)

文档地址:

微信支付-开发者文档

第一步:composer安装

composer require wechatpay/wechatpay-guzzle-middleware

第二步:开始编写代码,此处需要注意的是需要两个密钥文件都需要放在可访问目录下(商户私钥和微信支付平台证书),商户密钥是从微信支付平台下载的,微信支付平台证书是通过代码进行下载的,下载网址为:https://api.mch.weixin.qq.com/v3/certificates具体方法在下方展示。此处代码为navicate下单部分关键代码,一些作用类的代码在下方。

2.1 WeChat.php代码如下

<?php

declare (strict_types=1);

namespace app\pay\controller;


use GuzzleHttp\Client;

use GuzzleHttp\Exception\RequestException;

use GuzzleHttp\HandlerStack;

use WechatPay\GuzzleMiddleware\Util\PemUtil;

use WechatPay\GuzzleMiddleware\WechatPayMiddleware;


class Wechat

{


    /**

     *

     * 微信支付,此处需要有创建文件权限

     */

    private $client;    //  微信支付client

    private $merchantId;    //  商户号

    private $apiclient_key; //  商户私钥地址

    private $wechat_key; //  微信支付平台证书地址

    private $resArr;    //  返回错误信息

    private $appid; //  公众号/小程序/app - appid

    const TYPE = 'wechat';

    public function __construct($type = 1)

    {

            $this->resArr = [

            'code' => 0,

            'msg' => 'success',

            'data' => ''

        ];

        $this->apiclient_key = root_path() . '/app/pay/cert/apiclient_key.pem';

        $this->wechat_key = root_path() . '/app/pay/cert/wechat.pem';

        $this->appid = config('wechat.appid');

        // 商户相关配置

        $this->merchantId = config('wechat.merchantId'); // 商户号

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

        $merchantPrivateKey = PemUtil::loadPrivateKey($this->apiclient_key); // 商户私钥

        //  检测微信支付平台证书是否存在,不存在重新生成

        if (!file_exists($this->wechat_key)) {

            $cert = new GetCert($this->apiclient_key);

            $res = $cert->makeCert();

            if (!$res) {

                $this->resArr['code'] = -1;

                $this->resArr['msg'] = '证书下载失败,请检查是否相关目录是否赋予权限!';

                return $this->resArr;

            }

        }

        // 微信支付平台配置

        $wechatpayCertificate = PemUtil::loadCertificate($this->wechat_key); // 微信支付平台证书

        // 构造一个WechatPayMiddleware

        $wechatpayMiddleware = WechatPayMiddleware::builder()

            ->withMerchant($this->merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置

            ->withWechatPay([$wechatpayCertificate]) // 可传入多个微信支付平台证书,参数类型为array

            ->build();

        // 将WechatPayMiddleware添加到Guzzle的HandlerStack中

        $stack = HandlerStack::create();

        $stack->push($wechatpayMiddleware, 'wechatpay');

        // 创建Guzzle HTTP Client时,将HandlerStack传入

        $this->client = new Client(['handler' => $stack]);

    }



    /**

     * navicate下单

     * $total:金额,单位(分)

     * $out_trade_no:订单号

     * $notify_url:回调地址

     */

    public function navicate($total = 1, $out_trade_no = '', $notify_url = '')

    {

        try {

            $resp = $this->client->request(

                'POST',

                'https://api.mch.weixin.qq.com/v3/pay/transactions/native', //  请求URL

                [

                    // JSON请求体

                    'json' => [

                        "amount" => [

                            "total" => $total,

                            "currency" => "CNY",

                        ],

                        "mchid" => $this->merchantId,

                        "description" => "微信支付测试",

                        "notify_url" => $notify_url,

                        "out_trade_no" => $out_trade_no,

                        "appid" => $this->appid,

                    ],

                    'headers' => ['Accept' => 'application/json']

                ]

            );

            $statusCode = $resp->getStatusCode();

            if ($statusCode == 200) { //处理成功

                $res = json_decode($resp->getBody()->getContents());

                $this->resArr['data'] = $res->code_url;

                return $this->resArr;  //  返回微信二维码地址

            } else if ($statusCode == 204) { //处理成功,无返回Body

                $this->resArr['msg'] = '暂无body';

                return $this->resArr;;

            }

        } catch (RequestException $e) {

            // 进行错误处理

            echo $e->getMessage() . "\n";

            $this->resArr['code'] = -1;

            $this->resArr['msg'] = $e->getMessage();

            if ($e->hasResponse()) {

                $this->resArr['msg'] = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";

            }

            return $this->resArr;

        }

    }

       /**

     * 查询订单-通过微信支付订单

     */

    public function findByTransactionId($id)

    {

        try {

            $resp = $this->client->request(

                'GET',

                'https://api.mch.weixin.qq.com/v3/pay/transactions/id/' . $id . '?mchid=' . $this->merchantId //  请求URL

            );

            $statusCode = $resp->getStatusCode();

            if ($statusCode == 200) { //处理成功

                $res = json_decode($resp->getBody()->getContents());

                $this->resArr['data'] = $res;

                return $this->resArr;  //  返回微信二维码地址

            } else if ($statusCode == 204) { //处理成功,无返回Body

                $this->resArr['msg'] = '暂无body';

                return $this->resArr;;

            }

        } catch (RequestException $e) {

            // 进行错误处理

            echo $e->getMessage() . "\n";

            $this->resArr['msg'] = $e->getMessage();

            if ($e->hasResponse()) {

                $this->resArr['msg'] = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";

            }

            return $this->resArr;

        }



    }


    /**

     * 查询订单-通过商户订单号

     */

    public function findByTransactionOrder($orderId)

    {

        try {

            $resp = $this->client->request(

                'GET',

                'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $orderId . '?mchid=' . $this->merchantId //  请求URL

            );

            $statusCode = $resp->getStatusCode();

            if ($statusCode == 200) { //处理成功

                $res = json_decode($resp->getBody()->getContents());

                $this->resArr['data'] = $res;

                return $this->resArr;  //  返回微信二维码地址

            } else if ($statusCode == 204) { //处理成功,无返回Body

                $this->resArr['msg'] = '暂无body';

                return $this->resArr;;

            }

        } catch (RequestException $e) {

            // 进行错误处理

            echo $e->getMessage() . "\n";

            $this->resArr['msg'] = $e->getMessage();

            if ($e->hasResponse()) {

                $this->resArr['msg'] = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";

            }

            return $this->resArr;

        }

    }

}

2.2 Demo.php 代码如下

<?php

namespace app\pay\controller;

class Demo

{

    /**

     * 说明:

     * 1.可自行选择安装sodium扩展

     * 2.参数请自行配置,所需参数:商户密钥文件、商户号、appid、v3密钥、证书序列号等

     * 3.wechat控制器:封装支付、查询等接口方法

     * 4.Notify控制器:回调通知示例

     * 5.GetCert控制器:封装下载证书方法,参数无误可直接使用

     * 6.AesUtil控制器:封装解密方法

    */


    /**

     * navicate支付

     * 参数:{ total:金额,单位(分),out_trade_no:订单号,notify_url }

    */

   function navicate(){

       $pay = new Wechat('wechat');

       //   参数请看上面注释

       $res = $pay->navicate(1, 'br' . time() . '1654', 'https://api.open.sdbaizhi.com/open/site/not');

       halt($res);

   }

}

2.3 AesUtil.php 代码如下

<?php



namespace app\pay\controller;



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');

    }

}

2.4 GetCert.php 代码如下

<?php

declare (strict_types=1);

namespace app\pay\controller;


class GetCert

{

    /**

     * 通过接口获取微信支付平台证书

     *

     */

    private $apiclient_key; //  商户私钥文件目录

    public function __construct($apiclient_key)

    {

        $this->apiclient_key = !empty($apiclient_key) ?? root_path() . '/app/pay/cert/apiclient_key.pem';

    }


    /**

     * 下载证书

     */

    public function makeCert()

    {

        //  获取平台证书列表url地址

        $uri = 'https://api.mch.weixin.qq.com/v3/certificates';

        //  请求头部

        $header = [

            'Authorization:' . $this->makeAuthorization(),  //  身份认证信息

            'Content-Type:application/json',

            'Accept:application/json',

            'User-Agent:' . $_SERVER['HTTP_USER_AGENT']

        ];

        //  获取请求结果

        $res = json_decode(curlGet($uri, $header), true);

        $associated_data = '';

        $nonce = '';

        $ciphertext = '';

        foreach ($res['data'] as $k => $v) {

            $associated_data = $v['encrypt_certificate']['associated_data'];

            $nonce = $v['encrypt_certificate']['nonce'];

            $ciphertext = $v['encrypt_certificate']['ciphertext'];

        }

        $aesUitl = new AesUtil(config('wechat.aesKey'));

        //  证书解密

        $certText = $aesUitl->decryptToString($associated_data, $nonce, $ciphertext);

        //  生成证书文件

        $filePath = root_path() . '/app/pay/cert/wechat.pem';

        $file = fopen($filePath, 'a');

        fwrite($file, $certText);

        fclose($file);

        if (file_exists($filePath)) return true;

        return false;

    }


    /**

     * 生成身份认证信息

     */

    protected function makeAuthorization($http_method = 'GET')

    {

        $url = 'https://api.mch.weixin.qq.com/v3/certificates'; //  请求地址

        $timestamp = time();    //  时间戳

        $nonce = getRandStr();  //  随机字符串

        $body = ''; //  请求体

        $url_parts = parse_url($url);

        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));

        $message = $http_method . "\n" .

            $canonical_url . "\n" .

            $timestamp . "\n" .

            $nonce . "\n" .

            $body . "\n";

        //  读取商户私钥

        $mch_private_key = file_get_contents($this->apiclient_key);

        //  使用商户私钥对待签名串进行SHA256 with RSA签名

        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');

        //  对签名结果进行Base64编码得到签名值

        $sign = base64_encode($raw_sign);

        //  组合身份认证信息

        $schema = 'WECHATPAY2-SHA256-RSA2048';

        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',

            $this->merchantId, $nonce, $timestamp, config('wechat.merchantSerialNumber'), $sign);

        return $schema . ' ' . $token;

    }

}

2.5 Notify.php 代码如下

<?php

declare (strict_types=1);

namespace app\pay\controller;


use app\common\model\Order;


class Notify

{


    /**

     *

     * 回调通知

     * 此处只选择我认为可用部分,其他内容请根据文档自行定义

     * 微信签名暂未认证

    */

    public function notify()

    {

        //  接收微信回调通知并转成数组,注意:v3版本以json格式回调,不再使用xml格式;以post请求

        $postData = input('post.');

        if ($postData['summary'] != '支付成功') return false;

        $sign = request()->header('Wechatpay-Signature');   //  接收微信签名

        //  信息解密

        $associated_data = $postData['resource']['associated_data'];

        $nonce = $postData['resource']['nonce'];

        $ciphertxt = $postData['resource']['ciphertext'];

        $aes = new AesUtil(config('wechat.aesKey'));

        $res = json_decode($aes->decryptToString($associated_data, $nonce, $ciphertxt), true);  //  解密文件并生成数组

        if (empty($res)) return false;

        //  判断appid与商户id是否一致

        if ($res['appid'] != config('wechat.appid') && $res['mchid'] != config('wechat.merchantId')) return false;

        //  其他信息暂未列出,具体根据自己实际情况使用

        $orderData = [

            'out_trade_no' => $res['out_trade_no'],   //  订单号

            'transaction_id' => $res['transaction_id'],   //  微信支付订单号

            'trade_type' => $res['trade_type'],   //  交易类型

            'total' => $res['amount']['total'],   //  订单总金额,单位(分)

            'payer_total' => $res['amount']['payer_total'],   //  用户支付金额,单位(分)

            'openid' => $res['payer']['openid'],   //  支付者openid

            'success_time' => $res['success_time']  //  支付完成时间

        ];

        $order = new Order();

        try {

            $saveData = $order->save($orderData);

            if ($saveData !== false) {

                json(['code' => 'SUCCESS', 'message' => '成功']);

            }

            json(['code' => 'ERRPR', 'message' => '失败']);

        } catch (\Exception $e) {

            echo $e->getMessage();

            exit();

        }

    }

}

2.6 config里面参数如下

<?php

//  微信配置

return [

    'merchantId' => '商户号',   // 微信支付商户号

    'merchantSerialNumber' => '证书序列号',   //  证书序列号

    'appid' => 'appid',    //  公众号appid

    'aesKey' => 'v3密钥',    //  v3密钥

];

更多信息:柏知网

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值