1. 程式人生 > >支付開發填坑記之微信支付

支付開發填坑記之微信支付

wiki index 傳輸 系統 外網 ttr throw div union

微信支付,支持的支付方式比較多:有掃碼支付,刷卡支付,APP支付和公眾號支付。其中,APP和網站上最常用的就是APP支付和公眾號支付。前者集成在APP中,後者主要是為微信用戶提供了另一種支付方式(需要在微信的內置瀏覽器中打開頁面,再調起微信支付)。

微信支付,支持的支付方式比較多:有掃碼支付,刷卡支付,APP支付和公眾號支付。其中,APP和網站上最常用的就是APP支付和公眾號支付。前者集成在APP中,後者主要是為微信用戶提供了另一種支付方式(需要在微信的內置瀏覽器中打開頁面,再調起微信支付)。

同樣的,微信的APP支付和支付寶的APP支付也是很簡單:

APP支付

商戶系統和微信支付系統主要交互說明:

步驟1:用戶在商戶APP中選擇商品,提交訂單,選擇微信支付。

步驟2:商戶後臺收到用戶支付單,調用微信 支付統一 下單接口。參見 【統一下單API】 。

步驟3:統一下單接口返回正常的 prepay_id ,再按簽名規範重新生成簽名後,將數據傳輸給APP。參與簽名的字段名為 appIdpartnerIdprepayIdnonceStrtimeStamppackage註意:package的值格式為Sign=WXPay

步驟4:商戶APP調起微信支付。

步驟5:商戶後臺接收支付通知。

步驟6:商戶後臺查詢支付結果。

這裏主要的還是後臺幹活(獲取 prepay_id

,生成隨機字符串 nonceStr 和時間戳 timeStampappIdpartnerId 均能在後臺管理中查看。)

後臺的步驟也很簡潔,就是上述中的步驟1,2。

  1. 獲取 prepayId

    1. 設置獲取 prepayId 所需參數。

    此處需要調用微信的統一下單接口。這個過程, 官方文檔 已經寫得十分之詳細了,包括調用的接口API地址,需要傳遞的參數(必要和非必要的參數),還有返回結果也寫得很清楚。

    以下是我在實際項目開發中傳入的參數。

    技術分享

    技術分享

    1. 簽名。

    簽名都差不多,都是先將所有的帶簽名的參數進行字典排序。

    ksort($data);
    

    然後將參數以 {key}={value}

    的組合形式,用 & 連接。

    $a = array();
    foreach ($data as $k => $v) {
        if ((string) $v === ‘‘) {
            continue;
        }
        $a[] = "{$k}={$v}";
    }
    
    $a = implode(‘&‘, $a);
    

    最後拼上 &key={Your apiKey} ,然後對整串字符串進行MD5加密即可。

    $sign = strtoupper(md5($a));
    
  2. 將拼好的數據,以 XML 的格式發送給微信,請求 prepayId

    沒錯,就是要轉成 XML 格式再發送。

    但是,這個XML格式很簡單,只需要進行簡單的拼接即可:

    public functionarrayToXml(array $data)
    {
        $xml = "<xml>";
        foreach ($data as $k => $v) {
            if (is_numeric($v)) {
                $xml .= "<{$k}>{$v}</{$k}>";
            } else {
                $xml .= "<{$k}><![CDATA[{$v}]]></{$k}>";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }
    

    參數值用XML轉義即可,CDATA標簽用於說明數據不被XML解析器解析。。

    然後請求統一下單API即可(url = https://api.mch.weixin.qq.com/pay/unifiedorder

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    $response = curl_exec($ch);
    if (!$response) {
        throw new Exception(‘CURL Error: ‘ . curl_errno($ch));
    }
    curl_close($ch);
    

    請求回來的數據也為XML格式,只需要簡單做下處理,轉換成array即可:

    public functionxmlToArray($xml){
        return json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);
    }
    

    如果返回值中的 return_coderesult_code 都為 SUCCESS 的時候會返回 交易類型 trade_type 和 預支付交易會話標識 prepayId 。到這裏,我們就可以獲取到 prepayId

  3. 將獲取的 prepayId 與其他參數拼接,返回給APP即可。

    $params = array();
    // 商戶號
    $params[‘appid‘] = $this->config->appId;
    // 時間戳
    $params[‘timestamp‘] = ‘‘ . time();
    // 隨機字符串
    $params[‘noncestr‘] = md5(uniqid(mt_rand(), true));
    // 固定為 ‘Sign=WXPay‘
    $params[‘package‘] = ‘Sign=WXPay‘;
    // 步驟3獲取的預支付交易會話標識
    $params[‘prepayid‘] = $prepayId;
    // 合作夥伴id
    $params[‘partnerid‘] = $this->config->partnerId;
    // 步驟2生成的簽名。
    $params[‘sign‘] = $this->sign($params);
    

微信APP支付,後臺需要幹的活到這裏就暫時結束了(因為還有支付成功後的異步通知商戶後面再講)

jsapi支付

下面就是web版的微信支付(公司項目是在微信瀏覽器內,選擇微信支付後,在微信中調起的微信支付)

技術分享

web版微信支付的步驟和APP的大同小異,也是現獲取 prepayId ,再在頁面中,調用jsapi進行支付。

但是,此處有2個坑

坑1: 支付時出現 appid and openid not match 的報錯

原因非常的簡單,就是支付時所獲取的 openid 在並不屬於支付的商戶。

這個 openid 為微信用戶在商戶對應appid下的唯一標識。也就是說,必須根據支付的商戶的 appid 去獲取用戶的 openid

因為業務邏輯需要,項目中用於 微信登錄用的公眾號A用於支付的公眾號B (其實還和開放平臺用於APP支付的 appId 也是不一樣的)是不一樣的,雖然所獲取unionid是一致,但是 openid 是不!一!樣!的!所以,在獲取 openid 時,需要使用當前支付時所用到的 appid 去請求用戶的 openid ,同時,請求 openid 後的回調也必須是 支付商戶 後臺所設置好的回調地址,要不然就會報 redirect_uri 參數錯誤 的錯誤。

坑2: 參數名大小寫不一致。

技術分享

↑ APP支付的參數

技術分享

↑ web支付的參數

仔細看看劃橫線的地方。沒錯,app中的參數的key全是小寫,web支付中的key則為駝峰命名方式。而且,簽名方式 signType 是必填的, 簽名的字段也變成了 paySign ,其中 package 的值也是不一樣,APP支付是固定的值,web支付則為 prepayId ,這也要註意。當然, 官方文檔 也是很詳細的說明,但是需要細心觀察(所以說嘛,還是直接拷貝必填項最保險了2333)。

拿到所有參數後,就可以在頁面中發起微信支付的請求了。

代碼可以直接使用官方提供的js代碼

functiononBridgeReady(){
   WeixinJSBridge.invoke(
       ‘getBrandWCPayRequest‘, YOUR_PARAMS,
       function(res){     
           if(res.err_msg == "get_brand_wcpay_request:ok" ) {
               // success_callback
           }     // 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回 ok,但並不保證它絕對可靠。
       }
   ); 
}
if (typeof WeixinJSBridge == "undefined"){
   if( document.addEventListener ){
       document.addEventListener(‘WeixinJSBridgeReady‘, onBridgeReady, false);
   }else if (document.attachEvent){
       document.attachEvent(‘WeixinJSBridgeReady‘, onBridgeReady); 
       document.attachEvent(‘onWeixinJSBridgeReady‘, onBridgeReady);
   }
}else{
   onBridgeReady();
}

其中 YOUR_PARAMS 是參數轉換成json格式直接渲染至頁面即可。

如果參數沒錯的話,那麽就可以順利的調起支付窗口了。

坑3: APP支付和jsapi支付不是同一個號

APP支付是在開放平臺中申請下來的,appId和apiKey都是不一樣的。而jsapi支付實質就是公眾號支付,是在公眾平臺中申請得到的。所以,在這裏,需要註意一下。

重要的來了,能體現後臺的重要性的地方終於來了 —

支付結果的異步通知

官方文檔 寫得也很詳細了(不得不說,微信的開發文檔真的很清晰。很容易找到。就是沒有詳細的步驟區分)。

首先需要申明的是:異步通知的URL是 必須能在公網訪問 的,而且,必須 不能攜帶參數

也就是說, http://domain.com/payment/wxpay/notify.php 是沒問題的,但是 http://domain.com/payment/notify.php?payment_code=wxpay 這樣的URL是不行的。如果要想達到這種效果,要不服務器(Nginx , Apache)進行rewrite,要不在notify.php 中,手動修改 $_GET 中的參數。

返回的數據,都是一致的:

技術分享

這時候,商戶後臺拿到這些異步通知的數據進行簡單的校驗即可,然後修改商戶中相應訂單的支付狀態。

  1. 校驗返回碼是否成功

    $d = $this->xmlToArray(file_get_contents(‘php://input‘));
    if (empty($d)) {
        throw new Exception(__METHOD__);
    }
    if ($d[‘return_code‘] != ‘SUCCESS‘) {
        throw new Exception($d[‘return_msg‘]);
    }
    if ($d[‘result_code‘] != ‘SUCCESS‘) {
        throw new Exception("[{$d[‘err_code‘]}]{$d[‘err_code_des‘]}");
    }
    
  2. 對返回數據進行校驗

    和請求 prepayId 時處理數據的方式差不多,先取出簽名 sign ,然後除去簽名後,進行字典排序,以 {key}={value} 的方式進行組合,並在最後加上 &key={apiKey} 得到待校驗字符串,最後,將待校驗字符串進行MD5加密,和簽名進行比較,若一致則校驗成功,並且支付成功,然後後臺做相應操作。

    if (!$this->verify($d)) {
        throw new Exception("Invalid signature");
    }
    
    // 驗證函數
    if (empty($d[‘sign‘])) {
        return false;
    }
    $sign = $d[‘sign‘];
    unset($d[‘sign‘]);
    return $sign == $this->sign($d);
    

微信退款

有支付肯定就會有退款。微信的退款操作也是很簡單,而且退款速度非常快,測試時基本都是秒退。

但是退款是有註意事項的:

  1. 交易時間超過 一年 的訂單無法提交退款;
  2. 微信支付退款支持單筆交易分多次退款,多次退款需要提交 原支付訂單的商戶訂單號 和設置 不同的退款單號 。總退款金額不能超過用戶實際支付金額。 一筆退款失敗後重新提交,請不要更換退款單號,請使用原商戶退款單號 。
  3. 退款請求需要證書

【證書獲取方式:】

微信支付接口中,涉及資金回滾的接口會使用到商戶證書,包括退款、撤銷接口。商家在申請微信支付成功後,收到的相應郵件後,可以按照指引下載API證書,也可以按照以下路徑下載:微信商戶平臺(pay.weixin.qq.com)–>賬戶中心–>賬戶設置–>API安全–>證書下載。

微信退款程序流程:

技術分享
  1. 設置退款時得參數。

    請求的參數有:

    1. appid : 公眾賬號ID
    2. mch_id : 商戶號
    3. nonce_str : 隨機字符串
    4. sign: 簽名
    5. transaction_id / out_trade_no :微信訂單號 / 商戶訂單號 二者中傳其中一個即可。
    6. out_refund_no: 商戶退款單號(由商戶自行生成的唯一標識)
    7. total_fee:訂單金額(單位為分)
    8. refund_fee:退款金額(單位為分),退款金額不能大於訂單金額。
    9. op_user_id:操作員帳號, 默認為商戶號

    簽名還是老規矩(默認是MD5方式),先將所有參數進行字典排序,然後以 $key=$value 的形式用 & 字符拼接成字符串,最後將拼上 &key=YOUR_APIKEY 的待簽名字符串進行MD5加密即可。

    技術分享
  2. 將參數列表轉換成XML格式。

    技術分享
  3. 發送退款請求。

    退款請求需要攜帶微信上下載的證書,請保證證書存放路徑外網不能直接訪問。

    技術分享
  4. 解析請求結果。

    當返回的結果中, return_coderesult_code 均為 SUCCESS ,即為退款申請成功。更多返回結果,請移步至 官網

結尾

微信支付的官方開發文檔其實算是很詳細了,傳遞的參數,返回結果,如果判斷是否成功,都寫的很好。只是,開發中的邏輯過程需要自己慢慢摸索,理清思路後,開發起來其實都是很迅速的。

但是,開發微信支付時,需要留個心,需要將所有涉及到的微信後臺提供的數據小心保存(比如AppSecret,一當忘記只能重置。)

祝各位開發過程順利進行。

支付開發填坑記之微信支付