@zuplo/zudoku-plugin-monetization 0.0.27 → 0.0.28
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.
- package/dist/index.mjs +109 -87
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -78,6 +78,16 @@ const useDeploymentName = () => {
|
|
|
78
78
|
return deploymentName;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/hooks/usePurchaseDetails.ts
|
|
83
|
+
const usePurchaseDetails = (planId) => {
|
|
84
|
+
const zudoku = useZudoku();
|
|
85
|
+
return useSuspenseQuery({
|
|
86
|
+
queryKey: [`/v3/zudoku-metering/${useDeploymentName()}/plans/${planId}/purchase-details`],
|
|
87
|
+
meta: { context: zudoku }
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
81
91
|
//#endregion
|
|
82
92
|
//#region src/MonetizationContext.tsx
|
|
83
93
|
const MonetizationContext = createContext({});
|
|
@@ -215,6 +225,24 @@ const getPriceFromPlan = (plan) => {
|
|
|
215
225
|
};
|
|
216
226
|
};
|
|
217
227
|
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/utils/purchaseDetails.ts
|
|
230
|
+
const getPlanFromPurchaseDetails = (response) => {
|
|
231
|
+
return response;
|
|
232
|
+
};
|
|
233
|
+
const getTaxAmountFromPurchaseDetails = (response) => {
|
|
234
|
+
const taxAmount = response?.tax?.taxAmount;
|
|
235
|
+
const numericAmount = typeof taxAmount === "number" ? taxAmount : Number.parseFloat(taxAmount ?? "");
|
|
236
|
+
if (!Number.isFinite(numericAmount)) return;
|
|
237
|
+
return numericAmount;
|
|
238
|
+
};
|
|
239
|
+
const getTaxLabelFromPurchaseDetails = (response) => {
|
|
240
|
+
return (response.tax?.taxes ?? []).some((tax) => tax.taxType?.toLowerCase() === "vat") ? "VAT" : "tax";
|
|
241
|
+
};
|
|
242
|
+
const isTaxInclusiveFromPurchaseDetails = (response) => {
|
|
243
|
+
return response.tax?.taxInclusive === true;
|
|
244
|
+
};
|
|
245
|
+
|
|
218
246
|
//#endregion
|
|
219
247
|
//#region src/ZuploMonetizationWrapper.tsx
|
|
220
248
|
const DEFAULT_GATEWAY_URL = "https://api.zuploedge.com";
|
|
@@ -289,15 +317,6 @@ const ZuploMonetizationWrapper = ({ options = {} }) => /* @__PURE__ */ jsx(Query
|
|
|
289
317
|
|
|
290
318
|
//#endregion
|
|
291
319
|
//#region src/pages/CheckoutConfirmPage.tsx
|
|
292
|
-
const getPlanFromPurchaseDetails = (response) => {
|
|
293
|
-
return "plan" in response ? response.plan : response;
|
|
294
|
-
};
|
|
295
|
-
const getTaxAmountFromPurchaseDetails = (response) => {
|
|
296
|
-
const taxAmount = response?.tax?.amount;
|
|
297
|
-
const numericAmount = typeof taxAmount === "number" ? taxAmount : Number.parseFloat(taxAmount ?? "");
|
|
298
|
-
if (!Number.isFinite(numericAmount)) return;
|
|
299
|
-
return numericAmount;
|
|
300
|
-
};
|
|
301
320
|
const CheckoutConfirmPage = () => {
|
|
302
321
|
const [search] = useSearchParams();
|
|
303
322
|
const planId = search.get("planId");
|
|
@@ -306,12 +325,11 @@ const CheckoutConfirmPage = () => {
|
|
|
306
325
|
const navigate = useNavigate();
|
|
307
326
|
const { pricing } = useMonetizationConfig();
|
|
308
327
|
if (!planId) throw new Error("Parameter `planId` missing");
|
|
309
|
-
const purchaseDetails =
|
|
310
|
-
queryKey: [`/v3/zudoku-metering/${deploymentName}/plans/${planId}/purchase-details`],
|
|
311
|
-
meta: { context: zudoku }
|
|
312
|
-
});
|
|
328
|
+
const purchaseDetails = usePurchaseDetails(planId);
|
|
313
329
|
const selectedPlan = getPlanFromPurchaseDetails(purchaseDetails.data);
|
|
314
330
|
const taxAmount = getTaxAmountFromPurchaseDetails(purchaseDetails.data);
|
|
331
|
+
const taxLabel = getTaxLabelFromPurchaseDetails(purchaseDetails.data);
|
|
332
|
+
const taxInclusive = isTaxInclusiveFromPurchaseDetails(purchaseDetails.data);
|
|
315
333
|
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
316
334
|
const { quotas, features } = categorizeRateCards(rateCards ?? [], {
|
|
317
335
|
currency: selectedPlan?.currency,
|
|
@@ -396,13 +414,9 @@ const CheckoutConfirmPage = () => {
|
|
|
396
414
|
className: "text-sm text-muted-foreground font-normal",
|
|
397
415
|
children: ["Billed ", formatBillingCycle(billingCycle)]
|
|
398
416
|
}),
|
|
399
|
-
taxAmount != null && /* @__PURE__ */
|
|
417
|
+
taxAmount != null && /* @__PURE__ */ jsx("div", {
|
|
400
418
|
className: "text-xs text-muted-foreground font-normal mt-1",
|
|
401
|
-
children:
|
|
402
|
-
"+ ",
|
|
403
|
-
formatPrice(taxAmount, selectedPlan?.currency),
|
|
404
|
-
" VAT"
|
|
405
|
-
]
|
|
419
|
+
children: taxInclusive ? `${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel} included` : `+ ${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel}`
|
|
406
420
|
})
|
|
407
421
|
]
|
|
408
422
|
}),
|
|
@@ -787,14 +801,18 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
787
801
|
const [search] = useSearchParams();
|
|
788
802
|
const planId = search.get("planId");
|
|
789
803
|
const subscriptionId = search.get("subscriptionId");
|
|
804
|
+
const mode = search.get("mode");
|
|
790
805
|
const zudoku = useZudoku();
|
|
791
806
|
const deploymentName = useDeploymentName();
|
|
792
807
|
const navigate = useNavigate();
|
|
793
|
-
const { data: plans } = usePlans();
|
|
794
808
|
const { pricing } = useMonetizationConfig();
|
|
795
|
-
const selectedPlan = plans?.items?.find((plan) => plan.id === planId);
|
|
796
809
|
if (!planId) throw new Error("Parameter `planId` missing");
|
|
797
810
|
if (!subscriptionId) throw new Error("Parameter `subscriptionId` missing");
|
|
811
|
+
const purchaseDetails = usePurchaseDetails(planId);
|
|
812
|
+
const selectedPlan = getPlanFromPurchaseDetails(purchaseDetails.data);
|
|
813
|
+
const taxAmount = getTaxAmountFromPurchaseDetails(purchaseDetails.data);
|
|
814
|
+
const taxLabel = getTaxLabelFromPurchaseDetails(purchaseDetails.data);
|
|
815
|
+
const taxInclusive = isTaxInclusiveFromPurchaseDetails(purchaseDetails.data);
|
|
798
816
|
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
799
817
|
const { quotas, features } = categorizeRateCards(rateCards ?? [], {
|
|
800
818
|
currency: selectedPlan?.currency,
|
|
@@ -803,6 +821,7 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
803
821
|
});
|
|
804
822
|
const price = selectedPlan ? getPriceFromPlan(selectedPlan) : null;
|
|
805
823
|
const billingCycle = selectedPlan?.billingCadence ? formatDuration(selectedPlan.billingCadence) : null;
|
|
824
|
+
const effectiveChangeMessage = mode === "downgrade" ? "This change will take effect at the start of your next billing cycle." : "This change will take effect immediately.";
|
|
806
825
|
const changeMutation = useMutation({
|
|
807
826
|
mutationKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/change`],
|
|
808
827
|
meta: {
|
|
@@ -837,13 +856,20 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
837
856
|
}),
|
|
838
857
|
/* @__PURE__ */ jsxs("div", {
|
|
839
858
|
className: "text-center mb-8",
|
|
840
|
-
children: [
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
859
|
+
children: [
|
|
860
|
+
/* @__PURE__ */ jsx("h1", {
|
|
861
|
+
className: "text-2xl font-bold text-card-foreground mb-3",
|
|
862
|
+
children: "Confirm plan change"
|
|
863
|
+
}),
|
|
864
|
+
/* @__PURE__ */ jsx("p", {
|
|
865
|
+
className: "text-muted-foreground text-base",
|
|
866
|
+
children: effectiveChangeMessage
|
|
867
|
+
}),
|
|
868
|
+
/* @__PURE__ */ jsx("p", {
|
|
869
|
+
className: "text-muted-foreground text-base",
|
|
870
|
+
children: "Please confirm the details below to change your subscription."
|
|
871
|
+
})
|
|
872
|
+
]
|
|
847
873
|
}),
|
|
848
874
|
selectedPlan && /* @__PURE__ */ jsxs(Card, {
|
|
849
875
|
className: "bg-muted/50",
|
|
@@ -868,13 +894,20 @@ const SubscriptionChangeConfirmPage = () => {
|
|
|
868
894
|
}),
|
|
869
895
|
price && price.monthly > 0 && /* @__PURE__ */ jsxs("div", {
|
|
870
896
|
className: "text-right",
|
|
871
|
-
children: [
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
897
|
+
children: [
|
|
898
|
+
/* @__PURE__ */ jsx("div", {
|
|
899
|
+
className: "text-2xl font-bold",
|
|
900
|
+
children: formatPrice(price.monthly, selectedPlan?.currency)
|
|
901
|
+
}),
|
|
902
|
+
billingCycle && /* @__PURE__ */ jsxs("div", {
|
|
903
|
+
className: "text-sm text-muted-foreground font-normal",
|
|
904
|
+
children: ["Billed ", formatBillingCycle(billingCycle)]
|
|
905
|
+
}),
|
|
906
|
+
taxAmount != null && /* @__PURE__ */ jsx("div", {
|
|
907
|
+
className: "text-xs text-muted-foreground font-normal mt-1",
|
|
908
|
+
children: taxInclusive ? `${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel} included` : `+ ${formatPrice(taxAmount, selectedPlan?.currency)} ${taxLabel}`
|
|
909
|
+
})
|
|
910
|
+
]
|
|
878
911
|
}),
|
|
879
912
|
price && price.monthly === 0 && /* @__PURE__ */ jsx("div", {
|
|
880
913
|
className: "text-2xl text-muted-foreground font-bold",
|
|
@@ -1529,7 +1562,12 @@ const modeLabelMap = {
|
|
|
1529
1562
|
downgrade: "Downgrade",
|
|
1530
1563
|
private: "Switch"
|
|
1531
1564
|
};
|
|
1532
|
-
const
|
|
1565
|
+
const isSwitchPlanTarget = (value) => {
|
|
1566
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1567
|
+
if (!("subscriptionId" in value) || !("plan" in value) || !("mode" in value)) return false;
|
|
1568
|
+
return true;
|
|
1569
|
+
};
|
|
1570
|
+
const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange, isSwitching }) => {
|
|
1533
1571
|
const price = getPriceFromPlan(comparison.plan);
|
|
1534
1572
|
const isCustom = comparison.plan.key === "enterprise";
|
|
1535
1573
|
const displayPrice = price.monthly;
|
|
@@ -1569,6 +1607,7 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange
|
|
|
1569
1607
|
mode
|
|
1570
1608
|
}),
|
|
1571
1609
|
size: "sm",
|
|
1610
|
+
disabled: isSwitching,
|
|
1572
1611
|
children: modeLabelMap[mode]
|
|
1573
1612
|
})]
|
|
1574
1613
|
}), hasChanges && /* @__PURE__ */ jsxs("div", {
|
|
@@ -1663,24 +1702,32 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange
|
|
|
1663
1702
|
})]
|
|
1664
1703
|
});
|
|
1665
1704
|
};
|
|
1666
|
-
const
|
|
1705
|
+
const SwitchPlanModal = ({ subscription, children }) => {
|
|
1706
|
+
const [open, setOpen] = useState(false);
|
|
1707
|
+
const { data: plansData } = usePlans();
|
|
1708
|
+
const { pricing } = useMonetizationConfig();
|
|
1667
1709
|
const deploymentName = useDeploymentName();
|
|
1668
1710
|
const context = useZudoku();
|
|
1669
1711
|
const { generateUrl } = useUrlUtils();
|
|
1670
|
-
const
|
|
1712
|
+
const switchPlanMutation = useMutation({
|
|
1671
1713
|
mutationKey: [`/v3/zudoku-metering/${deploymentName}/stripe/checkout`],
|
|
1672
1714
|
meta: {
|
|
1673
1715
|
context,
|
|
1674
|
-
request: {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1716
|
+
request: (variables) => {
|
|
1717
|
+
if (!isSwitchPlanTarget(variables)) throw new Error("Couldn't start the plan change. Please refresh and try again.");
|
|
1718
|
+
const switchTo = variables;
|
|
1719
|
+
return {
|
|
1720
|
+
method: "POST",
|
|
1721
|
+
body: JSON.stringify({
|
|
1679
1722
|
planId: switchTo.plan.id,
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1723
|
+
successURL: generateUrl(`/subscription-change-confirm`, { searchParams: {
|
|
1724
|
+
planId: switchTo.plan.id,
|
|
1725
|
+
subscriptionId: switchTo.subscriptionId,
|
|
1726
|
+
mode: switchTo.mode
|
|
1727
|
+
} }),
|
|
1728
|
+
cancelURL: generateUrl("/subscriptions", { searchParams: { subscriptionId: switchTo.subscriptionId } })
|
|
1729
|
+
})
|
|
1730
|
+
};
|
|
1684
1731
|
}
|
|
1685
1732
|
},
|
|
1686
1733
|
retry: false,
|
|
@@ -1688,38 +1735,6 @@ const ConfirmSwitchAlert = ({ switchTo, onRequestClose }) => {
|
|
|
1688
1735
|
window.location.href = data.url;
|
|
1689
1736
|
}
|
|
1690
1737
|
});
|
|
1691
|
-
return /* @__PURE__ */ jsx(AlertDialog, {
|
|
1692
|
-
open: true,
|
|
1693
|
-
onOpenChange: onRequestClose,
|
|
1694
|
-
children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
|
|
1695
|
-
/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
|
|
1696
|
-
"Confirm",
|
|
1697
|
-
" ",
|
|
1698
|
-
switchTo.mode === "private" ? "plan change" : switchTo.mode === "upgrade" ? "upgrade" : "downgrade"
|
|
1699
|
-
] }),
|
|
1700
|
-
mutation.isError && /* @__PURE__ */ jsx(Alert, {
|
|
1701
|
-
variant: "destructive",
|
|
1702
|
-
children: /* @__PURE__ */ jsx(AlertDescription, {
|
|
1703
|
-
className: "first-letter:uppercase",
|
|
1704
|
-
children: mutation.error.message
|
|
1705
|
-
})
|
|
1706
|
-
}),
|
|
1707
|
-
/* @__PURE__ */ jsx(AlertDialogDescription, { children: switchTo.mode === "private" ? `Are you sure you want to switch to ${switchTo.plan.name}? This will take effect immediately.` : switchTo.mode === "upgrade" ? `Are you sure you want to upgrade to ${switchTo.plan.name}? This will take effect immediately.` : `Are you sure you want to downgrade to ${switchTo.plan.name}? This will take effect at the start of your next billing cycle.` })
|
|
1708
|
-
] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, {
|
|
1709
|
-
disabled: mutation.isPending,
|
|
1710
|
-
children: "Cancel"
|
|
1711
|
-
}), /* @__PURE__ */ jsx(ActionButton, {
|
|
1712
|
-
isPending: mutation.isPending,
|
|
1713
|
-
onClick: () => mutation.mutate(),
|
|
1714
|
-
children: modeLabelMap[switchTo.mode]
|
|
1715
|
-
})] })] })
|
|
1716
|
-
});
|
|
1717
|
-
};
|
|
1718
|
-
const SwitchPlanModal = ({ subscription, children }) => {
|
|
1719
|
-
const [open, setOpen] = useState(false);
|
|
1720
|
-
const { data: plansData } = usePlans();
|
|
1721
|
-
const [switchTo, setSwitchTo] = useState(null);
|
|
1722
|
-
const { pricing } = useMonetizationConfig();
|
|
1723
1738
|
const currentPlan = plansData?.items.find((p) => p.id === subscription.plan.id);
|
|
1724
1739
|
const { upgrades, downgrades, privatePlans } = useMemo(() => {
|
|
1725
1740
|
if (!plansData?.items || !currentPlan) return {
|
|
@@ -1742,10 +1757,7 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1742
1757
|
currentPlan,
|
|
1743
1758
|
pricing?.units
|
|
1744
1759
|
]);
|
|
1745
|
-
return /* @__PURE__ */ jsxs(
|
|
1746
|
-
switchTo,
|
|
1747
|
-
onRequestClose: () => setSwitchTo(null)
|
|
1748
|
-
}), /* @__PURE__ */ jsxs(Dialog, {
|
|
1760
|
+
return /* @__PURE__ */ jsxs(Dialog, {
|
|
1749
1761
|
open,
|
|
1750
1762
|
onOpenChange: setOpen,
|
|
1751
1763
|
children: [/* @__PURE__ */ jsx(DialogTrigger, {
|
|
@@ -1766,6 +1778,13 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1766
1778
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1767
1779
|
className: "mt-4 space-y-6",
|
|
1768
1780
|
children: [
|
|
1781
|
+
switchPlanMutation.isError && /* @__PURE__ */ jsx(Alert, {
|
|
1782
|
+
variant: "destructive",
|
|
1783
|
+
children: /* @__PURE__ */ jsx(AlertDescription, {
|
|
1784
|
+
className: "first-letter:uppercase",
|
|
1785
|
+
children: switchPlanMutation.error.message
|
|
1786
|
+
})
|
|
1787
|
+
}),
|
|
1769
1788
|
currentPlan && /* @__PURE__ */ jsx(Item, {
|
|
1770
1789
|
variant: "outline",
|
|
1771
1790
|
children: /* @__PURE__ */ jsxs(ItemContent, { children: [/* @__PURE__ */ jsx(ItemTitle, { children: "Current Plan" }), /* @__PURE__ */ jsx(ItemDescription, {
|
|
@@ -1791,7 +1810,8 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1791
1810
|
comparison,
|
|
1792
1811
|
subscriptionId: subscription.id,
|
|
1793
1812
|
mode: "upgrade",
|
|
1794
|
-
onRequestChange:
|
|
1813
|
+
onRequestChange: (target) => switchPlanMutation.mutate(target),
|
|
1814
|
+
isSwitching: switchPlanMutation.isPending
|
|
1795
1815
|
}, comparison.plan.id))
|
|
1796
1816
|
})] }),
|
|
1797
1817
|
downgrades.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -1812,7 +1832,8 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1812
1832
|
comparison,
|
|
1813
1833
|
subscriptionId: subscription.id,
|
|
1814
1834
|
mode: "downgrade",
|
|
1815
|
-
onRequestChange:
|
|
1835
|
+
onRequestChange: (target) => switchPlanMutation.mutate(target),
|
|
1836
|
+
isSwitching: switchPlanMutation.isPending
|
|
1816
1837
|
}, comparison.plan.id))
|
|
1817
1838
|
})] }),
|
|
1818
1839
|
privatePlans.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -1833,13 +1854,14 @@ const SwitchPlanModal = ({ subscription, children }) => {
|
|
|
1833
1854
|
comparison,
|
|
1834
1855
|
subscriptionId: subscription.id,
|
|
1835
1856
|
mode: "private",
|
|
1836
|
-
onRequestChange:
|
|
1857
|
+
onRequestChange: (target) => switchPlanMutation.mutate(target),
|
|
1858
|
+
isSwitching: switchPlanMutation.isPending
|
|
1837
1859
|
}, comparison.plan.id))
|
|
1838
1860
|
})] })
|
|
1839
1861
|
]
|
|
1840
1862
|
})]
|
|
1841
1863
|
}) })]
|
|
1842
|
-
})
|
|
1864
|
+
});
|
|
1843
1865
|
};
|
|
1844
1866
|
|
|
1845
1867
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuplo/zudoku-plugin-monetization",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zuplo/zudoku",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"react": "19.2.4",
|
|
32
32
|
"react-dom": "19.2.4",
|
|
33
33
|
"tsdown": "0.20.3",
|
|
34
|
-
"zudoku": "0.
|
|
34
|
+
"zudoku": "0.73.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=19.2.0",
|