← Files WixARCHIVED FILE
skills/wix-headless-templates/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts
2.18 KB · Oct 8, 2026 · 12:02 UTC
// The purchase action as a framework-free store — the logic behind usePlanPurchase, usable from
// React (usePlanPurchase wraps it), from a static page's card or detail CTA, from Vue/Svelte, or
// as the specification for a port. All correctness (the hosted redirect session, member
// login/signup, payment) lives in the data layer; this store tracks the in-flight plan and the
// last failure, and navigates the FULL document to the hosted checkout.
//
// One store per purchase surface (a grid and a detail CTA each key their own spinner):
// createPlanPurchaseStore(), not a singleton. No async work on mount, so no start/stop.
import { purchasePlan, type PurchaseOptions } from "./purchase";
export interface PlanPurchaseState {
/** The plan id a purchase is in flight for (null when idle) — key CTA spinners off it. */
purchasingId: string | null;
/** The last failed purchase's visitor-facing message; a new purchase clears it. */
error: string | null;
}
export interface PlanPurchaseStore {
getState(): PlanPurchaseState;
subscribe(listener: () => void): () => void;
/**
* Starts the hosted checkout — when it resolves the browser is already navigating away, and
* `purchasingId` stays set. Rejects (and records `error`) otherwise — surface it, don't swallow it.
*/
purchase(planId: string, options?: PurchaseOptions): Promise<void>;
}
export function createPlanPurchaseStore(): PlanPurchaseStore {
let state: PlanPurchaseState = { purchasingId: null, error: null };
const listeners = new Set<() => void>();
function setState(patch: Partial<PlanPurchaseState>): void {
state = { ...state, ...patch };
for (const fn of listeners) fn();
}
return {
getState: () => state,
subscribe(fn) {
listeners.add(fn);
return () => listeners.delete(fn);
},
async purchase(planId, options) {
setState({ purchasingId: planId, error: null });
try {
window.location.href = await purchasePlan(planId, options);
// stays "purchasing" — the browser is leaving for the hosted checkout
} catch (e) {
setState({ purchasingId: null, error: e instanceof Error ? e.message : String(e) });
throw e;
}
},
};
}
SHA-256: e9aecd3beb6cc050e0f58b77a541349b9816f1dc75b7968c565d91e89296e83f