---
title: "What does productAlreadyPurchasedError (code 6) mean in the purchases SDK?"
description: "productAlreadyPurchasedError (code 6) means the customer already owns the product. Refresh customer info or restore purchases instead of selling it again."
url: https://revenuedot.app/docs/errors/sdk-product-already-purchased-error
---

# What does productAlreadyPurchasedError (code 6) mean in the purchases SDK?

The purchases SDK raises productAlreadyPurchasedError (code 6) when the store refuses to sell a product the customer already owns.

## Quick facts

| | |
|---|---|
| Error | `ErrorCode.productAlreadyPurchasedError` (iOS), `PurchasesErrorCode.ProductAlreadyPurchasedError` (Android), code 6 |
| Where | Purchases SDK (iOS, Android and the hybrid SDKs built on them) |
| What the vendor says | This product is already active for the user. |

## Cause

- Android: Play Billing `ITEM_ALREADY_OWNED`. Google says it is usually permanent but can be stale cache, and recommends `queryPurchasesAsync()`.
- StoreKit 1: an `unknown` `SKError` whose underlying error says the customer is currently subscribed.
- On Android, the backend answered 7852 (a web purchase that belongs to another user); iOS reports that as `purchaseBelongsToOtherUser` instead.

## Fix

1. Call `getCustomerInfo` and check the entitlement. If it is active, close the paywall.
2. If it is not active, call `restorePurchases()` so the SDK posts the owned purchase.
3. On Android, acknowledge or consume purchases so they do not stay owned and block a second purchase.
4. Hide the buy button for owners by checking the entitlement before you show the paywall.

## 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 .productAlreadyPurchasedError:
        // Good news: the customer already owns it. Refresh customer info or restore, then close the paywall.
        print("Already purchased; restore or refresh customer info")
    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

Deep dive: [revenuedot.app/errors/product-already-purchased-error](https://revenuedot.app/errors/product-already-purchased-error). See also [how to restore purchases](https://revenuedot.app/docs/help/restore-purchases.md).

## Related

- [What does Play Billing ITEM_ALREADY_OWNED (7) mean?](https://revenuedot.app/docs/errors/play-billing-item-already-owned.md)
- [What does receiptAlreadyInUseError (code 7) mean in the purchases SDK?](https://revenuedot.app/docs/errors/sdk-receipt-already-in-use-error.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-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)
- [RevenueDot purchases-android: common/errors.kt (backend code mapping)](https://github.com/revenuedot/purchases-android/blob/revenuedot/main-patches/purchases/src/main/kotlin/com/revenuecat/purchases/common/errors.kt)

Checked: 2026-10-03
