微信v3支付-php

准备工作:

PHP版本>=7.3
appid               // 应用id 
merchantId          // 商户id 
v3_key              // v3支付秘钥 
serial_no           // api证书序列号 
merchantPrivateKey   // 商户私钥证书 
wechatpayCertificate // 商户公钥证书

1.下载官方sdk并添加依赖

sdk下载地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml

composer require wechatpay/wechatpay-guzzle-middleware  // 下载完成后在项目目录下执行
// 或者 在项目的composer.json中加入以下配置
"require": {
    "wechatpay/wechatpay-guzzle-middleware": "^0.2.0"
}
// 之后执行安装
composer install

2.获取签名字符串

官方地址: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_0.shtml

// 组合数据
GET\n                                 // 提交方式
/v3/certificates\n                     // 签名接口 固定
1554208460\n                          // 时间戳
593BEC0C930BF1AFEB40B4A08C8FB242\n      // 随机字符串 32位 数字+大写字母

// 执行命令获得签名字符串
echo -n -e \
"GET\n/v3/certificates\n1554208460\n593BEC0C930BF1AFEB40B4A08C8FB242\n\n" \
  | openssl dgst -sha256 -sign apiclient_key.pem \
  | openssl base64 -A

3.生成加密报文

// 需要参数
mchid       // 商户id
serial_no   // 证书序列号
nonce_str   // 随机数
timestamp   // 时间戳
signature   // 上一步获取的签名字符串

// 执行命令获取签名
curl https://api.mch.weixin.qq.com/v3/certificates -H 'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"'
3.1 获取证书序列号
方法1. 登陆商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号
方法2. 通过命令行 openssl x509 -in 公钥证书地址 -noout -serial
得到 serial=1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C
方法3. 通过PHP方法 openssl_x509_read(file_get_contents($filepath));
方法4. 通过解析工具 工具地址:https://myssl.com/cert_decode.html 上传公钥证书

正确返回后可得如下参数:

{
    "data": [
        {
            "effective_time": "2020-10-21T10:15:24+08:00",
            "encrypt_certificate": {
                "algorithm": "AEAD_AES_256_GCM",    // 加密算法
                "associated_data": "certificate",   // 附加数据包(可能为空)
                "ciphertext": "XvGG+ezFmogjOojaNWUvjLmMLl0Y0MZCWr9bp9dm5Pvv9WxdHswPb7K8ynrattd51rg/m9hw1+S51mkKz63kMfjDdR03G5M1rnEog9Aq0ITbqc3nAxGh8EsTh0jd6StGefOkVqCKydUuSg5IJpL3cqB0hz6ILrTp96V7Cb+id9z9R+rr2QUe05o8u3Uxone7VezSm3qzj4/Gberb1irVuqbqkaMxCGH4XU+PeMXds5DeYHhM23HxuOmyD4b1sSg/YuUWqU1XmBwZdgt/X/eykWixfnwfB3r8LxYEisu5uHev6PgFECnq0oeiTHiEiIFNLLme5mqCyise+R7i1XwFQXiguQLeYOVTmS0vjwdFVvu6QJfxn0FPd6qPc0Kb3D5+xZsEMrxuvGT0sjqix2Df/o/+j8W4fr6gMOlge8EVB4UWni4fVT14zp53OTFQuhnw3kmSSUztVnVhYwwaWvVLnHkxMm87Va3dKubvO/O2TvutmDmgGPydpvqgXdcWBd1E/kYk/kRB+V6/M5sCYb7QM3HiQvdKBxbnuaQCBrnZ5tHPZdxpqmxsh7ZoKLqsTSUXYyMs+rRr1LKpEUbtUNtG3aIwpNNaQe3SU8cpw0QhFMTS4UPOErfY2pL9h/oPKtq4SG6hZtwsJdfXgSfQtBv1fhngHSdoMsG4KNGQAIwT4utmzu07LLYBQg5g0bznEQ+OHisDlDUI1w2lwbLTei8LE9o/rdV5Sty9VHHR1e8CqzuY84cnxlHa2NBmtIF47q5tpnNYHznODjWvWIOtKOY4Z83t7qgyX9S355+9vQYXin4mxiPTnhrmeoBI8Us4AeUsCqI9qkozbnyjkHQ4jdW8LJnUcKj0suseuVbEykN0OsFYplQ2Z1S62EPn8034KFVCaCLbJKrqkbBKQ9rSoDdYtgug780q4akT81jBxIYv7fx7ARRh8rOsSTSdhOjc6rnRoLBdsp4+zGoAFPjiIXieBAO8bpw/nlnWR60evcnvbEFeusvMHAEqjbdMN/nIB6Y6Ii4LYziOpc8nVlPd18rAOVa6CpuIpRXgUdNQl2G9KOpSJ3jTYJSG+jwdQHowQLp9aUdCA2YpGf4pMMgRz8yhK+2J0d2eqALnYog/5AuWjs8BF65odMxKYQ9WUSjei/x4kFbAPxd6fPnvtnFVk+Qo7IZ47N/5RRoBCEpDxK0dLY7uJF1gblKbWWbn+c0bQqzVkfhCBObOvP/u9YiDnuhHD6yBOkYhkAtNeamITNie7KN3/XqlbVmh9TJkaYzlLw8PGQ1K4La6q9wBdE95IKInsrIFc9urrF8dn2f7v/k3iaCUf9y10BD6K21zoOpIUN55Eh5s+e54Nh/1G5gVT3fbA2nZ3/Bs8jbkyIpplRzzG75zX5flLJuxBmJPblhpLJqpCWZeXcBOXe2SiDQTwpnDqyKpxPXqYQWKVc+cxaekc1iSBs/g6C51lkinGMTn/npbTuI6No01v/JA925+MFLRxBWZ6Lsd3WBQZVqgbLpNre6/Bm63lMkA4NfBcdfhd9ffhHKuol2hRLzhIPSnrEFQgsx8tkUASjYIEKR+IBJZ3vlmCjBYPJJZUbal0qj4Ing0JWEoZh28ehOs1+7CBZWTbapPe/M887PPsDQP5gN6Jjn19PubCwOPP7NxCUm0rrAzaY17QN7oIE26uSp1ZRD4j/2v6Lhzhu+X7qD7FYtqEo54Fg1ZqcZ4vDrAdidkrfjNupyKytnTacjM3ZWusHNB1DzqImlx82/3aZDnhiMv1nySjjO2zdkapl5OIxjERhyeC6anQqDprSFRtU3KqudA6/tfAUfN0g9RaUlDmz0vv8Tm29uo9/ThCSscMi73npYzrR8HvUS4pvg2slzJJtV5IYnZSGsD0w==",   // Base64编码后的密文
                "nonce": "5a45b7152b39" // 加密使用的随机串初始化向量
            },
            "expire_time": "2025-10-20T10:15:24+08:00",             // 过期时间
            "serial_no": "6AD4207FA35A4AB5CE3BC166AD08F58BEBFD4A4B"
        }
    ]
}

4.解密报文 获得证书

const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
public function decryptToString(){
        $aesKey         = '';   // v3支付秘钥
        $associatedData = '';   // 同上 associated_data
        $nonceStr       = '';   // 同上 nonce     
        $ciphertext     = '';   // 同上 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, $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, $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);
          dump(44);
          return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr,
              $authTag, $associatedData);
      }
      throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }

5.将证书放到项目中与私钥文件同一目录

6.使用(以App支付为例) 支付接口: https://api.mch.weixin.qq.com/v3/pay/transactions/app

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

protected $appid                = '';  // 应用ID
protected $merchantId           = '';   // 商户ID
protected $AppSecret            = '';   // AppSecret
protected $v3_key               = '';
protected $serial_no            = '';  // api证书序列号
protected $merchantPrivateKey   = './cert/apiclient_key.pem';  // 商户私钥
protected $wechatpayCertificate = './cert/cert.pem';          // 上面解密后得到的证书
protected $apppay = 'https://api.mch.weixin.qq.com/v3/pay/transactions/app';
public function return_data($code,$msg,$data=array()){
    return array('code' => $code,'msg'=>$msg,'data'=>$data);
}
/**
* @Notes: app支付
* @Interface pay
* @param $description      商品描述
* @param $out_trade_no     商户单号
* @param $notify_url       回调地址
* @param $total            支付金额 分
* @author: Chengzhitao
* @Time: 2021/6/9 15:43
*/
public function pay($description,$out_trade_no,$notify_url,int $total){

    // 构造一个WechatPayMiddleware
    $wechatpayMiddleware = WechatPayMiddleware::builder()
        $merchantPrivateKey   = PemUtil::loadPrivateKey($this->merchantPrivateKey);
        $wechatpayCertificate = PemUtil::loadCertificate($this->wechatpayCertificate);
        ->withMerchant($this->merchantId, $this->serial_no, $merchantPrivateKey) // 传入商户相关配置
        ->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
        ->build();

    // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
    $stack = HandlerStack::create();
    $stack->push($wechatpayMiddleware, 'wechatpay');

    // 创建Guzzle HTTP Client时,将HandlerStack传入
    $client = new Client(['handler' => $stack]);

    // 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
    try {
        $resp = $client->request('POST', $this->apppay, [
            'json' => [ // JSON请求体
                'appid'        => $this->appid,
                'mchid'        => $this->merchantId,
                'description'  => $description,
                'out_trade_no' => $out_trade_no,
                'notify_url'   => $notify_url,
                'amount'       => [
                    'total'    => $total,
                    'currency' => 'CNY'
                ]
            ],
            'headers' => [
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
                'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'     // 模拟手机
            ]
        ]);

        if ($resp->getStatusCode() == 200) {
            $rep = json_decode($resp->getBody(),true);
            $data = [
                'appid'     => $this->appid,
                'partnerid' => $this->merchantId,   // 商户号
                'prepayid'  => $rep['prepay_id'],   // 预支付id
                'package'   => 'Sign=WXPay',
                'noncestr'  => $this->nonce_str(),
                'timestamp' => time()
            ];
            $data['sign'] = $this->sign($data);
            $data = $this->return_data(1,'成功',$data);
        }else{
            $data = $this->return_data(0,$resp->getReasonPhrase());
        }
    } catch (RequestException $e) {
        $data = $this->return_data(0,$e->getMessage());            
    }
    return $data;
}
/**
 * v3 二签签名
 * [sign description]
 * @Author   念天地之悠悠
 * @DateTime 2021-06-11
 * @param    [type]     $data [description]
 * @return   [type]           [description]
 */
public function sign($data){
    $message = $data['appid']."\n".
        $data['timestamp']."\n".
        $data['noncestr']."\n".
        $data['prepayid']."\n";
    openssl_sign($message, $raw_sign, openssl_get_privatekey(file_get_contents($this->merchantPrivateKey)), 'sha256WithRSAEncryption');
    return base64_encode($raw_sign);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容