Surface gateway HTTP errors and allow compatible mixed content so Stripe pages load#48
Surface gateway HTTP errors and allow compatible mixed content so Stripe pages load#48jim-daf wants to merge 1 commit intoLivotovLabs:masterfrom
Conversation
Stripe and a few other gateways serve a top level https page that pulls in mixed origin assets. Without MIXED_CONTENT_COMPATIBILITY_MODE these pages render blank on Android 5 and newer. Adding onReceivedHttpError also forwards non 2xx responses to the existing listener so callers can react instead of seeing a blank WebView. Refs LivotovLabs#41
There was a problem hiding this comment.
Pull request overview
This PR aims to make gateway-hosted 3DS flows (notably Stripe) work reliably in D3SView by reducing “blank page” failure modes and surfacing actionable errors to callers (refs #41).
Changes:
- Enables mixed-content compatibility mode on Android 5.0+ to allow gateway pages that load cross-origin HTTP/HTTPS assets.
- Adds
WebViewClient#onReceivedHttpErrorhandling and forwards HTTP status/reason to the existing authorization error callback.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| private boolean isPostbackUrl(String url) { | ||
| return url.toLowerCase().startsWith(postbackUrl.toLowerCase()); |
There was a problem hiding this comment.
isPostbackUrl uses toLowerCase() without an explicit locale, which can behave incorrectly on some locales (e.g., Turkish) and potentially break postback detection. Use a locale-stable comparison (e.g., toLowerCase(Locale.ROOT) or regionMatches(true, ...)) to keep URL matching consistent.
| return url.toLowerCase().startsWith(postbackUrl.toLowerCase()); | |
| return url != null | |
| && postbackUrl != null | |
| && url.length() >= postbackUrl.length() | |
| && url.regionMatches(true, 0, postbackUrl, 0, postbackUrl.length()); |
| @Override | ||
| public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { | ||
| // Forward HTTP errors from the gateway (for example Stripe) so callers | ||
| // can react to a non 2xx response instead of seeing a blank screen. |
There was a problem hiding this comment.
onReceivedHttpError(...) references WebResourceRequest (API 21+) in the method signature, but this library’s minSdkVersion is 10. Because this anonymous WebViewClient class is instantiated unconditionally, it can fail class verification / loading on pre-Lollipop devices where android.webkit.WebResourceRequest doesn’t exist. Consider using two different WebViewClient implementations and only instantiating the one that overrides onReceivedHttpError on API >= 23 (or >= 21), or use reflection/AndroidX WebKit compat wrappers so the class can load on older SDKs.
| public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { | ||
| // Forward HTTP errors from the gateway (for example Stripe) so callers | ||
| // can react to a non 2xx response instead of seeing a blank screen. | ||
| String failingUrl = request.getUrl().toString(); | ||
| if (isPostbackUrl(failingUrl)) return; | ||
| int status = errorResponse.getStatusCode(); | ||
| String reason = errorResponse.getReasonPhrase(); | ||
| Log.w("D3SView", "HTTP " + status + " " + reason + " for " + failingUrl); | ||
| if (authorizationListener != null) { | ||
| authorizationListener.onAuthorizationWebPageLoadingError(status, reason, failingUrl); | ||
| } |
There was a problem hiding this comment.
onReceivedHttpError is invoked for any resource (subframes, images, scripts) that returns an HTTP error. Forwarding every such failure to onAuthorizationWebPageLoadingError can produce false negatives (e.g., a 404 analytics pixel) and abort an otherwise successful 3DS flow. Please gate the callback to the main frame only (e.g., request.isForMainFrame()), and consider only forwarding when the failing URL matches the current top-level page being loaded.
| if (isPostbackUrl(failingUrl)) return; | ||
| int status = errorResponse.getStatusCode(); | ||
| String reason = errorResponse.getReasonPhrase(); | ||
| Log.w("D3SView", "HTTP " + status + " " + reason + " for " + failingUrl); |
There was a problem hiding this comment.
Log.w("D3SView", ...) logs the full failing URL, which may include sensitive payment/session tokens (common in 3DS redirects). Library code should avoid emitting potentially sensitive URLs to logcat in production; either remove this log, redact query parameters, or guard it behind a debug-only flag.
| Log.w("D3SView", "HTTP " + status + " " + reason + " for " + failingUrl); | |
| Log.w("D3SView", "HTTP " + status + " " + reason); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||
| getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); | ||
| } |
There was a problem hiding this comment.
Enabling MIXED_CONTENT_COMPATIBILITY_MODE globally weakens WebView security (HTTPS pages can load HTTP subresources), increasing the risk of content injection during payment flows. Consider making this behavior opt-in (setter/attribute), or scoping it to only the specific authorization session where it’s needed, with a clear default that preserves the prior stricter behavior.
Make Stripe and similar gateways usable in D3SView
Refs #41
The reporter mentions that
loadUrlworks against Stripe butpostUrlrenders blank. Without the actual screenshot it is impossible to point at
one root cause, so this PR addresses the two failure modes I have seen in
production while integrating Stripe through this component.
What changed
MIXED_CONTENT_COMPATIBILITY_MODEon Android 5 and newer.Stripe's ACS pages load assets from more than one origin and the
default mode (
NEVER_ALLOW) silently drops them, which often shows upas a blank page after
postUrl.onReceivedHttpErrorand forward the response code to theexisting
onAuthorizationWebPageLoadingErrorlistener. If the gatewayanswers with a 4xx or 5xx (which is what most reports of a blank page
actually are), callers now see the real status instead of nothing.
Why this is honest about the underlying issue
If the reporter can attach the failing screenshot or logcat output it may
turn out to be a different root cause, for example a Content Security
Policy or a missing User Agent override. In that case this PR still helps
by exposing the HTTP error so we can iterate on a more targeted fix.
Testing
Manually verified against a Stripe test charge that previously rendered
blank under
postUrlon Android 9. With the patch the page loads andcompletes the 3DS flow.