Skip to content

Surface gateway HTTP errors and allow compatible mixed content so Stripe pages load#48

Open
jim-daf wants to merge 1 commit intoLivotovLabs:masterfrom
jim-daf:fix/stripe-load-issue-41
Open

Surface gateway HTTP errors and allow compatible mixed content so Stripe pages load#48
jim-daf wants to merge 1 commit intoLivotovLabs:masterfrom
jim-daf:fix/stripe-load-issue-41

Conversation

@jim-daf
Copy link
Copy Markdown

@jim-daf jim-daf commented Apr 22, 2026

Make Stripe and similar gateways usable in D3SView

Refs #41

The reporter mentions that loadUrl works against Stripe but postUrl
renders 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

  1. Enable MIXED_CONTENT_COMPATIBILITY_MODE on 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 up
    as a blank page after postUrl.
  2. Implement onReceivedHttpError and forward the response code to the
    existing onAuthorizationWebPageLoadingError listener. If the gateway
    answers 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 postUrl on Android 9. With the patch the page loads and
completes the 3DS flow.

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
@jim-daf jim-daf marked this pull request as ready for review April 22, 2026 18:39
Copilot AI review requested due to automatic review settings April 22, 2026 18:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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#onReceivedHttpError handling 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());
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
return url.toLowerCase().startsWith(postbackUrl.toLowerCase());
return url != null
&& postbackUrl != null
&& url.length() >= postbackUrl.length()
&& url.regionMatches(true, 0, postbackUrl, 0, postbackUrl.length());

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +130
@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.
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +138
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);
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
if (isPostbackUrl(failingUrl)) return;
int status = errorResponse.getStatusCode();
String reason = errorResponse.getReasonPhrase();
Log.w("D3SView", "HTTP " + status + " " + reason + " for " + failingUrl);
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Log.w("D3SView", "HTTP " + status + " " + reason + " for " + failingUrl);
Log.w("D3SView", "HTTP " + status + " " + reason);

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +84
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants