#!/usr/bin/env php parseOpts(); if (!$opts) { $this->printHelp(); exit(1); } if (isset($opts['help'])) { $this->printHelp(); exit(0); } if (isset($opts['version'])) { echo self::VERSION . "\n"; exit(0); } $this->downloadCert($opts); } private function downloadCert($opts) { try { // 构造一个WechatPayMiddleware $builder = WechatPayMiddleware::builder() ->withMerchant($opts['mchid'], $opts['serialno'], PemUtil::loadPrivateKey($opts['privatekey'])); // 传入商户相关配置 if (isset($opts['wechatpay-cert'])) { $builder->withWechatPay([ PemUtil::loadCertificate($opts['wechatpay-cert']) ]); // 使用平台证书验证 } else { $builder->withValidator(new NoopValidator); // 临时"跳过”应答签名的验证 } $wechatpayMiddleware = $builder->build(); // 将WechatPayMiddleware添加到Guzzle的HandlerStack中 $stack = HandlerStack::create(); $stack->push($wechatpayMiddleware, 'wechatpay'); // 创建Guzzle HTTP Client时,将HandlerStack传入 $client = new GuzzleHttp\Client(['handler' => $stack]); // 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签 $resp = $client->request('GET', 'https://api.mch.weixin.qq.com/v3/certificates', [ 'headers' => [ 'Accept' => 'application/json' ] ]); if ($resp->getStatusCode() < 200 || $resp->getStatusCode() > 299) { echo "download failed, code={$resp->getStatusCode()}, body=[{$resp->getBody()}]\n"; return; } $list = json_decode($resp->getBody(), true); $plainCerts = []; $x509Certs = []; $decrypter = new AesUtil($opts['key']); foreach ($list['data'] as $item) { $encCert = $item['encrypt_certificate']; $plain = $decrypter->decryptToString($encCert['associated_data'], $encCert['nonce'], $encCert['ciphertext']); if (!$plain) { echo "encrypted certificate decrypt fail!\n"; exit(1); } // 通过加载对证书进行简单合法性检验 $cert = \openssl_x509_read($plain); // 从字符串中加载证书 if (!$cert) { echo "downloaded certificate check fail!\n"; exit(1); } $plainCerts[] = $plain; $x509Certs[] = $cert; } // 使用下载的证书再来验证一次应答的签名 $validator = new WechatPay2Validator(new CertificateVerifier($x509Certs)); if (!$validator->validate($resp)) { echo "validate response fail using downloaded certificates!"; exit(1); } // 输出证书信息,并保存到文件 foreach ($list['data'] as $index => $item) { echo "Certificate {\n"; echo " Serial Number: ".$item['serial_no']."\n"; echo " Not Before: ".(new DateTime($item['effective_time']))->format('Y-m-d H:i:s')."\n"; echo " Not After: ".(new DateTime($item['expire_time']))->format('Y-m-d H:i:s')."\n"; echo " Text: \n ".str_replace("\n", "\n ", $plainCerts[$index])."\n"; echo "}\n"; $outpath = $opts['output'].DIRECTORY_SEPARATOR.'wechatpay_'.$item['serial_no'].'.pem'; file_put_contents($outpath, $plainCerts[$index]); } } catch (RequestException $e) { echo "download failed, message=[{$e->getMessage()}] "; if ($e->hasResponse()) { echo "code={$e->getResponse()->getStatusCode()}, body=[{$e->getResponse()->getBody()}]\n"; } exit(1); } catch (Exception $e) { echo "download failed, message=[{$e->getMessage()}]\n"; echo $e; exit(1); } } private function parseOpts() { $opts = [ [ 'key', 'k', true ], [ 'mchid', 'm', true ], [ 'privatekey', 'f', true ], [ 'serialno', 's', true ], [ 'output', 'o', true ], [ 'wechatpay-cert', 'c', false ], ]; $shortopts = 'hV'; $longopts = [ 'help', 'version' ]; foreach ($opts as $opt) { $shortopts .= $opt[1].':'; $longopts[] = $opt[0].':'; } $parsed = getopt($shortopts, $longopts); if (!$parsed) { return false; } $args = []; foreach ($opts as $opt) { if (isset($parsed[$opt[0]])) { $args[$opt[0]] = $parsed[$opt[0]]; } else if (isset($parsed[$opt[1]])) { $args[$opt[0]] = $parsed[$opt[1]]; } else if ($opt[2]) { return false; } } if (isset($parsed['h']) || isset($parsed['help'])) { $args['help'] = true; } if (isset($parsed['V']) || isset($parsed['version'])) { $args['version'] = true; } return $args; } private function printHelp() { echo <<] -f= -k= -m= -o= -s= -m, --mchid= 商户号 -s, --serialno= 商户证书的序列号 -f, --privatekey= 商户的私钥文件 -k, --key= ApiV3Key -c, --wechatpay-cert= 微信支付平台证书,验证签名 -o, --output= 下载成功后保存证书的路径 -V, --version Print version information and exit. -h, --help Show this help message and exit. EOD; } } class NoopValidator implements Validator { public function validate(\Psr\Http\Message\ResponseInterface $response) { return true; } } // main (new CertificateDownloader())->run();