---
title: "What does purchaseCancelledError (code 1) mean in the purchases SDK?"
description: "purchaseCancelledError (code 1) means the customer closed the purchase sheet. Nothing was bought and nothing is broken: show no error and keep the paywall open."
url: https://revenuedot.app/docs/errors/sdk-purchase-cancelled-error
---

# What does purchaseCancelledError (code 1) mean in the purchases SDK?

The purchases SDK raises purchaseCancelledError (code 1) when the customer closes the purchase sheet without buying.

## Quick facts

| | |
|---|---|
| Error | `ErrorCode.purchaseCancelledError` (iOS), `PurchasesErrorCode.PurchaseCancelledError` (Android), code 1 |
| Where | Purchases SDK (iOS, Android and the hybrid SDKs built on them) |
| What the vendor says | Purchase was cancelled. |

## Cause

- StoreKit 2 reports `StoreKitError.userCancelled` and StoreKit 1 reports `SKError.paymentCancelled`; the SDK maps both to this error.
- On Android, Play Billing returns `USER_CANCELED` when the customer leaves the billing flow, and the SDK maps it here ([Google docs](https://developer.android.com/google/play/billing/errors)).
- The Cancel button in the Test Store purchase dialog raises it too, so a tester can see it without leaving the app.

## Fix

1. Treat it as an outcome, not an error. In the Swift async API read `userCancelled` on the result; in callbacks read the `userCancelled` flag.
2. Leave the paywall open with its products loaded so the customer can choose again.
3. Count cancels apart from failures in analytics.
4. Turn on debug logs if a tester says they did not cancel; the underlying error shows who reported the cancel.

## Example

```swift
// The purchases SDK throws `ErrorCode` values; match the one you handle and let the rest fall through.
func handle(_ error: Error) {
    guard let code = error as? ErrorCode else {
        print("Not a purchases SDK error: \(error)")
        return
    }
    switch code {
    case .purchaseCancelledError:
        // A cancel is the customer's choice: no dialog, no retry, nothing to unlock.
        print("Purchase cancelled")
    default:
        print("Other purchases error: \(code.description)")
    }
}
```

*Compile-checked: `npm run check:snippets` type-checks this snippet with `swiftc` together with the `ErrorCode.swift` file of the RevenueDot `purchases-ios` fork (checked 2026-10-03).*

## How the RevenueDot SDK reports it

RevenueDot never sees a cancelled purchase, because the SDK stops before it posts a receipt to `POST /v1/receipts`. Deep dive: [revenuedot.app/errors/purchase-cancelled-error](https://revenuedot.app/errors/purchase-cancelled-error).

## Related

- [What does StoreKitError.userCancelled mean?](https://revenuedot.app/docs/errors/storekit-error-user-cancelled.md)
- [What does Play Billing USER_CANCELED (1) mean?](https://revenuedot.app/docs/errors/play-billing-user-canceled.md)
- [What does paymentPendingError (code 20) mean in the purchases SDK?](https://revenuedot.app/docs/errors/sdk-payment-pending-error.md)
- [What RevenueDot's API errors mean (server codes)](https://revenuedot.app/docs/api/errors.md)

## Source

- [RevenueDot purchases-ios: ErrorCode.swift (the iOS ErrorCode enum)](https://github.com/revenuedot/purchases-ios/blob/revenuedot/main-patches/Sources/Generated/ErrorCode.swift)
- [RevenueDot purchases-android: PurchasesErrorCode.kt (the Android enum)](https://github.com/revenuedot/purchases-android/blob/revenuedot/main-patches/purchases/src/main/kotlin/generated/com/revenuecat/purchases/PurchasesErrorCode.kt)
- [RevenueDot purchases-ios: StoreKitError+Extensions.swift](https://github.com/revenuedot/purchases-ios/blob/revenuedot/main-patches/Sources/Error%20Handling/StoreKitError%2BExtensions.swift)
- [RevenueDot purchases-android: google/errors.kt (Play Billing mapping)](https://github.com/revenuedot/purchases-android/blob/revenuedot/main-patches/purchases/src/main/kotlin/com/revenuecat/purchases/google/errors.kt)

Checked: 2026-10-03
