@solvapay/react 1.0.0-preview.3 → 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/README.md +148 -15
- package/dist/index.cjs +432 -171
- package/dist/index.d.cts +216 -60
- package/dist/index.d.ts +216 -60
- package/dist/index.js +427 -171
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
agentRef,
|
|
40
|
-
apiBaseUrl = "/api/solvapay",
|
|
41
|
-
onPaymentReady
|
|
38
|
+
createPayment,
|
|
39
|
+
checkSubscription,
|
|
40
|
+
customerRef,
|
|
41
|
+
onCustomerRefUpdate,
|
|
42
|
+
children
|
|
42
43
|
}) => {
|
|
43
|
-
|
|
44
|
-
|
|
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 [
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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
|
-
|
|
259
|
+
buttonClassName
|
|
185
260
|
}) => {
|
|
186
|
-
const stripe = (0,
|
|
187
|
-
const elements = (0,
|
|
188
|
-
const [isProcessing, setIsProcessing] = (0,
|
|
189
|
-
const [message, setMessage] = (0,
|
|
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:
|
|
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
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
527
|
+
PlanBadge,
|
|
528
|
+
SolvaPayProvider,
|
|
529
|
+
SubscriptionGate,
|
|
530
|
+
useCheckout,
|
|
531
|
+
useSolvaPay,
|
|
532
|
+
useSubscription
|
|
272
533
|
});
|