@zuplo/zudoku-plugin-monetization 0.0.42 → 0.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -265,6 +265,216 @@ const categorizeRateCards = (rateCards, options) => {
265
265
  };
266
266
  };
267
267
  //#endregion
268
+ //#region src/utils/comparePlanEntitlements.ts
269
+ /** Compact, human-readable value for a quota row. */
270
+ const quotaValueLabel = (q) => {
271
+ if (q.unitPrice) return q.unitPrice;
272
+ if (q.tierPrices && q.tierPrices.length > 0) return "Tiered pricing";
273
+ if (q.isPayg) return "Usage-based";
274
+ return `${q.limit.toLocaleString("en-US")} / ${q.period}`;
275
+ };
276
+ const featureValueLabel = (f) => f.value ?? "Included";
277
+ const isPlainNumericQuota = (q) => !q.isPayg && !q.unitPrice && (!q.tierPrices || q.tierPrices.length === 0);
278
+ const sameTierSchedule = (a, b) => (a ?? []).join("\n") === (b ?? []).join("\n");
279
+ const sameQuota = (a, b) => a.name === b.name && a.limit === b.limit && a.period === b.period && a.isPayg === b.isPayg && a.unitPrice === b.unitPrice && sameTierSchedule(a.tierPrices, b.tierPrices);
280
+ const sameFeature = (a, b) => a.name === b.name && a.value === b.value;
281
+ /**
282
+ * Whether two entitlement sets render identically: the same quota and feature
283
+ * keys, each with identical display fields. Order-insensitive (matched by
284
+ * key), so two phases whose rate cards merely differ in order still compare
285
+ * equal. Used to collapse per-phase entitlement lists that would repeat the
286
+ * exact same rows.
287
+ */
288
+ const sameEntitlementSet = (a, b) => {
289
+ if (a.quotas.length !== b.quotas.length || a.features.length !== b.features.length) return false;
290
+ const bQuotas = new Map(b.quotas.map((q) => [q.key, q]));
291
+ const bFeatures = new Map(b.features.map((f) => [f.key, f]));
292
+ return a.quotas.every((q) => {
293
+ const other = bQuotas.get(q.key);
294
+ return other !== void 0 && sameQuota(q, other);
295
+ }) && a.features.every((f) => {
296
+ const other = bFeatures.get(f.key);
297
+ return other !== void 0 && sameFeature(f, other);
298
+ });
299
+ };
300
+ /**
301
+ * Compare two plans' entitlements, matching strictly by feature key (never by
302
+ * display name). Each key yields exactly one change row, so a key that exists
303
+ * on one side and a differently-keyed feature that merely shares a display
304
+ * name can never read as a contradictory "added" + "removed" of the same
305
+ * thing. Labels are disambiguated afterwards when they would collide.
306
+ */
307
+ const comparePlanEntitlements = (current, target) => {
308
+ const changes = [];
309
+ const curQuota = new Map(current.quotas.map((q) => [q.key, q]));
310
+ const tgtQuota = new Map(target.quotas.map((q) => [q.key, q]));
311
+ const curFeat = new Map(current.features.map((f) => [f.key, f]));
312
+ const tgtFeat = new Map(target.features.map((f) => [f.key, f]));
313
+ for (const key of new Set([...curQuota.keys(), ...tgtQuota.keys()])) {
314
+ const c = curQuota.get(key);
315
+ const t = tgtQuota.get(key);
316
+ if (c && t) {
317
+ const currentValue = quotaValueLabel(c);
318
+ const targetValue = quotaValueLabel(t);
319
+ let change = "same";
320
+ if (isPlainNumericQuota(c) && isPlainNumericQuota(t) && c.period === t.period) {
321
+ if (t.limit > c.limit) change = "increase";
322
+ else if (t.limit < c.limit) change = "decrease";
323
+ } else if (currentValue !== targetValue || !sameTierSchedule(c.tierPrices, t.tierPrices)) change = "changed";
324
+ changes.push({
325
+ key,
326
+ label: t.name,
327
+ kind: "quota",
328
+ change,
329
+ currentValue,
330
+ targetValue,
331
+ tierPrices: t.tierPrices,
332
+ period: t.period
333
+ });
334
+ } else if (t) changes.push({
335
+ key,
336
+ label: t.name,
337
+ kind: "quota",
338
+ change: "added",
339
+ targetValue: quotaValueLabel(t),
340
+ tierPrices: t.tierPrices,
341
+ period: t.period
342
+ });
343
+ else if (c) changes.push({
344
+ key,
345
+ label: c.name,
346
+ kind: "quota",
347
+ change: "removed",
348
+ currentValue: quotaValueLabel(c),
349
+ period: c.period
350
+ });
351
+ }
352
+ for (const key of new Set([...curFeat.keys(), ...tgtFeat.keys()])) {
353
+ const c = curFeat.get(key);
354
+ const t = tgtFeat.get(key);
355
+ if (c && t) {
356
+ const currentValue = featureValueLabel(c);
357
+ const targetValue = featureValueLabel(t);
358
+ changes.push({
359
+ key,
360
+ label: t.name,
361
+ kind: "feature",
362
+ change: currentValue === targetValue ? "same" : "changed",
363
+ currentValue,
364
+ targetValue
365
+ });
366
+ } else if (t) changes.push({
367
+ key,
368
+ label: t.name,
369
+ kind: "feature",
370
+ change: "added",
371
+ targetValue: featureValueLabel(t)
372
+ });
373
+ else if (c) changes.push({
374
+ key,
375
+ label: c.name,
376
+ kind: "feature",
377
+ change: "removed",
378
+ currentValue: featureValueLabel(c)
379
+ });
380
+ }
381
+ const labelCounts = /* @__PURE__ */ new Map();
382
+ for (const ch of changes) labelCounts.set(ch.label, (labelCounts.get(ch.label) ?? 0) + 1);
383
+ return changes.map(({ period, ...ch }) => {
384
+ if ((labelCounts.get(ch.label) ?? 0) > 1 && ch.kind === "quota" && period) return {
385
+ ...ch,
386
+ label: `${ch.label} (${period})`
387
+ };
388
+ return ch;
389
+ });
390
+ };
391
+ //#endregion
392
+ //#region src/utils/getPlanPrice.ts
393
+ const sumFlatFeeAmounts = (rateCards) => {
394
+ let total = 0;
395
+ for (const rc of rateCards) if (rc.type === "flat_fee" && rc.price && rc.billingCadence !== null) {
396
+ const amount = Number(rc.price.amount);
397
+ if (Number.isFinite(amount)) total += amount;
398
+ }
399
+ return total;
400
+ };
401
+ /**
402
+ * The plan's headline recurring price: the sum of every recurring `flat_fee`
403
+ * rate-card amount on the plan's steady-state (last) phase, expressed in the
404
+ * plan's own `billingCadence`. One-time fees (`flat_fee` with
405
+ * `billingCadence: null`, e.g. a setup fee) are excluded.
406
+ *
407
+ * This is derived entirely from the plan's rate cards. It deliberately does
408
+ * NOT read any server-provided `monthlyPrice` / `yearlyPrice` and performs no
409
+ * cadence conversion, so it stays correct for any billing cadence — hourly
410
+ * (`PT1H`), weekly, monthly, yearly, etc. Callers pair the returned amount
411
+ * with `formatDuration(plan.billingCadence)` to render e.g. `$2.99/hour`.
412
+ *
413
+ * Returns `0` when there are no phases or no recurring flat fee, which callers
414
+ * render as "Free" (or "Pay as you go" when the plan bills on usage — see
415
+ * {@link formatPlanPrice}).
416
+ */
417
+ const getPlanPrice = (plan) => {
418
+ const lastPhase = plan.phases?.at(-1);
419
+ if (!lastPhase) return 0;
420
+ return sumFlatFeeAmounts(lastPhase.rateCards ?? []);
421
+ };
422
+ //#endregion
423
+ //#region src/utils/formatPlanPrice.ts
424
+ const isPricedUsageRateCard = (rc) => {
425
+ if (rc.type !== "usage_based" || !rc.price) return false;
426
+ const p = rc.price;
427
+ if (p.type === "unit") return parseFloat(p.amount) > 0;
428
+ if (p.type === "tiered") return p.tiers.some(tierHasPositivePrice);
429
+ return true;
430
+ };
431
+ const hasPricedUsageRateCard = (plan) => plan.phases.some((phase) => phase.rateCards.some(isPricedUsageRateCard));
432
+ /**
433
+ * Headline pricing for plan cards. Centralizes the "Pay as you go" detection:
434
+ * plans whose flat-fee total is zero but that bill on usage shouldn't render
435
+ * as "Free" - they're charged per-unit.
436
+ */
437
+ const formatPlanPrice = (plan) => {
438
+ if (!plan.phases || plan.phases.length === 0) return { type: "free" };
439
+ const amount = getPlanPrice(plan);
440
+ if (amount > 0) return {
441
+ type: "priced",
442
+ amount
443
+ };
444
+ if (hasPricedUsageRateCard(plan)) return {
445
+ type: "payg",
446
+ main: "Pay as you go",
447
+ sub: "Usage-based pricing"
448
+ };
449
+ return { type: "free" };
450
+ };
451
+ //#endregion
452
+ //#region src/utils/getPhasePriceLabel.ts
453
+ /**
454
+ * Headline price for a SINGLE phase, derived only from that phase's own rate
455
+ * cards. Mirrors {@link formatPlanPrice}'s rules, but scoped to the phase:
456
+ * a positive recurring flat-fee total is `priced`; otherwise a priced
457
+ * `usage_based` card in this phase makes it `payg`; otherwise it's `free`.
458
+ *
459
+ * Like {@link getPlanPrice}, one-time fees (`flat_fee` with
460
+ * `billingCadence: null`) and `price: null` rate cards contribute nothing —
461
+ * an intro phase whose fees all have `price: null` derives as `free`.
462
+ */
463
+ const getPhasePriceLabel = (phase) => {
464
+ const rateCards = phase.rateCards ?? [];
465
+ const amount = sumFlatFeeAmounts(rateCards);
466
+ if (amount > 0) return {
467
+ type: "priced",
468
+ amount
469
+ };
470
+ if (rateCards.some(isPricedUsageRateCard)) return {
471
+ type: "payg",
472
+ main: "Pay as you go",
473
+ sub: "Usage-based pricing"
474
+ };
475
+ return { type: "free" };
476
+ };
477
+ //#endregion
268
478
  //#region src/pricing-ui/CheckIcon.tsx
269
479
  /**
270
480
  * Inline `Check` icon, visually identical to `lucide-react`'s `CheckIcon`
@@ -372,43 +582,128 @@ const EntitlementList = ({ quotas, features, header, itemClassName }) => {
372
582
  };
373
583
  //#endregion
374
584
  //#region src/pricing-ui/PlanEntitlements.tsx
375
- const PhaseSection = ({ phase, currency, showName, billingCadence, units, itemClassName }) => {
376
- const { quotas, features } = categorizeRateCards(phase.rateCards, {
585
+ const priceLabelText = (label, currency, billingCadence) => {
586
+ if (label.type === "payg") return label.main;
587
+ if (label.type === "free") return "Free";
588
+ const amount = formatPrice(label.amount, currency);
589
+ return billingCadence ? `${amount}/${formatDuration(billingCadence)}` : amount;
590
+ };
591
+ /**
592
+ * Section header for one phase of a multi-phase plan: the phase name, its
593
+ * duration, and the phase's own price. Shared by {@link PlanEntitlements} and
594
+ * the plan-change card so per-phase sections read identically everywhere.
595
+ */
596
+ const PlanPhaseHeader = ({ phase, currency, billingCadence }) => /* @__PURE__ */ jsxs("div", {
597
+ className: "text-sm font-medium text-card-foreground",
598
+ children: [
599
+ phase.name,
600
+ phase.duration && /* @__PURE__ */ jsxs("span", {
601
+ className: "text-muted-foreground font-normal",
602
+ children: [
603
+ " ",
604
+ "— ",
605
+ formatDuration(phase.duration)
606
+ ]
607
+ }),
608
+ /* @__PURE__ */ jsxs("span", {
609
+ className: "text-muted-foreground font-normal",
610
+ children: [
611
+ " ",
612
+ "·",
613
+ " ",
614
+ priceLabelText(getPhasePriceLabel(phase), currency, billingCadence)
615
+ ]
616
+ })
617
+ ]
618
+ });
619
+ const PhaseSection = ({ phase, set, currency, billingCadence, itemClassName }) => /* @__PURE__ */ jsx(EntitlementList, {
620
+ quotas: set.quotas,
621
+ features: set.features,
622
+ itemClassName,
623
+ header: /* @__PURE__ */ jsx(PlanPhaseHeader, {
624
+ phase,
625
+ currency,
626
+ billingCadence
627
+ })
628
+ });
629
+ /**
630
+ * A plan's entitlements, phase by phase. Multi-phase plans whose phases all
631
+ * resolve to the same entitlements collapse into a single list (the phases
632
+ * only differ in price, which the price schedule already tells); phases with
633
+ * genuinely different entitlements render as separate sections headed by the
634
+ * phase name, duration, and that phase's own price.
635
+ */
636
+ const PlanEntitlements = ({ phases, currency, billingCadence, units, itemClassName }) => {
637
+ const sets = phases.map((phase) => categorizeRateCards(phase.rateCards, {
377
638
  currency,
378
639
  units,
379
640
  planBillingCadence: billingCadence
380
- });
381
- return /* @__PURE__ */ jsx(EntitlementList, {
382
- quotas,
383
- features,
384
- itemClassName,
385
- header: showName ? /* @__PURE__ */ jsxs("div", {
386
- className: "text-sm font-medium text-card-foreground",
387
- children: [phase.name, phase.duration && /* @__PURE__ */ jsxs("span", {
388
- className: "text-muted-foreground font-normal",
389
- children: [
390
- " ",
391
- "— ",
392
- formatDuration(phase.duration)
393
- ]
394
- })]
395
- }) : void 0
396
- });
397
- };
398
- const PlanEntitlements = ({ phases, currency, billingCadence, units, itemClassName }) => {
641
+ }));
642
+ if (phases.length <= 1 || sets.every((set) => sameEntitlementSet(set, sets[0]))) {
643
+ const steady = sets.at(-1);
644
+ return /* @__PURE__ */ jsx("div", {
645
+ className: "space-y-4",
646
+ children: steady && /* @__PURE__ */ jsx(EntitlementList, {
647
+ quotas: steady.quotas,
648
+ features: steady.features,
649
+ itemClassName
650
+ })
651
+ });
652
+ }
399
653
  return /* @__PURE__ */ jsx("div", {
400
654
  className: "space-y-4",
401
655
  children: phases.map((phase, idx) => /* @__PURE__ */ jsx(PhaseSection, {
402
656
  phase,
657
+ set: sets[idx],
403
658
  currency,
404
- showName: phases.length > 1,
405
659
  billingCadence,
406
- units,
407
660
  itemClassName
408
661
  }, phase.key ?? String(idx)))
409
662
  });
410
663
  };
411
664
  //#endregion
665
+ //#region src/pricing-ui/PlanPriceSchedule.tsx
666
+ const RowPrice = ({ price, currency, billingCadence, className }) => {
667
+ if (price.type === "priced") return /* @__PURE__ */ jsxs("span", {
668
+ className: cn("font-semibold text-card-foreground", className),
669
+ children: [formatPrice(price.amount, currency), billingCadence && /* @__PURE__ */ jsxs("span", {
670
+ className: "text-muted-foreground font-normal text-sm",
671
+ children: ["/", formatDuration(billingCadence)]
672
+ })]
673
+ });
674
+ return /* @__PURE__ */ jsx("span", {
675
+ className: cn("font-semibold text-card-foreground", className),
676
+ children: price.type === "payg" ? price.main : "Free"
677
+ });
678
+ };
679
+ /**
680
+ * Stacked per-phase price rows for a multi-phase plan, replacing the single
681
+ * headline price (which only reflects the steady-state phase): each row pairs
682
+ * a phase label ("First 3 months", "After that") with that phase's own price.
683
+ * Every row gets equal visual weight — the intro price is part of the plan's
684
+ * price, not a footnote.
685
+ *
686
+ * Callers derive the rows via {@link getPlanPriceSchedule} and fall back to
687
+ * the single-price rendering when it returns `undefined`. `size` picks the
688
+ * typographic treatment: `"lg"` for a card's headline area, `"sm"` for
689
+ * compact contexts (plan-change rows, summary cards).
690
+ */
691
+ const PlanPriceSchedule = ({ schedule, currency, billingCadence, size = "sm", className }) => /* @__PURE__ */ jsx("div", {
692
+ className: cn("space-y-1 text-sm", className),
693
+ children: schedule.map((row) => /* @__PURE__ */ jsxs("div", {
694
+ className: "flex items-baseline justify-between gap-3",
695
+ children: [/* @__PURE__ */ jsx("span", {
696
+ className: "text-muted-foreground",
697
+ children: row.label
698
+ }), /* @__PURE__ */ jsx(RowPrice, {
699
+ price: row.price,
700
+ currency,
701
+ billingCadence,
702
+ className: size === "lg" ? "text-lg" : void 0
703
+ })]
704
+ }, row.key))
705
+ });
706
+ //#endregion
412
707
  //#region src/pricing-ui/PlanPriceTag.tsx
413
708
  /**
414
709
  * Headline price for a plan/subscription: `$X/cadence`, "Pay as you go", or
@@ -445,64 +740,38 @@ const PlanPriceTag = ({ label, currency, billingCadence, description = false, si
445
740
  });
446
741
  };
447
742
  //#endregion
448
- //#region src/utils/getPlanPrice.ts
449
- const sumFlatFeeAmounts = (rateCards) => {
450
- let total = 0;
451
- for (const rc of rateCards) if (rc.type === "flat_fee" && rc.price && rc.billingCadence !== null) {
452
- const amount = Number(rc.price.amount);
453
- if (Number.isFinite(amount)) total += amount;
454
- }
455
- return total;
743
+ //#region src/utils/getPlanPriceSchedule.ts
744
+ const samePriceLabel = (a, b) => {
745
+ if (a.type !== b.type) return false;
746
+ return a.type !== "priced" || b.type !== "priced" || a.amount === b.amount;
456
747
  };
457
- /**
458
- * The plan's headline recurring price: the sum of every recurring `flat_fee`
459
- * rate-card amount on the plan's steady-state (last) phase, expressed in the
460
- * plan's own `billingCadence`. One-time fees (`flat_fee` with
461
- * `billingCadence: null`, e.g. a setup fee) are excluded.
462
- *
463
- * This is derived entirely from the plan's rate cards. It deliberately does
464
- * NOT read any server-provided `monthlyPrice` / `yearlyPrice` and performs no
465
- * cadence conversion, so it stays correct for any billing cadence — hourly
466
- * (`PT1H`), weekly, monthly, yearly, etc. Callers pair the returned amount
467
- * with `formatDuration(plan.billingCadence)` to render e.g. `$2.99/hour`.
468
- *
469
- * Returns `0` when there are no phases or no recurring flat fee, which callers
470
- * render as "Free" (or "Pay as you go" when the plan bills on usage — see
471
- * {@link formatPlanPrice}).
472
- */
473
- const getPlanPrice = (plan) => {
474
- const lastPhase = plan.phases?.at(-1);
475
- if (!lastPhase) return 0;
476
- return sumFlatFeeAmounts(lastPhase.rateCards ?? []);
748
+ const rowLabel = (phase, index, lastIndex) => {
749
+ if (index === lastIndex) return "After that";
750
+ if (!phase.duration) return phase.name;
751
+ const duration = formatDuration(phase.duration);
752
+ return index === 0 ? `First ${duration}` : `Next ${duration}`;
477
753
  };
478
- //#endregion
479
- //#region src/utils/formatPlanPrice.ts
480
- const isPricedUsageRateCard = (rc) => {
481
- if (rc.type !== "usage_based" || !rc.price) return false;
482
- const p = rc.price;
483
- if (p.type === "unit") return parseFloat(p.amount) > 0;
484
- if (p.type === "tiered") return p.tiers.some(tierHasPositivePrice);
485
- return true;
486
- };
487
- const hasPricedUsageRateCard = (plan) => plan.phases.some((phase) => phase.rateCards.some(isPricedUsageRateCard));
488
754
  /**
489
- * Headline pricing for plan cards. Centralizes the "Pay as you go" detection:
490
- * plans whose flat-fee total is zero but that bill on usage shouldn't render
491
- * as "Free" - they're charged per-unit.
755
+ * A stacked price schedule for a multi-phase plan: one row per phase, each
756
+ * priced from its own rate cards (e.g. "First 3 months Free" then
757
+ * "After that — $750/month"). This is how an intro/ramp phase's price gets
758
+ * surfaced instead of only the steady-state price from {@link getPlanPrice}.
759
+ *
760
+ * Returns `undefined` when there is nothing to stack — fewer than two phases,
761
+ * or every phase resolving to the same price label (a free trial into a free
762
+ * plan, two identically-priced phases, …) — so callers fall back to the
763
+ * single-headline rendering.
492
764
  */
493
- const formatPlanPrice = (plan) => {
494
- if (!plan.phases || plan.phases.length === 0) return { type: "free" };
495
- const amount = getPlanPrice(plan);
496
- if (amount > 0) return {
497
- type: "priced",
498
- amount
499
- };
500
- if (hasPricedUsageRateCard(plan)) return {
501
- type: "payg",
502
- main: "Pay as you go",
503
- sub: "Usage-based pricing"
504
- };
505
- return { type: "free" };
765
+ const getPlanPriceSchedule = (plan) => {
766
+ const phases = plan.phases ?? [];
767
+ if (phases.length <= 1) return void 0;
768
+ const prices = phases.map(getPhasePriceLabel);
769
+ if (prices.every((price) => samePriceLabel(price, prices[0]))) return;
770
+ return phases.map((phase, index) => ({
771
+ key: phase.key ?? String(index),
772
+ label: rowLabel(phase, index, phases.length - 1),
773
+ price: prices[index]
774
+ }));
506
775
  };
507
776
  //#endregion
508
777
  //#region src/utils/pricingTaxLegend.ts
@@ -557,6 +826,7 @@ const PricingCard = ({ plan, isPopular = false, units, action, className }) => {
557
826
  const priceLabel = formatPlanPrice(plan);
558
827
  const isCustom = isCustomPlan(plan);
559
828
  const billingInterval = formatDuration(plan.billingCadence);
829
+ const schedule = isCustom ? void 0 : getPlanPriceSchedule(plan);
560
830
  return /* @__PURE__ */ jsxs("div", {
561
831
  className: cn("relative rounded-lg border p-6 flex flex-col", isPopular && "border-primary border-2", className),
562
832
  children: [
@@ -574,7 +844,12 @@ const PricingCard = ({ plan, isPopular = false, units, action, className }) => {
574
844
  className: "text-base font-semibold text-muted-foreground mb-2",
575
845
  children: plan.name
576
846
  }),
577
- /* @__PURE__ */ jsx("div", {
847
+ schedule ? /* @__PURE__ */ jsx(PlanPriceSchedule, {
848
+ schedule,
849
+ currency: plan.currency,
850
+ billingCadence: plan.billingCadence,
851
+ size: "lg"
852
+ }) : /* @__PURE__ */ jsx("div", {
578
853
  className: "flex items-baseline gap-1 flex-wrap",
579
854
  children: isCustom ? /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
580
855
  className: "text-3xl font-bold text-card-foreground",
@@ -660,4 +935,4 @@ const PricingTable = ({ plans, units, renderAction, renderCard, isPopular = (pla
660
935
  })] });
661
936
  };
662
937
  //#endregion
663
- export { formatDurationInterval as S, formatStaticEntitlementConfig as _, planHasDefaultTaxBehavior as a, formatDuration as b, formatPlanPrice as c, PlanEntitlements as d, EntitlementList as f, formatTieredPriceBreakdown as g, categorizeRateCards as h, collectDefaultTaxBehaviors as i, getPlanPrice as l, FeatureItem as m, PricingCard as n, subscriptionTaxLegendSentence as o, QuotaItem as p, isCustomPlan as r, taxBehaviorLegendSentence as s, PricingTable as t, PlanPriceTag as u, formatMinorCurrencyAmount as v, formatDurationAdjective as x, formatPrice as y };
938
+ export { formatStaticEntitlementConfig as C, formatDurationAdjective as D, formatDuration as E, formatDurationInterval as O, formatTieredPriceBreakdown as S, formatPrice as T, formatPlanPrice as _, planHasDefaultTaxBehavior as a, sameEntitlementSet as b, getPlanPriceSchedule as c, PlanEntitlements as d, PlanPhaseHeader as f, getPhasePriceLabel as g, FeatureItem as h, collectDefaultTaxBehaviors as i, PlanPriceTag as l, QuotaItem as m, PricingCard as n, subscriptionTaxLegendSentence as o, EntitlementList as p, isCustomPlan as r, taxBehaviorLegendSentence as s, PricingTable as t, PlanPriceSchedule as u, getPlanPrice as v, formatMinorCurrencyAmount as w, categorizeRateCards as x, comparePlanEntitlements as y };