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

Registers a listener invoked when Encore's offer flow completes and a purchase is needed. The listener receives the product ID and placement ID and should trigger the purchase via your billing library.

<Warning>
  You **must** call [`completePurchaseRequest()`](./complete-purchase-request) in **every** code path of your handler — both success and failure. Failing to do so will block the SDK from presenting future offers until the app restarts.
</Warning>

## Signature

```tsx theme={null}
onPurchaseRequest(handler: (event: PurchaseRequestEvent) => void): () => void
```

## Parameters

| Name      | Type                                    | Description                                   |
| --------- | --------------------------------------- | --------------------------------------------- |
| `handler` | `(event: PurchaseRequestEvent) => void` | Callback receiving the purchase request event |

### PurchaseRequestEvent

| Field         | Type    | Description                                       |
| ------------- | ------- | ------------------------------------------------- |
| `productId`   | string  | The App Store / Play Store product ID to purchase |
| `placementId` | string? | Which placement triggered this request            |

## Returns

An unsubscribe function. Call it to remove the listener.

## 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="RevenueCat">
    ```tsx theme={null}
    const unsubscribe = Encore.onPurchaseRequest(async ({ productId }) => {
      try {
        const { customerInfo } = await Purchases.purchaseProduct(productId);
        await Encore.completePurchaseRequest(true);
      } catch (error) {
        await Encore.completePurchaseRequest(false);
      }
    });
    ```
  </Tab>

  <Tab title="react-native-iap">
    ```tsx theme={null}
    const unsubscribe = Encore.onPurchaseRequest(async ({ productId }) => {
      try {
        await RNIap.requestSubscription({ sku: productId });
        await Encore.completePurchaseRequest(true);
      } catch (error) {
        await Encore.completePurchaseRequest(false);
      }
    });
    ```
  </Tab>

  <Tab title="Custom">
    ```tsx theme={null}
    const unsubscribe = Encore.onPurchaseRequest(async ({ productId, placementId }) => {
      try {
        // Your billing library here
        await yourBillingService.purchase(productId);
        await Encore.completePurchaseRequest(true);
      } catch (error) {
        await Encore.completePurchaseRequest(false);
      }
    });
    ```
  </Tab>
</Tabs>

<Warning>
  Every code path must call `completePurchaseRequest()`. If your handler throws without calling it, the SDK will remain locked until the app restarts. Always use try/catch.
</Warning>

### With onPassthrough

```tsx theme={null}
useEffect(() => {
  const unsub1 = Encore.onPurchaseRequest(async ({ productId }) => {
    try {
      await billingService.purchase(productId);
      await Encore.completePurchaseRequest(true);
    } catch (error) {
      await Encore.completePurchaseRequest(false);
    }
  });

  const unsub2 = Encore.onPassthrough(({ placementId }) => {
    proceedWithCancellation();
  });

  return () => {
    unsub1();
    unsub2();
  };
}, []);
```

<Tip>
  Clean up listeners when your component unmounts by calling the unsubscribe function in a `useEffect` cleanup.
</Tip>
