<?php namespace App\Models; use WeChatPay\Builder; use WeChatPay\Crypto\Rsa; use WeChatPay\Util\PemUtil; /** * 实测 可用 相关配置弄好 * date: 2022/09/19 18:10 * desc: */ class PayV3 { private $v3_key; private $merchantId; private $merchantCertificateSerial; private $merchantPrivateKeyFilePath; private $platformCertificateFilePath; private $platformPublicKeyInstance; private $instance; const AUTH_TAG_LENGTH_BYTE = 16; public function __construct() { //证书生成命令(生成的证书在sdk目录 或 自己指定) //php ./bin/CertificateDownloader.php -k jiaxin**************uzhou20 -m 1614781491 -f /www/wwwroot/项目目录/server/public/cert/apiclient_key.pem -s 629BD177D7******************8CE43244 -o ./ // 商户号 $this->merchantId = env('WX_XCX_MCH_ID'); // v3api秘钥 $this->v3_key = env('WX_XCX_MCH_KEY_V3'); // 「商户API证书」的「证书序列号」 商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号 $this->merchantCertificateSerial = '75BFCD14831525581A301D0CD190330606153F0E'; // 商户API私钥 //$this->merchantPrivateKeyFilePath = $pay_config['apiclient_key']; //"file://" $this->merchantPrivateKeyFilePath = "file://" . public_path() . '/cert/apiclient_key.pem'; // ; // 微信支付平台证书 首次通过命令行生成 https://github.com/wechatpay-apiv3/wechatpay-php/tree/main/bin //$this->platformCertificateFilePath = $pay_config['apiclient_cert']; $this->platformCertificateFilePath = "file://" . public_path() . "/cert//wechatpay_38D2255A4177BE40747CE1ECAF6ED0812E7D00C8.pem"; // 构建客户端实例 $this->instance = $this->buildInstance(); //var_dump($this->merchantPrivateKeyFilePath,$this->platformCertificateFilePath);exit; } /** * 商家付款到零钱 * @param string $batch_sn 商户系统内部的商家批次单号 * @param string $out_detail_no 账明细单的唯一标识 * @param float $money 转账金额,单位:元 * @param string $openid 用户openid * @param string $remark 转账备注 * @param string $user_name 用户姓名,转账金额大于2000元,必填 * @return array * @throws \Exception */ public function transfer(string $batch_sn, string $out_detail_no, float $money, string $openid, string $remark, string $user_name = ''): array { $trans_detail = [ 'out_detail_no' => $out_detail_no, 'transfer_amount' => intval($money * 100), 'transfer_remark' => $remark, 'openid' => $openid, ]; if ($money >= 2000) { if (empty($user_name)) { throw new \Exception('提现金额大于2000元,真实姓名不能为空'); } $trans_detail['user_name'] = $this->encrypt($user_name); } $response = $this->instance->chain('v3/transfer/batches')->post([ 'json' => [ 'appid' => env('WX_XCX_APPID'), 'out_batch_no' => $batch_sn, 'batch_name' => '提现转账', 'batch_remark' => '提现转账', 'total_amount' => intval($money * 100), 'total_num' => 1, 'transfer_detail_list' => [ $trans_detail ] ] ]); return json_decode($response->getBody(), true); } /** * 转账明细查询 * @param string $out_batch_no 商户系统内部的商家批次单号 * @param string $out_detail_no 账明细单的唯一标识 * @return array */ public function transferQuery(string $out_batch_no, string $out_detail_no = ''): array { $api = 'v3/transfer/batches/out-batch-no/' . $out_batch_no; $query = []; if ($out_detail_no) { $api .= '/details/out-detail-no/' . $out_detail_no; } else { $query = [ 'query' => [ 'need_query_detail' => true, 'detail_status' => 'ALL' ] ]; } $response = $this->instance->chain($api)->get($query); return json_decode($response->getBody(), true); } // 自动更新平台证书 public function autoUpdatePlatformCertificateFile() { $result = $this->v3Certificates()['data']; $data = end($result); $effective_time = strtotime($data['effective_time']); if ($effective_time - time() > 0 && $effective_time - time() <= 86400) { $encrypt_certificate = $data['encrypt_certificate']; $cert_content = $this->decryptToString($encrypt_certificate['associated_data'], $encrypt_certificate['nonce'], $encrypt_certificate['ciphertext']); file_exists($this->platformCertificateFilePath) && file_put_contents($this->platformCertificateFilePath, $cert_content); } } // 获取平台证书 private function v3Certificates() { $response = $this->instance->chain('v3/certificates')->get(); return json_decode($response->getBody(), true); } /** * Decrypt AEAD_AES_256_GCM ciphertext * 用于解密v3Certificates接口数据获取证书 * https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml * @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 */ private 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->v3_key); } // 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->v3_key); } // 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->v3_key, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData); } throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php'); } // 加密 private function encrypt(string $msg): string { return Rsa::encrypt($msg, $this->platformPublicKeyInstance); } private function buildInstance(): \WeChatPay\BuilderChainable { // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 $merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); // 从本地文件中加载 微信支付平台证书 $this->platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); // 从「微信支付平台证书」中获取「证书序列号」 $platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath); // 构造一个 APIv3 客户端实例 return Builder::factory([ 'mchid' => $this->merchantId, 'serial' => $this->merchantCertificateSerial, 'privateKey' => $merchantPrivateKeyInstance, 'certs' => [ $platformCertificateSerial => $this->platformPublicKeyInstance, ], ]); } }