> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwacks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# In-App Purchases

> Sell shop items for real money through the App Store and Google Play.

A real-money item is a shop item the player pays for in the App Store or Google Play. The store
charges the player; Flock asks the store whether the purchase is real, then delivers the item. Your
game makes the same call it makes for any item: `Shop.PurchaseAsync(item.Id)`.

```
Player taps Buy ──▶ store charges ──▶ Flock checks with the store ──▶ item delivered ──▶ store purchase finished
```

The store's purchase is finished only after Flock delivered, so a crash or a lost connection in
between never loses what the player paid for: the store hands the purchase back at the next launch
and Flock delivers it then. One store purchase is delivered once, however many times it is sent.

> [!NOTE]
>
> Real-money purchases need the Flock SDK for Unity 1.98.0 or later, and Unity IAP 5
> (`com.unity.purchasing` 5.x) in the project. Wallet purchases need neither: see
> [Shops and Inventory](https://docs.qwacks.com/guides/shops-and-inventory.md). On Android, set **Minimum API Level** to 23 or
> higher (**Player Settings > Other Settings**): Unity IAP 5's Google Play Billing library needs it, and
> a Unity 2021.3 project starts at 22, so its build stops with a manifest merger error.

## What you set up

1. **Connect each store to Flock** in the dashboard, with your game's own keys.
2. **Price the items in real money** and give each the id of its product in the store.
3. **Create the products in the store** with those ids.
4. **Connect the store in your game** once at start.
5. **Check the setup** from the game.

**1. Connect the stores to Flock**

Open **Monetization > Payments** in the Flock dashboard and connect each store you sell in. Flock uses
these keys to ask the store about a purchase; they never reach the player's device.

> [tab] Google Play
>
> | Field | What to enter |
> | - | - |
> | Package name | Your app's package name, as in the Google Play Console (e.g. `com.studio.game`) |
> | Service account JSON | The whole text of a Google Cloud service account's JSON key |
>
> To make the service account:
>
> 1. In the Google Cloud console, in any project, turn on the **Google Play Android Developer API**.
> 2. Create a **service account** there, and add a **JSON key** to it. The key downloads as a file.
> 3. In the Google Play Console, under **Users and permissions**, invite the service account's email
>    address, give it your app, and allow **View financial data** and **Manage orders and
>    subscriptions**.
> 4. Paste the JSON file's whole text into **Service account JSON**.
>
> Google can take a while to accept a new service account. Until it does, the setup check below says
> the store refused your keys. In our test it took effect a minute after re-saving one of the app's
> products in the Play Console.

> [tab] App Store
>
> | Field | What to enter |
> | - | - |
> | Issuer ID | From App Store Connect, **Users and Access > Integrations > In-App Purchase** |
> | Key ID | The key you generate on that page |
> | Private key | The whole text of the `.p8` file that key downloads as |
> | Bundle ID | Your app's bundle identifier |

The page also shows a **notification address** for each store. Give it to the store (App Store
Connect's App Store Server Notifications, Google Play's real-time developer notifications) so Flock
hears about refunds.

**2. Price the items in real money**

An item costs real money when its currency is one of the **Real Currencies** in the currency config of
its Game Version. Its price is in the currency's smallest unit: `399` in `SAR` is SAR 3.99.

On each such item, under **Store product IDs**, enter its product id for each store, exactly as the
store has it. The item's type decides what kind of product it is in the store:

| Item type | Store product | What the player gets |
| - | - | - |
| Currency pack | Consumable | Its rewards, straight into the wallet |
| Consumable | Consumable | An inventory entry, used up later with `ConsumeOneAsync` |
| Standard | One-time (non-consumable) | An inventory entry. The store sells it once per store account |

**3. Create the products in the store**

Create each product with the same id: in the Google Play Console under **Monetize > Products > One-time
products**, in App Store Connect under your app's **In-App Purchases**. Activate it. The price the
player sees is the one the store sets for their country.

**4. Connect the store in your game**

Connect once, after the SDK starts, and keep the connection for the whole session. On a phone the
store is found by itself; on Windows, macOS, Linux and WebGL the same call connects a web checkout instead
([Web Payments](https://docs.qwacks.com/guides/web-checkout.md)).

```csharp theme={null}
FlockStore store;

async void Start()
{
    store = await FlockStore.ConnectAsync();

    // Every real-money purchase Flock delivers, the ones PurchaseAsync answers included:
    // refresh what the screen shows here.
    store.PurchaseCompleted += (item, result) => RefreshTheShop();

    // A purchase the player paid for that Flock could not deliver. It comes back at the next launch.
    store.PurchaseNotDelivered += purchase => Debug.LogWarning(purchase.Error.Message);
}

void OnDestroy() => store?.Dispose();
```

From now on `Shop.PurchaseAsync` buys real-money items through this connection. Without one, it
refuses a real-money item before anything is sent, and says to connect the store.

**5. Check the setup**

`CheckSetupAsync` asks the store and Flock's server whether everything is in place, without buying
anything. It needs a signed-in player and throws `FlockAuthException` without one, so run it from a
developer button after signing in, while you set up.

```csharp theme={null}
FlockStoreSetupCheck check = await store.CheckSetupAsync();

if (check.IsReady)
    Debug.Log("The store has every item, and Flock can check purchases with your keys.");
foreach (ShopItem item in check.NotFoundInTheStore)
    Debug.Log($"{item.Name}: the store has no product with this item's id.");
foreach (ShopItem item in check.MissingProductId)
    Debug.Log($"{item.Name}: no product id for this store on the item.");
if (check.ServerProblem != null)
    Debug.Log(check.ServerProblem.Message);   // the keys in Monetization > Payments, and how to fix them
```

## Show the store's price

Show the price the store gives, which is in the player's currency with their taxes, rather than the
item's Flock price.

```csharp theme={null}
FlockStorePrice price = store.GetPrice(item.Id);
string label = price != null ? price.Text : "Not available";   // e.g. "SAR 4.59", "$1.09"
```

`GetPrice` is null when the store did not find the product. The prices in the comment are examples:
the text is the store's own. On a web checkout `GetPrice` gives the item's own price and is never null.

## Buy

```csharp theme={null}
try
{
    PurchaseResult bought = await FlockClient.Instance.Shop.PurchaseAsync(item.Id);

    if (bought.IsPaid)
        ShowReward(bought.Granted);          // delivered
    else
        ShowWaitingForPayment();             // pending: PurchaseCompleted reports it once paid
}
catch (FlockStoreException e) when (e.Reason == FlockStoreFailureReasons.Cancelled)
{
    // The player closed the store's dialog. Nothing was charged.
}
catch (FlockStoreException e) when (e.Reason == FlockStoreFailureReasons.AlreadyOwned)
{
    // This store account already owns the one-time item: offer Restore purchases.
}
catch (FlockStoreException e)
{
    // The store did not take the payment (declined, not available, store down...). Nothing was charged.
    Debug.Log(e.Reason + ": " + e.Message);
}
catch (FlockException e) when (e.OutcomeUnknown)
{
    // No answer said whether Flock delivered. Read the wallet and inventory again.
}
catch (FlockException e)
{
    // The player paid, and Flock refused the purchase (e.ErrorCode says why). The store keeps it,
    // and it is sent again at the next launch. PurchaseNotDelivered reports it too.
    Debug.LogWarning(e.Message);
}
```

Every case, in short:

| What happens | What `PurchaseAsync` does | What the player sees |
| - | - | - |
| Paid and delivered | Returns, `IsPaid` true | The reward |
| The store waits for the payment (a slow way to pay, "Ask to Buy") | Returns, `Status` pending | "Waiting for payment"; `PurchaseCompleted` follows once paid |
| The player cancels | Throws `FlockStoreException`, `Cancelled` | Nothing; nothing charged |
| The payment is declined (App Store) | `FlockStoreException`, `PaymentDeclined` | Try another way to pay; nothing charged |
| A one-time item already owned | `FlockStoreException`, `AlreadyOwned` | Offer **Restore purchases** |
| An earlier purchase of it still waits | `FlockStoreException`, `AlreadyPending` | It arrives through `PurchaseCompleted` |
| The product is not in the store | `FlockStoreException`, `ProductUnavailable` | Check the product id (the setup check lists it) |
| The store is offline or off, or Google Play declined the payment (Google reports both the same way) | `FlockStoreException`, `StoreUnavailable` | Check the connection and the way to pay, then try again; nothing charged |
| Nobody signed in to the store | `FlockStoreException`, `NotSignedInToTheStore` | Sign in to the store, then try again |
| Paid, and Flock refused it | Throws `FlockException` with its code | Delivered at a later launch once fixed |
| No answer from Flock | `FlockException`, `OutcomeUnknown` true | Read the wallet and inventory again |

If you cancel the call's own `CancellationToken`, the store's purchase carries on, and
`PurchaseCompleted` reports it once paid. If the player signs out while the store's dialog is open, the call
ends as cancelled too, and the store's purchase, once paid, is delivered to the player who bought it at a
later sign-in, or through Restore purchases.

## Restore purchases

A player on a new phone, or after a reinstall, gets their one-time items back with **Restore
purchases**. Start it from a button once the player is signed in to Flock: without a signed-in player
`RestoreAsync` throws `FlockAuthException`. On the App Store it also asks the player to sign in to the store.

```csharp theme={null}
FlockStoreRestore restore = await store.RestoreAsync();

foreach (ShopItem item in restore.Restored)
    Debug.Log("Restored " + item.Name);
foreach (FlockUndeliveredPurchase purchase in restore.NotDelivered)
    Debug.Log(purchase.ProductId + ": " + purchase.Error.Message);   // e.g. bought by another Flock player
```

A purchase delivered before grants nothing again. One-time items belong to the Flock player who bought
them, so the player must sign in to that same Flock account (a device sign-in is tied to the device;
link the account to keep purchases across devices). See [Linking Accounts](https://docs.qwacks.com/guides/account-linking.md).

## Purchases finished later

Some purchases end after the call: a payment that clears later, or a purchase interrupted by a crash or
a lost connection. The store hands each back when the game connects to it; once a player is signed in,
Flock delivers it and `PurchaseCompleted` reports it. One Flock could not be reached for is sent again at
the next launch, or at once when the player taps **Restore purchases**. Subscribe to `PurchaseCompleted`
before anything else needs it, and use it to refresh the shop and the wallet.

## Stop selling an item

Set the item, or its shop, to **Inactive** in the dashboard. Players' shop reads leave it out at once,
and `PurchaseAsync` checks with Flock just before the store's dialog, so a game still showing the item
refuses with `ShopItemNotForSale` and the store never charges. A purchase the player already paid for is
still delivered: the game keeps the products it sold, so an unfinished purchase of an inactive item is
delivered at the next launch, and restoring still gives back one-time items.

## Test before you ship

* **In the editor**, the build target decides: with Android or iOS selected, Unity IAP's fake store
  answers. On another build target, name the store to test with:
  `FlockStore.ConnectAsync(new FlockStoreOptions { PaymentProvider = PaymentProviders.GooglePlay })`. The
  setup check works there and tests your keys, but Flock refuses the fake store's purchases, so a
  purchase in the editor ends as not delivered.
* **Google Play**: upload a build to the **internal testing** track, add your testers under
  **License testing** in the Play Console, and install the game from the testing link in the Play Store.
  A tester's purchase is not charged.
* **App Store**: use a **Sandbox** tester from App Store Connect on a device.

## Revenue

Flock records revenue on the server, once per store purchase, at the item's Flock price and currency.
The SDK sends no revenue of its own for a store purchase, and a wallet purchase is never counted as
revenue.

## When something fails

Every failure's `Message` says what went wrong and, on a `Fix:` line, how to fix it. The store
purchase's own codes:

| Code | What happened | What to do |
| - | - | - |
| `PaymentProviderStoreCredentialsRejected` | The store refused your game's keys | Check the keys in **Monetization > Payments**, and the service account's access in the Play Console. The same purchase goes through once fixed |
| `PaymentProviderCredentialsUnreadable` | The server cannot read the stored keys | Enter the keys again |
| `PaymentProviderProviderNotFound` | The store is not connected, or it is paused | Connect it, or turn **Active** back on |
| `PaymentProviderReceiptProductMismatch` | The purchase is for another product than the item's | Check the item's **Store product IDs** |
| `PaymentProviderReceiptBelongsToAnotherPlayer` | Another Flock player bought it, or redeemed it | Sign in as the player who bought it |
| `PaymentProviderReceiptPending` | The store still waits for the payment | Nothing to do: it is sent again at the next launch |
| `PaymentProviderStoreUnavailable` | Flock could not reach the store | Nothing to do: it is sent again at the next launch |
| `PaymentProviderReceiptInvalid` | The store did not confirm the purchase | Read the reason: a fake purchase, a refunded one, or a bad moment at the store |

## Unity IAP 4

A game on Unity IAP 4.x keeps its own purchase code and hands each purchase to Flock with
`Shop.RedeemStoreReceiptAsync`, finishing it in the store only once that returns. The SDK's
`Docs~/recipes/FlockUnityIap4Store.cs` is a ready-made version to copy.


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