> ## 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.

# onPurchaseRequest()

> Handle purchase flow after offer completion

Registers a handler invoked when Encore's offer flow completes and a purchase is needed. The handler receives a `PurchaseRequest` containing the product ID, placement ID, and optional promotional offer ID. Use it to trigger the purchase via your subscription manager (e.g. StoreKit, RevenueCat).

<Warning>
  Set `onPurchaseRequest` before calling `placement(_:).show()`. If not set, Encore falls back to a native StoreKit purchase automatically. For best results with 3P subscription managers, set this handler to purchase through your provider directly.
</Warning>

## Signature

```swift theme={null}
@discardableResult
public func onPurchaseRequest(_ handler: @escaping (PurchaseRequest) async throws -> Bool) -> Encore
```

<Note>
  **Return `true` on success.** Returning `true` tells Encore the purchase completed, and Encore **auto-dismisses the offer sheet**. Return `false` (or throw) when the purchase was cancelled or failed — the sheet stays up so the user can retry. A thrown error is caught by the SDK and treated as `false`.

  A legacy `... async throws -> Void` overload also exists but is **deprecated**: it can't signal success/failure reliably, so dismiss behavior is less predictable. Always use the `Bool`-returning form.
</Note>

## Parameters

| Name      | Type                                     | Description                                                                                                               |
| --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `handler` | `(PurchaseRequest) async throws -> Bool` | Async throwing callback receiving a `PurchaseRequest`. Trigger your purchase flow here and return `true` if it succeeded. |

### PurchaseRequest Properties

| Property       | Type      | Description                                                            |
| -------------- | --------- | ---------------------------------------------------------------------- |
| `productId`    | `String`  | The App Store product identifier to purchase                           |
| `placementId`  | `String?` | Which placement triggered this purchase, if any                        |
| `promoOfferId` | `String?` | Promotional offer identifier to apply, or `nil` for standard purchases |

## Returns

`@discardableResult` — returns the `Encore` instance for chaining (`.onPurchaseRequest { … }.onPassthrough { … }`).

## When It Fires

| Scenario                          | Fires?                                       |
| --------------------------------- | -------------------------------------------- |
| User accepts an offer             | Yes                                          |
| User dismisses without purchasing | No — see [onPassthrough()](./on-passthrough) |
| No offer available for this user  | No — see [onPassthrough()](./on-passthrough) |

## Usage

<Tabs>
  <Tab title="StoreKit">
    ```swift theme={null}
    Encore.shared.onPurchaseRequest { purchaseRequest in
        guard let product = try await Product.products(for: [purchaseRequest.productId]).first else {
            return false
        }
        let result = try await product.purchase()
        guard case .success(let verification) = result,
              case .verified(let transaction) = verification else {
            return false  // cancelled, pending, or unverified — keep the sheet up
        }
        await transaction.finish()
        return true  // verified purchase — dismiss the sheet
    }
    ```
  </Tab>

  <Tab title="RevenueCat">
    ```swift theme={null}
    Encore.shared.onPurchaseRequest { purchaseRequest in
        let products = try await Purchases.shared.products([purchaseRequest.productId])
        guard let product = products.first else { return false }
        let result = try await Purchases.shared.purchase(product: product)
        return !result.userCancelled
    }
    ```
  </Tab>

  <Tab title="Adapty">
    ```swift theme={null}
    Encore.shared.onPurchaseRequest { purchaseRequest in
        let products = try await Adapty.getPaywallProducts(paywall: paywall)
        guard let product = products.first(where: { $0.vendorProductId == purchaseRequest.productId }) else {
            return false
        }
        let result = try await Adapty.makePurchase(product: product)
        return !result.isPurchaseCancelled
    }
    ```
  </Tab>

  <Tab title="Qonversion">
    ```swift theme={null}
    Encore.shared.onPurchaseRequest { purchaseRequest in
        let result = try await Qonversion.shared().purchaseProduct(productId: purchaseRequest.productId)
        return !result.isCancelled
    }
    ```
  </Tab>

  <Tab title="Custom">
    ```swift theme={null}
    Encore.shared.onPurchaseRequest { purchaseRequest in
        // purchaseRequest.productId — App Store product ID to purchase
        // purchaseRequest.placementId — which placement triggered this (optional)
        // purchaseRequest.promoOfferId — promotional offer to apply (optional)
        let didSucceed = try await yourManager.purchase(purchaseRequest.productId)
        return didSucceed   // true → Encore dismisses the offer sheet
    }
    ```
  </Tab>
</Tabs>

### Chained with onPassthrough

```swift theme={null}
Encore.shared
    .onPurchaseRequest { purchaseRequest in
        guard let product = try await Product.products(for: [purchaseRequest.productId]).first else {
            return false
        }
        let result = try await product.purchase()
        if case .success = result { return true }
        return false
    }
    .onPassthrough { placementId in
        // User didn't purchase — continue original flow
        proceedWithCancellation()
    }
```

<Info>
  The handler is an `async throws -> Bool` function, so you can `await` other async APIs (StoreKit, network requests) directly inside it. Return `true` once the purchase succeeds so Encore dismisses the offer sheet.
</Info>

<Note>
  When no `onPurchaseRequest` handler is set, Encore handles purchases via native StoreKit 2 automatically.
  Use [onPurchaseComplete](/publishers/ios/sdk-reference/on-purchase-complete) to sync these transactions with providers that don't auto-detect StoreKit transactions (e.g., Adapty, Qonversion).
</Note>
