15 "js/wix/plan-access-core.js": "// Declarations of plan-access-core.ts: what this file exports, their arguments and what they return.\n// import type { Raw } from \"./plans-core.js\";\n// export declare const ACCESS_ORDER_STATUSES: readonly [\"ACTIVE\"];\n// export interface MemberOrdersQuery {\n// planIds: string[];\n// orderStatuses: readonly (\"ACTIVE\")[];\n// }\n// export declare function memberOrdersQuery(requiredPlanIds: string[]): MemberOrdersQuery;\n// export declare function hasPlanAccess(orders: Raw[] | null | undefined, requiredPlanIds: string[]): boolean;\n/** The one order status that grants access: purchased, started, not paused/ended/canceled. */\nexport const ACCESS_ORDER_STATUSES = [\"ACTIVE\"];\n/** The filter for \"the current member's ACTIVE orders for these plans\"; throws on an empty plan list. */\nexport function memberOrdersQuery(requiredPlanIds) {\n if (!requiredPlanIds.length)\n throw new Error(\"At least one required plan id is needed to check access.\");\n return { planIds: requiredPlanIds, orderStatuses: ACCESS_ORDER_STATUSES };\n}\n/**\n * Access = the member has at least one order with status ACTIVE whose planId is one of the required\n * plans. Re-checked here even though the query already filtered (a caller may pass orders it read\n * for another reason). Visitors never reach this: they have no orders to list.\n */\nexport function hasPlanAccess(orders, requiredPlanIds) {\n return (orders ?? []).some((o) => o?.status === \"ACTIVE\" && requiredPlanIds.includes(o?.planId));\n}\n",
16 "js/wix/plan-access-store.js": "// Declarations of plan-access-store.ts: what this file exports, their arguments and what they return.\n// import { type MemberOrdersCall } from \"./plan-access.js\";\n// export interface PlanAccessSession {\n// loggedIn: boolean;\n// loading: boolean;\n// }\n// export interface PlanAccessStoreOptions {\n// planIds: string[];\n// initialAccess?: boolean;\n// call?: MemberOrdersCall;\n// }\n// export interface PlanAccessState {\n// hasAccess: boolean;\n// loading: boolean;\n// error: string | null;\n// }\n// export interface PlanAccessStore {\n// getState(): PlanAccessState;\n// subscribe(listener: (state: PlanAccessState) => void): () => void;\n// check(session: PlanAccessSession): void;\n// stop(): void;\n// }\n// export declare function createPlanAccessStore({ planIds, initialAccess, call }: PlanAccessStoreOptions): PlanAccessStore;\n// Plan access (paywall) as a framework-free store — the logic behind usePlanAccess, usable from\n// React (usePlanAccess wraps it with useSyncExternalStore), from a static page or Vue/Svelte\n// (subscribe and render), or as the specification for a port. State, subscribe/getState,\n// check/stop, emit after every change.\n//\n// It does not know who is logged in — that is the `members` vertical's session (its store's\n// `{ loggedIn, loading }`), handed in through `check(session)`. The rules: while the session is\n// still loading, so is access; a visitor has no access and NO call is made (Member List Orders is\n// member-only — it would be refused); a member gets one read, re-run only when the session flips.\n// A failed read leaves `hasAccess: false` with the message in `error` — gated content stays closed.\n//\n// SSR-friendly: `initialAccess` (resolved in frontmatter with hasActiveOrderFor under a member\n// session) renders at once with no client call. One store per gated surface: createPlanAccessStore().\n// It imports the data layer by names the REST twin exports identically, so the same file runs over\n// the SDK in Astro and over REST on a static page.\nimport { hasActiveOrderFor } from \"./plan-access.js\";\nexport function createPlanAccessStore({ planIds, initialAccess, call }) {\n let state = { hasAccess: initialAccess ?? false, loading: initialAccess === undefined, error: null };\n let stopped = false;\n /** The loggedIn value the last read ran for — a member is read once, not on every render. */\n let readFor = initialAccess === undefined ? null : true;\n const listeners = new Set();\n function setState(patch) {\n state = { ...state, ...patch };\n for (const fn of listeners)\n fn();\n }\n return {\n getState: () => state,\n subscribe(fn) {\n const listener = () => { try {\n fn(state);\n }\n catch (e) {\n console.error(\"plan-access-store listener failed\", e);\n } };\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n check(session) {\n stopped = false;\n if (session.loading) {\n if (!state.loading)\n setState({ loading: true });\n return;\n }\n if (readFor === session.loggedIn)\n return;\n readFor = session.loggedIn;\n if (!session.loggedIn) {\n setState({ hasAccess: false, loading: false, error: null }); // a visitor: no orders, no call\n return;\n }\n setState({ loading: true, error: null });\n hasActiveOrderFor(planIds, call)\n .then((hasAccess) => { if (!stopped)\n setState({ hasAccess, loading: false }); })\n .catch((e) => { if (!stopped)\n setState({ hasAccess: false, loading: false, error: e instanceof Error ? e.message : String(e) }); });\n },\n stop() {\n stopped = true;\n },\n };\n}\n",
17 "js/wix/plan-access.js": "// Declarations of plan-access.ts: what this file exports, their arguments and what they return.\n// import { type MemberOrdersQuery } from \"./plan-access-core.js\";\n// import type { Raw } from \"./plans-core.js\";\n// export type { MemberOrdersQuery };\n// export type MemberOrdersCall = (query: MemberOrdersQuery) => Promise<Raw>;\n// export declare function hasActiveOrderFor(requiredPlanIds: string[], call?: MemberOrdersCall): Promise<boolean>;\n// Plan access (paywall) over REST — the twin of app/wix/pricing-plans/plan-access.ts. Same export,\n// same signature; the rule comes from plan-access-core (the SAME file the SDK transport uses).\n// Member List Orders is a MEMBER call: it runs with whatever token ./client.js holds — a member's\n// after the members vertical's rest/auth.ts login (`loggedInHint()` true) — and is refused for a\n// visitor, so callers check the session first (the store does) and never call for a visitor.\n// Porting: keep the path and the query string; the rule is one line in the core.\n// docs: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/orders/member-orders-service-list-orders.md\nimport { wixRequest } from \"./client.js\";\nimport { hasPlanAccess, memberOrdersQuery } from \"./plan-access-core.js\";\n/**\n * The current member's orders for the plans, ACTIVE only. Array filters repeat the key.\n * GET /pricing-plans/v2/member/orders?planIds=<id>&planIds=<id>&orderStatuses=ACTIVE → { orders: [{ id, planId, status, … }] }\n */\nconst sharedSeam = (query) => wixRequest(\"/pricing-plans/v2/member/orders\", { method: \"GET\", query: { planIds: query.planIds, orderStatuses: query.orderStatuses } });\n/**\n * True when the current member has an ACTIVE order for one of `requiredPlanIds`. Member-only —\n * throws when the call is refused (a visitor token, an expired session); the store shields that.\n */\nexport async function hasActiveOrderFor(requiredPlanIds, call = sharedSeam) {\n const res = await call(memberOrdersQuery(requiredPlanIds));\n return hasPlanAccess(res?.orders, requiredPlanIds);\n}\n",
18 "js/wix/plan-purchase-store.js": "// Declarations of plan-purchase-store.ts: what this file exports, their arguments and what they return.\n// import { type PurchaseOptions } from \"./purchase.js\";\n// export interface PlanPurchaseState {\n// purchasingId: string | null;\n// error: string | null;\n// }\n// export interface PlanPurchaseStore {\n// getState(): PlanPurchaseState;\n// subscribe(listener: (state: PlanPurchaseState) => void): () => void;\n// purchase(planId: string, options?: PurchaseOptions): Promise<void>;\n// }\n// export declare function createPlanPurchaseStore(): PlanPurchaseStore;\n// The purchase action as a framework-free store — the logic behind usePlanPurchase, usable from\n// React (usePlanPurchase wraps it), from a static page's card or detail CTA, from Vue/Svelte, or\n// as the specification for a port. All correctness (the hosted redirect session, member\n// login/signup, payment) lives in the data layer; this store tracks the in-flight plan and the\n// last failure, and navigates the FULL document to the hosted checkout.\n//\n// One store per purchase surface (a grid and a detail CTA each key their own spinner):\n// createPlanPurchaseStore(), not a singleton. No async work on mount, so no start/stop.\nimport { purchasePlan } from \"./purchase.js\";\nexport function createPlanPurchaseStore() {\n let state = { purchasingId: null, error: null };\n const listeners = new Set();\n function setState(patch) {\n state = { ...state, ...patch };\n for (const fn of listeners)\n fn();\n }\n return {\n getState: () => state,\n subscribe(fn) {\n const listener = () => { try {\n fn(state);\n }\n catch (e) {\n console.error(\"plan-purchase-store listener failed\", e);\n } };\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async purchase(planId, options) {\n setState({ purchasingId: planId, error: null });\n try {\n window.location.href = await purchasePlan(planId, options);\n // stays \"purchasing\" — the browser is leaving for the hosted checkout\n }\n catch (e) {\n setState({ purchasingId: null, error: e instanceof Error ? e.message : String(e) });\n throw e;\n }\n },\n };\n}\n",
19 "js/wix/plans-core.js": "// Declarations of plans-core.ts: what this file exports, their arguments and what they return.\n// import type { PlanDetail, PlanFee, PlanSummary } from \"./pricing-plans-types.js\";\n// export type Raw = Record<string, any>;\n// export type ImgSrc = (value: any, width: number, height: number) => string;\n// export interface PlansQueryOptions {\n// limit?: number;\n// slug?: string;\n// ids?: string[];\n// }\n// export declare function plansQuery({ limit, slug, ids }?: PlansQueryOptions): Raw;\n// export interface FormatOptions {\n// locale?: string;\n// }\n// export declare function formatAmount(value: string | number | undefined, currency: string | undefined, locale?: string): string;\n// export declare function formatPrice(value: string | number, currency: string | undefined, locale?: string): string;\n// export declare function planAmount(raw: Raw): string | undefined;\n// export declare function planFees(variant: Raw | undefined, currency: string | undefined, locale?: string): PlanFee[];\n// export declare function isRecurring(terms: Raw | undefined): boolean;\n// export declare function billingLabel(terms: Raw | undefined): string;\n// export declare function durationLabel(terms: Raw | undefined): string | null;\n// export declare const PLAN_IMAGE_WIDTH = 1200;\n// export declare const PLAN_IMAGE_HEIGHT = 675;\n// export declare const PLAN_IMAGE_RATIO: number;\n// export declare function planImage(raw: Raw): string;\n// export declare function toSummary(raw: Raw, imgSrc: ImgSrc, { locale }?: FormatOptions): PlanSummary | null;\n// export declare function toSummaries(raws: Raw[], imgSrc: ImgSrc, options?: FormatOptions): PlanSummary[];\n// export declare function toDetail(raw: Raw, imgSrc: ImgSrc, options?: FormatOptions): PlanDetail | null;\nconst id = (raw) => raw?._id ?? raw?.id ?? \"\";\n/**\n * The Query Plans body both transports send: only PUBLIC plans (a HIDDEN plan is merchant-assigned\n * and never listed), cursor paging, the slug when one plan is wanted, `id: { $in }` for a set of\n * plans. REST filters on `id`; the SDK builder spells the same filters as `.eq(\"visibility\",\n * \"PUBLIC\")`, `.eq(\"slug\", slug)`, `.in(\"_id\", ids)`.\n */\nexport function plansQuery({ limit = 100, slug, ids } = {}) {\n if (!Number.isInteger(limit) || limit < 1 || limit > 100)\n throw new Error(\"limit must be between 1 and 100.\");\n const filter = { visibility: \"PUBLIC\" };\n if (slug)\n filter.slug = slug;\n if (ids) {\n if (!ids.length)\n throw new Error(\"At least one plan id is required.\");\n filter.id = { $in: ids };\n }\n return { filter, cursorPaging: { limit } };\n}\n/**\n * A decimal-string amount → \"€25.00\" in the plan's currency; \"\" when the value isn't a finite\n * number (an absent fee amount). Never assumes USD: the currency is the plan's, site-derived.\n */\nexport function formatAmount(value, currency, locale) {\n if (value == null || value === \"\")\n return \"\";\n const n = Number(value);\n if (!Number.isFinite(n))\n return \"\";\n // No currency → a bare number, never an assumed \"$\": the plan's currency is site-derived and always sent.\n if (!currency)\n return new Intl.NumberFormat(locale).format(n);\n try {\n return new Intl.NumberFormat(locale, { style: \"currency\", currency }).format(n);\n }\n catch {\n return `${value} ${currency}`;\n }\n}\n/** \"€29.00\" for the plan's currency; \"Free\" for a zero amount. Callers pass a valid amount — see `planAmount`. */\nexport function formatPrice(value, currency, locale) {\n return Number(value) === 0 ? \"Free\" : formatAmount(value, currency, locale);\n}\n/**\n * The plan's amount — `pricingVariants[0].pricingStrategies[0].flatRate.amount`, a decimal string\n * (\"0\" = free) — or undefined for a MALFORMED plan: no pricing variant, or an amount that isn't a\n * number. A malformed plan is dropped by the mappers (never rendered \"Free\" with a live CTA): the\n * hosted checkout would charge something the card didn't show.\n */\nexport function planAmount(raw) {\n const variant = raw.pricingVariants?.[0];\n const amount = variant?.pricingStrategies?.[0]?.flatRate?.amount;\n if (variant == null || amount == null || amount === \"\" || !Number.isFinite(Number(amount)))\n return undefined;\n return String(amount);\n}\n/**\n * Additional fees — `pricingVariants[0].fees[]`, each `{ name, fixedAmountOptions: { amount } }`,\n * charged with the first payment (appliedAt FIRST_PAYMENT is the only value). Formatted in the\n * plan's currency; a fee without a numeric amount is skipped.\n */\nexport function planFees(variant, currency, locale) {\n return (variant?.fees ?? [])\n .map((f) => ({ name: String(f.name ?? \"\"), amount: formatAmount(f.fixedAmountOptions?.amount, currency, locale) }))\n .filter((f) => f.amount);\n}\n// ---- billing rules --------------------------------------------------------------------------------\n/**\n * Recurring vs one-time, from billingTerms: endType UNTIL_CANCELLED with a billingCycle recurs;\n * CYCLES_COMPLETED recurs unless billingCycleCount is 1 (bill once, then end); no cycle never recurs.\n * billingCycleCount is a STRING on both transports (\"1\") — compare as a string.\n */\nexport function isRecurring(terms) {\n if (!terms?.billingCycle)\n return false;\n if (terms.endType === \"UNTIL_CANCELLED\")\n return true;\n if (terms.endType === \"CYCLES_COMPLETED\")\n return String(terms.cyclesCompletedDetails?.billingCycleCount) !== \"1\";\n return false;\n}\nconst periodLabel = (cycle, total) => {\n const period = String(cycle.period ?? \"MONTH\").toLowerCase();\n return `${total} ${period}${total === 1 ? \"\" : \"s\"}`;\n};\n/**\n * billingTerms → the CADENCE only: \"per month\", \"every 3 months\", \"one-time\". How long the plan\n * lasts is `durationLabel`, a separate value — \"per month × 6\" folded both into one string and read\n * wrong for a one-time plan with a cycle. billingCycle.count is a STRING (\"1\") — Number() it.\n */\nexport function billingLabel(terms) {\n if (!isRecurring(terms))\n return \"one-time\";\n const cycle = terms.billingCycle;\n const count = Number(cycle.count ?? 1) || 1;\n const period = String(cycle.period ?? \"MONTH\").toLowerCase();\n return count === 1 ? `per ${period}` : `every ${count} ${period}s`;\n}\n/**\n * billingTerms → how long the plan stays valid, or null when it runs until canceled (endType\n * UNTIL_CANCELLED, or no terms/cycle at all). Recurring for N cycles → count × N (\"6 months\" for a\n * monthly plan over 6 cycles); one-time with a cycle → that cycle (\"1 month\").\n */\nexport function durationLabel(terms) {\n const cycle = terms?.billingCycle;\n if (!terms || !cycle || terms.endType === \"UNTIL_CANCELLED\")\n return null;\n const count = Number(cycle.count ?? 1) || 1;\n const cycles = isRecurring(terms) ? Number(terms.cyclesCompletedDetails?.billingCycleCount ?? 1) || 1 : 1;\n return periodLabel(cycle, count * cycles);\n}\n// ---- image ----------------------------------------------------------------------------------------\n/** The size every plan image resolves at — landscape, matching the 16:9 band the detail page shows. */\nexport const PLAN_IMAGE_WIDTH = 1200;\nexport const PLAN_IMAGE_HEIGHT = 675;\n/** height / width — pass to imgAttrs(plan.imageUrl, sizes, PLAN_IMAGE_RATIO) so srcset candidates keep the aspect. */\nexport const PLAN_IMAGE_RATIO = PLAN_IMAGE_HEIGHT / PLAN_IMAGE_WIDTH;\n/**\n * The plan's image as a value imgSrc resolves: the SDK hands a `wix:image://…` string; REST hands\n * an Image object { id, url } — its url when present, else the media id in the wix:image form the\n * URL builder scales. \"\" when the plan has no image.\n */\nexport function planImage(raw) {\n const img = raw.image;\n if (!img)\n return \"\";\n if (typeof img === \"string\")\n return img;\n if (img.url)\n return String(img.url);\n return img.id ? `wix:image://v1/${img.id}/${img.id}` : \"\";\n}\n// ---- DTO mappers -----------------------------------------------------------------------------------\n/**\n * The price is DISPLAY-ONLY: a decimal string at pricingVariants[0].pricingStrategies[0]\n * .flatRate.amount (\"0\" = free), paired with plan.currency (site-derived); fees likewise. Wix\n * settles the actual charge, tax, and schedule at the hosted checkout. Null for a malformed plan\n * (see `planAmount`) — callers drop it (`toSummaries`) or treat it as not found.\n */\nexport function toSummary(raw, imgSrc, { locale } = {}) {\n const amount = planAmount(raw);\n if (amount === undefined)\n return null;\n const variant = raw.pricingVariants[0];\n const free = Number(amount) === 0;\n return {\n id: id(raw),\n slug: raw.slug ?? \"\",\n name: raw.name ?? \"\",\n description: raw.description ?? \"\",\n pricingVariantId: id(variant),\n price: formatPrice(amount, raw.currency, locale),\n free,\n billing: free ? \"\" : billingLabel(variant.billingTerms),\n duration: durationLabel(variant.billingTerms),\n fees: planFees(variant, raw.currency, locale),\n freeTrialDays: variant.freeTrialDays || null,\n perks: (raw.perks ?? []).map((p) => p.description ?? \"\").filter((d) => d),\n buyable: raw.buyable === true, // a PUBLIC plan can still be merchant-assigned — no CTA then\n imageUrl: imgSrc(planImage(raw), PLAN_IMAGE_WIDTH, PLAN_IMAGE_HEIGHT),\n };\n}\n/** A list of raw plans → cards, with every malformed plan dropped (one console.warn each, so the grid still renders). */\nexport function toSummaries(raws, imgSrc, options = {}) {\n const out = [];\n for (const raw of raws) {\n const summary = toSummary(raw, imgSrc, options);\n if (summary)\n out.push(summary);\n else\n console.warn(`Pricing plan \"${raw?.name ?? id(raw)}\" has no numeric price — not shown.`);\n }\n return out;\n}\nexport function toDetail(raw, imgSrc, options = {}) {\n const summary = toSummary(raw, imgSrc, options);\n return summary ? { ...summary, termsAndConditions: raw.termsAndConditions ?? \"\" } : null;\n}\n",
20 "js/wix/plans-store.js": "// Declarations of plans-store.ts: what this file exports, their arguments and what they return.\n// import type { PlanSummary } from \"./pricing-plans-types.js\";\n// export interface PlansStoreOptions {\n// initialPlans?: PlanSummary[];\n// }\n// export interface PlansState {\n// plans: PlanSummary[] | null;\n// error: string | null;\n// }\n// export interface PlansStore {\n// getState(): PlansState;\n// subscribe(listener: (state: PlansState) => void): () => void;\n// start(): void;\n// stop(): void;\n// }\n// export declare function createPlansStore({ initialPlans }?: PlansStoreOptions): PlansStore;\n// The plans listing as a framework-free store — the logic behind usePlans, usable from React\n// (usePlans wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and\n// render), or as the specification for a port. State, subscribe/getState, start/stop, emit after\n// every change.\n//\n// SSR-friendly: seed with `initialPlans` and they render at once with no client fetch; without a\n// seed `start()` fetches the public plans. A failed load leaves `plans: []` with the message in\n// `error`, so the surface renders its empty state and the message, never a skeleton forever.\n//\n// One store per mounted listing (a pricing page and a home-page strip can coexist): createPlansStore(),\n// not a singleton. It imports the data layer by names the REST twin exports identically, so the\n// same file runs over the SDK in Astro and over REST on a static page.\nimport { fetchPlans } from \"./plans.js\";\nexport function createPlansStore({ initialPlans } = {}) {\n let state = { plans: initialPlans ?? null, error: null };\n let started = false;\n const listeners = new Set();\n function setState(patch) {\n state = { ...state, ...patch };\n for (const fn of listeners)\n fn();\n }\n return {\n getState: () => state,\n subscribe(fn) {\n const listener = () => { try {\n fn(state);\n }\n catch (e) {\n console.error(\"plans-store listener failed\", e);\n } };\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n start() {\n if (started)\n return;\n started = true;\n if (initialPlans)\n return;\n fetchPlans()\n .then((plans) => { if (started)\n setState({ plans }); })\n .catch((e) => { if (started)\n setState({ plans: [], error: e instanceof Error ? e.message : String(e) }); });\n },\n stop() {\n started = false;\n },\n };\n}\n",
21 "js/wix/plans.js": "// Declarations of plans.ts: what this file exports, their arguments and what they return.\n// import { type FormatOptions } from \"./plans-core.js\";\n// import type { PlanDetail, PlanSummary } from \"./pricing-plans-types.js\";\n// export type { FormatOptions };\n// export interface FetchPlansOptions extends FormatOptions {\n// limit?: number;\n// }\n// export declare function fetchPlans({ limit, locale }?: FetchPlansOptions): Promise<PlanSummary[]>;\n// export declare function fetchPlansByIds(ids: string[], { locale }?: FormatOptions): Promise<PlanSummary[]>;\n// export declare function fetchPlanBySlug(slug: string, { locale }?: FormatOptions): Promise<PlanDetail | null>;\n// export declare function fetchPlanById(planId: string, { locale }?: FormatOptions): Promise<PlanDetail | null>;\n// Plan reads over REST — the twin of app/wix/pricing-plans/plans.ts. Same exports, same DTOs; the\n// rules and mappers come from plans-core (the SAME file the SDK transport uses, deployed flat next\n// to this one by deploy.mjs --stack static), so this file is only the transport: one fetch with a\n// literal body per function. Every call here is safe from a browser with a visitor token.\n// A plan without a numeric price is malformed and never returned (plans-core `planAmount`).\n// Porting: keep the path, keep the body, port the core once.\n// docs: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/plans-v3/query-plans.md\nimport { wixRequest } from \"./client.js\";\nimport { imgSrc } from \"./media.js\";\nimport { plansQuery, toDetail, toSummaries } from \"./plans-core.js\";\nconst QUERY = \"/pricing-plans/v3/plans/query\";\n/**\n * The public plans for the pricing grid, as card-ready DTOs.\n * POST /pricing-plans/v3/plans/query { query: { filter: { visibility: \"PUBLIC\" }, cursorPaging: { limit } } }\n */\nexport async function fetchPlans({ limit = 100, locale } = {}) {\n const res = await wixRequest(QUERY, { body: { query: plansQuery({ limit }) } });\n return toSummaries((res?.plans ?? []), imgSrc, { locale });\n}\n/**\n * Specific public plans by id, in the API's order; unknown ids are simply absent.\n * POST /pricing-plans/v3/plans/query { query: { filter: { visibility: \"PUBLIC\", id: { $in: ids } }, cursorPaging: { limit } } }\n */\nexport async function fetchPlansByIds(ids, { locale } = {}) {\n const res = await wixRequest(QUERY, { body: { query: plansQuery({ ids, limit: Math.min(Math.max(ids.length, 1), 100) }) } });\n return toSummaries((res?.plans ?? []), imgSrc, { locale });\n}\n/**\n * One public plan by its URL slug; null when the slug doesn't resolve (an empty `plans`, not a 404).\n * POST /pricing-plans/v3/plans/query { query: { filter: { visibility: \"PUBLIC\", slug }, cursorPaging: { limit: 1 } } }\n */\nexport async function fetchPlanBySlug(slug, { locale } = {}) {\n const res = await wixRequest(QUERY, { body: { query: plansQuery({ limit: 1, slug }) } });\n const raw = res?.plans?.[0];\n return raw ? toDetail(raw, imgSrc, { locale }) : null;\n}\n/**\n * One public plan by id; null when not found.\n * POST /pricing-plans/v3/plans/query { query: { filter: { visibility: \"PUBLIC\", id: { $in: [planId] } }, cursorPaging: { limit: 1 } } }\n */\nexport async function fetchPlanById(planId, { locale } = {}) {\n const res = await wixRequest(QUERY, { body: { query: plansQuery({ limit: 1, ids: [planId] }) } });\n const raw = res?.plans?.[0];\n return raw ? toDetail(raw, imgSrc, { locale }) : null;\n}\n",
22 "js/wix/pricing-plans-types.js": "// Declarations of pricing-plans-types.ts: what this file exports, their arguments and what they return.\n// /** An additional fee on a plan (a setup fee), charged with the first payment. */\n// export interface PlanFee {\n// /** The merchant's label, e.g. \"Setup fee\". */\n// name: string;\n// /** Formatted amount in the plan's currency (e.g. \"€25.00\"). */\n// amount: string;\n// }\n// /** A plan as a pricing-grid card needs it. */\n// export interface PlanSummary {\n// id: string;\n// slug: string;\n// name: string;\n// description: string;\n// /**\n// * The pricing variant the price belongs to (`pricingVariants[0]`). Not needed by the shipped\n// * purchase flow (paidPlansCheckout takes the plan id); it is the key an eCom checkout line item\n// * needs (`planOptions: { pricingVariantId }`) when a plan must share a checkout with products.\n// */\n// pricingVariantId: string;\n// /** Formatted price (e.g. \"$29.00\"); \"Free\" when the plan costs nothing. */\n// price: string;\n// free: boolean;\n// /** Display-ready cadence only: \"per month\", \"every 3 months\", \"one-time\"; \"\" for free plans. */\n// billing: string;\n// /**\n// * How long the plan stays valid after purchase, display-ready (\"6 months\", \"1 year\");\n// * null when it runs until canceled. Independent of `billing`: a plan can bill \"per month\"\n// * for \"6 months\", or bill \"one-time\" and be valid \"1 month\".\n// */\n// duration: string | null;\n// /** Additional fees charged with the first payment, formatted — show them with the price, never hide them. [] when none. */\n// fees: PlanFee[];\n// /** Free-trial length in days (null when the plan has none). */\n// freeTrialDays: number | null;\n// /** Display-only feature bullets, in order. */\n// perks: string[];\n// /** False → a PUBLIC but merchant-assigned plan: show it without a subscribe CTA. */\n// buyable: boolean;\n// /** Resolved https URL at 16:9 (\"\" when the plan has no image). */\n// imageUrl: string;\n// }\n// /** A plan as the detail page needs it. */\n// export interface PlanDetail extends PlanSummary {\n// /** Terms & conditions as plain text (\"\" when not set) — render as-is (pre-wrap). */\n// termsAndConditions: string;\n// }\n// Pricing Plans DTOs — the serializable shapes every hook, component, and page consumes.\n// Plain JSON: safe as Astro island props or across server/client boundaries. Images are\n// resolved https URLs; every displayable price is a ready formatted string.\nexport {};\n",
23 "js/wix/purchase-core.js": "// Declarations of purchase-core.ts: what this file exports, their arguments and what they return.\n// import type { Raw } from \"./plans-core.js\";\n// export interface PurchaseOptions {\n// thankYouPageUrl?: string;\n// postFlowUrl?: string;\n// }\n// export declare function purchaseBody(planId: string, { thankYouPageUrl, postFlowUrl }?: PurchaseOptions): Raw;\n// export declare function redirectUrl(session: Raw | null | undefined): string;\n/**\n * The Create Redirect Session body for a plan: `paidPlansCheckout: { planId }` plus the callbacks.\n * postFlowUrl defaults to the current page (browser); a server caller passes it explicitly.\n * The return origin must be the site's real https origin as registered on the OAuth app's allowed\n * domains — an unlisted or http origin 403s on return.\n */\nexport function purchaseBody(planId, { thankYouPageUrl, postFlowUrl } = {}) {\n if (!planId)\n throw new Error(\"A plan id is required to start checkout.\");\n const href = typeof window !== \"undefined\" ? window.location.href : \"\";\n return {\n paidPlansCheckout: { planId },\n callbacks: {\n postFlowUrl: postFlowUrl ?? (href || undefined),\n ...(thankYouPageUrl ? { thankYouPageUrl } : {}),\n },\n };\n}\n/** The hosted checkout URL out of a redirect-session response; throws when there is none. */\nexport function redirectUrl(session) {\n const url = session?.redirectSession?.fullUrl;\n if (!url)\n throw new Error(\"Checkout couldn't start — please try again.\");\n return url;\n}\n",
24 "js/wix/purchase.js": "// Declarations of purchase.ts: what this file exports, their arguments and what they return.\n// import { type PurchaseOptions } from \"./purchase-core.js\";\n// export type { PurchaseOptions };\n// export declare function purchasePlan(planId: string, options?: PurchaseOptions): Promise<string>;\n// The purchase flow over REST — the twin of app/wix/pricing-plans/purchase.ts. Same export, same\n// signature; the body and the result reading come from purchase-core (the SAME file the SDK\n// transport uses). Purchasing is a MEMBER action, but the hosted flow handles login/signup, the\n// order form, and payment itself, so a visitor token starts it. Never create the order yourself,\n// never hand-build a checkout URL.\n// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md\nimport { wixRequest } from \"./client.js\";\nimport { purchaseBody, redirectUrl } from \"./purchase-core.js\";\n/**\n * Start the hosted purchase for a plan; resolves to the URL to navigate the FULL document to.\n * POST /headless/v1/redirect-session { paidPlansCheckout: { planId }, callbacks: { postFlowUrl, thankYouPageUrl? } }\n */\nexport async function purchasePlan(planId, options = {}) {\n const session = await wixRequest(\"/headless/v1/redirect-session\", { body: purchaseBody(planId, options) });\n return redirectUrl(session);\n}\n"