一般情況下腿准,要實(shí)現(xiàn)應(yīng)用內(nèi)支付接入 App 支付 SDK 即可滿足業(yè)務(wù)需求,不過(guò)考慮到對(duì)于一些類(lèi)似游戲中心的場(chǎng)景拾碌,更多是需要支持
H5 支付吐葱。相對(duì)微信來(lái)說(shuō),支付寶的對(duì)接簡(jiǎn)單完善很多校翔,所以本篇文章主要說(shuō)說(shuō)接入微信 H5 支付的流程和一些問(wèn)題弟跑。
申請(qǐng)流程
按照微信支付官網(wǎng) H5 支付說(shuō)明 ,目前是沒(méi)有直接申請(qǐng)接入 H5 支付的入口防症,只能在微信商戶平臺(tái)中去另行開(kāi)通孟辑。然鵝~微信商戶平臺(tái)賬戶也不支持直接注冊(cè)申請(qǐng),只能先注冊(cè)微信開(kāi)放平臺(tái)后接入微信 App 支付后才會(huì)有商戶賬號(hào)分配蔫敲。
1. 注冊(cè)微信開(kāi)放平臺(tái)賬戶
2. 申請(qǐng)開(kāi)放平臺(tái)開(kāi)發(fā)者認(rèn)證
3. 創(chuàng)建一個(gè)應(yīng)用提交申核
需要應(yīng)用相關(guān)資質(zhì)饲嗽,主要是為了開(kāi)通支付功能。
4. 為應(yīng)用申請(qǐng)微信 App 支付奈嘿,開(kāi)通微信支付功能
開(kāi)通成功后會(huì)自動(dòng)分配微信商戶平臺(tái)賬戶
5. 登錄商戶平臺(tái)申請(qǐng)開(kāi)通 H5 支付
這里只是簡(jiǎn)單介紹下申請(qǐng)流程的主要環(huán)節(jié)貌虾,具體操作起來(lái)有多麻煩我也不想去體會(huì)。
應(yīng)用內(nèi)接入
說(shuō)到這里可能有些人想笑了裙犹,既然叫 H5 支付那不是應(yīng)該跟應(yīng)用本身沒(méi)多大關(guān)系才對(duì)酝惧,不就是一個(gè)支付鏈接跳轉(zhuǎn)而已嗎。
話是這么說(shuō)沒(méi)錯(cuò)伯诬,但是具體操作起來(lái)還是有些坑需要去踩。由于微信 H5 支付本身就是瀏覽器網(wǎng)頁(yè)支付場(chǎng)景下的產(chǎn)物巫财,所以微信官方并不推薦在應(yīng)用中使用 H5 支付盗似。
跟瀏覽器不一樣,在 WebView 中我們還需要自己處理一些問(wèn)題平项。比如為了實(shí)現(xiàn)調(diào)起微信支付赫舒,需要對(duì)支付鏈接進(jìn)行攔截后才能進(jìn)行處理悍及,下面就來(lái)看看這個(gè)流程。
WebViewClient webViewClient = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 判斷 url 的 scheme 進(jìn)行相應(yīng)的處理
if (url.startsWith("weixin://")){
try{
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}catch (Exception e) {
//防止手機(jī)沒(méi)有安裝處理某個(gè) scheme 開(kāi)頭的 url 的APP導(dǎo)致crash
AlertDialog.Builder builder;
builder = new AlertDialog.Builder(mActivity);
builder.setTitle("支付中心").setMessage("該手機(jī)沒(méi)有安裝微信客戶端接癌,請(qǐng)安裝微信后重新完成支付心赶,或換用支付寶進(jìn)行支付").setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
}).create().show();
return true;
}
}else if (url.startsWith("alipays://") || url.startsWith("alipay")){
try{
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}catch (Exception e) {
// 啟動(dòng)支付寶失敗,換成網(wǎng)頁(yè)支付
return true;
}
}
if (!(url.startsWith("http") || url.startsWith("https"))) {
return true;
}
view.loadUrl(url, map);
return true;
}
}
商家參數(shù)格式有誤缺猛,請(qǐng)聯(lián)系商家解決
你以為這樣就完了是吧缨叫,然鵝并沒(méi)有,實(shí)際開(kāi)發(fā)中很大機(jī)率會(huì)出現(xiàn)微信提示商家參數(shù)格式有誤荔燎,請(qǐng)聯(lián)系商家解決的問(wèn)題耻姥,而且更尷尬的是:這個(gè)問(wèn)題并不會(huì) iOS 中出現(xiàn),也不會(huì) Android 瀏覽器中出現(xiàn)有咨,偏偏就是在應(yīng)用的 WebView 中出現(xiàn)了琐簇。
查看官方文檔出錯(cuò)問(wèn)題介紹,說(shuō)是當(dāng)前調(diào)起 H5支付的 referer 為空導(dǎo)致座享,WTF? 難道 Android WebView 打開(kāi)一個(gè)鏈接的 referer 不知指向當(dāng)前頁(yè)面的域名婉商?都說(shuō)實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),抓包看看好像還真的是渣叛,很好丈秩,再一次感覺(jué)到了 Android 系統(tǒng)咖喱味代碼。
沒(méi)辦法诗箍,這鍋也不能甩給微信癣籽,只能按照文檔說(shuō)的解決方法自己來(lái)背。然而這文檔說(shuō)的也是不明不白的滤祖,只是說(shuō)域名設(shè)置要一致筷狼,廢話不多說(shuō),直接動(dòng)手更簡(jiǎn)單匠童,下面給出示例代碼(已自行檢驗(yàn)過(guò)埂材,真實(shí)可用的)
直接在原有的代碼基礎(chǔ)上進(jìn)行更改
WebViewClient webViewClient = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
...
if (!(url.startsWith("http") || url.startsWith("https"))) {
return true;
}
// 比如我們申請(qǐng)時(shí)填寫(xiě)的是經(jīng)常用來(lái)測(cè)試網(wǎng)絡(luò)連通性的 http://www.baidu.com
HashMap<String, String> map = new HashMap<String, String>();
// 指定申請(qǐng)微信 H5 支付時(shí)填寫(xiě)的域名,
map.put("Referer", "http://www.baidu.com");
view.loadUrl(url, map);
return true;
}
}
But... 測(cè)試過(guò)程中發(fā)現(xiàn)一個(gè)兼容性問(wèn)題:在4.4.4汤求、4.4.3的設(shè)備上俏险,下單時(shí)問(wèn)題還是復(fù)現(xiàn)了,通過(guò)抓包發(fā)現(xiàn)設(shè)置的 Referer 并沒(méi)有生效
最終參考這篇文章中提到方法成功的解決了問(wèn)題
if (("4.4.3".equals(android.os.Build.VERSION.RELEASE))
|| ("4.4.4".equals(android.os.Build.VERSION.RELEASE))) {
//兼容這兩個(gè)版本設(shè)置referer無(wú)效的問(wèn)題
view.loadDataWithBaseURL("商戶申請(qǐng)H5時(shí)提交的授權(quán)域名",
"<script>window.location.href=\"" + targetUrl + "\";</script>",
"text/html", "utf-8", null);
} else {
Map<String, String> extraHeaders = new HashMap<>();
extraHeaders.put("Referer", "商戶申請(qǐng)H5時(shí)提交的授權(quán)域名");
view.loadUrl(targetUrl, extraHeaders);
}
有朋友反映說(shuō)用了上面這個(gè)方法后扬绪,頁(yè)面處于循環(huán)加載的狀態(tài)竖独,這其實(shí)是跟 shouldOverrideUrlLoading 的使用方式有關(guān),有興趣的同學(xué)可以參考這篇文章:關(guān)于shouldOverrideUrlLoading方法的一些考證
關(guān)于 shouldOverrideUrlLoading 方法返回值的說(shuō)明:
- 若沒(méi)有設(shè)置 WebViewClient 則由系統(tǒng)(Activity Manager)處理該 url挤牛,通常是使用瀏覽器打開(kāi)或彈出瀏覽器選擇對(duì)話框莹痢。
- 若設(shè)置 WebViewClient 且該方法返回 true ,則說(shuō)明由應(yīng)用的代碼處理該 url,WebView 不處理竞膳,也就是程序員自己做處理航瞭。
- 若設(shè)置 WebViewClient 且該方法返回 false,則說(shuō)明由 WebView 處理該 url坦辟,即用 WebView 加載該 url刊侯。
下面給出最終修改完成以后的完整代碼:
完整解決方案
// 設(shè)置微信 H5 支付調(diào)用 loadDataWithBaseURL 的標(biāo)記位,避免循環(huán)調(diào)用锉走,
// 再次進(jìn)入微信 H5 支付流程時(shí)記得重置此標(biāo)記位狀態(tài)
boolean firstVisitWXH5PayUrl = true;
WebViewClient webViewClient = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("weixin://")) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
} catch (Exception e) {
// 防止手機(jī)沒(méi)有安裝處理某個(gè) scheme 開(kāi)頭的 url 的 APP 導(dǎo)致 crash
showToast("該手機(jī)沒(méi)有安裝微信");
return true;
}
} else if (url.startsWith("alipays://") || url.startsWith("alipay")) {
try{
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
} catch (Exception e) {
// 防止手機(jī)沒(méi)有安裝處理某個(gè) scheme 開(kāi)頭的 url 的 APP 導(dǎo)致 crash
// 啟動(dòng)支付寶 App 失敗滨彻,會(huì)自行跳轉(zhuǎn)支付寶網(wǎng)頁(yè)支付
return true;
}
}
// 處理普通 http 請(qǐng)求跳轉(zhuǎn)
if (!(url.startsWith("http") || url.startsWith("https"))) {
return true;
}
// 處理微信 H5 支付跳轉(zhuǎn)時(shí)驗(yàn)證請(qǐng)求頭 referer 失效
// 驗(yàn)證不通過(guò)會(huì)出現(xiàn)“商家參數(shù)格式有誤,請(qǐng)聯(lián)系商家解決”
if (url.contains("wx.tenpay.com")){
// 申請(qǐng)微信 H5 支付時(shí)填寫(xiě)的域名
// 比如經(jīng)常用來(lái)測(cè)試網(wǎng)絡(luò)連通性的 http://www.baidu.com
String referer = GameConfig.WX_H5_PAY_HOST;
// 兼容 Android 4.4.3 和 4.4.4 兩個(gè)系統(tǒng)版本設(shè)置 referer 無(wú)效的問(wèn)題
if (("4.4.3".equals(android.os.Build.VERSION.RELEASE))
|| ("4.4.4".equals(android.os.Build.VERSION.RELEASE))) {
if (firstVisitWXH5PayUrl){
view.loadDataWithBaseURL(referer, "<script>window.location.href=\"" + url + "\";</script>",
"text/html", "utf-8", null);
// 修改標(biāo)記位狀態(tài)挠日,避免循環(huán)調(diào)用
// 再次進(jìn)入微信H5支付流程時(shí)記得重置狀態(tài) firstVisitWXH5PayUrl = true
firstVisitWXH5PayUrl = false;
}
// 返回 false 由系統(tǒng) WebView 自己處理該 url
return false;
} else {
// HashMap 指定容量初始化疮绷,避免不必要的內(nèi)存消耗
HashMap<String, String> map = new HashMap<>(1);
map.put("Referer", referer);
view.loadUrl(url, map);
return true;
}
}
return false;
}
}