# CustomPurchaseControllerProvider

A modern, hooks-based approach to handling purchases and purchase restores with the Superwall SDK.

The `CustomPurchaseControllerProvider` component allows you to integrate your own purchase handling logic with the Superwall SDK. It provides a modern, hooks-based approach to handling purchases and purchase restores.

## Usage

```tsx
import { CustomPurchaseControllerProvider } from 'expo-superwall'
import { SuperwallProvider } from 'expo-superwall'

export default function App() {
  return (
    <CustomPurchaseControllerProvider
      controller={{
        onPurchase: async (params) => {
          if (params.platform === "ios") {
            console.log("iOS purchase:", params)
            // Handle iOS purchase with StoreKit
          } else {
            console.log("Android purchase:", params.productId)
            // Handle Android purchase with Google Play Billing
          }
        },
        onPurchaseRestore: async () => {
          console.log("Restore purchases requested")
          // Handle restore purchases logic
        },
      }}
    >
      <SuperwallProvider apiKeys={{ ios: "YOUR_IOS_KEY", android: "YOUR_ANDROID_KEY" }}>
        {/* Your app content */}
      </SuperwallProvider>
    </CustomPurchaseControllerProvider>
  )
}
```

> **Warning:** **Important:** The `onPurchase` and `onPurchaseRestore` callbacks communicate the outcome through the resolved value or an error:- **Return/resolve `void` or a success result** → Superwall records a successful purchase or restore
> - **Return a failure/cancelled result or throw** → Superwall records a failed or cancelled outcomeIf your purchase function returns a status like `"cancelled"` or `"error"`, return a `PurchaseResult` with that type (or throw) so Superwall records the correct outcome:```tsx
> onPurchase: async (params) => {
>   const result = await yourPurchaseFunction(params.productId);
> 
>   if (result !== "success") {
>     return { type: "failed", error: `Purchase ${result}` };
>   }
> 
>   // Only reaches here on success
> },
> ```**Why this matters:** If your callback resolves without signaling failure, Superwall will count it as a conversion.

## Props

<TypeTable
  type="{
  controller: {
    type: &#x22;CustomPurchaseControllerContext&#x22;,
    description: &#x22;Object that implements purchase and restore handlers.&#x22;,
    required: true,
  },
  children: {
    type: &#x22;React.ReactNode&#x22;,
    description: &#x22;Child components wrapped by this provider.&#x22;,
    required: true,
  },
}"
/>

### CustomPurchaseControllerContext

<TypeTable
  type="{
  onPurchase: {
    type: &#x22;(params: OnPurchaseParams) => Promise<PurchaseResult | void>&#x22;,
    description: &#x22;Handle a purchase and return a result or throw to signal failure.&#x22;,
  },
  onPurchaseRestore: {
    type: &#x22;() => Promise<RestoreResult | void>&#x22;,
    description: &#x22;Handle restore purchases and return a result or throw to signal failure.&#x22;,
  },
}"
/>

### OnPurchaseParams (iOS)

<TypeTable
  type="{
  platform: {
    type: &#x22;\&#x22;ios\&#x22;&#x22;,
    description: &#x22;Platform identifier for iOS purchases.&#x22;,
    required: true,
  },
  productId: {
    type: &#x22;string&#x22;,
    description: &#x22;App Store product identifier.&#x22;,
    required: true,
  },
  store: {
    type: &#x22;ProductStore?&#x22;,
    description: &#x22;The store backing the product. When `\&#x22;CUSTOM\&#x22;`, the product is not backed by StoreKit and your purchase handler must implement the purchase itself. See ProductStore for all possible values.&#x22;,
  },
}"
/>

### OnPurchaseParams (Android)

<TypeTable
  type="{
  platform: {
    type: &#x22;\&#x22;android\&#x22;&#x22;,
    description: &#x22;Platform identifier for Android purchases.&#x22;,
    required: true,
  },
  productId: {
    type: &#x22;string&#x22;,
    description: &#x22;Google Play product identifier.&#x22;,
    required: true,
  },
  basePlanId: {
    type: &#x22;string&#x22;,
    description: &#x22;Subscription base plan ID.&#x22;,
    required: true,
  },
  offerId: {
    type: &#x22;string?&#x22;,
    description: &#x22;Optional promotional offer ID.&#x22;,
  },
}"
/>

### ProductStore

<TypeTable
  type="{
  APP_STORE: {
    type: &#x22;\&#x22;APP_STORE\&#x22;&#x22;,
    description: &#x22;Apple App Store product.&#x22;,
  },
  PLAY_STORE: {
    type: &#x22;\&#x22;PLAY_STORE\&#x22;&#x22;,
    description: &#x22;Google Play Store product.&#x22;,
  },
  STRIPE: {
    type: &#x22;\&#x22;STRIPE\&#x22;&#x22;,
    description: &#x22;Stripe-managed product.&#x22;,
  },
  PADDLE: {
    type: &#x22;\&#x22;PADDLE\&#x22;&#x22;,
    description: &#x22;Paddle-managed product.&#x22;,
  },
  SUPERWALL: {
    type: &#x22;\&#x22;SUPERWALL\&#x22;&#x22;,
    description: &#x22;Manually granted entitlement from Superwall.&#x22;,
  },
  CUSTOM: {
    type: &#x22;\&#x22;CUSTOM\&#x22;&#x22;,
    description: &#x22;Custom product that your purchase handler must purchase outside StoreKit or Google Play Billing.&#x22;,
  },
  OTHER: {
    type: &#x22;\&#x22;OTHER\&#x22;&#x22;,
    description: &#x22;Unknown or unsupported store.&#x22;,
  },
}"
/>

### PurchaseResult

<TypeTable
  type="{
  type: {
    type: &#x22;\&#x22;cancelled\&#x22; | \&#x22;failed\&#x22; | \&#x22;purchased\&#x22; | \&#x22;pending\&#x22;&#x22;,
    description: &#x22;Outcome of the purchase flow.&#x22;,
    required: true,
  },
  error: {
    type: &#x22;string?&#x22;,
    description: &#x22;Optional error message when type is failed.&#x22;,
  },
}"
/>

### RestoreResult

<TypeTable
  type="{
  type: {
    type: &#x22;\&#x22;restored\&#x22; | \&#x22;failed\&#x22;&#x22;,
    description: &#x22;Outcome of the restore flow.&#x22;,
    required: true,
  },
  error: {
    type: &#x22;string?&#x22;,
    description: &#x22;Optional error message when type is failed.&#x22;,
  },
}"
/>

## Hook

### `useCustomPurchaseController()`

A hook that provides access to the custom purchase controller context from child components.

```tsx
import { useCustomPurchaseController } from 'expo-superwall'

function MyComponent() {
  const controller = useCustomPurchaseController()
  
  if (!controller) {
    // Not within a CustomPurchaseControllerProvider
    return null
  }
  
  const handlePurchase = async () => {
    // Access controller methods if needed
  }
  
  return <Button onPress={handlePurchase}>Purchase</Button>
}
```

<TypeTable
  type="{
  value: {
    type: &#x22;CustomPurchaseControllerContext | null&#x22;,
    description: &#x22;Controller object passed to the provider, or null if not within a provider.&#x22;,
  },
}"
/>

## How It Works

The `CustomPurchaseControllerProvider` listens for purchase events from the Superwall SDK using the `useSuperwallEvents` hook internally. When a purchase or restore event occurs:

1. It calls your provided `onPurchase` or `onPurchaseRestore` method
2. After your method completes, it notifies the Superwall SDK that the purchase was successful
3. Superwall then dismisses the paywall and continues with the user flow

## Integration with RevenueCat

For a complete RevenueCat integration with error handling, subscription status synchronization, and working examples, see the [Using RevenueCat](/docs/expo/guides/using-revenuecat) guide.

## Notes

* The provider must wrap your app at a level where both the Superwall SDK and your purchase logic can access it
* Purchase success/failure handling is automatic - you just need to perform the actual purchase