Skip to content

Conversation

@facumenzella
Copy link
Member

@facumenzella facumenzella commented Feb 5, 2026

Summary

When users complete purchases via third-party payment apps (e.g., UPI in India), switching to the external app causes AMSError.paymentSheetFailed to be returned. This error was previously mapped to PURCHASE_CANCELLED, even though the user may be completing (not cancelling) the payment.

Changes

  • Adds new error code purchaseInterruptedError (code 43, codeName PURCHASE_INTERRUPTED)
  • Tracks app background state during in-flight purchases via purchaseBackgroundedState
  • When AMSError.paymentSheetFailed is received AND app was backgrounded during purchase, returns purchaseInterruptedError instead of purchaseCancelledError
  • purchaseInterruptedError.isCancelledError returns false, so userCancelled will be false for interrupted purchases

Behavior

Scenario Error Returned userCancelled
User taps "Cancel" button purchaseCancelledError true
User switches to UPI app (app backgrounds) purchaseInterruptedError false

Developer Guidance

When receiving purchaseInterruptedError, developers should call getCustomerInfo() to verify actual entitlement status, as the payment may have succeeded in the external app.

Test plan

  • Builds successfully
  • ErrorCodeTests pass (including new testPurchaseInterruptedError)
  • Manual testing with UPI payment flow on device in India

Addresses #6194

@facumenzella facumenzella marked this pull request as ready for review February 6, 2026 11:15
@facumenzella facumenzella requested a review from a team as a code owner February 6, 2026 11:15
@claude
Copy link

claude bot commented Feb 6, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

facumenzella and others added 4 commits February 10, 2026 12:02
When users complete purchases via third-party payment apps (e.g., UPI in India),
switching to the external app causes AMSError.paymentSheetFailed to be returned.
This error was previously mapped to PURCHASE_CANCELLED, even though the user may
be completing (not cancelling) the payment.

This change:
- Adds new error code `purchaseInterruptedError` (code 43)
- Tracks app background state during in-flight purchases
- When AMSError.paymentSheetFailed is received AND app was backgrounded,
  returns `purchaseInterruptedError` instead of `purchaseCancelledError`
- `purchaseInterruptedError.isCancelledError` returns false, so `userCancelled`
  will be false for interrupted purchases

Developers should handle this error by calling `getCustomerInfo()` to verify
actual entitlement status.

Fixes #6194

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Store notification observer and remove in deinit to prevent leaks
- Clean up purchaseBackgroundedState in handlePurchasedTransaction (SK1)
- Clean up purchaseBackgroundedState in handleDeferredTransaction
- Add test for UPI/external payment app interrupted flow

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add purchaseInterruptedError (code 43) to the public API baseline.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The error message for storeProblemError differs between macOS and iOS.
Update test to handle both platforms.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@facumenzella facumenzella force-pushed the fix/purchase-interrupted-upi-payments branch from 7d51c3d to 03569a2 Compare February 10, 2026 11:04
@facumenzella facumenzella requested a review from a team as a code owner February 10, 2026 11:04
Logger.debug(Strings.purchase.purchase_interrupted_external_app(
productIdentifier: skError.userInfo["productId"] as? String ?? "unknown"
))
return ErrorUtils.purchaseInterruptedError(error: error)
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I think changing the return error code in this case is a kind of behavioral breaking change... For example, we will likely need to update our hybrids to handle this appropriately as well... Maybe we could do it if we do a major, but maybe it's worth exposing this information some other way for now...

Could we maybe add this information as part of the error's userInfo or something like that? for devs that want to handle this differently than a normal cancellation? Not sure how common this use case would be. If developer should handle this case differently than a cancellation, then maybe this is ok. If it's uncommon, let's avoid the behavior change if we can 🙏

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, cc @ajpallares , not sure if this can have any effects on the RCT project we're working on?

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't think about this 🤔
I think I like it? Given that introducing a breaking change will most likely open other doors for issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another thing is whether we might want to reuse the paymentPendingError error code for this scenario, instead of introducing a new one... but it would still be a bit of a behavioral breaking change...

In any case, it would be good to get some @RevenueCat/catforms feedback on this PR.

Copy link
Member

@rickvdl rickvdl left a comment

Choose a reason for hiding this comment

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

Interesting edge case, thanks for digging into this!

Maybe this also affects RCT and / or is a potential case we should cover there, so it might make sense if @ajpallares could also have a look at it.

@helians11
Copy link

helians11 commented Feb 12, 2026

UPIs are very common in India, Most of the users make the payment by UPI only. There should be a way for us to distinguish user cancellation vs payment pending/in progress so that we can show the right state to the users.
Currently for this to work we have to keep the user waiting for 5 mins. We keep this payment timeout and keep on checking the payment status in background. Although this works, but the problem here is that even if the user is cancelling the payment or the purchase flow then also they will have to wait for 5 mins as we have no way to tell if user has actually cancelled it or the payment is in progress state.
I hope this gets resolved for good user experience and better tracking.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants