Skip to content

Checkout sheet dismissed when a payment method redirects to a third-party page returning a non-2xx status with a renderable body (PayPo via Przelewy24) #493

Description

@olgabednarczyk

What area is the issue related to?

Checkout Sheet Kit

What platform does the issue affect?

All platforms

What version of @shopify/checkout-sheet-kit are you using?

  • @shopify/checkout-sheet-kit (React Native) 3.8.0 → iOS SDK 3.8.0, Android SDK 3.5.3 - iOS: device on iOS 26 - Android: device on Android 16 - Payment method: PayPo (BNPL via Przelewy24, Shopify Payments, Poland)

Do you have reproducible example code?

No response

Steps to Reproduce

  1. Use a Shopify Payments store with Przelewy24 enabled and PayPo available (PL market).
  2. Present checkout with present(checkoutUrl) for a cart whose total qualifies for PayPo.
  3. Choose PayPo as the payment method and continue (Przelewy24Inne systemy ratalne i odroczonePayPo).
  4. Checkout redirects through:
    {shop}.myshopify.com/cart/c/…  →  secure.przelewy24.pl/trnRequest/…  →  process.paypo.pl/smartplan/{uuid}
    
  5. Observe the checkout fail / sheet dismiss instead of the PayPo page loading.

Expected Behavior

HTTP status-code error handling should be scoped to the merchant's own checkout origin.
A third-party payment page reached via redirect should be allowed to load/render exactly as a
browser would — including a page that returns a non-2xx status but serves a renderable body.

process.paypo.pl is a client-side-routed SPA on S3/CloudFront. The deep path isn't a real S3
object, so it responds HTTP 404 (x-amz-error-code: NoSuchKey) but serves the SPA
index.html as the body
. Every real browser (Safari, Chrome — including with the SDK's exact
User-Agent) renders that body, the SPA boots, reads the UUID, and PayPo completes normally.

Actual Behavior

The kit treats the third-party 404 as a fatal checkout error and tears down the sheet, so PayPo
is unusable in-app on both platforms. It polices HTTP status codes on external
payment-provider domains
, which it should not – providers legitimately serve non-2xx
responses with renderable bodies.

Root cause per platform:

  • iOS (ShopifyCheckoutSheetKit 3.8.0): CheckoutWebView.handleResponse cancels any
    response with statusCode >= 400 and emits CheckoutHTTPError. The isCheckout(url:) guard
    is self.url == url, and self.url follows redirects onto process.paypo.pl, so checkout
    error handling is applied to a third-party page.
  • Android (com.shopify:checkout-sheet-kit, incl. main): BaseWebView.onReceivedHttpError
    handleError fires a non-recoverable HttpException for any main-frame HTTP error, with
    no host/origin check at all. CheckoutDialog then dismisses the sheet.

Storefront domain

wasalaa.myshopify.com

Screenshots/Videos/Log output

Started provisional navigation - url:https://{shop}.myshopify.com/cart/c/…
Started provisional navigation - url:https://secure.przelewy24.pl/trnRequest/…
Started provisional navigation - url:https://process.paypo.pl/smartplan/…
Handling response for URL: https://process.paypo.pl/smartplan/…, status code: 404
Not found (404)
Failed provisional navigation with error: Frame load interrupted

404 response (the redirect target that the kit aborts on):

HTTP/1.1 404 Not Found
Server: CloudFront
x-amz-error-code: NoSuchKey
x-amz-error-message: The specified key does not exist.

Body = PayPo SPA index.html (<div id="application"></div> + module scripts) — fully
renderable; a browser ignores the 404 status on a top-level navigation and runs the SPA.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions