@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.
package/dist/index.js ADDED
@@ -0,0 +1,869 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BILLING_INTERVALS: () => BILLING_INTERVALS,
24
+ CHECKOUT_MODE: () => CHECKOUT_MODE,
25
+ CURRENCY_SYMBOLS: () => CURRENCY_SYMBOLS,
26
+ DEFAULT_CURRENCY: () => DEFAULT_CURRENCY,
27
+ ERROR_MESSAGES: () => ERROR_MESSAGES,
28
+ INVOICE_STATUS: () => INVOICE_STATUS,
29
+ PAYMENT_METHODS: () => PAYMENT_METHODS,
30
+ PaymentsProvider: () => PaymentsProvider,
31
+ STRIPE_API_VERSION: () => STRIPE_API_VERSION,
32
+ SUBSCRIPTION_STATUS: () => SUBSCRIPTION_STATUS,
33
+ WEBHOOK_EVENTS: () => WEBHOOK_EVENTS,
34
+ formatBillingInterval: () => formatBillingInterval,
35
+ formatCardBrand: () => formatCardBrand,
36
+ formatCurrency: () => formatCurrency,
37
+ formatDate: () => formatDate,
38
+ formatPaymentMethodDisplay: () => formatPaymentMethodDisplay,
39
+ formatPricingPlan: () => formatPricingPlan,
40
+ formatRelativeTime: () => formatRelativeTime,
41
+ formatSubscriptionStatus: () => formatSubscriptionStatus,
42
+ formatTrialPeriod: () => formatTrialPeriod,
43
+ getEnvironmentConfig: () => getEnvironmentConfig,
44
+ isClient: () => isClient,
45
+ isDevelopment: () => isDevelopment,
46
+ isProduction: () => isProduction,
47
+ isServer: () => isServer,
48
+ truncateText: () => truncateText,
49
+ usePayments: () => usePayments,
50
+ usePaymentsConfig: () => usePaymentsConfig,
51
+ usePaymentsConfigSafe: () => usePaymentsConfigSafe,
52
+ validateAmount: () => validateAmount,
53
+ validateCurrentEnvironment: () => validateCurrentEnvironment,
54
+ validateEmail: () => validateEmail,
55
+ validateEnvironment: () => validateEnvironment,
56
+ validatePaymentsConfig: () => validatePaymentsConfig,
57
+ validatePhoneNumber: () => validatePhoneNumber,
58
+ validatePricingPlan: () => validatePricingPlan,
59
+ validateStripeId: () => validateStripeId
60
+ });
61
+ module.exports = __toCommonJS(index_exports);
62
+
63
+ // src/shared/constants.ts
64
+ var STRIPE_API_VERSION = "2023-10-16";
65
+ var PAYMENT_METHODS = {
66
+ CARD: "card",
67
+ BANK_ACCOUNT: "bank_account",
68
+ SEPA_DEBIT: "sepa_debit",
69
+ IDEAL: "ideal",
70
+ SOFORT: "sofort"
71
+ };
72
+ var SUBSCRIPTION_STATUS = {
73
+ ACTIVE: "active",
74
+ CANCELED: "canceled",
75
+ PAST_DUE: "past_due",
76
+ UNPAID: "unpaid",
77
+ INCOMPLETE: "incomplete",
78
+ INCOMPLETE_EXPIRED: "incomplete_expired",
79
+ TRIALING: "trialing"
80
+ };
81
+ var INVOICE_STATUS = {
82
+ DRAFT: "draft",
83
+ OPEN: "open",
84
+ PAID: "paid",
85
+ UNCOLLECTIBLE: "uncollectible",
86
+ VOID: "void"
87
+ };
88
+ var CHECKOUT_MODE = {
89
+ PAYMENT: "payment",
90
+ SUBSCRIPTION: "subscription",
91
+ SETUP: "setup"
92
+ };
93
+ var BILLING_INTERVALS = {
94
+ DAY: "day",
95
+ WEEK: "week",
96
+ MONTH: "month",
97
+ YEAR: "year"
98
+ };
99
+ var CURRENCY_SYMBOLS = {
100
+ USD: "$",
101
+ EUR: "\u20AC",
102
+ GBP: "\xA3",
103
+ JPY: "\xA5",
104
+ CAD: "C$",
105
+ AUD: "A$",
106
+ CHF: "CHF",
107
+ CNY: "\xA5",
108
+ SEK: "kr",
109
+ NZD: "NZ$"
110
+ };
111
+ var DEFAULT_CURRENCY = "USD";
112
+ var WEBHOOK_EVENTS = {
113
+ CUSTOMER_SUBSCRIPTION_CREATED: "customer.subscription.created",
114
+ CUSTOMER_SUBSCRIPTION_UPDATED: "customer.subscription.updated",
115
+ CUSTOMER_SUBSCRIPTION_DELETED: "customer.subscription.deleted",
116
+ INVOICE_PAYMENT_SUCCEEDED: "invoice.payment_succeeded",
117
+ INVOICE_PAYMENT_FAILED: "invoice.payment_failed",
118
+ CHECKOUT_SESSION_COMPLETED: "checkout.session.completed",
119
+ PAYMENT_INTENT_SUCCEEDED: "payment_intent.succeeded",
120
+ PAYMENT_INTENT_PAYMENT_FAILED: "payment_intent.payment_failed"
121
+ };
122
+ var ERROR_MESSAGES = {
123
+ PROVIDER_NOT_CONFIGURED: "Payments provider is not properly configured",
124
+ STRIPE_NOT_LOADED: "Stripe has not been loaded yet",
125
+ INVALID_PRICE_ID: "Invalid price ID provided",
126
+ INVALID_CUSTOMER_ID: "Invalid customer ID provided",
127
+ CHECKOUT_FAILED: "Checkout session creation failed",
128
+ PAYMENT_FAILED: "Payment processing failed",
129
+ SUBSCRIPTION_NOT_FOUND: "Subscription not found",
130
+ CUSTOMER_NOT_FOUND: "Customer not found"
131
+ };
132
+
133
+ // src/shared/utils/validation.ts
134
+ var validateEmail = (email) => {
135
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
136
+ return emailRegex.test(email);
137
+ };
138
+ var validatePhoneNumber = (phone) => {
139
+ const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
140
+ return phoneRegex.test(phone);
141
+ };
142
+ var validatePaymentsConfig = (config) => {
143
+ const errors = [];
144
+ if (!config.publishableKey) {
145
+ errors.push("publishableKey is required");
146
+ }
147
+ if (!config.provider) {
148
+ errors.push("provider is required");
149
+ }
150
+ if (config.provider !== "stripe") {
151
+ errors.push("Only stripe provider is currently supported");
152
+ }
153
+ if (!config.environment) {
154
+ errors.push("environment is required");
155
+ }
156
+ if (config.environment && !["development", "production"].includes(config.environment)) {
157
+ errors.push('environment must be either "development" or "production"');
158
+ }
159
+ if (config.publishableKey && !config.publishableKey.startsWith("pk_")) {
160
+ errors.push('publishableKey must start with "pk_"');
161
+ }
162
+ if (config.secretKey && !config.secretKey.startsWith("sk_")) {
163
+ errors.push('secretKey must start with "sk_"');
164
+ }
165
+ if (config.webhookSecret && !config.webhookSecret.startsWith("whsec_")) {
166
+ errors.push('webhookSecret must start with "whsec_"');
167
+ }
168
+ return {
169
+ isValid: errors.length === 0,
170
+ errors
171
+ };
172
+ };
173
+ var validatePricingPlan = (plan) => {
174
+ const errors = [];
175
+ if (!plan.id) {
176
+ errors.push("id is required");
177
+ }
178
+ if (!plan.name) {
179
+ errors.push("name is required");
180
+ }
181
+ if (typeof plan.price !== "number" || plan.price < 0) {
182
+ errors.push("price must be a non-negative number");
183
+ }
184
+ if (!plan.currency) {
185
+ errors.push("currency is required");
186
+ }
187
+ if (!plan.interval) {
188
+ errors.push("interval is required");
189
+ }
190
+ if (!["day", "week", "month", "year"].includes(plan.interval)) {
191
+ errors.push("interval must be one of: day, week, month, year");
192
+ }
193
+ if (!plan.stripePriceId) {
194
+ errors.push("stripePriceId is required");
195
+ }
196
+ if (plan.stripePriceId && !plan.stripePriceId.startsWith("price_")) {
197
+ errors.push('stripePriceId must start with "price_"');
198
+ }
199
+ if (!Array.isArray(plan.features)) {
200
+ errors.push("features must be an array");
201
+ }
202
+ if (plan.intervalCount && (typeof plan.intervalCount !== "number" || plan.intervalCount < 1)) {
203
+ errors.push("intervalCount must be a positive number");
204
+ }
205
+ if (plan.trialPeriodDays && (typeof plan.trialPeriodDays !== "number" || plan.trialPeriodDays < 0)) {
206
+ errors.push("trialPeriodDays must be a non-negative number");
207
+ }
208
+ return {
209
+ isValid: errors.length === 0,
210
+ errors
211
+ };
212
+ };
213
+ var validateStripeId = (id, type) => {
214
+ const prefixes = {
215
+ customer: "cus_",
216
+ subscription: "sub_",
217
+ price: "price_",
218
+ product: "prod_",
219
+ payment_intent: "pi_"
220
+ };
221
+ return id.startsWith(prefixes[type]);
222
+ };
223
+ var validateAmount = (amount, currency) => {
224
+ if (typeof amount !== "number") {
225
+ return { isValid: false, error: "Amount must be a number" };
226
+ }
227
+ if (amount < 0) {
228
+ return { isValid: false, error: "Amount must be non-negative" };
229
+ }
230
+ const minimumAmounts = {
231
+ USD: 50,
232
+ // $0.50
233
+ EUR: 50,
234
+ // €0.50
235
+ GBP: 30,
236
+ // £0.30
237
+ JPY: 50,
238
+ // ¥50
239
+ CAD: 50,
240
+ // C$0.50
241
+ AUD: 50
242
+ // A$0.50
243
+ };
244
+ const minAmount = minimumAmounts[currency.toUpperCase()] || 50;
245
+ if (amount < minAmount) {
246
+ return {
247
+ isValid: false,
248
+ error: `Amount must be at least ${minAmount} ${currency.toLowerCase()} cents`
249
+ };
250
+ }
251
+ return { isValid: true };
252
+ };
253
+
254
+ // src/shared/utils/formatting.ts
255
+ var formatCurrency = (amount, currency = DEFAULT_CURRENCY, options = {}) => {
256
+ const { showSymbol = true, showCents = true, locale = "en-US" } = options;
257
+ const currencyCode = currency.toUpperCase();
258
+ const isZeroDecimalCurrency = ["JPY", "KRW", "VND", "CLP"].includes(
259
+ currencyCode
260
+ );
261
+ const displayAmount = isZeroDecimalCurrency ? amount : amount / 100;
262
+ if (showSymbol) {
263
+ try {
264
+ return new Intl.NumberFormat(locale, {
265
+ style: "currency",
266
+ currency: currencyCode,
267
+ minimumFractionDigits: showCents && !isZeroDecimalCurrency ? 2 : 0,
268
+ maximumFractionDigits: showCents && !isZeroDecimalCurrency ? 2 : 0
269
+ }).format(displayAmount);
270
+ } catch (error) {
271
+ const symbol = CURRENCY_SYMBOLS[currencyCode] || currencyCode;
272
+ const formattedAmount = showCents && !isZeroDecimalCurrency ? displayAmount.toFixed(2) : Math.round(displayAmount).toString();
273
+ return `${symbol}${formattedAmount}`;
274
+ }
275
+ }
276
+ return showCents && !isZeroDecimalCurrency ? displayAmount.toFixed(2) : Math.round(displayAmount).toString();
277
+ };
278
+ var formatPricingPlan = (plan) => {
279
+ const price = formatCurrency(plan.price, plan.currency);
280
+ const interval = plan.intervalCount && plan.intervalCount > 1 ? `${plan.intervalCount} ${plan.interval}s` : plan.interval;
281
+ return `${price}/${interval}`;
282
+ };
283
+ var formatSubscriptionStatus = (status) => {
284
+ const statusMap = {
285
+ active: "Active",
286
+ canceled: "Canceled",
287
+ past_due: "Past Due",
288
+ unpaid: "Unpaid",
289
+ incomplete: "Incomplete",
290
+ incomplete_expired: "Incomplete (Expired)",
291
+ trialing: "Trial"
292
+ };
293
+ return statusMap[status] || status;
294
+ };
295
+ var formatDate = (date, options = {}) => {
296
+ const { format = "medium", locale = "en-US", timeZone } = options;
297
+ const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date;
298
+ const formatOptions = {
299
+ timeZone
300
+ };
301
+ switch (format) {
302
+ case "short":
303
+ formatOptions.dateStyle = "short";
304
+ break;
305
+ case "medium":
306
+ formatOptions.dateStyle = "medium";
307
+ break;
308
+ case "long":
309
+ formatOptions.dateStyle = "long";
310
+ break;
311
+ case "full":
312
+ formatOptions.dateStyle = "full";
313
+ break;
314
+ }
315
+ try {
316
+ return new Intl.DateTimeFormat(locale, formatOptions).format(dateObj);
317
+ } catch (error) {
318
+ return dateObj.toLocaleDateString();
319
+ }
320
+ };
321
+ var formatRelativeTime = (date, options = {}) => {
322
+ const { locale = "en-US", numeric = "auto" } = options;
323
+ const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date;
324
+ const now = /* @__PURE__ */ new Date();
325
+ const diffInSeconds = Math.floor((now.getTime() - dateObj.getTime()) / 1e3);
326
+ try {
327
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric });
328
+ if (Math.abs(diffInSeconds) < 60) {
329
+ return rtf.format(-diffInSeconds, "second");
330
+ } else if (Math.abs(diffInSeconds) < 3600) {
331
+ return rtf.format(-Math.floor(diffInSeconds / 60), "minute");
332
+ } else if (Math.abs(diffInSeconds) < 86400) {
333
+ return rtf.format(-Math.floor(diffInSeconds / 3600), "hour");
334
+ } else if (Math.abs(diffInSeconds) < 2592e3) {
335
+ return rtf.format(-Math.floor(diffInSeconds / 86400), "day");
336
+ } else if (Math.abs(diffInSeconds) < 31536e3) {
337
+ return rtf.format(-Math.floor(diffInSeconds / 2592e3), "month");
338
+ } else {
339
+ return rtf.format(-Math.floor(diffInSeconds / 31536e3), "year");
340
+ }
341
+ } catch (error) {
342
+ if (Math.abs(diffInSeconds) < 60) {
343
+ return "just now";
344
+ } else if (Math.abs(diffInSeconds) < 3600) {
345
+ const minutes = Math.floor(Math.abs(diffInSeconds) / 60);
346
+ return diffInSeconds < 0 ? `in ${minutes} minutes` : `${minutes} minutes ago`;
347
+ } else if (Math.abs(diffInSeconds) < 86400) {
348
+ const hours = Math.floor(Math.abs(diffInSeconds) / 3600);
349
+ return diffInSeconds < 0 ? `in ${hours} hours` : `${hours} hours ago`;
350
+ } else {
351
+ const days = Math.floor(Math.abs(diffInSeconds) / 86400);
352
+ return diffInSeconds < 0 ? `in ${days} days` : `${days} days ago`;
353
+ }
354
+ }
355
+ };
356
+ var formatBillingInterval = (interval, count = 1) => {
357
+ const intervalMap = {
358
+ day: count === 1 ? "daily" : `every ${count} days`,
359
+ week: count === 1 ? "weekly" : `every ${count} weeks`,
360
+ month: count === 1 ? "monthly" : `every ${count} months`,
361
+ year: count === 1 ? "yearly" : `every ${count} years`
362
+ };
363
+ return intervalMap[interval] || interval;
364
+ };
365
+ var formatTrialPeriod = (days) => {
366
+ if (days === 0) return "No trial";
367
+ if (days === 1) return "1 day trial";
368
+ if (days < 7) return `${days} days trial`;
369
+ if (days === 7) return "1 week trial";
370
+ if (days < 30) return `${Math.floor(days / 7)} weeks trial`;
371
+ if (days === 30) return "1 month trial";
372
+ return `${Math.floor(days / 30)} months trial`;
373
+ };
374
+ var truncateText = (text, maxLength) => {
375
+ if (text.length <= maxLength) return text;
376
+ return `${text.substring(0, maxLength - 3)}...`;
377
+ };
378
+ var formatCardBrand = (brand) => {
379
+ const brandMap = {
380
+ visa: "Visa",
381
+ mastercard: "Mastercard",
382
+ amex: "American Express",
383
+ discover: "Discover",
384
+ jcb: "JCB",
385
+ diners: "Diners Club",
386
+ unionpay: "UnionPay"
387
+ };
388
+ return brandMap[brand.toLowerCase()] || brand;
389
+ };
390
+ var formatPaymentMethodDisplay = (paymentMethod) => {
391
+ if (paymentMethod.type === "card" && paymentMethod.card) {
392
+ return `${formatCardBrand(paymentMethod.card.brand)} \u2022\u2022\u2022\u2022 ${paymentMethod.card.last4}`;
393
+ }
394
+ if (paymentMethod.type === "bank_account" && paymentMethod.bankAccount) {
395
+ const bankName = paymentMethod.bankAccount.bankName || "Bank";
396
+ return `${bankName} \u2022\u2022\u2022\u2022 ${paymentMethod.bankAccount.last4}`;
397
+ }
398
+ return paymentMethod.type;
399
+ };
400
+
401
+ // src/shared/utils/env.ts
402
+ var validateEnvironment = (config) => {
403
+ const errors = [];
404
+ const warnings = [];
405
+ if (!config.stripePublishableKey) {
406
+ errors.push("STRIPE_PUBLISHABLE_KEY is required");
407
+ } else if (!config.stripePublishableKey.startsWith("pk_")) {
408
+ errors.push('STRIPE_PUBLISHABLE_KEY must start with "pk_"');
409
+ }
410
+ if (!config.environment) {
411
+ errors.push("Environment must be specified (development or production)");
412
+ } else if (!["development", "production"].includes(config.environment)) {
413
+ errors.push('Environment must be either "development" or "production"');
414
+ }
415
+ if (config.environment === "production") {
416
+ if (config.stripePublishableKey?.includes("test")) {
417
+ warnings.push("Using test keys in production environment");
418
+ }
419
+ if (!config.appUrl) {
420
+ warnings.push("APP_URL should be set in production for proper redirects");
421
+ } else if (!config.appUrl.startsWith("https://")) {
422
+ warnings.push("APP_URL should use HTTPS in production");
423
+ }
424
+ }
425
+ if (typeof window === "undefined") {
426
+ if (!config.stripeSecretKey) {
427
+ warnings.push(
428
+ "STRIPE_SECRET_KEY is recommended for server-side operations"
429
+ );
430
+ } else if (!config.stripeSecretKey.startsWith("sk_")) {
431
+ errors.push('STRIPE_SECRET_KEY must start with "sk_"');
432
+ }
433
+ if (!config.stripeWebhookSecret) {
434
+ warnings.push(
435
+ "STRIPE_WEBHOOK_SECRET is recommended for webhook verification"
436
+ );
437
+ } else if (!config.stripeWebhookSecret.startsWith("whsec_")) {
438
+ errors.push('STRIPE_WEBHOOK_SECRET must start with "whsec_"');
439
+ }
440
+ }
441
+ return {
442
+ isValid: errors.length === 0,
443
+ errors,
444
+ warnings
445
+ };
446
+ };
447
+ var getEnvironmentConfig = () => {
448
+ const isServer2 = typeof window === "undefined";
449
+ const config = {
450
+ stripePublishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY || "",
451
+ environment: process.env.NODE_ENV || "development",
452
+ appUrl: process.env.NEXT_PUBLIC_APP_URL
453
+ };
454
+ if (isServer2) {
455
+ config.stripeSecretKey = process.env.STRIPE_SECRET_KEY;
456
+ config.stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
457
+ }
458
+ return config;
459
+ };
460
+ var validateCurrentEnvironment = () => {
461
+ const config = getEnvironmentConfig();
462
+ const validation = validateEnvironment(config);
463
+ if (!validation.isValid) {
464
+ console.error("\u274C Environment validation failed:");
465
+ validation.errors.forEach((error) => console.error(` - ${error}`));
466
+ if (typeof window === "undefined") {
467
+ throw new Error(
468
+ `Environment validation failed: ${validation.errors.join(", ")}`
469
+ );
470
+ }
471
+ }
472
+ if (validation.warnings.length > 0) {
473
+ console.warn("\u26A0\uFE0F Environment warnings:");
474
+ validation.warnings.forEach((warning) => console.warn(` - ${warning}`));
475
+ }
476
+ return validation;
477
+ };
478
+ var isDevelopment = () => {
479
+ return process.env.NODE_ENV === "development";
480
+ };
481
+ var isProduction = () => {
482
+ return process.env.NODE_ENV === "production";
483
+ };
484
+ var isServer = () => {
485
+ return typeof window === "undefined";
486
+ };
487
+ var isClient = () => {
488
+ return typeof window !== "undefined";
489
+ };
490
+
491
+ // src/shared/providers/PaymentsProvider.tsx
492
+ var import_react = require("react");
493
+ var import_jsx_runtime = require("react/jsx-runtime");
494
+ var PaymentsContext = (0, import_react.createContext)(
495
+ void 0
496
+ );
497
+ var PaymentsProvider = ({
498
+ children,
499
+ config
500
+ }) => {
501
+ const isConfigured = Boolean(
502
+ config.publishableKey && config.provider === "stripe" && config.environment
503
+ );
504
+ const isProduction2 = config.environment === "production";
505
+ if (!isConfigured && config.environment === "development") {
506
+ console.warn(
507
+ "PaymentsProvider: Configuration incomplete. Please ensure publishableKey and provider are set."
508
+ );
509
+ }
510
+ const contextValue = {
511
+ config,
512
+ isConfigured,
513
+ isProduction: isProduction2
514
+ };
515
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PaymentsContext.Provider, { value: contextValue, children });
516
+ };
517
+ var usePaymentsConfig = () => {
518
+ const context = (0, import_react.useContext)(PaymentsContext);
519
+ if (!context) {
520
+ throw new Error("usePaymentsConfig must be used within PaymentsProvider");
521
+ }
522
+ return context;
523
+ };
524
+ var usePaymentsConfigSafe = () => {
525
+ return (0, import_react.useContext)(PaymentsContext) || null;
526
+ };
527
+
528
+ // src/shared/hooks/usePayments.ts
529
+ var import_react2 = require("react");
530
+ var usePayments = (options = {}) => {
531
+ const { customerId, autoFetch = true, onError } = options;
532
+ const { isConfigured } = usePaymentsConfig();
533
+ const [customer, setCustomer] = (0, import_react2.useState)(null);
534
+ const [subscriptions, setSubscriptions] = (0, import_react2.useState)([]);
535
+ const [loading, setLoading] = (0, import_react2.useState)(false);
536
+ const [error, setError] = (0, import_react2.useState)(null);
537
+ const handleError = (0, import_react2.useCallback)(
538
+ (errorMessage) => {
539
+ setError(errorMessage);
540
+ onError?.(errorMessage);
541
+ console.error("Payments error:", errorMessage);
542
+ },
543
+ [onError]
544
+ );
545
+ const fetchCustomerData = (0, import_react2.useCallback)(
546
+ async (id) => {
547
+ if (!validateStripeId(id, "customer")) {
548
+ handleError("Invalid customer ID");
549
+ return;
550
+ }
551
+ try {
552
+ const response = await fetch(`/api/payments/customer/${id}`);
553
+ if (!response.ok) {
554
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
555
+ throw new Error(errorData.error || ERROR_MESSAGES.CUSTOMER_NOT_FOUND);
556
+ }
557
+ const customerData = await response.json();
558
+ setCustomer(customerData);
559
+ const subsResponse = await fetch(
560
+ `/api/payments/customer/${id}/subscriptions`
561
+ );
562
+ if (subsResponse.ok) {
563
+ const subsData = await subsResponse.json();
564
+ setSubscriptions(subsData.subscriptions || []);
565
+ }
566
+ } catch (err) {
567
+ const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CUSTOMER_NOT_FOUND;
568
+ handleError(errorMessage);
569
+ }
570
+ },
571
+ [handleError]
572
+ );
573
+ const refetch = (0, import_react2.useCallback)(async () => {
574
+ if (!customerId) {
575
+ handleError("Customer ID is required");
576
+ return;
577
+ }
578
+ if (!isConfigured) {
579
+ handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
580
+ return;
581
+ }
582
+ setLoading(true);
583
+ setError(null);
584
+ try {
585
+ await fetchCustomerData(customerId);
586
+ } finally {
587
+ setLoading(false);
588
+ }
589
+ }, [customerId, isConfigured, fetchCustomerData, handleError]);
590
+ const createCustomer = (0, import_react2.useCallback)(
591
+ async (params) => {
592
+ if (!isConfigured) {
593
+ handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
594
+ return null;
595
+ }
596
+ if (!validateEmail(params.email)) {
597
+ handleError("Invalid email address");
598
+ return null;
599
+ }
600
+ setLoading(true);
601
+ setError(null);
602
+ try {
603
+ const response = await fetch("/api/payments/customer", {
604
+ method: "POST",
605
+ headers: { "Content-Type": "application/json" },
606
+ body: JSON.stringify(params)
607
+ });
608
+ if (!response.ok) {
609
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
610
+ throw new Error(errorData.error || "Failed to create customer");
611
+ }
612
+ const customerData = await response.json();
613
+ setCustomer(customerData);
614
+ return customerData;
615
+ } catch (err) {
616
+ const errorMessage = err instanceof Error ? err.message : "Failed to create customer";
617
+ handleError(errorMessage);
618
+ return null;
619
+ } finally {
620
+ setLoading(false);
621
+ }
622
+ },
623
+ [isConfigured, handleError]
624
+ );
625
+ const updateCustomer = (0, import_react2.useCallback)(
626
+ async (params) => {
627
+ if (!customerId) {
628
+ handleError("Customer ID is required");
629
+ return false;
630
+ }
631
+ if (!isConfigured) {
632
+ handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
633
+ return false;
634
+ }
635
+ if (params.email && !validateEmail(params.email)) {
636
+ handleError("Invalid email address");
637
+ return false;
638
+ }
639
+ setLoading(true);
640
+ setError(null);
641
+ try {
642
+ const response = await fetch(`/api/payments/customer/${customerId}`, {
643
+ method: "PUT",
644
+ headers: { "Content-Type": "application/json" },
645
+ body: JSON.stringify(params)
646
+ });
647
+ if (!response.ok) {
648
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
649
+ throw new Error(errorData.error || "Failed to update customer");
650
+ }
651
+ await refetch();
652
+ return true;
653
+ } catch (err) {
654
+ const errorMessage = err instanceof Error ? err.message : "Failed to update customer";
655
+ handleError(errorMessage);
656
+ return false;
657
+ } finally {
658
+ setLoading(false);
659
+ }
660
+ },
661
+ [customerId, isConfigured, refetch, handleError]
662
+ );
663
+ const subscribe = (0, import_react2.useCallback)(
664
+ async (priceId, options2 = {}) => {
665
+ if (!customerId) {
666
+ handleError("Customer ID is required");
667
+ return false;
668
+ }
669
+ if (!isConfigured) {
670
+ handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
671
+ return false;
672
+ }
673
+ if (!validateStripeId(priceId, "price")) {
674
+ handleError(ERROR_MESSAGES.INVALID_PRICE_ID);
675
+ return false;
676
+ }
677
+ setLoading(true);
678
+ setError(null);
679
+ try {
680
+ const response = await fetch("/api/payments/create-subscription", {
681
+ method: "POST",
682
+ headers: { "Content-Type": "application/json" },
683
+ body: JSON.stringify({
684
+ customerId,
685
+ priceId,
686
+ ...options2
687
+ })
688
+ });
689
+ if (!response.ok) {
690
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
691
+ throw new Error(errorData.error || "Failed to create subscription");
692
+ }
693
+ await refetch();
694
+ return true;
695
+ } catch (err) {
696
+ const errorMessage = err instanceof Error ? err.message : "Failed to create subscription";
697
+ handleError(errorMessage);
698
+ return false;
699
+ } finally {
700
+ setLoading(false);
701
+ }
702
+ },
703
+ [customerId, isConfigured, refetch, handleError]
704
+ );
705
+ const cancelSubscription = (0, import_react2.useCallback)(
706
+ async (subscriptionId) => {
707
+ const activeSubscription2 = subscriptions.find((s) => s.status === "active");
708
+ const idToCancel = subscriptionId || activeSubscription2?.id;
709
+ if (!idToCancel) {
710
+ handleError("No active subscription to cancel");
711
+ return false;
712
+ }
713
+ if (!validateStripeId(idToCancel, "subscription")) {
714
+ handleError("Invalid subscription ID");
715
+ return false;
716
+ }
717
+ try {
718
+ const response = await fetch(
719
+ `/api/payments/subscription/${idToCancel}/cancel`,
720
+ {
721
+ method: "POST",
722
+ headers: { "Content-Type": "application/json" }
723
+ }
724
+ );
725
+ if (!response.ok) {
726
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
727
+ throw new Error(errorData.error || "Failed to cancel subscription");
728
+ }
729
+ await refetch();
730
+ return true;
731
+ } catch (err) {
732
+ const errorMessage = err instanceof Error ? err.message : "Failed to cancel subscription";
733
+ handleError(errorMessage);
734
+ return false;
735
+ }
736
+ },
737
+ [subscriptions, refetch, handleError]
738
+ );
739
+ const reactivateSubscription = (0, import_react2.useCallback)(
740
+ async (subscriptionId) => {
741
+ const cancelledSubscription = subscriptions.find(
742
+ (s) => s.cancelAtPeriodEnd
743
+ );
744
+ const idToReactivate = subscriptionId || cancelledSubscription?.id;
745
+ if (!idToReactivate) {
746
+ handleError("No cancelled subscription to reactivate");
747
+ return false;
748
+ }
749
+ if (!validateStripeId(idToReactivate, "subscription")) {
750
+ handleError("Invalid subscription ID");
751
+ return false;
752
+ }
753
+ try {
754
+ const response = await fetch(
755
+ `/api/payments/subscription/${idToReactivate}/reactivate`,
756
+ {
757
+ method: "POST",
758
+ headers: { "Content-Type": "application/json" }
759
+ }
760
+ );
761
+ if (!response.ok) {
762
+ const errorData = await response.json().catch(() => ({ error: "Network error" }));
763
+ throw new Error(
764
+ errorData.error || "Failed to reactivate subscription"
765
+ );
766
+ }
767
+ await refetch();
768
+ return true;
769
+ } catch (err) {
770
+ const errorMessage = err instanceof Error ? err.message : "Failed to reactivate subscription";
771
+ handleError(errorMessage);
772
+ return false;
773
+ }
774
+ },
775
+ [subscriptions, refetch, handleError]
776
+ );
777
+ const hasActiveSubscription = (0, import_react2.useCallback)(() => {
778
+ return subscriptions.some(
779
+ (s) => s.status === "active" || s.status === "trialing"
780
+ );
781
+ }, [subscriptions]);
782
+ const isSubscribedToPlan = (0, import_react2.useCallback)(
783
+ (priceId) => {
784
+ return subscriptions.some(
785
+ (s) => s.priceId === priceId && (s.status === "active" || s.status === "trialing")
786
+ );
787
+ },
788
+ [subscriptions]
789
+ );
790
+ const getSubscriptionStatus = (0, import_react2.useCallback)(
791
+ (subscriptionId) => {
792
+ if (subscriptionId) {
793
+ const subscription = subscriptions.find((s) => s.id === subscriptionId);
794
+ return subscription?.status || null;
795
+ }
796
+ const activeSubscription2 = subscriptions.find(
797
+ (s) => s.status === "active" || s.status === "trialing"
798
+ );
799
+ return activeSubscription2?.status || null;
800
+ },
801
+ [subscriptions]
802
+ );
803
+ const activeSubscription = subscriptions.find((s) => s.status === "active" || s.status === "trialing") || null;
804
+ (0, import_react2.useEffect)(() => {
805
+ if (autoFetch && customerId && isConfigured) {
806
+ refetch();
807
+ }
808
+ }, [autoFetch, customerId, isConfigured, refetch]);
809
+ return {
810
+ // State
811
+ customer,
812
+ subscriptions,
813
+ activeSubscription,
814
+ loading,
815
+ error,
816
+ // Customer methods
817
+ createCustomer,
818
+ updateCustomer,
819
+ // Subscription methods
820
+ subscribe,
821
+ cancelSubscription,
822
+ reactivateSubscription,
823
+ // Utility methods
824
+ refetch,
825
+ hasActiveSubscription,
826
+ isSubscribedToPlan,
827
+ getSubscriptionStatus
828
+ };
829
+ };
830
+ // Annotate the CommonJS export names for ESM import in node:
831
+ 0 && (module.exports = {
832
+ BILLING_INTERVALS,
833
+ CHECKOUT_MODE,
834
+ CURRENCY_SYMBOLS,
835
+ DEFAULT_CURRENCY,
836
+ ERROR_MESSAGES,
837
+ INVOICE_STATUS,
838
+ PAYMENT_METHODS,
839
+ PaymentsProvider,
840
+ STRIPE_API_VERSION,
841
+ SUBSCRIPTION_STATUS,
842
+ WEBHOOK_EVENTS,
843
+ formatBillingInterval,
844
+ formatCardBrand,
845
+ formatCurrency,
846
+ formatDate,
847
+ formatPaymentMethodDisplay,
848
+ formatPricingPlan,
849
+ formatRelativeTime,
850
+ formatSubscriptionStatus,
851
+ formatTrialPeriod,
852
+ getEnvironmentConfig,
853
+ isClient,
854
+ isDevelopment,
855
+ isProduction,
856
+ isServer,
857
+ truncateText,
858
+ usePayments,
859
+ usePaymentsConfig,
860
+ usePaymentsConfigSafe,
861
+ validateAmount,
862
+ validateCurrentEnvironment,
863
+ validateEmail,
864
+ validateEnvironment,
865
+ validatePaymentsConfig,
866
+ validatePhoneNumber,
867
+ validatePricingPlan,
868
+ validateStripeId
869
+ });