83 lines
2.8 KiB
Markdown
83 lines
2.8 KiB
Markdown
# 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<PurchaseOfferResponse>;
|
|
}
|
|
|
|
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:<offerSlug>`, `purchase.item:<itemId>`) 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.
|