Android經過外部瀏覽器調用微信H5支付,Android+PHP詳解

看了好多關於講解微信H5支付開發的文章,大多數都是經過微信內部瀏覽器來調用支付接口(其實就是公衆號支付),多是由於H5支付接口剛開放不久吧。 微信官方體驗連接:wxpay.wxutil.com/mch/pay/h5.… 看了上面的體驗連接,若是感興趣,能夠接着往下看,但願對你有所幫助。javascript

1、Android端

Android端代碼相對來講比較簡單一些,我這邊直接調用系統瀏覽器打開H5支付頁面php

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來加載支付頁面,可是調試接口的時候發現一直報以下錯誤:html

微信官方報錯示例圖
根據微信官方的報錯示例能夠看出,應該是webview中沒有設置referer致使的。因而,我按照說明加上了referer,然鵝,並無什麼卵用,依然報錯。我只好放棄使用webview改用系統瀏覽器。使用系統瀏覽器的弊端仍是挺明顯的,客戶端和後臺傳值很差處理(正在看文章的你,若是用webview調用成功了,還請賜教)

2、PHP端

1.獲取從Android端傳過來的參數

目前我只想到一種方法,就是經過url攜帶參數給服務端傳值,OK,獲取方式以下:java

//當前頁面的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函數代碼android

//將字符串參數變爲數組
function convertUrlQuery($query)
{
    $queryParts = explode('&', $query);
    $params = array();
    foreach ($queryParts as $param) {
        $item = explode('=', $param);
        $params[$item[0]] = $item[1];
    }
    return $params;
}
複製代碼
2.調用微信統一下單API
2.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傳過去,微信將會返回 網絡環境未能經過安全驗證,請稍後再試 的錯誤web

這時可使用如下方式獲取用戶真實IPjson

//獲取用戶真實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 = "<xml>";
    	foreach ($this->values as $key=>$val)
    	{
    		if (is_numeric($val)){
    			$xml.="<".$key.">".$val."</".$key.">";
    		}else{
    			$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
    		}
        }
        $xml.="</xml>";
        return $xml; 
	}
複製代碼
  • 簽名方法

簽名生成的通用步驟以下:api

第一步,設全部發送或者接收到的數據爲集合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參數調起微信APP
if ($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 "<script LANGUAGE='Javascript'>";
        echo "location.replace('$payUrl')";
        echo "</script>";
    }
複製代碼

這裏詳細解釋一下payUrl中redirect_url參數的含義

正常流程用戶支付完成後會返回至發起支付的頁面,如需返回至指定頁面,則能夠在MWEB_URL後拼接上redirect_url參數,來指定回調頁面。 如,您但願用戶支付完成後跳轉至https://www.wechatpay.com.cn,則能夠作以下處理: 假設您經過統一下單接口獲到的MWEB_URL= wx.tenpay.com/cgi-bin/mmp… 則拼接後的地址爲MWEB_URL= wx.tenpay.com/cgi-bin/mmp… 注意: 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);
	}
	
	/**
	 * 
	 * 回調方法入口,子類可重寫該方法
	 * 注意:
	 * 一、微信回調超時時間爲2s,建議用戶使用異步處理流程,確認成功以後馬上回復微信服務器
	 * 二、微信服務器在調用失敗或者接到回包爲非確認包的時候,會發起重試,需確保你的回調是能夠重入
	 * @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 "<script LANGUAGE='Javascript'>";
        echo "location.replace('$successUrl')";
        echo "</script>";
    }else{
        $failUrl="fail.html";
        echo "<script LANGUAGE='Javascript'>";
        echo "location.replace('$failUrl')";
        echo "</script>";
    }
	exit();
}
?>
<script type="text/javascript">
    ///
    window.onload=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;
            }
        }
    }
</script>
複製代碼

3、總結

微信H5支付的整個流程,大概就是這樣子,如下是流程圖

接口流程圖

  1. 用戶在商戶側完成下單,使用微信支付進行支付
  2. 由商戶後臺向微信支付發起下單請求(調用統一下單接口)注:交易類型trade_type=MWEB
  3. 統一下單接口返回支付相關參數給商戶後臺,如支付跳轉url(參數名「mweb_url」),商戶經過mweb_url調起微信支付中間頁
  4. 中間頁進行H5權限的校驗,安全性檢查(此處常見錯誤請見下文)
  5. 如支付成功,商戶後臺會接收到微信側的異步通知
  6. 用戶在微信支付收銀臺完成支付或取消支付,返回商戶頁面(默認爲返回支付發起頁面)
  7. 商戶在展現頁面,引導用戶主動發起支付結果的查詢
  8. 商戶後臺判斷是否接到收微信側的支付結果通知,如沒有,後臺調用咱們的訂單查詢接口確認訂單狀態
  9. 展現最終的訂單支付結果給用戶

轉載請註明出處:www.jianshu.com/p/e49c8003c… 做者:TSY

喜歡的話,能夠關注下個人微信公衆號

微信公衆號
相關文章
相關標籤/搜索