php 微信支付V3接口

堵彬彬
2023-12-01

注意事项

1,请求接口签名方式——RSA,用到了API证书序列号,后缀为key.pem的密钥文件,cert文件要用java自己生成,具体参考:https://developers.weixin.qq.com/community/develop/doc/000e4a0d5dc1486acc19c6fd15bc00?_at=1569021781371

2,返给小程序或者H5的支付参数签名也要RSA加密,而且仅支持RSA加密。

3,支付成功回调的数据要解密,根据php版本有的需要安装libsodium-php扩展:http://pecl.php.net/package/libsodium  我公司的服务器是php7.0的

wget http://pecl.php.net/get/libsodium-2.0.10.tgz
pecl install libsodium-2.0.10.tgz

php.ini 文件添加扩展,重启php就可以了

extension=sodium.so

安装官方sdk

composer require wechatpay/wechatpay-guzzle-middleware

代码

<?php


namespace app\store\controller\api;

use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Client;

/**
 *
 * Class PayV3
 * @package app\store\controller\api
 */
class PayV3
{

    // 商户相关配置
    public $spAppId;
    public $subAppId;
    public $merchantId; // 商户号
    public $merchantSerialNumber = ''; // 商户API证书序列号
    public $merchantPrivateKey ; // 商户证书私钥
    public $wechatpayCertificateV3; // 微信支付平台证书

    public $key ;
    public $keyV3 ;

    public $out_trade_no;

    public function __construct()
    {
        //服务商appid
        $this->spAppId = 'wxxxxxxxxxxxxxxxxx';
        //特约商户appid
        $this->subAppId = 'wxxxxxxxxxxxxxxxxx';
        //服务商商户号
        $this->merchantId = '160xxxxxxx';
        //特约商户商户号(测试用项目里做成变量)
        $this->subMerchantId = '160xxxxxxx';
        $this->key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
        //apiV3 key
        $this->keyV3 = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
        //微信商户号查看
        $this->merchantSerialNumber = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
        //微信商户号下载
        $this->merchantPrivateKey = PemUtil::loadPrivateKey('config/01jh_cert/apiclient_key.pem');
        //此文件是java环境生成的不是商户号下载的,具体自作参考:https://developers.weixin.qq.com/community/develop/doc/000e4a0d5dc1486acc19c6fd15bc00?_at=1569021781371
        //注意java版本 我之前的是1.8.0_131 不好使跟新为 1.8.0_291 生成成功了
        $this->wechatpayCertificateV3 = PemUtil::loadCertificate('config/01jh_cert/v3_cert.pem');
    }


    private function client() {
        $wechatpayMiddleware = WechatPayMiddleware::builder()
            ->withMerchant($this->merchantId, $this->merchantSerialNumber, $this->merchantPrivateKey) // 传入商户相关配置
            ->withWechatPay([ $this->wechatpayCertificateV3 ]) // 可传入多个微信支付平台证书,参数类型为array
            ->build();
        // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
        $stack = HandlerStack::create();
        $stack->push($wechatpayMiddleware, 'wechatpay');
        // 创建Guzzle HTTP Client时,将HandlerStack传入
        return new Client(['handler' => $stack]);
    }


    private function request($url,$data,$method='POST') {
        $client = $this->client();
        // 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
        try {
            $resp = $client->request($method, $url, [
                'headers' => ['Accept' => 'application/json', 'Content-Type' => 'application/json'],
                'json' => $data,
            ]);

            //echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
            //echo $resp->getBody()."\n";
            if($resp->getReasonPhrase() == 'OK') {
                return $resp->getBody();
            }
            return false;
        } catch (RequestException $e) {
            // 进行错误处理
            echo $e->getMessage();
//            echo $e->getMessage()."\n";
//            if ($e->hasResponse()) {
//                echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
//                echo $e->getResponse()->getBody();
//            }
        }
    }

    //小程序获取支付参数(使用时直接调取这个方法就ok了)
     public function jsapiParams($data) {
         $this->out_trade_no = $data['out_trade_no'];
        //这可以设置默认的参数,也可以不设置
        $d = [
            'sp_appid'            => $this->spAppId ,
            'sp_mchid'            => $this->merchantId,
            'sub_appid'           => $this->subAppId,
            //支付到特约商户上
            'sub_mchid'           => $this->subMerchantId,
            'description'         =>'自定义商品描述',
            'out_trade_no'        => $this->out_trade_no,
            'notify_url'          => 'https://xxxxx.xxxxx.com/ygyAdmin/public/store/api.v3pay/notify',
            'amount'              => ['total' => 1000],
            //'payer'               => ['sp_openid' => 'o8MCu5UvKGRs8gJsP_VNFLvZj_fM'],
            'payer'               => ['sub_openid' => 'ogD1q5Hz0bNdoPac0DYfV6jLcR0s'],
            //是否分账
            //'settle_info'         =>['profit_sharing' => true]
        ];
        foreach ($data as $k=>$v) $d[$k] = $v;

        $body = $this->request('https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi',$d);
        if($body === false) return false;
        $nonce_str = $this->getNoncestr();
        $prepay_id = json_decode($body,true)['prepay_id'];
        $time = time();
        //在哪个小程序下
        //$tmp['appId'] = $this->appid;
        $tmp['appId'] = $this->subAppId;
        $tmp['timeStamp'] = $time;
        $tmp['nonceStr'] = $nonce_str;
        $tmp['package'] = 'prepay_id='.$prepay_id;
        $data = array();
        $data['state'] = 200;
        $data['timeStamp'] = "$time";
        $data['nonceStr'] = $nonce_str;
        $data['package'] = 'prepay_id='.$prepay_id;//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
        $data['paySign'] = $this->sign($tmp);
        $data['out_trade_no'] = $this->out_trade_no;
        return $data;
    }

    //签名 $data要先排好顺序
    private function sign($data){
        $string = '';
        foreach ($data as $key=>$value){
            if(!$value) continue;
            $string .= $value."\n";
        }
        $private_key = $this->merchantPrivateKey;
        openssl_sign($string, $raw_sign, $private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        return $sign;
    }

    /**
     * 获取随机数
     */
    public function getNoncestr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 支付回调数据解密
     * @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->aesKeyV3);
        }

        // 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->aesKeyV3);
        }
        // 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->aesKeyV3, \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }
        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
}

 

 

 类似资料: