Setting the file. One moment.
Plans · Wix Headless Fast · wix/skills · Skills Docs
ContentsBack to the top of the page Reference
references/pricing-plans/app/wix/pricing-plans/ plans.ts
TypeScript · 75 lines · 4 KB
{ PlanDetail, PlanSummary }
from
"./types"
;
11
12 const plans = wixModule (plansV3);
13
14 type Raw = Record < string , any >;
15
16 function formatPrice ( value : string | undefined , currency : string | undefined ) : string {
17 if (value == null || value === "" || Number (value) === 0 ) return "Free" ;
18 try {
19 return new Intl. NumberFormat ( undefined , { style: "currency" , currency: currency || "USD" }). format ( Number (value));
20 } catch {
21 return `${ value } ${ currency ?? ""}` . trim ();
22 }
23 }
24
25 // billingTerms → a display label. billingCycle.count comes back as a STRING in SDK reads
26 // ("1") — Number() it. endType CYCLES_COMPLETED with billingCycleCount 1 bills once.
27 function billingLabel ( terms : Raw | undefined ) : string {
28 const cycle = terms?.billingCycle;
29 if ( ! cycle) return "one-time" ;
30 const cyclesTotal = Number (terms?.cyclesCompletedDetails?.billingCycleCount ?? 0 );
31 if (terms?.endType === "CYCLES_COMPLETED" && cyclesTotal === 1 ) return "one-time" ;
32 const count = Number (cycle.count ?? 1 ) || 1 ;
33 const period = String (cycle.period ?? "MONTH" ). toLowerCase ();
34 const base = count === 1 ? `per ${ period }` : `every ${ count } ${ period }s` ;
35 return terms?.endType === "CYCLES_COMPLETED" && cyclesTotal > 1 ? `${ base } × ${ cyclesTotal }` : base;
36 }
37
38 // The price is DISPLAY-ONLY: a decimal string at pricingVariants[0].pricingStrategies[0]
39 // .flatRate.amount ("0" = free), paired with plan.currency (site-derived — never assume
40 // USD). Wix settles the actual charge, tax, and schedule at the hosted checkout.
41 function toSummary ( raw : Raw ) : PlanSummary {
42 const variant : Raw = raw.pricingVariants?.[ 0 ] ?? {};
43 const amount : string | undefined = variant.pricingStrategies?.[ 0 ]?.flatRate?.amount;
44 const free = amount == null || Number (amount) === 0 ;
45 return {
46 id: raw._id ?? "" , // SDK entity id is _id, never id (that's the REST view)
47 slug: raw.slug ?? "" ,
48 name: raw.name ?? "" ,
49 description: raw.description ?? "" ,
50 price: free ? "Free" : formatPrice (amount, raw.currency),
51 free,
52 billing: free ? "" : billingLabel (variant.billingTerms),
53 freeTrialDays: variant.freeTrialDays || null ,
54 perks: (raw.perks ?? []). map (( p : Raw ) => p.description ?? "" ). filter (( d : string ) => d),
55 buyable: raw.buyable === true ,
56 imageUrl: imgSrc (raw.image, 800 , 800 ),
57 };
58 }
59
60 function toDetail ( raw : Raw ) : PlanDetail {
61 return { ... toSummary (raw), termsAndConditions: raw.termsAndConditions ?? "" };
62 }
63
64 /** List the public plans for the pricing grid, as card-ready DTOs. */
65 export async function fetchPlans ({ limit = 100 } = {}) : Promise < PlanSummary []> {
66 const res = await plans. queryPlans (). eq ( "visibility" , "PUBLIC" ). limit (limit). find ();
67 return (res.items ?? []). map (( p : Raw ) => toSummary (p));
68 }
69
70 /** Fetch one public plan by its URL slug. Null when not found. */
71 export async function fetchPlanBySlug ( slug : string ) : Promise < PlanDetail | null > {
72 const res = await plans. queryPlans (). eq ( "visibility" , "PUBLIC" ). eq ( "slug" , slug). limit ( 1 ). find ();
73 const raw = res.items?.[ 0 ];
74 return raw ? toDetail (raw as Raw ) : null ;
75 }