4 月 19 日微信官方發布消息表示受蘋果公司新規定影響,iOS版微信公眾平臺讚賞功能將被關閉。此消息一經發出,就立刻引起了軒然大波,蘋果與微信又一次站在了風口浪尖。很多人估計又要思考讚賞功能關閉後,還有什麼方法可以打賞了。這場蘋果與騰訊的戰爭,讚賞功能的關閉只是表象,究其原因其實還是蘋果對微信支付的一種制約,是雙方利益爭奪的產物。蘋果如此打壓微信支付的原因大家心知肚明,那麼微信強大的支付功能究竟如何實現開發呢?今天我們就來探個究竟。
支付幾乎是所有商業模式中實現閉環的必經環節,因此微信支付是微信生態中尤為重要的一個組成部分,也是近年來許多創業者願意選擇把產品第一版本的實現使用微信公眾號的原因。
服務號的微信支付,按照接入的方式,可以把它們歸納為三大類:微信支付服務商、聚合支付和自主開發。下面對這三類接入方式從適用場景、模式介紹和優劣勢三個維度進行分析對比,大家可根據自身需求進行匹配選擇。
微信支付服務商適用商家:沒有技術開發能力的商家,例如餐館、小賣部等。
介紹:微信支付服務商,指由微信支付審核且籤約合作的,具有優秀的技術開發能力的第三方開發者。服務商可為所拓展的特約商戶完成支付申請、技術接入、活動營銷等全生態服務。
注意事項:藉助這種方式接入微信支付時需要注意,微信支付收款的錢到了哪裡。一種是收款直接到了商戶的微信支付帳戶,再按照微信的結算周期進行提現。另一種是收款先到微信支付服務商,進行二次清算,然後再由商戶登錄服務商後臺進行提現,相當於是錢到了服務商帳戶,再到商戶帳戶。這種方式的接入,用戶在微信支付過程中輸入密碼的彈出框中,顯示的是服務提供商的名字,而並非商家名字。
優勢:擁有對帳等高級接口,可以享有微信支付接入、交易、營銷等全生態服務;提供O2O解決方案,提供線下收款POS機,甚至還提供線上線下分銷解決方案;有些微信支付服務商還提供會員體系的解決方案。
劣勢:微信支付環節不受商家控制和定製。
舉例:掌貝、樂刷、拉卡拉。
適用商家:具有一定技術開發能力,並且需要對接的支付渠道較多(微信支付、支付寶支付、銀聯支付、京東支付,等等)的商家。
介紹:一站式支付解決方案,把多個支付渠道的支付接入打包成一個解決方案,開發者只需要進行少量的代碼開發,便可以接入支付。這類聚合支付提供商,會在構建支付憑據、調起支付控制項以及異步通知等處做封裝處理,商戶不需要關心各渠道支付的參數、請求地址、加密方式和流程的區別,大大簡化商戶的接入成本。商戶可以自行申請各支付渠道的資質,也可以由服務商代申請(通常是收費服務),申請通過之後,在服務商後臺填入相應的支付參數。
注意事項:其本質上跟微信支付服務商做的事情是一樣的,因此,需要關注資金的流向。通常,這類聚合支付服務提供商不會介入資金流。
優勢:支持多渠道的支付接入,開發者無需開發多套支付接入代碼;該模式的接入,不會影響自主開發微信支付;集中進行跨渠道的交易管理、查帳對帳、數據分析、報表輸出等;提供良好的支付體驗。
劣勢:支付強依賴於第三方服務,增加系統的外部依賴因素。因此,穩定性和安全性是選擇支付集成提供商的重要考量。
舉例:BeeCloud、Ping++。
適用商家:具有自主研發能力,對支付場景和支付體驗要求高的商家。
介紹:根據微信支付開發文檔,由商家自主開發微信支付的整套流程。
優勢:靈活度較高,資金可控。
劣勢:接入周期較長。
由上述分析可知,選擇微信支付服務商和第三方支付集成提供商,不會涉及或只涉及較少的代碼開發,接入這兩種微信支付模式比較容易。下面的內容會對微信支付的自主開發進行詳細介紹,並對一些支付場景和體驗給出解決方案。
接入微信支付的公眾號,必須滿足以下兩個條件。
開發之前,還需要做一些簡單的設置。
① 設置支付授權目錄:所有使用公眾號支付方式(JSAPI)發起的支付請求的連結地址,都必須在支付授權目錄之下,最多可設置 3 個,並且必須在同一域名下。域名必須經過ICP備案,支持HTTP和HTTPS;須細化到二級或三級目錄,以左斜槓「/」結尾;必須上傳授權文件至支付域名的根目錄。另外,可以設置測試支付授權目錄,方便開發者在開發期間測試微信支付。有關支付授權目錄設置的要求,參見公眾號微信支付後臺,依次進入「微信支付→開發配置→公眾號支付」進行設置。下面給出幾個合法和非法的支付目錄設置示例:
發起支付的連結:http://wx.hello1010.com/wechat/pay/?order_id=8805
合法的支付授權目錄:http://wx.hello1010.com/wechat/pay/
非法的支付授權目錄:http://wx.hello1010.com/wechat/ (未精確到二級目錄)
非法的支付授權目錄:http://wx.hello1010.com/wechat/pay (沒有以左斜槓結尾)
② 設置支付API密鑰:登錄商戶平臺,首次進入後需要安裝操作證書,然後依次進入「帳戶中心→API安全→API密鑰」,第一次進入時會需要設置一個密鑰,建議設置成長度為 32 的字符串。需要注意的是,API密鑰屬於敏感信息,需要妥善保管,不要洩露,如果懷疑密鑰洩露,需要重新設置API密鑰。API密鑰的作用是,在API調用時按照指定規則對請求參數進行籤名,確保調用者的合法身份。
經過上述兩個步驟的設置準備工作,就可以進行開發了。
公眾號支付,指的是用戶在微信環境內進入商家H5頁面,頁面內調用JS-SDK完成支付。從用戶發起支付,輸入支付密碼,支付成功,到商家後臺收到異步回調,微信支付的整個交互細節可以總結為以下三步。
① 用戶在微信環境內打開商戶商品頁面,發起支付,在網頁端,通過WeixinJSBridge調用getBrandWCPayRequest接口發起微信支付請求,用戶輸入支付密碼或指紋驗證。
② 用戶支付成功後,商戶的前端頁面會收到getBrandWCPayRequest的返回值err_msg,根據返回值的含義跳轉到不同的頁面,err_msg的值及含義如下。
支付成功:get_brand_wcpay_request:ok
用戶取消支付:get_brand_wcpay_request:cancel
支付失敗:get_brand_wcpay_request:fail
③ 商戶後臺收到來自微信伺服器的支付成功異步回調,設置該筆交易訂單狀態。支付失敗或支付取消不會收到回調。
需要特別說明的是,微信支付成功的標誌,一定要以第③步中的後臺異步回調為準,收到異步回調並通過驗證後才能把訂單狀態設置為已支付。第②步中的頁面支付狀態,只能作為跳轉到不同頁面的參數判斷。
微信官方提供了公眾號支付API對於的SDK和調用示例,有不用語言的版本(JAVA、.NET C#和PHP),下載地址為:https://pay.weixin.qq.com/wiki/doc/api/index.html
我們可以根據業務需求和支付場景,選擇不同的支付方式,進入相應頁面下載相應的原始碼。微信也提供了一個微信支付的體驗地址,大家可以在微信端打開頁面體驗:http://paysdk.weixin.qq.com/
PHP版本的SDK下載後解壓,目錄結構和文件介紹如下所示。
├── cert 商戶證書目錄
│ ├── apiclient_cert.pem
│ └── apiclient_key.pem
├── doc SDK使用文檔目錄
│ ├── README
│ ├── README.doc
├── example 示例代碼目錄
│ ├── WxPay.JsApiPay.php
│ ├── WxPay.MicroPay.php
│ ├── WxPay.NativePay.php
│ ├── download.php
│ ├── jsapi.php
│ ├── log.php
│ ├── micropay.php
│ ├── native.php
│ ├── native_notify.php
│ ├── notify.php
│ ├── orderquery.php
│ ├── phpqrcode 一個開源的二維碼生成類,PHP版本的實現
│ │ └── phpqrcode.php
│ ├── qrcode.php
│ ├── refund.php
│ └── refundquery.php
├── image
│ ├── bk.png
│ ├── image001.jpg
│ └── image002.png
├── index.php
├── lib API接口代碼封裝
│ ├── WxPay.Api.php 接口訪問類,包含所有微信支付API的封裝
│ ├── WxPay.Config.php 商戶配置信息,例如商戶號,支付密鑰等
│ ├── WxPay.Data.php 輸入數據對象基礎類,例如計算、設置和獲取籤名
│ ├── WxPay.Exception.php API異常處理類
│ └── WxPay.Notify.php 支付成功後臺異步回調基類
└── logs其中,cert目錄的證書文件,可以登錄商戶管理後臺,依次進入「帳戶中心→API安全→API證書」即可下載。證書文件,主要是用來界定接口請求者的身份信息,也包括界定所調用服務及域名的真實性,保證了調用方和被調用方的身份信息和安全。部分安全性要求較高的API(例如退款、撤銷訂單)需要使用該證書,以免被盜用而造成資金損失。需要特別注意的是,證書的有效期是兩年,到期後需要更改證書並下載。
把下載的SDK目錄以及文件全部複製至項目工程中,這類SDK屬於第三方平臺庫,在third_party目錄中新建wxpay目錄,並複製微信支付SDK至wxpay目錄。
打開lib目錄的WxPay.config.php文件,填寫APPID、MCHID、KEY和APPSECRET四個參數,這些參數的含義如下。
在example文件夾中,jsapi.php是公眾號支付的示例,該源碼中沒有做代碼分層處理,不符合我們項目工程的MVC架構,因此需要對代碼進行分解。
在controllers目錄中新建Wxpay.php文件,用來實現微信支付的相關代碼邏輯,對應的視圖文件在view/wxpay/目錄中,目錄結構如圖 1 所示。
圖1 微信支付相關目錄結構
Wxpay.php文件的實現如代碼清單 1 所示。
代碼清單1
<?php /**
* 微信支付示例代碼
*/class Wxpay extends MY_Controller{
/**
* 微信支付
*/
public function pay(){
$data['head_title'] = '微信支付';
$this->check_wechat_login();
$social_info = $this->session->userdata(KEY_SOCIAL_USER_INFO);
//加載微信支付兩個核心文件
require_once APPPATH . 'third_party/wxpay/lib/WxPay.Api.php';
require_once APPPATH . 'third_party/wxpay/WxPay.JsApiPay.php';
$tools = new JsApiPay();
$openId = $social_info['social_id'];
$input = new WxPayUnifiedOrder();
$input->SetBody("測試商品");
$input->SetAttach("hello_attach_data");
$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($this->config->item('app_path') . 'wxpay/notify');
$input->SetTrade_type("JSAPI");
$input->SetOpenid($openId);
$order = WxPayApi::unifiedOrder($input);
$jsApiParameters = $tools->GetJsApiParameters($order);
$data['jsApiParameters'] = $jsApiParameters;
$this->render('index', $data);
}
/**
* 微信支付結果通知
* 回調地址: wxpay/notify
*/ public function notify(){
require_once APPPATH . 'third_party/wxpay/WxPay.Notify.php';
$notify = new PayNotifyCallBack();
$notify->Handle(false);
}
/**
* 生成訂單號,同一個訂單,生成的微信支付訂單號相同
* @param $order_id 訂單號
* @return string
*/
private function build_order_id($order_id){
return date("Ymd", time()) . $order_id;
}}代碼清單 1 中定義了兩個函數,分別是微信支付實現函數pay、微信支付後臺異步回調函數notify。
pay函數中的代碼邏輯跟微信支付SDK中的example/jsapi.php代碼基本保持一致,主要實現了微信支付API中的統一下單流程,在WxPayApi::unifiedOrder方法中實現,並在微信支付服務後臺生成預支付交易單。這是除被掃支付場景外,所有微信支付場景(掃碼、JSAPI、App等)都需要執行的步驟。需要注意的有兩點。
notify方法封裝了微信支付結果通知邏輯。
注意
關於微信支付中的商戶訂單號(對應到out_trade_no欄位)生成,需要特別說明一下。微信支付要求商戶訂單號保持唯一性,重新發起一筆支付時需要使用原訂單號,以避免重複支付。另外,已支付或已撤銷的訂單號不能重複發起支付。這裡的做法是,在生成系統訂單的同時,生成商戶訂單號,並根據當前系統時間加隨機序列的方式來保證唯一性。不要在用戶發起微信支付時才生成out_trade_no的值,那樣會帶來問題,比如用戶對同一筆訂單多次發起支付,則每次生成的商戶訂單號都不一樣,在用戶成功支付後,再點擊微信左上角的「返回」,會回到上一個頁面,即微信支付頁面。假如該頁面是直接拉起微信支付的,則會誤導用戶再支付一次。假如我們採取預先生成商戶訂單號的方式,那麼就算再次拉起支付,由於商戶訂單號相同,微信支付會提示訂單號重複的錯誤,用戶無法再次支付。當然,剛討論的重複支付問題也有其他解決方案,例如讓用戶主動點擊按鈕再發起微信支付。
Wxpay.php中的pay方法加載的視圖文件在view/layout/wxpay/index.php中,它主要實現在前端頁面通過JSAPI發起微信支付,如代碼清單 2 所示。需要注意的是,由於JSAPI依賴於WeixinJSBridge內置對象,涉及微信WebView與App層的交互,因此需要等待該對象準備就緒才能調用相關方法。另外,WeixinJSBridge內置對象在非微信瀏覽器環境中無效。JSAPI發起的微信支付只能在微信瀏覽器環境生效。
代碼清單2
<script type="text/javascript">
/**
* 綁定微信支付事件
*/
$('#pay').bind('click', function () {
callWechatPay();
});
/**
* 調用微信JS api 支付
*/
function wechatJsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
<?php echo isset($jsApiParameters) ? $jsApiParameters : "''"; ?>,
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok"){
//微信支付成功
alert('微信支付成功');
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
//用戶取消支付
alert('用戶取消支付');
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
//微信支付失敗
alert('微信支付失敗');
}
}
);
}
/**
* 微信支付
*/
function callWechatPay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', wechatJsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', wechatJsApiCall);
document.attachEvent('onWeixinJSBridgeReady', wechatJsApiCall);
}
}else{
wechatJsApiCall();
}
}</script>微信支付的結果通過以下兩種方式通知。
前端頁面JSAPI回調:支付JSAPI中的getBrandWCPayRequest方法接收兩個參數,即支付參數(公眾號AppId、微信籤名等)和支付回調函數。支付回調函數接收一個res參數,代表支付結果,分為支付成功、支付過程中用戶取消和支付失敗這三種情況。我們可以根據這三種情況分別做不同的頁面跳轉邏輯。示例代碼中只是做了簡單的alert彈窗提示。由於前端交互複雜,微信官方文檔中對支付過程中用戶取消和支付失敗的這兩種情況,建議不必細化處理,可以統一處理為用戶遇到錯誤或主動放棄。需要特別說明的一點是,通過前端支付回調函數的結果值判斷支付是否成功,並不絕對可靠。也就是說,前端的支付狀態,不能作為設置訂單支付狀態的依據,只能作為頁面跳轉的依據。
後臺異步支付結果通知:剛剛提到,前端的支付狀態並不能作為設置訂單狀態的最終依據,那麼應該以什麼為準呢,答案就是微信支付的異步結果通知。微信支付完成後,微信會把相關支付結果和用戶信息發送給商戶,該回調地址在統一下單時通過SetNotify_url設置,在示例代碼中,回調地址是wxpay/notify。主要的代碼邏輯在third_party/wxpay/WxPay.Notify.php文件的NotifyProcess方法中,在這裡做籤名校驗、訂單狀態設置以及消息通知等,如代碼清單 3 所示。
代碼清單3
//重寫回調處理函數public function NotifyProcess($data, &$msg){
log_message('debug', "wechat pay notify: " . json_encode($data));
if(!array_key_exists("transaction_id", $data)){
$msg = "輸入參數不正確";
return false;
}
//查詢訂單,判斷訂單真實性
if(!$this->Queryorder($data["transaction_id"])){
$msg = "訂單查詢失敗";
return false;
}
//①檢查是否已經通知過
//TODO .
//②設置訂單狀態
//TODO .
//③發送消息通知
//TODO .
//④其他代碼邏輯
//TODO .
return true;
}下面給出一個伺服器收到的微信支付回調通知數據(appId參數已被替換,隱藏了真實的appId):
DEBUG - 2016-09-20 01:23:02 --> wechat pay notify: {"appid":"xxxxxx","attach": "hello_attach_data","bank_type":"CFT","cash_fee":"1","fee_type":"CNY","is_subscribe":"Y","mch_id":"1295040401","nonce_str":"43x2c9zfifysbdys4ntsqurs2sj38hws","openid":"oNgYlt-L_P-4d6QsawLFhTSIBz7Q","out_trade_no":"20160920012227","result_code":"SUCCESS","return_code":"SUCCESS","sign": "B7C9E0B6841D2160B8D1A53F5BB4E176","time_end":"20160926012301","total_fee":"1","trade_type":"JSAPI","transaction_id":"4007442001201609260837192495"}通過以上的分析可知,上述兩種支付結果通知方式,我們要明白它們的通知意圖,再做出相應的處理。
關於第二種通知方式,有一個細節需要明確一下:同樣的支付結果通知,商戶伺服器可能會收到多次。也就是說我們的代碼邏輯需要能處理重複通知的情況,以避免重複的通知帶來的問題。例如消息的重複發送,這將會帶來不好的用戶體驗。這裡的做法是收到支付通知時,則判斷是否已經通知過,假如已通知過,則直接返回true。狀態的初始化,是在發起支付時,在Redis緩存中設置訂單的支付通知狀態為未通知。
關於微信支付的結果通知,在調試過程中發現,按照微信支付文檔寫完代碼邏輯之後,並正常完成支付,並不能正常收到支付結果的異步通知,經過日誌調試,發現在third_party/wxpay/lib/WxPay.Api.php文件中的notify方法中(第 414 行),通過$GLOBALS['HTTP_RAW_POST_DATA']方式獲取的xml數據為空,如下所示:
//獲取通知的數據
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];在網上搜索相關問題後,在php.net中發現這麼一段說明:
always_populate_raw_post_data boolean
Always populate the $HTTP_RAW_POST_DATA containing the raw POST data. Otherwise, the variable is populated only with unrecognized MIME type of the data. However, the preferred method for accessing the raw POST data is php://input. $HTTP_RAW_POST_DATA is not available with enctype="multipart/form-data".文檔連結地址:http://php.net/manual/zh/ini.core.php#ini.always-populate-raw- post-data。
以上說明提示我們需要設置php.ini中某個參數值,或者是使用php://input的方式獲取數據,因此解決方案有兩個。
always_populate_raw_post_data = true//獲取通知的數據
$xml = file_get_contents("php://input");採用第二種方式修改方案,修改代碼之後,就能正確收到支付結果通知了。最後需要強調的一點是,由於微信支付的回調通知不會帶任何cookies信息,因此,不能在回調通知函數(包括類的構造函數)中做任何身份驗證的代碼邏輯,例如獲取session會話。
下面是微信支付示例的運行效果,如圖 2 、圖 3 和圖 4 所示。
圖2 微信支付商品展示頁
圖3 微信支付輸入密碼
圖4 微信支付成功
(本文節選自張劍明作品《微信公眾平臺與小程序開發——從零搭建整套系統》)
(點擊圖片,查看全書)
求職必備,Web工程師與後臺工程師必修技能。
即學即用,系統詳盡地呈現最高效的開發步驟。
自我修煉,從思路到實踐,全面提升開發水平。