@solvapay/react 1.0.0-preview.4 → 1.0.0-preview.5

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.cjs CHANGED
@@ -21,184 +21,265 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  PaymentForm: () => PaymentForm,
24
- SolvaPayProvider: () => SolvaPayProvider
24
+ PlanBadge: () => PlanBadge,
25
+ SolvaPayProvider: () => SolvaPayProvider,
26
+ SubscriptionGate: () => SubscriptionGate,
27
+ useCheckout: () => useCheckout,
28
+ useSolvaPay: () => useSolvaPay,
29
+ useSubscription: () => useSubscription
25
30
  });
26
31
  module.exports = __toCommonJS(index_exports);
27
32
 
28
33
  // src/SolvaPayProvider.tsx
29
34
  var import_react = require("react");
30
- var import_react_stripe_js = require("@stripe/react-stripe-js");
31
- var import_stripe_js = require("@stripe/stripe-js");
32
35
  var import_jsx_runtime = require("react/jsx-runtime");
36
+ var SolvaPayContext = (0, import_react.createContext)(null);
33
37
  var SolvaPayProvider = ({
34
- children,
35
- amount = 999,
36
- // Default $9.99
37
- currency = "USD",
38
- planRef,
39
- agentRef,
40
- apiBaseUrl = "/api/solvapay",
41
- onPaymentReady
38
+ createPayment,
39
+ checkSubscription,
40
+ customerRef,
41
+ onCustomerRefUpdate,
42
+ children
42
43
  }) => {
43
- const [stripePromise, setStripePromise] = (0, import_react.useState)(null);
44
- const [clientSecret, setClientSecret] = (0, import_react.useState)("");
44
+ if (!createPayment || typeof createPayment !== "function") {
45
+ throw new Error("SolvaPayProvider: createPayment prop is required and must be a function");
46
+ }
47
+ if (!checkSubscription || typeof checkSubscription !== "function") {
48
+ throw new Error("SolvaPayProvider: checkSubscription prop is required and must be a function");
49
+ }
50
+ const [subscriptionData, setSubscriptionData] = (0, import_react.useState)({
51
+ subscriptions: []
52
+ });
45
53
  const [loading, setLoading] = (0, import_react.useState)(false);
46
- const [error, setError] = (0, import_react.useState)(null);
47
- const createPayment = async (paymentAmount) => {
54
+ const [internalCustomerRef, setInternalCustomerRef] = (0, import_react.useState)(customerRef);
55
+ (0, import_react.useEffect)(() => {
56
+ if (customerRef && customerRef !== internalCustomerRef) {
57
+ setInternalCustomerRef(customerRef);
58
+ }
59
+ }, [customerRef, internalCustomerRef]);
60
+ const fetchSubscription = (0, import_react.useCallback)(async () => {
61
+ const currentCustomerRef = internalCustomerRef;
62
+ if (!currentCustomerRef) {
63
+ setSubscriptionData({ subscriptions: [] });
64
+ setLoading(false);
65
+ return;
66
+ }
48
67
  setLoading(true);
49
- setError(null);
50
68
  try {
51
- console.log("Creating SolvaPay payment intent for amount:", paymentAmount);
52
- const response = await fetch(`${apiBaseUrl}/create-payment-intent`, {
53
- method: "POST",
54
- headers: {
55
- "Content-Type": "application/json"
56
- },
57
- body: JSON.stringify({
58
- planRef,
59
- agentRef,
60
- amount: paymentAmount,
61
- currency,
62
- description: "SolvaPay - Plan Purchase"
63
- })
69
+ const data = await checkSubscription(currentCustomerRef);
70
+ setSubscriptionData(data);
71
+ } catch (error) {
72
+ console.error("\u274C [SolvaPayProvider] Failed to fetch subscription:", error);
73
+ setSubscriptionData({
74
+ customerRef: currentCustomerRef,
75
+ subscriptions: []
64
76
  });
65
- if (!response.ok) {
66
- const errorText = await response.text();
67
- throw new Error(`Payment intent creation failed: ${response.status} ${errorText}`);
68
- }
69
- const result = await response.json();
70
- if (!result || !result.clientSecret || !result.publishableKey) {
71
- throw new Error("Invalid response from server: missing required fields");
77
+ } finally {
78
+ setLoading(false);
79
+ }
80
+ }, [internalCustomerRef, checkSubscription]);
81
+ (0, import_react.useEffect)(() => {
82
+ fetchSubscription();
83
+ }, [fetchSubscription]);
84
+ const updateCustomerRef = (0, import_react.useCallback)((newCustomerRef) => {
85
+ const previousRef = internalCustomerRef;
86
+ setInternalCustomerRef(newCustomerRef);
87
+ if (onCustomerRefUpdate) {
88
+ onCustomerRefUpdate(newCustomerRef);
89
+ }
90
+ if (previousRef !== newCustomerRef) {
91
+ }
92
+ }, [onCustomerRefUpdate, internalCustomerRef]);
93
+ const subscription = (0, import_react.useMemo)(() => ({
94
+ loading,
95
+ customerRef: subscriptionData.customerRef,
96
+ email: subscriptionData.email,
97
+ name: subscriptionData.name,
98
+ subscriptions: subscriptionData.subscriptions,
99
+ hasActiveSubscription: subscriptionData.subscriptions.some(
100
+ (sub) => sub.status === "active"
101
+ ),
102
+ hasPlan: (planName) => {
103
+ return subscriptionData.subscriptions.some(
104
+ (sub) => sub.planName.toLowerCase() === planName.toLowerCase() && sub.status === "active"
105
+ );
106
+ }
107
+ }), [loading, subscriptionData]);
108
+ const contextValue = (0, import_react.useMemo)(() => ({
109
+ subscription,
110
+ refetchSubscription: fetchSubscription,
111
+ createPayment,
112
+ customerRef: internalCustomerRef,
113
+ updateCustomerRef
114
+ }), [subscription, fetchSubscription, createPayment, internalCustomerRef, updateCustomerRef]);
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SolvaPayContext.Provider, { value: contextValue, children });
116
+ };
117
+
118
+ // src/PaymentForm.tsx
119
+ var import_react4 = require("react");
120
+ var import_react_stripe_js = require("@stripe/react-stripe-js");
121
+
122
+ // src/hooks/useCheckout.ts
123
+ var import_react3 = require("react");
124
+ var import_stripe_js = require("@stripe/stripe-js");
125
+
126
+ // src/hooks/useSolvaPay.ts
127
+ var import_react2 = require("react");
128
+ function useSolvaPay() {
129
+ const context = (0, import_react2.useContext)(SolvaPayContext);
130
+ if (!context) {
131
+ throw new Error(
132
+ "useSolvaPay must be used within a SolvaPayProvider. Wrap your component tree with <SolvaPayProvider> to use this hook."
133
+ );
134
+ }
135
+ return context;
136
+ }
137
+
138
+ // src/hooks/useCheckout.ts
139
+ function useCheckout(planRef) {
140
+ const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
141
+ const [loading, setLoading] = (0, import_react3.useState)(false);
142
+ const [error, setError] = (0, import_react3.useState)(
143
+ !planRef || typeof planRef !== "string" ? new Error("useCheckout: planRef parameter is required and must be a string") : null
144
+ );
145
+ const [stripePromise, setStripePromise] = (0, import_react3.useState)(null);
146
+ const [clientSecret, setClientSecret] = (0, import_react3.useState)(null);
147
+ const isStartingRef = (0, import_react3.useRef)(false);
148
+ const startCheckout = (0, import_react3.useCallback)(async () => {
149
+ if (isStartingRef.current || loading) {
150
+ return;
151
+ }
152
+ if (!planRef || typeof planRef !== "string") {
153
+ setError(new Error("useCheckout: planRef parameter is required and must be a string"));
154
+ return;
155
+ }
156
+ if (!customerRef) {
157
+ setError(new Error("No customer reference available. Please ensure you are logged in."));
158
+ return;
159
+ }
160
+ isStartingRef.current = true;
161
+ setLoading(true);
162
+ setError(null);
163
+ try {
164
+ const result = await createPayment({ planRef, customerRef });
165
+ if (!result || typeof result !== "object") {
166
+ throw new Error("Invalid payment intent response from server");
72
167
  }
73
- console.log("Payment intent received:", {
74
- hasClientSecret: !!result.clientSecret,
75
- hasPublishableKey: !!result.publishableKey,
76
- hasAccountId: !!result.accountId,
77
- clientSecretLength: result.clientSecret?.length || 0,
78
- publishableKeyLength: result.publishableKey?.length || 0
79
- });
80
- const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
81
- const stripe = await (0, import_stripe_js.loadStripe)(result.publishableKey, stripeOptions);
82
- if (!stripe) {
83
- throw new Error("Failed to initialize Stripe. Please check your configuration.");
168
+ if (!result.clientSecret || typeof result.clientSecret !== "string") {
169
+ throw new Error("Invalid client secret in payment intent response");
84
170
  }
85
- setStripePromise(stripe);
86
- setClientSecret(result.clientSecret);
87
- if (onPaymentReady) {
88
- onPaymentReady(stripe, result.clientSecret);
171
+ if (!result.publishableKey || typeof result.publishableKey !== "string") {
172
+ throw new Error("Invalid publishable key in payment intent response");
89
173
  }
90
- } catch (error2) {
91
- console.error("Failed to create payment:", error2);
92
- let errorMessage = "Payment creation failed";
93
- if (error2 instanceof Error) {
94
- if (error2.message.includes("publishable key")) {
95
- errorMessage = "Server configuration error: Missing Stripe publishable key";
96
- } else if (error2.message.includes("client secret")) {
97
- errorMessage = "Server error: Payment intent creation failed";
98
- } else if (error2.message.includes("Failed to initialize Stripe")) {
99
- errorMessage = "Stripe initialization failed. Please check your configuration.";
174
+ if (result.customerRef && result.customerRef !== customerRef) {
175
+ console.log(`\u{1F504} [useCheckout] Backend returned different customerRef:`, {
176
+ sent: customerRef,
177
+ received: result.customerRef
178
+ });
179
+ if (updateCustomerRef) {
180
+ updateCustomerRef(result.customerRef);
181
+ console.log("\u2705 [useCheckout] Customer reference updated via callback");
100
182
  } else {
101
- errorMessage = error2.message;
183
+ console.warn("\u26A0\uFE0F [useCheckout] No updateCustomerRef callback available!");
102
184
  }
185
+ } else if (result.customerRef === customerRef) {
186
+ console.log("\u2705 [useCheckout] Customer reference unchanged:", customerRef);
103
187
  }
104
- setError(errorMessage);
188
+ const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
189
+ const stripe = (0, import_stripe_js.loadStripe)(result.publishableKey, stripeOptions);
190
+ setStripePromise(stripe);
191
+ setClientSecret(result.clientSecret);
192
+ } catch (err) {
193
+ const error2 = err instanceof Error ? err : new Error("Failed to start checkout");
194
+ setError(error2);
105
195
  } finally {
106
196
  setLoading(false);
197
+ isStartingRef.current = false;
107
198
  }
199
+ }, [planRef, customerRef, createPayment, updateCustomerRef, loading]);
200
+ const reset = (0, import_react3.useCallback)(() => {
201
+ isStartingRef.current = false;
202
+ setLoading(false);
203
+ setError(null);
204
+ setStripePromise(null);
205
+ setClientSecret(null);
206
+ }, []);
207
+ return {
208
+ loading,
209
+ error,
210
+ stripePromise,
211
+ clientSecret,
212
+ startCheckout,
213
+ reset
108
214
  };
109
- (0, import_react.useEffect)(() => {
110
- if (amount && !stripePromise && !loading && !error) {
111
- createPayment(amount);
112
- }
113
- }, [amount]);
114
- if (error) {
115
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
116
- padding: "1rem",
117
- backgroundColor: "#fed7d7",
118
- color: "#742a2a",
119
- borderRadius: "0.375rem",
120
- marginBottom: "1rem"
121
- }, children: [
122
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Payment Setup Error:" }),
123
- " ",
124
- error,
125
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
126
- "button",
127
- {
128
- onClick: () => createPayment(amount),
129
- style: {
130
- marginLeft: "1rem",
131
- padding: "0.5rem 1rem",
132
- backgroundColor: "#742a2a",
133
- color: "white",
134
- border: "none",
135
- borderRadius: "0.25rem",
136
- cursor: "pointer"
137
- },
138
- children: "Retry"
139
- }
140
- )
141
- ] });
142
- }
143
- if (loading) {
144
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
145
- padding: "1rem",
146
- textAlign: "center",
147
- color: "#4a5568"
148
- }, children: "Setting up payment..." });
149
- }
150
- if (!stripePromise || !clientSecret) {
151
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
152
- padding: "1rem",
153
- textAlign: "center",
154
- color: "#4a5568"
155
- }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
156
- "button",
157
- {
158
- onClick: () => createPayment(amount),
159
- style: {
160
- padding: "0.75rem 1.5rem",
161
- backgroundColor: "#3182ce",
162
- color: "white",
163
- border: "none",
164
- borderRadius: "0.375rem",
165
- cursor: "pointer",
166
- fontSize: "1rem"
167
- },
168
- children: "Initialize Payment"
169
- }
170
- ) });
171
- }
172
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_stripe_js.Elements, { stripe: stripePromise, options: { clientSecret }, children });
173
- };
215
+ }
216
+
217
+ // src/hooks/useSubscription.ts
218
+ function useSubscription() {
219
+ const { subscription, refetchSubscription } = useSolvaPay();
220
+ return {
221
+ ...subscription,
222
+ refetch: refetchSubscription
223
+ };
224
+ }
174
225
 
175
226
  // src/PaymentForm.tsx
176
- var import_react2 = require("react");
177
- var import_react_stripe_js2 = require("@stripe/react-stripe-js");
178
227
  var import_jsx_runtime2 = require("react/jsx-runtime");
179
- var PaymentForm = ({
228
+ var Spinner = ({
229
+ className = "",
230
+ size = "md"
231
+ }) => {
232
+ const sizeClasses = {
233
+ sm: "w-4 h-4 border-2",
234
+ md: "w-6 h-6 border-2",
235
+ lg: "w-8 h-8 border-2"
236
+ };
237
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
238
+ "div",
239
+ {
240
+ className: `inline-block border-gray-300 border-t-gray-600 rounded-full animate-spin ${sizeClasses[size]} ${className}`,
241
+ role: "status",
242
+ "aria-busy": "true"
243
+ }
244
+ );
245
+ };
246
+ var StripePaymentFormWrapper = (props) => {
247
+ const stripe = (0, import_react_stripe_js.useStripe)();
248
+ const elements = (0, import_react_stripe_js.useElements)();
249
+ if (!stripe || !elements) {
250
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { size: "md" }) }) });
251
+ }
252
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StripePaymentForm, { ...props });
253
+ };
254
+ var StripePaymentForm = ({
180
255
  onSuccess,
181
256
  onError,
182
257
  returnUrl,
183
258
  submitButtonText = "Pay Now",
184
- className
259
+ buttonClassName
185
260
  }) => {
186
- const stripe = (0, import_react_stripe_js2.useStripe)();
187
- const elements = (0, import_react_stripe_js2.useElements)();
188
- const [isProcessing, setIsProcessing] = (0, import_react2.useState)(false);
189
- const [message, setMessage] = (0, import_react2.useState)(null);
261
+ const stripe = (0, import_react_stripe_js.useStripe)();
262
+ const elements = (0, import_react_stripe_js.useElements)();
263
+ const [isProcessing, setIsProcessing] = (0, import_react4.useState)(false);
264
+ const [message, setMessage] = (0, import_react4.useState)(null);
190
265
  const handleSubmit = async (event) => {
191
266
  event.preventDefault();
192
267
  if (!stripe || !elements) {
268
+ const errorMessage = "Stripe is not available. Please refresh the page.";
269
+ setMessage(errorMessage);
270
+ if (onError) {
271
+ onError(new Error(errorMessage));
272
+ }
193
273
  return;
194
274
  }
195
275
  setIsProcessing(true);
196
276
  setMessage(null);
197
277
  try {
278
+ const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
198
279
  const { error, paymentIntent } = await stripe.confirmPayment({
199
280
  elements,
200
281
  confirmParams: {
201
- return_url: returnUrl || `${window.location.origin}/checkout/complete`
282
+ return_url: finalReturnUrl
202
283
  },
203
284
  redirect: "if_required"
204
285
  // Only redirect if required by payment method
@@ -214,8 +295,8 @@ var PaymentForm = ({
214
295
  if (onSuccess) {
215
296
  onSuccess(paymentIntent);
216
297
  }
217
- } else {
218
- setMessage(`Payment status: ${paymentIntent?.status || "processing"}`);
298
+ } else if (paymentIntent) {
299
+ setMessage(`Payment status: ${paymentIntent.status || "processing"}`);
219
300
  }
220
301
  } catch (err) {
221
302
  const error = err instanceof Error ? err : new Error("Unknown error occurred");
@@ -227,46 +308,226 @@ var PaymentForm = ({
227
308
  setIsProcessing(false);
228
309
  }
229
310
  };
230
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className, children: [
231
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_stripe_js2.PaymentElement, {}),
232
- message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
233
- "div",
234
- {
235
- style: {
236
- marginTop: "1rem",
237
- padding: "0.75rem",
238
- borderRadius: "0.375rem",
239
- backgroundColor: message.includes("successful") ? "#d1fae5" : "#fee2e2",
240
- color: message.includes("successful") ? "#065f46" : "#7f1d1d"
311
+ const isSuccess = message?.includes("successful") ?? false;
312
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
313
+ "form",
314
+ {
315
+ onSubmit: handleSubmit,
316
+ className: "space-y-6",
317
+ noValidate: true,
318
+ children: [
319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_stripe_js.PaymentElement, {}),
320
+ message && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
321
+ "div",
322
+ {
323
+ className: `mt-6 p-4 rounded-lg ${isSuccess ? "bg-green-50 border border-green-400 text-green-700" : "bg-red-50 border border-red-400 text-red-700"}`,
324
+ role: isSuccess ? "status" : "alert",
325
+ "aria-live": isSuccess ? "polite" : "assertive",
326
+ "aria-atomic": "true",
327
+ children: message
328
+ }
329
+ ),
330
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
331
+ "button",
332
+ {
333
+ type: "submit",
334
+ disabled: !stripe || isProcessing,
335
+ className: buttonClassName,
336
+ "aria-busy": isProcessing,
337
+ "aria-disabled": !stripe || isProcessing,
338
+ children: isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex items-center justify-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { size: "sm" }) }) : submitButtonText
339
+ }
340
+ )
341
+ ]
342
+ }
343
+ );
344
+ };
345
+ var PaymentForm = ({
346
+ planRef,
347
+ onSuccess,
348
+ onError,
349
+ returnUrl,
350
+ submitButtonText = "Pay Now",
351
+ className,
352
+ buttonClassName,
353
+ initialButtonText,
354
+ cancelButtonText = "Cancel"
355
+ }) => {
356
+ const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
357
+ const { loading, error, stripePromise, clientSecret, startCheckout, reset } = useCheckout(validPlanRef);
358
+ const { refetch } = useSubscription();
359
+ const [showPaymentForm, setShowPaymentForm] = (0, import_react4.useState)(false);
360
+ const [hasStartedCheckout, setHasStartedCheckout] = (0, import_react4.useState)(false);
361
+ const initializationKey = (0, import_react4.useRef)("");
362
+ const planRefError = !planRef || typeof planRef !== "string" ? new Error("PaymentForm: planRef is required") : null;
363
+ if (planRefError) {
364
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: planRefError.message }) });
365
+ }
366
+ (0, import_react4.useEffect)(() => {
367
+ const key = `${planRef}-${initialButtonText}`;
368
+ if (initializationKey.current === key) {
369
+ return;
370
+ }
371
+ setShowPaymentForm(false);
372
+ setHasStartedCheckout(false);
373
+ reset();
374
+ initializationKey.current = key;
375
+ if (!initialButtonText && planRef && !loading && !error) {
376
+ requestAnimationFrame(() => {
377
+ setHasStartedCheckout(true);
378
+ startCheckout().then(() => {
379
+ setShowPaymentForm(true);
380
+ }).catch(() => {
381
+ });
382
+ });
383
+ }
384
+ }, [planRef, initialButtonText, loading, error, reset]);
385
+ const handleStartCheckout = async () => {
386
+ setHasStartedCheckout(true);
387
+ await startCheckout();
388
+ setShowPaymentForm(true);
389
+ };
390
+ const handleSuccess = async (paymentIntent) => {
391
+ setShowPaymentForm(false);
392
+ reset();
393
+ await refetch();
394
+ if (onSuccess) {
395
+ onSuccess(paymentIntent);
396
+ }
397
+ };
398
+ const handleCancel = () => {
399
+ setShowPaymentForm(false);
400
+ reset();
401
+ };
402
+ const handleError = (err) => {
403
+ if (onError) {
404
+ onError(err);
405
+ }
406
+ };
407
+ const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
408
+ const shouldShowForm = (!initialButtonText || showPaymentForm) && stripePromise && clientSecret;
409
+ if (shouldShowForm) {
410
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { size: "md" }) }) }), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
412
+ import_react_stripe_js.Elements,
413
+ {
414
+ stripe: stripePromise,
415
+ options: { clientSecret },
416
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
417
+ StripePaymentFormWrapper,
418
+ {
419
+ onSuccess: handleSuccess,
420
+ onError: handleError,
421
+ returnUrl: finalReturnUrl,
422
+ submitButtonText,
423
+ buttonClassName
424
+ }
425
+ )
241
426
  },
242
- children: message
243
- }
244
- ),
245
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
427
+ clientSecret
428
+ ) }),
429
+ cancelButtonText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
430
+ "button",
431
+ {
432
+ onClick: handleCancel,
433
+ type: "button",
434
+ className: "mt-6 w-full px-4 py-2 text-xs text-slate-600 bg-transparent rounded-full hover:text-slate-900 hover:bg-slate-50 font-medium transition-all duration-200",
435
+ children: cancelButtonText
436
+ }
437
+ )
438
+ ] });
439
+ }
440
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
441
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
442
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { size: "md" }) }),
443
+ initialButtonText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
444
+ "button",
445
+ {
446
+ disabled: true,
447
+ className: buttonClassName,
448
+ "aria-busy": "true",
449
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex items-center justify-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, { size: "sm" }) })
450
+ }
451
+ )
452
+ ] }),
453
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
454
+ initialButtonText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
455
+ "button",
456
+ {
457
+ onClick: () => {
458
+ reset();
459
+ handleStartCheckout();
460
+ },
461
+ className: buttonClassName,
462
+ children: initialButtonText
463
+ }
464
+ ),
465
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: error.message })
466
+ ] }),
467
+ !loading && !error && initialButtonText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
246
468
  "button",
247
469
  {
248
- type: "submit",
249
- disabled: !stripe || isProcessing,
250
- style: {
251
- marginTop: "1.5rem",
252
- width: "100%",
253
- padding: "0.75rem 1.5rem",
254
- backgroundColor: isProcessing || !stripe ? "#9ca3af" : "#3b82f6",
255
- color: "white",
256
- border: "none",
257
- borderRadius: "0.375rem",
258
- fontSize: "1rem",
259
- fontWeight: "500",
260
- cursor: isProcessing || !stripe ? "not-allowed" : "pointer",
261
- transition: "background-color 0.2s"
262
- },
263
- children: isProcessing ? "Processing..." : submitButtonText
470
+ onClick: handleStartCheckout,
471
+ className: buttonClassName,
472
+ children: initialButtonText
264
473
  }
265
474
  )
266
475
  ] });
267
476
  };
477
+
478
+ // src/components/PlanBadge.tsx
479
+ var import_jsx_runtime3 = require("react/jsx-runtime");
480
+ var PlanBadge = ({
481
+ children,
482
+ as: Component = "div",
483
+ className
484
+ }) => {
485
+ const { subscriptions, loading } = useSubscription();
486
+ if (children) {
487
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: children({ subscriptions, loading }) });
488
+ }
489
+ const computedClassName = typeof className === "function" ? className({ subscriptions }) : className;
490
+ const activeSubs = subscriptions.filter((sub) => sub.status === "active");
491
+ const latestSub = activeSubs.length > 0 ? activeSubs.reduce((latest, current) => {
492
+ return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
493
+ }) : null;
494
+ const displayText = loading ? "Loading..." : latestSub ? latestSub.planName : "Free";
495
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
496
+ Component,
497
+ {
498
+ className: computedClassName,
499
+ "data-loading": loading,
500
+ "data-has-subscription": activeSubs.length > 0,
501
+ role: "status",
502
+ "aria-live": "polite",
503
+ "aria-busy": loading,
504
+ "aria-label": loading ? "Loading subscription status" : `Current plan: ${displayText}`,
505
+ children: displayText
506
+ }
507
+ );
508
+ };
509
+
510
+ // src/components/SubscriptionGate.tsx
511
+ var import_jsx_runtime4 = require("react/jsx-runtime");
512
+ var SubscriptionGate = ({
513
+ requirePlan,
514
+ children
515
+ }) => {
516
+ const { subscriptions, loading, hasPlan } = useSubscription();
517
+ const hasAccess = requirePlan ? hasPlan(requirePlan) : subscriptions.some((sub) => sub.status === "active");
518
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: children({
519
+ hasAccess,
520
+ subscriptions,
521
+ loading
522
+ }) });
523
+ };
268
524
  // Annotate the CommonJS export names for ESM import in node:
269
525
  0 && (module.exports = {
270
526
  PaymentForm,
271
- SolvaPayProvider
527
+ PlanBadge,
528
+ SolvaPayProvider,
529
+ SubscriptionGate,
530
+ useCheckout,
531
+ useSolvaPay,
532
+ useSubscription
272
533
  });