# Stores / purchases — `client.stores` `StoresDomain` (source: `src/domains/StoresDomain.ts`, handles in `src/state/shops.ts`). The observable store list plus the purchase executor. Synced under revision key `stores`. This is the SDK's economy surface — there is no separate economy service. ## Surface ```ts client.stores.data // ShopHandle[] | undefined client.stores.onChange(cb); await client.stores.load() / .reload(); // Direct purchase (bypassing handles): await client.stores.purchase(storeSlug, offerSlug, options?); ``` ## Handles ```ts class ShopHandle { readonly slug: string; readonly name?: string; readonly description?: string; readonly data?: { [key: string]: unknown }; readonly offers: OfferHandle[]; } class OfferHandle { readonly id: string; // authoring id, differs between staging and prod readonly slug: string; // stable across environments — what purchases use readonly name?: string; readonly price?: OfferPrice; // { amount?: number; currency?: string } readonly contents: OfferContent[]; // { amount?: number; itemId?: string }[] readonly maxPurchases?: number; buy(options?: BuyOptions): Promise; } interface BuyOptions { idempotencyKey?: string } interface PurchaseOfferResponse { error?: string; purchaseId?: string; success?: boolean; } ``` ## Purchase semantics - `stores.purchase(storeSlug, offerSlug, options?)` and `offer.buy(options?)` are the same executor; handles just bind the store slug and the offer slug. - When `options.idempotencyKey` is omitted the SDK generates `crypto.randomUUID()` per call — safe retries require passing your own key. - On success the executor invalidates `player`, `inventory`, and `stores`, so subscribers observe fresh wallet/inventory/store data immediately. - Check `response.success` / `response.error` — a failed purchase is a resolved response, not necessarily a thrown error. - Purchase metrics (`purchase.offer:`, `purchase.item:`) are reported to quests automatically server-side — do not report them manually (see `reference/quests.md`). ## Wire types ```ts interface Store { createdAt?: string; data?: { [key: string]: unknown }; description?: string; environment?: string; id?: string; name?: string; offers?: Offer[]; projectId?: string; scenarioId?: string; slug?: string; status?: string; updatedAt?: string; } interface Offer { contents?: OfferContent[]; createdAt?: string; id?: string; maxPurchases?: number; name?: string; price?: OfferPrice; slug?: string; updatedAt?: string; } ``` `ShopHandle`/`OfferHandle` throw a plain `Error` if constructed from a store without `slug` / an offer without `id` or `slug` — in practice the domain only builds handles from server data that has all of them.