@zuplo/zudoku-plugin-monetization 0.0.23 → 0.0.24
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 +180 -34
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -184,6 +184,16 @@ const categorizeRateCards = (rateCards, currency, units) => {
|
|
|
184
184
|
};
|
|
185
185
|
};
|
|
186
186
|
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/utils/formatBillingCycle.ts
|
|
189
|
+
const formatBillingCycle = (duration) => {
|
|
190
|
+
if (duration === "month") return "monthly";
|
|
191
|
+
if (duration === "year") return "annually";
|
|
192
|
+
if (duration === "week") return "weekly";
|
|
193
|
+
if (duration === "day") return "daily";
|
|
194
|
+
return `every ${duration}`;
|
|
195
|
+
};
|
|
196
|
+
|
|
187
197
|
//#endregion
|
|
188
198
|
//#region src/utils/getPriceFromPlan.ts
|
|
189
199
|
const getPriceFromPlan = (plan) => {
|
|
@@ -217,7 +227,13 @@ const queryClient = new QueryClient({ defaultOptions: {
|
|
|
217
227
|
}
|
|
218
228
|
});
|
|
219
229
|
const response = await fetch(q.meta?.context ? await q.meta.context.signRequest(request) : request);
|
|
220
|
-
if (!response.ok)
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
if (response.headers.get("content-type")?.includes("application/problem+json")) {
|
|
232
|
+
const data = await response.json();
|
|
233
|
+
throw new Error(data.detail ?? data.title);
|
|
234
|
+
}
|
|
235
|
+
throw new Error("Failed to fetch request");
|
|
236
|
+
}
|
|
221
237
|
return response.json();
|
|
222
238
|
}
|
|
223
239
|
},
|
|
@@ -268,14 +284,6 @@ const ZuploMonetizationWrapper = () => {
|
|
|
268
284
|
|
|
269
285
|
//#endregion
|
|
270
286
|
//#region src/pages/CheckoutConfirmPage.tsx
|
|
271
|
-
const formatBillingCycle = (duration) => {
|
|
272
|
-
if (duration === "month") return "monthly";
|
|
273
|
-
if (duration === "year") return "annually";
|
|
274
|
-
if (duration === "week") return "weekly";
|
|
275
|
-
if (duration === "day") return "daily";
|
|
276
|
-
if (duration.includes(" ")) return `every ${duration}`;
|
|
277
|
-
return `every ${duration}`;
|
|
278
|
-
};
|
|
279
287
|
const CheckoutConfirmPage = () => {
|
|
280
288
|
const [search] = useSearchParams();
|
|
281
289
|
const planId = search.get("plan");
|
|
@@ -729,6 +737,150 @@ const PricingPage = ({ subtitle = "See our pricing options and choose the one th
|
|
|
729
737
|
});
|
|
730
738
|
};
|
|
731
739
|
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/pages/SubscriptionChangeConfirmPage.tsx
|
|
742
|
+
const SubscriptionChangeConfirmPage = () => {
|
|
743
|
+
const [search] = useSearchParams();
|
|
744
|
+
const planId = search.get("planId");
|
|
745
|
+
const subscriptionId = search.get("subscriptionId");
|
|
746
|
+
const zudoku = useZudoku();
|
|
747
|
+
const deploymentName = useDeploymentName();
|
|
748
|
+
const navigate = useNavigate();
|
|
749
|
+
const { data: plans } = usePlans();
|
|
750
|
+
const selectedPlan = plans?.items?.find((plan) => plan.id === planId);
|
|
751
|
+
if (!planId) throw new Error("Parameter `planId` missing");
|
|
752
|
+
if (!subscriptionId) throw new Error("Parameter `subscriptionId` missing");
|
|
753
|
+
const rateCards = selectedPlan?.phases.at(-1)?.rateCards;
|
|
754
|
+
const { quotas, features } = categorizeRateCards(rateCards ?? [], selectedPlan?.currency);
|
|
755
|
+
const price = selectedPlan ? getPriceFromPlan(selectedPlan) : null;
|
|
756
|
+
const billingCycle = selectedPlan?.billingCadence ? formatDuration(selectedPlan.billingCadence) : null;
|
|
757
|
+
const changeMutation = useMutation({
|
|
758
|
+
mutationKey: [`/v3/zudoku-metering/${deploymentName}/subscriptions/${subscriptionId}/change`],
|
|
759
|
+
meta: {
|
|
760
|
+
context: zudoku,
|
|
761
|
+
request: {
|
|
762
|
+
method: "POST",
|
|
763
|
+
body: JSON.stringify({ planId })
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
onSuccess: async (subscription) => {
|
|
767
|
+
await queryClient.invalidateQueries();
|
|
768
|
+
navigate(`/subscriptions/${subscription.id}`, { state: { planSwitched: { newPlanName: selectedPlan?.name } } });
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
return /* @__PURE__ */ jsx("div", {
|
|
772
|
+
className: "w-full bg-muted min-h-screen flex items-center justify-center px-4 py-12 gap-4",
|
|
773
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
774
|
+
className: "max-w-2xl w-full",
|
|
775
|
+
children: [changeMutation.isError && /* @__PURE__ */ jsxs(Alert, {
|
|
776
|
+
className: "mb-4",
|
|
777
|
+
variant: "destructive",
|
|
778
|
+
children: [/* @__PURE__ */ jsx(AlertTitle, { children: "Error" }), /* @__PURE__ */ jsx(AlertDescription, { children: changeMutation.error.message })]
|
|
779
|
+
}), /* @__PURE__ */ jsxs(Card, {
|
|
780
|
+
className: "p-8 w-full max-w-7xl",
|
|
781
|
+
children: [
|
|
782
|
+
/* @__PURE__ */ jsx("div", {
|
|
783
|
+
className: "flex justify-center mb-6",
|
|
784
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
785
|
+
className: "rounded-full bg-primary/10 p-3",
|
|
786
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-9 text-primary" })
|
|
787
|
+
})
|
|
788
|
+
}),
|
|
789
|
+
/* @__PURE__ */ jsxs("div", {
|
|
790
|
+
className: "text-center mb-8",
|
|
791
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
792
|
+
className: "text-2xl font-bold text-card-foreground mb-3",
|
|
793
|
+
children: "Confirm plan change"
|
|
794
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
795
|
+
className: "text-muted-foreground text-base",
|
|
796
|
+
children: "Please confirm the details below to change your subscription."
|
|
797
|
+
})]
|
|
798
|
+
}),
|
|
799
|
+
selectedPlan && /* @__PURE__ */ jsxs(Card, {
|
|
800
|
+
className: "bg-muted/50",
|
|
801
|
+
children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
|
|
802
|
+
className: "flex justify-between items-start",
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ jsxs("div", {
|
|
805
|
+
className: "flex items-center gap-3",
|
|
806
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
807
|
+
className: "flex flex-col text-2xl font-bold bg-primary text-primary-foreground items-center justify-center rounded size-12",
|
|
808
|
+
children: selectedPlan.name.at(0)?.toUpperCase()
|
|
809
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
810
|
+
className: "flex flex-col",
|
|
811
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
812
|
+
className: "text-lg font-bold",
|
|
813
|
+
children: selectedPlan.name
|
|
814
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
815
|
+
className: "text-sm font-normal text-muted-foreground",
|
|
816
|
+
children: selectedPlan.description || "New plan"
|
|
817
|
+
})]
|
|
818
|
+
})]
|
|
819
|
+
}),
|
|
820
|
+
price && price.monthly > 0 && /* @__PURE__ */ jsxs("div", {
|
|
821
|
+
className: "text-right",
|
|
822
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
823
|
+
className: "text-2xl font-bold",
|
|
824
|
+
children: formatPrice(price.monthly, selectedPlan?.currency)
|
|
825
|
+
}), billingCycle && /* @__PURE__ */ jsxs("div", {
|
|
826
|
+
className: "text-sm text-muted-foreground font-normal",
|
|
827
|
+
children: ["Billed ", formatBillingCycle(billingCycle)]
|
|
828
|
+
})]
|
|
829
|
+
}),
|
|
830
|
+
price && price.monthly === 0 && /* @__PURE__ */ jsx("div", {
|
|
831
|
+
className: "text-2xl text-muted-foreground font-bold",
|
|
832
|
+
children: "Free"
|
|
833
|
+
})
|
|
834
|
+
]
|
|
835
|
+
}) }), /* @__PURE__ */ jsxs(CardContent, { children: [
|
|
836
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
837
|
+
/* @__PURE__ */ jsx("div", {
|
|
838
|
+
className: "text-sm font-medium mb-3 mt-3",
|
|
839
|
+
children: "What's included:"
|
|
840
|
+
}),
|
|
841
|
+
/* @__PURE__ */ jsxs("div", {
|
|
842
|
+
className: "grid grid-cols-2 gap-2 text-muted-foreground",
|
|
843
|
+
children: [quotas.map((quota) => /* @__PURE__ */ jsx(QuotaItem, {
|
|
844
|
+
quota,
|
|
845
|
+
className: "text-muted-foreground"
|
|
846
|
+
}, quota.key)), features.map((feature) => /* @__PURE__ */ jsx(FeatureItem, {
|
|
847
|
+
feature,
|
|
848
|
+
className: "text-muted-foreground"
|
|
849
|
+
}, feature.key))]
|
|
850
|
+
})
|
|
851
|
+
] })]
|
|
852
|
+
}),
|
|
853
|
+
/* @__PURE__ */ jsxs("div", {
|
|
854
|
+
className: "space-y-3 mt-4",
|
|
855
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
856
|
+
className: "w-full",
|
|
857
|
+
onClick: () => changeMutation.mutate(),
|
|
858
|
+
disabled: changeMutation.isPending,
|
|
859
|
+
children: changeMutation.isPending ? "Changing plan..." : "Confirm & Change Plan"
|
|
860
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
861
|
+
variant: "ghost",
|
|
862
|
+
className: "w-full",
|
|
863
|
+
disabled: changeMutation.isPending,
|
|
864
|
+
asChild: !changeMutation.isPending,
|
|
865
|
+
children: /* @__PURE__ */ jsx(Link$1, {
|
|
866
|
+
to: `/subscriptions/${subscriptionId}`,
|
|
867
|
+
children: "Cancel"
|
|
868
|
+
})
|
|
869
|
+
})]
|
|
870
|
+
}),
|
|
871
|
+
/* @__PURE__ */ jsx("div", {
|
|
872
|
+
className: "mt-6 pt-6 border-t text-center",
|
|
873
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
874
|
+
className: "text-xs text-muted-foreground",
|
|
875
|
+
children: "By confirming, you agree to our Terms of Service and Privacy Policy."
|
|
876
|
+
})
|
|
877
|
+
})
|
|
878
|
+
]
|
|
879
|
+
})]
|
|
880
|
+
})
|
|
881
|
+
});
|
|
882
|
+
};
|
|
883
|
+
|
|
732
884
|
//#endregion
|
|
733
885
|
//#region src/hooks/useSubscriptions.ts
|
|
734
886
|
const useSubscriptions = (environmentName) => {
|
|
@@ -1362,29 +1514,26 @@ const PlanComparisonItem = ({ comparison, subscriptionId, mode, onRequestChange
|
|
|
1362
1514
|
const ConfirmSwitchAlert = ({ switchTo, onRequestClose }) => {
|
|
1363
1515
|
const deploymentName = useDeploymentName();
|
|
1364
1516
|
const context = useZudoku();
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1517
|
+
const { generateUrl } = useUrlUtils();
|
|
1518
|
+
const successUrl = new URL(generateUrl("/subscription-change-confirm"));
|
|
1519
|
+
successUrl.searchParams.set("planId", switchTo.plan.id);
|
|
1520
|
+
successUrl.searchParams.set("subscriptionId", switchTo.subscriptionId);
|
|
1367
1521
|
const mutation = useMutation({
|
|
1368
|
-
mutationKey: [`/v3/zudoku-metering/${deploymentName}/
|
|
1522
|
+
mutationKey: [`/v3/zudoku-metering/${deploymentName}/stripe/checkout`],
|
|
1369
1523
|
meta: {
|
|
1370
1524
|
context,
|
|
1371
1525
|
request: {
|
|
1372
1526
|
method: "POST",
|
|
1373
|
-
body: JSON.stringify({
|
|
1527
|
+
body: JSON.stringify({
|
|
1528
|
+
planId: switchTo.plan.id,
|
|
1529
|
+
successURL: successUrl.toString(),
|
|
1530
|
+
cancelURL: generateUrl(`/subscriptions/${switchTo.subscriptionId}`)
|
|
1531
|
+
})
|
|
1374
1532
|
}
|
|
1375
1533
|
},
|
|
1376
1534
|
retry: false,
|
|
1377
|
-
onSuccess:
|
|
1378
|
-
|
|
1379
|
-
navigate(`/subscriptions/${subscription.id}`, { state: { planSwitched: {
|
|
1380
|
-
mode: switchTo.mode,
|
|
1381
|
-
newPlanName: switchTo.plan.name
|
|
1382
|
-
} } });
|
|
1383
|
-
onRequestClose();
|
|
1384
|
-
window.scrollTo({
|
|
1385
|
-
top: 0,
|
|
1386
|
-
behavior: "smooth"
|
|
1387
|
-
});
|
|
1535
|
+
onSuccess: (data) => {
|
|
1536
|
+
window.location.href = data.url;
|
|
1388
1537
|
}
|
|
1389
1538
|
});
|
|
1390
1539
|
return /* @__PURE__ */ jsx(AlertDialog, {
|
|
@@ -1784,16 +1933,8 @@ const ActiveSubscription = ({ subscription, deploymentName }) => {
|
|
|
1784
1933
|
variant: "info",
|
|
1785
1934
|
children: [
|
|
1786
1935
|
/* @__PURE__ */ jsx(CheckCheckIcon, { className: "size-4" }),
|
|
1787
|
-
/* @__PURE__ */
|
|
1788
|
-
|
|
1789
|
-
" ",
|
|
1790
|
-
planSwitched.mode === "upgrade" ? "upgraded" : planSwitched.mode === "downgrade" ? "downgraded" : "changed"
|
|
1791
|
-
] }),
|
|
1792
|
-
/* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
1793
|
-
"You have successfully switched to ",
|
|
1794
|
-
planSwitched.newPlanName,
|
|
1795
|
-
"."
|
|
1796
|
-
] }),
|
|
1936
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Plan changed" }),
|
|
1937
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: planSwitched.newPlanName ? `You have successfully switched to ${planSwitched.newPlanName}.` : "Your plan has been successfully changed." }),
|
|
1797
1938
|
/* @__PURE__ */ jsx(DismissibleAlertAction, {})
|
|
1798
1939
|
]
|
|
1799
1940
|
}),
|
|
@@ -1985,6 +2126,10 @@ const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
|
1985
2126
|
path: "/checkout-confirm",
|
|
1986
2127
|
element: /* @__PURE__ */ jsx(CheckoutConfirmPage, {})
|
|
1987
2128
|
},
|
|
2129
|
+
{
|
|
2130
|
+
path: "/subscription-change-confirm",
|
|
2131
|
+
element: /* @__PURE__ */ jsx(SubscriptionChangeConfirmPage, {})
|
|
2132
|
+
},
|
|
1988
2133
|
{
|
|
1989
2134
|
path: "/manage-payment",
|
|
1990
2135
|
element: /* @__PURE__ */ jsx(ManagePaymentPage, {})
|
|
@@ -2011,6 +2156,7 @@ const zuploMonetizationPlugin = createPlugin((options) => ({
|
|
|
2011
2156
|
return [
|
|
2012
2157
|
"/checkout/*",
|
|
2013
2158
|
"/checkout-confirm",
|
|
2159
|
+
"/subscription-change-confirm",
|
|
2014
2160
|
"/subscriptions/*",
|
|
2015
2161
|
"/manage-payment"
|
|
2016
2162
|
];
|
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.24",
|
|
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.71.
|
|
34
|
+
"zudoku": "0.71.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=19.2.0",
|