@digilogiclabs/saas-factory-payments 0.1.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.
@@ -0,0 +1,1651 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/web/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BillingPortal: () => BillingPortal,
34
+ CheckoutButton: () => CheckoutButton,
35
+ PaymentForm: () => PaymentForm,
36
+ PricingTable: () => PricingTable,
37
+ StripeProvider: () => StripeProvider,
38
+ confirmPayment: () => confirmPayment,
39
+ createCheckoutSession: () => createCheckoutSession,
40
+ createPaymentIntent: () => createPaymentIntent,
41
+ createPortalSession: () => createPortalSession,
42
+ formatStripeError: () => formatStripeError,
43
+ getStripe: () => getStripe2,
44
+ initializeStripe: () => initializeStripe,
45
+ redirectToCheckout: () => redirectToCheckout,
46
+ useCheckout: () => useCheckout,
47
+ useCustomer: () => useCustomer,
48
+ useSubscription: () => useSubscription
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/web/components/CheckoutButton.tsx
53
+ var import_react2 = require("react");
54
+ var import_react_stripe_js = require("@stripe/react-stripe-js");
55
+
56
+ // src/shared/providers/PaymentsProvider.tsx
57
+ var import_react = require("react");
58
+ var import_jsx_runtime = require("react/jsx-runtime");
59
+ var PaymentsContext = (0, import_react.createContext)(
60
+ void 0
61
+ );
62
+ var usePaymentsConfig = () => {
63
+ const context = (0, import_react.useContext)(PaymentsContext);
64
+ if (!context) {
65
+ throw new Error("usePaymentsConfig must be used within PaymentsProvider");
66
+ }
67
+ return context;
68
+ };
69
+
70
+ // src/shared/constants.ts
71
+ var CURRENCY_SYMBOLS = {
72
+ USD: "$",
73
+ EUR: "\u20AC",
74
+ GBP: "\xA3",
75
+ JPY: "\xA5",
76
+ CAD: "C$",
77
+ AUD: "A$",
78
+ CHF: "CHF",
79
+ CNY: "\xA5",
80
+ SEK: "kr",
81
+ NZD: "NZ$"
82
+ };
83
+ var DEFAULT_CURRENCY = "USD";
84
+ var ERROR_MESSAGES = {
85
+ PROVIDER_NOT_CONFIGURED: "Payments provider is not properly configured",
86
+ STRIPE_NOT_LOADED: "Stripe has not been loaded yet",
87
+ INVALID_PRICE_ID: "Invalid price ID provided",
88
+ INVALID_CUSTOMER_ID: "Invalid customer ID provided",
89
+ CHECKOUT_FAILED: "Checkout session creation failed",
90
+ PAYMENT_FAILED: "Payment processing failed",
91
+ SUBSCRIPTION_NOT_FOUND: "Subscription not found",
92
+ CUSTOMER_NOT_FOUND: "Customer not found"
93
+ };
94
+
95
+ // src/shared/utils/validation.ts
96
+ var validateEmail = (email) => {
97
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
98
+ return emailRegex.test(email);
99
+ };
100
+ var validateStripeId = (id, type) => {
101
+ const prefixes = {
102
+ customer: "cus_",
103
+ subscription: "sub_",
104
+ price: "price_",
105
+ product: "prod_",
106
+ payment_intent: "pi_"
107
+ };
108
+ return id.startsWith(prefixes[type]);
109
+ };
110
+
111
+ // src/web/components/CheckoutButton.tsx
112
+ var import_jsx_runtime2 = require("react/jsx-runtime");
113
+ var CheckoutButton = ({
114
+ priceId,
115
+ customerId,
116
+ customerEmail,
117
+ successUrl = typeof window !== "undefined" ? `${window.location.origin}/success` : "/success",
118
+ cancelUrl = typeof window !== "undefined" ? `${window.location.origin}/cancel` : "/cancel",
119
+ children,
120
+ className = "",
121
+ disabled = false,
122
+ allowPromotionCodes = true,
123
+ billingAddressCollection = "auto",
124
+ metadata,
125
+ trialPeriodDays,
126
+ onSuccess,
127
+ onError,
128
+ onLoading
129
+ }) => {
130
+ const stripe = (0, import_react_stripe_js.useStripe)();
131
+ const { isConfigured } = usePaymentsConfig();
132
+ const [loading, setLoading] = (0, import_react2.useState)(false);
133
+ const handleCheckout = async () => {
134
+ if (!stripe) {
135
+ const error = ERROR_MESSAGES.STRIPE_NOT_LOADED;
136
+ onError?.(error);
137
+ console.error(error);
138
+ return;
139
+ }
140
+ if (!isConfigured) {
141
+ const error = ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED;
142
+ onError?.(error);
143
+ console.error(error);
144
+ return;
145
+ }
146
+ if (!validateStripeId(priceId, "price")) {
147
+ const error = ERROR_MESSAGES.INVALID_PRICE_ID;
148
+ onError?.(error);
149
+ console.error(error);
150
+ return;
151
+ }
152
+ if (customerId && !validateStripeId(customerId, "customer")) {
153
+ const error = ERROR_MESSAGES.INVALID_CUSTOMER_ID;
154
+ onError?.(error);
155
+ console.error(error);
156
+ return;
157
+ }
158
+ setLoading(true);
159
+ onLoading?.(true);
160
+ try {
161
+ const response = await fetch("/api/payments/create-checkout-session", {
162
+ method: "POST",
163
+ headers: {
164
+ "Content-Type": "application/json"
165
+ },
166
+ body: JSON.stringify({
167
+ priceId,
168
+ customerId,
169
+ customerEmail,
170
+ successUrl,
171
+ cancelUrl,
172
+ allowPromotionCodes,
173
+ billingAddressCollection,
174
+ metadata,
175
+ trialPeriodDays
176
+ })
177
+ });
178
+ if (!response.ok) {
179
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
180
+ throw new Error(errorData.error || ERROR_MESSAGES.CHECKOUT_FAILED);
181
+ }
182
+ const { sessionId } = await response.json();
183
+ if (!sessionId) {
184
+ throw new Error("No session ID returned from server");
185
+ }
186
+ const result = await stripe.redirectToCheckout({ sessionId });
187
+ if (result.error) {
188
+ throw new Error(result.error.message || ERROR_MESSAGES.CHECKOUT_FAILED);
189
+ }
190
+ onSuccess?.();
191
+ } catch (error) {
192
+ const errorMessage = error instanceof Error ? error.message : ERROR_MESSAGES.CHECKOUT_FAILED;
193
+ onError?.(errorMessage);
194
+ console.error("Checkout error:", error);
195
+ } finally {
196
+ setLoading(false);
197
+ onLoading?.(false);
198
+ }
199
+ };
200
+ const isDisabled = !stripe || loading || disabled || !isConfigured;
201
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
202
+ "button",
203
+ {
204
+ onClick: handleCheckout,
205
+ disabled: isDisabled,
206
+ className: `
207
+ inline-flex items-center justify-center px-4 py-2
208
+ border border-transparent text-sm font-medium rounded-md
209
+ text-white bg-blue-600 hover:bg-blue-700
210
+ focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
211
+ disabled:opacity-50 disabled:cursor-not-allowed
212
+ transition-colors duration-200
213
+ ${className}
214
+ `,
215
+ "aria-label": typeof children === "string" ? children : "Checkout",
216
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
218
+ "svg",
219
+ {
220
+ className: "animate-spin -ml-1 mr-3 h-4 w-4 text-white",
221
+ xmlns: "http://www.w3.org/2000/svg",
222
+ fill: "none",
223
+ viewBox: "0 0 24 24",
224
+ children: [
225
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
226
+ "circle",
227
+ {
228
+ className: "opacity-25",
229
+ cx: "12",
230
+ cy: "12",
231
+ r: "10",
232
+ stroke: "currentColor",
233
+ strokeWidth: "4"
234
+ }
235
+ ),
236
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
237
+ "path",
238
+ {
239
+ className: "opacity-75",
240
+ fill: "currentColor",
241
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
242
+ }
243
+ )
244
+ ]
245
+ }
246
+ ),
247
+ "Processing..."
248
+ ] }) : children
249
+ }
250
+ );
251
+ };
252
+
253
+ // src/shared/utils/formatting.ts
254
+ var formatCurrency = (amount, currency = DEFAULT_CURRENCY, options = {}) => {
255
+ const { showSymbol = true, showCents = true, locale = "en-US" } = options;
256
+ const currencyCode = currency.toUpperCase();
257
+ const isZeroDecimalCurrency = ["JPY", "KRW", "VND", "CLP"].includes(
258
+ currencyCode
259
+ );
260
+ const displayAmount = isZeroDecimalCurrency ? amount : amount / 100;
261
+ if (showSymbol) {
262
+ try {
263
+ return new Intl.NumberFormat(locale, {
264
+ style: "currency",
265
+ currency: currencyCode,
266
+ minimumFractionDigits: showCents && !isZeroDecimalCurrency ? 2 : 0,
267
+ maximumFractionDigits: showCents && !isZeroDecimalCurrency ? 2 : 0
268
+ }).format(displayAmount);
269
+ } catch (error) {
270
+ const symbol = CURRENCY_SYMBOLS[currencyCode] || currencyCode;
271
+ const formattedAmount = showCents && !isZeroDecimalCurrency ? displayAmount.toFixed(2) : Math.round(displayAmount).toString();
272
+ return `${symbol}${formattedAmount}`;
273
+ }
274
+ }
275
+ return showCents && !isZeroDecimalCurrency ? displayAmount.toFixed(2) : Math.round(displayAmount).toString();
276
+ };
277
+ var formatPricingPlan = (plan) => {
278
+ const price = formatCurrency(plan.price, plan.currency);
279
+ const interval = plan.intervalCount && plan.intervalCount > 1 ? `${plan.intervalCount} ${plan.interval}s` : plan.interval;
280
+ return `${price}/${interval}`;
281
+ };
282
+ var formatTrialPeriod = (days) => {
283
+ if (days === 0) return "No trial";
284
+ if (days === 1) return "1 day trial";
285
+ if (days < 7) return `${days} days trial`;
286
+ if (days === 7) return "1 week trial";
287
+ if (days < 30) return `${Math.floor(days / 7)} weeks trial`;
288
+ if (days === 30) return "1 month trial";
289
+ return `${Math.floor(days / 30)} months trial`;
290
+ };
291
+
292
+ // src/web/components/PricingTable.tsx
293
+ var import_jsx_runtime3 = require("react/jsx-runtime");
294
+ var PricingTable = ({
295
+ plans,
296
+ customerId,
297
+ customerEmail,
298
+ successUrl,
299
+ cancelUrl,
300
+ className = "",
301
+ onPlanSelect,
302
+ onCheckoutSuccess,
303
+ onCheckoutError,
304
+ showFeatures = true,
305
+ showTrialInfo = true,
306
+ layout = "grid",
307
+ maxColumns = 3
308
+ }) => {
309
+ if (!plans || plans.length === 0) {
310
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-center py-8 text-gray-500", children: "No pricing plans available" });
311
+ }
312
+ const gridCols = Math.min(plans.length, maxColumns);
313
+ const gridClass = layout === "grid" ? `grid gap-6 ${gridCols === 1 ? "grid-cols-1" : gridCols === 2 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"}` : "space-y-6";
314
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `pricing-table ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: gridClass, children: plans.map((plan) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
315
+ PricingCard,
316
+ {
317
+ plan,
318
+ customerId,
319
+ customerEmail,
320
+ successUrl,
321
+ cancelUrl,
322
+ onSelect: () => onPlanSelect?.(plan),
323
+ onCheckoutSuccess: () => onCheckoutSuccess?.(plan),
324
+ onCheckoutError: (error) => onCheckoutError?.(plan, error),
325
+ showFeatures,
326
+ showTrialInfo
327
+ },
328
+ plan.id
329
+ )) }) });
330
+ };
331
+ var PricingCard = ({
332
+ plan,
333
+ customerId,
334
+ customerEmail,
335
+ successUrl,
336
+ cancelUrl,
337
+ onSelect,
338
+ onCheckoutSuccess,
339
+ onCheckoutError,
340
+ showFeatures = true,
341
+ showTrialInfo = true
342
+ }) => {
343
+ const isPopular = plan.popular;
344
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
345
+ "div",
346
+ {
347
+ className: `
348
+ relative bg-white rounded-lg shadow-lg overflow-hidden
349
+ ${isPopular ? "ring-2 ring-blue-500 ring-opacity-50" : "border border-gray-200"}
350
+ transition-transform duration-200 hover:scale-105
351
+ `,
352
+ children: [
353
+ isPopular && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute top-0 left-0 right-0 bg-blue-500 text-white text-center py-2 text-sm font-medium", children: "Most Popular" }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `p-6 ${isPopular ? "pt-12" : ""}`, children: [
355
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center mb-6", children: [
356
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-xl font-semibold text-gray-900 mb-2", children: plan.name }),
357
+ plan.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-gray-600 text-sm mb-4", children: plan.description }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-3xl font-bold text-gray-900", children: formatPricingPlan(plan) }) }),
359
+ showTrialInfo && plan.trialPeriodDays && plan.trialPeriodDays > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-sm text-green-600 font-medium", children: formatTrialPeriod(plan.trialPeriodDays) })
360
+ ] }),
361
+ showFeatures && plan.features && plan.features.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-6", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "space-y-3", children: plan.features.map((feature, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "flex items-start", children: [
362
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
363
+ "svg",
364
+ {
365
+ className: "flex-shrink-0 w-5 h-5 text-green-500 mt-0.5 mr-3",
366
+ fill: "none",
367
+ stroke: "currentColor",
368
+ viewBox: "0 0 24 24",
369
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
370
+ "path",
371
+ {
372
+ strokeLinecap: "round",
373
+ strokeLinejoin: "round",
374
+ strokeWidth: 2,
375
+ d: "M5 13l4 4L19 7"
376
+ }
377
+ )
378
+ }
379
+ ),
380
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-gray-700 text-sm", children: feature })
381
+ ] }, index)) }) }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
383
+ CheckoutButton,
384
+ {
385
+ priceId: plan.stripePriceId,
386
+ customerId,
387
+ customerEmail,
388
+ successUrl,
389
+ cancelUrl,
390
+ trialPeriodDays: plan.trialPeriodDays,
391
+ metadata: plan.metadata,
392
+ onSuccess: onCheckoutSuccess,
393
+ onError: onCheckoutError,
394
+ className: `
395
+ w-full justify-center py-3 px-4 text-base font-medium
396
+ ${isPopular ? "bg-blue-600 hover:bg-blue-700 text-white" : "bg-gray-100 hover:bg-gray-200 text-gray-900 border border-gray-300"}
397
+ `,
398
+ children: "Get Started"
399
+ }
400
+ ) })
401
+ ] })
402
+ ]
403
+ }
404
+ );
405
+ };
406
+
407
+ // src/web/components/PaymentForm.tsx
408
+ var import_react3 = require("react");
409
+ var import_react_stripe_js2 = require("@stripe/react-stripe-js");
410
+ var import_jsx_runtime4 = require("react/jsx-runtime");
411
+ var PaymentForm = ({
412
+ clientSecret,
413
+ customerId,
414
+ customerEmail,
415
+ amount,
416
+ currency = "usd",
417
+ description,
418
+ metadata,
419
+ onSuccess,
420
+ onError,
421
+ onLoading,
422
+ className = "",
423
+ showBillingDetails = true,
424
+ elementType = "payment"
425
+ }) => {
426
+ const stripe = (0, import_react_stripe_js2.useStripe)();
427
+ const elements = (0, import_react_stripe_js2.useElements)();
428
+ const { isConfigured } = usePaymentsConfig();
429
+ const [loading, setLoading] = (0, import_react3.useState)(false);
430
+ const [email, setEmail] = (0, import_react3.useState)(customerEmail || "");
431
+ const [billingDetails, setBillingDetails] = (0, import_react3.useState)({
432
+ name: "",
433
+ email: customerEmail || "",
434
+ phone: "",
435
+ address: {
436
+ line1: "",
437
+ line2: "",
438
+ city: "",
439
+ state: "",
440
+ postal_code: "",
441
+ country: "US"
442
+ }
443
+ });
444
+ const handleSubmit = async (event) => {
445
+ event.preventDefault();
446
+ if (!stripe || !elements) {
447
+ const error = ERROR_MESSAGES.STRIPE_NOT_LOADED;
448
+ onError?.(error);
449
+ return;
450
+ }
451
+ if (!isConfigured) {
452
+ const error = ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED;
453
+ onError?.(error);
454
+ return;
455
+ }
456
+ if (showBillingDetails && email && !validateEmail(email)) {
457
+ onError?.("Please enter a valid email address");
458
+ return;
459
+ }
460
+ setLoading(true);
461
+ onLoading?.(true);
462
+ try {
463
+ let result;
464
+ if (elementType === "payment" && clientSecret) {
465
+ result = await stripe.confirmPayment({
466
+ elements,
467
+ confirmParams: {
468
+ return_url: window.location.href,
469
+ receipt_email: email || billingDetails.email
470
+ },
471
+ redirect: "if_required"
472
+ });
473
+ } else if (elementType === "card") {
474
+ const cardElement = elements.getElement(import_react_stripe_js2.CardElement);
475
+ if (!cardElement) {
476
+ throw new Error("Card element not found");
477
+ }
478
+ if (clientSecret) {
479
+ result = await stripe.confirmCardPayment(clientSecret, {
480
+ payment_method: {
481
+ card: cardElement,
482
+ billing_details: showBillingDetails ? {
483
+ name: billingDetails.name || void 0,
484
+ email: billingDetails.email || void 0,
485
+ phone: billingDetails.phone || void 0,
486
+ address: billingDetails.address.line1 ? billingDetails.address : void 0
487
+ } : void 0
488
+ }
489
+ });
490
+ } else if (amount) {
491
+ const response = await fetch("/api/payments/create-payment-intent", {
492
+ method: "POST",
493
+ headers: { "Content-Type": "application/json" },
494
+ body: JSON.stringify({
495
+ amount,
496
+ currency,
497
+ customerId,
498
+ description,
499
+ metadata
500
+ })
501
+ });
502
+ if (!response.ok) {
503
+ throw new Error("Failed to create payment intent");
504
+ }
505
+ const { client_secret } = await response.json();
506
+ result = await stripe.confirmCardPayment(client_secret, {
507
+ payment_method: {
508
+ card: cardElement,
509
+ billing_details: showBillingDetails ? {
510
+ name: billingDetails.name || void 0,
511
+ email: billingDetails.email || void 0,
512
+ phone: billingDetails.phone || void 0,
513
+ address: billingDetails.address.line1 ? billingDetails.address : void 0
514
+ } : void 0
515
+ }
516
+ });
517
+ } else {
518
+ throw new Error("Either clientSecret or amount is required");
519
+ }
520
+ }
521
+ if (result?.error) {
522
+ throw new Error(result.error.message || ERROR_MESSAGES.PAYMENT_FAILED);
523
+ }
524
+ if (result?.paymentIntent?.status === "succeeded") {
525
+ onSuccess?.(result.paymentIntent);
526
+ }
527
+ } catch (error) {
528
+ const errorMessage = error instanceof Error ? error.message : ERROR_MESSAGES.PAYMENT_FAILED;
529
+ onError?.(errorMessage);
530
+ console.error("Payment error:", error);
531
+ } finally {
532
+ setLoading(false);
533
+ onLoading?.(false);
534
+ }
535
+ };
536
+ const cardElementOptions = {
537
+ style: {
538
+ base: {
539
+ fontSize: "16px",
540
+ color: "#424770",
541
+ "::placeholder": {
542
+ color: "#aab7c4"
543
+ }
544
+ },
545
+ invalid: {
546
+ color: "#9e2146"
547
+ }
548
+ }
549
+ };
550
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: handleSubmit, className: `payment-form ${className}`, children: [
551
+ showBillingDetails && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-6 space-y-4", children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
553
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
554
+ "label",
555
+ {
556
+ htmlFor: "email",
557
+ className: "block text-sm font-medium text-gray-700 mb-1",
558
+ children: "Email"
559
+ }
560
+ ),
561
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
562
+ "input",
563
+ {
564
+ type: "email",
565
+ id: "email",
566
+ value: email,
567
+ onChange: (e) => {
568
+ setEmail(e.target.value);
569
+ setBillingDetails((prev) => ({ ...prev, email: e.target.value }));
570
+ },
571
+ className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500",
572
+ placeholder: "your@email.com",
573
+ required: true
574
+ }
575
+ )
576
+ ] }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
578
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
579
+ "label",
580
+ {
581
+ htmlFor: "name",
582
+ className: "block text-sm font-medium text-gray-700 mb-1",
583
+ children: "Full Name"
584
+ }
585
+ ),
586
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
587
+ "input",
588
+ {
589
+ type: "text",
590
+ id: "name",
591
+ value: billingDetails.name,
592
+ onChange: (e) => setBillingDetails((prev) => ({ ...prev, name: e.target.value })),
593
+ className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500",
594
+ placeholder: "John Doe"
595
+ }
596
+ )
597
+ ] })
598
+ ] }),
599
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-6", children: [
600
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Payment Information" }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-3 border border-gray-300 rounded-md", children: elementType === "payment" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js2.PaymentElement, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js2.CardElement, { options: cardElementOptions }) })
602
+ ] }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
604
+ "button",
605
+ {
606
+ type: "submit",
607
+ disabled: !stripe || loading,
608
+ className: "w-full bg-blue-600 text-white py-3 px-4 rounded-md font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200",
609
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
610
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
611
+ "svg",
612
+ {
613
+ className: "animate-spin -ml-1 mr-3 h-4 w-4 text-white inline",
614
+ xmlns: "http://www.w3.org/2000/svg",
615
+ fill: "none",
616
+ viewBox: "0 0 24 24",
617
+ children: [
618
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
619
+ "circle",
620
+ {
621
+ className: "opacity-25",
622
+ cx: "12",
623
+ cy: "12",
624
+ r: "10",
625
+ stroke: "currentColor",
626
+ strokeWidth: "4"
627
+ }
628
+ ),
629
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
630
+ "path",
631
+ {
632
+ className: "opacity-75",
633
+ fill: "currentColor",
634
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
635
+ }
636
+ )
637
+ ]
638
+ }
639
+ ),
640
+ "Processing..."
641
+ ] }) : `Pay ${amount ? `$${(amount / 100).toFixed(2)}` : ""}`
642
+ }
643
+ )
644
+ ] });
645
+ };
646
+
647
+ // src/web/components/BillingPortal.tsx
648
+ var import_react4 = require("react");
649
+ var import_jsx_runtime5 = require("react/jsx-runtime");
650
+ var BillingPortal = ({
651
+ customerId,
652
+ returnUrl = typeof window !== "undefined" ? window.location.href : "/",
653
+ children,
654
+ className = "",
655
+ onSuccess,
656
+ onError,
657
+ onLoading
658
+ }) => {
659
+ const { isConfigured } = usePaymentsConfig();
660
+ const [loading, setLoading] = (0, import_react4.useState)(false);
661
+ const handleOpenPortal = async () => {
662
+ if (!isConfigured) {
663
+ const error = ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED;
664
+ onError?.(error);
665
+ console.error(error);
666
+ return;
667
+ }
668
+ if (!validateStripeId(customerId, "customer")) {
669
+ const error = ERROR_MESSAGES.INVALID_CUSTOMER_ID;
670
+ onError?.(error);
671
+ console.error(error);
672
+ return;
673
+ }
674
+ setLoading(true);
675
+ onLoading?.(true);
676
+ try {
677
+ const response = await fetch("/api/payments/create-portal-session", {
678
+ method: "POST",
679
+ headers: {
680
+ "Content-Type": "application/json"
681
+ },
682
+ body: JSON.stringify({
683
+ customerId,
684
+ returnUrl
685
+ })
686
+ });
687
+ if (!response.ok) {
688
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
689
+ throw new Error(errorData.error || "Failed to create portal session");
690
+ }
691
+ const { url } = await response.json();
692
+ if (!url) {
693
+ throw new Error("No portal URL returned from server");
694
+ }
695
+ window.location.href = url;
696
+ onSuccess?.();
697
+ } catch (error) {
698
+ const errorMessage = error instanceof Error ? error.message : "Failed to open billing portal";
699
+ onError?.(errorMessage);
700
+ console.error("Billing portal error:", error);
701
+ } finally {
702
+ setLoading(false);
703
+ onLoading?.(false);
704
+ }
705
+ };
706
+ const isDisabled = loading || !isConfigured || !customerId;
707
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
708
+ "button",
709
+ {
710
+ onClick: handleOpenPortal,
711
+ disabled: isDisabled,
712
+ className: `
713
+ inline-flex items-center justify-center px-4 py-2
714
+ border border-gray-300 text-sm font-medium rounded-md
715
+ text-gray-700 bg-white hover:bg-gray-50
716
+ focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
717
+ disabled:opacity-50 disabled:cursor-not-allowed
718
+ transition-colors duration-200
719
+ ${className}
720
+ `,
721
+ "aria-label": "Manage billing",
722
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
724
+ "svg",
725
+ {
726
+ className: "animate-spin -ml-1 mr-3 h-4 w-4 text-gray-700",
727
+ xmlns: "http://www.w3.org/2000/svg",
728
+ fill: "none",
729
+ viewBox: "0 0 24 24",
730
+ children: [
731
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
732
+ "circle",
733
+ {
734
+ className: "opacity-25",
735
+ cx: "12",
736
+ cy: "12",
737
+ r: "10",
738
+ stroke: "currentColor",
739
+ strokeWidth: "4"
740
+ }
741
+ ),
742
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
743
+ "path",
744
+ {
745
+ className: "opacity-75",
746
+ fill: "currentColor",
747
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
748
+ }
749
+ )
750
+ ]
751
+ }
752
+ ),
753
+ "Loading..."
754
+ ] }) : children || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
755
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
756
+ "svg",
757
+ {
758
+ className: "w-4 h-4 mr-2",
759
+ fill: "none",
760
+ stroke: "currentColor",
761
+ viewBox: "0 0 24 24",
762
+ children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
764
+ "path",
765
+ {
766
+ strokeLinecap: "round",
767
+ strokeLinejoin: "round",
768
+ strokeWidth: 2,
769
+ d: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
770
+ }
771
+ ),
772
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
773
+ "path",
774
+ {
775
+ strokeLinecap: "round",
776
+ strokeLinejoin: "round",
777
+ strokeWidth: 2,
778
+ d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
779
+ }
780
+ )
781
+ ]
782
+ }
783
+ ),
784
+ "Manage Billing"
785
+ ] })
786
+ }
787
+ );
788
+ };
789
+
790
+ // src/web/providers/StripeProvider.tsx
791
+ var import_react5 = __toESM(require("react"));
792
+ var import_stripe_js = require("@stripe/stripe-js");
793
+ var import_react_stripe_js3 = require("@stripe/react-stripe-js");
794
+ var import_jsx_runtime6 = require("react/jsx-runtime");
795
+ var stripePromise = null;
796
+ var getStripe = (publishableKey) => {
797
+ if (!stripePromise) {
798
+ stripePromise = (0, import_stripe_js.loadStripe)(publishableKey);
799
+ }
800
+ return stripePromise;
801
+ };
802
+ var StripeProvider = ({
803
+ children,
804
+ options = {}
805
+ }) => {
806
+ const { config, isConfigured } = usePaymentsConfig();
807
+ const stripe = import_react5.default.useMemo(() => {
808
+ if (!isConfigured) {
809
+ console.error(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
810
+ return null;
811
+ }
812
+ return getStripe(config.publishableKey);
813
+ }, [config.publishableKey, isConfigured]);
814
+ if (!isConfigured) {
815
+ if (config.environment === "development") {
816
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
817
+ "div",
818
+ {
819
+ style: {
820
+ padding: "20px",
821
+ backgroundColor: "#fee",
822
+ border: "1px solid #fcc",
823
+ borderRadius: "4px",
824
+ margin: "10px",
825
+ fontFamily: "monospace"
826
+ },
827
+ children: [
828
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Payments Provider Error:" }),
829
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("br", {}),
830
+ ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED,
831
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("br", {}),
832
+ "Please check your PaymentsProvider configuration."
833
+ ]
834
+ }
835
+ );
836
+ }
837
+ return null;
838
+ }
839
+ const elementsOptions = {
840
+ fonts: options.fonts,
841
+ locale: options.locale,
842
+ appearance: {
843
+ theme: "stripe"
844
+ }
845
+ };
846
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js3.Elements, { stripe, options: elementsOptions, children });
847
+ };
848
+
849
+ // src/web/hooks/useSubscription.ts
850
+ var import_react6 = require("react");
851
+ var useSubscription = (options = {}) => {
852
+ const { customerId, subscriptionId, autoFetch = true, onError } = options;
853
+ const [subscription, setSubscription] = (0, import_react6.useState)(null);
854
+ const [subscriptions, setSubscriptions] = (0, import_react6.useState)([]);
855
+ const [loading, setLoading] = (0, import_react6.useState)(false);
856
+ const [error, setError] = (0, import_react6.useState)(null);
857
+ const handleError = (0, import_react6.useCallback)(
858
+ (errorMessage) => {
859
+ setError(errorMessage);
860
+ onError?.(errorMessage);
861
+ console.error("Subscription error:", errorMessage);
862
+ },
863
+ [onError]
864
+ );
865
+ const fetchSubscription = (0, import_react6.useCallback)(
866
+ async (id) => {
867
+ if (!validateStripeId(id, "subscription")) {
868
+ handleError("Invalid subscription ID");
869
+ return null;
870
+ }
871
+ try {
872
+ const response = await fetch(`/api/payments/subscription/${id}`);
873
+ if (!response.ok) {
874
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
875
+ throw new Error(
876
+ errorData.error || ERROR_MESSAGES.SUBSCRIPTION_NOT_FOUND
877
+ );
878
+ }
879
+ const data = await response.json();
880
+ return data;
881
+ } catch (err) {
882
+ const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.SUBSCRIPTION_NOT_FOUND;
883
+ handleError(errorMessage);
884
+ return null;
885
+ }
886
+ },
887
+ [handleError]
888
+ );
889
+ const fetchCustomerSubscriptions = (0, import_react6.useCallback)(
890
+ async (id) => {
891
+ if (!validateStripeId(id, "customer")) {
892
+ handleError("Invalid customer ID");
893
+ return [];
894
+ }
895
+ try {
896
+ const response = await fetch(
897
+ `/api/payments/customer/${id}/subscriptions`
898
+ );
899
+ if (!response.ok) {
900
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
901
+ throw new Error(errorData.error || ERROR_MESSAGES.CUSTOMER_NOT_FOUND);
902
+ }
903
+ const data = await response.json();
904
+ return data.subscriptions || [];
905
+ } catch (err) {
906
+ const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CUSTOMER_NOT_FOUND;
907
+ handleError(errorMessage);
908
+ return [];
909
+ }
910
+ },
911
+ [handleError]
912
+ );
913
+ const refetch = (0, import_react6.useCallback)(async () => {
914
+ if (!customerId && !subscriptionId) {
915
+ handleError("Either customerId or subscriptionId is required");
916
+ return;
917
+ }
918
+ setLoading(true);
919
+ setError(null);
920
+ try {
921
+ if (subscriptionId) {
922
+ const sub = await fetchSubscription(subscriptionId);
923
+ setSubscription(sub);
924
+ }
925
+ if (customerId) {
926
+ const subs = await fetchCustomerSubscriptions(customerId);
927
+ setSubscriptions(subs);
928
+ if (!subscriptionId && subs.length > 0) {
929
+ const activeSub = subs.find((s) => s.status === "active") || subs[0];
930
+ setSubscription(activeSub);
931
+ }
932
+ }
933
+ } finally {
934
+ setLoading(false);
935
+ }
936
+ }, [
937
+ customerId,
938
+ subscriptionId,
939
+ fetchSubscription,
940
+ fetchCustomerSubscriptions,
941
+ handleError
942
+ ]);
943
+ const cancel = (0, import_react6.useCallback)(
944
+ async (targetSubscriptionId) => {
945
+ const idToCancel = targetSubscriptionId || subscriptionId;
946
+ if (!idToCancel) {
947
+ handleError("Subscription ID is required to cancel");
948
+ return false;
949
+ }
950
+ if (!validateStripeId(idToCancel, "subscription")) {
951
+ handleError("Invalid subscription ID");
952
+ return false;
953
+ }
954
+ try {
955
+ const response = await fetch(
956
+ `/api/payments/subscription/${idToCancel}/cancel`,
957
+ {
958
+ method: "POST",
959
+ headers: { "Content-Type": "application/json" }
960
+ }
961
+ );
962
+ if (!response.ok) {
963
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
964
+ throw new Error(errorData.error || "Failed to cancel subscription");
965
+ }
966
+ await refetch();
967
+ return true;
968
+ } catch (err) {
969
+ const errorMessage = err instanceof Error ? err.message : "Failed to cancel subscription";
970
+ handleError(errorMessage);
971
+ return false;
972
+ }
973
+ },
974
+ [subscriptionId, refetch, handleError]
975
+ );
976
+ const reactivate = (0, import_react6.useCallback)(
977
+ async (targetSubscriptionId) => {
978
+ const idToReactivate = targetSubscriptionId || subscriptionId;
979
+ if (!idToReactivate) {
980
+ handleError("Subscription ID is required to reactivate");
981
+ return false;
982
+ }
983
+ if (!validateStripeId(idToReactivate, "subscription")) {
984
+ handleError("Invalid subscription ID");
985
+ return false;
986
+ }
987
+ try {
988
+ const response = await fetch(
989
+ `/api/payments/subscription/${idToReactivate}/reactivate`,
990
+ {
991
+ method: "POST",
992
+ headers: { "Content-Type": "application/json" }
993
+ }
994
+ );
995
+ if (!response.ok) {
996
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
997
+ throw new Error(
998
+ errorData.error || "Failed to reactivate subscription"
999
+ );
1000
+ }
1001
+ await refetch();
1002
+ return true;
1003
+ } catch (err) {
1004
+ const errorMessage = err instanceof Error ? err.message : "Failed to reactivate subscription";
1005
+ handleError(errorMessage);
1006
+ return false;
1007
+ }
1008
+ },
1009
+ [subscriptionId, refetch, handleError]
1010
+ );
1011
+ const updatePaymentMethod = (0, import_react6.useCallback)(
1012
+ async (targetSubscriptionId, paymentMethodId) => {
1013
+ if (!validateStripeId(targetSubscriptionId, "subscription")) {
1014
+ handleError("Invalid subscription ID");
1015
+ return false;
1016
+ }
1017
+ try {
1018
+ const response = await fetch(
1019
+ `/api/payments/subscription/${targetSubscriptionId}/payment-method`,
1020
+ {
1021
+ method: "PUT",
1022
+ headers: { "Content-Type": "application/json" },
1023
+ body: JSON.stringify({ paymentMethodId })
1024
+ }
1025
+ );
1026
+ if (!response.ok) {
1027
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1028
+ throw new Error(errorData.error || "Failed to update payment method");
1029
+ }
1030
+ await refetch();
1031
+ return true;
1032
+ } catch (err) {
1033
+ const errorMessage = err instanceof Error ? err.message : "Failed to update payment method";
1034
+ handleError(errorMessage);
1035
+ return false;
1036
+ }
1037
+ },
1038
+ [refetch, handleError]
1039
+ );
1040
+ (0, import_react6.useEffect)(() => {
1041
+ if (autoFetch && (customerId || subscriptionId)) {
1042
+ refetch();
1043
+ }
1044
+ }, [autoFetch, customerId, subscriptionId, refetch]);
1045
+ return {
1046
+ subscription,
1047
+ subscriptions,
1048
+ loading,
1049
+ error,
1050
+ refetch,
1051
+ cancel,
1052
+ reactivate,
1053
+ updatePaymentMethod
1054
+ };
1055
+ };
1056
+
1057
+ // src/web/hooks/useCheckout.ts
1058
+ var import_react7 = require("react");
1059
+ var import_react_stripe_js4 = require("@stripe/react-stripe-js");
1060
+ var useCheckout = (options = {}) => {
1061
+ const { onSuccess, onError, onLoading } = options;
1062
+ const stripe = (0, import_react_stripe_js4.useStripe)();
1063
+ const { isConfigured } = usePaymentsConfig();
1064
+ const [loading, setLoading] = (0, import_react7.useState)(false);
1065
+ const [error, setError] = (0, import_react7.useState)(null);
1066
+ const handleError = (0, import_react7.useCallback)(
1067
+ (errorMessage) => {
1068
+ setError(errorMessage);
1069
+ onError?.(errorMessage);
1070
+ console.error("Checkout error:", errorMessage);
1071
+ },
1072
+ [onError]
1073
+ );
1074
+ const validateCheckoutParams = (0, import_react7.useCallback)(
1075
+ (params) => {
1076
+ if (!params.priceId) {
1077
+ return "Price ID is required";
1078
+ }
1079
+ if (!validateStripeId(params.priceId, "price")) {
1080
+ return ERROR_MESSAGES.INVALID_PRICE_ID;
1081
+ }
1082
+ if (params.customerId && !validateStripeId(params.customerId, "customer")) {
1083
+ return ERROR_MESSAGES.INVALID_CUSTOMER_ID;
1084
+ }
1085
+ if (params.customerEmail && !validateEmail(params.customerEmail)) {
1086
+ return "Invalid customer email";
1087
+ }
1088
+ if (!params.successUrl) {
1089
+ return "Success URL is required";
1090
+ }
1091
+ if (!params.cancelUrl) {
1092
+ return "Cancel URL is required";
1093
+ }
1094
+ return null;
1095
+ },
1096
+ []
1097
+ );
1098
+ const createCheckoutSession2 = (0, import_react7.useCallback)(
1099
+ async (params) => {
1100
+ if (!isConfigured) {
1101
+ handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
1102
+ return null;
1103
+ }
1104
+ const validationError = validateCheckoutParams(params);
1105
+ if (validationError) {
1106
+ handleError(validationError);
1107
+ return null;
1108
+ }
1109
+ setLoading(true);
1110
+ setError(null);
1111
+ onLoading?.(true);
1112
+ try {
1113
+ const response = await fetch("/api/payments/create-checkout-session", {
1114
+ method: "POST",
1115
+ headers: {
1116
+ "Content-Type": "application/json"
1117
+ },
1118
+ body: JSON.stringify(params)
1119
+ });
1120
+ if (!response.ok) {
1121
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1122
+ throw new Error(errorData.error || ERROR_MESSAGES.CHECKOUT_FAILED);
1123
+ }
1124
+ const { sessionId } = await response.json();
1125
+ if (!sessionId) {
1126
+ throw new Error("No session ID returned from server");
1127
+ }
1128
+ onSuccess?.(sessionId);
1129
+ return sessionId;
1130
+ } catch (err) {
1131
+ const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CHECKOUT_FAILED;
1132
+ handleError(errorMessage);
1133
+ return null;
1134
+ } finally {
1135
+ setLoading(false);
1136
+ onLoading?.(false);
1137
+ }
1138
+ },
1139
+ [isConfigured, validateCheckoutParams, handleError, onSuccess, onLoading]
1140
+ );
1141
+ const redirectToCheckout2 = (0, import_react7.useCallback)(
1142
+ async (params) => {
1143
+ if (!stripe) {
1144
+ handleError(ERROR_MESSAGES.STRIPE_NOT_LOADED);
1145
+ return false;
1146
+ }
1147
+ const sessionId = await createCheckoutSession2(params);
1148
+ if (!sessionId) {
1149
+ return false;
1150
+ }
1151
+ try {
1152
+ const result = await stripe.redirectToCheckout({ sessionId });
1153
+ if (result.error) {
1154
+ throw new Error(
1155
+ result.error.message || ERROR_MESSAGES.CHECKOUT_FAILED
1156
+ );
1157
+ }
1158
+ return true;
1159
+ } catch (err) {
1160
+ const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CHECKOUT_FAILED;
1161
+ handleError(errorMessage);
1162
+ return false;
1163
+ }
1164
+ },
1165
+ [stripe, createCheckoutSession2, handleError]
1166
+ );
1167
+ const createPaymentIntent2 = (0, import_react7.useCallback)(
1168
+ async (params) => {
1169
+ if (!isConfigured) {
1170
+ handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
1171
+ return null;
1172
+ }
1173
+ if (params.amount <= 0) {
1174
+ handleError("Amount must be greater than 0");
1175
+ return null;
1176
+ }
1177
+ if (params.customerId && !validateStripeId(params.customerId, "customer")) {
1178
+ handleError(ERROR_MESSAGES.INVALID_CUSTOMER_ID);
1179
+ return null;
1180
+ }
1181
+ setLoading(true);
1182
+ setError(null);
1183
+ onLoading?.(true);
1184
+ try {
1185
+ const response = await fetch("/api/payments/create-payment-intent", {
1186
+ method: "POST",
1187
+ headers: {
1188
+ "Content-Type": "application/json"
1189
+ },
1190
+ body: JSON.stringify(params)
1191
+ });
1192
+ if (!response.ok) {
1193
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1194
+ throw new Error(errorData.error || "Failed to create payment intent");
1195
+ }
1196
+ const { client_secret } = await response.json();
1197
+ if (!client_secret) {
1198
+ throw new Error("No client secret returned from server");
1199
+ }
1200
+ return client_secret;
1201
+ } catch (err) {
1202
+ const errorMessage = err instanceof Error ? err.message : "Failed to create payment intent";
1203
+ handleError(errorMessage);
1204
+ return null;
1205
+ } finally {
1206
+ setLoading(false);
1207
+ onLoading?.(false);
1208
+ }
1209
+ },
1210
+ [isConfigured, handleError, onLoading]
1211
+ );
1212
+ return {
1213
+ loading,
1214
+ error,
1215
+ createCheckoutSession: createCheckoutSession2,
1216
+ redirectToCheckout: redirectToCheckout2,
1217
+ createPaymentIntent: createPaymentIntent2
1218
+ };
1219
+ };
1220
+
1221
+ // src/web/hooks/useCustomer.ts
1222
+ var import_react8 = require("react");
1223
+ var useCustomer = (options = {}) => {
1224
+ const { customerId, autoFetch = true, onError } = options;
1225
+ const [customer, setCustomer] = (0, import_react8.useState)(null);
1226
+ const [paymentMethods, setPaymentMethods] = (0, import_react8.useState)([]);
1227
+ const [loading, setLoading] = (0, import_react8.useState)(false);
1228
+ const [error, setError] = (0, import_react8.useState)(null);
1229
+ const handleError = (0, import_react8.useCallback)(
1230
+ (errorMessage) => {
1231
+ setError(errorMessage);
1232
+ onError?.(errorMessage);
1233
+ console.error("Customer error:", errorMessage);
1234
+ },
1235
+ [onError]
1236
+ );
1237
+ const fetchCustomer = (0, import_react8.useCallback)(
1238
+ async (id) => {
1239
+ if (!validateStripeId(id, "customer")) {
1240
+ handleError("Invalid customer ID");
1241
+ return null;
1242
+ }
1243
+ try {
1244
+ const response = await fetch(`/api/payments/customer/${id}`);
1245
+ if (!response.ok) {
1246
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1247
+ throw new Error(errorData.error || ERROR_MESSAGES.CUSTOMER_NOT_FOUND);
1248
+ }
1249
+ const data = await response.json();
1250
+ return data;
1251
+ } catch (err) {
1252
+ const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CUSTOMER_NOT_FOUND;
1253
+ handleError(errorMessage);
1254
+ return null;
1255
+ }
1256
+ },
1257
+ [handleError]
1258
+ );
1259
+ const refetch = (0, import_react8.useCallback)(async () => {
1260
+ if (!customerId) {
1261
+ handleError("Customer ID is required");
1262
+ return;
1263
+ }
1264
+ setLoading(true);
1265
+ setError(null);
1266
+ try {
1267
+ const customerData = await fetchCustomer(customerId);
1268
+ setCustomer(customerData);
1269
+ } finally {
1270
+ setLoading(false);
1271
+ }
1272
+ }, [customerId, fetchCustomer, handleError]);
1273
+ const createCustomer = (0, import_react8.useCallback)(
1274
+ async (params) => {
1275
+ if (!validateEmail(params.email)) {
1276
+ handleError("Invalid email address");
1277
+ return null;
1278
+ }
1279
+ setLoading(true);
1280
+ setError(null);
1281
+ try {
1282
+ const response = await fetch("/api/payments/customer", {
1283
+ method: "POST",
1284
+ headers: { "Content-Type": "application/json" },
1285
+ body: JSON.stringify(params)
1286
+ });
1287
+ if (!response.ok) {
1288
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1289
+ throw new Error(errorData.error || "Failed to create customer");
1290
+ }
1291
+ const customerData = await response.json();
1292
+ setCustomer(customerData);
1293
+ return customerData;
1294
+ } catch (err) {
1295
+ const errorMessage = err instanceof Error ? err.message : "Failed to create customer";
1296
+ handleError(errorMessage);
1297
+ return null;
1298
+ } finally {
1299
+ setLoading(false);
1300
+ }
1301
+ },
1302
+ [handleError]
1303
+ );
1304
+ const updateCustomer = (0, import_react8.useCallback)(
1305
+ async (params) => {
1306
+ if (!customerId) {
1307
+ handleError("Customer ID is required");
1308
+ return false;
1309
+ }
1310
+ if (params.email && !validateEmail(params.email)) {
1311
+ handleError("Invalid email address");
1312
+ return false;
1313
+ }
1314
+ setLoading(true);
1315
+ setError(null);
1316
+ try {
1317
+ const response = await fetch(`/api/payments/customer/${customerId}`, {
1318
+ method: "PUT",
1319
+ headers: { "Content-Type": "application/json" },
1320
+ body: JSON.stringify(params)
1321
+ });
1322
+ if (!response.ok) {
1323
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1324
+ throw new Error(errorData.error || "Failed to update customer");
1325
+ }
1326
+ await refetch();
1327
+ return true;
1328
+ } catch (err) {
1329
+ const errorMessage = err instanceof Error ? err.message : "Failed to update customer";
1330
+ handleError(errorMessage);
1331
+ return false;
1332
+ } finally {
1333
+ setLoading(false);
1334
+ }
1335
+ },
1336
+ [customerId, refetch, handleError]
1337
+ );
1338
+ const deleteCustomer = (0, import_react8.useCallback)(async () => {
1339
+ if (!customerId) {
1340
+ handleError("Customer ID is required");
1341
+ return false;
1342
+ }
1343
+ setLoading(true);
1344
+ setError(null);
1345
+ try {
1346
+ const response = await fetch(`/api/payments/customer/${customerId}`, {
1347
+ method: "DELETE"
1348
+ });
1349
+ if (!response.ok) {
1350
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1351
+ throw new Error(errorData.error || "Failed to delete customer");
1352
+ }
1353
+ setCustomer(null);
1354
+ setPaymentMethods([]);
1355
+ return true;
1356
+ } catch (err) {
1357
+ const errorMessage = err instanceof Error ? err.message : "Failed to delete customer";
1358
+ handleError(errorMessage);
1359
+ return false;
1360
+ } finally {
1361
+ setLoading(false);
1362
+ }
1363
+ }, [customerId, handleError]);
1364
+ const fetchPaymentMethods = (0, import_react8.useCallback)(async () => {
1365
+ if (!customerId) {
1366
+ handleError("Customer ID is required");
1367
+ return [];
1368
+ }
1369
+ try {
1370
+ const response = await fetch(
1371
+ `/api/payments/customer/${customerId}/payment-methods`
1372
+ );
1373
+ if (!response.ok) {
1374
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1375
+ throw new Error(errorData.error || "Failed to fetch payment methods");
1376
+ }
1377
+ const data = await response.json();
1378
+ const methods = data.paymentMethods || [];
1379
+ setPaymentMethods(methods);
1380
+ return methods;
1381
+ } catch (err) {
1382
+ const errorMessage = err instanceof Error ? err.message : "Failed to fetch payment methods";
1383
+ handleError(errorMessage);
1384
+ return [];
1385
+ }
1386
+ }, [customerId, handleError]);
1387
+ const addPaymentMethod = (0, import_react8.useCallback)(
1388
+ async (paymentMethodId) => {
1389
+ if (!customerId) {
1390
+ handleError("Customer ID is required");
1391
+ return false;
1392
+ }
1393
+ try {
1394
+ const response = await fetch(
1395
+ `/api/payments/customer/${customerId}/payment-methods`,
1396
+ {
1397
+ method: "POST",
1398
+ headers: { "Content-Type": "application/json" },
1399
+ body: JSON.stringify({ paymentMethodId })
1400
+ }
1401
+ );
1402
+ if (!response.ok) {
1403
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1404
+ throw new Error(errorData.error || "Failed to add payment method");
1405
+ }
1406
+ await fetchPaymentMethods();
1407
+ return true;
1408
+ } catch (err) {
1409
+ const errorMessage = err instanceof Error ? err.message : "Failed to add payment method";
1410
+ handleError(errorMessage);
1411
+ return false;
1412
+ }
1413
+ },
1414
+ [customerId, fetchPaymentMethods, handleError]
1415
+ );
1416
+ const removePaymentMethod = (0, import_react8.useCallback)(
1417
+ async (paymentMethodId) => {
1418
+ if (!customerId) {
1419
+ handleError("Customer ID is required");
1420
+ return false;
1421
+ }
1422
+ try {
1423
+ const response = await fetch(
1424
+ `/api/payments/customer/${customerId}/payment-methods/${paymentMethodId}`,
1425
+ {
1426
+ method: "DELETE"
1427
+ }
1428
+ );
1429
+ if (!response.ok) {
1430
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1431
+ throw new Error(errorData.error || "Failed to remove payment method");
1432
+ }
1433
+ await fetchPaymentMethods();
1434
+ return true;
1435
+ } catch (err) {
1436
+ const errorMessage = err instanceof Error ? err.message : "Failed to remove payment method";
1437
+ handleError(errorMessage);
1438
+ return false;
1439
+ }
1440
+ },
1441
+ [customerId, fetchPaymentMethods, handleError]
1442
+ );
1443
+ const setDefaultPaymentMethod = (0, import_react8.useCallback)(
1444
+ async (paymentMethodId) => {
1445
+ if (!customerId) {
1446
+ handleError("Customer ID is required");
1447
+ return false;
1448
+ }
1449
+ try {
1450
+ const response = await fetch(
1451
+ `/api/payments/customer/${customerId}/default-payment-method`,
1452
+ {
1453
+ method: "PUT",
1454
+ headers: { "Content-Type": "application/json" },
1455
+ body: JSON.stringify({ paymentMethodId })
1456
+ }
1457
+ );
1458
+ if (!response.ok) {
1459
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1460
+ throw new Error(
1461
+ errorData.error || "Failed to set default payment method"
1462
+ );
1463
+ }
1464
+ await Promise.all([refetch(), fetchPaymentMethods()]);
1465
+ return true;
1466
+ } catch (err) {
1467
+ const errorMessage = err instanceof Error ? err.message : "Failed to set default payment method";
1468
+ handleError(errorMessage);
1469
+ return false;
1470
+ }
1471
+ },
1472
+ [customerId, refetch, fetchPaymentMethods, handleError]
1473
+ );
1474
+ (0, import_react8.useEffect)(() => {
1475
+ if (autoFetch && customerId) {
1476
+ refetch();
1477
+ fetchPaymentMethods();
1478
+ }
1479
+ }, [autoFetch, customerId, refetch, fetchPaymentMethods]);
1480
+ return {
1481
+ customer,
1482
+ paymentMethods,
1483
+ loading,
1484
+ error,
1485
+ refetch,
1486
+ createCustomer,
1487
+ updateCustomer,
1488
+ deleteCustomer,
1489
+ fetchPaymentMethods,
1490
+ addPaymentMethod,
1491
+ removePaymentMethod,
1492
+ setDefaultPaymentMethod
1493
+ };
1494
+ };
1495
+
1496
+ // src/web/utils/stripe-web.ts
1497
+ var import_stripe_js2 = require("@stripe/stripe-js");
1498
+ var stripeInstance = null;
1499
+ var getStripe2 = (publishableKey) => {
1500
+ if (!stripeInstance) {
1501
+ stripeInstance = (0, import_stripe_js2.loadStripe)(publishableKey);
1502
+ }
1503
+ return stripeInstance;
1504
+ };
1505
+ var initializeStripe = async (config) => {
1506
+ if (!config.publishableKey) {
1507
+ console.error(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
1508
+ return null;
1509
+ }
1510
+ try {
1511
+ const stripe = await getStripe2(config.publishableKey);
1512
+ return stripe;
1513
+ } catch (error) {
1514
+ console.error("Failed to initialize Stripe:", error);
1515
+ return null;
1516
+ }
1517
+ };
1518
+ var createCheckoutSession = async (params) => {
1519
+ try {
1520
+ const response = await fetch("/api/payments/create-checkout-session", {
1521
+ method: "POST",
1522
+ headers: { "Content-Type": "application/json" },
1523
+ body: JSON.stringify(params)
1524
+ });
1525
+ if (!response.ok) {
1526
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1527
+ return { error: errorData.error || ERROR_MESSAGES.CHECKOUT_FAILED };
1528
+ }
1529
+ const data = await response.json();
1530
+ return { sessionId: data.sessionId };
1531
+ } catch (error) {
1532
+ return {
1533
+ error: error instanceof Error ? error.message : ERROR_MESSAGES.CHECKOUT_FAILED
1534
+ };
1535
+ }
1536
+ };
1537
+ var createPaymentIntent = async (params) => {
1538
+ try {
1539
+ const response = await fetch("/api/payments/create-payment-intent", {
1540
+ method: "POST",
1541
+ headers: { "Content-Type": "application/json" },
1542
+ body: JSON.stringify(params)
1543
+ });
1544
+ if (!response.ok) {
1545
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1546
+ return { error: errorData.error || "Failed to create payment intent" };
1547
+ }
1548
+ const data = await response.json();
1549
+ return { clientSecret: data.client_secret };
1550
+ } catch (error) {
1551
+ return {
1552
+ error: error instanceof Error ? error.message : "Failed to create payment intent"
1553
+ };
1554
+ }
1555
+ };
1556
+ var createPortalSession = async (params) => {
1557
+ try {
1558
+ const response = await fetch("/api/payments/create-portal-session", {
1559
+ method: "POST",
1560
+ headers: { "Content-Type": "application/json" },
1561
+ body: JSON.stringify(params)
1562
+ });
1563
+ if (!response.ok) {
1564
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
1565
+ return { error: errorData.error || "Failed to create portal session" };
1566
+ }
1567
+ const data = await response.json();
1568
+ return { url: data.url };
1569
+ } catch (error) {
1570
+ return {
1571
+ error: error instanceof Error ? error.message : "Failed to create portal session"
1572
+ };
1573
+ }
1574
+ };
1575
+ var confirmPayment = async (stripe, elements, clientSecret, options = {}) => {
1576
+ try {
1577
+ const result = await stripe.confirmPayment({
1578
+ elements,
1579
+ clientSecret,
1580
+ confirmParams: {
1581
+ return_url: options.returnUrl || window.location.href,
1582
+ receipt_email: options.receiptEmail
1583
+ },
1584
+ redirect: "if_required"
1585
+ });
1586
+ if (result.error) {
1587
+ return { success: false, error: result.error.message };
1588
+ }
1589
+ if (result.paymentIntent?.status === "succeeded") {
1590
+ return { success: true, paymentIntent: result.paymentIntent };
1591
+ }
1592
+ return { success: false, error: "Payment was not completed" };
1593
+ } catch (error) {
1594
+ return {
1595
+ success: false,
1596
+ error: error instanceof Error ? error.message : ERROR_MESSAGES.PAYMENT_FAILED
1597
+ };
1598
+ }
1599
+ };
1600
+ var redirectToCheckout = async (stripe, sessionId) => {
1601
+ try {
1602
+ const result = await stripe.redirectToCheckout({ sessionId });
1603
+ if (result.error) {
1604
+ return { success: false, error: result.error.message };
1605
+ }
1606
+ return { success: true };
1607
+ } catch (error) {
1608
+ return {
1609
+ success: false,
1610
+ error: error instanceof Error ? error.message : ERROR_MESSAGES.CHECKOUT_FAILED
1611
+ };
1612
+ }
1613
+ };
1614
+ var formatStripeError = (error) => {
1615
+ if (typeof error === "string") {
1616
+ return error;
1617
+ }
1618
+ if (error?.message) {
1619
+ return error.message;
1620
+ }
1621
+ if (error?.code) {
1622
+ const errorMessages = {
1623
+ card_declined: "Your card was declined.",
1624
+ expired_card: "Your card has expired.",
1625
+ incorrect_cvc: "Your card's security code is incorrect.",
1626
+ processing_error: "An error occurred while processing your card.",
1627
+ incorrect_number: "Your card number is incorrect."
1628
+ };
1629
+ return errorMessages[error.code] || `Payment failed: ${error.code}`;
1630
+ }
1631
+ return "An unexpected error occurred.";
1632
+ };
1633
+ // Annotate the CommonJS export names for ESM import in node:
1634
+ 0 && (module.exports = {
1635
+ BillingPortal,
1636
+ CheckoutButton,
1637
+ PaymentForm,
1638
+ PricingTable,
1639
+ StripeProvider,
1640
+ confirmPayment,
1641
+ createCheckoutSession,
1642
+ createPaymentIntent,
1643
+ createPortalSession,
1644
+ formatStripeError,
1645
+ getStripe,
1646
+ initializeStripe,
1647
+ redirectToCheckout,
1648
+ useCheckout,
1649
+ useCustomer,
1650
+ useSubscription
1651
+ });