微信公眾號支付開發細節


開發背景

  微信長按二維碼識別支付的功能被取消了,所以項目改為使用公眾號支付。默認讀者已經獲得 微信公眾號的開發權限和在微信開通了商戶號

開發流程

     一、微信支付DEMO功能簡要介紹

   微信平台上有支付的DEMO,我們可以下載下來直接使用。下面我們來分析一下DEMO中主要文件的作用。

      example/jsapi.php主要是微信公眾號支付

            example/native.php主要是微信掃碼支付,其中包括模式一和模式二。(模式一:先掃碼,再生成訂單。模式二:先生成訂單,再掃碼)

            example/notify.php主要是用來處理回調信息的。為什么要處理回調信息呢?我們可以這樣的思考:當我們支付成功之后,微信肯定要告訴我們支付是否      成功。那么微信要告訴誰呢?要告訴那個函數呢?這個就需要我們自己來設置了,我們給微信一個回調地址,那么微信就會把支付結果信息發送到這個地址上去。光      接收到信息是不夠的,我們呢,還需要函數來處理這些信息。notify.php里面就包括了處理函數。

     

    二、微信公眾號支付及其流程 

           我先從頭到尾的按照線性順序來屢一下流程吧。具體的可以看下圖:

        

              要用微信公眾號支付,首先我們要做的就是調用jsapi.php。

     jsapi.php具體內容如下:一些地方添加有注釋幫助大家理解

<?php 
ini_set('date.timezone','Asia/Shanghai');
//error_reporting(E_ERROR);
//這個是引用所需的文件
require_once "../lib/WxPay.Api.php"; require_once "WxPay.JsApiPay.php"; require_once 'log.php'; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); //打印輸出數組信息 function printf_info($data) { foreach($data as $key=>$value){ echo "<font color='#00ff55;'>$key</font> : $value <br/>"; } } //①、獲取用戶openid $tools = new JsApiPay(); $openId = $tools->GetOpenid(); //②、統一下單 $input = new WxPayUnifiedOrder(); $input->SetBody("test");//一般填寫用戶的訂單號 $input->SetAttach("test");//一般填寫用戶的訂單號 $input->SetOut_trade_no(WxPayConfig::MCHID.date("YmdHis"));//填寫用戶的訂單號 $input->SetTotal_fee("1");//支付費用 $input->SetTime_start(date("YmdHis")); $input->SetTime_expire(date("YmdHis", time() + 600)); $input->SetGoods_tag("test"); $input->SetNotify_url("http://paysdk.weixin.qq.com/example/notify.php");//剛才講的回調函數所在的地址,要自己設定 $input->SetTrade_type("JSAPI"); $input->SetOpenid($openId); // $order = WxPayApi::unifiedOrder($input); echo '<font color="#f00"><b>統一下單支付單信息</b></font><br/>'; printf_info($order);//打印訂單的詳細信息 $jsApiParameters = $tools->GetJsApiParameters($order); //獲取共享收貨地址js函數參數 $editAddress = $tools->GetEditAddressParameters(); //③、在支持成功回調通知中處理成功之后的事宜,見 notify.php /** * 注意: * 1、當你的回調地址不可訪問的時候,回調通知會失敗,可以通過查詢訂單來確認支付是否成功 * 2、jsapi支付時需要填入用戶openid,WxPay.JsApiPay.php中有獲取openid流程 (文檔可以參考微信公眾平台“網頁授權接口”, * 參考http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html) */ ?> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>微信支付樣例-支付</title> <script type="text/javascript"> //調用微信JS api 支付 function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', <?php echo $jsApiParameters; ?>, function(res){ WeixinJSBridge.log(res.err_msg); 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> <script type="text/javascript"> //獲取共享地址 function editAddress() { WeixinJSBridge.invoke( 'editAddress', <?php echo $editAddress; ?>, function(res){ var value1 = res.proviceFirstStageName; var value2 = res.addressCitySecondStageName; var value3 = res.addressCountiesThirdStageName; var value4 = res.addressDetailInfo; var tel = res.telNumber; alert(value1 + value2 + value3 + value4 + ":" + tel); } ); } //這段代碼的功能是在支付之前,彈出一個地址填寫框 window.onload = function(){ if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', editAddress, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', editAddress); document.attachEvent('onWeixinJSBridgeReady', editAddress); } }else{ editAddress(); } }; </script> </head> <body> <br/> <font color="#9ACD32"><b>該筆訂單支付金額為<span style="color:#f00;font-size:50px">1分</span>錢</b></font><br/><br/> <div align="center"> <button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button> </div> </body> </html>

      我們先講一下獲取用戶openid這段代碼$openId = $tools->GetOpenid();吧。

      //①、獲取用戶openid
      $tools = new JsApiPay();
      $openId = $tools->GetOpenid();
      JsApiPay是Wxpay.JsApipay.php中的一個類
$tools = new JsApiPay();這個新建了一個JsApiPay類,然后執行GetOpenid()這個函數。這個函數代碼在下面:
    /**
     * 
     * 通過跳轉獲取用戶的openid,跳轉流程如下:
     * 1、設置自己需要調回的url及其其他參數,跳轉到微信服務器https://open.weixin.qq.com/connect/oauth2/authorize
     * 2、微信服務處理完成之后會跳轉回用戶redirect_uri地址,此時會帶上一些參數,如:code
     * 
     * @return 用戶的openid
     */
    public function GetOpenid()
    {
        //通過code獲得openid
        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 {
            //獲取code碼,以獲取openid
            $code = $_GET['code'];
            $openid = $this->getOpenidFromMp($code);
            return $openid;
        }
    }

    isset用來判斷是不是存在$_GET['code']這個變量,如果不在的話,就執行了if語句,如果存在,就執行了else語句。

    當我們第一次調用GetOpenid()的時候當然不存在$_GET['code']這個變量,所以就執行了if語句。接下來我們看看if語句的內容:

    $baseUrl是用來獲取你當前的url,為什么要獲取當前的url呢?我們先看這一段代碼$url = $this->__CreateOauthUrlForCode($baseUrl);它調用了一個函數,函數的具體內容如下:

	private function __CreateOauthUrlForCode($redirectUrl)
	{
		$urlObj["appid"] = WxPayConfig::APPID;
		$urlObj["redirect_uri"] = $redirectUrl;
		$urlObj["response_type"] = "code";
		$urlObj["scope"] = "snsapi_base";
		$urlObj["state"] = "STATE"."#wechat_redirect";
		$bizString = $this->ToUrlParams($urlObj);
		return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString;
	}

    我們可以看到有設置一些變量,比如appid、response_type、scope、state等。appid就是你配置的APPID,$urlObj["response_type"]='code'也就是說它會返回一些值,這些值的類型是code類型,看到這里有沒有發現一些奇怪的地方?對,剛才有個if語句判斷是否存在$_GET['code']這個變量而現在它給我們返回了code類型的數據。是不是巧合呢?當然不是巧合,別忘了還有一個$urlObj["redirect_uri"] = $redirectUrl;是把我們剛才獲取的當前的url作為一個變量傳遞過去。傳到哪里?傳到https://api.weixin.qq.com/sns/oauth2/access_token這個地方,然后呢,微信會進行驗證,如果你給它的數據沒錯的話,那么它會返回相關的數據給這個變量$urlObj["redirect_uri"]。

    這樣就又跳到了下面這個函數的地方,不過不同的是這次我們有了code,於是我們就通過$openid = $this->getOpenidFromMp($code);這個函數獲得到了openID.

    public function GetOpenid()
    {
        //通過code獲得openid
        if (!isset($_GET['code'])){
            //觸發微信返回code碼
            $baseUrl = urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].$_SERVER['QUERY_STRING']);
            $url = $this->__CreateOauthUrlForCode($baseUrl);
            Header("Location: $url");
            exit();
        } else {
            //獲取code碼,以獲取openid
            $code = $_GET['code'];
            $openid = $this->getOpenidFromMp($code);
            return $openid;
        }
    }

    獲取openID之后,如果前面的配置信息沒有什么錯誤的話,那么我們就會彈出這個頁面了。

    這就說明我們做的成功了。

    其中有一點要說明一下,因為它在獲取openID的時候,來來回回的跳了好多次,所以$baseUrl 里面的一些參數變量會丟失,所以建議大家自己寫url,同時也可傳遞一些參數。這個問題大家要格外的注意一下

    接着我們再談談回調函數的問題:以下是處理回調的函數代碼也就是notify.php

<?php
ini_set('date.timezone','Asia/Shanghai');
error_reporting(E_ERROR);

require_once "../lib/WxPay.Api.php"; require_once '../lib/WxPay.Notify.php'; require_once 'log.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); $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 true; } return false; } //重寫回調處理函數 public function NotifyProcess($data, &$msg) { Log::DEBUG("call back:" . json_encode($data)); $notfiyOutput = array(); if(!array_key_exists("transaction_id", $data)){ $msg = "輸入參數不正確"; return false; } //查詢訂單,判斷訂單真實性 if(!$this->Queryorder($data["transaction_id"])){ $msg = "訂單查詢失敗"; return false; } return true; } } Log::DEBUG("begin notify"); $notify = new PayNotifyCallBack(); $notify->Handle(false);

 

    我們直接看$notify->Handle(false);這個是回調函數的入口。但是我們在這段代碼中並沒有找到handle()函數,這是為什么呢?因為PayNotifyCallBack類繼承了WxPayNotify類,所以我們到lib/WxPay.Notify.php中WxPayNotify中去找就可以了。

    其中代碼如下:

<?php
/**
 * 
 * 回調基礎類
 * @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());
    }
}

    可以看到$result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);這句話是最關鍵的,它會調用NotifyCallBack函數,然后NotifyCallBack函數會調用$result = $this->NotifyProcess($data, $msg);函數,其中public function NotifyProcess($data, &$msg);默認是返回true的,我們可以在這個函數里寫自己要操作的邏輯,比如把訂單插入到數據庫中等操作。到此,就完成了整個流程

    那$data這個數據是什么呢?

    {

    "appid":"",

    "attach":"",

    "bank_type":"",

    "cash_fee":"1",

    "fee_type":"CNY",

    "is_subscribe":"Y",

    "mch_id":"",

    "nonce_str":"",

    "openid":"",

    "out_trade_no":"",

    "result_code":"SUCCESS",

    "return_code":"",

    "sign":"",

    "time_end":"20170409184335",

    "total_fee":"1",

    "trade_type":"JSAPI",

    "transaction_id":""}

那就到此結束啦!!

2017.4.18日補充:

我們同時需要在這個地方填寫支付授權目錄,如果支付授權目錄填寫錯了的話,會報錯redirect_uri,具體過程請參考下面的鏈接文檔:

http://www.cnqn.com/archives/180482.html

http://www.thinkphp.cn/code/1620.html

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM