Skip to content

Native APM v2: failed web redirect is never reported to the backend — invoice stays pending #535

Description

@dbashtani

Summary

When a native alternative payment (v2 flow) performs a web-type redirect and the redirect fails — the customer closes ASWebAuthenticationSession, or the session fails to start (e.g. HTTPS callback used without the required webcredentials associated domain) — the SDK surfaces processout-mobile.cancelled to the merchant app, but never informs the backend. The invoice remains in its pre-redirect pending state in the dashboard, so the SDK-reported outcome and the invoice state disagree, which breaks merchant-side reconciliation.

Details

In NativeAlternativePaymentDefaultInteractor.uncheckedRedirect(to:):

  • deep_link branch: a failed redirect IS reported — redirectResult = redirect.confirmationRequired ? .init(success: didOpenUrl) : nil, sent via continuePayment.
  • web branch: webAuthenticationSession.authenticate(using:) throws, execution never reaches continuePayment, and the failure is only set locally via setFailureState. The backend gets nothing, even when the response's redirect.confirmationRequired is true.

So the two redirect types are inconsistent: the protocol has a redirect confirmation payload (PONativeAlternativePaymentRedirectResultV2(success:)) and the backend opts into it via confirmationRequired, but web redirect failures bypass it entirely.

Note that the failure is visible in SDK telemetry (setFailureState logs at warn with the invoice ID attached, which reaches the telemetry endpoint) — but telemetry doesn't transition invoice state, so the dashboard still shows the payment as pending until expiry.

The Android SDK has the same asymmetry (handleWebRedirect(failure:) completes locally without sending PONativeAlternativePaymentRedirectConfirmation(success = false)), so this appears to be a protocol-level gap rather than a platform quirk.

Steps to reproduce

  1. Start a native APM payment whose gateway responds with a redirect of type: "web" and confirmationRequired: true.
  2. Close the presented ASWebAuthenticationSession (or make it fail to start).
  3. Observe: app receives POFailure with code processout-mobile.cancelled; the invoice in the dashboard remains pending — no state change, no record of the failed redirect attempt.

Expected behavior

When redirect.confirmationRequired is true and the web redirect fails, the SDK should report redirect: .init(success: false) via continuePayment (mirroring the deep link branch) before surfacing the local failure, so the backend can transition the invoice accordingly.

Environment

  • Observed on 4.42.0; the relevant code is unchanged on current master (post-4.44.0).
  • Context: we (Glovo) hit this at scale when web redirect sessions failed to start, and payments our app reported as failed/cancelled stayed pending in the dashboard.

Proposed fix

PR incoming that mirrors the deep link semantics (gated on confirmationRequired, best-effort report, original error still propagated unchanged). Two points we'd like your guidance on in review:

  1. What the backend does on success: false for web redirects (fail the attempt vs. allow retry) — the deep link precedent suggests it's handled, but please confirm.
  2. Whether the report should also be attempted from a cancelled task context (user-cancel path), which would require an unstructured task to survive cancellation.

Additionally, the confirmation payload only carries success: Bool — an optional failure reason field could be a useful protocol extension so the backend can distinguish customer cancellation from technical failures, but that's beyond this PR's scope.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions