> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# onPurchaseComplete

> Callback invoked after Encore completes a native StoreKit purchase

## Signature

```swift theme={null}
@discardableResult
public func onPurchaseComplete(
    _ handler: @escaping (StoreKit.Transaction, String) async -> Void
) -> Encore
```

## Parameters

| Parameter | Type                                  | Description                                                           |
| :-------- | :------------------------------------ | :-------------------------------------------------------------------- |
| `handler` | `(Transaction, String) async -> Void` | Closure receiving the verified StoreKit `Transaction` and `productId` |

## When It Fires

`onPurchaseComplete` fires **only** when:

1. No `onPurchaseRequest` handler is registered
2. Encore successfully completes a native StoreKit 2 purchase
3. The transaction is verified and finished

It does **not** fire when:

* An `onPurchaseRequest` handler is set (the delegated handler takes full responsibility)
* The user cancels the purchase
* The purchase fails or is pending

## Usage

Use this callback to sync StoreKit transactions with subscription managers that don't automatically detect external purchases.

### Adapty

```swift theme={null}
Encore.shared.onPurchaseComplete { transaction, productId in
    try? await Adapty.reportTransaction(transaction)
}
```

### Qonversion

```swift theme={null}
Encore.shared.onPurchaseComplete { transaction, productId in
    Qonversion.shared().syncStoreKit2Purchases()
}
```

<Note>
  RevenueCat and Superwall auto-detect StoreKit transactions when configured in observer mode. They do not need `onPurchaseComplete`.
</Note>

## Chaining

```swift theme={null}
Encore.shared
    .onPurchaseComplete { transaction, productId in
        try? await Adapty.reportTransaction(transaction)
    }
    .onPassthrough { placementId in
        router.handleOriginalAction(for: placementId)
    }
```

## Behavior

* **Survives `reset()`** — app-level infrastructure, not user state
* **Last handler wins** — setting a new handler replaces the previous one
* **Main actor** — called on the main thread
