> For the complete documentation index, see [llms.txt](https://assetstore.essentialkit.voxelbusters.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://assetstore.essentialkit.voxelbusters.com/features/billing-services/playmaker/use-cases/use-case-2-simple-purchase.md).

# Simple Product Purchase

## Goal

Initialize the billing store and allow users to purchase a product (non-consumable or consumable) with automatic transaction completion.

## Actions Required

| Action                                  | Purpose                                                                |
| --------------------------------------- | ---------------------------------------------------------------------- |
| BillingServicesInitializeStore          | Load product catalog and initialize billing                            |
| BillingServicesGetInitializeStoreError  | Read cached init error after failure (optional)                        |
| BillingServicesCanMakePayments          | Check if user can make purchases                                       |
| BillingServicesBuyProduct               | Initiate purchase flow                                                 |
| BillingServicesGetBuyProductError       | Read cached buy error after failure (optional)                         |
| BillingServicesOnTransactionStateChange | Listen for purchase state changes (persistent)                         |
| BillingServicesGetTransactionInfo       | Read transaction state, verification state, and details after an event |

## Variables Needed

* productId (String) = "premium\_unlock" or "coins\_100"
* canMakePurchase (Bool)
* transactionId (String)
* receiptVerificationState (Enum: BillingReceiptVerificationState)

## Implementation Steps

### 0. State: RegisterTransactionListener (Persistent)

**Action:** BillingServicesOnTransactionStateChange

* Keep this state active (for example in your bootstrap scene or a DontDestroyOnLoad FSM).
* This action is what actually fires purchase lifecycle events (`purchasingEvent`, `purchasedEvent`, `failedEvent`, `deferredEvent`) and caches transactions for `BillingServicesGetTransactionInfo`.

### 1. State: InitializeStore (On App Startup)

**Action:** BillingServicesInitializeStore

* **Events:**
  * successEvent → CheckPaymentCapability
  * failureEvent → ShowError (billing not available)

**Note:** This caches all configured products from Essential Kit settings. **Optional:** On `failureEvent`, call `BillingServicesGetInitializeStoreError` to read `errorCode` + `errorDescription`.

### 2. State: CheckPaymentCapability

**Action:** BillingServicesCanMakePayments

* **Outputs:**
  * result → canMakePurchase variable
* **Transition:**
  * If canMakePurchase = true → ShowPurchaseUI
  * If canMakePurchase = false → ShowRestrictionMessage

### 3. State: ShowPurchaseUI

Display purchase button. When user taps "Buy":

* Go to BuyProduct state

### 4. State: BuyProduct

**Action:** BillingServicesBuyProduct

* **Inputs:**
  * productId: productId
  * quantity: 1
* **Events:**
  * successEvent → WaitForTransactionEvents
  * failureEvent → ShowBuyError

**Important:** `BillingServicesBuyProduct` only dispatches the purchase request. The actual purchase outcome comes from your already-registered `BillingServicesOnTransactionStateChange` listener.

### 5. State: ShowBuyError (immediately on failure)

Call `BillingServicesGetBuyProductError` as soon as you enter this state.

* Enter this state from:
  * `BillingServicesBuyProduct.failureEvent` (dispatch failed: invalid input/store not ready/exception)
  * `BillingServicesOnTransactionStateChange.failedEvent` (store purchase failed/cancelled)

### 6. State: WaitForTransactionEvents

No action needed here (optional). Your `BillingServicesOnTransactionStateChange` state will fire:

* `purchasingEvent` → show processing UI
* `purchasedEvent` → read transaction info, then grant content only if `receiptVerificationState == Success`
* `failedEvent` → go to `ShowBuyError`
* `deferredEvent` → show pending/deferred UI

### 7. Read Transaction Details

After `purchasedEvent` or `failedEvent`, call `BillingServicesGetTransactionInfo` with `transactionIndex = 0` to read details.

* Read `transactionId`, `receipt`, and `receiptVerificationState`.
* Grant content only when:
  * `transactionState == Purchased`
  * `receiptVerificationState == Success`
* If `purchasedEvent` fires but `receiptVerificationState != Success`, treat the transaction as invalid and do not grant content.

**Important:** `BillingReceiptVerificationState` is the result of local verification performed by the plugin by default. If verification fails, the transaction should not be treated as valid even if the state is `Purchased`.

## Purchase Flow

```
RegisterTransactionListener (keep active)

InitializeStore
    └─ CheckPaymentCapability
        └─ BuyProduct
            └─ (events come from BillingServicesOnTransactionStateChange)
                ├─ purchasingEvent → Show Processing
                ├─ purchasedEvent → GetTransactionInfo → Check receiptVerificationState
                │                    ├─ Success → Grant Content
                │                    └─ Failed/NotDetermined → Do Not Grant
                ├─ failedEvent → ShowBuyError (then BillingServicesGetBuyProductError)
                └─ deferredEvent → Show Pending
```

## Common Issues

* **"Cannot connect to store"**: Check internet connection and platform billing setup
* **Purchase Stuck**: Ensure OnTransactionStateChange listener is active before calling BuyProduct
* **Test Purchases**: Use sandbox/test accounts for development
* **AutoFinishTransactions**: If enabled in settings, transactions complete automatically after Purchased state
* **Purchased but verification failed**: Do not grant content. `receiptVerificationState` is the plugin's local validation result and must be `Success`

## Platform Requirements

**iOS:**

* In-app purchases configured in App Store Connect
* Sandbox tester account for testing

**Android:**

* Products configured in Google Play Console
* Use license testing for development builds (and make sure you upload tleast once to play store after enabling billing featute)

## Performance Tip

Initialize store once at app startup and keep products cached. Don't reinitialize on every purchase.
