@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.
Files changed (2) hide show
  1. package/dist/index.mjs +180 -34
  2. 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) throw new Error("Failed to fetch request");
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 queryClient = useQueryClient();
1366
- const navigate = useNavigate();
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}/subscriptions/${switchTo.subscriptionId}/change`],
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({ planId: switchTo.plan.id })
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: async (subscription) => {
1378
- await queryClient.invalidateQueries();
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__ */ jsxs(AlertTitle, { children: [
1788
- "Plan",
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.23",
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.2"
34
+ "zudoku": "0.71.3"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": ">=19.2.0",