看了好多關于講解微信H5支付開發的文章,大多數都是通過微信內部瀏覽器來調用支付接口(其實就是公眾號支付),可能是因為H5支付接口剛開放不久吧。
微信官方體驗鏈接:http://wxpay.wxutil.com/mch/pay/h5.v2.php,請在微信外瀏覽器打開。
看了上面的體驗鏈接,如果感興趣,可以接著往下看,希望對你有所幫助。
一、Android端
Android端代碼相對來說比較簡單一些,我這邊直接調用系統瀏覽器打開H5支付頁面Intent?intent?=?new?Intent();
intent.setAction("android.intent.action.VIEW");
Uri?content_url?=?Uri.parse(url);?//url里面包含了后端需要用到的參數,例如金額,用戶ID
intent.setData(content_url);
startActivity(intent);
剛開始考慮過使用webview來加載支付頁面,但是調試接口的時候發現一直報如下錯誤:
微信官方報錯示例圖
根據微信官方的報錯示例可以看出,應該是webview中沒有設置referer導致的。于是,我按照說明加上了referer,然鵝,并沒有什么卵用,依然報錯。我只好放棄使用webview改用系統瀏覽器。使用系統瀏覽器的弊端還是挺明顯的,客戶端和后臺傳值不好處理(正在看文章的你,如果用webview調用成功了,還請賜教)
二、PHP端
1.獲取從Android端傳過來的參數
目前我只想到一種方法,就是通過url攜帶參數給服務端傳值,OK,獲取方式如下://當前頁面的URL地址為:http://www.XXXXX.com/wechatpay/h5Pay.php?money=100&uid=337828932$string?=?$_SERVER['QUERY_STRING'];//通過$_SERVER['QUERY_STRING']函數獲取url地址?后面的值$androidData?=?convertUrlQuery($string);//將字符串$string轉化為數組$money?=?$androidData['money?'];//獲取money值?100$uid?=?$androidData['uid'];//獲取用戶ID?337828932
上面用到的convertUrlQuery函數代碼//將字符串參數變為數組function?convertUrlQuery($query){
$queryParts?=?explode('&',?$query);
$params?=?array();????foreach?($queryParts?as?$param)?{
$item?=?explode('=',?$param);
$params[$item[0]]?=?$item[1];
}????return?$params;
}
2.調用微信統一下單API2.1 具體參數名稱以及各參數的用途請查看微信官方文檔 統一下單API//配置需要傳遞給微信的參數
$input?=?new?WxPayUnifiedOrder();
$input->SetBody("xxxx-商品購買");
$input->SetAttach("xxxx");
$input->SetDevice_info("WEB");
$input->SetOut_trade_no(WxPayConfig::MCHID?.?date("YmdHis").$uid);//把UID加在末尾主要用于識別用戶,給對應的用戶充值余額
$input->SetTotal_fee($money);
$input->SetTime_start(date("YmdHis"));
$input->SetTime_expire(date("YmdHis",?time()?+?600));
$input->SetGoods_tag("t");
$input->SetNotify_url("http://www.xxxxx.com/wechatpay/notify.php");//用于接收微信下發的支付結果通知
$input->SetTrade_type("MWEB");
$input->SetOpenid($openId);
$input->SetScene_info("{\"h5_info\":?\"h5_info\"{\"type\":?\"Wap\",\"wap_url\":?\"http://www.xxxxx.com/shop\",\"wap_name\":?\"xxx\"}}");2.2 接下來通過$order = WxPayApi::unifiedOrder($input);獲取微信返回的參數,unifiedOrder函數具體實現如下/**
*
*?統一下單,WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
*?appid、mchid、spbill_create_ip、nonce_str不需要填入
*?@param?WxPayUnifiedOrder?$inputObj
*?@param?int?$timeOut
*?@throws?WxPayException
*?@return?成功時返回,其他拋異常
*/
public?static?function?unifiedOrder($inputObj,?$timeOut?=?6)
{
$url?=?"https://api.mch.weixin.qq.com/pay/unifiedorder";????????//檢測必填參數
if(!$inputObj->IsOut_trade_noSet())?{????????????throw?new?WxPayException("缺少統一支付接口必填參數out_trade_no!");
}else?if(!$inputObj->IsBodySet()){????????????throw?new?WxPayException("缺少統一支付接口必填參數body!");
}else?if(!$inputObj->IsTotal_feeSet())?{????????????throw?new?WxPayException("缺少統一支付接口必填參數total_fee!");
}else?if(!$inputObj->IsTrade_typeSet())?{????????????throw?new?WxPayException("缺少統一支付接口必填參數trade_type!");
}
//異步通知url未設置,則使用配置文件中的url
if(!$inputObj->IsNotify_urlSet()){
$inputObj->SetNotify_url(WxPayConfig::NOTIFY_URL);//異步通知url
}
$inputObj->SetAppid(WxPayConfig::APPID);//公眾賬號ID
$inputObj->SetMch_id(WxPayConfig::MCHID);//商戶號
$inputObj->SetSpbill_create_ip(self::getIp());//終端ip
//$inputObj->SetSpbill_create_ip("1.1.1.1");
$inputObj->SetNonce_str(self::getNonceStr());//隨機字符串
//簽名
$inputObj->SetSign();
$xml?=?$inputObj->ToXml();
$startTimeStamp?=?self::getMillisecond();//請求開始時間
$response?=?self::postXmlCurl($xml,?$url,?false,?$timeOut);
$result?=?WxPayResults::Init($response);????????self::reportCostTime($url,?$startTimeStamp,?$result);//上報請求花費時間
return?$result;
}2.3 上面代碼中需要注意的有以下3點獲取客服端的真實IP地址使用REMOTE_ADDR只能獲取訪問者本地連接中設置的IP。經本人粗略測試,使用UC瀏覽的時候將無法直接獲取真實IP,這是如果把這個IP傳過去,微信將會返回 網絡環境未能通過安全驗證,請稍后再試 的錯誤
這時可以使用以下方式獲取用戶真實IP//獲取用戶真實IP
public?static?function?getIp(){
$ip?=?'';????????if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip?=?$_SERVER['HTTP_X_FORWARDED_FOR'];
}elseif(isset($_SERVER['HTTP_CLIENT_IP'])){
$ip?=?$_SERVER['HTTP_CLIENT_IP'];
}else{
$ip?=?$_SERVER['REMOTE_ADDR'];
}
$ip_arr?=?explode(',',?$ip);????????return?$ip_arr[0];
}把參數轉換成XML格式在這JSON橫行的年代,還使用XML格式傳遞數據,也許這是微信的高端操作,也許是他們以前的框架就是那樣,這我們就不管了。直接上代碼/**
*?輸出xml字符
*?@throws?WxPayException
**/
public?function?ToXml()
{????????if(!is_array($this->values)
||?count($this->values)?<=?0)
{????????????throw?new?WxPayException("數組數據異常!");
}
$xml?=?"";????????foreach?($this->values?as?$key=>$val)
{????????????if?(is_numeric($val)){
$xml.="".$val."".$key.">";
}else{
$xml.="".$key.">";
}
}
$xml.="";????????return?$xml;
}簽名方法
簽名生成的通用步驟如下:
第一步,設所有發送或者接收到的數據為集合M,將集合M內非空參數值的參數按照參數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特別注意以下重要規則:參數名ASCII碼從小到大排序(字典序);
如果參數的值為空不參與簽名;
參數名區分大小寫;
驗證調用返回或微信主動通知簽名時,傳送的sign參數不參與簽名,將生成的簽名與該sign值作校驗。
微信接口可能增加字段,驗證簽名時必須支持增加的擴展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并對stringSignTemp進行MD5運算,再將得到的字符串所有字符轉換為大寫,得到sign值signValue。
key設置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全-->密鑰設置/**
*?生成簽名
*?@return?簽名
*/
public?function?MakeSign()
{????????//簽名步驟一:按字典序排序參數
ksort($this->values);
$string?=?$this->ToUrlParams();????????//簽名步驟二:在string后加入KEY
$string?=?$string?.?"&key=".WxPayConfig::KEY;????????//簽名步驟三:MD5加密
$string?=?md5($string);????????//簽名步驟四:所有字符轉為大寫
$result?=?strtoupper($string);????????return?$result;
}????/**
*?格式化參數??--格式化成url參數
*/
public?function?ToUrlParams()
{
$buff?=?"";????????foreach?($this->values?as?$k?=>?$v)
{????????????if($k?!=?"sign"?&&?$v?!=?""?&&?!is_array($v)){
$buff?.=?$k?.?"="?.?$v?.?"&";
}
}
$buff?=?trim($buff,?"&");????????return?$buff;
}2.4 通過返回的mweb_url參數調起微信APPif?($order['mweb_url'])?{
$orderId?=?$input->GetOut_trade_no();
$payUrl?=?$order['mweb_url']."&redirect_url=http%3A%2F%2Fwww.xxxx.com%2Fwechatpay%2Forderquery.php%3Ftransaction_id%3D".$orderId;
Log::DEBUG($payUrl);????????//通過JS打開鏈接,調起微信APP支付
echo?"";
}這里詳細解釋一下payUrl中redirect_url參數的含義
正常流程用戶支付完成后會返回至發起支付的頁面,如需返回至指定頁面,則可以在MWEB_URL后拼接上redirect_url參數,來指定回調頁面。
如,您希望用戶支付完成后跳轉至https://www.wechatpay.com.cn,則可以做如下處理:
假設您通過統一下單接口獲到的MWEB_URL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096
則拼接后的地址為MWEB_URL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn
注意:
1.需對redirect_url進行urlencode處理
2.由于設置redirect_url后,回跳指定頁面的操作可能發生在:1,微信支付中間頁調起微信收銀臺后超過5秒 2,用戶點擊“取消支付“或支付完成后點“完成”按鈕。因此無法保證頁面回跳時,支付流程已結束,所以商戶設置的redirect_url地址不能自動執行查單操作,應讓用戶去點擊按鈕觸發查單操作,而我后面拼接的$orderId就是用于查單操作的參數。回跳頁面展示效果可參考下圖
回調頁面示例圖.png
3.處理微信支付結果通知支付完成后,微信會把相關支付結果和用戶信息發送給商戶,商戶需要接收處理,并返回應答。
對后臺通知交互時,如果微信收到商戶的應答不是成功或超時,微信認為通知失敗,微信會通過一定的策略定期重新發起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。 (通知頻率為15/15/30/180/1800/1800/1800/1800/3600,單位:秒)
注意:同樣的通知可能會多次發送給商戶系統。商戶系統必須能夠正確處理重復的通知。
推薦的做法是,當收到通知進行處理時,首先檢查對應業務數據的狀態,判斷該通知是否已經處理過,如果沒有處理過再進行處理,如果處理過直接返回結果成功。在對業務數據進行狀態檢查和處理之前,要采用數據鎖進行并發控制,以避免函數重入造成的數據混亂。
特別提醒:商戶系統對于支付結果通知的內容一定要做簽名驗證,并校驗返回的訂單金額是否與商戶側的訂單金額一致,防止數據泄漏導致出現“假通知”,造成資金損失。
支付回調基礎類/**
*
*?回調基礎類
*?@author?widyhu
*
*/class?WxPayNotify?extends?WxPayNotifyReply{????/**
*
*?回調入口
*?@param?bool?$needSign??是否需要簽名輸出
*/
final?public?function?Handle($needSign?=?true)
{
$msg?=?"OK";????????//當返回false的時候,表示notify中調用NotifyCallBack回調失敗獲取簽名校驗失敗,此時直接回復失敗
$result?=?WxpayApi::notify(array($this,?'NotifyCallBack'),?$msg);????????if($result?==?false){????????????$this->SetReturn_code("FAIL");????????????$this->SetReturn_msg($msg);????????????$this->ReplyNotify(false);????????????return;
}?else?{????????????//該分支在成功回調到NotifyCallBack方法,處理完成之后流程
$this->SetReturn_code("SUCCESS");????????????$this->SetReturn_msg("OK");
}????????$this->ReplyNotify($needSign);
}
/**
*
*?回調方法入口,子類可重寫該方法
*?注意:
*?1、微信回調超時時間為2s,建議用戶使用異步處理流程,確認成功之后立刻回復微信服務器
*?2、微信服務器在調用失敗或者接到回包為非確認包的時候,會發起重試,需確保你的回調是可以重入
*?@param?array?$data?回調解釋出的參數
*?@param?string?$msg?如果回調處理失敗,可以將錯誤信息輸出到該方法
*?@return?true回調出來完成不需要繼續回調,false回調處理未完成需要繼續回調
*/
public?function?NotifyProcess($data,?&$msg)
{????????//TODO?用戶基礎該類之后需要重寫該方法,成功的時候返回true,失敗返回false
return?true;
}
/**
*
*?notify回調方法,該方法中需要賦值需要輸出的參數,不可重寫
*?@param?array?$data
*?@return?true回調出來完成不需要繼續回調,false回調處理未完成需要繼續回調
*/
final?public?function?NotifyCallBack($data)
{
$msg?=?"OK";
$result?=?$this->NotifyProcess($data,?$msg);
if($result?==?true){????????????$this->SetReturn_code("SUCCESS");????????????$this->SetReturn_msg("OK");
}?else?{????????????$this->SetReturn_code("FAIL");????????????$this->SetReturn_msg($msg);
}????????return?$result;
}
/**
*
*?回復通知
*?@param?bool?$needSign?是否需要簽名輸出
*/
final?private?function?ReplyNotify($needSign?=?true)
{????????//如果需要簽名
if($needSign?==?true?&&
$this->GetReturn_code($return_code)?==?"SUCCESS")
{????????????$this->SetSign();
}
WxpayApi::replyNotify($this->ToXml());
}
}需要注意的是,如果用戶只是打開付款界面,而沒有執行付款操作的話,是不會觸發通知的。
當我們接收到的參數中同時含有return_code,result_code,trade_state并且每個返回值都為SUCCESS的時候,代表用戶付款成功if(array_key_exists("return_code",?$result)
&&?array_key_exists("result_code",?$result)
&&array_key_exists("trade_state",?$resultData)
&&?$resultData["trade_state"]?==?"SUCCESS"
&&?$result["return_code"]?==?"SUCCESS"
&&?$result["result_code"]?==?"SUCCESS")
{????????????//這里執行給用戶充值余額的操作
}
4.redirect_url回調頁面處理
如果在第2.4步驟中MWEB_URL后拼接上了redirect_url參數,用戶支付完成后將返回到這個頁面。
當用戶點擊界面的已完成按鈕,將觸發查單操作
微信查單操作API
查單操作相關代碼如下//查詢訂單
public?function?Queryorder($transaction_id)
{
$input?=?new?WxPayOrderQuery();
$input->SetTransaction_id($transaction_id);
$result?=?WxPayApi::orderQuery($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?$result;
}????????return?false;
}
界面布局以及相關的邏輯處理:if(isset($_REQUEST["out_trade_no"])?&&?$_REQUEST["out_trade_no"]?!=?""){
$out_trade_no?=?$_REQUEST["out_trade_no"];
$input?=?new?WxPayOrderQuery();
$input->SetOut_trade_no($out_trade_no);
Log::DEBUG("out_trade_no".$out_trade_no);//??printf_info(WxPayApi::orderQuery($input));
$result=WxPayApi::orderQuery($input);????if(array_key_exists("return_code",?$result)
&&?array_key_exists("result_code",?$result)
&&?array_key_exists("trade_state",?$result)
&&?$result["trade_state"]?==?"SUCCESS"
&&?$result["return_code"]?==?"SUCCESS"
&&?$result["result_code"]?==?"SUCCESS"){
$successUrl="success.html";????????echo?"";
}else{
$failUrl="fail.html";????????echo?"";
}????exit();
}?>
window.=function?()?{????????var?Request=new?UrlSearch();?//實例化
document.getElementById("out_trade_no").value=Request.transaction_id;
document.getElementById("transaction_id").style.height?=?0;
document.getElementById("out_trade_no").style.height?=?0;
document.getElementById("btn").style.height?=?0;
$.DialogByZ.Confirm({Title:?"",?Content:?"請確認微信支付是否已完成",FunL:confirmL,FunR:Immediate})
}????///
function?confirmL(){
$.DialogByZ.Close();
document.getElementById('btn').click();
}????function?Immediate(){????????//location.href="http://sc.chinaz.com/jiaoben/"
window.location.href="nonglegou://data/?state=fail";
}????function?UrlSearch()
{????????var?name,value;????????var?str=location.href;?//取得整個地址欄
var?num=str.indexOf("?")
str=str.substr(num+1);?//取得所有參數???stringvar.substr(start?[,?length?]
var?arr=str.split("&");?//各個參數放到數組里
for(var?i=0;i?<?arr.length;i++){
num=arr[i].indexOf("=");????????????if(num>0){
name=arr[i].substring(0,num);
value=arr[i].substr(num+1);
this[name]=value;
}
}
}
三、總結
微信H5支付的整個流程,大概就是這樣子,以下是流程圖
接口流程圖用戶在商戶側完成下單,使用微信支付進行支付
由商戶后臺向微信支付發起下單請求(調用統一下單接口)注:交易類型trade_type=MWEB
統一下單接口返回支付相關參數給商戶后臺,如支付跳轉url(參數名“mweb_url”),商戶通過mweb_url調起微信支付中間頁
中間頁進行H5權限的校驗,安全性檢查(此處常見錯誤請見下文)
如支付成功,商戶后臺會接收到微信側的異步通知
用戶在微信支付收銀臺完成支付或取消支付,返回商戶頁面(默認為返回支付發起頁面)
商戶在展示頁面,引導用戶主動發起支付結果的查詢
商戶后臺判斷是否接到收微信側的支付結果通知,如沒有,后臺調用我們的訂單查詢接口確認訂單狀態
展示最終的訂單支付結果給用戶