2.8 KiB
2.8 KiB
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
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
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?)andoffer.buy(options?)are the same executor; handles just bind the store slug and the offer slug.- When
options.idempotencyKeyis omitted the SDK generatescrypto.randomUUID()per call — safe retries require passing your own key. - On success the executor invalidates
player,inventory, andstores, 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 (seereference/quests.md).
Wire types
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.