@carlonicora/nextjs-jsonapi 1.15.0 → 1.16.0
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/{ApiResponseInterface-B4QdWh-y.d.mts → ApiResponseInterface-BvWIeLkq.d.ts} +2 -1
- package/dist/{ApiResponseInterface-QLDnxLA9.d.ts → ApiResponseInterface-CAbw0sv7.d.mts} +2 -1
- package/dist/{BlockNoteEditor-ITJLAOXC.mjs → BlockNoteEditor-HFX7Z5BQ.mjs} +5 -5
- package/dist/{BlockNoteEditor-FGXYUAWI.js → BlockNoteEditor-MBFDWP7X.js} +15 -15
- package/dist/{BlockNoteEditor-FGXYUAWI.js.map → BlockNoteEditor-MBFDWP7X.js.map} +1 -1
- package/dist/JsonApiRequest-45CLE65I.js +24 -0
- package/dist/{JsonApiRequest-FXZCYIER.js.map → JsonApiRequest-45CLE65I.js.map} +1 -1
- package/dist/{JsonApiRequest-HFWXMKMA.mjs → JsonApiRequest-6IPS3DZJ.mjs} +2 -2
- package/dist/{chunk-C6QXZGL7.js → chunk-2AZLCF6D.js} +1617 -158
- package/dist/chunk-2AZLCF6D.js.map +1 -0
- package/dist/{chunk-WAFOKMKT.mjs → chunk-5RAUCUAA.mjs} +3722 -396
- package/dist/chunk-5RAUCUAA.mjs.map +1 -0
- package/dist/{chunk-TGBXBUWM.mjs → chunk-BCKYJQ3K.mjs} +8 -1
- package/dist/chunk-BCKYJQ3K.mjs.map +1 -0
- package/dist/{chunk-JGVXZS7M.mjs → chunk-BCQSE3EU.mjs} +1588 -129
- package/dist/chunk-BCQSE3EU.mjs.map +1 -0
- package/dist/{chunk-FPZPD4JI.js → chunk-GPGJNTHP.js} +17 -10
- package/dist/chunk-GPGJNTHP.js.map +1 -0
- package/dist/{chunk-PK5DRSUD.js → chunk-ONB2DAIV.js} +4090 -764
- package/dist/chunk-ONB2DAIV.js.map +1 -0
- package/dist/{chunk-SJIVGCNM.mjs → chunk-POKIJ56Q.mjs} +7 -2
- package/dist/chunk-POKIJ56Q.mjs.map +1 -0
- package/dist/{chunk-6YD42BP6.js → chunk-R5QSSISB.js} +14 -9
- package/dist/chunk-R5QSSISB.js.map +1 -0
- package/dist/client/index.d.mts +5 -5
- package/dist/client/index.d.ts +5 -5
- package/dist/client/index.js +7 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +6 -4
- package/dist/components/index.d.mts +253 -9
- package/dist/components/index.d.ts +253 -9
- package/dist/components/index.js +83 -5
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +82 -4
- package/dist/{config-eceYM5kN.d.ts → config-CWsTwnsK.d.mts} +7 -2
- package/dist/{config-C5tGGrYf.d.mts → config-DEaUbBqR.d.ts} +7 -2
- package/dist/{content.interface-TB2MfJGs.d.ts → content.interface-D_4b4RQt.d.ts} +1 -1
- package/dist/{content.interface-CxBBC7ec.d.mts → content.interface-Dk4UZcJM.d.mts} +1 -1
- package/dist/contexts/index.d.mts +2 -2
- package/dist/contexts/index.d.ts +2 -2
- package/dist/contexts/index.js +5 -5
- package/dist/contexts/index.mjs +4 -4
- package/dist/core/index.d.mts +521 -18
- package/dist/core/index.d.ts +521 -18
- package/dist/core/index.js +53 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +52 -2
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +56 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +55 -3
- package/dist/{notification.interface-lG6UpTpt.d.mts → notification.interface-BllkURRm.d.mts} +1 -2
- package/dist/{notification.interface-lG6UpTpt.d.ts → notification.interface-BllkURRm.d.ts} +1 -2
- package/dist/{s3.service-DP_hsssD.d.mts → s3.service-BEfGqho0.d.ts} +20 -2
- package/dist/{s3.service-Dq-PTUNa.d.ts → s3.service-DIQRYe93.d.mts} +20 -2
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/generator.js +66 -0
- package/dist/scripts/generate-web-module/generator.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts +8 -0
- package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.js +18 -1
- package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.js +141 -0
- package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/env.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/env.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/env.template.js +110 -0
- package/dist/scripts/generate-web-module/templates/project/env.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/main-layout.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/main-layout.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/main-layout.template.js +101 -0
- package/dist/scripts/generate-web-module/templates/project/main-layout.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/middleware-env.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/middleware-env.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/middleware-env.template.js +66 -0
- package/dist/scripts/generate-web-module/templates/project/middleware-env.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-container.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/settings-container.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-container.template.js +257 -0
- package/dist/scripts/generate-web-module/templates/project/settings-container.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-context.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/settings-context.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-context.template.js +124 -0
- package/dist/scripts/generate-web-module/templates/project/settings-context.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.js +78 -0
- package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.js.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-page.template.d.ts +7 -0
- package/dist/scripts/generate-web-module/templates/project/settings-page.template.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/templates/project/settings-page.template.js +75 -0
- package/dist/scripts/generate-web-module/templates/project/settings-page.template.js.map +1 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +1 -1
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
- package/dist/server/index.d.mts +4 -4
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +12 -12
- package/dist/server/index.mjs +2 -2
- package/dist/stripe-subscription.interface-C63L6hVg.d.mts +226 -0
- package/dist/stripe-subscription.interface-CUvNDvw5.d.ts +226 -0
- package/dist/{useSocket-Bua6MwLi.d.mts → useSocket-BpenBR2z.d.mts} +1 -1
- package/dist/{useSocket-D5dhUp4m.d.ts → useSocket-D-QYA0Sr.d.ts} +1 -1
- package/package.json +9 -1
- package/scripts/generate-web-module/generator.ts +83 -0
- package/scripts/generate-web-module/templates/index.ts +10 -0
- package/scripts/generate-web-module/templates/project/bootstrapper.template.ts +108 -0
- package/scripts/generate-web-module/templates/project/env.template.ts +77 -0
- package/scripts/generate-web-module/templates/project/main-layout.template.tsx +68 -0
- package/scripts/generate-web-module/templates/project/middleware-env.template.ts +33 -0
- package/scripts/generate-web-module/templates/project/settings-container.template.tsx +224 -0
- package/scripts/generate-web-module/templates/project/settings-context.template.tsx +91 -0
- package/scripts/generate-web-module/templates/project/settings-module-page.template.tsx +45 -0
- package/scripts/generate-web-module/templates/project/settings-page.template.tsx +42 -0
- package/scripts/generate-web-module/types/template-data.interface.ts +1 -1
- package/src/client/config.ts +9 -0
- package/src/components/index.ts +7 -0
- package/src/core/abstracts/AbstractService.ts +104 -0
- package/src/core/endpoint/EndpointCreator.ts +7 -4
- package/src/core/index.ts +12 -4
- package/src/core/interfaces/ApiResponseInterface.ts +1 -0
- package/src/core/registry/ModuleRegistry.ts +11 -2
- package/src/core/utils/translateResponse.ts +17 -0
- package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +97 -0
- package/src/features/billing/components/cards/CustomerInfoCard.tsx +112 -0
- package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +114 -0
- package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +119 -0
- package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +146 -0
- package/src/features/billing/components/cards/index.ts +5 -0
- package/src/features/billing/components/containers/BillingDashboardContainer.tsx +427 -0
- package/src/features/billing/components/containers/index.ts +1 -0
- package/src/features/billing/components/index.ts +6 -0
- package/src/features/billing/components/modals/BillingDetailModal.tsx +36 -0
- package/src/features/billing/components/modals/index.ts +1 -0
- package/src/features/billing/components/providers/StripeProvider.tsx +48 -0
- package/src/features/billing/components/providers/index.ts +1 -0
- package/src/features/billing/components/utils/currency.ts +49 -0
- package/src/features/billing/components/utils/date.ts +21 -0
- package/src/features/billing/components/utils/index.ts +2 -0
- package/src/features/billing/components/widgets/BillingAlertBanner.tsx +63 -0
- package/src/features/billing/components/widgets/index.ts +1 -0
- package/src/features/billing/data/Billing.ts +17 -0
- package/src/features/billing/data/billing.service.ts +58 -0
- package/src/features/billing/data/index.ts +5 -0
- package/src/features/billing/index.ts +3 -0
- package/src/features/billing/modules/billing.module.ts +9 -0
- package/src/features/billing/modules/index.ts +1 -0
- package/src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx +79 -0
- package/src/features/billing/stripe-customer/components/containers/index.ts +1 -0
- package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +151 -0
- package/src/features/billing/stripe-customer/components/details/index.ts +1 -0
- package/src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx +186 -0
- package/src/features/billing/stripe-customer/components/forms/index.ts +1 -0
- package/src/features/billing/stripe-customer/components/index.ts +4 -0
- package/src/features/billing/stripe-customer/components/lists/PaymentMethodsList.tsx +19 -0
- package/src/features/billing/stripe-customer/components/lists/index.ts +1 -0
- package/src/features/billing/stripe-customer/data/index.ts +5 -0
- package/src/features/billing/stripe-customer/data/payment-method.interface.ts +27 -0
- package/src/features/billing/stripe-customer/data/payment-method.ts +119 -0
- package/src/features/billing/stripe-customer/data/stripe-customer.interface.ts +16 -0
- package/src/features/billing/stripe-customer/data/stripe-customer.service.ts +128 -0
- package/src/features/billing/stripe-customer/data/stripe-customer.ts +71 -0
- package/src/features/billing/stripe-customer/index.ts +3 -0
- package/src/features/billing/stripe-customer/stripe-customer.module.ts +9 -0
- package/src/features/billing/stripe-customer/stripe-payment-method.module.ts +9 -0
- package/src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx +66 -0
- package/src/features/billing/stripe-invoice/components/containers/index.ts +1 -0
- package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +172 -0
- package/src/features/billing/stripe-invoice/components/details/index.ts +1 -0
- package/src/features/billing/stripe-invoice/components/index.ts +4 -0
- package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +84 -0
- package/src/features/billing/stripe-invoice/components/lists/index.ts +1 -0
- package/src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx +41 -0
- package/src/features/billing/stripe-invoice/components/widgets/index.ts +1 -0
- package/src/features/billing/stripe-invoice/data/index.ts +3 -0
- package/src/features/billing/stripe-invoice/data/stripe-invoice.interface.ts +65 -0
- package/src/features/billing/stripe-invoice/data/stripe-invoice.service.ts +64 -0
- package/src/features/billing/stripe-invoice/data/stripe-invoice.ts +177 -0
- package/src/features/billing/stripe-invoice/index.ts +2 -0
- package/src/features/billing/stripe-invoice/stripe-invoice.module.ts +9 -0
- package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +304 -0
- package/src/features/billing/stripe-price/components/forms/index.ts +1 -0
- package/src/features/billing/stripe-price/components/index.ts +2 -0
- package/src/features/billing/stripe-price/components/lists/PricesList.tsx +283 -0
- package/src/features/billing/stripe-price/components/lists/index.ts +1 -0
- package/src/features/billing/stripe-price/data/index.ts +3 -0
- package/src/features/billing/stripe-price/data/stripe-price.interface.ts +48 -0
- package/src/features/billing/stripe-price/data/stripe-price.service.ts +123 -0
- package/src/features/billing/stripe-price/data/stripe-price.ts +156 -0
- package/src/features/billing/stripe-price/index.ts +2 -0
- package/src/features/billing/stripe-price/stripe-price.module.ts +9 -0
- package/src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx +86 -0
- package/src/features/billing/stripe-product/components/containers/index.ts +1 -0
- package/src/features/billing/stripe-product/components/forms/ProductEditor.tsx +100 -0
- package/src/features/billing/stripe-product/components/forms/index.ts +1 -0
- package/src/features/billing/stripe-product/components/index.ts +3 -0
- package/src/features/billing/stripe-product/components/lists/ProductsList.tsx +206 -0
- package/src/features/billing/stripe-product/components/lists/index.ts +1 -0
- package/src/features/billing/stripe-product/data/index.ts +3 -0
- package/src/features/billing/stripe-product/data/stripe-product.interface.ts +18 -0
- package/src/features/billing/stripe-product/data/stripe-product.service.ts +112 -0
- package/src/features/billing/stripe-product/data/stripe-product.ts +74 -0
- package/src/features/billing/stripe-product/index.ts +2 -0
- package/src/features/billing/stripe-product/stripe-product.module.ts +9 -0
- package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +304 -0
- package/src/features/billing/stripe-subscription/components/containers/index.ts +1 -0
- package/src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx +223 -0
- package/src/features/billing/stripe-subscription/components/details/index.ts +1 -0
- package/src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx +116 -0
- package/src/features/billing/stripe-subscription/components/forms/SubscriptionEditor.tsx +331 -0
- package/src/features/billing/stripe-subscription/components/forms/index.ts +2 -0
- package/src/features/billing/stripe-subscription/components/index.ts +5 -0
- package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +104 -0
- package/src/features/billing/stripe-subscription/components/lists/index.ts +1 -0
- package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +95 -0
- package/src/features/billing/stripe-subscription/components/widgets/PricingCardsGrid.tsx +110 -0
- package/src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx +41 -0
- package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx +60 -0
- package/src/features/billing/stripe-subscription/components/widgets/index.ts +4 -0
- package/src/features/billing/stripe-subscription/data/index.ts +3 -0
- package/src/features/billing/stripe-subscription/data/stripe-subscription.interface.ts +66 -0
- package/src/features/billing/stripe-subscription/data/stripe-subscription.service.ts +193 -0
- package/src/features/billing/stripe-subscription/data/stripe-subscription.ts +135 -0
- package/src/features/billing/stripe-subscription/hooks/index.ts +1 -0
- package/src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts +111 -0
- package/src/features/billing/stripe-subscription/index.ts +5 -0
- package/src/features/billing/stripe-subscription/stripe-subscription.module.ts +9 -0
- package/src/features/billing/stripe-usage/components/containers/UsageContainer.tsx +109 -0
- package/src/features/billing/stripe-usage/components/containers/index.ts +1 -0
- package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +90 -0
- package/src/features/billing/stripe-usage/components/details/index.ts +1 -0
- package/src/features/billing/stripe-usage/components/index.ts +4 -0
- package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +72 -0
- package/src/features/billing/stripe-usage/components/lists/index.ts +1 -0
- package/src/features/billing/stripe-usage/components/widgets/UsageSummaryCards.tsx +19 -0
- package/src/features/billing/stripe-usage/components/widgets/index.ts +1 -0
- package/src/features/billing/stripe-usage/data/index.ts +3 -0
- package/src/features/billing/stripe-usage/data/stripe-usage.interface.ts +55 -0
- package/src/features/billing/stripe-usage/data/stripe-usage.service.ts +129 -0
- package/src/features/billing/stripe-usage/data/stripe-usage.ts +70 -0
- package/src/features/billing/stripe-usage/index.ts +2 -0
- package/src/features/billing/stripe-usage/stripe-usage.module.ts +9 -0
- package/src/features/company/components/forms/CompanyEditor.tsx +2 -2
- package/src/features/company/contexts/CompanyContext.tsx +2 -2
- package/src/features/feature/components/forms/FormFeatures.tsx +13 -106
- package/src/features/feature/data/feature.interface.ts +1 -1
- package/src/features/feature/data/feature.ts +4 -4
- package/src/features/index.ts +7 -0
- package/src/features/module/data/module.interface.ts +0 -1
- package/src/features/module/data/module.ts +0 -6
- package/src/features/user/components/lists/ContributorsList.tsx +2 -2
- package/src/features/user/components/widgets/UserAvatar.tsx +1 -1
- package/src/index.ts +1 -1
- package/src/shadcnui/custom/link.tsx +16 -6
- package/src/utils/blocknote-diff.util.ts +2 -1
- package/src/utils/blocknote-word-diff-renderer.util.ts +8 -7
- package/dist/AuthComponent-hxOPs9o8.d.mts +0 -11
- package/dist/AuthComponent-hxOPs9o8.d.ts +0 -11
- package/dist/JsonApiRequest-FXZCYIER.js +0 -24
- package/dist/chunk-6YD42BP6.js.map +0 -1
- package/dist/chunk-C6QXZGL7.js.map +0 -1
- package/dist/chunk-FPZPD4JI.js.map +0 -1
- package/dist/chunk-JGVXZS7M.mjs.map +0 -1
- package/dist/chunk-PK5DRSUD.js.map +0 -1
- package/dist/chunk-SJIVGCNM.mjs.map +0 -1
- package/dist/chunk-TGBXBUWM.mjs.map +0 -1
- package/dist/chunk-WAFOKMKT.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-ITJLAOXC.mjs.map → BlockNoteEditor-HFX7Z5BQ.mjs.map} +0 -0
- /package/dist/{JsonApiRequest-HFWXMKMA.mjs.map → JsonApiRequest-6IPS3DZJ.mjs.map} +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ProrationPreviewInterface } from "../../../stripe-invoice/data/stripe-invoice.interface";
|
|
4
|
+
import { formatCurrency, formatDate } from "../../../components/utils";
|
|
5
|
+
|
|
6
|
+
type ProrationPreviewProps = {
|
|
7
|
+
preview: ProrationPreviewInterface;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function ProrationPreview({ preview }: ProrationPreviewProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
13
|
+
<h4 className="font-semibold text-blue-900 mb-3">Proration Breakdown</h4>
|
|
14
|
+
|
|
15
|
+
<div className="space-y-2">
|
|
16
|
+
{preview.lineItems.map((item, index) => (
|
|
17
|
+
<div key={index} className="flex justify-between text-sm">
|
|
18
|
+
<span className="text-blue-800">{item.description}</span>
|
|
19
|
+
<span className={`font-medium ${item.amount < 0 ? "text-green-600" : "text-blue-900"}`}>
|
|
20
|
+
{formatCurrency(item.amount, preview.currency)}
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
|
|
25
|
+
<div className="border-t border-blue-200 pt-2 mt-2">
|
|
26
|
+
<div className="flex justify-between font-semibold">
|
|
27
|
+
<span className="text-blue-900">Net Due Today</span>
|
|
28
|
+
<span className="text-blue-900">{formatCurrency(preview.immediateCharge, preview.currency)}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{preview.lineItems.length > 0 && preview.lineItems[0].period && (
|
|
33
|
+
<div className="text-xs text-blue-700 mt-2">
|
|
34
|
+
Next invoice on {formatDate(preview.lineItems[0].period.end)} for{" "}
|
|
35
|
+
{formatCurrency(preview.amountDue, preview.currency)}
|
|
36
|
+
</div>
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { SubscriptionStatus } from "../../data";
|
|
4
|
+
|
|
5
|
+
type SubscriptionStatusBadgeProps = {
|
|
6
|
+
status: SubscriptionStatus;
|
|
7
|
+
cancelAtPeriodEnd?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type StatusConfig = {
|
|
11
|
+
label: string;
|
|
12
|
+
color: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const statusConfig: Record<SubscriptionStatus, StatusConfig> = {
|
|
16
|
+
[SubscriptionStatus.ACTIVE]: {
|
|
17
|
+
label: "Active",
|
|
18
|
+
color: "bg-green-100 text-green-800",
|
|
19
|
+
},
|
|
20
|
+
[SubscriptionStatus.TRIALING]: {
|
|
21
|
+
label: "Trial",
|
|
22
|
+
color: "bg-blue-100 text-blue-800",
|
|
23
|
+
},
|
|
24
|
+
[SubscriptionStatus.PAST_DUE]: {
|
|
25
|
+
label: "Past Due",
|
|
26
|
+
color: "bg-red-100 text-red-800",
|
|
27
|
+
},
|
|
28
|
+
[SubscriptionStatus.CANCELED]: {
|
|
29
|
+
label: "Canceled",
|
|
30
|
+
color: "bg-gray-100 text-gray-800",
|
|
31
|
+
},
|
|
32
|
+
[SubscriptionStatus.PAUSED]: {
|
|
33
|
+
label: "Paused",
|
|
34
|
+
color: "bg-yellow-100 text-yellow-800",
|
|
35
|
+
},
|
|
36
|
+
[SubscriptionStatus.UNPAID]: {
|
|
37
|
+
label: "Unpaid",
|
|
38
|
+
color: "bg-orange-100 text-orange-800",
|
|
39
|
+
},
|
|
40
|
+
[SubscriptionStatus.INCOMPLETE]: {
|
|
41
|
+
label: "Incomplete",
|
|
42
|
+
color: "bg-gray-100 text-gray-800",
|
|
43
|
+
},
|
|
44
|
+
[SubscriptionStatus.INCOMPLETE_EXPIRED]: {
|
|
45
|
+
label: "Expired",
|
|
46
|
+
color: "bg-gray-100 text-gray-800",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const cancelingConfig: StatusConfig = {
|
|
51
|
+
label: "Canceling",
|
|
52
|
+
color: "bg-amber-100 text-amber-800",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function SubscriptionStatusBadge({ status, cancelAtPeriodEnd }: SubscriptionStatusBadgeProps) {
|
|
56
|
+
// Show "Canceling" when subscription is set to cancel at period end
|
|
57
|
+
const config = cancelAtPeriodEnd ? cancelingConfig : statusConfig[status] || statusConfig[SubscriptionStatus.CANCELED];
|
|
58
|
+
|
|
59
|
+
return <span className={`${config.color} text-xs px-2 py-1 rounded-full font-medium`}>{config.label}</span>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Subscription Enums
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { ApiDataInterface, StripePriceInterface } from "../../../../core";
|
|
6
|
+
|
|
7
|
+
export enum SubscriptionStatus {
|
|
8
|
+
ACTIVE = "active",
|
|
9
|
+
PAST_DUE = "past_due",
|
|
10
|
+
UNPAID = "unpaid",
|
|
11
|
+
CANCELED = "canceled",
|
|
12
|
+
INCOMPLETE = "incomplete",
|
|
13
|
+
INCOMPLETE_EXPIRED = "incomplete_expired",
|
|
14
|
+
TRIALING = "trialing",
|
|
15
|
+
PAUSED = "paused",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Subscription Interfaces
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface StripeSubscriptionInterface extends ApiDataInterface {
|
|
23
|
+
get stripeSubscriptionId(): string;
|
|
24
|
+
get status(): SubscriptionStatus;
|
|
25
|
+
get currentPeriodStart(): Date;
|
|
26
|
+
get currentPeriodEnd(): Date;
|
|
27
|
+
get cancelAtPeriodEnd(): boolean;
|
|
28
|
+
get canceledAt(): Date | undefined;
|
|
29
|
+
get trialStart(): Date | undefined;
|
|
30
|
+
get trialEnd(): Date | undefined;
|
|
31
|
+
get price(): StripePriceInterface | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Subscription Input DTOs
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export type StripeSubscriptionInput = {
|
|
39
|
+
id: string;
|
|
40
|
+
// For CREATE - goes to relationships.stripePrice
|
|
41
|
+
priceId?: string;
|
|
42
|
+
// For CHANGE-PLAN - goes to attributes.priceId
|
|
43
|
+
newPriceId?: string;
|
|
44
|
+
// For CANCEL - goes to attributes.cancelImmediately
|
|
45
|
+
cancelImmediately?: boolean;
|
|
46
|
+
// Shared optional fields
|
|
47
|
+
quantity?: number;
|
|
48
|
+
trialPeriodDays?: number;
|
|
49
|
+
paymentMethodId?: string;
|
|
50
|
+
metadata?: Record<string, any>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Subscription Response Types (for SCA payment confirmation)
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
export interface StripeSubscriptionCreateMeta {
|
|
58
|
+
clientSecret: string | null;
|
|
59
|
+
paymentIntentId: string | null;
|
|
60
|
+
requiresAction: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface StripeSubscriptionCreateResponse {
|
|
64
|
+
subscription: StripeSubscriptionInterface;
|
|
65
|
+
meta: StripeSubscriptionCreateMeta;
|
|
66
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef, PreviousRef } from "../../../../core";
|
|
2
|
+
import { ProrationPreviewInterface } from "../../stripe-invoice/data/stripe-invoice.interface";
|
|
3
|
+
import {
|
|
4
|
+
StripeSubscriptionCreateMeta,
|
|
5
|
+
StripeSubscriptionCreateResponse,
|
|
6
|
+
StripeSubscriptionInput,
|
|
7
|
+
StripeSubscriptionInterface,
|
|
8
|
+
} from "./stripe-subscription.interface";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Customer-facing billing service for managing subscriptions, payments, and usage
|
|
12
|
+
*/
|
|
13
|
+
export class StripeSubscriptionService extends AbstractService {
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Subscription Methods
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* List all subscriptions for the current user
|
|
20
|
+
*/
|
|
21
|
+
static async listSubscriptions(params?: {
|
|
22
|
+
next?: NextRef;
|
|
23
|
+
prev?: PreviousRef;
|
|
24
|
+
}): Promise<StripeSubscriptionInterface[]> {
|
|
25
|
+
const endpoint = new EndpointCreator({
|
|
26
|
+
endpoint: Modules.StripeSubscription,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return this.callApi({
|
|
30
|
+
type: Modules.StripeSubscription,
|
|
31
|
+
method: HttpMethod.GET,
|
|
32
|
+
endpoint: endpoint.generate(),
|
|
33
|
+
next: params?.next,
|
|
34
|
+
previous: params?.prev,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a specific subscription by ID
|
|
40
|
+
*/
|
|
41
|
+
static async getSubscription(params: { subscriptionId: string }): Promise<StripeSubscriptionInterface> {
|
|
42
|
+
const endpoint = new EndpointCreator({
|
|
43
|
+
endpoint: Modules.StripeSubscription,
|
|
44
|
+
id: params.subscriptionId,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return this.callApi<StripeSubscriptionInterface>({
|
|
48
|
+
type: Modules.StripeSubscription,
|
|
49
|
+
method: HttpMethod.GET,
|
|
50
|
+
endpoint: endpoint.generate(),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a new subscription
|
|
56
|
+
* Returns subscription data along with meta containing SCA payment confirmation details
|
|
57
|
+
*/
|
|
58
|
+
static async createSubscription(params: StripeSubscriptionInput): Promise<StripeSubscriptionCreateResponse> {
|
|
59
|
+
const endpoint = new EndpointCreator({
|
|
60
|
+
endpoint: Modules.StripeSubscription,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await this.callApiWithMeta<StripeSubscriptionInterface>({
|
|
64
|
+
type: Modules.StripeSubscription,
|
|
65
|
+
method: HttpMethod.POST,
|
|
66
|
+
endpoint: endpoint.generate(),
|
|
67
|
+
input: params,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
subscription: result.data,
|
|
72
|
+
meta: (result.meta as StripeSubscriptionCreateMeta) ?? {
|
|
73
|
+
clientSecret: null,
|
|
74
|
+
paymentIntentId: null,
|
|
75
|
+
requiresAction: false,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Change the plan of an existing subscription
|
|
82
|
+
*/
|
|
83
|
+
static async changePlan(params: StripeSubscriptionInput): Promise<StripeSubscriptionInterface> {
|
|
84
|
+
const endpoint = new EndpointCreator({
|
|
85
|
+
endpoint: Modules.StripeSubscription,
|
|
86
|
+
id: params.id,
|
|
87
|
+
childEndpoint: "change-plan",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return this.callApi<StripeSubscriptionInterface>({
|
|
91
|
+
type: Modules.StripeSubscription,
|
|
92
|
+
method: HttpMethod.PUT,
|
|
93
|
+
endpoint: endpoint.generate(),
|
|
94
|
+
input: params,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get a proration preview for a plan change
|
|
100
|
+
*/
|
|
101
|
+
static async getProrationPreview(params: {
|
|
102
|
+
subscriptionId: string;
|
|
103
|
+
newPriceId: string;
|
|
104
|
+
quantity?: number;
|
|
105
|
+
}): Promise<ProrationPreviewInterface> {
|
|
106
|
+
const endpoint = new EndpointCreator({
|
|
107
|
+
endpoint: Modules.StripeSubscription,
|
|
108
|
+
id: params.subscriptionId,
|
|
109
|
+
childEndpoint: "proration-preview",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
endpoint.addAdditionalParam("newPriceId", params.newPriceId);
|
|
113
|
+
if (params.quantity) {
|
|
114
|
+
endpoint.addAdditionalParam("quantity", params.quantity.toString());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return this.callApi({
|
|
118
|
+
type: Modules.StripeSubscription,
|
|
119
|
+
method: HttpMethod.GET,
|
|
120
|
+
endpoint: endpoint.generate(),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cancel a subscription
|
|
126
|
+
*/
|
|
127
|
+
static async cancelSubscription(params: StripeSubscriptionInput): Promise<StripeSubscriptionInterface> {
|
|
128
|
+
const endpoint = new EndpointCreator({
|
|
129
|
+
endpoint: Modules.StripeSubscription,
|
|
130
|
+
id: params.id,
|
|
131
|
+
childEndpoint: "cancel",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return this.callApi<StripeSubscriptionInterface>({
|
|
135
|
+
type: Modules.StripeSubscription,
|
|
136
|
+
method: HttpMethod.POST,
|
|
137
|
+
endpoint: endpoint.generate(),
|
|
138
|
+
input: params,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Pause a subscription
|
|
144
|
+
*/
|
|
145
|
+
static async pauseSubscription(params: { subscriptionId: string }): Promise<StripeSubscriptionInterface> {
|
|
146
|
+
const endpoint = new EndpointCreator({
|
|
147
|
+
endpoint: Modules.StripeSubscription,
|
|
148
|
+
id: params.subscriptionId,
|
|
149
|
+
childEndpoint: "pause",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return this.callApi<StripeSubscriptionInterface>({
|
|
153
|
+
type: Modules.StripeSubscription,
|
|
154
|
+
method: HttpMethod.POST,
|
|
155
|
+
endpoint: endpoint.generate(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resume a paused subscription
|
|
161
|
+
*/
|
|
162
|
+
static async resumeSubscription(params: { subscriptionId: string }): Promise<StripeSubscriptionInterface> {
|
|
163
|
+
const endpoint = new EndpointCreator({
|
|
164
|
+
endpoint: Modules.StripeSubscription,
|
|
165
|
+
id: params.subscriptionId,
|
|
166
|
+
childEndpoint: "resume",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return this.callApi<StripeSubscriptionInterface>({
|
|
170
|
+
type: Modules.StripeSubscription,
|
|
171
|
+
method: HttpMethod.POST,
|
|
172
|
+
endpoint: endpoint.generate(),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Sync a subscription with the latest data from Stripe
|
|
178
|
+
* This is useful after payment confirmation to get the updated status
|
|
179
|
+
*/
|
|
180
|
+
static async syncSubscription(params: { subscriptionId: string }): Promise<StripeSubscriptionInterface> {
|
|
181
|
+
const endpoint = new EndpointCreator({
|
|
182
|
+
endpoint: Modules.StripeSubscription,
|
|
183
|
+
id: params.subscriptionId,
|
|
184
|
+
childEndpoint: "sync",
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return this.callApi<StripeSubscriptionInterface>({
|
|
188
|
+
type: Modules.StripeSubscription,
|
|
189
|
+
method: HttpMethod.POST,
|
|
190
|
+
endpoint: endpoint.generate(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../../core";
|
|
2
|
+
import { StripePriceInterface } from "../../stripe-price";
|
|
3
|
+
import {
|
|
4
|
+
StripeSubscriptionInput,
|
|
5
|
+
StripeSubscriptionInterface,
|
|
6
|
+
SubscriptionStatus,
|
|
7
|
+
} from "./stripe-subscription.interface";
|
|
8
|
+
|
|
9
|
+
export class StripeSubscription extends AbstractApiData implements StripeSubscriptionInterface {
|
|
10
|
+
private _stripeSubscriptionId?: string;
|
|
11
|
+
private _status?: SubscriptionStatus;
|
|
12
|
+
private _currentPeriodStart?: Date;
|
|
13
|
+
private _currentPeriodEnd?: Date;
|
|
14
|
+
private _cancelAtPeriodEnd: boolean = false;
|
|
15
|
+
private _canceledAt?: Date;
|
|
16
|
+
private _trialStart?: Date;
|
|
17
|
+
private _trialEnd?: Date;
|
|
18
|
+
private _price?: StripePriceInterface;
|
|
19
|
+
|
|
20
|
+
get stripeSubscriptionId(): string {
|
|
21
|
+
if (!this._stripeSubscriptionId) throw new Error("stripeSubscriptionId is not defined");
|
|
22
|
+
return this._stripeSubscriptionId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get status(): SubscriptionStatus {
|
|
26
|
+
if (!this._status) throw new Error("status is not defined");
|
|
27
|
+
return this._status;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get currentPeriodStart(): Date {
|
|
31
|
+
if (!this._currentPeriodStart) throw new Error("currentPeriodStart is not defined");
|
|
32
|
+
return this._currentPeriodStart;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get currentPeriodEnd(): Date {
|
|
36
|
+
if (!this._currentPeriodEnd) throw new Error("currentPeriodEnd is not defined");
|
|
37
|
+
return this._currentPeriodEnd;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get cancelAtPeriodEnd(): boolean {
|
|
41
|
+
return this._cancelAtPeriodEnd;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get canceledAt(): Date | undefined {
|
|
45
|
+
return this._canceledAt;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get trialStart(): Date | undefined {
|
|
49
|
+
return this._trialStart;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get trialEnd(): Date | undefined {
|
|
53
|
+
return this._trialEnd;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get price(): StripePriceInterface | undefined {
|
|
57
|
+
return this._price;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
61
|
+
super.rehydrate(data);
|
|
62
|
+
|
|
63
|
+
this._stripeSubscriptionId = data.jsonApi.attributes.stripeSubscriptionId;
|
|
64
|
+
this._status = data.jsonApi.attributes.status;
|
|
65
|
+
|
|
66
|
+
this._currentPeriodStart = data.jsonApi.attributes.currentPeriodStart
|
|
67
|
+
? new Date(data.jsonApi.attributes.currentPeriodStart)
|
|
68
|
+
: undefined;
|
|
69
|
+
this._currentPeriodEnd = data.jsonApi.attributes.currentPeriodEnd
|
|
70
|
+
? new Date(data.jsonApi.attributes.currentPeriodEnd)
|
|
71
|
+
: undefined;
|
|
72
|
+
|
|
73
|
+
this._cancelAtPeriodEnd = data.jsonApi.attributes.cancelAtPeriodEnd ?? false;
|
|
74
|
+
this._canceledAt = data.jsonApi.attributes.canceledAt ? new Date(data.jsonApi.attributes.canceledAt) : undefined;
|
|
75
|
+
|
|
76
|
+
this._trialStart = data.jsonApi.attributes.trialStart ? new Date(data.jsonApi.attributes.trialStart) : undefined;
|
|
77
|
+
this._trialEnd = data.jsonApi.attributes.trialEnd ? new Date(data.jsonApi.attributes.trialEnd) : undefined;
|
|
78
|
+
|
|
79
|
+
// Hydrate price relationship
|
|
80
|
+
this._price = this._readIncluded(data, "price", Modules.StripePrice) as StripePriceInterface;
|
|
81
|
+
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
createJsonApi(data: StripeSubscriptionInput): any {
|
|
86
|
+
const response: any = {
|
|
87
|
+
data: {
|
|
88
|
+
type: Modules.StripeSubscription.name,
|
|
89
|
+
id: data.id,
|
|
90
|
+
attributes: {},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// CREATE: priceId goes to relationships
|
|
95
|
+
if (data.priceId) {
|
|
96
|
+
response.data.relationships = {
|
|
97
|
+
stripePrice: {
|
|
98
|
+
data: {
|
|
99
|
+
type: Modules.StripePrice.name,
|
|
100
|
+
id: data.priceId,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// CHANGE-PLAN: newPriceId goes to attributes.priceId
|
|
107
|
+
if (data.newPriceId) {
|
|
108
|
+
response.data.attributes.priceId = data.newPriceId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// CANCEL: cancelImmediately goes to attributes
|
|
112
|
+
if (data.cancelImmediately !== undefined) {
|
|
113
|
+
response.data.attributes.cancelImmediately = data.cancelImmediately;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Shared optional fields
|
|
117
|
+
if (data.quantity !== undefined) {
|
|
118
|
+
response.data.attributes.quantity = data.quantity;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (data.trialPeriodDays !== undefined) {
|
|
122
|
+
response.data.attributes.trialPeriodDays = data.trialPeriodDays;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (data.paymentMethodId) {
|
|
126
|
+
response.data.attributes.paymentMethodId = data.paymentMethodId;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (data.metadata) {
|
|
130
|
+
response.data.attributes.metadata = data.metadata;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return response;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useConfirmSubscriptionPayment";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useStripe } from "@stripe/react-stripe-js";
|
|
4
|
+
import { useCallback, useState } from "react";
|
|
5
|
+
|
|
6
|
+
export interface ConfirmPaymentResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UseConfirmSubscriptionPaymentReturn {
|
|
12
|
+
confirmPayment: (clientSecret: string) => Promise<ConfirmPaymentResult>;
|
|
13
|
+
isConfirming: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook for confirming subscription payments using Stripe's SCA-compliant flow.
|
|
18
|
+
*
|
|
19
|
+
* This hook wraps stripe.confirmCardPayment() and handles:
|
|
20
|
+
* - Loading state during confirmation
|
|
21
|
+
* - Error handling for Stripe errors
|
|
22
|
+
* - 3D Secure authentication popups (handled automatically by Stripe)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const { confirmPayment, isConfirming } = useConfirmSubscriptionPayment();
|
|
27
|
+
*
|
|
28
|
+
* const handleCreateSubscription = async () => {
|
|
29
|
+
* const result = await StripeSubscriptionService.createSubscription({ priceId });
|
|
30
|
+
*
|
|
31
|
+
* if (result.meta.requiresAction && result.meta.clientSecret) {
|
|
32
|
+
* const confirmation = await confirmPayment(result.meta.clientSecret);
|
|
33
|
+
* if (!confirmation.success) {
|
|
34
|
+
* setError(confirmation.error);
|
|
35
|
+
* return;
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* // Subscription is now active
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function useConfirmSubscriptionPayment(): UseConfirmSubscriptionPaymentReturn {
|
|
44
|
+
const stripe = useStripe();
|
|
45
|
+
const [isConfirming, setIsConfirming] = useState(false);
|
|
46
|
+
|
|
47
|
+
const confirmPayment = useCallback(
|
|
48
|
+
async (clientSecret: string): Promise<ConfirmPaymentResult> => {
|
|
49
|
+
if (!stripe) {
|
|
50
|
+
console.error("[useConfirmSubscriptionPayment] Stripe not initialized");
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: "Payment system not initialized. Please refresh the page and try again.",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!clientSecret) {
|
|
58
|
+
console.error("[useConfirmSubscriptionPayment] No client secret provided");
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: "Payment confirmation failed. Missing payment details.",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setIsConfirming(true);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(clientSecret);
|
|
69
|
+
|
|
70
|
+
if (stripeError) {
|
|
71
|
+
console.error("[useConfirmSubscriptionPayment] Stripe error:", stripeError);
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: stripeError.message || "Payment confirmation failed. Please try again.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (paymentIntent?.status === "succeeded") {
|
|
79
|
+
return { success: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (paymentIntent?.status === "requires_action") {
|
|
83
|
+
// This shouldn't happen as Stripe handles 3DS inline, but handle it just in case
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: "Additional authentication required. Please complete the verification.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: "Payment could not be completed. Please try again.",
|
|
93
|
+
};
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
console.error("[useConfirmSubscriptionPayment] Unexpected error:", err);
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: err.message || "An unexpected error occurred during payment confirmation.",
|
|
99
|
+
};
|
|
100
|
+
} finally {
|
|
101
|
+
setIsConfirming(false);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[stripe],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
confirmPayment,
|
|
109
|
+
isConfirming,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ModuleFactory } from "../../../permissions";
|
|
2
|
+
import { StripeSubscription } from "./data";
|
|
3
|
+
|
|
4
|
+
export const StripeSubscriptionModule = (factory: ModuleFactory) =>
|
|
5
|
+
factory({
|
|
6
|
+
name: "stripe-subscriptions",
|
|
7
|
+
model: StripeSubscription,
|
|
8
|
+
moduleId: "5e8797ef-650b-4dd5-ac79-a2e530a6c7ba",
|
|
9
|
+
});
|