View Issue Details

IDProjectCategoryView StatusLast Update
0007939module PayPal Checkoutmodule PayPal checkout - subpublic2026-05-21 14:52
Reportermario_lorenz Assigned To 
PrioritynormalSeverityminorReproducibilityhave not tried
Status resolvedResolutionfixed 
Product Version2.8.0 / 3.7.0 
Target Version2.8.4 / 3.7.4Fixed in Version2.8.4 / 3.7.4 
Summary0007939: Customers with Apple iPhones and the "Google Search App" cannot log in (only loading animation is displayed).
DescriptionWKWebView in app containers (the one used in the Google Search app on iOS) handles pop-ups with restrictions. The PayPal SDK opens the login via a pop-up window—if this is blocked by the container or malfunctions, the user never reaches the Approve page.

The log shows exactly what we see: `createOrder` runs, `PAYER_ACTION_REQUIRED` hangs, and no `onApprove` event is triggered.

Apple ITP/Cookie restrictions disrupt the cross-domain journey (shop <-> paypal.com <-> shop) if the user does manage to log in to PayPal.
Steps To ReproduceTry to pay with Apple iPhones and the "Google Search App".

It doesn't necessarily have to be only this combination that's affected, but it can also be any type of mobile device with third-party apps based on "WKWebView in app container"-technique.
TagsNo tags attached.

Activities

mario_lorenz

2026-04-30 13:58

developer   ~0018477

We divide the solution to the problem into two phases:

Phase 1 — shipping in 2.8.4 / 3.7.4

Four coordinated changes per template:

1. Default branch in both onApprove chains — anything we don't explicitly handle now reports diagnostic data (User-Agent, the received status, the failing context) to the existing cl=ajaxpay&fnc=logError endpoint and redirects the customer to a new recovery endpoint instead of leaving the spinner hanging.
2. .catch() on all four fetch chains (createOrder and onApprove, both variants) — network errors, JSON parse failures and exceptions from upstream all flow into the same diagnostic + recovery path. createOrder rethrows after logging so the PayPal SDK still fires its onError callback; onApprove redirects to recovery directly.
3. JSON parse error is distinguished from network error — res.json() is wrapped in its own .catch() that throws with a not-json: prefix, so we can tell whether the server returned HTML (typical session-loss symptom) or whether the request never completed.
4. A new OrderController::paymentInterrupted endpoint queues a localized error message (OSC_PAYPAL_PAYMENT_INTERRUPTED, German and English) via the existing RedirectWithMessage mechanism and forwards the customer to the payment page. The message asks the customer to retry or pick a different payment method, and includes a conditional hint: if they happen to be inside an in-app browser, opening the shop in a standard browser (Safari, Chrome, Firefox) may resolve the issue. We deliberately phrased this as a hint, not a diagnosis — silent-fail can also be triggered by transient network or proxy issues, and we don't want to mislead customers who are already in a regular browser.
5. The existing logError endpoint was raised from debug to warning so these diagnostic entries surface in operations logs without enabling debug verbosity. The diagnostic payload includes the User-Agent string, which is the discriminator we need.

What Phase 1 does not solve

Customers in genuinely restricted in-app browsers will still not be able to complete a PayPal payment — the cookie/popup restrictions sit at the OS/app layer and the module cannot work around them. What changes is that they now see a meaningful error and can switch browser or payment method, and we have server-side data points to act on.

Phase 2 — planned, evidence-driven

With Phase 1 deployed for ~4–8 weeks, the operations log will contain the User-Agent strings of the actually affected combinations. Once we have that data we plan to add:

- Configurable, evidence-based User-Agent detection (rendered in the backend, not hard-coded).
- A pre-emptive hint shown above the PayPal button when a known-problematic UA is detected — soft mode (banner, button still clickable) by default, optional hard mode (button hidden) for operators who prefer it.
- Possibly a more targeted error message variant for the detected-UA case, separate from the generic recovery message we ship in Phase 1.

We're not committing to Phase 2 in 2.8.4 / 3.7.4 because it would otherwise be guesswork — the UA list we'd hard-code today wouldn't have evidence behind it. Phase 1 is what unblocks that.