PHP版本微信支付開發----電腦網站掃碼支付(native)(心得、總結)
此文章引用愛玲姐姐(如有意見聯系課刪除)
早就聽說微信支付比支付寶支付的坑多,但還得得該填的填,該繞的繞, 最終我們網站的微信支付功能成功上線啦♪(^ ∇ ^*)
首先自報家門,我的PHP版本是7,微信demo用的是php_sdk_v3.0.9
一、手續備齊、按流程走
在進行微信支付開發之前,首先你得擁有微信支付的權限。哦對了,在此之前,你還得有一個公眾號(服務號), 在微信公眾平台申請。進入微信公眾后台后,
左側有一個微信支付
,如果你想要獲得微信支付功能,還需要先進行微信公眾號的認證,嘻嘻(此處省略300元
~~~費時費力)
我覺得微信認證過程還蠻麻煩的,我是和老趙一起做的,他負責給我認證材料,我負責上傳。
認證完之后,還要申請商戶號
,這也是老趙申請的,花費了好幾天,然后申請好了之后你會得到微信發給你的商戶號,如果你是超級管理員,你就可以在微信支付平台橫着走了,你可以給別人(員工)設置不同的權限。在我們本次操作中,老趙是“超級管理員
”,我被授權為“管理員
”(僅次於“超級管理員”的位卑權低小角色>_<) 不得不說的是,一涉及到賬號或者權限或者秘鑰的頁面,都只能超級管理員
查看,這真是太不方便了,以至於這幾天我天天求着老趙的微信給我掃碼登一下看看┭┮﹏┭┮
二、下載微信支付demo,修改配置信息
1.開發文檔
我做的項目是電腦網站,所以就選擇了native支付
在這里可以下載demo,我下載了PHP的,這是目錄結構
做過支付寶支付的人可能知道支付寶提供了異步回調和同步回調兩種方式,而微信沒有提供同步回調,只有異步回調(在此處理你的業務邏輯如數據庫的更新)。
2.修改配置文件example/Wxpay.config.php
AppId
這是你的微信公眾平台的APPID,去微信公眾平台里面查看MerchantId
這是你申請的商戶號NotifyUrl
這是異步回調地址,要求外網可以訪問(不能寫localhost域名)、不能帶任何參數Key
這是你的APIkey,在微信開發平台里,超級管理員
可以修改
3.配置文件代碼:
<?php /** * * example目錄下為簡單的支付樣例,僅能用於搭建快速體驗微信支付使用 * 樣例的作用僅限於指導如何使用sdk,在安全上面僅做了簡單處理, 復制使用樣例代碼時請慎重 * 請勿直接直接使用樣例對外提供服務 * **/ require_once "../lib/WxPay.Config.Interface.php"; /** * * 該類需要業務自己繼承, 該類只是作為deamon使用 * 實際部署時,請務必保管自己的商戶密鑰,證書等 * */ class WxPayConfig extends WxPayConfigInterface { //=======【基本信息設置】===================================== /** * TODO: 修改這里配置為您自己申請的商戶信息 * 微信公眾號信息配置 * * APPID:綁定支付的APPID(必須配置,開戶郵件中可查看) * * MCHID:商戶號(必須配置,開戶郵件中可查看) * */ public function GetAppId() { return 'xxxxxxxxxxxx';//jal } public function GetMerchantId() { return 'xxxxxxxxxxx';//jal } //=======【支付相關配置:支付成功回調地址/簽名方式】=================================== /** * TODO:支付回調url * 簽名和驗證簽名方式, 支持md5和sha256方式 **/ public function GetNotifyUrl() { return "http://xxx.xxxxxx.com/JudgeOnline/wxpay/example/notify.php"; } public function GetSignType() { return "HMAC-SHA256"; } //=======【curl代理設置】=================================== /** * TODO:這里設置代理機器,只有需要代理的時候才設置,不需要代理,請設置為0.0.0.0和0 * 本例程通過curl使用HTTP POST方法,此處可修改代理服務器, * 默認CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此時不開啟代理(如有需要才設置) * @var unknown_type */ public function GetProxy(&$proxyHost, &$proxyPort) { $proxyHost = "0.0.0.0"; $proxyPort = 0; } //=======【上報信息配置】=================================== /** * TODO:接口調用上報等級,默認緊錯誤上報(注意:上報超時間為【1s】,上報無論成敗【永不拋出異常】, * 不會影響接口調用流程),開啟上報之后,方便微信監控請求調用的質量,建議至少 * 開啟錯誤上報。 * 上報等級,0.關閉上報; 1.僅錯誤出錯上報; 2.全量上報 * @var int */ public function GetReportLevenl() { return 1; } //=======【商戶密鑰信息-需要業務方繼承】=================================== /* * KEY:商戶支付密鑰,參考開戶郵件設置(必須配置,登錄商戶平台自行設置), 請妥善保管, 避免密鑰泄露 * 設置地址:https://pay.weixin.qq.com/index.php/account/api_cert * * APPSECRET:公眾帳號secert(僅JSAPI支付的時候需要配置, 登錄公眾平台,進入開發者中心可設置), 請妥善保管, 避免密鑰泄露 * 獲取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN * @var string */ public function GetKey() { return 'xxxxxxxxxxxxxxxxxxxxxxxxxx';//jal //return '6f637dd7fe44a1e8ea19e4599fb2c7ff';//sandbox_sign_key這里有坑 } public function GetAppSecret() { return '7813490da6f1265e4901ffb80afaa36f';//native不需要填 } //=======【證書路徑設置-需要業務方繼承】===================================== /** * TODO:設置商戶證書路徑 * 證書路徑,注意應該填寫絕對路徑(僅退款、撤銷訂單時需要,可登錄商戶平台下載, * API證書下載地址:https://pay.weixin.qq.com/index.php/account/api_cert,下載之前需要安裝商戶操作證書) * 注意: * 1.證書文件不能放在web服務器虛擬目錄,應放在有訪問權限控制的目錄中,防止被他人下載; * 2.建議將證書文件名改為復雜且不容易猜測的文件名; * 3.商戶服務器要做好病毒和木馬防護工作,不被非法侵入者竊取證書文件。 * @var path */ public function GetSSLCertPath(&$sslCertPath, &$sslKeyPath) { $sslCertPath = '../cert/apiclient_cert.pem'; $sslKeyPath = '../cert/apiclient_key.pem'; } }
三、微信支付的巨坑(速速繞過
) ---- 沙箱環境–>getsignkey
相信大家都看到了這里吧,知道要獲取獲取驗簽秘鑰APIsignkey
這個東西。但我估計大家也一定在此處吃盡了苦頭,耗費了很長時間吧。
微信的開發文檔中只說了去https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey
獲取驗簽秘鑰API,但並沒有給出具體的實現代碼,┭┮﹏┭┮這可真是難為我了,我在這里花了整整兩天時間查資料研究怎么獲取驗簽秘鑰API,最終我是下載了一個谷歌插件PostMan工具來模擬http請求獲得了signkey,
然而,每次我獲取到的signkey總是不變的,每次都是一樣的值(我每次給出的隨機串不同),這令我很郁悶,我感覺我的邏輯沒錯啊,不應該每次都獲得一樣的返回值。
我拿着我得到的這個極有可能有毛病的signkey去放到了配置文件中,
替換了GetKey的返回值。然后還將整個demo中所有的wei.qq.com
后面加上了/sandboxnew
,然而,這時候打開native.php頁面居然又報錯了,說我的沙箱秘鑰不正確,讓我去https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey
獲取驗簽秘鑰API,真讓人感到很無語,我我我…唉,氣死我了。我在這里耗費了兩天,居然就給我這個結果!我又繼續上網查資料,發現有很多很多人都遇到了微信沙箱的各種坑,我心里獲得了不少安慰。之后,我決定不用沙箱環境了,非得用沙箱環境仿真嗎,非得提交驗收嗎,答案是NO
,我發現不用沙箱環境在服務器上代碼也能跑的起來,也可以支付,那為什么要用那個麻煩的沙箱環境呢,浪費了我那么長時間,實在是忍不下去了。我覺得繞過沙箱環境是提高微信支付開發效率最關鍵的一步
四、 現在要正式進行編程了,首先是前台的支付頁面完成后的跳轉
由於微信沒有給出同步回調地址,那什么時候完成支付進行頁面跳轉都需要看我們用js發送請求查詢了。
上面是我的訂單提交頁面(index.php)這個頁面比較簡單不再多說。點擊付款按鈕后,就會跳轉到下面的掃碼支付頁面example/native.php,這是我自己模仿微信給出的樣例畫的一個漂亮的收銀台,嘻嘻* ^_^ *
微信的圖片logo是在這個頁面下載的,也可以點擊點擊直接下載
1.native.php代碼如下:
<?php /** * * example目錄下為簡單的支付樣例,僅能用於搭建快速體驗微信支付使用 * 樣例的作用僅限於指導如何使用sdk,在安全上面僅做了簡單處理, 復制使用樣例代碼時請慎重 * 請勿直接直接使用樣例對外提供服務 * **/ require_once "../lib/WxPay.Api.php"; require_once "WxPay.NativePay.php"; require_once 'log.php'; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); $notify = new NativePay(); //模式二 /** * 流程: * 1、調用統一下單,取得code_url,生成二維碼 * 2、用戶掃描二維碼,進行支付 * 3、支付完成之后,微信服務器會通知支付成功 * 4、在支付成功通知中需要查單確認是否真正支付成功(見:notify.php) */ //jal $amount = isset($_POST['WIDtotal_amount'])?$_POST['WIDtotal_amount']:""; $body = isset($_POST['WIDsubject'])?$_POST['WIDsubject']:""; $total_fee = intval(doubleval($amount)*100); $product_id = isset($_POST['goods_id'])?$_POST['goods_id']:""; $attach = isset($_POST['word'])?$_POST['word']:"test"; $out_trade_no = isset($_POST['WIDout_trade_no'])?$_POST['WIDout_trade_no']:"phpsdk".date("YmdHis"); $input = new WxPayUnifiedOrder(); $input->SetBody($body); $input->SetAttach($attach); $input->SetOut_trade_no($out_trade_no); $input->SetTotal_fee($total_fee); $input->SetTime_start(date("YmdHis")); $input->SetTime_expire(date("YmdHis", time() + 600)); $input->SetGoods_tag("test"); //$input->SetNotify_url("http://paysdk.weixin.qq.com/notify.php"); $input->SetNotify_url("http://xxx.xxxxxxx.com/JudgeOnline/wxpay/example/notify.php");//這里寫你的回調地址 $input->SetTrade_type("NATIVE"); $input->SetProduct_id($product_id); $result = $notify->GetPayUrl($input); $url2 = $result["code_url"]; ?> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1" /> <script src="../../template/bs3/jquery.min.js"></script> <link rel="icon" href="img/logo.png"> <title>微信支付</title> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="row"> <div style="padding: 10px 30px"> <img src="img/logo.png" alt="" style="height: 35px;vertical-align:middle"><span style="font-size: large; font-weight: 600">微信收銀台</span> </div> </div> <div class="row" style="background-color: lightgrey; padding: 10px 30px;"> <div style="display: inline-block;"> <div>訂單編號:<span><?php echo $out_trade_no?></span></div> <div>訂單名稱:<span><?php echo $body?></span></div> </div> <div style="float: right; display: inline-block;padding: 10px">應付金額:¥<span><?php echo $amount?></span></div> </div> <div class="row" style="margin: 20px 30px; padding: 10px 20px; border-color: orange; border-style: solid;border-width: medium"> <div style="display: inline-block"> <img src="img/WePayLogo.png" alt="" style="height: 50px;vertical-align: middle"> <img src="img/right.png" alt="" style="height: 30px; vertical-align: middle"> <span style="color: lightgrey">億萬用戶的選擇,更快更安全</span> </div> <div style="float: right; display: inline-block; padding: 10px"> 支付<span style="color: orange"><?php echo $amount?></span>元 </div> </div> <div class="row" style="text-align: center"> <img alt="掃碼支付" src="qrcode.php?data=<?php echo urlencode($url2);?>" style="width:250px;height:250px;"/><div> 支付提示:<span id="query_result" style="color: red">WAITING...</span> </div> <input id="out_trade_no" type="hidden" value="<?php echo $out_trade_no;?>"> <img src="img/scan.png" alt="" style="width: 250px; height: 90px"> </div> </div> <script> var t1; var sum=0; $(document).ready(function () { t1=setInterval("ajaxstatus()", 3000); }); function ajaxstatus() { sum++; if(sum>600){ window.clearInterval(t1);return false;} if(sum>180){ m=sum % 10; if(m!=0){return false;} } if ($("#out_trade_no").val() != 0) { $.post("orderqueryajax.php", { out_trade_no:$("#out_trade_no").val() }, function (data) { data = $.trim(data); $("#query_result").html(data); if (data=="SUCCESS") { $("#query_result").html("支付成功,即將跳轉..."); <?php if (isset($_POST['history_go']) && $_POST['history_go'] == 3){ echo 'window.setTimeout("history.go(-3);",2000);'; }else echo 'window.setTimeout("history.go(-2);",2000);'; ?> } }); } } </script> </body> </html>
2. 其中這個頁面包涵了js定時查詢部分,看注釋
var t1; var sum=0; $(document).ready(function () { t1=setInterval("ajaxstatus()", 3000);//三秒查詢一次 }); function ajaxstatus() { sum++; if(sum>600){ window.clearInterval(t1);return false;}//如果查詢次數過多就放棄 if(sum>180){ m=sum % 10; if(m!=0){return false;} } if ($("#out_trade_no").val() != 0) { //此處很關鍵,用ajax異步查詢orderqueryajax.php頁面,看看這筆訂單是否已完成支付 $.post("orderqueryajax.php", { out_trade_no:$("#out_trade_no").val() }, function (data) { data = $.trim(data); $("#query_result").html(data);//一直更新查詢結果 if (data=="SUCCESS") { $("#query_result").html("支付成功,即將跳轉...");//如果查詢到的結果是成功支付,就進行頁面跳轉 <?php if (isset($_POST['history_go']) && $_POST['history_go'] == 3){ echo 'window.setTimeout("history.go(-3);",2000);';//此處只是為了判斷是頁面返回兩頁還是三頁 }else echo 'window.setTimeout("history.go(-2);",2000);'; ?> } }); } }
3. 后台orderqueryajax.php頁面,其實這個頁面是我仿照已有的example/orderquery.php抄的啦,嘿嘿
<?php /** * * ajax異步查詢訂單是否完成 * **/ require_once "../lib/WxPay.Api.php"; require_once 'log.php'; require_once "WxPay.Config.php"; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); $v = $_POST["out_trade_no"]; if(isset($v) && $v != ""){ $out_trade_no = $v; $input = new WxPayOrderQuery(); $input->SetOut_trade_no($out_trade_no); $config = new WxPayConfig(); $result = WxPayApi::orderQuery($config, $input); if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){//返回查詢結果 echo $result['trade_state']; }else{ echo "FAIL"; } } ?>
這些寫好后,就可以提交到服務器上跑一下啦
哦對了,如果你的代碼在服務器上跑不起來的話,連最初demo里面的二維碼都無法顯示的話,那應該是你的php.ini里面的curl擴展功能沒有開,我當時就是因為curl沒開,導致在服務器上打開就是500錯誤,怎么打開curl擴展還是老趙幫我的,你們可以自行百度~
五、支付完成后異步回調,處理業務邏輯如:更新數據庫
這一部分功能我是在example/notify.php中處理的
<?php /** * * example目錄下為簡單的支付樣例,僅能用於搭建快速體驗微信支付使用 * 樣例的作用僅限於指導如何使用sdk,在安全上面僅做了簡單處理, 復制使用樣例代碼時請慎重 * 請勿直接直接使用樣例對外提供服務 * **/ require_once "../lib/WxPay.Api.php"; require_once '../lib/WxPay.Notify.php'; require_once "WxPay.Config.php"; require_once 'log.php'; require_once("../../include/memcache.php"); //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); class PayNotifyCallBack extends WxPayNotify { //查詢訂單 public function Queryorder($transaction_id) { $input = new WxPayOrderQuery(); $input->SetTransaction_id($transaction_id); $config = new WxPayConfig(); $result = WxPayApi::orderQuery($config, $input); Log::DEBUG("query:" . json_encode($result)); if(array_key_exists("return_code", $result) && array_key_exists("result_code", $result) && $result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { return true; } return false; } /** * * 回包前的回調方法 * 業務可以繼承該方法,打印日志方便定位 * @param string $xmlData 返回的xml參數 * **/ public function LogAfterProcess($xmlData) { Log::DEBUG("call back, return xml:" . $xmlData); return; } //重寫回調處理函數 /** * @param WxPayNotifyResults $data 回調解釋出的參數 * @param WxPayConfigInterface $config * @param string $msg 如果回調處理失敗,可以將錯誤信息輸出到該方法 * @return true回調出來完成不需要繼續回調,false回調處理未完成需要繼續回調 */ public function NotifyProcess($objData, $config, &$msg) { $data = $objData->GetValues(); //TODO 1、進行參數校驗 if(!array_key_exists("return_code", $data) ||(array_key_exists("return_code", $data) && $data['return_code'] != "SUCCESS")) { //TODO失敗,不是支付成功的通知 //如果有需要可以做失敗時候的一些清理處理,並且做一些監控 $msg = "異常異常"; return false; } if(!array_key_exists("transaction_id", $data)){ $msg = "輸入參數不正確"; return false; } //TODO 2、進行簽名驗證 try { $checkResult = $objData->CheckSign($config); if($checkResult == false){ //簽名錯誤 Log::ERROR("簽名錯誤..."); return false; } } catch(Exception $e) { Log::ERROR(json_encode($e)); } //TODO 3、處理業務邏輯 Log::DEBUG("call back:" . json_encode($data)); $notfiyOutput = array(); //查詢訂單,判斷訂單真實性 if(!$this->Queryorder($data["transaction_id"])){ $msg = "訂單查詢失敗"; return false; } //可以在此處寫入數據庫 $trade_no = $data['transaction_id']; $trade_status = $data['result_code']; $receipt_amount = $data['total_fee']; $gmt_payment = date("Y-m-d H:i:s"); $buyer_logon_id = ""; $buyer_user_id = ""; $total_amount = $data['total_fee']; $out_trade_no = $data['out_trade_no']; $body = ""; $subject= pdo_query("select subject from trade WHERE out_trade_no = ?", $out_trade_no)[0][0]; $sql = "update trade set trade_no = ?, trade_status = ?,valid=1, receipt_amount = ?, gmt_payment = ?, buyer_logon_id = ?, buyer_user_id = ? where out_trade_no = ? limit 1; "; $res = pdo_query($sql,$trade_no, $trade_status, $receipt_amount, $gmt_payment, $buyer_logon_id, $buyer_user_id,$out_trade_no); $sql = "update goods set goods_number = goods_number-1, goods_trade_number = goods_trade_number+1 where goods_name=?"; pdo_query($sql, $subject); return true; } } $config = new WxPayConfig(); Log::DEBUG("begin notify"); $notify = new PayNotifyCallBack(); $notify->Handle($config, false);
這里回調部分又有坑啦,不踩幾個坑都不好意思說自己弄過微信開發~
如果你的回調中的代碼沒有生效,但你的回調地址又沒有寫錯的話,估計你是和我遇着同樣的坑了,且不得不填。極有可能是因為微信給你發的支付通知信息你沒收到,解決方法我寫在這篇文里面了,微信支付開發中的坑---- php廢棄$GLOBALS[‘HTTP_RAW_POST_DATA’]