1. 程式人生 > >談談那些年微信支付踩過的坑

談談那些年微信支付踩過的坑

很早的時候就想寫這篇文章了,作為BAT中的一員,還真不想吐槽它,免得被人身攻擊。有人說,微信支付很簡單嘛,官網有例子,網上也有現成的例子,不過誰用誰知道,本人也是在深入瞭解之後,真心覺得微信支付裡的坑太多,BAT的開發們太敷衍了事,結果給不少的其他開發者帶來諸多麻煩。我在這裡做個稍全一點的介紹,儘量減少其他同學們掉坑裡的概率。

在微信上建立你的應用

這裡特別強調一下,這一步很重要,不然微信支付整合除錯會出現莫名的錯誤。
1,在註冊之前對於Android客戶端,需要提供app應用的包名和應用簽名(md5值),這兩個東西問開發或產品同學要。儘量在註冊前提供。另外還需準備一個28x28和108x108的logo圖片,問設計或產品同學要。
2,在

微信開放平臺上註冊,注意了,是微信開放平臺,不是公眾號平臺,公眾號平臺賬號不能在開放平臺登入。如果已經註冊過,請直接登入,登入成功後,點選建立移動應用,建立Android應用時,應用包名應用簽名,儘量一次性填對了。應用建立成功後,將得到appid(以wx開頭的一串數字)。然後再去申請微信支付(需做開發者認證並繳費)
3,在[微信支付商戶平臺]註冊或登入,申請app支付,申請通過後,將得到商戶idmch_id(一串10位數字),然後在賬戶設定–>API安全–>金鑰設定中設定API金鑰key(32位的字串)

更多的申請幫助,請參考:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317780&token=6c7b59a75b08969c15fa41141ee8d88c236f01ab&lang=zh_CN

注:關於微信支付申請,請在開放平臺申請(同公眾號支付申請流程,公眾號支付不在本文討論範圍之內)微信的工作人員稽核通過後,會發稽核通過的郵件。裡面包含微信支付商戶號mch_id(一串10位數字)和APPID等資訊。然後根據郵件提示或者直接在微信商戶平臺中–>賬戶設定–>API安全–>金鑰設定中下載api證書並設定API金鑰key(32位的字串)

在微信開放平臺建立應用成功後,APP支付也申請通過了。請提供給開發同學以下東西:

  • APPID appid(以wx開頭的一串數字)
  • 商戶id mch_id(一串10位數字)
  • API金鑰 key
     (32位的字串)
    對於Android應用,還需要保證應用包名應用簽名正確

只要上面的資訊正確無誤,下面就交給開發同學了。如果是直接使用微信支付sdk的同學,請準備好踩坑吧。

支付SDK和demo

微信支付SDK

SDK從4.0.2開始,已改為使用gradle方式。

程式碼如下:

dependencies {
   compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
  • 1
  • 2
  • 3

以前是直接使用libammsdk.jar,相比起來,算是進步了。

demo

我建議直接忽略官方的demo,不信我的可以直接去Android資源下載下載相關demo

以下是我踩過的坑:

  • 支付Demo在另外一個單獨的project中,儘管sdk demo與pay demo非常類似,搞不懂為啥不合為一個demo project。我昨天在微信網站下載的支付demo,今天在微信網站上就找不著了。昨天下載的支付demo,看版本是v3(sdk demo已經是5.0.2了),還是eclipse工程,不過匯入到eclipse之後,編譯不通過。sdk中原來的com.tencent.mm.sdk包換成了com.tencent.mm.opensdk,demo,導致src程式碼一片紅。
  • 支付Demo中的服務端下單介面地址(http://wxpay.weixin.qq.com/pub_v2/app/app_pay.php?plat=android) 不能訪問,就算修復了編譯錯誤,結果仍然執行不了。
    然後我看了下其它的文件,sdk換了,但是相關的文件並沒有更新,尤其是混淆配置,直接影響到聯調,這也是一個隱藏的坑。

統一下單

統一下單是商戶系統(客戶端或服務端)向微信支付後臺傳送請求,以拿到預支付交易會話標識(prepayid)等資訊。關於介面的請求與響應資訊,請參考統一下單介面描述。文件寫得還是蠻全的,不過,實際上如何,我就只能呵呵了。請看下面

  • 介面的請求與響應都是xml格式,xml的解析不太方便,微信僅支援這個格式。算是小坑。
  • 介面文件中定義了一個NOT_UTF8的錯誤碼,似乎微信要求的請求編碼為utf-8,不過使用官方demo的Util.httpPost時,如果引數中有中文,還需將請求的xml轉為ISO-8859-1編碼的字串才行。不然,微信直接返回給你一個空字串,保證讓你找不著北。
  • 介面文件中定義了一個LACK_PARAMS的錯誤碼,指的是如果必填的引數為空,會返回此錯誤碼,可事實上呢,有一次我把total_fee引數搞混了,寫成了支付寶的引數名,結果介面返回一個空串,結果讓我一頓好找。
  • Android從6.0開始,刪除了apache的http元件,於是乎我把Util中的apache http元件換成了HttpUrlConnection,關於請求頭,解析都和原來保持一致,結果返回個簽名錯誤。要不是我換http元件之前可以成功下單,我幾乎就信了它,真的去查簽名了(事實上根本不是簽名錯誤好吧)。後來我修改http的請求頭,嘗試各種請求方式與編碼,結果還是返回簽名錯誤,這讓我很是折騰,一度曾想改回apache的http client。最後,我把請求的xml轉為utf-8編碼,然後HttpUrlConnection全都預設設定,這才下單成功。(PS,這裡順帶講一個HttpUrlConnection的坑,設定請求方法為post,可是debug中看到的HttpUrlConnection物件,請求方法仍為get,當初還以為是這個導致的,後來才知道HttpUrlConnection真正的實現是在delegate中)

總結一下,統一下單的響應、錯誤碼和錯誤描述比較混亂,成功響應還好,一旦出現問題,準讓你摸不清東南西北。

簽名這塊,請參考簽名演算法,注意按字母升序組織請求引數。我用的是有序的TreeMap儲存引數並做簽名。沒掉坑裡。這裡附上微信提供的簽名檢驗工具

呼叫支付介面

這一步是客戶端拿到prepayid之後,向微信傳送PayReq。請求引數有7個(詳細參考微信sdk中的PayReq類,其中partnerId填商戶IDmch_idpackageValue固定填Sign=WXPaytimestamp為北京時間戳,單位為秒。其它的顧名思義,對號入座即可),缺一不可。這裡呢,也有坑

  • PayResp(響應)定義了errCode和errStr,只要支付失敗,errCode都為-1,errStr永遠為null,所以,具體的錯誤原因:簽名錯誤、未註冊APPID、專案設定APPID不正確、註冊的APPID與設定的不匹配、其他異常等,一個一個查吧。
  • 統一下單介面不返回timestamp,如果是我們自己設定一個,那麼需要對請求重新簽名,統一下單返回的sign就不能用了。不然呼叫支付介面肯定返回-1。
  • 如果介面呼叫失敗,返回-1,是應用簽名(apk的簽名)不正確(比如debug和release版本使用的不同簽名)導致的,那麼需要清空微信快取,才能支付成功。不過,我可不敢隨便清微信快取,我建議可以將手機重啟,如果仍然不行,再清微信的快取。

libammsdk.jar衝突

因微信支付和分享是在同一個jar中,所以如果使用了第三方的分享sdk,極有可能會出現jar衝突。即使是相同模組中的jar完全一致也不行。解決的辦法是隻保留一個模組中的libammsdk.jar然後clen build。

建議

  • apk的包名,簽名在工程初始化的時候就確定好,debug和release使用同一個簽名。
  • 微信開放平臺在建立應用前,確認好apk包名和簽名。
  • 如果是服務端下單,確保sign值是對的(需要再次簽名的再籤一次)。客戶端拿到七個引數後,可以直接支付。
  • 建議在服務端下單,這樣更安全。
  • 使用第三方的支付sdk,避免入坑。

af-pay

最後推薦一個Android上的支付sdk:af-pay,github地址為:https://github.com/Jamling/af-pay
af-pay是一個Android平臺上支付庫,支援支付寶和微信,使用af-pay可以讓支付變得更簡單,並且支援服務端與服務單下單。示例程式碼如下:

程式碼如下:

private void doWxpay(String orderInfo) {
    final Activity activity = this;
    // 獲取支付類
    Wxpay wxpay = Wxpay.getInstance(activity);
    // 設定支付回撥監聽
    wxpay.setPayListener(new Wxpay.PayListener() {
        @Override
        public void onPaySuccess(BaseResp resp) {
            showToast(activity, "支付成功");
        }

        @Override
        public void onPayCanceled(BaseResp resp) {
            showToast(activity, "支付取消");
        }

        @Override
        public void onPayFailure(BaseResp resp) {
            showToast(activity, "支付失敗");
        }
    });
    // 這裡是服務端下單,內容是統一下單返回的xml
    if (!TextUtils.isEmpty(orderInfo)) {
        PayReq req = OrderInfoUtil.getPayReq(orderInfo);
        wxpay.pay(req);
    }
    else { // 客戶端下單
        Wxpay.DEBUG = true; // 開啟日誌
        // API金鑰,在微信商戶平臺設定
        Wxpay.Config.api_key = "32位的字串";
        // APPID,在微信開放平臺建立應用後生成
        Wxpay.Config.app_id = "wx...";
        // 商戶ID,註冊商戶平臺後生成
        Wxpay.Config.mch_id = "14...";
        // 支付結果非同步通知介面,由後臺開發提供
        Wxpay.Config.notify_url = "http://www.ieclipse.cn/app/pay/wxpay_notify.do";
        // 建立統一下單非同步任務
        Wxpay.DefaultOrderTask task = new Wxpay.DefaultOrderTask(wxpay);
        // 這個商戶訂單號,由後臺返回,在這裡隨便生成一個
        String outTradeNo = OrderInfoUtil2_0.genOutTradeNo();
        // 設定統一下單的請求引數
        task.setParams(OrderInfoUtil.buildOrderParamMap(outTradeNo, "測試支付", "", "1", null, null, null));
        task.execute();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

附上完整的支付過程中的日誌
完整日誌

如果是服務端下單,客戶端只需生成PayReq物件,然後呼叫wxpay.pay(PayReq)即可。af-pay已經配置好回撥的WXPayActivity和Receiver。詳細資訊請訪問Github。

關於

QuickAF是一個Android平臺上的app快速開發框架,歡迎讀者在github star或fork。本人寫作水平有限,歡迎廣大讀者指正,如有問題,可與我直接聯絡或在我的官方部落格中給出評論。

參考

QuickAF: https://github.com/Jamling/QuickAF
微信支付開發文件:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1