# Stores and inventory

Verified against the bundled `Rudder.Core.dll` and
`Samples~/StoreInventory/StoreInventorySample.cs`.

## Stores — `client.Stores` (`StoresService`)

```csharp
var stores = await client.Stores.ListAsync();
var response = await client.Stores.PurchaseAsync(storeSlug, offerId);
if (response != null && response.Success != true)
    throw new InvalidOperationException(response.Error ?? "Purchase rejected");
```

- `Task<IReadOnlyList<Store>> ListAsync(CancellationToken = default)`
- `Task<Store> GetAsync(string slug, CancellationToken = default)`
- `Task<PurchaseOfferResponse> PurchaseAsync(string storeSlug, string offerId, string idempotencyKey = null, CancellationToken = default)` — generates an idempotency key when omitted.

Models:

- `Store` — `Id`, `Slug`, `Name`, `Description`, `Status`, `Environment`,
  `ProjectId`, `ScenarioId`, `Data` (`JToken`), `Offers` (`List<Offer>`),
  `CreatedAt`, `UpdatedAt`.
- `Offer` — `Id`, `Name`, `Price` (`OfferPrice`), `Contents`
  (`List<OfferContent>`), `MaxPurchases` (`int?`), `CreatedAt`, `UpdatedAt`.
- `OfferPrice` — `Currency`, `Amount` (`long?`).
- `OfferContent` — `ItemId`, `Amount` (`long?`).
- `PurchaseOfferResponse` — `Success` (`bool?`), `Error`, `PurchaseId`.

Check `Success != true` (nullable bool), not `!Success`. After a purchase,
reload wallets (`Player.GetProfileAsync`) and inventory — there is no
`onChange` callback. Purchases auto-report quest metrics
`purchase.offer:<offerId>` / `purchase.item:<itemId>` (see
[quests.md](quests.md)). The store slug must exist in the dashboard, else 404.

## Inventory — `client.Inventory` (`InventoryService`)

```csharp
var items = await client.Inventory.GetAsync();
```

- `Task<IReadOnlyList<PlayerInventoryItem>> GetAsync(CancellationToken = default)`
- `PlayerInventoryItem` — `Slug`, `Amount` (`long?`), `NameOverride`,
  `PropertiesOverride` (`JToken`), `UpdatedAt`.
