PHP
PHP对接微信APP支付
PHP对接微信APP支付
前面跟大家分享了PHP对接支付宝APP支付,今天跟大家分享对接微信APP支付,还是参考之前微信V3加密那篇,自己封装函数,不使用微信的SDK,下面贴出相关代码,我这里使用的是thinkphp开发,大家可以参考代码后,根据实际情况进行修改。
一、微信类
我们先创建一个微信的类,命名为WxPay,代码如下
<?php /** * Created by PhpStorm. * User: zhangw * Date: 2024-03-23 * Time: 10:11 */ namespace app\service; /** * 微信支付 * Class WxPay * @package app\service */ class WxPay { //偏移量 const AUTH_TAG_LENGTH_BYTE = 16; /** * 获取微信支付参数 * @param string $outTradeNo * @param int $amount * @param string $notifyUrl * @return mixed */ static function getPayData($outTradeNo = "", $amount = 0 ,$notifyUrl = "" ) { #获取配置参数 $wxAppId = Config('appConfig.wx_pay_app_id'); //微信appId $wxMerchantId = Config('appConfig.wx_mch_id'); //商户号Id $wxApiSerialNo = Config('appConfig.wx_api_serial_no'); //商户API证书序列号 $wxMerchantApiPrivateKey = public_path() . "wx/apiclient_key.pem"; //商户私钥证书 #设置超时时间 $outTime = time() + 2 * 60; $timeExpire = date("Y-m-d", $outTime) . "T" . date("H:i:s", $outTime) . "+08:00"; #请求接口地址 $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app"; #订单金额转换【单位为分】 $amount = $amount * 100; #组合基本信息 $params = array( "appid" => $wxAppId, //appId "mchid" => $wxMerchantId, //直连商户号 "description" => "商品购买", //描述 "out_trade_no" => $outTradeNo, //商户订单号 "time_expire" => $timeExpire, //超时时间 "notify_url" => $notifyUrl, //回调地址 "amount" => array( //订单金额 "total" => $amount, //订单金额 "currency" => "CNY" //金额单位 ), ); #获取authorization $authorization = self::RequestSign("POST", $url, json_encode($params), $wxMerchantId, $wxMerchantApiPrivateKey, $wxApiSerialNo); #请求接口 $data = self::curlPostWithWx($url, $authorization, $params, 30); #返回 return $data; } /** * 获取呼起支付参数 * @param string $prepayId * @return array */ static function getPayParams($prepayId = "") { #获取配置参数 $wxAppId = Config('appConfig.wx_pay_app_id'); //微信appId $wxMerchantId = Config('appConfig.wx_mch_id'); //商户号Id $wxMerchantApiPrivateKey = public_path() . "wx/apiclient_key.pem"; //商户私钥证书 #获取当前时间戳 $timeStamp = time(); #生成一个随机字符串 $nonceStr = self::getNonceStr(); #构造签名串 $requestSign = sprintf("%s\n%s\n%s\n%s\n", $wxAppId, $timeStamp, $nonceStr, $prepayId); #计算计算签名值 $sign = self::calculateSignatureValue($requestSign, $wxMerchantApiPrivateKey); #组合参数 $params = array( "appid" => $wxAppId, "partnerid" => $wxMerchantId, "prepayid" => $prepayId, "package" => "Sign=WXPay", "noncestr" => $nonceStr, "timestamp" => (string)$timeStamp, "sign" => $sign ); #返回 return $params; } /** * 解密数据 * @param $associatedData * @param $nonceStr * @param $cipherText * @return bool|string */ public static function decryptToString($associatedData, $nonceStr, $cipherText) { $cipherText = \base64_decode($cipherText); if (strlen($cipherText) <= self::AUTH_TAG_LENGTH_BYTE) { return false; } //微信API v3密钥 $wxApiV3Key = Config('appConfig.wx_v3_key'); // 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, $wxApiV3Key); } // 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, $wxApiV3Key); } // 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', $wxApiV3Key, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData); } throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php'); } /** * 生成签名 * @param string $method * @param string $url * @param string $request * @param string $wxMerchantId * @param string $certKey * @param string $wxApiSerialNo * @return string */ static function RequestSign($method = "POST", $url = "", $request = "", $wxMerchantId = "", $certKey = "", $wxApiSerialNo = "") { #截取获取当前请求地址【去除域名】 $url_parts = parse_url($url); $path = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : "")); #获取当前时间戳 $timeStamp = time(); #生成一个随机字符串 $nonceStr = self::getNonceStr(); #构造签名串 $requestSign = sprintf("%s\n%s\n%s\n%s\n%s\n", $method, $path, $timeStamp, $nonceStr, $request); #计算计算签名值 $sign = self::calculateSignatureValue($requestSign, $certKey); #设置HTTP头获取Authorization $token = self::createToken($wxMerchantId, $nonceStr, $timeStamp, $wxApiSerialNo, $sign); #返回 return $token; } /** * 计算签名值 * @param $requestSign * @param $certKey * @return string * 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值 */ static function calculateSignatureValue($requestSign, $certKey) { $certKey = file_get_contents($certKey); openssl_sign($requestSign, $raw_sign, $certKey, 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); return $sign; } /** * 获取token * @param $merchant_id * @param $nonce * @param $timestamp * @param $serial_no * @param $sign * @return string */ static function createToken($merchant_id, $nonce, $timestamp, $serial_no, $sign) { $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign); return $token; } /** * 产生随机字符串,不长于32位 * @param int $length * @return string */ static 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; } /** * post请求 * @param string $url * @param string $authorization * @param array $params * @param int $timeout * @return mixed */ static function curlPostWithWx($url = "", $authorization = "", $params = array(), $timeout = 30) { $paramsString = json_encode($params); // 初始化curl $ch = curl_init(); // 设置超时 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); // post数据 curl_setopt($ch, CURLOPT_POST, 1); // post的变量 curl_setopt($ch, CURLOPT_POSTFIELDS, $paramsString); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($paramsString), 'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization, 'Accept: application/json', 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36' ) ); // 运行curl,结果以jason形式返回 $res = curl_exec($ch); curl_close($ch); // 取出数据 $data = json_decode($res, true); return $data; } }
二、新增配置
我这里是放在thinkphp的config下的appConfig.php文件下,分别是
//微信支付参数 'wx_pay_app_id' => '微信应用appId', 'wx_mch_id'=> '微信商户Id', 'wx_v3_key'=> '微信V3密钥Key', 'wx_api_serial_no'=> '微信商户API证书序列号',
然后我们在public目录下新建wx目录,将生成的私钥证书放到该目录下,并且命名为apiclient_key.pem。
三、获取前端需要支付参数
#初始化数据 $order_sn = "1223333" //订单编号 $total_money = 0.01; //订单金额 $notify_url = ""; //回调地址 #获取微信付款参数 $wxPay = new WxPay(); $wxPayInfo = $wxPay->getPayData($order_sn,$total_money,$notify_url); #获取支付参数 $wxPayUrl = $wxPay->getPayParams($wxPayInfo["prepay_id"]);
这样我们就可以获得前端呼起支付所有参数。
四、回调处理
我们同样新增一个接口来处理回调,代码如下
#获取用户参数 $signData = request()->post(); #记录请求参数 Log::write("微信回调",json_encode($signData)); #提取参数 $resource = isset($signData["resource"])?$signData["resource"]:[]; //加密信息体 #判断加密参数 if (!is_array($resource) || count($resource)<=0) { return json(["code"=>"FAIL","message"=>"加密体获取失败","msg"=>""]); } #判断加密要素是否存在 $associatedData = isset($resource["associated_data"]) ? $resource["associated_data"] : ""; $nonce = isset($resource["nonce"]) ? $resource["nonce"] : ""; $cipherText = isset($resource["ciphertext"]) ? $resource["ciphertext"] : ""; if (empty($associatedData) || empty($nonce) || empty($cipherText)) { return json(["code"=>"FAIL","message"=>"加密参数有误","msg"=>""]); } #解密数据 $wxPay = new WxPay(); $wxOrderInfo = $wxPay::decryptToString($associatedData, $nonce, $cipherText); if (empty($wxOrderInfo)) { return json(["code"=>"FAIL","message"=>"回调数据解密失败","msg"=>""]); } $wxOrderInfo = json_decode($wxOrderInfo, true); #提取数据 $tradeState = isset($wxOrderInfo["trade_state"])?$wxOrderInfo["trade_state"]:""; $outTradeNo = isset($wxOrderInfo["out_trade_no"])?$wxOrderInfo["out_trade_no"]:""; $successTime = isset($wxOrderInfo["success_time"])?$wxOrderInfo["success_time"]:""; $tradeNo = isset($wxOrderInfo["transaction_id"])?$wxOrderInfo["transaction_id"]:""; if(empty($tradeState) || empty($outTradeNo) || empty($successTime) || empty($tradeNo)){ return json(["code"=>"FAIL","message"=>"回调数据获取失败","msg"=>""]); } #判断是否支付成功 if($tradeState!="SUCCESS"){ return json(["code"=>"FAIL","message"=>"支付失败","msg"=>""]); } #获取支付用户Id $buyerId = $wxOrderInfo["payer"]["openid"]; #时间转化 $gmtPayment = strtotime($successTime);
以上是部分片段,这部分代码基本可以直接复制,下面的就是各自业务处理逻辑,我这里就不放上去了,仅供大家参考
以上就是thinkphp不通过微信SDK,进行微信APP支付以及回调处理。
0条评论