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

# IEncorePurchaseController

> The interface your game implements so a product an Encore offer sells is bought through your own billing code.

When a `ReduceChurn` offer sells a product, Encore asks your game to buy it through an `IEncorePurchaseController` you pass to [`Configure()`](https://docs.encorekit.com/publishers/unity/sdk-reference/configure). Encore decides when; your code buys it, through your own store or billing library. Encore owns the sheet, the analytics, and the attribution around the call. A reward placement never asks for a purchase.

## Definition

```csharp theme={null}
public interface IEncorePurchaseController
{
    // Called on the Unity main thread. Throw, or fault the task, for a real failure.
    Task<PurchaseResult> Purchase(PurchaseRequest request);
}

public enum PurchaseResult
{
    Purchased, // the purchase completed
    Cancelled, // the player backed out
    Pending,   // the store deferred the purchase
}
```

* **A throw is a failed purchase**, never a cancelled one. So are a faulted task, a null task, and a result the package doesn't know.
* **The controller lives for the process.** `Configure` takes it once; a second `Configure` is ignored.

## PurchaseRequest

```csharp theme={null}
public sealed class PurchaseRequest
{
    public string ProductId;    // the store product to buy
    public string PlacementId;  // null when the purchase did not come from a named placement
    public string PromoOfferId; // the offer to apply: an App Store promotional offer on iOS, a Google Play offer on Android
    public string BasePlanId;   // Android only: the Google Play base plan to buy
}
```

| Field | Type | Description |
| - | - | - |
| `ProductId` | `string` | The store product identifier to buy |
| `PlacementId` | `string` | The placement the purchase came from, or null when it came from no named placement |
| `PromoOfferId` | `string` | The offer to apply, when the offer has one. On iOS, an App Store promotional offer. On Android, a Google Play offer ID. The package sends no `offerToken`, so your billing code picks the offer whose base plan is `BasePlanId` and whose offer ID is this one, and buys with that offer's `offerToken` |
| `BasePlanId` | `string` | Android only. The Google Play base plan to buy, when the offer has one |

## Example

Implement the controller over your own billing code:

```csharp GamePurchases.cs theme={null}
using System.Threading.Tasks;
using EncoreKit;

public sealed class GamePurchases : IEncorePurchaseController
{
    public async Task<PurchaseResult> Purchase(PurchaseRequest request)
    {
        // Pass PromoOfferId when it is set: a promotional offer on iOS, a Play offer ID with BasePlanId on Android.
        var outcome = await MyStore.Buy(request.ProductId, request.BasePlanId, request.PromoOfferId);
        if (outcome.Cancelled) return PurchaseResult.Cancelled;
        if (outcome.Deferred) return PurchaseResult.Pending;
        return PurchaseResult.Purchased;
        // Throw for a real failure.
    }
}
```

`MyStore` stands for your own billing code, such as Unity IAP or RevenueCat. Then pass the controller to `Configure` in place of the plain call, and show a Reduce Churn placement:

```csharp theme={null}
// In your bootstrap's Awake():
Encore.Configure("pk_your_api_key_here", new GamePurchases());

// Later, in an async method, when the player turns down your paywall:
var result = await Encore.Placement("paywall_decline").UseCase(UseCase.ReduceChurn).ShowAsync();
```

## Notes

* **Android:** the sheet draws in your game's own window, so Unity keeps running and your controller is called while the sheet is open. If your game sets `com.encorekit.offerSheetInHostWindow` to `false` in its manifest, the sheet opens as its own Activity, Unity pauses, and a purchase runs only after the sheet closes.
* **WebGL:** the web SDK sells nothing, so Encore never calls the controller.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.