小程式線上支付教程二
上一章節我們講解了小程式線上支付的前期準備工作,這一章我們將講解如何編寫支付介面。之前我們也說了,我使用的是thinkphp5,因此希望大家在看我這篇文章的時候瞭解一下thinkphp5。
一、相關函式
/** * 密碼字符集 * @param int $length * @return string */ public function generateNonceStr($length=16){ // 密碼字符集,可任意新增你需要的字元 $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $str = ""; for($i = 0; $i < $length; $i++) { $str .= $chars[mt_rand(0, strlen($chars) - 1)]; } return $str; } //獲取IP public function GetIP() { static $ip = NULL; if($ip !== NULL) return $ip; if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); $pos = array_search('unknown',$arr); if(false !== $pos) unset($arr[$pos]); $ip= trim($arr[0]); } else if(isset($_SERVER['HTTP_CLIENT_IP'])){ $ip = $_SERVER['HTTP_CLIENT_IP']; }else if(isset($_SERVER['REMOTE_ADDR'])){ $ip = $_SERVER['REMOTE_ADDR']; } //IP地址合法驗證 $ip = (false !== ip2long($ip)) ? $ip : '0.0.0.0'; return $ip; } /** * 獲取訂單序號 * @return string */ public function getOrder() { //訂單號碼主體(YYYYMMDDHHIISSNNNNNNNN) $order_id_main = date('YmdHis') . rand(10000000,99999999); //訂單號碼主體長度 $order_id_len = strlen($order_id_main); $order_id_sum = 0; for($i=0; $i<$order_id_len; $i++){ $order_id_sum += (int)(substr($order_id_main,$i,1)); } //唯一訂單號碼(YYYYMMDDHHIISSNNNNNNNNCC) return $order_id_main . str_pad((100 - $order_id_sum % 100) % 100,2,'0',STR_PAD_LEFT); } /** * 返回提示資訊 * @param $code string 錯誤碼4001 空值4002 格式不正確4003 長度4004 提示200正確放回 ,0失敗 * @param $msg string 錯誤描述 * @param $data string 返回值 * @return array */ public function resMap($code, $msg, $data) { $map = array(); $map['errMsg'] = $msg; $map['data'] = $data; $map['errCode'] = $code; return json($map); }
二、簽名函式
/** * 生成簽名, $KEY就是支付key * @return string 簽名 */ public function MakeSign($params,$KEY){ //簽名步驟一:按字典序排序陣列引數 ksort($params); $string = $this->ToUrlParams($params);//引數進行拼接key=value&k=v Log::write('引數進行拼接:' .$string); //簽名步驟二:在string後加入KEY $string2 = $string . "&key=".$KEY; //簽名步驟三:MD5加密 $string3 = md5($string2); //簽名步驟四:所有字元轉為大寫 $result = strtoupper($string3); return $result; } /** * 將引數拼接為url: key=value&key=value * @param $params * @return string */ public function ToUrlParams($params){ $string = ''; if(!empty($params)){ $array = array(); foreach( $params as $key => $value ){ $array[] = $key.'='.$value; } $string = implode("&",$array); } return $string; }
三、HTTP請求
/** * 呼叫介面, $data是陣列引數 * @return string 簽名 */ public function http_request($url,$data = null,$headers=array()) { $curl = curl_init(); if( count($headers) >= 1 ){ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); if (!empty($data)){ curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($curl); curl_close($curl); return $output; } //獲取xml裡面資料,轉換成array private function xml2array($xml){ $p = xml_parser_create(); xml_parse_into_struct($p, $xml, $vals, $index); xml_parser_free($p); $data = ""; foreach ($index as $key=>$value) { if($key == 'xml' || $key == 'XML') continue; $tag = $vals[$value[0]]['tag']; $value = $vals[$value[0]]['value']; $data[$tag] = $value; } return $data; } /** * 將xml轉為array * @param $xml * @return bool|mixed */ public function xml_to_array($xml){ if(!$xml) { return false; } //將XML轉為array libxml_disable_entity_loader(true); $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $data; } public function post_data(){ $receipt = $_REQUEST; if($receipt==null){ $receipt = file_get_contents("php://input"); if($receipt == null){ $receipt = $GLOBALS['HTTP_RAW_POST_DATA']; } } return $receipt; }
在這裡,我要說明一下,在支付的時候我們需要商戶號和商戶金鑰,這兩樣可以登入微信支付平臺獲取,相關截圖如下:

商戶金鑰

商戶號
四、支付函式
/** * 付款 * @param $total_fee int 金額 * @param $openid string 使用者OPENID * @param $order_id string 訂單號 * @return array */ publicfunction Pay($total_fee,$openid,$order_id){ if(empty($total_fee)){ return $this->resMap(4002,'金額有誤','金額有誤'); } if(empty($openid)){ return $this->resMap(4002,'登入失效,請重新登入','登入失效,請重新登入'); } if(empty($order_id)){ return $this->resMap(4002,'訂單不存在','訂單不存在'); } $appid = 'xxx';//如果是公眾號 就是公眾號的appid;小程式就是小程式的appid $body = '訂單支付'; $mch_id ='xxx'; //這是商戶號,登入微信支付平臺就能看到,參考教程一 $KEY = 'xxxx'; //這裡是商戶的支付金鑰 $nonce_str =$this->generateNonceStr();//隨機字串 $notify_url ='https://xxxxxxx/rest/xiao_notify_url';//支付完成回撥地址url,不能帶引數 $out_trade_no = $order_id;//商戶訂單號 $spbill_create_ip = $this->GetIP(); $trade_type = 'JSAPI';//交易型別 預設JSAPI //這裡是按照順序的 因為下面的簽名是按照(字典序)順序 排序錯誤 肯定出錯 $post['appid'] = $appid; $post['body'] = $body; $post['mch_id'] = $mch_id; $post['nonce_str'] = $nonce_str;//隨機字串 $post['notify_url'] = $notify_url; $post['openid'] = $openid; $post['out_trade_no'] = $out_trade_no; $post['spbill_create_ip'] = $spbill_create_ip;//伺服器終端的ip $post['total_fee'] = intval($total_fee) * 100;//總金額 最低為一分錢 必須是整數 $post['trade_type'] = $trade_type; Log::write($post); $sign = $this->MakeSign($post,$KEY);//簽名 Log::write($sign); //列印日誌 $post['sign'] = $sign; ksort($post); //簽名生成以後,還要進行一次排序,如果缺少這一步,你的簽名永遠都是“簽名錯誤”的提示,這裡我被坑過,PHP用的函式是ksort $post_xml = '<xml> <appid>'.$post['appid'].'</appid> <body>'.$post['body'].'</body> <mch_id>'.$post['mch_id'].'</mch_id> <nonce_str>'.$post['nonce_str'].'</nonce_str> <notify_url>'.$post['notify_url'].'</notify_url> <openid>'.$post['openid'].'</openid> <out_trade_no>'.$post['out_trade_no'].'</out_trade_no> <spbill_create_ip>'.$post['spbill_create_ip'].'</spbill_create_ip> <total_fee>'.$post['total_fee'].'</total_fee><trade_type>'.$post['trade_type'].'</trade_type><sign>'.$post['sign'].'</sign></xml> '; Log::write($post_xml);//列印日誌 //統一下單介面prepay_id $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; $xml = $this->http_request($url,$post_xml);//POST方式請求http $array = $this->xml2array($xml);//將【統一下單】api返回xml資料轉換成陣列,全要大寫 Log::write($array); if($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS'){ $time = time(); $tmp='';//臨時陣列用於簽名 $tmp['appId'] = $appid; $tmp['nonceStr'] = $nonce_str; $tmp['package'] = 'prepay_id='.$array['PREPAY_ID']; $tmp['signType'] = 'MD5'; $tmp['timeStamp'] = "$time"; //這裡的引數都是參考文件來的,地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 $data['state'] = 1; $data['timeStamp'] ="$time";//時間戳 $data['nonceStr'] = $nonce_str;//隨機字串 $data['signType'] = 'MD5';//簽名演算法,暫支援 MD5 $data['package'] = 'prepay_id='.$array['PREPAY_ID'];//統一下單介面返回的 prepay_id 引數值,提交格式如:prepay_id=* $data['paySign'] = $this->MakeSign($tmp,$KEY);//簽名,具體簽名方案參見微信公眾號支付幫助文件; $data['out_trade_no'] = $out_trade_no; $data['RETURN_CODE'] = $array['RETURN_CODE']; $data['RETURN_MSG'] = $array['RETURN_MSG']; Log::write($tmp); Log::write($data); return $this->resMap(200, $data, $data); }else{ $data['state'] = 0; $data['text'] = "錯誤"; $data['RETURN_CODE'] = $array['RETURN_CODE']; $data['RETURN_MSG'] = $array['RETURN_MSG']; return $this->resMap(4002, $data, $data); } }
五、回撥函式
當支付成功後,我們需要提供一個回撥函式,幫助我們更改訂單狀態
/** * 支付成功後跳轉 */ public function xiao_notify_url() { $post = $this->post_data();//接受POST資料XML個數 $post_data = $this->xml_to_array($post);//微信支付成功,返回回撥地址url的資料:XML轉陣列Array $postSign = $post_data['sign']; unset($post_data['sign']); ksort($post_data);// 對資料進行排序 $str = $this->ToUrlParams($post_data);//對陣列資料拼接成key=value字串 $user_sign = strtoupper(md5($str));//再次生成簽名,與$postSign比較 $out_trade_no = $post_data['out_trade_no']; Log::write('訂單好:' . $out_trade_no); $orderModel = model('Order'); $res = $orderModel->getOrderMoney($out_trade_no); Log::write('支付成功後跳轉:' .$orderModel->getLastSql()); if($post_data['return_code']=='SUCCESS'&&$postSign) { if ($res['g_state'] == 2) { return $this->return_success(); } else { //修改訂單狀態 $result = $orderModel->updateOrderStatus($out_trade_no,2); Log::write('支付成功後跳轉2:' .$orderModel->getLastSql()); if ($result) { //訂單更新成功 $this->return_success(); } else { return $this->resMap(4002,'訂單更新失敗','訂單更新失敗'); } } } else { return $this->resMap(4002,'微信支付失敗','微信支付失敗'); } } /* * 給微信傳送確認訂單金額和簽名正確,SUCCESS資訊 -xzz0521 */ public function return_success(){ $return['return_code'] = 'SUCCESS'; $return['return_msg'] = 'OK'; $xml_post = '<xml> <return_code>'.$return['return_code'].'</return_code> <return_msg>'.$return['return_msg'].'</return_msg> </xml>'; return $this->resMap(200, $this->xml2array($xml_post), $this->xml2array($xml_post)); }
以上就是支付介面的核心程式碼,下一章節我們將講解小程式中如何使用我們剛剛寫的介面,謝謝大家。