---
title: "What does StoreKitError.userCancelled mean?"
description: "StoreKitError.userCancelled means the person cancelled a StoreKit 2 flow. It is not a failure: show no error and leave the paywall as it was."
url: https://revenuedot.app/docs/errors/storekit-error-user-cancelled
---

# What does StoreKitError.userCancelled mean?

StoreKitError.userCancelled means the person cancelled, so nothing was bought and nothing is broken.

## Quick facts

| | |
|---|---|
| Error | `StoreKitError.userCancelled` |
| Where | StoreKit 2, Swift (`StoreKitError`) |
| Available since | iOS 15.0, iPadOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0 |
| What the vendor says | The user canceled. |

## Cause

- The person dismissed a StoreKit flow, such as the purchase confirmation sheet, before finishing it.
- A purchase made with `Product.purchase()` reports a cancel as the result `Product.PurchaseResult.userCancelled`, which Apple documents as the user cancelling the purchase. The error case is the same outcome in error form, so handle both the same way.
- Apple documents no other cause. A cancel is the person's choice, not an App Store failure.

## Fix

1. Do not show an error. A cancel is a normal step in a purchase funnel.
2. Leave the paywall open with its products loaded, so the person can choose again.
3. Count cancels apart from failures in your analytics, so real errors stay visible.
4. Do not unlock anything and do not retry for the person.

## Example

```swift
import StoreKit

// Treat a cancel as an outcome, not an error: no alert, no retry, no unlock.
func buy(_ product: Product) async {
    do {
        switch try await product.purchase() {
        case .userCancelled:
            print("Cancelled by the person; leave the paywall as it is")
        case .pending:
            print("Waiting for approval; do not unlock yet")
        case .success(let verification):
            print("Bought: \(verification)")
        @unknown default:
            break
        }
    } catch StoreKitError.userCancelled {
        // The error form of the same outcome.
        print("Cancelled by the person; leave the paywall as it is")
    } catch {
        print("Purchase failed: \(error)")
    }
}
```

*Compile-checked: `npm run check:snippets` type-checks this snippet with `swiftc` against the Apple SDK (macOS target, checked 2026-10-03).*

## How the RevenueDot SDK reports it

The RevenueDot `purchases-ios` fork maps `StoreKitError.userCancelled` to `ErrorCode.purchaseCancelledError` (code 1) and sets `userCancelled` on the purchase result. RevenueDot's server never sees a cancelled purchase, because the SDK stops before it posts a receipt. See [what purchaseCancelledError means](https://revenuedot.app/docs/errors/sdk-purchase-cancelled-error.md).

## Related

- [What does purchaseCancelledError (code 1) mean in the purchases SDK?](https://revenuedot.app/docs/errors/sdk-purchase-cancelled-error.md)
- [What does Play Billing USER_CANCELED (1) mean?](https://revenuedot.app/docs/errors/play-billing-user-canceled.md)
- [What does Product.PurchaseError.purchaseNotAllowed mean?](https://revenuedot.app/docs/errors/storekit-purchase-error-purchase-not-allowed.md)

## Source

- [Apple: StoreKitError.userCancelled](https://developer.apple.com/documentation/storekit/storekiterror/usercancelled)
- [Apple: Product.PurchaseResult.userCancelled](https://developer.apple.com/documentation/storekit/product/purchaseresult/usercancelled)
- [RevenueDot purchases-ios: StoreKitError+Extensions.swift (how the SDK maps StoreKit 2 errors)](https://github.com/revenuedot/purchases-ios/blob/revenuedot/main-patches/Sources/Error%20Handling/StoreKitError%2BExtensions.swift)

Checked: 2026-10-03
