支付開發填坑記之微信支付
微信支付,支持的支付方式比較多:有掃碼支付,刷卡支付,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。
-
獲取
prepayId
:- 設置獲取
prepayId
所需參數。
此處需要調用微信的統一下單接口。這個過程, 官方文檔 已經寫得十分之詳細了,包括調用的接口API地址,需要傳遞的參數(必要和非必要的參數),還有返回結果也寫得很清楚。
以下是我在實際項目開發中傳入的參數。
- 簽名。
簽名都差不多,都是先將所有的帶簽名的參數進行字典排序。
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));
- 設置獲取
-
將拼好的數據,以
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
。 -
將獲取的
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
中的參數。
返回的數據,都是一致的:
這時候,商戶後臺拿到這些異步通知的數據進行簡單的校驗即可,然後修改商戶中相應訂單的支付狀態。
-
校驗返回碼是否成功
$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‘]}"); }
-
對返回數據進行校驗
和請求
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);
微信退款
有支付肯定就會有退款。微信的退款操作也是很簡單,而且退款速度非常快,測試時基本都是秒退。
但是退款是有註意事項的:
- 交易時間超過 一年 的訂單無法提交退款;
- 微信支付退款支持單筆交易分多次退款,多次退款需要提交 原支付訂單的商戶訂單號 和設置 不同的退款單號 。總退款金額不能超過用戶實際支付金額。 一筆退款失敗後重新提交,請不要更換退款單號,請使用原商戶退款單號 。
- 退款請求需要證書 。
【證書獲取方式:】
微信支付接口中,涉及資金回滾的接口會使用到商戶證書,包括退款、撤銷接口。商家在申請微信支付成功後,收到的相應郵件後,可以按照指引下載API證書,也可以按照以下路徑下載:微信商戶平臺(pay.weixin.qq.com)–>賬戶中心–>賬戶設置–>API安全–>證書下載。
微信退款程序流程:
-
設置退款時得參數。
請求的參數有:
- appid : 公眾賬號ID
- mch_id : 商戶號
- nonce_str : 隨機字符串
- sign: 簽名
- transaction_id / out_trade_no :微信訂單號 / 商戶訂單號 二者中傳其中一個即可。
- out_refund_no: 商戶退款單號(由商戶自行生成的唯一標識)
- total_fee:訂單金額(單位為分)
- refund_fee:退款金額(單位為分),退款金額不能大於訂單金額。
- op_user_id:操作員帳號, 默認為商戶號
簽名還是老規矩(默認是MD5方式),先將所有參數進行字典排序,然後以
$key=$value
的形式用&
字符拼接成字符串,最後將拼上&key=YOUR_APIKEY
的待簽名字符串進行MD5加密即可。 -
將參數列表轉換成XML格式。
-
發送退款請求。
退款請求需要攜帶微信上下載的證書,請保證證書存放路徑外網不能直接訪問。
-
解析請求結果。
當返回的結果中,
return_code
和result_code
均為SUCCESS
,即為退款申請成功。更多返回結果,請移步至 官網
結尾
微信支付的官方開發文檔其實算是很詳細了,傳遞的參數,返回結果,如果判斷是否成功,都寫的很好。只是,開發中的邏輯過程需要自己慢慢摸索,理清思路後,開發起來其實都是很迅速的。
但是,開發微信支付時,需要留個心,需要將所有涉及到的微信後臺提供的數據小心保存(比如AppSecret,一當忘記只能重置。)
祝各位開發過程順利進行。
支付開發填坑記之微信支付