@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/LICENSE +22 -0
- package/README.md +679 -0
- package/dist/index.d.mts +365 -0
- package/dist/index.d.ts +365 -0
- package/dist/index.js +869 -0
- package/dist/index.mjs +806 -0
- package/dist/native/index.d.mts +266 -0
- package/dist/native/index.d.ts +266 -0
- package/dist/native/index.js +1833 -0
- package/dist/native/index.mjs +1815 -0
- package/dist/server/index.d.mts +122 -0
- package/dist/server/index.d.ts +122 -0
- package/dist/server/index.js +484 -0
- package/dist/server/index.mjs +443 -0
- package/dist/web/index.d.mts +281 -0
- package/dist/web/index.d.ts +281 -0
- package/dist/web/index.js +1651 -0
- package/dist/web/index.mjs +1604 -0
- package/package.json +153 -0
|
@@ -0,0 +1,1833 @@
|
|
|
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/native/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CheckoutButton: () => CheckoutButton,
|
|
24
|
+
PaymentForm: () => PaymentForm,
|
|
25
|
+
PricingTable: () => PricingTable,
|
|
26
|
+
StripeProvider: () => StripeProvider,
|
|
27
|
+
cancelSubscription: () => cancelSubscription,
|
|
28
|
+
createCustomer: () => createCustomer,
|
|
29
|
+
createPaymentIntent: () => createPaymentIntent,
|
|
30
|
+
createSubscriptionSetup: () => createSubscriptionSetup,
|
|
31
|
+
fetchCustomerSubscriptions: () => fetchCustomerSubscriptions,
|
|
32
|
+
fetchSubscription: () => fetchSubscription,
|
|
33
|
+
formatStripeError: () => formatStripeError,
|
|
34
|
+
useCheckout: () => useCheckout,
|
|
35
|
+
useCustomer: () => useCustomer,
|
|
36
|
+
useSubscription: () => useSubscription,
|
|
37
|
+
validatePaymentsConfig: () => validatePaymentsConfig
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/native/components/CheckoutButton.tsx
|
|
42
|
+
var import_react2 = require("react");
|
|
43
|
+
var import_react_native = require("react-native");
|
|
44
|
+
var import_stripe_react_native = require("@stripe/stripe-react-native");
|
|
45
|
+
|
|
46
|
+
// src/shared/providers/PaymentsProvider.tsx
|
|
47
|
+
var import_react = require("react");
|
|
48
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
49
|
+
var PaymentsContext = (0, import_react.createContext)(
|
|
50
|
+
void 0
|
|
51
|
+
);
|
|
52
|
+
var usePaymentsConfig = () => {
|
|
53
|
+
const context = (0, import_react.useContext)(PaymentsContext);
|
|
54
|
+
if (!context) {
|
|
55
|
+
throw new Error("usePaymentsConfig must be used within PaymentsProvider");
|
|
56
|
+
}
|
|
57
|
+
return context;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/shared/constants.ts
|
|
61
|
+
var CURRENCY_SYMBOLS = {
|
|
62
|
+
USD: "$",
|
|
63
|
+
EUR: "\u20AC",
|
|
64
|
+
GBP: "\xA3",
|
|
65
|
+
JPY: "\xA5",
|
|
66
|
+
CAD: "C$",
|
|
67
|
+
AUD: "A$",
|
|
68
|
+
CHF: "CHF",
|
|
69
|
+
CNY: "\xA5",
|
|
70
|
+
SEK: "kr",
|
|
71
|
+
NZD: "NZ$"
|
|
72
|
+
};
|
|
73
|
+
var DEFAULT_CURRENCY = "USD";
|
|
74
|
+
var ERROR_MESSAGES = {
|
|
75
|
+
PROVIDER_NOT_CONFIGURED: "Payments provider is not properly configured",
|
|
76
|
+
STRIPE_NOT_LOADED: "Stripe has not been loaded yet",
|
|
77
|
+
INVALID_PRICE_ID: "Invalid price ID provided",
|
|
78
|
+
INVALID_CUSTOMER_ID: "Invalid customer ID provided",
|
|
79
|
+
CHECKOUT_FAILED: "Checkout session creation failed",
|
|
80
|
+
PAYMENT_FAILED: "Payment processing failed",
|
|
81
|
+
SUBSCRIPTION_NOT_FOUND: "Subscription not found",
|
|
82
|
+
CUSTOMER_NOT_FOUND: "Customer not found"
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/shared/utils/validation.ts
|
|
86
|
+
var validateEmail = (email) => {
|
|
87
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
88
|
+
return emailRegex.test(email);
|
|
89
|
+
};
|
|
90
|
+
var validateStripeId = (id, type) => {
|
|
91
|
+
const prefixes = {
|
|
92
|
+
customer: "cus_",
|
|
93
|
+
subscription: "sub_",
|
|
94
|
+
price: "price_",
|
|
95
|
+
product: "prod_",
|
|
96
|
+
payment_intent: "pi_"
|
|
97
|
+
};
|
|
98
|
+
return id.startsWith(prefixes[type]);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/native/components/CheckoutButton.tsx
|
|
102
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
103
|
+
var CheckoutButton = ({
|
|
104
|
+
priceId,
|
|
105
|
+
customerId,
|
|
106
|
+
customerEmail,
|
|
107
|
+
children,
|
|
108
|
+
style,
|
|
109
|
+
textStyle,
|
|
110
|
+
disabled = false,
|
|
111
|
+
metadata,
|
|
112
|
+
trialPeriodDays,
|
|
113
|
+
onSuccess,
|
|
114
|
+
onError,
|
|
115
|
+
onLoading
|
|
116
|
+
}) => {
|
|
117
|
+
const { initPaymentSheet, presentPaymentSheet } = (0, import_stripe_react_native.useStripe)();
|
|
118
|
+
const { isConfigured } = usePaymentsConfig();
|
|
119
|
+
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
120
|
+
const handleError = (errorMessage) => {
|
|
121
|
+
onError?.(errorMessage);
|
|
122
|
+
import_react_native.Alert.alert("Payment Error", errorMessage);
|
|
123
|
+
console.error("Checkout error:", errorMessage);
|
|
124
|
+
};
|
|
125
|
+
const handleCheckout = async () => {
|
|
126
|
+
if (!isConfigured) {
|
|
127
|
+
handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!validateStripeId(priceId, "price")) {
|
|
131
|
+
handleError(ERROR_MESSAGES.INVALID_PRICE_ID);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (customerId && !validateStripeId(customerId, "customer")) {
|
|
135
|
+
handleError(ERROR_MESSAGES.INVALID_CUSTOMER_ID);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
setLoading(true);
|
|
139
|
+
onLoading?.(true);
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch("/api/payments/create-subscription-setup", {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
priceId,
|
|
146
|
+
customerId,
|
|
147
|
+
customerEmail,
|
|
148
|
+
metadata,
|
|
149
|
+
trialPeriodDays
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
154
|
+
throw new Error(errorData.error || ERROR_MESSAGES.CHECKOUT_FAILED);
|
|
155
|
+
}
|
|
156
|
+
const {
|
|
157
|
+
setupIntentClientSecret,
|
|
158
|
+
customerId: returnedCustomerId,
|
|
159
|
+
customerEphemeralKeySecret
|
|
160
|
+
} = await response.json();
|
|
161
|
+
if (!setupIntentClientSecret) {
|
|
162
|
+
throw new Error("No setup intent client secret returned from server");
|
|
163
|
+
}
|
|
164
|
+
const { error: initError } = await initPaymentSheet({
|
|
165
|
+
setupIntentClientSecret,
|
|
166
|
+
customerId: returnedCustomerId,
|
|
167
|
+
customerEphemeralKeySecret,
|
|
168
|
+
merchantDisplayName: "Your App Name",
|
|
169
|
+
// TODO: Make this configurable
|
|
170
|
+
allowsDelayedPaymentMethods: true
|
|
171
|
+
});
|
|
172
|
+
if (initError) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
initError.message || "Failed to initialize payment sheet"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const { error: presentError } = await presentPaymentSheet();
|
|
178
|
+
if (presentError) {
|
|
179
|
+
if (presentError.code !== "Canceled") {
|
|
180
|
+
throw new Error(
|
|
181
|
+
presentError.message || ERROR_MESSAGES.PAYMENT_FAILED
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
onSuccess?.();
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const errorMessage = error instanceof Error ? error.message : ERROR_MESSAGES.CHECKOUT_FAILED;
|
|
189
|
+
handleError(errorMessage);
|
|
190
|
+
} finally {
|
|
191
|
+
setLoading(false);
|
|
192
|
+
onLoading?.(false);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const isDisabled = loading || disabled || !isConfigured;
|
|
196
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
197
|
+
import_react_native.TouchableOpacity,
|
|
198
|
+
{
|
|
199
|
+
style: [styles.button, style, isDisabled && styles.disabled],
|
|
200
|
+
onPress: handleCheckout,
|
|
201
|
+
disabled: isDisabled,
|
|
202
|
+
activeOpacity: 0.8,
|
|
203
|
+
children: [
|
|
204
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.ActivityIndicator, { color: "#ffffff", size: "small", style: styles.loader }) : null,
|
|
205
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
206
|
+
import_react_native.Text,
|
|
207
|
+
{
|
|
208
|
+
style: [styles.buttonText, textStyle, loading && styles.loadingText],
|
|
209
|
+
children: loading ? "Processing..." : children
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
var styles = import_react_native.StyleSheet.create({
|
|
217
|
+
button: {
|
|
218
|
+
backgroundColor: "#007AFF",
|
|
219
|
+
paddingHorizontal: 16,
|
|
220
|
+
paddingVertical: 12,
|
|
221
|
+
borderRadius: 8,
|
|
222
|
+
flexDirection: "row",
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
justifyContent: "center",
|
|
225
|
+
minHeight: 48
|
|
226
|
+
},
|
|
227
|
+
buttonText: {
|
|
228
|
+
color: "#ffffff",
|
|
229
|
+
fontSize: 16,
|
|
230
|
+
fontWeight: "600",
|
|
231
|
+
textAlign: "center"
|
|
232
|
+
},
|
|
233
|
+
disabled: {
|
|
234
|
+
opacity: 0.5
|
|
235
|
+
},
|
|
236
|
+
loader: {
|
|
237
|
+
marginRight: 8
|
|
238
|
+
},
|
|
239
|
+
loadingText: {
|
|
240
|
+
marginLeft: 8
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// src/native/components/PricingTable.tsx
|
|
245
|
+
var import_react_native2 = require("react-native");
|
|
246
|
+
|
|
247
|
+
// src/shared/utils/formatting.ts
|
|
248
|
+
var formatCurrency = (amount, currency = DEFAULT_CURRENCY, options = {}) => {
|
|
249
|
+
const { showSymbol = true, showCents = true, locale = "en-US" } = options;
|
|
250
|
+
const currencyCode = currency.toUpperCase();
|
|
251
|
+
const isZeroDecimalCurrency = ["JPY", "KRW", "VND", "CLP"].includes(
|
|
252
|
+
currencyCode
|
|
253
|
+
);
|
|
254
|
+
const displayAmount = isZeroDecimalCurrency ? amount : amount / 100;
|
|
255
|
+
if (showSymbol) {
|
|
256
|
+
try {
|
|
257
|
+
return new Intl.NumberFormat(locale, {
|
|
258
|
+
style: "currency",
|
|
259
|
+
currency: currencyCode,
|
|
260
|
+
minimumFractionDigits: showCents && !isZeroDecimalCurrency ? 2 : 0,
|
|
261
|
+
maximumFractionDigits: showCents && !isZeroDecimalCurrency ? 2 : 0
|
|
262
|
+
}).format(displayAmount);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const symbol = CURRENCY_SYMBOLS[currencyCode] || currencyCode;
|
|
265
|
+
const formattedAmount = showCents && !isZeroDecimalCurrency ? displayAmount.toFixed(2) : Math.round(displayAmount).toString();
|
|
266
|
+
return `${symbol}${formattedAmount}`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return showCents && !isZeroDecimalCurrency ? displayAmount.toFixed(2) : Math.round(displayAmount).toString();
|
|
270
|
+
};
|
|
271
|
+
var formatPricingPlan = (plan) => {
|
|
272
|
+
const price = formatCurrency(plan.price, plan.currency);
|
|
273
|
+
const interval = plan.intervalCount && plan.intervalCount > 1 ? `${plan.intervalCount} ${plan.interval}s` : plan.interval;
|
|
274
|
+
return `${price}/${interval}`;
|
|
275
|
+
};
|
|
276
|
+
var formatTrialPeriod = (days) => {
|
|
277
|
+
if (days === 0) return "No trial";
|
|
278
|
+
if (days === 1) return "1 day trial";
|
|
279
|
+
if (days < 7) return `${days} days trial`;
|
|
280
|
+
if (days === 7) return "1 week trial";
|
|
281
|
+
if (days < 30) return `${Math.floor(days / 7)} weeks trial`;
|
|
282
|
+
if (days === 30) return "1 month trial";
|
|
283
|
+
return `${Math.floor(days / 30)} months trial`;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// src/native/components/PricingTable.tsx
|
|
287
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
288
|
+
var { width: screenWidth } = import_react_native2.Dimensions.get("window");
|
|
289
|
+
var PricingTable = ({
|
|
290
|
+
plans,
|
|
291
|
+
customerId,
|
|
292
|
+
customerEmail,
|
|
293
|
+
onPlanSelect,
|
|
294
|
+
onCheckoutSuccess,
|
|
295
|
+
onCheckoutError,
|
|
296
|
+
showFeatures = true,
|
|
297
|
+
showTrialInfo = true,
|
|
298
|
+
layout = "carousel",
|
|
299
|
+
containerStyle
|
|
300
|
+
}) => {
|
|
301
|
+
if (!plans || plans.length === 0) {
|
|
302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.emptyContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.emptyText, children: "No pricing plans available" }) });
|
|
303
|
+
}
|
|
304
|
+
const renderPricingCard = (plan, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
305
|
+
PricingCard,
|
|
306
|
+
{
|
|
307
|
+
plan,
|
|
308
|
+
customerId,
|
|
309
|
+
customerEmail,
|
|
310
|
+
onSelect: () => onPlanSelect?.(plan),
|
|
311
|
+
onCheckoutSuccess: () => onCheckoutSuccess?.(plan),
|
|
312
|
+
onCheckoutError: (error) => onCheckoutError?.(plan, error),
|
|
313
|
+
showFeatures,
|
|
314
|
+
showTrialInfo,
|
|
315
|
+
style: layout === "carousel" ? styles2.carouselCard : styles2.listCard
|
|
316
|
+
},
|
|
317
|
+
plan.id
|
|
318
|
+
);
|
|
319
|
+
if (layout === "carousel") {
|
|
320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: [styles2.container, containerStyle], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
321
|
+
import_react_native2.ScrollView,
|
|
322
|
+
{
|
|
323
|
+
horizontal: true,
|
|
324
|
+
showsHorizontalScrollIndicator: false,
|
|
325
|
+
contentContainerStyle: styles2.carouselContainer,
|
|
326
|
+
snapToInterval: screenWidth * 0.8 + 16,
|
|
327
|
+
decelerationRate: "fast",
|
|
328
|
+
children: plans.map(renderPricingCard)
|
|
329
|
+
}
|
|
330
|
+
) });
|
|
331
|
+
}
|
|
332
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.ScrollView, { style: [styles2.container, containerStyle], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.listContainer, children: plans.map(renderPricingCard) }) });
|
|
333
|
+
};
|
|
334
|
+
var PricingCard = ({
|
|
335
|
+
plan,
|
|
336
|
+
customerId,
|
|
337
|
+
customerEmail,
|
|
338
|
+
onSelect,
|
|
339
|
+
onCheckoutSuccess,
|
|
340
|
+
onCheckoutError,
|
|
341
|
+
showFeatures = true,
|
|
342
|
+
showTrialInfo = true,
|
|
343
|
+
style
|
|
344
|
+
}) => {
|
|
345
|
+
const isPopular = plan.popular;
|
|
346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.card, isPopular && styles2.popularCard, style], children: [
|
|
347
|
+
isPopular && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.popularBadge, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.popularBadgeText, children: "Most Popular" }) }),
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: styles2.cardHeader, children: [
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.planName, children: plan.name }),
|
|
350
|
+
plan.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.planDescription, children: plan.description }),
|
|
351
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.planPrice, children: formatPricingPlan(plan) }),
|
|
352
|
+
showTrialInfo && plan.trialPeriodDays && plan.trialPeriodDays > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.trialInfo, children: formatTrialPeriod(plan.trialPeriodDays) })
|
|
353
|
+
] }),
|
|
354
|
+
showFeatures && plan.features && plan.features.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.featuresContainer, children: plan.features.map((feature, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: styles2.featureItem, children: [
|
|
355
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.featureCheckmark, children: "\u2713" }),
|
|
356
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.Text, { style: styles2.featureText, children: feature })
|
|
357
|
+
] }, index)) }),
|
|
358
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.buttonContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
359
|
+
CheckoutButton,
|
|
360
|
+
{
|
|
361
|
+
priceId: plan.stripePriceId,
|
|
362
|
+
customerId,
|
|
363
|
+
customerEmail,
|
|
364
|
+
trialPeriodDays: plan.trialPeriodDays,
|
|
365
|
+
metadata: plan.metadata,
|
|
366
|
+
onSuccess: onCheckoutSuccess,
|
|
367
|
+
onError: onCheckoutError,
|
|
368
|
+
style: [
|
|
369
|
+
styles2.ctaButton,
|
|
370
|
+
isPopular ? styles2.popularButton : styles2.regularButton
|
|
371
|
+
],
|
|
372
|
+
textStyle: [
|
|
373
|
+
styles2.ctaButtonText,
|
|
374
|
+
isPopular ? styles2.popularButtonText : styles2.regularButtonText
|
|
375
|
+
],
|
|
376
|
+
children: "Get Started"
|
|
377
|
+
}
|
|
378
|
+
) })
|
|
379
|
+
] });
|
|
380
|
+
};
|
|
381
|
+
var styles2 = import_react_native2.StyleSheet.create({
|
|
382
|
+
container: {
|
|
383
|
+
flex: 1
|
|
384
|
+
},
|
|
385
|
+
emptyContainer: {
|
|
386
|
+
flex: 1,
|
|
387
|
+
justifyContent: "center",
|
|
388
|
+
alignItems: "center",
|
|
389
|
+
padding: 20
|
|
390
|
+
},
|
|
391
|
+
emptyText: {
|
|
392
|
+
fontSize: 16,
|
|
393
|
+
color: "#666",
|
|
394
|
+
textAlign: "center"
|
|
395
|
+
},
|
|
396
|
+
carouselContainer: {
|
|
397
|
+
paddingHorizontal: 16
|
|
398
|
+
},
|
|
399
|
+
listContainer: {
|
|
400
|
+
padding: 16
|
|
401
|
+
},
|
|
402
|
+
carouselCard: {
|
|
403
|
+
width: screenWidth * 0.8,
|
|
404
|
+
marginRight: 16
|
|
405
|
+
},
|
|
406
|
+
listCard: {
|
|
407
|
+
marginBottom: 16
|
|
408
|
+
},
|
|
409
|
+
card: {
|
|
410
|
+
backgroundColor: "#ffffff",
|
|
411
|
+
borderRadius: 12,
|
|
412
|
+
padding: 20,
|
|
413
|
+
shadowColor: "#000",
|
|
414
|
+
shadowOffset: {
|
|
415
|
+
width: 0,
|
|
416
|
+
height: 2
|
|
417
|
+
},
|
|
418
|
+
shadowOpacity: 0.1,
|
|
419
|
+
shadowRadius: 8,
|
|
420
|
+
elevation: 4,
|
|
421
|
+
borderWidth: 1,
|
|
422
|
+
borderColor: "#e0e0e0"
|
|
423
|
+
},
|
|
424
|
+
popularCard: {
|
|
425
|
+
borderColor: "#007AFF",
|
|
426
|
+
borderWidth: 2
|
|
427
|
+
},
|
|
428
|
+
popularBadge: {
|
|
429
|
+
position: "absolute",
|
|
430
|
+
top: -1,
|
|
431
|
+
left: -1,
|
|
432
|
+
right: -1,
|
|
433
|
+
backgroundColor: "#007AFF",
|
|
434
|
+
paddingVertical: 8,
|
|
435
|
+
borderTopLeftRadius: 12,
|
|
436
|
+
borderTopRightRadius: 12,
|
|
437
|
+
alignItems: "center"
|
|
438
|
+
},
|
|
439
|
+
popularBadgeText: {
|
|
440
|
+
color: "#ffffff",
|
|
441
|
+
fontSize: 12,
|
|
442
|
+
fontWeight: "600"
|
|
443
|
+
},
|
|
444
|
+
cardHeader: {
|
|
445
|
+
alignItems: "center",
|
|
446
|
+
marginBottom: 20,
|
|
447
|
+
marginTop: 20
|
|
448
|
+
// Extra space for popular badge
|
|
449
|
+
},
|
|
450
|
+
planName: {
|
|
451
|
+
fontSize: 20,
|
|
452
|
+
fontWeight: "700",
|
|
453
|
+
color: "#333",
|
|
454
|
+
marginBottom: 8,
|
|
455
|
+
textAlign: "center"
|
|
456
|
+
},
|
|
457
|
+
planDescription: {
|
|
458
|
+
fontSize: 14,
|
|
459
|
+
color: "#666",
|
|
460
|
+
textAlign: "center",
|
|
461
|
+
marginBottom: 16,
|
|
462
|
+
lineHeight: 20
|
|
463
|
+
},
|
|
464
|
+
planPrice: {
|
|
465
|
+
fontSize: 28,
|
|
466
|
+
fontWeight: "800",
|
|
467
|
+
color: "#333",
|
|
468
|
+
marginBottom: 8,
|
|
469
|
+
textAlign: "center"
|
|
470
|
+
},
|
|
471
|
+
trialInfo: {
|
|
472
|
+
fontSize: 12,
|
|
473
|
+
color: "#22c55e",
|
|
474
|
+
fontWeight: "600",
|
|
475
|
+
textAlign: "center"
|
|
476
|
+
},
|
|
477
|
+
featuresContainer: {
|
|
478
|
+
marginBottom: 24
|
|
479
|
+
},
|
|
480
|
+
featureItem: {
|
|
481
|
+
flexDirection: "row",
|
|
482
|
+
alignItems: "flex-start",
|
|
483
|
+
marginBottom: 12
|
|
484
|
+
},
|
|
485
|
+
featureCheckmark: {
|
|
486
|
+
color: "#22c55e",
|
|
487
|
+
fontSize: 16,
|
|
488
|
+
fontWeight: "700",
|
|
489
|
+
marginRight: 12,
|
|
490
|
+
marginTop: 2
|
|
491
|
+
},
|
|
492
|
+
featureText: {
|
|
493
|
+
flex: 1,
|
|
494
|
+
fontSize: 14,
|
|
495
|
+
color: "#555",
|
|
496
|
+
lineHeight: 20
|
|
497
|
+
},
|
|
498
|
+
buttonContainer: {
|
|
499
|
+
marginTop: "auto"
|
|
500
|
+
},
|
|
501
|
+
ctaButton: {
|
|
502
|
+
paddingVertical: 16,
|
|
503
|
+
borderRadius: 8
|
|
504
|
+
},
|
|
505
|
+
ctaButtonText: {
|
|
506
|
+
fontSize: 16,
|
|
507
|
+
fontWeight: "600"
|
|
508
|
+
},
|
|
509
|
+
popularButton: {
|
|
510
|
+
backgroundColor: "#007AFF"
|
|
511
|
+
},
|
|
512
|
+
popularButtonText: {
|
|
513
|
+
color: "#ffffff"
|
|
514
|
+
},
|
|
515
|
+
regularButton: {
|
|
516
|
+
backgroundColor: "#f8f9fa",
|
|
517
|
+
borderWidth: 1,
|
|
518
|
+
borderColor: "#e0e0e0"
|
|
519
|
+
},
|
|
520
|
+
regularButtonText: {
|
|
521
|
+
color: "#333"
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// src/native/components/PaymentForm.tsx
|
|
526
|
+
var import_react3 = require("react");
|
|
527
|
+
var import_react_native3 = require("react-native");
|
|
528
|
+
var import_stripe_react_native2 = require("@stripe/stripe-react-native");
|
|
529
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
530
|
+
var PaymentForm = ({
|
|
531
|
+
clientSecret,
|
|
532
|
+
customerId,
|
|
533
|
+
customerEmail,
|
|
534
|
+
amount,
|
|
535
|
+
currency = "usd",
|
|
536
|
+
description,
|
|
537
|
+
metadata,
|
|
538
|
+
onSuccess,
|
|
539
|
+
onError,
|
|
540
|
+
onLoading,
|
|
541
|
+
containerStyle,
|
|
542
|
+
showBillingDetails = true
|
|
543
|
+
}) => {
|
|
544
|
+
const { confirmPayment } = (0, import_stripe_react_native2.useStripe)();
|
|
545
|
+
const { isConfigured } = usePaymentsConfig();
|
|
546
|
+
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
547
|
+
const [email, setEmail] = (0, import_react3.useState)(customerEmail || "");
|
|
548
|
+
const [name, setName] = (0, import_react3.useState)("");
|
|
549
|
+
const [phone, setPhone] = (0, import_react3.useState)("");
|
|
550
|
+
const [cardComplete, setCardComplete] = (0, import_react3.useState)(false);
|
|
551
|
+
const handleError = (errorMessage) => {
|
|
552
|
+
onError?.(errorMessage);
|
|
553
|
+
import_react_native3.Alert.alert("Payment Error", errorMessage);
|
|
554
|
+
console.error("Payment error:", errorMessage);
|
|
555
|
+
};
|
|
556
|
+
const handlePayment = async () => {
|
|
557
|
+
if (!isConfigured) {
|
|
558
|
+
handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (showBillingDetails && email && !validateEmail(email)) {
|
|
562
|
+
handleError("Please enter a valid email address");
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!cardComplete) {
|
|
566
|
+
handleError("Please complete your card information");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
let paymentClientSecret = clientSecret;
|
|
570
|
+
if (!paymentClientSecret && amount) {
|
|
571
|
+
try {
|
|
572
|
+
const response = await fetch("/api/payments/create-payment-intent", {
|
|
573
|
+
method: "POST",
|
|
574
|
+
headers: { "Content-Type": "application/json" },
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
amount,
|
|
577
|
+
currency,
|
|
578
|
+
customerId,
|
|
579
|
+
description,
|
|
580
|
+
metadata
|
|
581
|
+
})
|
|
582
|
+
});
|
|
583
|
+
if (!response.ok) {
|
|
584
|
+
throw new Error("Failed to create payment intent");
|
|
585
|
+
}
|
|
586
|
+
const { client_secret } = await response.json();
|
|
587
|
+
paymentClientSecret = client_secret;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
handleError("Failed to initialize payment");
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (!paymentClientSecret) {
|
|
594
|
+
handleError("Payment could not be initialized");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
setLoading(true);
|
|
598
|
+
onLoading?.(true);
|
|
599
|
+
try {
|
|
600
|
+
const billingDetails = showBillingDetails ? {
|
|
601
|
+
email: email || void 0,
|
|
602
|
+
name: name || void 0,
|
|
603
|
+
phone: phone || void 0
|
|
604
|
+
} : void 0;
|
|
605
|
+
const { error, paymentIntent } = await confirmPayment(
|
|
606
|
+
paymentClientSecret,
|
|
607
|
+
{
|
|
608
|
+
paymentMethodType: "Card",
|
|
609
|
+
paymentMethodData: {
|
|
610
|
+
billingDetails
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
);
|
|
614
|
+
if (error) {
|
|
615
|
+
throw new Error(error.message || ERROR_MESSAGES.PAYMENT_FAILED);
|
|
616
|
+
}
|
|
617
|
+
if (paymentIntent?.status === "Succeeded") {
|
|
618
|
+
onSuccess?.(paymentIntent);
|
|
619
|
+
import_react_native3.Alert.alert("Success", "Payment completed successfully!");
|
|
620
|
+
} else {
|
|
621
|
+
throw new Error("Payment was not completed");
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
const errorMessage = error instanceof Error ? error.message : ERROR_MESSAGES.PAYMENT_FAILED;
|
|
625
|
+
handleError(errorMessage);
|
|
626
|
+
} finally {
|
|
627
|
+
setLoading(false);
|
|
628
|
+
onLoading?.(false);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
632
|
+
import_react_native3.KeyboardAvoidingView,
|
|
633
|
+
{
|
|
634
|
+
behavior: import_react_native3.Platform.OS === "ios" ? "padding" : "height",
|
|
635
|
+
style: [styles3.container, containerStyle],
|
|
636
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.ScrollView, { showsVerticalScrollIndicator: false, children: [
|
|
637
|
+
showBillingDetails && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.billingSection, children: [
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.Text, { style: styles3.sectionTitle, children: "Billing Information" }),
|
|
639
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.inputContainer, children: [
|
|
640
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.Text, { style: styles3.inputLabel, children: "Email *" }),
|
|
641
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
642
|
+
import_react_native3.TextInput,
|
|
643
|
+
{
|
|
644
|
+
style: styles3.textInput,
|
|
645
|
+
value: email,
|
|
646
|
+
onChangeText: setEmail,
|
|
647
|
+
placeholder: "your@email.com",
|
|
648
|
+
keyboardType: "email-address",
|
|
649
|
+
autoCapitalize: "none",
|
|
650
|
+
autoCorrect: false
|
|
651
|
+
}
|
|
652
|
+
)
|
|
653
|
+
] }),
|
|
654
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.inputContainer, children: [
|
|
655
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.Text, { style: styles3.inputLabel, children: "Full Name" }),
|
|
656
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
657
|
+
import_react_native3.TextInput,
|
|
658
|
+
{
|
|
659
|
+
style: styles3.textInput,
|
|
660
|
+
value: name,
|
|
661
|
+
onChangeText: setName,
|
|
662
|
+
placeholder: "John Doe",
|
|
663
|
+
autoCapitalize: "words"
|
|
664
|
+
}
|
|
665
|
+
)
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.inputContainer, children: [
|
|
668
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.Text, { style: styles3.inputLabel, children: "Phone" }),
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
670
|
+
import_react_native3.TextInput,
|
|
671
|
+
{
|
|
672
|
+
style: styles3.textInput,
|
|
673
|
+
value: phone,
|
|
674
|
+
onChangeText: setPhone,
|
|
675
|
+
placeholder: "+1 (555) 123-4567",
|
|
676
|
+
keyboardType: "phone-pad"
|
|
677
|
+
}
|
|
678
|
+
)
|
|
679
|
+
] })
|
|
680
|
+
] }),
|
|
681
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.cardSection, children: [
|
|
682
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.Text, { style: styles3.sectionTitle, children: "Payment Information" }),
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: styles3.cardContainer, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
684
|
+
import_stripe_react_native2.CardField,
|
|
685
|
+
{
|
|
686
|
+
postalCodeEnabled: true,
|
|
687
|
+
placeholders: {
|
|
688
|
+
number: "4242 4242 4242 4242"
|
|
689
|
+
},
|
|
690
|
+
cardStyle: styles3.cardField,
|
|
691
|
+
style: styles3.cardFieldContainer,
|
|
692
|
+
onCardChange: (cardDetails) => {
|
|
693
|
+
setCardComplete(cardDetails.complete);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
) })
|
|
697
|
+
] }),
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
699
|
+
import_react_native3.TouchableOpacity,
|
|
700
|
+
{
|
|
701
|
+
style: [styles3.payButton, loading && styles3.payButtonDisabled],
|
|
702
|
+
onPress: handlePayment,
|
|
703
|
+
disabled: loading || !cardComplete,
|
|
704
|
+
activeOpacity: 0.8,
|
|
705
|
+
children: loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.ActivityIndicator, { color: "#ffffff", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.Text, { style: styles3.payButtonText, children: [
|
|
706
|
+
"Pay ",
|
|
707
|
+
amount ? `$${(amount / 100).toFixed(2)}` : ""
|
|
708
|
+
] })
|
|
709
|
+
}
|
|
710
|
+
)
|
|
711
|
+
] })
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
};
|
|
715
|
+
var styles3 = import_react_native3.StyleSheet.create({
|
|
716
|
+
container: {
|
|
717
|
+
flex: 1,
|
|
718
|
+
padding: 20,
|
|
719
|
+
backgroundColor: "#ffffff"
|
|
720
|
+
},
|
|
721
|
+
billingSection: {
|
|
722
|
+
marginBottom: 24
|
|
723
|
+
},
|
|
724
|
+
cardSection: {
|
|
725
|
+
marginBottom: 32
|
|
726
|
+
},
|
|
727
|
+
sectionTitle: {
|
|
728
|
+
fontSize: 18,
|
|
729
|
+
fontWeight: "600",
|
|
730
|
+
color: "#333",
|
|
731
|
+
marginBottom: 16
|
|
732
|
+
},
|
|
733
|
+
inputContainer: {
|
|
734
|
+
marginBottom: 16
|
|
735
|
+
},
|
|
736
|
+
inputLabel: {
|
|
737
|
+
fontSize: 14,
|
|
738
|
+
fontWeight: "500",
|
|
739
|
+
color: "#555",
|
|
740
|
+
marginBottom: 8
|
|
741
|
+
},
|
|
742
|
+
textInput: {
|
|
743
|
+
borderWidth: 1,
|
|
744
|
+
borderColor: "#ddd",
|
|
745
|
+
borderRadius: 8,
|
|
746
|
+
paddingHorizontal: 12,
|
|
747
|
+
paddingVertical: 12,
|
|
748
|
+
fontSize: 16,
|
|
749
|
+
backgroundColor: "#fafafa"
|
|
750
|
+
},
|
|
751
|
+
cardContainer: {
|
|
752
|
+
borderWidth: 1,
|
|
753
|
+
borderColor: "#ddd",
|
|
754
|
+
borderRadius: 8,
|
|
755
|
+
backgroundColor: "#fafafa"
|
|
756
|
+
},
|
|
757
|
+
cardFieldContainer: {
|
|
758
|
+
width: "100%",
|
|
759
|
+
height: 50
|
|
760
|
+
},
|
|
761
|
+
cardField: {
|
|
762
|
+
backgroundColor: "#fafafa",
|
|
763
|
+
textColor: "#333",
|
|
764
|
+
fontSize: 16,
|
|
765
|
+
placeholderColor: "#999"
|
|
766
|
+
},
|
|
767
|
+
payButton: {
|
|
768
|
+
backgroundColor: "#007AFF",
|
|
769
|
+
paddingVertical: 16,
|
|
770
|
+
borderRadius: 8,
|
|
771
|
+
alignItems: "center",
|
|
772
|
+
justifyContent: "center",
|
|
773
|
+
minHeight: 52
|
|
774
|
+
},
|
|
775
|
+
payButtonDisabled: {
|
|
776
|
+
opacity: 0.5
|
|
777
|
+
},
|
|
778
|
+
payButtonText: {
|
|
779
|
+
color: "#ffffff",
|
|
780
|
+
fontSize: 18,
|
|
781
|
+
fontWeight: "600"
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
// src/native/providers/StripeProvider.tsx
|
|
786
|
+
var import_stripe_react_native3 = require("@stripe/stripe-react-native");
|
|
787
|
+
var import_react_native4 = require("react-native");
|
|
788
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
789
|
+
var StripeProvider = ({
|
|
790
|
+
children,
|
|
791
|
+
merchantIdentifier,
|
|
792
|
+
urlScheme,
|
|
793
|
+
setReturnUrlSchemeOnAndroid = false
|
|
794
|
+
}) => {
|
|
795
|
+
const { config, isConfigured } = usePaymentsConfig();
|
|
796
|
+
if (!isConfigured) {
|
|
797
|
+
if (config.environment === "development") {
|
|
798
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native4.View, { style: styles4.errorContainer, children: [
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Text, { style: styles4.errorTitle, children: "Payments Provider Error" }),
|
|
800
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Text, { style: styles4.errorMessage, children: ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED }),
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Text, { style: styles4.errorSubtext, children: "Please check your PaymentsProvider configuration." })
|
|
802
|
+
] });
|
|
803
|
+
}
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
807
|
+
import_stripe_react_native3.StripeProvider,
|
|
808
|
+
{
|
|
809
|
+
publishableKey: config.publishableKey,
|
|
810
|
+
merchantIdentifier,
|
|
811
|
+
urlScheme,
|
|
812
|
+
setReturnUrlSchemeOnAndroid,
|
|
813
|
+
children
|
|
814
|
+
}
|
|
815
|
+
);
|
|
816
|
+
};
|
|
817
|
+
var styles4 = import_react_native4.StyleSheet.create({
|
|
818
|
+
errorContainer: {
|
|
819
|
+
padding: 20,
|
|
820
|
+
backgroundColor: "#fee",
|
|
821
|
+
borderColor: "#fcc",
|
|
822
|
+
borderWidth: 1,
|
|
823
|
+
borderRadius: 4,
|
|
824
|
+
margin: 10
|
|
825
|
+
},
|
|
826
|
+
errorTitle: {
|
|
827
|
+
fontWeight: "bold",
|
|
828
|
+
fontSize: 16,
|
|
829
|
+
color: "#c53030",
|
|
830
|
+
marginBottom: 8
|
|
831
|
+
},
|
|
832
|
+
errorMessage: {
|
|
833
|
+
fontSize: 14,
|
|
834
|
+
color: "#c53030",
|
|
835
|
+
marginBottom: 4
|
|
836
|
+
},
|
|
837
|
+
errorSubtext: {
|
|
838
|
+
fontSize: 12,
|
|
839
|
+
color: "#a0a0a0"
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// src/native/hooks/useSubscription.ts
|
|
844
|
+
var import_react4 = require("react");
|
|
845
|
+
var import_react_native5 = require("react-native");
|
|
846
|
+
var useSubscription = (options = {}) => {
|
|
847
|
+
const {
|
|
848
|
+
customerId,
|
|
849
|
+
subscriptionId,
|
|
850
|
+
autoFetch = true,
|
|
851
|
+
onError,
|
|
852
|
+
showErrorAlert = true
|
|
853
|
+
} = options;
|
|
854
|
+
const [subscription, setSubscription] = (0, import_react4.useState)(null);
|
|
855
|
+
const [subscriptions, setSubscriptions] = (0, import_react4.useState)([]);
|
|
856
|
+
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
857
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
858
|
+
const handleError = (0, import_react4.useCallback)(
|
|
859
|
+
(errorMessage) => {
|
|
860
|
+
setError(errorMessage);
|
|
861
|
+
onError?.(errorMessage);
|
|
862
|
+
if (showErrorAlert) {
|
|
863
|
+
import_react_native5.Alert.alert("Subscription Error", errorMessage);
|
|
864
|
+
}
|
|
865
|
+
console.error("Subscription error:", errorMessage);
|
|
866
|
+
},
|
|
867
|
+
[onError, showErrorAlert]
|
|
868
|
+
);
|
|
869
|
+
const fetchSubscription2 = (0, import_react4.useCallback)(
|
|
870
|
+
async (id) => {
|
|
871
|
+
if (!validateStripeId(id, "subscription")) {
|
|
872
|
+
handleError("Invalid subscription ID");
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
const response = await fetch(`/api/payments/subscription/${id}`);
|
|
877
|
+
if (!response.ok) {
|
|
878
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
879
|
+
throw new Error(
|
|
880
|
+
errorData.error || ERROR_MESSAGES.SUBSCRIPTION_NOT_FOUND
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
const data = await response.json();
|
|
884
|
+
return data;
|
|
885
|
+
} catch (err) {
|
|
886
|
+
const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.SUBSCRIPTION_NOT_FOUND;
|
|
887
|
+
handleError(errorMessage);
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
[handleError]
|
|
892
|
+
);
|
|
893
|
+
const fetchCustomerSubscriptions2 = (0, import_react4.useCallback)(
|
|
894
|
+
async (id) => {
|
|
895
|
+
if (!validateStripeId(id, "customer")) {
|
|
896
|
+
handleError("Invalid customer ID");
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
const response = await fetch(
|
|
901
|
+
`/api/payments/customer/${id}/subscriptions`
|
|
902
|
+
);
|
|
903
|
+
if (!response.ok) {
|
|
904
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
905
|
+
throw new Error(errorData.error || ERROR_MESSAGES.CUSTOMER_NOT_FOUND);
|
|
906
|
+
}
|
|
907
|
+
const data = await response.json();
|
|
908
|
+
return data.subscriptions || [];
|
|
909
|
+
} catch (err) {
|
|
910
|
+
const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CUSTOMER_NOT_FOUND;
|
|
911
|
+
handleError(errorMessage);
|
|
912
|
+
return [];
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
[handleError]
|
|
916
|
+
);
|
|
917
|
+
const refetch = (0, import_react4.useCallback)(async () => {
|
|
918
|
+
if (!customerId && !subscriptionId) {
|
|
919
|
+
handleError("Either customerId or subscriptionId is required");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
setLoading(true);
|
|
923
|
+
setError(null);
|
|
924
|
+
try {
|
|
925
|
+
if (subscriptionId) {
|
|
926
|
+
const sub = await fetchSubscription2(subscriptionId);
|
|
927
|
+
setSubscription(sub);
|
|
928
|
+
}
|
|
929
|
+
if (customerId) {
|
|
930
|
+
const subs = await fetchCustomerSubscriptions2(customerId);
|
|
931
|
+
setSubscriptions(subs);
|
|
932
|
+
if (!subscriptionId && subs.length > 0) {
|
|
933
|
+
const activeSub = subs.find((s) => s.status === "active") || subs[0];
|
|
934
|
+
setSubscription(activeSub);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} finally {
|
|
938
|
+
setLoading(false);
|
|
939
|
+
}
|
|
940
|
+
}, [
|
|
941
|
+
customerId,
|
|
942
|
+
subscriptionId,
|
|
943
|
+
fetchSubscription2,
|
|
944
|
+
fetchCustomerSubscriptions2,
|
|
945
|
+
handleError
|
|
946
|
+
]);
|
|
947
|
+
const cancel = (0, import_react4.useCallback)(
|
|
948
|
+
async (targetSubscriptionId) => {
|
|
949
|
+
const idToCancel = targetSubscriptionId || subscriptionId;
|
|
950
|
+
if (!idToCancel) {
|
|
951
|
+
handleError("Subscription ID is required to cancel");
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
if (!validateStripeId(idToCancel, "subscription")) {
|
|
955
|
+
handleError("Invalid subscription ID");
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
return new Promise((resolve) => {
|
|
959
|
+
import_react_native5.Alert.alert(
|
|
960
|
+
"Cancel Subscription",
|
|
961
|
+
"Are you sure you want to cancel your subscription? It will remain active until the end of your current billing period.",
|
|
962
|
+
[
|
|
963
|
+
{
|
|
964
|
+
text: "Keep Subscription",
|
|
965
|
+
style: "cancel",
|
|
966
|
+
onPress: () => resolve(false)
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
text: "Cancel Subscription",
|
|
970
|
+
style: "destructive",
|
|
971
|
+
onPress: async () => {
|
|
972
|
+
try {
|
|
973
|
+
const response = await fetch(
|
|
974
|
+
`/api/payments/subscription/${idToCancel}/cancel`,
|
|
975
|
+
{
|
|
976
|
+
method: "POST",
|
|
977
|
+
headers: { "Content-Type": "application/json" }
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
if (!response.ok) {
|
|
981
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
982
|
+
throw new Error(
|
|
983
|
+
errorData.error || "Failed to cancel subscription"
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
await refetch();
|
|
987
|
+
import_react_native5.Alert.alert(
|
|
988
|
+
"Success",
|
|
989
|
+
"Your subscription has been cancelled and will end at the end of your current billing period."
|
|
990
|
+
);
|
|
991
|
+
resolve(true);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to cancel subscription";
|
|
994
|
+
handleError(errorMessage);
|
|
995
|
+
resolve(false);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
]
|
|
1000
|
+
);
|
|
1001
|
+
});
|
|
1002
|
+
},
|
|
1003
|
+
[subscriptionId, refetch, handleError]
|
|
1004
|
+
);
|
|
1005
|
+
const reactivate = (0, import_react4.useCallback)(
|
|
1006
|
+
async (targetSubscriptionId) => {
|
|
1007
|
+
const idToReactivate = targetSubscriptionId || subscriptionId;
|
|
1008
|
+
if (!idToReactivate) {
|
|
1009
|
+
handleError("Subscription ID is required to reactivate");
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
if (!validateStripeId(idToReactivate, "subscription")) {
|
|
1013
|
+
handleError("Invalid subscription ID");
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
try {
|
|
1017
|
+
const response = await fetch(
|
|
1018
|
+
`/api/payments/subscription/${idToReactivate}/reactivate`,
|
|
1019
|
+
{
|
|
1020
|
+
method: "POST",
|
|
1021
|
+
headers: { "Content-Type": "application/json" }
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
if (!response.ok) {
|
|
1025
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1026
|
+
throw new Error(
|
|
1027
|
+
errorData.error || "Failed to reactivate subscription"
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
await refetch();
|
|
1031
|
+
import_react_native5.Alert.alert("Success", "Your subscription has been reactivated!");
|
|
1032
|
+
return true;
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to reactivate subscription";
|
|
1035
|
+
handleError(errorMessage);
|
|
1036
|
+
return false;
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
[subscriptionId, refetch, handleError]
|
|
1040
|
+
);
|
|
1041
|
+
const updatePaymentMethod = (0, import_react4.useCallback)(
|
|
1042
|
+
async (targetSubscriptionId, paymentMethodId) => {
|
|
1043
|
+
if (!validateStripeId(targetSubscriptionId, "subscription")) {
|
|
1044
|
+
handleError("Invalid subscription ID");
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
const response = await fetch(
|
|
1049
|
+
`/api/payments/subscription/${targetSubscriptionId}/payment-method`,
|
|
1050
|
+
{
|
|
1051
|
+
method: "PUT",
|
|
1052
|
+
headers: { "Content-Type": "application/json" },
|
|
1053
|
+
body: JSON.stringify({ paymentMethodId })
|
|
1054
|
+
}
|
|
1055
|
+
);
|
|
1056
|
+
if (!response.ok) {
|
|
1057
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1058
|
+
throw new Error(errorData.error || "Failed to update payment method");
|
|
1059
|
+
}
|
|
1060
|
+
await refetch();
|
|
1061
|
+
import_react_native5.Alert.alert("Success", "Payment method updated successfully!");
|
|
1062
|
+
return true;
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to update payment method";
|
|
1065
|
+
handleError(errorMessage);
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
[refetch, handleError]
|
|
1070
|
+
);
|
|
1071
|
+
(0, import_react4.useEffect)(() => {
|
|
1072
|
+
if (autoFetch && (customerId || subscriptionId)) {
|
|
1073
|
+
refetch();
|
|
1074
|
+
}
|
|
1075
|
+
}, [autoFetch, customerId, subscriptionId, refetch]);
|
|
1076
|
+
return {
|
|
1077
|
+
subscription,
|
|
1078
|
+
subscriptions,
|
|
1079
|
+
loading,
|
|
1080
|
+
error,
|
|
1081
|
+
refetch,
|
|
1082
|
+
cancel,
|
|
1083
|
+
reactivate,
|
|
1084
|
+
updatePaymentMethod
|
|
1085
|
+
};
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// src/native/hooks/useCheckout.ts
|
|
1089
|
+
var import_react5 = require("react");
|
|
1090
|
+
var import_react_native6 = require("react-native");
|
|
1091
|
+
var import_stripe_react_native4 = require("@stripe/stripe-react-native");
|
|
1092
|
+
var useCheckout = (options = {}) => {
|
|
1093
|
+
const {
|
|
1094
|
+
onSuccess,
|
|
1095
|
+
onError,
|
|
1096
|
+
onLoading,
|
|
1097
|
+
showErrorAlert = true,
|
|
1098
|
+
showSuccessAlert = true
|
|
1099
|
+
} = options;
|
|
1100
|
+
const { initPaymentSheet, presentPaymentSheet, confirmPayment } = (0, import_stripe_react_native4.useStripe)();
|
|
1101
|
+
const { isConfigured } = usePaymentsConfig();
|
|
1102
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
1103
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
1104
|
+
const handleError = (0, import_react5.useCallback)(
|
|
1105
|
+
(errorMessage) => {
|
|
1106
|
+
setError(errorMessage);
|
|
1107
|
+
onError?.(errorMessage);
|
|
1108
|
+
if (showErrorAlert) {
|
|
1109
|
+
import_react_native6.Alert.alert("Payment Error", errorMessage);
|
|
1110
|
+
}
|
|
1111
|
+
console.error("Checkout error:", errorMessage);
|
|
1112
|
+
},
|
|
1113
|
+
[onError, showErrorAlert]
|
|
1114
|
+
);
|
|
1115
|
+
const handleSuccess = (0, import_react5.useCallback)(
|
|
1116
|
+
(result) => {
|
|
1117
|
+
onSuccess?.(result);
|
|
1118
|
+
if (showSuccessAlert) {
|
|
1119
|
+
import_react_native6.Alert.alert("Success", "Payment completed successfully!");
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
[onSuccess, showSuccessAlert]
|
|
1123
|
+
);
|
|
1124
|
+
const createSubscription = (0, import_react5.useCallback)(
|
|
1125
|
+
async (params) => {
|
|
1126
|
+
if (!isConfigured) {
|
|
1127
|
+
handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
if (!validateStripeId(params.priceId, "price")) {
|
|
1131
|
+
handleError(ERROR_MESSAGES.INVALID_PRICE_ID);
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
if (params.customerId && !validateStripeId(params.customerId, "customer")) {
|
|
1135
|
+
handleError(ERROR_MESSAGES.INVALID_CUSTOMER_ID);
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
if (params.customerEmail && !validateEmail(params.customerEmail)) {
|
|
1139
|
+
handleError("Invalid customer email");
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
setLoading(true);
|
|
1143
|
+
setError(null);
|
|
1144
|
+
onLoading?.(true);
|
|
1145
|
+
try {
|
|
1146
|
+
const response = await fetch(
|
|
1147
|
+
"/api/payments/create-subscription-setup",
|
|
1148
|
+
{
|
|
1149
|
+
method: "POST",
|
|
1150
|
+
headers: { "Content-Type": "application/json" },
|
|
1151
|
+
body: JSON.stringify(params)
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
if (!response.ok) {
|
|
1155
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1156
|
+
throw new Error(
|
|
1157
|
+
errorData.error || "Failed to create subscription setup"
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
const {
|
|
1161
|
+
setupIntentClientSecret,
|
|
1162
|
+
customerId: returnedCustomerId,
|
|
1163
|
+
customerEphemeralKeySecret
|
|
1164
|
+
} = await response.json();
|
|
1165
|
+
if (!setupIntentClientSecret) {
|
|
1166
|
+
throw new Error("No setup intent client secret returned from server");
|
|
1167
|
+
}
|
|
1168
|
+
const { error: initError } = await initPaymentSheet({
|
|
1169
|
+
setupIntentClientSecret,
|
|
1170
|
+
customerId: returnedCustomerId,
|
|
1171
|
+
customerEphemeralKeySecret,
|
|
1172
|
+
merchantDisplayName: "Your App Name",
|
|
1173
|
+
// TODO: Make this configurable
|
|
1174
|
+
allowsDelayedPaymentMethods: true
|
|
1175
|
+
});
|
|
1176
|
+
if (initError) {
|
|
1177
|
+
throw new Error(
|
|
1178
|
+
initError.message || "Failed to initialize payment sheet"
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
const { error: presentError } = await presentPaymentSheet();
|
|
1182
|
+
if (presentError) {
|
|
1183
|
+
if (presentError.code === "Canceled") {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
presentError.message || ERROR_MESSAGES.PAYMENT_FAILED
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
handleSuccess({ type: "subscription", customerId: returnedCustomerId });
|
|
1191
|
+
return true;
|
|
1192
|
+
} catch (err) {
|
|
1193
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to create subscription";
|
|
1194
|
+
handleError(errorMessage);
|
|
1195
|
+
return false;
|
|
1196
|
+
} finally {
|
|
1197
|
+
setLoading(false);
|
|
1198
|
+
onLoading?.(false);
|
|
1199
|
+
}
|
|
1200
|
+
},
|
|
1201
|
+
[
|
|
1202
|
+
isConfigured,
|
|
1203
|
+
initPaymentSheet,
|
|
1204
|
+
presentPaymentSheet,
|
|
1205
|
+
handleError,
|
|
1206
|
+
handleSuccess,
|
|
1207
|
+
onLoading
|
|
1208
|
+
]
|
|
1209
|
+
);
|
|
1210
|
+
const createPaymentIntent2 = (0, import_react5.useCallback)(
|
|
1211
|
+
async (params) => {
|
|
1212
|
+
if (!isConfigured) {
|
|
1213
|
+
handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
if (params.amount <= 0) {
|
|
1217
|
+
handleError("Amount must be greater than 0");
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
if (params.customerId && !validateStripeId(params.customerId, "customer")) {
|
|
1221
|
+
handleError(ERROR_MESSAGES.INVALID_CUSTOMER_ID);
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
setLoading(true);
|
|
1225
|
+
setError(null);
|
|
1226
|
+
onLoading?.(true);
|
|
1227
|
+
try {
|
|
1228
|
+
const response = await fetch("/api/payments/create-payment-intent", {
|
|
1229
|
+
method: "POST",
|
|
1230
|
+
headers: { "Content-Type": "application/json" },
|
|
1231
|
+
body: JSON.stringify(params)
|
|
1232
|
+
});
|
|
1233
|
+
if (!response.ok) {
|
|
1234
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1235
|
+
throw new Error(errorData.error || "Failed to create payment intent");
|
|
1236
|
+
}
|
|
1237
|
+
const { client_secret } = await response.json();
|
|
1238
|
+
if (!client_secret) {
|
|
1239
|
+
throw new Error("No client secret returned from server");
|
|
1240
|
+
}
|
|
1241
|
+
return client_secret;
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to create payment intent";
|
|
1244
|
+
handleError(errorMessage);
|
|
1245
|
+
return null;
|
|
1246
|
+
} finally {
|
|
1247
|
+
setLoading(false);
|
|
1248
|
+
onLoading?.(false);
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1251
|
+
[isConfigured, handleError, onLoading]
|
|
1252
|
+
);
|
|
1253
|
+
const processPayment = (0, import_react5.useCallback)(
|
|
1254
|
+
async (clientSecret, billingDetails) => {
|
|
1255
|
+
if (!isConfigured) {
|
|
1256
|
+
handleError(ERROR_MESSAGES.PROVIDER_NOT_CONFIGURED);
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
if (billingDetails?.email && !validateEmail(billingDetails.email)) {
|
|
1260
|
+
handleError("Invalid email address");
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
setLoading(true);
|
|
1264
|
+
setError(null);
|
|
1265
|
+
onLoading?.(true);
|
|
1266
|
+
try {
|
|
1267
|
+
const { error: error2, paymentIntent } = await confirmPayment(clientSecret, {
|
|
1268
|
+
paymentMethodType: "Card",
|
|
1269
|
+
paymentMethodData: {
|
|
1270
|
+
billingDetails
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
if (error2) {
|
|
1274
|
+
throw new Error(error2.message || ERROR_MESSAGES.PAYMENT_FAILED);
|
|
1275
|
+
}
|
|
1276
|
+
if (paymentIntent?.status === "Succeeded") {
|
|
1277
|
+
handleSuccess({ type: "payment", paymentIntent });
|
|
1278
|
+
return true;
|
|
1279
|
+
} else {
|
|
1280
|
+
throw new Error("Payment was not completed");
|
|
1281
|
+
}
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.PAYMENT_FAILED;
|
|
1284
|
+
handleError(errorMessage);
|
|
1285
|
+
return false;
|
|
1286
|
+
} finally {
|
|
1287
|
+
setLoading(false);
|
|
1288
|
+
onLoading?.(false);
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
[isConfigured, confirmPayment, handleError, handleSuccess, onLoading]
|
|
1292
|
+
);
|
|
1293
|
+
return {
|
|
1294
|
+
loading,
|
|
1295
|
+
error,
|
|
1296
|
+
createSubscription,
|
|
1297
|
+
createPaymentIntent: createPaymentIntent2,
|
|
1298
|
+
processPayment
|
|
1299
|
+
};
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// src/native/hooks/useCustomer.ts
|
|
1303
|
+
var import_react6 = require("react");
|
|
1304
|
+
var import_react_native7 = require("react-native");
|
|
1305
|
+
var useCustomer = (options = {}) => {
|
|
1306
|
+
const {
|
|
1307
|
+
customerId,
|
|
1308
|
+
autoFetch = true,
|
|
1309
|
+
onError,
|
|
1310
|
+
showErrorAlert = true,
|
|
1311
|
+
showSuccessAlert = true
|
|
1312
|
+
} = options;
|
|
1313
|
+
const [customer, setCustomer] = (0, import_react6.useState)(null);
|
|
1314
|
+
const [paymentMethods, setPaymentMethods] = (0, import_react6.useState)([]);
|
|
1315
|
+
const [loading, setLoading] = (0, import_react6.useState)(false);
|
|
1316
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1317
|
+
const handleError = (0, import_react6.useCallback)(
|
|
1318
|
+
(errorMessage) => {
|
|
1319
|
+
setError(errorMessage);
|
|
1320
|
+
onError?.(errorMessage);
|
|
1321
|
+
if (showErrorAlert) {
|
|
1322
|
+
import_react_native7.Alert.alert("Customer Error", errorMessage);
|
|
1323
|
+
}
|
|
1324
|
+
console.error("Customer error:", errorMessage);
|
|
1325
|
+
},
|
|
1326
|
+
[onError, showErrorAlert]
|
|
1327
|
+
);
|
|
1328
|
+
const handleSuccess = (0, import_react6.useCallback)(
|
|
1329
|
+
(message) => {
|
|
1330
|
+
if (showSuccessAlert) {
|
|
1331
|
+
import_react_native7.Alert.alert("Success", message);
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
[showSuccessAlert]
|
|
1335
|
+
);
|
|
1336
|
+
const fetchCustomer = (0, import_react6.useCallback)(
|
|
1337
|
+
async (id) => {
|
|
1338
|
+
if (!validateStripeId(id, "customer")) {
|
|
1339
|
+
handleError("Invalid customer ID");
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
try {
|
|
1343
|
+
const response = await fetch(`/api/payments/customer/${id}`);
|
|
1344
|
+
if (!response.ok) {
|
|
1345
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1346
|
+
throw new Error(errorData.error || ERROR_MESSAGES.CUSTOMER_NOT_FOUND);
|
|
1347
|
+
}
|
|
1348
|
+
const data = await response.json();
|
|
1349
|
+
return data;
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
const errorMessage = err instanceof Error ? err.message : ERROR_MESSAGES.CUSTOMER_NOT_FOUND;
|
|
1352
|
+
handleError(errorMessage);
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
[handleError]
|
|
1357
|
+
);
|
|
1358
|
+
const refetch = (0, import_react6.useCallback)(async () => {
|
|
1359
|
+
if (!customerId) {
|
|
1360
|
+
handleError("Customer ID is required");
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
setLoading(true);
|
|
1364
|
+
setError(null);
|
|
1365
|
+
try {
|
|
1366
|
+
const customerData = await fetchCustomer(customerId);
|
|
1367
|
+
setCustomer(customerData);
|
|
1368
|
+
} finally {
|
|
1369
|
+
setLoading(false);
|
|
1370
|
+
}
|
|
1371
|
+
}, [customerId, fetchCustomer, handleError]);
|
|
1372
|
+
const createCustomer2 = (0, import_react6.useCallback)(
|
|
1373
|
+
async (params) => {
|
|
1374
|
+
if (!validateEmail(params.email)) {
|
|
1375
|
+
handleError("Invalid email address");
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
setLoading(true);
|
|
1379
|
+
setError(null);
|
|
1380
|
+
try {
|
|
1381
|
+
const response = await fetch("/api/payments/customer", {
|
|
1382
|
+
method: "POST",
|
|
1383
|
+
headers: { "Content-Type": "application/json" },
|
|
1384
|
+
body: JSON.stringify(params)
|
|
1385
|
+
});
|
|
1386
|
+
if (!response.ok) {
|
|
1387
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1388
|
+
throw new Error(errorData.error || "Failed to create customer");
|
|
1389
|
+
}
|
|
1390
|
+
const customerData = await response.json();
|
|
1391
|
+
setCustomer(customerData);
|
|
1392
|
+
handleSuccess("Customer created successfully!");
|
|
1393
|
+
return customerData;
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to create customer";
|
|
1396
|
+
handleError(errorMessage);
|
|
1397
|
+
return null;
|
|
1398
|
+
} finally {
|
|
1399
|
+
setLoading(false);
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
[handleError, handleSuccess]
|
|
1403
|
+
);
|
|
1404
|
+
const updateCustomer = (0, import_react6.useCallback)(
|
|
1405
|
+
async (params) => {
|
|
1406
|
+
if (!customerId) {
|
|
1407
|
+
handleError("Customer ID is required");
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1410
|
+
if (params.email && !validateEmail(params.email)) {
|
|
1411
|
+
handleError("Invalid email address");
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
setLoading(true);
|
|
1415
|
+
setError(null);
|
|
1416
|
+
try {
|
|
1417
|
+
const response = await fetch(`/api/payments/customer/${customerId}`, {
|
|
1418
|
+
method: "PUT",
|
|
1419
|
+
headers: { "Content-Type": "application/json" },
|
|
1420
|
+
body: JSON.stringify(params)
|
|
1421
|
+
});
|
|
1422
|
+
if (!response.ok) {
|
|
1423
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1424
|
+
throw new Error(errorData.error || "Failed to update customer");
|
|
1425
|
+
}
|
|
1426
|
+
await refetch();
|
|
1427
|
+
handleSuccess("Customer updated successfully!");
|
|
1428
|
+
return true;
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to update customer";
|
|
1431
|
+
handleError(errorMessage);
|
|
1432
|
+
return false;
|
|
1433
|
+
} finally {
|
|
1434
|
+
setLoading(false);
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
[customerId, refetch, handleError, handleSuccess]
|
|
1438
|
+
);
|
|
1439
|
+
const deleteCustomer = (0, import_react6.useCallback)(async () => {
|
|
1440
|
+
if (!customerId) {
|
|
1441
|
+
handleError("Customer ID is required");
|
|
1442
|
+
return false;
|
|
1443
|
+
}
|
|
1444
|
+
return new Promise((resolve) => {
|
|
1445
|
+
import_react_native7.Alert.alert(
|
|
1446
|
+
"Delete Customer",
|
|
1447
|
+
"Are you sure you want to delete this customer? This action cannot be undone.",
|
|
1448
|
+
[
|
|
1449
|
+
{
|
|
1450
|
+
text: "Cancel",
|
|
1451
|
+
style: "cancel",
|
|
1452
|
+
onPress: () => resolve(false)
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
text: "Delete",
|
|
1456
|
+
style: "destructive",
|
|
1457
|
+
onPress: async () => {
|
|
1458
|
+
setLoading(true);
|
|
1459
|
+
setError(null);
|
|
1460
|
+
try {
|
|
1461
|
+
const response = await fetch(
|
|
1462
|
+
`/api/payments/customer/${customerId}`,
|
|
1463
|
+
{
|
|
1464
|
+
method: "DELETE"
|
|
1465
|
+
}
|
|
1466
|
+
);
|
|
1467
|
+
if (!response.ok) {
|
|
1468
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
errorData.error || "Failed to delete customer"
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
setCustomer(null);
|
|
1474
|
+
setPaymentMethods([]);
|
|
1475
|
+
handleSuccess("Customer deleted successfully!");
|
|
1476
|
+
resolve(true);
|
|
1477
|
+
} catch (err) {
|
|
1478
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to delete customer";
|
|
1479
|
+
handleError(errorMessage);
|
|
1480
|
+
resolve(false);
|
|
1481
|
+
} finally {
|
|
1482
|
+
setLoading(false);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
]
|
|
1487
|
+
);
|
|
1488
|
+
});
|
|
1489
|
+
}, [customerId, handleError, handleSuccess]);
|
|
1490
|
+
const fetchPaymentMethods = (0, import_react6.useCallback)(async () => {
|
|
1491
|
+
if (!customerId) {
|
|
1492
|
+
handleError("Customer ID is required");
|
|
1493
|
+
return [];
|
|
1494
|
+
}
|
|
1495
|
+
try {
|
|
1496
|
+
const response = await fetch(
|
|
1497
|
+
`/api/payments/customer/${customerId}/payment-methods`
|
|
1498
|
+
);
|
|
1499
|
+
if (!response.ok) {
|
|
1500
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1501
|
+
throw new Error(errorData.error || "Failed to fetch payment methods");
|
|
1502
|
+
}
|
|
1503
|
+
const data = await response.json();
|
|
1504
|
+
const methods = data.paymentMethods || [];
|
|
1505
|
+
setPaymentMethods(methods);
|
|
1506
|
+
return methods;
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch payment methods";
|
|
1509
|
+
handleError(errorMessage);
|
|
1510
|
+
return [];
|
|
1511
|
+
}
|
|
1512
|
+
}, [customerId, handleError]);
|
|
1513
|
+
const addPaymentMethod = (0, import_react6.useCallback)(
|
|
1514
|
+
async (paymentMethodId) => {
|
|
1515
|
+
if (!customerId) {
|
|
1516
|
+
handleError("Customer ID is required");
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
const response = await fetch(
|
|
1521
|
+
`/api/payments/customer/${customerId}/payment-methods`,
|
|
1522
|
+
{
|
|
1523
|
+
method: "POST",
|
|
1524
|
+
headers: { "Content-Type": "application/json" },
|
|
1525
|
+
body: JSON.stringify({ paymentMethodId })
|
|
1526
|
+
}
|
|
1527
|
+
);
|
|
1528
|
+
if (!response.ok) {
|
|
1529
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1530
|
+
throw new Error(errorData.error || "Failed to add payment method");
|
|
1531
|
+
}
|
|
1532
|
+
await fetchPaymentMethods();
|
|
1533
|
+
handleSuccess("Payment method added successfully!");
|
|
1534
|
+
return true;
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to add payment method";
|
|
1537
|
+
handleError(errorMessage);
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
},
|
|
1541
|
+
[customerId, fetchPaymentMethods, handleError, handleSuccess]
|
|
1542
|
+
);
|
|
1543
|
+
const removePaymentMethod = (0, import_react6.useCallback)(
|
|
1544
|
+
async (paymentMethodId) => {
|
|
1545
|
+
if (!customerId) {
|
|
1546
|
+
handleError("Customer ID is required");
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
return new Promise((resolve) => {
|
|
1550
|
+
import_react_native7.Alert.alert(
|
|
1551
|
+
"Remove Payment Method",
|
|
1552
|
+
"Are you sure you want to remove this payment method?",
|
|
1553
|
+
[
|
|
1554
|
+
{
|
|
1555
|
+
text: "Cancel",
|
|
1556
|
+
style: "cancel",
|
|
1557
|
+
onPress: () => resolve(false)
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
text: "Remove",
|
|
1561
|
+
style: "destructive",
|
|
1562
|
+
onPress: async () => {
|
|
1563
|
+
try {
|
|
1564
|
+
const response = await fetch(
|
|
1565
|
+
`/api/payments/customer/${customerId}/payment-methods/${paymentMethodId}`,
|
|
1566
|
+
{
|
|
1567
|
+
method: "DELETE"
|
|
1568
|
+
}
|
|
1569
|
+
);
|
|
1570
|
+
if (!response.ok) {
|
|
1571
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
errorData.error || "Failed to remove payment method"
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
await fetchPaymentMethods();
|
|
1577
|
+
handleSuccess("Payment method removed successfully!");
|
|
1578
|
+
resolve(true);
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to remove payment method";
|
|
1581
|
+
handleError(errorMessage);
|
|
1582
|
+
resolve(false);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
]
|
|
1587
|
+
);
|
|
1588
|
+
});
|
|
1589
|
+
},
|
|
1590
|
+
[customerId, fetchPaymentMethods, handleError, handleSuccess]
|
|
1591
|
+
);
|
|
1592
|
+
const setDefaultPaymentMethod = (0, import_react6.useCallback)(
|
|
1593
|
+
async (paymentMethodId) => {
|
|
1594
|
+
if (!customerId) {
|
|
1595
|
+
handleError("Customer ID is required");
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
try {
|
|
1599
|
+
const response = await fetch(
|
|
1600
|
+
`/api/payments/customer/${customerId}/default-payment-method`,
|
|
1601
|
+
{
|
|
1602
|
+
method: "PUT",
|
|
1603
|
+
headers: { "Content-Type": "application/json" },
|
|
1604
|
+
body: JSON.stringify({ paymentMethodId })
|
|
1605
|
+
}
|
|
1606
|
+
);
|
|
1607
|
+
if (!response.ok) {
|
|
1608
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1609
|
+
throw new Error(
|
|
1610
|
+
errorData.error || "Failed to set default payment method"
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
await Promise.all([refetch(), fetchPaymentMethods()]);
|
|
1614
|
+
handleSuccess("Default payment method updated successfully!");
|
|
1615
|
+
return true;
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to set default payment method";
|
|
1618
|
+
handleError(errorMessage);
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
},
|
|
1622
|
+
[customerId, refetch, fetchPaymentMethods, handleError, handleSuccess]
|
|
1623
|
+
);
|
|
1624
|
+
(0, import_react6.useEffect)(() => {
|
|
1625
|
+
if (autoFetch && customerId) {
|
|
1626
|
+
refetch();
|
|
1627
|
+
fetchPaymentMethods();
|
|
1628
|
+
}
|
|
1629
|
+
}, [autoFetch, customerId, refetch, fetchPaymentMethods]);
|
|
1630
|
+
return {
|
|
1631
|
+
customer,
|
|
1632
|
+
paymentMethods,
|
|
1633
|
+
loading,
|
|
1634
|
+
error,
|
|
1635
|
+
refetch,
|
|
1636
|
+
createCustomer: createCustomer2,
|
|
1637
|
+
updateCustomer,
|
|
1638
|
+
deleteCustomer,
|
|
1639
|
+
fetchPaymentMethods,
|
|
1640
|
+
addPaymentMethod,
|
|
1641
|
+
removePaymentMethod,
|
|
1642
|
+
setDefaultPaymentMethod
|
|
1643
|
+
};
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
// src/native/utils/stripe-native.ts
|
|
1647
|
+
var createSubscriptionSetup = async (params) => {
|
|
1648
|
+
try {
|
|
1649
|
+
const response = await fetch("/api/payments/create-subscription-setup", {
|
|
1650
|
+
method: "POST",
|
|
1651
|
+
headers: { "Content-Type": "application/json" },
|
|
1652
|
+
body: JSON.stringify(params)
|
|
1653
|
+
});
|
|
1654
|
+
if (!response.ok) {
|
|
1655
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1656
|
+
return {
|
|
1657
|
+
error: errorData.error || "Failed to create subscription setup"
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
const data = await response.json();
|
|
1661
|
+
return {
|
|
1662
|
+
setupIntentClientSecret: data.setupIntentClientSecret,
|
|
1663
|
+
customerId: data.customerId,
|
|
1664
|
+
customerEphemeralKeySecret: data.customerEphemeralKeySecret
|
|
1665
|
+
};
|
|
1666
|
+
} catch (error) {
|
|
1667
|
+
return {
|
|
1668
|
+
error: error instanceof Error ? error.message : "Failed to create subscription setup"
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
var createPaymentIntent = async (params) => {
|
|
1673
|
+
try {
|
|
1674
|
+
const response = await fetch("/api/payments/create-payment-intent", {
|
|
1675
|
+
method: "POST",
|
|
1676
|
+
headers: { "Content-Type": "application/json" },
|
|
1677
|
+
body: JSON.stringify(params)
|
|
1678
|
+
});
|
|
1679
|
+
if (!response.ok) {
|
|
1680
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1681
|
+
return { error: errorData.error || "Failed to create payment intent" };
|
|
1682
|
+
}
|
|
1683
|
+
const data = await response.json();
|
|
1684
|
+
return { clientSecret: data.client_secret };
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
return {
|
|
1687
|
+
error: error instanceof Error ? error.message : "Failed to create payment intent"
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
var validatePaymentsConfig = (config) => {
|
|
1692
|
+
const errors = [];
|
|
1693
|
+
if (!config.publishableKey) {
|
|
1694
|
+
errors.push("publishableKey is required");
|
|
1695
|
+
}
|
|
1696
|
+
if (!config.provider) {
|
|
1697
|
+
errors.push("provider is required");
|
|
1698
|
+
}
|
|
1699
|
+
if (config.provider !== "stripe") {
|
|
1700
|
+
errors.push("Only stripe provider is currently supported");
|
|
1701
|
+
}
|
|
1702
|
+
if (!config.environment) {
|
|
1703
|
+
errors.push("environment is required");
|
|
1704
|
+
}
|
|
1705
|
+
if (config.environment && !["development", "production"].includes(config.environment)) {
|
|
1706
|
+
errors.push('environment must be either "development" or "production"');
|
|
1707
|
+
}
|
|
1708
|
+
if (config.publishableKey && !config.publishableKey.startsWith("pk_")) {
|
|
1709
|
+
errors.push('publishableKey must start with "pk_"');
|
|
1710
|
+
}
|
|
1711
|
+
return {
|
|
1712
|
+
isValid: errors.length === 0,
|
|
1713
|
+
errors
|
|
1714
|
+
};
|
|
1715
|
+
};
|
|
1716
|
+
var formatStripeError = (error) => {
|
|
1717
|
+
if (typeof error === "string") {
|
|
1718
|
+
return error;
|
|
1719
|
+
}
|
|
1720
|
+
if (error?.message) {
|
|
1721
|
+
return error.message;
|
|
1722
|
+
}
|
|
1723
|
+
if (error?.code) {
|
|
1724
|
+
const errorMessages = {
|
|
1725
|
+
Canceled: "Payment was cancelled",
|
|
1726
|
+
Failed: "Payment failed",
|
|
1727
|
+
card_declined: "Your card was declined.",
|
|
1728
|
+
expired_card: "Your card has expired.",
|
|
1729
|
+
incorrect_cvc: "Your card's security code is incorrect.",
|
|
1730
|
+
processing_error: "An error occurred while processing your card.",
|
|
1731
|
+
incorrect_number: "Your card number is incorrect."
|
|
1732
|
+
};
|
|
1733
|
+
return errorMessages[error.code] || `Payment failed: ${error.code}`;
|
|
1734
|
+
}
|
|
1735
|
+
return "An unexpected error occurred.";
|
|
1736
|
+
};
|
|
1737
|
+
var createCustomer = async (params) => {
|
|
1738
|
+
try {
|
|
1739
|
+
const response = await fetch("/api/payments/customer", {
|
|
1740
|
+
method: "POST",
|
|
1741
|
+
headers: { "Content-Type": "application/json" },
|
|
1742
|
+
body: JSON.stringify(params)
|
|
1743
|
+
});
|
|
1744
|
+
if (!response.ok) {
|
|
1745
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1746
|
+
return { error: errorData.error || "Failed to create customer" };
|
|
1747
|
+
}
|
|
1748
|
+
const customer = await response.json();
|
|
1749
|
+
return { customer };
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
return {
|
|
1752
|
+
error: error instanceof Error ? error.message : "Failed to create customer"
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
var fetchSubscription = async (subscriptionId) => {
|
|
1757
|
+
try {
|
|
1758
|
+
const response = await fetch(
|
|
1759
|
+
`/api/payments/subscription/${subscriptionId}`
|
|
1760
|
+
);
|
|
1761
|
+
if (!response.ok) {
|
|
1762
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1763
|
+
return {
|
|
1764
|
+
error: errorData.error || ERROR_MESSAGES.SUBSCRIPTION_NOT_FOUND
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
const subscription = await response.json();
|
|
1768
|
+
return { subscription };
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
return {
|
|
1771
|
+
error: error instanceof Error ? error.message : ERROR_MESSAGES.SUBSCRIPTION_NOT_FOUND
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
var fetchCustomerSubscriptions = async (customerId) => {
|
|
1776
|
+
try {
|
|
1777
|
+
const response = await fetch(
|
|
1778
|
+
`/api/payments/customer/${customerId}/subscriptions`
|
|
1779
|
+
);
|
|
1780
|
+
if (!response.ok) {
|
|
1781
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1782
|
+
return { error: errorData.error || ERROR_MESSAGES.CUSTOMER_NOT_FOUND };
|
|
1783
|
+
}
|
|
1784
|
+
const data = await response.json();
|
|
1785
|
+
return { subscriptions: data.subscriptions || [] };
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
return {
|
|
1788
|
+
error: error instanceof Error ? error.message : ERROR_MESSAGES.CUSTOMER_NOT_FOUND
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
var cancelSubscription = async (subscriptionId) => {
|
|
1793
|
+
try {
|
|
1794
|
+
const response = await fetch(
|
|
1795
|
+
`/api/payments/subscription/${subscriptionId}/cancel`,
|
|
1796
|
+
{
|
|
1797
|
+
method: "POST",
|
|
1798
|
+
headers: { "Content-Type": "application/json" }
|
|
1799
|
+
}
|
|
1800
|
+
);
|
|
1801
|
+
if (!response.ok) {
|
|
1802
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
1803
|
+
return {
|
|
1804
|
+
success: false,
|
|
1805
|
+
error: errorData.error || "Failed to cancel subscription"
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
return { success: true };
|
|
1809
|
+
} catch (error) {
|
|
1810
|
+
return {
|
|
1811
|
+
success: false,
|
|
1812
|
+
error: error instanceof Error ? error.message : "Failed to cancel subscription"
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1817
|
+
0 && (module.exports = {
|
|
1818
|
+
CheckoutButton,
|
|
1819
|
+
PaymentForm,
|
|
1820
|
+
PricingTable,
|
|
1821
|
+
StripeProvider,
|
|
1822
|
+
cancelSubscription,
|
|
1823
|
+
createCustomer,
|
|
1824
|
+
createPaymentIntent,
|
|
1825
|
+
createSubscriptionSetup,
|
|
1826
|
+
fetchCustomerSubscriptions,
|
|
1827
|
+
fetchSubscription,
|
|
1828
|
+
formatStripeError,
|
|
1829
|
+
useCheckout,
|
|
1830
|
+
useCustomer,
|
|
1831
|
+
useSubscription,
|
|
1832
|
+
validatePaymentsConfig
|
|
1833
|
+
});
|