PHP實現 APP端微信支付功能
阿新 • • 發佈:2018-11-02
1.我封裝好的一個支付類檔案,多餘的東西都去除掉了,並且把配置引數放到了這個支付類中,只需要修改Weixinpayandroid方法內的幾個引數就可以直接複製使用:
class Wxpayandroid { //引數配置 public $config = array( 'appid' => "", /*微信開放平臺上的應用id*/ 'mch_id' => "", /*微信申請成功之後郵件中的商戶id*/ 'api_key' => "", /*在微信商戶平臺上自己設定的api金鑰 32位*/ ); //伺服器非同步通知頁面路徑(必填) public $notify_url= ''; //商戶訂單號(必填,商戶網站訂單系統中唯一訂單號) public $out_trade_no = ''; //商品描述(必填,不填則為商品名稱) public $body = ''; //付款金額(必填) public $total_fee = 0; //自定義超時(選填,支援dhmc) public $time_expire = ''; private $WxPayHelper; public function Weixinpayandroid($total_fee,$tade_no) { $this->total_fee = intval($total_fee* 100);//訂單的金額 1元 $this->out_trade_no = $tade_no;// date('YmdHis') . substr(time(), - 5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));//訂單號 $this->body = 'wxpay';//支付描述資訊 $this->time_expire = date('YmdHis', time() + 86400);//訂單支付的過期時間(eg:一天過期) $this->notify_url = "http://www.ceshi.com/notifyandroid";//非同步通知URL(更改支付狀態) //資料以JSON的形式返回給APP $app_response = $this->doPay(); if (isset($app_response['return_code']) && $app_response['return_code'] == 'FAIL') { $errorCode = 100; $errorMsg = $app_response['return_msg']; $this->echoResult($errorCode, $errorMsg); } else { $errorCode = 0; $errorMsg = 'success'; $responseData = array( 'notify_url' => $this->notify_url, 'app_response' => $app_response, ); $this->echoResult($errorCode, $errorMsg, $responseData); } } //介面輸出 function echoResult($errorCode = 0, $errorMsg = 'success', $responseData = array()) { $arr = array( 'errorCode' => $errorCode, 'errorMsg' => $errorMsg, 'responseData' => $responseData, ); exit(json_encode($arr)); //exit可以正常傳送給APP json資料 // return json_encode($arr); //在TP5中return這個json資料,APP接收到的是null,無法正常吊起微信支付 } function getVerifySign($data, $key) { $String = $this->formatParameters($data, false); //簽名步驟二:在string後加入KEY $String = $String . "&key=" . $key; //簽名步驟三:MD5加密 $String = md5($String); //簽名步驟四:所有字元轉為大寫 $result = strtoupper($String); return $result; } function formatParameters($paraMap, $urlencode) { $buff = ""; ksort($paraMap); foreach ($paraMap as $k => $v) { if($k=="sign"){ continue; } if ($urlencode) { $v = urlencode($v); } $buff .= $k . "=" . $v . "&"; } $reqPar; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff) - 1); } return $reqPar; } /** * 得到簽名 * @param object $obj * @param string $api_key * @return string */ function getSign($obj, $api_key) { foreach ($obj as $k => $v) { $Parameters[strtolower($k)] = $v; } //簽名步驟一:按字典序排序引數 ksort($Parameters); $String = $this->formatBizQueryParaMap($Parameters, false); //簽名步驟二:在string後加入KEY $String = $String."&key=".$api_key; //簽名步驟三:MD5加密 $result = strtoupper(md5($String)); return $result; } /** * 獲取指定長度的隨機字串 * @param int $length * @return Ambigous <NULL, string> */ function getRandChar($length){ $str = null; $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; $max = strlen($strPol)-1; for($i=0;$i<$length;$i++){ $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介於min和max兩個數之間的一個隨機整數 } return $str; } /** * 陣列轉xml * @param array $arr * @return string */ function arrayToXml($arr) { $xml = "<xml>"; foreach ($arr as $key=>$val) { if (is_numeric($val)) { $xml.="<".$key.">".$val."</".$key.">"; } else $xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; } $xml.="</xml>"; return $xml; } /** * 以post方式提交xml到對應的介面url * * @param string $xml 需要post的xml資料 * @param string $url url * @param bool $useCert 是否需要證書,預設不需要 * @param int $second url執行超時時間,預設30s * @throws WxPayException */ function postXmlCurl($xml, $url, $second=30, $useCert=false, $sslcert_path='', $sslkey_path='') { $ch = curl_init(); //設定超時 curl_setopt($ch, CURLOPT_TIMEOUT, $second); curl_setopt($ch,CURLOPT_URL, $url); //設定header curl_setopt($ch, CURLOPT_HEADER, FALSE); //要求結果為字串且輸出到螢幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); if($useCert == true){ curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//嚴格校驗 //設定證書 //使用證書:cert 與 key 分別屬於兩個.pem檔案 curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLCERT, $sslcert_path); curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLKEY, $sslkey_path); } //post提交方式 curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); //執行curl $data = curl_exec($ch); //返回結果 if($data){ curl_close($ch); return $data; } else { $error = curl_errno($ch); curl_close($ch); return false; } } /** * 獲取當前伺服器的IP * @return Ambigous <string, unknown> */ function get_client_ip() { if (isset($_SERVER['REMOTE_ADDR'])) { $cip = $_SERVER['REMOTE_ADDR']; } elseif (getenv("REMOTE_ADDR")) { $cip = getenv("REMOTE_ADDR"); } elseif (getenv("HTTP_CLIENT_IP")) { $cip = getenv("HTTP_CLIENT_IP"); } else { $cip = "127.0.0.1"; } return $cip; } /** * 將陣列轉成uri字串 * @param array $paraMap * @param bool $urlencode * @return string */ function formatBizQueryParaMap($paraMap, $urlencode) { $buff = ""; ksort($paraMap); foreach ($paraMap as $k => $v) { if($urlencode) { $v = urlencode($v); } $buff .= strtolower($k) . "=" . $v . "&"; } $reqPar; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff)-1); } return $reqPar; } /** * XML轉陣列 * @param unknown $xml * @return mixed */ function xmlToArray($xml) { //將XML轉為array $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $array_data; } public function chkParam() { //使用者網站訂單號 if (empty($this->out_trade_no)) { die('out_trade_no error'); } //商品描述 if (empty($this->body)) { die('body error'); } if (empty($this->time_expire)){ die('time_expire error'); } //檢測支付金額 if (empty($this->total_fee) || !is_numeric($this->total_fee)) { die('total_fee error'); } //非同步通知URL if (empty($this->notify_url)) { die('notify_url error'); } if (!preg_match("#^http:\/\/#i", $this->notify_url)) { $this->notify_url = "http://" . $_SERVER['HTTP_HOST'] . $this->notify_url; } return true; } /** * 生成支付(返回給APP) * @return boolean|mixed */ public function doPay() { //檢測構造引數 $this->chkParam(); return $this->createAppPara(); } /** * APP統一下單 */ private function createAppPara() { $url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; $data["appid"] = $this->config['appid'];//微信開放平臺稽核通過的應用APPID $data["body"] = $this->body;//商品或支付單簡要描述 $data["mch_id"] = $this->config['mch_id'];//商戶號 $data["nonce_str"] = $this->getRandChar(32);//隨機字串 $data["notify_url"] = $this->notify_url;//通知地址 $data["out_trade_no"] = $this->out_trade_no;//商戶訂單號 $data["spbill_create_ip"] = $this->get_client_ip();//終端IP $data["total_fee"] = $this->total_fee;//總金額 $data["time_expire"] = $this->time_expire;//交易結束時間 $data["trade_type"] = "APP";//交易型別 $data["sign"] = $this->getSign($data, $this->config['api_key']);//簽名 $xml = $this->arrayToXml($data); $response = $this->postXmlCurl($xml, $url); //將微信返回的結果xml轉成陣列 $responseArr = $this->xmlToArray($response); if(isset($responseArr["return_code"]) && $responseArr["return_code"]=='SUCCESS'){ return $this->getOrder($responseArr['prepay_id']); } return $responseArr; } /** * 執行第二次簽名,才能返回給客戶端使用 * @param int $prepayId:預支付交易會話標識 * @return array */ public function getOrder($prepayId) { $data["appid"] = $this->config['appid']; $data["noncestr"] = $this->getRandChar(32); $data["package"] = "Sign=WXPay"; $data["partnerid"] = $this->config['mch_id']; $data["prepayid"] = $prepayId; $data["timestamp"] = time(); $data["sign"] = $this->getSign($data, $this->config['api_key']); $data["packagestr"] = "Sign=WXPay"; return $data; } /** * 非同步通知資訊驗證 * @return boolean|mixed */ public function verifyNotify() { $xml = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : ''; if(!$xml){ return false; } $wx_back = $this->xmlToArray($xml); if(empty($wx_back)){ return false; } $checkSign = $this->getVerifySign($wx_back, $this->config['api_key']); if($checkSign=$wx_back['sign']){ return $wx_back; }else{ return false; } } }
2.建立控制器定義統一下單介面和支付後的非同步回撥介面:
//非同步通知介面 public function notifyandroid() { $wxpayandroid = new \Wxpayandroid; //例項化微信支付類 $verify_result = $wxpayandroid->verifyNotify(); if ($verify_result['return_code']=='SUCCESS' && $verify_result['result_code']=='SUCCESS') { //商戶訂單號 $out_trade_no = $verify_result['out_trade_no']; //交易號 $trade_no = $verify_result['transaction_id']; //交易狀態 $trade_status = $verify_result['result_code']; //支付金額 $total_fee = $verify_result['total_fee']/100; //支付過期時間 $pay_date = $verify_result['time_end']; $order = new Order(); $ret = $order->getOrderN2($out_trade_no); //獲取訂單資訊 $total_amount=$ret['money']; if ($total_amount==$total_fee) { // 驗證成功 修改資料庫的訂單狀態等 $result['out_trade_no']為訂單號 //此處寫自己的邏輯程式碼 } exit('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'); }else{ exit('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>'); } } //呼叫統一下單介面生成預支付訂單並把資料返回給APP public function wxpayandroid(Request $request) { $param = $request->param(); //接收值 $tade_no = $param['orderCode']; $order = new Order(); //例項化訂單 $ret = $order->getOrderN2($tade_no); //查詢訂單資訊 $total_fee = $ret['money']; //訂單總金額 $wxpayandroid = new \Wxpayandroid; //例項化微信支付類 $res = $wxpayandroid->Weixinpayandroid($total_fee,$tade_no); //呼叫weixinpay方法 }
封裝一個支付類檔案,並把配置引數放到支付類內,再定義控制器建立兩個方法,這樣兩步就可以把手機APP微信支付搞定啦。