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


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

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

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

APP支付

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

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

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

步驟3:統一下單接口返回正常的 prepay_id ,再按簽名規范重新生成簽名后,將數據傳輸給APP。參與簽名的字段名為 appId , partnerId , prepayId , nonceStr , timeStamp , package 。 注意:package的值格式為Sign=WXPay

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

步驟5:商戶后台接收支付通知。

步驟6:商戶后台查詢支付結果。

這里主要的還是后台干活(獲取 prepay_id ,生成隨機字符串 nonceStr 和時間戳 timeStamp, appId 和 partnerId 均能在后台管理中查看。)

后台的步驟也很簡潔,就是上述中的步驟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_code 和 result_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_code 和 result_code 均為 SUCCESS ,即為退款申請成功。更多返回結果,請移步至 官網

結尾

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

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

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


免責聲明!

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



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