config = $config; $merchantPrivateKey = PemUtil::loadPrivateKey($this->config["PrivateKey"]); // 商户私钥文件路径 // 微信支付平台配置 $wechatpayCertificate = PemUtil::loadCertificate($this->config["Certificate"]); // 微信支付平台证书文件路径 // 构造一个WechatPayMiddleware $wechatpayMiddleware = WechatPayMiddleware::builder() ->withMerchant($this->config["MCHID"],$this->config["merchantSerialNumber"], $merchantPrivateKey) // 传入商户相关配置 ->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array ->build(); // 将WechatPayMiddleware添加到Guzzle的HandlerStack中 $stack = HandlerStackWx::create(); $stack->push($wechatpayMiddleware, 'wechatpay'); // 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签 $this->client = new GuzzleHttpClient(['handler' => $stack]); } /** * 微信小程序支付 * @param type $post */ public function wxmpPay($post=[]){ $apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; $jsonData = []; //商户信息 $jsonData["appid"] = $this->config["APPID"]; $jsonData["mchid"] = $this->config["MCHID"]; $jsonData["notify_url"] = $this->config["NOTIFY_URL"]; //必填参数 $jsonData["description"] = $post["description"];//描述 $jsonData["out_trade_no"] = $post["out_trade_no"];//商户订单号 $jsonData["amount"] = [ "total"=> floatval($post["total"])*100,//订单金额,分 ]; // $jsonData["scene_info"] = [//支付场景描述 // "payer_client_ip"=>$post["payer_client_ip"],//客户端IP // ]; $jsonData["time_expire"] = date("Y-m-d\TH:i:s+08:00",time()+30*60);//交易结束时间2018-06-08T10:34:56+08:00 $jsonData["payer"]=[ "openid"=>$post["openid"] ]; $result = $this->clientHttp("POST", $apiUrl, $jsonData); if(empty($result)){ $this->errorMsg = "支付错误001"; return false; } $resuleAr = json_decode($result,true); if(empty($resuleAr)){ $this->errorMsg = "支付错误002"; return false; } if(empty($resuleAr["prepay_id"])){ $this->errorMsg = "支付错误003"; return false; } //组装支付参数 $payInfo=array(); $info['appId'] = $this->config["APPID"]; $info['timeStamp'] = time(); $info['nonceStr'] = $this->random_number(); //生成随机数,下面有生成实例,统一下单接口需要 $info["package"] = "prepay_id=".$result['prepay_id']; $info['signType'] = 'MD5'; $info['paySign'] = $this->MakeSign($info); return $info; } /** * h5支付 */ public function wapPay($post=[]){ $apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/h5"; $jsonData = []; //商户信息 $jsonData["appid"] = $this->config["APPID"]; $jsonData["mchid"] = $this->config["MCHID"]; $jsonData["notify_url"] = $this->config["NOTIFY_URL"]; //必填参数 $jsonData["description"] = $post["description"];//描述 $jsonData["out_trade_no"] = $post["out_trade_no"];//商户订单号 $jsonData["amount"] = [ "total"=> floatval($post["total"])*100,//订单金额,分 ]; $jsonData["scene_info"] = [//支付场景描述 "payer_client_ip"=>$post["payer_client_ip"],//客户端IP "h5_info"=>[//h5场景信息 "type"=>"Wap",//iOS, Android, Wap ] ]; $jsonData["time_expire"] = date("Y-m-d\TH:i:s+08:00",time()+30*60);//交易结束时间2018-06-08T10:34:56+08:00 $result = $this->clientHttp("POST", $apiUrl, $jsonData); if($result){ $resuleAr = json_decode($result,true); if(empty($resuleAr) || empty($resuleAr["h5_url"])){ $this->errorMsg = "h5_url不存在"; return false; } return $resuleAr["h5_url"]; } return false; } /** * app支付 */ public function appPay($post=[]){ $apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/app"; $jsonData = []; //商户信息 $jsonData["appid"] = $this->config["APPID"]; $jsonData["mchid"] = $this->config["MCHID"]; $jsonData["notify_url"] = $this->config["NOTIFY_URL"]; //必填参数 $jsonData["description"] = $post["description"];//描述 $jsonData["out_trade_no"] = $post["out_trade_no"];//商户订单号 $jsonData["amount"] = [ "total"=> floatval($post["total"])*100,//订单金额,分 ]; $jsonData["scene_info"] = [//支付场景描述 "payer_client_ip"=>$post["payer_client_ip"],//客户端IP ]; $jsonData["time_expire"] = date("Y-m-d\TH:i:s+08:00",time()+30*60);//交易结束时间2018-06-08T10:34:56+08:00 $this->clientHttp("POST", $apiUrl, $jsonData); } /** * 商户订单号 * @param type $out_trade_no */ public function searchNotifyOrder($out_trade_no){ $apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{$out_trade_no}?mchid={$this->config["MCHID"]}"; return $this->clientHttp("GET", $apiUrl); } /** * 关闭支付订单 * @param type $out_trade_no */ public function closePayOrder($out_trade_no){ $apiUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{$out_trade_no}/close"; $postData=array( "out_trade_no"=>$out_trade_no, "mchid"=>$this->config["MCHID"], ); return $this->clientHttp("POST", $apiUrl,$postData); } /** * Decrypt AEAD_AES_256_GCM ciphertext * * @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 */ public function decryptToString($associatedData, $nonceStr, $ciphertext) { if (strlen($this->config['aesKey']) != self::KEY_LENGTH_BYTE) { $this->errorMsg = "无效的ApiV3Key,长度应为32个字节"; return false; } $ciphertext = \base64_decode($ciphertext); if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) { $this->errorMsg = "字符长度错误"; 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->config['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, $this->config['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); return \openssl_decrypt($ctext, 'aes-256-gcm', $this->config['aesKey'], \OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData); } $this->errorMsg = "AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php"; return false; // throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php'); } /** * http提交 */ public function clientHttp($type='POST',$url='',$json=[]){ $httpData = []; if($type == 'POST'){ $httpData["json"] = $json; } $httpData["headers"] = ['Accept' => 'application/json']; $resp = $this->client->request($type,$url,$httpData); $statusCode = $resp->getStatusCode(); if ($statusCode == 200) { //处理成功 return $resp->getBody()->getContents(); } else if ($statusCode == 204) { //处理成功,无返回Body $this->errorMsg = "处理成功,无返回Body"; return false; }else{ $this->errorMsg = "未知错误"; return false; } // try{ // $resp = $this->client->request($type,$url,$httpData); // $statusCode = $resp->getStatusCode(); // var_dump($statusCode); // if ($statusCode == 200) { //处理成功 // return $resp->getBody()->getContents(); // } else if ($statusCode == 204) { //处理成功,无返回Body // $this->errorMsg = "处理成功,无返回Body"; // return false; // }else{ // $this->errorMsg = "未知错误"; // return false; // } // } catch (RequestException $e){ // var_dump($e); // // 进行错误处理 // $this->errorMsg = $e->getMessage(); // if ($e->hasResponse()) { // $this->errorMsg = $e->getResponse()->getBody()->getContents(); //// $this->errorMsg = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody(); // } // return false; // } } /** * 生成签名 * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值 */ public function MakeSign($values) { //签名步骤一:按字典序排序参数 ksort($values); $string = $this->ToUrlParams($values); //签名步骤二:在string后加入KEY $string = $string . "&key=" . \WxPayConfig::KEY; //签名步骤三:MD5加密 $string = md5($string); //签名步骤四:所有字符转为大写 $result = strtoupper($string); return $result; } /** * 参数数组转换为url参数 * @param array $urlObj */ private function ToUrlParams($urlObj) { $buff = ""; foreach ($urlObj as $k => $v) { $buff .= $k . "=" . $v . "&"; } $buff = trim($buff, "&"); return $buff; } /** * 生成随机字符串 * @param type $len * @param type $format * @return type */ public function random_number($len = 21, $format = 'ALL') { $is_abc = $is_numer = 0; $password = $tmp = ''; switch ($format) { case 'ALL': $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; break; case 'CHAR': $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; break; case 'NUMBER': $chars = '0123456789'; break; default : $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; break; } mt_srand((double) microtime() * 1000000 * getmypid()); while (strlen($password) < $len) { $tmp = substr($chars, (mt_rand() % strlen($chars)), 1); if (($is_numer <> 1 && is_numeric($tmp) && $tmp > 0 ) || $format == 'CHAR') { $is_numer = 1; } if (($is_abc <> 1 && preg_match('/[a-zA-Z]/', $tmp)) || $format == 'NUMBER') { $is_abc = 1; } $password .= $tmp; } if ($is_numer <> 1 || $is_abc <> 1 || empty($password)) { $password = $this->random_number($len, $format); } return $password; } }