版權聲明:本文來自 Crocutax 的博客 , 轉載請注明出處 http://crocutax.com
之前做微信支付的時候,直接是以庫形式引入項目的,雖然一直覺得微信支付的開發(fā)文檔不太理想,但是印象中也沒有遇到什么大坑。
今天項目組的一個小伙伴突然告訴我微信支付一直失敗,根本調不起來支付頁面,onResp()
中的返回碼一直是-1,而且他好像已經(jīng)搞了好幾個小時了。于是我pull了一下項目代碼開始排查問題。最終發(fā)現(xiàn)問題有兩個:
- 微信開放平臺上傳的簽名問題
- Manifest文件中
WXPayEntryActivity
的配置問題。
由于在處理這兩個問題的過程中,順便又回顧了一遍微信支付的流程,以前也沒有真正記錄下來過,所以這里記錄一下。
這里先把微信支付的返回碼貼一下,看看他們是有多么敷衍:
- 0 成功,展示成功頁面
- -1 錯誤,可能的原因:簽名錯誤、未注冊APPID、項目設置APPID不正確、注冊的APPID與設置的不匹配、其他異常等。
- -2 用戶取消,無需處理。發(fā)生場景:用戶不支付了,點擊取消,返回APP。
相信絕大部分同學在處理微信支付這塊的時候,遇到的都是返回-1,然而看看關于-1的解釋,完全不給力。尤其是一個其他異常,真省事。。。 其實除了appId和簽名以外,還有很多其他原因會導致返回-1,這些都被微信劃為了其他異常而一筆帶過。
整體集成流程可以查看 微信支付App端開發(fā)步驟 ,反正他們不會告訴你有坑的,還是要自己踩-_-!
獲取AppID
在 微信開放平臺 申請開發(fā)應用,獲取APP的唯一標識APPID,比較簡單。之后通過 管理中心-應用詳情 即可查看自己的AppID。
向開放平臺提交包名和簽名
自己應用的包名就不用說了,比如com.weixin.test
。
簽名的話,要用到 簽名工具 ,是微信自己做的一個app,只有17KB,安裝在手機上,輸入已經(jīng)安裝在手機上的自己應用的包名,即可獲取該包名對應的簽名數(shù)據(jù)。
吐槽一下:簽名工具做的也太丑陋 + 不友好了,誰用誰知道。
注意
這里獲取簽名的時候,務必使用release版安裝在手機上,然后去獲取簽名,因為真正最終上線運營的時候是release版。debug和release兩個jks不一致,會導致最終簽名不一致,這種不一致會導致微信支付調用失敗。
如果嫌release簽名調試不方便,有兩種方法:
- 先向開放平臺提交debug版簽名,等待開發(fā)調試完畢,再換成release簽名
- 向開放平臺提交release版簽名,在gradle中配置一下,debug版本build時候也使用release的jks即可。
今天項目中遇到的其中一個坑就是在這里,簽名錯誤,完全跟技術無關。這種問題,再debug 3天也發(fā)現(xiàn)不了。
調用微信支付
微信支付的邏輯,大部分是有服務器端完成的,客戶端只需要在3個節(jié)點上進行處理:
1.通知服務器向微信下單
這個服務器就是自己的后臺,我們給后臺傳遞必要的參數(shù),比如商品id,價格等,由后臺向微信服務器下訂單,下單成功后,后臺會將訂單信息如prepayId等回調給我們。這里就是一個Android客戶端-后臺服務器 Request和Response的過程。
2.在服務器回調中調用微信支付
在后臺向微信服務器下單成功后,會將微信支付中需要使用到的一些字段數(shù)據(jù)回傳給我們,我們拿著該字段去調起微信支付即可。
//首先在調用之前,需要先在代碼中進行微信API注冊
IWXAPI wxApi= WXAPIFactory.createWXAPI(context, null);
// 將該app注冊到微信
wxApi.registerApp("your AppID");
//創(chuàng)建一個支付請求對象
PayReq request = new PayReq();
//開始數(shù)據(jù)封裝,這里一共有7個字段,都是必傳的
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "7FFECB600D7157C5AA49810D2D8F28BC2811827B";
//發(fā)起請求
api.sendReq(req);
這里對幾個字段進行特殊說明:
- appId : 直接定義為常量即可
- packageValue :直接使用
"Sign=WXPay"
,這是一個固定的值。 - sign:如果服務器端已經(jīng)做過了簽名生成,那么這里直接拿著賦值給
PayReq
對象即可;如果服務器端沒有做,那么還需要在本地進行簽名生成之后,再賦值。
這個本地簽名生成其實就是將上面的除了sign
以外的6個字段,拼接成key-value形式的字符串,再進行MD5加密,代碼如下:
//開始將6個字段進行數(shù)據(jù)封裝
List<WXModel> list = new LinkedList<>();
list.add(new WXModel("appid", payReq.appId));
list.add(new WXModel("noncestr", payReq.nonceStr));
list.add(new WXModel("package", payReq.packageValue));
list.add(new WXModel("partnerid", payReq.partnerId));
list.add(new WXModel("prepayid", payReq.prepayId));
list.add(new WXModel("timestamp", payReq.timeStamp));
payReq.sign = genAppSign(list);
//...發(fā)起請求即可
/**
* 生成簽名
*/
private String genAppSign(List<WXModel> list) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
sb.append(list.get(i).key);
sb.append('=');
sb.append(list.get(i).value);
sb.append('&');
}
sb.append("key=");
sb.append(Constant.WX_APP_KEY);
String appSign = MD5Utils.getMessageDigest(sb.toString().getBytes()).toUpperCase();
return appSign;
}
注意
- 簽名工作一般由后臺完成,在生成簽名時,務必注意key的拼寫問題,比如必須拼appid而不是appId,必須拼prepayid而不是prepayId,這里跟上面
PayReq
對象中字段的命名方式不一樣,用錯了也是照樣返回-1。自己曾經(jīng)作為server端忽略了這一點,坑了移動端的同事一把,耽誤了好幾個小時-_-! - 簽名所使用的是 微信商戶平臺的API密鑰,而不是微信開放平臺 的 AppSecret。 API密鑰在商戶平臺后臺-->API安全-->先安裝操作證書,后設置密鑰。見下圖:
3.WXPayEntryActivity中接收回調
在該類的onResp()
方法中拿到微信支付的回調,然后去跟服務器再度確認支付結果。官方解釋如下:
在WXPayEntryActivity類中實現(xiàn)onResp函數(shù),支付完成后,微信APP會返回到商戶APP并回調onResp函數(shù),開發(fā)者需要在該函數(shù)中接收通知,判斷返回錯誤碼,如果支付成功則去后臺查詢支付結果再展示用戶實際支付結果。注意一定不能以客戶端返回作為用戶支付的結果,應以服務器端的接收的支付通知或查詢API返回的結果為準。
OK,到這里其實并不是就完事了。想正常接到支付成功的回調,除了上面一系列的AppId、應用簽名、微信支付請求等流程不能出錯外,這個類也得好好配置下才行,否則返回碼-1依然在等待著你。。。
類的位置
WXPayEntryActivity
類必須放在 包名.wxapi
下,比如com.weixin.test.wxapi.WXPayEntryActivity
,包名或類名不一致會造成無法回調。
Manifest文件中的聲明
WXPayEntryActivity
不是一個普通的類,而是要繼承Activity的一個View界面,所以必須在Manifest文件中聲明。那么這里坑又來了,如果僅僅只是在Manifest中聲明一下,在測試的時候會發(fā)現(xiàn),依然是返回-1。
需要這么配置才可以:
<activity
android:name=".wxapi.WXPayEntryActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="your AppId"/>
</intent-filter>
</activity>
清空緩存
最后再說一個坑,如果經(jīng)過一系列的配置,發(fā)現(xiàn)完全都配置好了,可是微信支付依然返回-1。而此時我們可能又會回頭排查各個節(jié)點的問題,是配置問題?是自己的數(shù)據(jù)傳遞問題?到底哪里的bug?
其實這時候,清空下自己的應用緩存就可以了,通過 設置-應用管理-your app-清空緩存,進行緩存清理過后,立竿見影拿到成功的回調!