For wechat pay, there are usually many scenarios where the native APP may be connected to the wechat pay SDK, and the application scenario of wechat H5 payment is on WebView. We accessed a third party’s business, which was carried by WebView, and made H5 payment. As a result, we found some problems in the payment process, including that other apps of our company had accessed wechat H5 payment before, and the experience was not good.

Before the dishes

Just a few quick starters before we get to the question that we’ll use later

Wechat H5 Payment official Wiki

Pay.weixin.qq.com/wiki/doc/ap…

“Trend” of URL in wechat H5 Payment process

  1. Initiate wechat pay: wx.tenpay.com/cgi-bin/mmp…
  2. Redirection to: weixin://wap/pay? Prepayid = [government] & package = [government] & noncestr = [government] & sign = [government]
  3. Call up the wechat payment page through wechat scheme(Weixin)
  4. Wait for payment to complete/cancel payment /5 seconds timeout, jump back to redirect_URL

WebView history

When loaded on the original WebView, the WebView will save the page access record:

  • The obvious behavior on the browser is to support forward and backward
  • This is available on H5window.historyThis object records the page stack
  • It can be called from the Android WebViewWebBackForwardList history = webView.copyBackForwardList(), which also records the page stack

When the size of history >1, we should press the back button to return history first, when size==1, directly return to the page Activity

@Override
public void onBackPressed(a) {
    / / webView. CanGoBack () is equivalent to the webView. CanGoBackOrForward (1)
    // webview.goback () equals webview.gobackorForward (-1).
    if (webView.canGoBack()) {
        webView.goBack();
        return;
    }
    super.onBackPressed();
}
Copy the code

About the WebViewClient. ShouldOverrideUrlLoading

Rewrite the WebViewClient. ShouldOverrideUrlLoading (WebView WebView, String url), to intercept the url

@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
    if (...) {
        ...
        // true: custom processing
        return true;
    }
    if (...) {
        ...
        // false: submit to the browser
        return false;
    }
    // Return false by default
    return super.shouldOverrideUrlLoading(webView, url);     
}
Copy the code

ShouldOverrideUrlLoading there are two ways to handle webView loading urls from the original page

  • Active loadUrl

    webView.loadUrl(url); // This is the custom processing
    return true;
    Copy the code

    However, this method will lose the header of the request unless you add it manually

    Map<String, String> headers = new HashMap<>();
    headers.put("referer"."Authorized Domain name submitted by Merchant in application for H5"); . webView.loadUrl(url, headers);return true;
    Copy the code
  • Return false; Recommended this way, does not affect the request header parameters, is completely browser behavior, very stable! , we will use this method to perfectly send back the referer header required for wechat H5 payment

Problem a

Payment error: the format of the merchant parameter is wrong, please contact the merchant to solve the problem. The reason is that the Referer is lost, and the code implementation is as described above: the headers is lost because of active loadUrl

Correct opening mode

Ensure that the url calling wechat H5 payment is loaded on the original WebView, but we do not actively loadUrl

@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {...if (isWXH5Pay(url)) {
        try {
            Uri uri = Uri.parse(url);
            // redirect_url = redirect_url
            redirectUrl = uri.getQueryParameter("redirect_url");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false; }...return super.shouldOverrideUrlLoading(webView, url); 
}
Copy the code

An implementation of isWXH5Pay(URL) is attached

/** * Whether wechat h5 payment link */
public static boolean isWXH5Pay(String url) {
    if (TextUtils.isEmpty(url)) {
        return false;
    }
    return url.toLowerCase().startsWith("https://wx.tenpay.com");
}
Copy the code

Question 2

The product came and said that XXX APP (previously connected to wechat H5 payment) could be opened normally, but there was something wrong with YYY app. I had not even clicked the payment button, but the payment completion page popped up.

As a result, I checked the official document of wechat H5 payment, about redirect_URL:

After redirect_URL is set, the operation of hopping back to the specified page may occur as follows: 1. The user clicks “Cancel payment” or clicks “Finish” button after payment is completed. Therefore, there is no guarantee that when the page jumps back, the payment process has finished, so the redirect_URL address set by the merchant cannot automatically perform the order lookup operation, and users should click the button to trigger the order lookup operation.

This can explain why users will click the payment completion page if they do not click the payment, so it is normal to jump back to the payment completion page, but it is abnormal to cover the wechat payment page, because users cannot continue to pay unless their hands are fast enough to complete the payment within 5s.

It works normally on XXX app because it is loaded on the original WebView after receiving the redirect_URL request, that is:

@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {...if (isRedirectUrl(url)) {
        // The url is the redirect_URL returned by wechat
        webView.loadUrl(url);
        return true; }...return super.shouldOverrideUrlLoading(webView, url); 
}
Copy the code

Yyy app is not our URL blocking protocol default is a new WebView load URL, there is no special processing of redirect_URL load, Android here is a new Activity, so cover the wechat payment page above.

Correct opening mode

Make sure the redirect_URL is loaded on the original WebView and handed over to the browser

@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {...if (isRedirectUrl(url)) {
        return false; }...return super.shouldOverrideUrlLoading(webView, url); 
}
Copy the code

An implementation of isRedirectUrl(URL) is attached

/** * whether wechat h5 payment backhop url * {@link#redirectUrl} is loaded from <a < a href = "https://wx.tenpay.com/xxx?redirect_url=xxx" > https://wx.tenpay.com/xxx?redirect_url=xxx / > parameters of parsing out < br / > * So this is equals * *@param url
 * @return* /
public boolean isRedirectUrl(String url) {
    if (TextUtils.isEmpty(url)) {
        return false;
    }
    return url.equalsIgnoreCase(redirectUrl);
}

Copy the code

Question 3

Is XXX APP normal? I tried it, it seems normal, but I also found a problem, when the payment completion page is pressed back, it returned a blank page, which is not good. And then the wechat payment page will pop up again, which is serious. The feeling that gives user is very rascal!

The question is, how did this blank page come about?

In the “direction” of url in wechat H5 payment process **, when initiating wechat H5 payment, wx.tenpay.com/xxx redirects to Weix… scheme

@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {...if(! URLUtil.isNetworkUrl(url)) {// Special Scheme processing, call external application open
        try {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            webView.getContext().startActivity(intent);
        } catch (Exception e) {
            // No wechat app is installed when possible
            e.printStackTrace();
        }
        return true; }...return super.shouldOverrideUrlLoading(webView, url); 
}
Copy the code

If you have the wechat app installed, you will be able to jump to the wechat Pay page.

The wx.tenpay.com/xxx page is a blank page, but it has processing logic, for example, to jump back to redirect_URL. I originally tried to open weixin:// XXX with intent.action_view, while going through webView.goback (); The blank page is returned, but the redirect_URL cannot be received, so the blank page cannot be killed during the entire payment process

Correct opening mode

Override the Activity’s onBackPressed() method to skip the blank page during the rollback of the WebView’s history

@Override
public boolean onBackPressed(a) {
    // back history
    int index = -1; // -1: rollback the previous page of history
    String url;
    WebBackForwardList history = mWebView.copyBackForwardList();
    while (mWebView.canGoBackOrForward(index)) {
        url = history.getItemAtIndex(history.getCurrentIndex() + index).getUrl();
        if(URLUtil.isNetworkUrl(url) && ! WXH5PayHandler.isWXH5Pay(url)) { mWebView.goBackOrForward(index);return;
        }
        index--;
    }
   super.onBackPressed(); 
}
Copy the code

conclusion

Several problems we encountered in the process of accessing wechat H5 payment are listed, analyzed one by one, and solutions are given:

  1. Webview loading wechat H5 payment URL, to bring the referer;
  2. Payment completed/cancel payment /5 seconds timeout, jump back to redirect_URL, open this page in the original WebView page;
  3. Press the Back key to roll back Webview history and dispose of the blank page.

This is the Android WebView in the right way to open wechat H5 payment, mainly requires us to the beginning of the article mentioned a few “dishes” can be well digested, in order to taste wechat H5 payment in a more “correct” way this meal.

Attach a code

Encapsulated in the WXH5PayHandler class

/ * * * WeChat h5 payment processing class * * < p > < a href = "https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4" > WeChat h5 pay Wiki < a / > < br / > * /
public class WXH5PayHandler {

    public static final String REDIRECT_URL = "redirect_url";

    /** * the url to initiate the H5 payment */
    private String h5Url;
    /** * Invoke the scheme protocol URL of the wechat app payment page */
    private String launchUrl;
    /** * skip back to page url

* For example, you want users to skip to https://xxx

*

* After redirect_URL is set, the operation of backhop to the specified page may occur in: 1, wechat pay middle page after the wechat cashier is activated more than 5 seconds 2, the user clicks "Cancel payment" or click "Finish" button after payment is completed. Therefore, there is no guarantee that when the page jumps back, the payment process has finished, so the redirect_URL address set by the merchant cannot automatically perform the order lookup operation, and users should click the button to trigger the order lookup operation. * /
private String redirectUrl; / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- step 1: receive a link to the h5, and open the original WebView page -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / /** * Whether wechat h5 payment link **@param url * @return* / public static boolean isWXH5Pay(String url) { if (TextUtils.isEmpty(url)) { return false; } return url.toLowerCase().startsWith("https://wx.tenpay.com"); } /** * Solution 1: Recommended, return false, call {@linkAndroid. Its. WebViewClient# shouldOverrideUrlLoading (WebView, String)} the default * < p > * please call {before invoking@link#isWXH5Pay(String)} Check whether wechat H5 pay * *@param url * @return* / public boolean pay(String url) { h5Url = url; redirectUrl = getRedirectUrl(url); return false; } /** * Option 2: not recommended, call {@linkWebView#loadUrl(String)}, return true.<br/> *@paramUrl} header argument, such as the required referer, which calls {@linkWebView#loadUrl(String, Map)} * <p>@link#isWXH5Pay(String)} Check whether wechat H5 pay * *@param webView * @param url * @paramHeaders Custom header, which must contain the referer * required for wechat H5 payment@return* / public boolean pay(WebView webView, String url, Map<String, String> headers) { h5Url = url; redirectUrl = getRedirectUrl(url); webView.loadUrl(url, headers); return true; } private String getRedirectUrl(String url) { try { Uri uri = Uri.parse(url); return uri.getQueryParameter(REDIRECT_URL); } catch (Exception e) { return null; }}/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- step 2: to arouse the WeChat scheme links, and arouse the pay WeChat app page -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / /** * Whether to invoke wechat H5 payment page **@paramUrl The url starting with weixin scheme: weixin://wap/pay? xxx *@return* / public boolean isWXLaunchUrl(String url) { if (TextUtils.isEmpty(url)) { return false; } return url.toLowerCase().startsWith("weixin://"); } /** * call {@link#h5Url} will be redirected to the scheme URL of wechat to evoke the h5 payment page of wechat app@link#isWXLaunchUrl(String)} #isWXLaunchUrl(String)@param url * @return* / public boolean launchWX(WebView webView, String url) { launchUrl = url; try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); webView.getContext().startActivity(intent); return true; } catch (Exception e) { // If you catch it, open it inside return false; }}/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- step 3: wait WeChat rebound redirect_url, and open the original WebView page -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * / /** * whether wechat h5 pay backhop url<br/> * call {@link#pay(String)} from <a < a href = "https://wx.tenpay.com/xxx?redirect_url=xxx" > https://wx.tenpay.com/xxx?redirect_url=xxx / > parameters of parsing out < br / > * So this is equals * *@param url * @return* / public boolean isRedirectUrl(String url) { if (TextUtils.isEmpty(url)) { return false; } return url.equalsIgnoreCase(redirectUrl); } /** * jump back to the page url at {@linkAndroid. Its. WebViewClient# shouldOverrideUrlLoading (WebView, String) call * *}@see #redirectUrl */ public boolean redirect(a) { // The original page opens return false; }}Copy the code

Rewrite the WebViewClient. ShouldOverrideUrlLoading (WebView WebView, String url), call WXH5PayHandler

public class XWebViewClient extends WebViewClient {

    private WXH5PayHandler mWXH5PayHandler;

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (TextUtils.isEmpty(url)) {
            return true;
        }

        Uri uri = null;
        try {
            uri = Uri.parse(url);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (uri == null) {
            return true;
        }

        if(! URLUtil.isNetworkUrl(url)) {// Process wechat H5 payment 2
            if(mWXH5PayHandler ! =null && mWXH5PayHandler.isWXLaunchUrl(url)) {
                mWXH5PayHandler.launchWX(view, url);
            } else {
                try {
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    view.getContext().startActivity(intent);
                } catch(Exception e) { e.printStackTrace(); }}return true;
        }

        if (WXH5PayHandler.isWXH5Pay(url)) {
            // Process wechat H5 payment 1
            mWXH5PayHandler = new WXH5PayHandler();
            return mWXH5PayHandler.pay(url);
        } else if(mWXH5PayHandler ! =null) {
            // Process wechat H5 payment 3
            if (mWXH5PayHandler.isRedirectUrl(url)) {
                boolean result = mWXH5PayHandler.redirect();
                mWXH5PayHandler = null;
                return result;
            }
            mWXH5PayHandler = null;
        }
       
        return super.shouldOverrideUrlLoading(view, url); }}Copy the code