最近项目中需要使用微信支付,决定使用微信官方V3 SDK版本接口 项目支持的环境如下: Guzzle 7.0,PHP >= 7.2.5Guzzle 6.5,PHP >= 7.1.2 安装 compos
最近项目中需要使用微信支付,决定使用微信官方V3 SDK版本接口
项目支持的环境如下:
composer require wechatpay/wechatpay
ℹ️ 以下是 微信支付 API v3 的指引。如果你是 api v2 的使用者,请看 README_APIv2。
商户 API 证书,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称 CA)签发,以防证书被伪造或篡改。详情见 什么是商户API证书?如何获取商户API证书? 。
商户 API 私钥。你申请商户 API 证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。为了证明 API 请求是由你发送的,你应使用商户 API 私钥对请求进行签名。
⚠️ 不要把私钥文件暴露在公共场合,如上传到 GitHub,写在 App 代码中等。
ℹ️ 你需要先手工 下载平台证书 才能使用 SDK 发起请求。
按官方文档使用各种报错,估计是因为有其他的库引起的
composer exec CertificateDownloader.PHP -- -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
public static function certificates() { $wxPayConfig = config('wx_pay_config'); $api_v3_key = $wxPayConfig['api_v3_key'] ?? ''; // 商户号 $result = self::wx_curl_query('v3/certificates','GET'); $result = JSON_decode($result, true); $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? ''; $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? ''; $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? ''; $decryptedMsg = AesGCm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data); $result['decryptedMsg'] = ($decryptedMsg); //解密后的内容,就是证书内容 dump($result); } //get请求 public static function wx_curl_query($urlV3 = '', $Http_method = 'POST', $body = '') { $url = 'https://api.mch.weixin.qq.com/' . $urlV3; $config = config('wx_pay_config');; $mchId = $config['mchid'] ?? ''; // 商户ID $serial_no = $config['cert_serial'] ?? ''; // 证书序列号 $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId; // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem'; $mch_private_key = openssl_get_privatekey(file_get_contents($merchantPrivateKeyFilePath));;//私钥 if(is_array($body)){ $body = json_encode($body); // 接口参数 } // 生成token 验签 $timestamp = time();//时间戳 $nonce = FORMatter::nonce(32);//随机串 $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";//报文主体 //计算签名值 openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); //设置HTTP头 $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $mchId, $nonce, $timestamp, $serial_no, $sign); $headers = [ 'Accept: application/json', 'User-Agent: * static function getCert() { // 设置参数 [$instance, $wxPayConfig] = self::init(); $api_v3_key = $wxPayConfig['api_v3_key'] ?? ''; // 「商户API证书」的「证书序列号」 // 发送请求 $resp = $instance->chain('v3/certificates')->get(// ['debug' => true] // 调试模式,https://docs.guzzlephp.org/en/stable/request-options.html#debug ); $result = json_decode($resp->getBody(), true); $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? ''; $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? ''; $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? ''; $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data); $result['ciphertext_msg'] = $decryptedMsg; dump($result); } static $merchantPrivateKeyInstance; // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 public static function init() { // 设置参数 $wxPayConfig = config('wx_pay_config'); $mchId = $wxPayConfig['mchid'] ?? ''; // 商户号 $serial = $wxPayConfig['cert_serial'] ?? ''; // 「商户API证书」的「证书序列号」 $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId; // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem'; self::$merchantPrivateKeyInstance = $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 $platformCertificateFilePath = $certPath . '/cert.pem'; $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); // 从「微信支付平台证书」中获取「证书序列号」 $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); // 构造一个 APIv3 客户端实例 $instance = Builder::factory([ 'mchid' => $mchId, 'serial' => $serial, 'privateKey' => $merchantPrivateKeyInstance, 'certs' => [ $platformCertificateSerial => $platformPublicKeyInstance, ], ]); return [$instance, $wxPayConfig]; }
static function pay($payData = [], $notifyUrl = '') { $notifyUrl = $notifyUrl ?? (config('host_api') . '/v1/notify/wx_notify'); [$instance, $wxPayConfig] = self::init(); // 赋值给变量 $merchantId = $wxPayConfig['mchid'] ?? ''; // 商户号 $appid = $wxPayConfig['appid'] ?? ''; // 商户号 // 调用下单 $payJsonData = [ 'mchid' => $merchantId, 'appid' => $appid, 'out_trade_no' => $payData['out_trade_no'] ?? 0, 'description' => $payData['description'] ?? 0, 'notify_url' => $notifyUrl, 'amount' => [ 'total' => $payData['money'] ?? 0, // 分 'currency' => 'CNY' ], ]; try { $resp = $instance ->chain('v3/pay/transactions/app') ->post(['json' => $payJsonData]); if ($resp->getStatusCode() == 200) { $wo = json_decode($resp->getBody(), true); //prepay_id $params = [ 'partnerId' => $appid, 'timeStamp' => (string)Formatter::timestamp(), 'nonceStr' => Formatter::nonce(), 'prepayId' => $wo["prepay_id"], ]; $params += ['sign' => Rsa::sign( Formatter::joinedByLineFeed(...array_values($params)), self::$merchantPrivateKeyInstance )]; return [1, $params]; } else { return [0, '微信支付失败']; } } catch (\Exception $e) { $code = $e->getCode(); $data = ''; // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); $data = json_decode($r->getBody(), true); LogHelperUtil::outLog("wx_pay_error", $data, "wx_pay"); } } return [$code, $data]; // end }
static function notify($header, $inBody) { $inWechatpaySignature = $header['wechatpay-signature']; $inWechatpayTimestamp = $header['wechatpay-timestamp']; $inWechatpaySerial = $header['wechatpay-serial']; $inWechatpayNonce = $header['wechatpay-nonce']; if (!$inWechatpaySignature or !$inWechatpayTimestamp or !$inWechatpaySerial or !$inWechatpayNonce) { header("Location:/404.html"); exit; } $wxPayConfig = config('wx_pay_config'); $merchantId = $wxPayConfig['mchid'] ?? ''; // 商户号 $apiv3Key = $wxPayConfig['api_v3_key'] ?? '';// 在商户平台上设置的APIv3密钥 $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $merchantId; // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 $platformCertificateFilePath = $certPath . '/cert.pem'; // 根据通知的平台证书序列号,查询本地平台证书文件,这里是自己生成的证书 $platformPublicKeyInstance = Rsa::from(file_get_contents($platformCertificateFilePath), Rsa::KEY_TYPE_PUBLIC); // 检查通知时间偏移量,允许5分钟之内的偏移 $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp); $verifiedStatus = Rsa::verify( // 构造验签名串 Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody), $inWechatpaySignature, $platformPublicKeyInstance ); if ($timeOffsetStatus && $verifiedStatus) { // 转换通知的JSON文本消息为PHP Array数组 $inBodyArray = (array)json_decode($inBody, true); // 使用PHP7的数据解构语法,从Array中解构并赋值变量 ['resource' => [ 'ciphertext' => $ciphertext, 'nonce' => $nonce, 'associated_data' => $aad ]] = $inBodyArray; // 加密文本消息解密 $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad); // 把解密后的文本转换为PHP Array数组 return (array)json_decode($inBodyResource, true); } else { return ['trade_state' => 'FAIL']; } }
static function refundOrder($orderSn, $tradeNo, $refundMoney) { [$instance, $wxPayConfig] = self::init(); try { $response = $instance ->chain('v3/refund/domestic/refunds') ->post([ 'json' => [ 'transaction_id' => $tradeNo, 'out_refund_no' => $orderSn, 'amount' => ['refund' => $refundMoney,'total' => $refundMoney,'currency' => 'CNY', ], ], ]); // 正常逻辑回调处理 $code = $response->getStatusCode(); $response = json_decode($response->getBody(), true); $status = $response['status'] ?? ''; if ($code != 200) { DingDingUtil::sendDingtalkText("微信退款未知原因!订单号:{$orderSn} 错误原因:" . json_encode($response)); } return [1, $response]; } catch (\Exception $e) { // 异常错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); $code = $r->getStatusCode(); $response = json_decode($r->getBody(), true); $subCode = $response['code'] ?? ''; $subMsg = $response['message'] ?? ''; $listErrorCode = [ 'NOT_ENOUGH', //余额不足', 'MCH_NOT_EXISTS', //MCHID不存在', 'NO_AUTH', //没有退款权限', ]; if (in_array($subCode, $listErrorCode)) { DingDingUtil::sendDingtalkText("微信退款失败!\n订单号:{$orderSn} \n错误原因:" . $subMsg); } return [$code, $response]; } } return [0, '退款失败']; }
namespace pay;use util\DingDingUtil;use util\LogHelperUtil;use WeChatPay\Builder;use WeChatPay\ClientJsonTrait;use WeChatPay\Crypto\AesGcm;use WeChatPay\Crypto\Rsa;use WeChatPay\Formatter;use WeChatPay\Util\PemUtil;class PayWxUtil{ static $merchantPrivateKeyInstance; // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 public static function init() { // 设置参数 $wxPayConfig = config('wx_pay_config'); $mchId = $wxPayConfig['mchid'] ?? ''; // 商户号 $serial = $wxPayConfig['cert_serial'] ?? ''; // 「商户API证书」的「证书序列号」 $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId; // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem'; self::$merchantPrivateKeyInstance = $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 $platformCertificateFilePath = $certPath . '/cert.pem'; $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); // 从「微信支付平台证书」中获取「证书序列号」 $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); // 构造一个 APIv3 客户端实例 $instance = Builder::factory([ 'mchid' => $mchId, 'serial' => $serial, 'privateKey' => $merchantPrivateKeyInstance, 'certs' => [ $platformCertificateSerial => $platformPublicKeyInstance, ], ]); return [$instance, $wxPayConfig]; } static function pay($payData = [], $notifyUrl = '') { $notifyUrl = $notifyUrl ?? (config('host_api') . '/v1/notify/wx_notify'); [$instance, $wxPayConfig] = self::init(); // 赋值给变量 $merchantId = $wxPayConfig['mchid'] ?? ''; // 商户号 $appid = $wxPayConfig['appid'] ?? ''; // 商户号 // 调用下单 $payJsonData = [ 'mchid' => $merchantId, 'appid' => $appid, 'out_trade_no' => $payData['out_trade_no'] ?? 0, 'description' => $payData['description'] ?? 0, 'notify_url' => $notifyUrl, 'amount' => [ 'total' => $payData['money'] ?? 0, // 分 'currency' => 'CNY' ], ]; try { $resp = $instance ->chain('v3/pay/transactions/app') ->post(['json' => $payJsonData]); if ($resp->getStatusCode() == 200) { $wo = json_decode($resp->getBody(), true); //prepay_id $params = [ 'partnerId' => $appid, 'timeStamp' => (string)Formatter::timestamp(), 'nonceStr' => Formatter::nonce(), 'prepayId' => $wo["prepay_id"], ]; $params += ['sign' => Rsa::sign( Formatter::joinedByLineFeed(...array_values($params)), self::$merchantPrivateKeyInstance )]; return [1, $params]; } else { return [0, '微信支付失败']; } } catch (\Exception $e) { $code = $e->getCode(); $data = ''; // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); $data = json_decode($r->getBody(), true); LogHelperUtil::outLog("wx_pay_error", $data, "wx_pay"); } } return [$code, $data]; // end } static function refundOrder($orderSn, $tradeNo, $refundMoney) { [$instance, $wxPayConfig] = self::init(); try { $response = $instance ->chain('v3/refund/domestic/refunds') ->post([ 'json' => [ 'transaction_id' => $tradeNo, 'out_refund_no' => $orderSn, 'amount' => ['refund' => $refundMoney,'total' => $refundMoney,'currency' => 'CNY', ], ], ]); // 正常逻辑回调处理 $code = $response->getStatusCode(); $response = json_decode($response->getBody(), true); $status = $response['status'] ?? ''; if ($code != 200) { DingDingUtil::sendDingtalkText("微信退款未知原因!订单号:{$orderSn} 错误原因:" . json_encode($response)); } return [1, $response]; } catch (\Exception $e) { // 异常错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); $code = $r->getStatusCode(); $response = json_decode($r->getBody(), true); $subCode = $response['code'] ?? ''; $subMsg = $response['message'] ?? ''; $listErrorCode = [ 'NOT_ENOUGH', //余额不足', 'MCH_NOT_EXISTS', //MCHID不存在', 'NO_AUTH', //没有退款权限', ]; if (in_array($subCode, $listErrorCode)) { DingDingUtil::sendDingtalkText("微信退款失败!\n订单号:{$orderSn} \n错误原因:" . $subMsg); } return [$code, $response]; } } return [0, '退款失败']; } static function notify($header, $inBody) { $inWechatpaySignature = $header['wechatpay-signature']; $inWechatpayTimestamp = $header['wechatpay-timestamp']; $inWechatpaySerial = $header['wechatpay-serial']; $inWechatpayNonce = $header['wechatpay-nonce']; if (!$inWechatpaySignature or !$inWechatpayTimestamp or !$inWechatpaySerial or !$inWechatpayNonce) { header("Location:/404.html"); exit; } $wxPayConfig = config('wx_pay_config'); $merchantId = $wxPayConfig['mchid'] ?? ''; // 商户号 $apiv3Key = $wxPayConfig['api_v3_key'] ?? '';// 在商户平台上设置的APIv3密钥 $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $merchantId; // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 $platformCertificateFilePath = $certPath . '/cert.pem'; // 根据通知的平台证书序列号,查询本地平台证书文件,这里是自己生成的证书 $platformPublicKeyInstance = Rsa::from(file_get_contents($platformCertificateFilePath), Rsa::KEY_TYPE_PUBLIC); // 检查通知时间偏移量,允许5分钟之内的偏移 $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp); $verifiedStatus = Rsa::verify( // 构造验签名串 Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody), $inWechatpaySignature, $platformPublicKeyInstance ); if ($timeOffsetStatus && $verifiedStatus) { // 转换通知的JSON文本消息为PHP Array数组 $inBodyArray = (array)json_decode($inBody, true); // 使用PHP7的数据解构语法,从Array中解构并赋值变量 ['resource' => [ 'ciphertext' => $ciphertext, 'nonce' => $nonce, 'associated_data' => $aad ]] = $inBodyArray; // 加密文本消息解密 $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad); // 把解密后的文本转换为PHP Array数组 return (array)json_decode($inBodyResource, true); } else { return ['trade_state' => 'FAIL']; } } static function getCert() { // 设置参数 [$instance, $wxPayConfig] = self::init(); $api_v3_key = $wxPayConfig['api_v3_key'] ?? ''; // 「商户API证书」的「证书序列号」 // 发送请求 $resp = $instance->chain('v3/certificates')->get(// ['debug' => true] // 调试模式,https://docs.guzzlephp.org/en/stable/request-options.html#debug ); $result = json_decode($resp->getBody(), true); $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? ''; $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? ''; $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? ''; $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data); $result['ciphertext_msg'] = $decryptedMsg; dump($result); } public static function certificates() { $wxPayConfig = config('wx_pay_config'); $api_v3_key = $wxPayConfig['api_v3_key'] ?? ''; // 商户号 $result = self::wx_curl_query('v3/certificates','GET'); $result = json_decode($result, true); $ciphertext = $result['data'][0]['encrypt_certificate']['ciphertext'] ?? ''; $nonce = $result['data'][0]['encrypt_certificate']['nonce'] ?? ''; $associated_data = $result['data'][0]['encrypt_certificate']['associated_data'] ?? ''; $decryptedMsg = AesGcm::decrypt($ciphertext, $api_v3_key, $nonce, $associated_data); $result['decryptedMsg'] = ($decryptedMsg); //解密后的内容,就是证书内容 dump($result); } //get请求 public static function wx_curl_query($urlV3 = '', $http_method = 'POST', $body = '') { $url = 'https://api.mch.weixin.qq.com/' . $urlV3; $config = config('wx_pay_config');; $mchId = $config['mchid'] ?? ''; // 商户ID $serial_no = $config['cert_serial'] ?? ''; // 证书序列号 $certPath = 'file://' . ROOT_PATH . 'cert/wx/' . $mchId; // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 $merchantPrivateKeyFilePath = $certPath . '/apiclient_key.pem'; $mch_private_key = openssl_get_privatekey(file_get_contents($merchantPrivateKeyFilePath));;//私钥 if(is_array($body)){ $body = json_encode($body); // 接口参数 } // 生成token 验签 $timestamp = time();//时间戳 $nonce = Formatter::nonce(32);//随机串 $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";//报文主体 //计算签名值 openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); //设置HTTP头 $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $mchId, $nonce, $timestamp, $serial_no, $sign); $headers = [ 'Accept: application/json', 'User-Agent: */*', 'Content-Type: application/json; charset=utf-8', 'Authorization: ' . $token, "Wechatpay-Serial:{$serial_no}" ]; $info = curl_init(); curl_setopt($info, CURLOPT_RETURNTRANSFER, true); curl_setopt($info, CURLOPT_HEADER, 0); curl_setopt($info, CURLOPT_NOBODY, 0); curl_setopt($info, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($info, CURLOPT_SSL_VERIFYHOST, false); if ($http_method == 'POST') { curl_setopt($info, CURLOPT_POST, 1); curl_setopt($info, CURLOPT_POSTFIELDS, json_encode($body)); } //设置header头 curl_setopt($info, CURLOPT_HTTPHEADER, $headers); curl_setopt($info, CURLOPT_URL, $url); $output = curl_exec($info); curl_close($info); return $output; }}
来源地址:https://blog.csdn.net/qq_23564667/article/details/132169681
--结束END--
本文标题: PHP 微信支付V3版本SDK使用整理汇总
本文链接: https://www.lsjlt.com/news/432979.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0