1. 程式人生 > >微信支付失敗-1徹底掃坑

微信支付失敗-1徹底掃坑

http://bbs.csdn.net/topics/391865275

由於伺服器返回的sign 不知道對應的timestamp,所以不能用它的,用本地自己的timestamp等引數生成sign,然後拿著這些引數請求支付介面,就可以了。

另外,注意要是簽名包

private void genPayReq(WeChatPayModel weChatPayModel) {

        req.appId = weChatPayModel.getAppid();
        req.partnerId = weChatPayModel.getMchId();
        req.prepayId = weChatPayModel.getPrepayId();
        req.packageValue = "Sign=WXPay";
        req.nonceStr = weChatPayModel.getNonceStr();
        req.timeStamp = String.valueOf(genTimeStamp());
        List<NameValuePair> signParams = new LinkedList<NameValuePair>();
        signParams.add(new BasicNameValuePair("appid", req.appId));
        signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
        signParams.add(new BasicNameValuePair("package", req.packageValue));
        signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
        signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
        signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));

        req.sign = genAppSign(signParams);
    }

    private long genTimeStamp() {
        return System.currentTimeMillis() / 1000;
    }

    private String genAppSign(List<NameValuePair> params) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < params.size(); i++) {
            sb.append(params.get(i).getName());
            sb.append('=');
            sb.append(params.get(i).getValue());
            sb.append('&');
        }
        sb.append("key=");
        sb.append(PayConfig.WECHAT_API_KEY);

        String appSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
        Log.e("orion", appSign);
        return appSign;
    }

http://www.eoeandroid.com/thread-918154-1-1.html?_dsign=88b40678

由於公司運營需要,Android客戶端要增加微信支付。在看了幾遍官方文件之後,加上之前有整合微信分享的經驗,所以很快就把呼叫微信支付的程式碼寫好了,待微信支付相關介面完成後聯調時,才發現山高路遠坑深啊!從下午2點半開始除錯,一直折騰到快6點,那個微信支付介面才“千呼萬喚始出來”,更坑爹的是,壓根兒就不是我客戶端的問題,而是後臺介面那邊sign生成時出了問題。在解決問題的過程中,看到網上太多關於微信支付各種問題的帖子,但遺憾的是並沒有找到真正有效的解決方案,所以就來徹底掃一下Android整合微信支付中的坑。

首先講一下我們的邏輯,如微信支付開發文件(https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=8_5)中Android部分描述的那樣,由伺服器端請求微信支付平臺生成prepayid,大家一般也都是這麼做的,發現網上有一部分人從伺服器端拿到prepayid後,在客戶端自己拼字串引數,然後呼叫演算法生成sign,這樣是可以,但是安全性不好,而且客戶端邏輯也變複雜了,估計大家是按照官方demo寫的,至於其demo暫時就不評價了,下面會提及。我們的做法是所有的必要引數,如partnerId、prapayId、packageValue、nonceStr、timeStamp、sign等都是由伺服器端生成,至於appId自己寫在客戶端也行,伺服器端傳過來也行,因為之前微信分享appId是寫在客戶端了,因此微信支付就沒讓伺服器端返回appId這個引數。其實微信支付官方文件也是這樣建議的,原文為“商戶伺服器生成支付訂單,先呼叫統一下單API(詳見第7節)生成預付單,獲取到prepay_id後將引數再次簽名傳輸給APP發起支付”。App端拿到上述6個主要引數後,加上appId,一共7個,就可以調起支付了。

如上所述,客戶端的邏輯就這麼簡單,所以當除錯時竟然調不出支付介面,真覺得不可思議。我遇到的問題是這樣的:當發起支付時調不出微信支付介面,直接響應WXPayEntryActivity中的onResp回撥,並且errCode始終返回-1。如果微信未登入,則會調起登陸介面,登陸完成後還是調不起來,errCode依然返回-1。

我們客戶端的實現邏輯基本跟官方文件一致(注意官方文件有個書寫錯誤,在呼叫支付部分程式碼最後一行的引數中,request寫成了req,後面也會提到),主要核心程式碼如下:

1.首先註冊,其中api為IWXAPI的例項

  1. api = WXAPIFactory.createWXAPI(context, APP_ID, false);  
  2. api.registerApp(APP_ID);  
2.從服務端拿到上述必要引數後,調支付即可,其中params是自定義的用來儲存從服務端獲取的所有的物件
  1. if (api != null) {  
  2.     if (isWXAppInstalled()) {  
  3.         PayReq req = new PayReq();  
  4.         req.appId = APP_ID;  
  5.         req.partnerId = params.getPartnerId();  
  6.         req.prepayId = params.getPrepayId();  
  7.         req.packageValue = params.getPackageValue();  
  8.         req.nonceStr = params.getNonceStr();  
  9.         req.timeStamp = params.getTimeStamp();  
  10.         req.sign = params.getSign();  
  11.         api.sendReq(req);  
  12.     }  
  13. }  
3.WXPayEntryActivity這個回撥介面實際上不會影響前面的調起支付的邏輯,寫過微信分享的應該知道,這個Activity一定要放到“App包名.wxapi”的package中,否則無法響應回撥,當然別忘了在AndroidManifest.xml中註冊。微信分享的回撥WXEntryActivity也是這樣的,放在同一個包即可。沒錯,微信就是這麼霸道。

Android客戶端的核心邏輯就是這些,下面來一一列舉微信支付中的坑,或者叫注意點吧,有些是我知道因此沒有親自踩上去的也一併列出。

1.首先如果要使用微信支付的話,必須先到微信開放平臺註冊應用,具體地址為https://open.weixin.qq.com/,註冊時需要填應用的包名和簽名,注意這裡的簽名是App正式版的簽名,可以找一個已上線的包或打一個正式包,使用微信提供的工具(簽名工具下載地址為https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apk)來獲取,獲取後填上即可。待稽核通過後,會得到一個AppID和AppSecret,AppID分享和支付都要用到,AppSecret沒什麼實際用途,此時微信分享能力是直接擁有的,支付能力還要額外申請,其中涉及到財務資訊等,最好讓公司財務部門去申請,申請成功後會拿到一個商戶id,後面生成sign時會用到。只有所有稽核都通過後,才可呼叫微信支付功能,這點是前提。

2.微信分享和微信支付SDK是同一個架包,名為libammsdk.jar

3.官方開發文件中有一處錯誤,需要注意下,如下圖最後一行引數req應該為request,照搬程式碼的估計IDE也不會放過你,哈哈。


4.測試微信支付時,務必對自己的App做正式簽名,因為一開始就在微信平臺註冊過簽名信息,微信SDK會做校驗,只有這樣才能調起微信分享和微信支付,直接debug版的包則絕對調不起來,這點務必注意,很多人是跌在這裡了!當初做微信分享曾遇到過,所以會很留心,也因為如此,如果微信分享能調起來,微信支付不行,那就不要懷疑簽名問題了

5.還是簽名,網上有人說要注意大小寫,這點其實是不必的。在微信開放平臺看到稽核通過的App的簽名是大寫的,而用微信簽名獲取工具獲得的則顯示小寫,這個沒關係,不要貿然改動平臺註冊資訊,不然又可能導致漫長的稽核等待,上面也說了,微信分享如可以,那就不是簽名問題。

6.來說下官方demo,這東西害人不淺啊!很多人蔘考其寫法,如生成sign放在客戶端啊,調支付的Activity新增intent-filter啊,最主要的還是簽名問題。其實客戶端邏輯很簡單,直接上手整合即可,demo看看邏輯就行,照抄小心掉坑裡。

7.網上有人說需要給呼叫支付的Activity配置如下intent-filter(見下圖),可能也是被demo誤導了

  1. <intent-filter>
  2.     <actionandroid:name="android.intent.action.VIEW"/>
  3.     <categoryandroid:name="android.intent.category.DEFAULT"/>
  4.     <dataandroid:scheme="appid"/>
  5. </intent-filter>

邏輯上來看,根本不會跳這個介面啊,所以當然是非必需的

8.對於errCode返回-1,有人說清除微信快取或切換賬戶就好了,這種解決方案治標不治本啊,根本不能算解決方案。雖然我沒遇到能用這方法解決的問題,但目測是簽名的問題,建議還得找到真正的問題所在。

9.生成sign時特別需要注意,首先將key-value鍵值對拼成字串,注意key都要小寫,如appid,noncestr,package,partnerid,prepayid,timestamp,key,並且名字得按上述名稱,我們遇到的錯誤就是因為partnerid寫成了partnerId,prepayid寫成了PrepayId,當然我們是在服務端寫的,如果在客戶端生成sign的話,也需要注意大小寫及名稱,詳細資訊請參考官方文件。還有這裡的key並非AppID或AppSectet,而是在商戶平臺設定的,官方描述為“key設定路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設定-->API安全-->金鑰設定”。對於noncestr,申請prepayid和生成sign時兩次需要用到,由於iOS同事看到相關文章說noncestr前後需要一致,因此這個隨機字串我們是設定成一樣的了,這樣做Android平臺也是OK的,不過個人感覺這裡可以不一致,由於這個邏輯在伺服器端,我並沒有驗證,方便的同學可以驗證下。

10.req.packageValue=”Sign=WXPay”,一般都是這樣寫死這個引數值。也有人說寫成req.packageValue=”prepay_id=” + prepayid,經測試Android兩種寫法都是可以調起微信支付的,至少最新版本SDK是可以的,以後則不清楚,官方也建議寫Sign=WXPay,據說iOS只支援這種寫法。

11.對於IWXAPI例項的建立,官方程式碼為: IWXAPI api = WXAPIFactory.createWXAPI(context, null);這樣寫就可以,如果呼叫另一個工廠方法:IWXAPI api = WXAPIFactory.createWXAPI(context, APP_ID, false);也是OK的,我都測試過,總之這裡不是問題的根源。

不得不再次吐槽一下Android微信支付,支付寶之類的支付整合是很簡單的,微信支付卻花了幾個小時才搞定,上面羅列了一系列注意事項,都是前人踩過的坑,希望大家看到這篇文章後,可以用20分鐘搞定微信支付,如果還有問題,歡迎回復探討。