php CI 微信支付擴充套件 微信掃碼支付 jssdk 支付 退款
微信支付API類庫來自:https://github.com/zhangv/wechat-pay
請先看一眼官方場景及支付時序圖:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
官方API列表:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
二維碼生成類庫:phpqrcode
直接上程式碼 ### _**掃碼支付篇 **_ ``` class WechatPay { const TRADETYPE_JSAPI = 'JSAPI',TRADETYPE_NATIVE = 'NATIVE',TRADETYPE_APP = 'APP'; const URL_UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder"; const URL_ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery"; const URL_CLOSEORDER = 'https://api.mch.weixin.qq.com/pay/closeorder'; const URL_REFUND = 'https://api.mch.weixin.qq.com/secapi/pay/refund'; const URL_REFUNDQUERY = 'https://api.mch.weixin.qq.com/pay/refundquery'; const URL_DOWNLOADBILL = 'https://api.mch.weixin.qq.com/pay/downloadbill'; const URL_REPORT = 'https://api.mch.weixin.qq.com/payitil/report'; const URL_SHORTURL = 'https://api.mch.weixin.qq.com/tools/shorturl'; const URL_MICROPAY = 'https://api.mch.weixin.qq.com/pay/micropay'; /** * 錯誤資訊 */ public $error = null; /** * 錯誤資訊XML */ public $errorXML = null; /** * 微信支付配置陣列 * appid 公眾賬號appid * mch_id 商戶號 * apikey 加密key * appsecret 公眾號appsecret * sslcertPath 證書路徑(apiclient_cert.pem) * sslkeyPath 金鑰路徑(apiclient_key.pem) */ private $_config; /** * @param $config 微信支付配置陣列 */ public function __construct($config) { $this->_config = $config; } /** * JSAPI獲取prepay_id * @param $body * @param $out_trade_no * @param $total_fee * @param $notify_url * @param $openid * @return null */ public function getPrepayId($body,$out_trade_no,$total_fee,$notify_url,$openid) { $data = array(); $data["nonce_str"] = $this->get_nonce_string(); $data["body"] = $body; $data["out_trade_no"] = $out_trade_no; $data["total_fee"] = $total_fee; $data["spbill_create_ip"] = $_SERVER["REMOTE_ADDR"]; $data["notify_url"] = $notify_url; $data["trade_type"] = self::TRADETYPE_JSAPI; $data["openid"] = $openid; $result = $this->unifiedOrder($data); if ($result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { return $result["prepay_id"]; } else { $this->error = $result["return_code"] == "SUCCESS" ? $result["err_code_des"] : $result["return_msg"]; $this->errorXML = $this->array2xml($result); return null; } } private function get_nonce_string() { return substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"),0,32); } /** * 統一下單介面 */ public function unifiedOrder($params) { $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["device_info"] = (isset($params['device_info'])&&trim($params['device_info'])!='')?$params['device_info']:null; $data["nonce_str"] = $this->get_nonce_string(); $data["body"] = $params['body']; $data["detail"] = isset($params['detail'])?$params['detail']:null;//optional $data["attach"] = isset($params['attach'])?$params['attach']:null;//optional $data["out_trade_no"] = isset($params['out_trade_no'])?$params['out_trade_no']:null; $data["fee_type"] = isset($params['fee_type'])?$params['fee_type']:'CNY'; $data["total_fee"] = $params['total_fee']; $data["spbill_create_ip"] = $params['spbill_create_ip']; $data["time_start"] = isset($params['time_start'])?$params['time_start']:null;//optional $data["time_expire"] = isset($params['time_expire'])?$params['time_expire']:null;//optional $data["goods_tag"] = isset($params['goods_tag'])?$params['goods_tag']:null; $data["notify_url"] = $params['notify_url']; $data["trade_type"] = $params['trade_type']; $data["product_id"] = isset($params['product_id'])?$params['product_id']:null;//required when trade_type = NATIVE $data["openid"] = isset($params['openid'])?$params['openid']:null;//required when trade_type = JSAPI $result = $this->post(self::URL_UNIFIEDORDER, $data); return $result; } private function post($url, $data,$cert = false) { $data["sign"] = $this->sign($data); $xml = $this->array2xml($data); $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_URL, $url); if($cert == true){ //使用證書:cert 與 key 分別屬於兩個.pem檔案 curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLCERT, $this->_config['sslcertPath']); curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLKEY, $this->_config['sslkeyPath']); } $content = curl_exec($ch); $array = $this->xml2array($content); return $array; } /** * 資料簽名 * @param $data * @return string */ private function sign($data) { ksort($data); $string1 = ""; foreach ($data as $k => $v) { if ($v && trim($v)!='') { $string1 .= "$k=$v&"; } } $stringSignTemp = $string1 . "key=" . $this->_config["apikey"]; $sign = strtoupper(md5($stringSignTemp)); return $sign; } private function array2xml($array) { $xml = "<xml>" . PHP_EOL; foreach ($array as $k => $v) { if($v && trim($v)!='') $xml .= "<$k><![CDATA[$v]]></$k>" . PHP_EOL; } $xml .= "</xml>"; return $xml; } private function xml2array($xml) { $array = array(); $tmp = null; try{ $tmp = (array) simplexml_load_string($xml); }catch(Exception $e){} if($tmp && is_array($tmp)){ foreach ( $tmp as $k => $v) { $array[$k] = (string) $v; } } return $array; } /** * 掃碼支付(模式二)獲取支付二維碼 * @param $body * @param $out_trade_no * @param $total_fee * @param $notify_url * @param $product_id * @return null */ public function getCodeUrl($body,$out_trade_no,$total_fee,$notify_url,$product_id){ $data = array(); $data["nonce_str"] = $this->get_nonce_string(); $data["body"] = $body; $data["out_trade_no"] = $out_trade_no; $data["total_fee"] = $total_fee; $data["spbill_create_ip"] = $_SERVER["SERVER_ADDR"]; $data["notify_url"] = $notify_url; $data["trade_type"] = self::TRADETYPE_NATIVE; $data["product_id"] = $product_id; $result = $this->unifiedOrder($data); if ($result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { return $result["code_url"]; } else { $this->error = $result["return_code"] == "SUCCESS" ? $result["err_code_des"] : $result["return_msg"]; return null; } } /** * 查詢訂單 * @param $transaction_id * @param $out_trade_no * @return array */ public function orderQuery($transaction_id,$out_trade_no){ $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["transaction_id"] = $transaction_id; $data["out_trade_no"] = $out_trade_no; $data["nonce_str"] = $this->get_nonce_string(); $result = $this->post(self::URL_ORDERQUERY, $data); return $result; } /** * 關閉訂單 * @param $out_trade_no * @return array */ public function closeOrder($out_trade_no){ $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["out_trade_no"] = $out_trade_no; $data["nonce_str"] = $this->get_nonce_string(); $result = $this->post(self::URL_CLOSEORDER, $data); return $result; } /** * 申請退款 - 使用商戶訂單號 * @param $out_trade_no 商戶訂單號 * @param $out_refund_no 退款單號 * @param $total_fee 總金額(單位:分) * @param $refund_fee 退款金額(單位:分) * @param $op_user_id 操作員賬號 * @return array */ public function refund($out_trade_no,$out_refund_no,$total_fee,$refund_fee,$op_user_id){ $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["nonce_str"] = $this->get_nonce_string(); $data["out_trade_no"] = $out_trade_no; $data["out_refund_no"] = $out_refund_no; $data["total_fee"] = $total_fee; $data["refund_fee"] = $refund_fee; $data["op_user_id"] = $op_user_id; $result = $this->post(self::URL_REFUND, $data,true); return $result; } /** * 申請退款 - 使用微信訂單號 * @param $out_trade_no 商戶訂單號 * @param $out_refund_no 退款單號 * @param $total_fee 總金額(單位:分) * @param $refund_fee 退款金額(單位:分) * @param $op_user_id 操作員賬號 * @return array */ public function refundByTransId($transaction_id,$out_refund_no,$total_fee,$refund_fee,$op_user_id){ $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["nonce_str"] = $this->get_nonce_string(); $data["transaction_id"] = $transaction_id; $data["out_refund_no"] = $out_refund_no; $data["total_fee"] = $total_fee; $data["refund_fee"] = $refund_fee; $data["op_user_id"] = $op_user_id; $result = $this->post(self::URL_REFUND, $data,true); return $result; } /** * 下載對賬單 * @param $bill_date 下載對賬單的日期,格式:20140603 * @param $bill_type 型別 * @return array */ public function downloadBill($bill_date,$bill_type = 'ALL'){ $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["bill_date"] = $bill_date; $data["bill_type"] = $bill_type; $data["nonce_str"] = $this->get_nonce_string(); $result = $this->post(self::URL_DOWNLOADBILL, $data); return $result; } /** * 獲取js支付使用的第二個引數 */ public function get_package($prepay_id) { $data = array(); $data["appId"] = $this->_config["appid"]; $data["timeStamp"] = time(); $data["nonceStr"] = $this->get_nonce_string(); $data["package"] = "prepay_id=$prepay_id"; $data["signType"] = "MD5"; $data["paySign"] = $this->sign($data); return $data; } /** * 獲取傳送到通知地址的資料(在通知地址內使用) * @return 結果陣列,如果不是微信伺服器傳送的資料返回null * appid * bank_type * cash_fee * fee_type * is_subscribe * mch_id * nonce_str * openid * out_trade_no 商戶訂單號 * result_code * return_code * sign * time_end * total_fee 總金額 * trade_type * transaction_id 微信支付訂單號 */ public function get_back_data() { $xml = file_get_contents("php://input"); $data = $this->xml2array($xml); if ($this->validate($data)) { return $data; } else { return null; } } /** * 驗證資料簽名 * @param $data 資料陣列 * @return 資料校驗結果 */ public function validate($data) { if (!isset($data["sign"])) { return false; } $sign = $data["sign"]; unset($data["sign"]); return $this->sign($data) == $sign; } /** * 響應微信支付後臺通知 * @param $return_code 返回狀態碼 SUCCESS/FAIL * @param $return_msg 返回資訊 */ public function response_back($return_code="SUCCESS", $return_msg=null) { $data = array(); $data["return_code"] = $return_code; if ($return_msg) { $data["return_msg"] = $return_msg; } $xml = $this->array2xml($data); print $xml; } } ``` 一、注意:此類庫整合到ci我們要改名WechatPay改為Wechatpay讓他符ci類庫規範,而且檔名也要改保持統一性
二、把Wechatpay.php放在application\libraries資料夾內,將證書之類的,日誌檔案之類的放置在和wechatpay.php同級目錄下即可,當然可以隨便放
三、將微信配置資訊,商戶號、appid、AppSecret、API key、證書位置等資訊放在wxpay_config.php檔案中,放在application\config目錄中 ### wxpay_config.php程式碼 ```
<?php defined('BASEPATH') OR exit('No direct script access allowed'); /** * Created by PhpStorm. * User: sxq * Date: 2016-04-20 * Time: 16:59 */ $config['appid'] = '你的公眾號appid'; $config['mch_id'] = '你的商戶號'; $config['apikey'] = '你的APIkey'; $config['appsecret'] = "你的AppSecret"; $config['sslcertPath'] = APPPATH.'libraries/cert/apiclient_cert.pem'; $config['sslkeyPath'] = APPPATH.'libraries/cert/apiclient_key.pem';
``` 四、phpqrcode檔案,這份檔案在微信官方sdk中,使用檔案有phpqrcode資料夾和qrcode.php也一同放置在application\libraries資料夾內
五、日誌檔案log.php,這份檔案在微信官方sdk中也一同放置在application\libraries資料夾內 ```
require_once (APPPATH.'libraries/log.php'); //初始化日誌 $logHandler= new CLogFileHandler(APPPATH."logs/".date('Y-m-d').'.log'); Log::Init($logHandler, 15); //我在控制器最頂部加了這個例項化,日誌檔案放在了application/logs資料夾 //呼叫方式:log::debug("輸出資訊");簡單記錄執行資訊方便除錯
``` 六、配置資訊寫完後,那麼在控制器裡呼叫吧 我們首先按照常規的載入配置資訊程式碼一樣去載入微信配置資訊,最後再載入三方類庫wechatpay.php ```
$this->load->config('wxpay_config'); $wxconfig['appid']=$this->config->item('appid'); $wxconfig['mch_id']=$this->config->item('mch_id'); $wxconfig['apikey']=$this->config->item('apikey'); $wxconfig['appsecret']=$this->config->item('appsecret'); $wxconfig['sslcertPath']=$this->config->item('sslcertPath'); $wxconfig['sslkeyPath']=$this->config->item('sslkeyPath'); //由於此類庫建構函式需要傳參,我們初始化類庫就傳引數給他吧 $this->load->library('Wechatpay',$wxconfig);
``` 這步基礎資訊配置完畢,接下來我們需要構造統一下單API介面引數 ``` $param['body']="商品名稱(自行看文件具體填什麼)"; $param['attach']="我有個引數要傳我就穿了個id過來,這裡不要有空格避免出錯"; $param['detail']="我填了商品名稱加訂單號"; $param['out_trade_no']="商戶訂單號"; $param['total_fee']="金額,記得乘以100,微信支付單位預設分";//如$total_fee*100 $param["spbill_create_ip"] =$_SERVER['REMOTE_ADDR'];//客戶端IP地址 $param["time_start"] = date("YmdHis");//請求開始時間 $param["time_expire"] =date("YmdHis", time() + 600);//請求超時時間 $param["goods_tag"] = urldecode($productname);//商品標籤,自行填寫 $param["notify_url"] = base_url()."home/wxnotify";//自行定義非同步通知url $param["trade_type"] = "NATIVE";//掃碼支付模式二 $param["product_id"] = $order->productid;//正好有產品id就傳了個,看文件說自己定義 //呼叫統一下單API介面 $result=$this->wechatpay->unifiedOrder($param); //這裡可以加日誌輸出,log::debug(json_encode($result)); //成功(return_code和result_code都為SUCCESS)就會返回含有帶支付二維碼連結的資料 if (isset($result["code_url"]) && !empty($result["code_url"])) { /> //二維碼圖片連結 $data['wxurl'] = $result["code_url"]; //這裡傳遞商戶訂單號到掃碼檢視,是因為我想做跳轉,根據商戶號去查詢訂單是否支付成功,如果成功了就跳轉,定時輪詢微信伺服器(這個誰有好的方法可以分享給我啊,表示感謝啦) $data['orderno'] = $out_trade_no; $this->load->view('home/pay', $data); } ``` ### pay.php掃碼檢視頁面程式碼如下 ```
<?php if(isset($wxurl)&&!empty($wxurl)){?> <div class="bgcolor"> <div class="container"> <div class="panel"> <div class="panel-heading"> <ol class="breadcrumb"> <li><a href="<?php echo base_url().'home';?>">首頁</a><span class='divider'>></span></li> <li class="active active-tab"><span><?php echo "二維碼支付";?></span></li> </ol> </div> <div class="panel-body"> <div class="page-header">二維碼支付</div> <div class="text-danger center-block text-center"> <input type="hidden" id="orderno" value="<?php echo $orderno;?>"/> <img alt="掃碼支付" src="<?php echo base_url().'home/qrcode?data='.urlencode($wxurl);?>" style="width:200px;height:200px;"/> </div> </div> </div> </div> </div> <?php }?> <script> // 每半秒請求一次資料,然後判斷,跳轉,增加使用者友好性 $(function(){ orderno = $('#orderno').val(); start = self.setInterval("checkstatus(orderno)", 500); }); function checkstatus(order_no){ if(order_no == undefined || order_no == ''){ window.clearInterval(start); } else{ $.ajax({ url:"<?php echo base_url().'home/queryorder';?>", type:'POST', dataType:'json', data:{orderno:orderno}, success:function(msg){ if(msg.trade_state == "SUCCESS") { window.clearInterval(start); alert('支付成功'); location.href = "<?php echo base_url().'home/myorder';?>"; } } }); } } </script>
``` 其實核心在二維碼連結如何轉換成二維碼圖片和如何定時輪詢支付結果
<?php echo base_url().'home/qrcode?data='.urlencode($wxurl);?>這句是呼叫phpqrcode類庫<br>輪詢方法程式碼:<br>該部分在home控制器下 ```
function queryorder() { $this->load->config('wxpay_config'); $wxconfig['appid']=$this->config->item('appid'); $wxconfig['mch_id']=$this->config->item('mch_id'); $wxconfig['apikey']=$this->config->item('apikey'); $wxconfig['appsecret']=$this->config->item('appsecret'); $wxconfig['sslcertPath']=$this->config->item('sslcertPath'); $wxconfig['sslkeyPath']=$this->config->item('sslkeyPath'); $this->load->library('Wechatpay',$wxconfig); $out_trade_no = $_POST['orderno']; //呼叫查詢訂單API介面 $array = $this->wechatpay->orderQuery('',$out_trade_no); echo json_encode($array); }
``` 那麼二維碼類庫呼叫在這裡 ```
function qrcode() { require_once(APPPATH.'libraries/phpqrcode/phpqrcode.php'); $url = urldecode($_GET["data"]); QRcode::png($url); }
``` 那麼二維碼生成支付圖片完成,支付輪詢也完成了,該如何去處理業務邏輯呢?
先說明下,這部分有個弊端,如果客戶一直不支付那麼他就一直輪詢,可以自行設定個有效期,我沒有設定。如果在輪詢到處理業務邏輯怎麼樣?可以的,但是也有個問題如果客戶直接關掉了,你來不及處理的業務怎麼辦?所以還要確保不掉單,還需要再微信非同步通知url那裡處理下業務 ``` /微信非同步通知 function wxnotify() { //$postStr = file_get_contents("php://input");//因為很多都設定了register_globals禁止,不能用$GLOBALS["HTTP_RAW_POST_DATA'] //這部分困擾了好久用上面這種一直接受不到資料,或者接受了解析不正確,最終用下面的正常了,有哪位願意指點的可以告知一二 $xml = $GLOBALS['HTTP_RAW_POST_DATA'];//這個需要開啟;always_populate_raw_post_data = On $this->load->config('wxpay_config'); $wxconfig['appid']=$this->config->item('appid'); $wxconfig['mch_id']=$this->config->item('mch_id'); $wxconfig['apikey']=$this->config->item('apikey'); $wxconfig['appsecret']=$this->config->item('appsecret'); $wxconfig['sslcertPath']=$this->config->item('sslcertPath'); $wxconfig['sslkeyPath']=$this->config->item('sslkeyPath'); $this->load->library('Wechatpay',$wxconfig); libxml_disable_entity_loader(true); $array= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); log::debug($xml); log::debug(json_encode($array)); if($array!=null) { $out_trade_no = $array['out_trade_no']; $trade_no = $array['transaction_id']; $data['orderid']=$array['attach']; $this->load->model('payorder'); $payinfo = $this->payorder->GetPayorder(array('orderno' => $out_trade_no)); if (!$payinfo) { $data['orderno'] = $out_trade_no; $data['money'] = $array['total_fee']; $data['tradeno'] = $trade_no; $rs=$this->payorder->AddPayorder($data); if($rs>0) { //告知微信我成功了 $this->wechatpay->response_back(); }else{ //告知微信我失敗了繼續發 $this->wechatpay->response_back("FAIL"); } }else{ $this->wechatpay->response_back(); } } } ``` **_
### jsapi篇 _** 微信支付介面文件:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
首先你得知道這個jsapi是不能離開微信進行呼叫支付的,明白了這個道理我們好下手,頁面是在微信內顯示並通過jsapi呼叫微信支付元件進行支付。
可以看看我們上一篇文章,主要是Native掃碼支付模式二
我們仍然繼續使用wechatpay.php這個支付整合類,簡單方便好理解,不過如果應用jsapi的話這個類有個bug
在我們構造jsapi需要的引數時有個時間戳,我們用time()生成的,會報微信支付呼叫JSAPI缺少引數:timeStamp 修改如下: ```
/** * 獲取js支付使用的第二個引數 */ public function get_package($prepay_id) { $data = array(); $data["appId"] = $this->_config["appid"]; //改動地方,把它變成字串 $time=time(); $data["timeStamp"] = "\"".$time."\""; $data["nonceStr"] = $this->get_nonce_string(); $data["package"] = "prepay_id=$prepay_id"; $data["signType"] = "MD5"; $data["paySign"] = $this->sign($data); return $data; }
``` 其實這個方法就是獲取jsapi的支付引數了 一、微信JSAPI支付
不能忘記配置授權目錄,呼叫jsapi我是在http://xxx.com/index.php/home下我配置了這個
首先我們還是要呼叫統一下單介面,獲取我們要的引數(如果此類的配置放置位置等不會的請參考上篇文章),此為pay方法,在呼叫統一下單介面的時候我們需要知道需要哪些引數
1、要獲取openid,這個我是專案用了一個微信API的類庫,https://github.com/dodgepudding/wechat-php-sdk,主要是用了這裡面的方法
此專案有朋友專門的對接了CodeIgniter框架的擴充套件類庫,可以直接用,目錄結構,我們直接上程式碼吧
![輸入圖片說明](https://static.oschina.net/uploads/img/201802/22140035_lwhK.jpg "在這裡輸入圖片標題") ```
public function __construct() { parent::__construct(); $this->load->library('CI_Wechat');//由於我的專案是時刻都跟微信綁在一起,所以直接載入在建構函式裡了,不用每個方法都載入了。 $this->load->library('pagination'); }
``` CI_Model內容大家看下上面的類庫原始碼,還有裡面如何配置的,下面我們看看如何獲取openid ```
function oauthurl() { $oauth_url = $this->ci_wechat->getOauthRedirect(base_url() . 'index.php/home/oauth', 1); header('Location: ' . $oauth_url); exit(); } function oauth() { if (!isset($_GET['code'])) { //觸發微信返回code碼 $baseUrl = urlencode('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . $_SERVER['QUERY_STRING']); $url = $this->__CreateOauthUrlForCode($baseUrl); Header("Location: $url"); exit(); } else { $json = $this->ci_wechat->getOauthAccessToken(); $openid = $json['openid']; //註冊使用者,成功後可以搶單 //return $this->_isRegistered($_SESSION['user']['openid']); return $openid; } }
``` 以上兩個方法就是獲取openid的,獲取之後我是儲存在session裡的,我每個頁面都判斷是否獲取了openid如果沒有獲取直接 ```
$this->session->set_userdata('openid', $this->oauth());
``` 這樣保證一直能得到openid 2、構造JSAPI支付所需引數(統一下單的引數構造) ```
$this->load->model('publist');//獲取訂單資訊 $pub = $this->publist->GetList(array('id' => $_SESSION['orderid'])); //微信支付配置的引數配置讀取 $this->load->config('wxpay_config'); $wxconfig['appid']=$this->config->item('appid'); $wxconfig['mch_id']=$this->config->item('mch_id'); $wxconfig['apikey']=$this->config->item('apikey'); $wxconfig['appsecret']=$this->config->item('appsecret'); $wxconfig['sslcertPath']=$this->config->item('sslcertPath'); $wxconfig['sslkeyPath']=$this->config->item('sslkeyPath'); $this->load->library('Wechatpay',$wxconfig); //商戶交易單號 $out_trade_no = $pub->listno; $total_fee=$pub->fee; $openid=$_SESSION['openid']; $param['body']="黑人牙膏"; $param['attach']=$pub->id; $param['detail']="黑人牙膏-".$out_trade_no; $param['out_trade_no']=$out_trade_no; $param['total_fee']=$total_fee*100; $param["spbill_create_ip"] =$_SERVER['REMOTE_ADDR']; $param["time_start"] = date("YmdHis"); $param["time_expire"] =date("YmdHis", time() + 600); $param["goods_tag"] = "黑人牙膏"; $param["notify_url"] = base_url()."index.php/home/notify"; $param["trade_type"] = "JSAPI"; $param["openid"] = $openid; //統一下單,獲取結果,結果是為了構造jsapi呼叫微信支付元件所需引數 $result=$this->wechatpay->unifiedOrder($param); //如果結果是成功的我們才能構造所需引數,首要判斷預支付id if (isset($result["prepay_id"]) && !empty($result["prepay_id"])) { //呼叫支付類裡的get_package方法,得到構造的引數 $data['parameters']=json_encode($this->wechatpay->get_package($result['prepay_id'])); $data['notifyurl']=$param["notify_url"]; $data['fee']=$total_fee; $data['pubid']=$_SESSION['orderid']; $this->load->view('home/header'); //要有個頁面將以上資料傳遞過去並展示給使用者 $this->load->view('home/pay', $data); $this->load->view('home/footer'); }
``` 3、支付頁面,views檢視pay.php ```
<?php $jsApiParameters = $parameters;//引數賦值 ?> <script type="text/javascript"> //呼叫微信JS api 支付 function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', <?php echo $jsApiParameters; ?>, function(res){ WeixinJSBridge.log(res.err_msg); if(res.err_msg == "get_brand_wcpay_request:ok" ){ $.alert('支付成功'); //我在這裡選擇了前臺只要支付成功將單號傳遞更新資料 $.ajax({ url:'<?php echo $notifyurl.'/'.$pubid;?>', dataType:'json', success : function(ret){ if(ret==1){ //成功後返回我的訂單頁面 location.href="<?php echo base_url().'index.php/home/myorder';?>"; } } }); }else { //$.alert('支付失敗'); } //alert(res.err_code+res.err_desc+res.err_msg); } ); } function callpay() { if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } }else{ jsApiCall(); } } </script> <div class="hd"> <h1 class="page_title">支付佣金</h1> <p class="page_desc">請認真核對佣金金額</p> </div> <div class="weui_cells"> <div class="weui_cell"> <div class="weui_cell_hd weui_cell_primary"> 該筆訂單支付金額為<span style="color:#f00;font-size:50px"><?php echo $fee; ?></span>元錢 </div> </div> </div> <button class="weui_btn weui_btn_primary" type="button" onclick="callpay()" >立即支付</button>
``` 以上程式碼可以用微信web開發者工具,使用方式自己看看吧,有了這個工具除錯不再難
4、支付成功跳轉頁面,我們看notify方法 ```
function notify() { $id = $this->uri->segment(3); if (isset($_SESSION['openid'])) { $this->load->model('publist');//更新業務邏輯 $rs = $this->publist->UpdateList(array('id' => $id, 'feestatus' => 1)); if ($rs > 0) { echo 1; exit; } else { echo 0; exit; } } }
``` 這樣我們的支付流程就徹底走完了。
二、當我們支付完之後,有些單子可以退單的,如何將款項也退回呢 我們申請退款需要引數有哪些?我們看看支付類裡的退款方法 ```
/** * 申請退款 - 使用商戶訂單號 * @param $out_trade_no 商戶訂單號 * @param $out_refund_no 退款單號 * @param $total_fee 總金額(單位:分) * @param $refund_fee 退款金額(單位:分) * @param $op_user_id 操作員賬號 * @return array */ public function refund($out_trade_no,$out_refund_no,$total_fee,$refund_fee,$op_user_id){ $data = array(); $data["appid"] = $this->_config["appid"]; $data["mch_id"] = $this->_config["mch_id"]; $data["nonce_str"] = $this->get_nonce_string(); $data["out_trade_no"] = $out_trade_no; $data["out_refund_no"] = $out_refund_no; $data["total_fee"] = $total_fee; $data["refund_fee"] = $refund_fee; $data["op_user_id"] = $op_user_id; $result = $this->post(self::URL_REFUND, $data,true); return $result; }
``` 商戶訂單號,商戶提供的退單號,付款金額,退款金額(不能退的比實際付款的多),操作員(一般商戶號)
控制器內寫退款方法 ```
//申請退款 function refund($id="") { if($id==""){ //方便我手動呼叫退單 $id = $this->uri->segment(3); } if (isset($id) && $id != "") { $this->load->model('publist'); //1、取消訂單可以退款。2、失敗訂單可以退款 $pub = $this->publist->GetList(array('id' => $id)); if ($pub->liststatus == 3 || $pub->liststatus == 4) { $listno = $pub->listno; $fee = $pub->fee * 100;
$this->load->config('wxpay_config'); $wxconfig['appid']=$this->config->item('appid'); $wxconfig['mch_id']=$this->config->item('mch_id'); $wxconfig['apikey']=$this->config->item('apikey'); $wxconfig['appsecret']=$this->config->item('appsecret'); $wxconfig['sslcertPath']=$this->config->item('sslcertPath'); $wxconfig['sslkeyPath']=$this->config->item('sslkeyPath'); $this->load->library('Wechatpay',$wxconfig); if (isset($listno) && $listno != "") { $out_trade_no = $listno; $total_fee = $fee; $refund_fee = $fee; //自定義商戶退單號 $out_refund_no=$wxconfig['mch_id'].date("YmdHis"); $result=$this->wechatpay->refund($out_trade_no,$out_refund_no,$total_fee,$refund_fee,$wxconfig['mch_id']); log::DEBUG(json_encode($result)); if (isset($result["return_code"]) && $result["return_code"]="SUCCESS"&&isset($result["result_code"]) && $result["result_code"]="SUCCESS") { echo "<script>$.toast('退款成功')</script>"; } //佣金狀態更改為已退款 $this->publist->UpdateList(array('id'=>$id,'liststatus'=>3,'listoutno'=>$out_refund_no)); redirect('home/myorder'); } } } }
```