微信支付,支持的支付方式比較多:有掃碼支付,刷卡支付,APP支付和公衆號支付。其中,APP和網站上最經常使用的就是APP支付和公衆號支付。前者集成在APP中,後者主要是爲微信用戶提供了另外一種支付方式(須要在微信的內置瀏覽器中打開頁面,再調起微信支付)。javascript
一樣的,微信的APP支付和支付寶的APP支付也是很簡單:php
商戶系統和微信支付系統主要交互說明:前端
步驟1:用戶在商戶APP中選擇商品,提交訂單,選擇微信支付。java
步驟2:商戶後臺收到用戶支付單,調用微信支付統一下單接口。參見【統一下單API】。web
步驟3:統一下單接口返回正常的prepay_id,再按簽名規範從新生成簽名後,將數據傳輸給APP。參與簽名的字段名爲
appId
,partnerId
,prepayId
,nonceStr
,timeStamp
,package
。注意:package的值格式爲Sign=WXPayjson步驟4:商戶APP調起微信支付。api
步驟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 function arrayToXml(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 function xmlToArray($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支付,後臺須要乾的活到這裏就暫時結束了(由於還有支付成功後的異步通知商戶後面再講)
下面就是web版的微信支付(公司項目是在微信瀏覽器內,選擇微信支付後,在微信中調起的微信支付)
web版微信支付的步驟和APP的大同小異,也是現獲取 prepayId
,再在頁面中,調用jsapi進行支付。
可是,此處有2個坑
appid and openid not match
的報錯緣由很是的簡單,就是支付時所獲取的 openid
在並不屬於支付的商戶。
這個 openid
爲微信用戶在商戶對應appid下的惟一標識。也就是說,必須根據支付的商戶的 appid 去獲取用戶的 openid
。
由於業務邏輯須要,項目中用於微信登陸用的公衆號A與用於支付的公衆號B(其實還和開放平臺用於APP支付的 appId
也是不同的)是不同的,雖然所獲取unionid是一致,可是 openid
是不!一!樣!的!因此,在獲取 openid
時,須要使用當前支付時所用到的 appid 去請求用戶的 openid
,同時,請求 openid 後的回調也必須是 支付商戶 後臺所設置好的回調地址,要否則就會報 redirect_uri 參數錯誤
的錯誤。
↑ APP支付的參數
↑ web支付的參數
仔細看看劃橫線的地方。沒錯,app中的參數的key全是小寫,web支付中的key則爲駝峯命名方式。並且,簽名方式 signType
是必填的, 簽名的字段也變成了 paySign
,其中 package
的值也是不同,APP支付是固定的值,web支付則爲 prepayId
,這也要注意。固然,官方文檔也是很詳細的說明,可是須要細心觀察(因此說嘛,仍是直接拷貝必填項最保險了2333)。
拿到全部參數後,就能夠在頁面中發起微信支付的請求了。
代碼能夠直接使用官方提供的js代碼
function onBridgeReady(){ 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格式直接渲染至頁面便可。
若是參數沒錯的話,那麼就能夠順利的調起支付窗口了。
APP支付是在開放平臺中申請下來的,appId和apiKey都是不同的。而jsapi支付實質就是公衆號支付,是在公衆平臺中申請獲得的。因此,在這裏,須要注意一下。
重要的來了,能體現後臺的重要性的地方終於來了 ---
官方文檔寫得也很詳細了(不得不說,微信的開發文檔真的很清晰。很容易找到。就是沒有詳細的步驟區分)。
首先須要申明的是:異步通知的URL是必須能在公網訪問的,並且,必須不能攜帶參數。
也就是說,http://domain.com/payment/wxp... 是沒問題的,可是 http://domain.com/payment/not... 這樣的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,一當忘記只能重置。)
祝各位開發過程順利進行。