@solvapay/react 1.0.0-preview.8 → 1.0.1-preview.1

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
@@ -23,236 +23,473 @@ __export(index_exports, {
23
23
  PaymentForm: () => PaymentForm,
24
24
  PlanBadge: () => PlanBadge,
25
25
  PlanSelector: () => PlanSelector,
26
+ PricingSelector: () => PricingSelector,
27
+ ProductBadge: () => ProductBadge,
28
+ PurchaseGate: () => PurchaseGate,
26
29
  SolvaPayProvider: () => SolvaPayProvider,
27
30
  Spinner: () => Spinner,
28
31
  StripePaymentFormWrapper: () => StripePaymentFormWrapper,
29
- SubscriptionGate: () => SubscriptionGate,
30
- filterSubscriptions: () => filterSubscriptions,
31
- getActiveSubscriptions: () => getActiveSubscriptions,
32
- getCancelledSubscriptionsWithEndDate: () => getCancelledSubscriptionsWithEndDate,
33
- getMostRecentSubscription: () => getMostRecentSubscription,
34
- getPrimarySubscription: () => getPrimarySubscription,
35
- hasActivePaidSubscription: () => hasActivePaidSubscription,
32
+ defaultAuthAdapter: () => defaultAuthAdapter,
33
+ filterPurchases: () => filterPurchases,
34
+ getActivePurchases: () => getActivePurchases,
35
+ getCancelledPurchasesWithEndDate: () => getCancelledPurchasesWithEndDate,
36
+ getMostRecentPurchase: () => getMostRecentPurchase,
37
+ getPrimaryPurchase: () => getPrimaryPurchase,
38
+ isPaidPurchase: () => isPaidPurchase,
36
39
  useCheckout: () => useCheckout,
40
+ useCustomer: () => useCustomer,
37
41
  usePlans: () => usePlans,
38
- useSolvaPay: () => useSolvaPay,
39
- useSubscription: () => useSubscription,
40
- useSubscriptionHelpers: () => useSubscriptionHelpers
42
+ usePurchase: () => usePurchase,
43
+ usePurchaseStatus: () => usePurchaseStatus,
44
+ useSolvaPay: () => useSolvaPay
41
45
  });
42
46
  module.exports = __toCommonJS(index_exports);
43
47
 
44
48
  // src/SolvaPayProvider.tsx
45
49
  var import_react = require("react");
46
50
 
47
- // src/utils/subscriptions.ts
48
- function filterSubscriptions(subscriptions) {
49
- const now = /* @__PURE__ */ new Date();
50
- const filtered = subscriptions.filter((sub) => {
51
- const subAny = sub;
52
- const isCancelled = sub.status === "cancelled" || subAny.cancelledAt;
53
- if (!isCancelled) {
54
- return true;
55
- }
56
- if (isCancelled && subAny.endDate) {
57
- const endDate = new Date(subAny.endDate);
58
- const isFuture = endDate > now;
59
- return isFuture;
60
- }
61
- return false;
62
- });
63
- return filtered;
51
+ // src/utils/purchases.ts
52
+ function filterPurchases(purchases) {
53
+ return purchases.filter((purchase) => purchase.status === "active");
64
54
  }
65
- function getActiveSubscriptions(subscriptions) {
66
- return subscriptions.filter((sub) => {
67
- const subAny = sub;
68
- return sub.status === "active" && !subAny.cancelledAt;
69
- });
55
+ function getActivePurchases(purchases) {
56
+ return purchases.filter((purchase) => purchase.status === "active");
70
57
  }
71
- function getCancelledSubscriptionsWithEndDate(subscriptions) {
72
- return subscriptions.filter((sub) => {
73
- const subAny = sub;
74
- const isCancelled = sub.status === "cancelled" || subAny.cancelledAt;
75
- return isCancelled && subAny.endDate && new Date(subAny.endDate) > /* @__PURE__ */ new Date();
58
+ function getCancelledPurchasesWithEndDate(purchases) {
59
+ const now = /* @__PURE__ */ new Date();
60
+ return purchases.filter((purchase) => {
61
+ return purchase.status === "active" && purchase.cancelledAt && purchase.endDate && new Date(purchase.endDate) > now;
76
62
  });
77
63
  }
78
- function getMostRecentSubscription(subscriptions) {
79
- if (subscriptions.length === 0) return null;
80
- return subscriptions.reduce((latest, current) => {
64
+ function getMostRecentPurchase(purchases) {
65
+ if (purchases.length === 0) return null;
66
+ return purchases.reduce((latest, current) => {
81
67
  return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
82
68
  });
83
69
  }
84
- function getPrimarySubscription(subscriptions) {
85
- const filtered = filterSubscriptions(subscriptions);
86
- const activeSubs = getActiveSubscriptions(filtered);
87
- if (activeSubs.length > 0) {
88
- return getMostRecentSubscription(activeSubs);
89
- }
90
- const cancelledWithEndDate = getCancelledSubscriptionsWithEndDate(filtered);
91
- if (cancelledWithEndDate.length > 0) {
92
- return getMostRecentSubscription(cancelledWithEndDate);
70
+ function getPrimaryPurchase(purchases) {
71
+ const filtered = filterPurchases(purchases);
72
+ if (filtered.length > 0) {
73
+ return getMostRecentPurchase(filtered);
93
74
  }
94
75
  return null;
95
76
  }
96
- function hasActivePaidSubscription(subscriptions, isPaidPlan) {
97
- const filtered = filterSubscriptions(subscriptions);
98
- const activePaid = getActiveSubscriptions(filtered).some((sub) => isPaidPlan(sub.planName));
99
- if (activePaid) return true;
100
- const cancelledPaid = getCancelledSubscriptionsWithEndDate(filtered).some((sub) => isPaidPlan(sub.planName));
101
- return cancelledPaid;
77
+ function isPaidPurchase(purchase) {
78
+ return (purchase.amount ?? 0) > 0;
102
79
  }
103
80
 
81
+ // src/adapters/auth.ts
82
+ var defaultAuthAdapter = {
83
+ async getToken() {
84
+ if (typeof window === "undefined") return null;
85
+ const token = localStorage.getItem("auth_token");
86
+ return token || null;
87
+ },
88
+ async getUserId() {
89
+ const token = await this.getToken();
90
+ if (!token) return null;
91
+ try {
92
+ const parts = token.split(".");
93
+ if (parts.length === 3) {
94
+ const payload = JSON.parse(atob(parts[1]));
95
+ return payload.sub || payload.user_id || null;
96
+ }
97
+ } catch {
98
+ }
99
+ return null;
100
+ }
101
+ };
102
+
104
103
  // src/SolvaPayProvider.tsx
105
104
  var import_jsx_runtime = require("react/jsx-runtime");
106
105
  var SolvaPayContext = (0, import_react.createContext)(null);
106
+ var CUSTOMER_REF_KEY = "solvapay_customerRef";
107
+ var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
108
+ var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
109
+ var CACHE_DURATION = 24 * 60 * 60 * 1e3;
110
+ function getCachedCustomerRef(userId) {
111
+ if (typeof window === "undefined") return null;
112
+ const cached = localStorage.getItem(CUSTOMER_REF_KEY);
113
+ const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
114
+ const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
115
+ if (!cached || !expiry) return null;
116
+ if (Date.now() > parseInt(expiry)) {
117
+ localStorage.removeItem(CUSTOMER_REF_KEY);
118
+ localStorage.removeItem(CUSTOMER_REF_EXPIRY);
119
+ localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
120
+ return null;
121
+ }
122
+ if (userId !== void 0 && userId !== null) {
123
+ if (cachedUserId !== userId) {
124
+ clearCachedCustomerRef();
125
+ return null;
126
+ }
127
+ }
128
+ return cached;
129
+ }
130
+ function setCachedCustomerRef(customerRef, userId) {
131
+ if (typeof window === "undefined") return;
132
+ if (userId === void 0 || userId === null) {
133
+ return;
134
+ }
135
+ localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
136
+ localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
137
+ localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
138
+ }
139
+ function clearCachedCustomerRef() {
140
+ if (typeof window === "undefined") return;
141
+ localStorage.removeItem(CUSTOMER_REF_KEY);
142
+ localStorage.removeItem(CUSTOMER_REF_EXPIRY);
143
+ localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
144
+ }
145
+ function getAuthAdapter(config) {
146
+ if (config?.auth?.adapter) {
147
+ return config.auth.adapter;
148
+ }
149
+ if (config?.auth?.getToken || config?.auth?.getUserId) {
150
+ return {
151
+ async getToken() {
152
+ return config?.auth?.getToken?.() || null;
153
+ },
154
+ async getUserId() {
155
+ return config?.auth?.getUserId?.() || null;
156
+ }
157
+ };
158
+ }
159
+ return defaultAuthAdapter;
160
+ }
107
161
  var SolvaPayProvider = ({
108
- createPayment,
109
- checkSubscription,
110
- processPayment,
111
- customerRef,
112
- onCustomerRefUpdate,
162
+ config,
163
+ createPayment: customCreatePayment,
164
+ checkPurchase: customCheckPurchase,
165
+ processPayment: customProcessPayment,
113
166
  children
114
167
  }) => {
115
- if (!createPayment || typeof createPayment !== "function") {
116
- throw new Error("SolvaPayProvider: createPayment prop is required and must be a function");
117
- }
118
- if (!checkSubscription || typeof checkSubscription !== "function") {
119
- throw new Error("SolvaPayProvider: checkSubscription prop is required and must be a function");
120
- }
121
- const [subscriptionData, setSubscriptionData] = (0, import_react.useState)({
122
- subscriptions: []
168
+ const [purchaseData, setPurchaseData] = (0, import_react.useState)({
169
+ purchases: []
123
170
  });
124
171
  const [loading, setLoading] = (0, import_react.useState)(false);
125
- const [internalCustomerRef, setInternalCustomerRef] = (0, import_react.useState)(customerRef);
172
+ const [internalCustomerRef, setInternalCustomerRef] = (0, import_react.useState)(void 0);
173
+ const [userId, setUserId] = (0, import_react.useState)(null);
174
+ const [isAuthenticated, setIsAuthenticated] = (0, import_react.useState)(false);
126
175
  const inFlightRef = (0, import_react.useRef)(null);
127
176
  const lastFetchedRef = (0, import_react.useRef)(null);
128
- const checkSubscriptionRef = (0, import_react.useRef)(checkSubscription);
177
+ const checkPurchaseRef = (0, import_react.useRef)(null);
178
+ const createPaymentRef = (0, import_react.useRef)(null);
179
+ const processPaymentRef = (0, import_react.useRef)(null);
180
+ const configRef = (0, import_react.useRef)(config);
181
+ const buildDefaultCheckPurchaseRef = (0, import_react.useRef)(
182
+ null
183
+ );
129
184
  (0, import_react.useEffect)(() => {
130
- checkSubscriptionRef.current = checkSubscription;
131
- }, [checkSubscription]);
132
- const fetchSubscription = (0, import_react.useCallback)(async (force = false) => {
133
- const currentCustomerRef = internalCustomerRef;
134
- if (!currentCustomerRef) {
135
- setSubscriptionData({ subscriptions: [] });
136
- setLoading(false);
137
- inFlightRef.current = null;
138
- lastFetchedRef.current = null;
139
- return;
185
+ configRef.current = config;
186
+ }, [config]);
187
+ (0, import_react.useEffect)(() => {
188
+ checkPurchaseRef.current = customCheckPurchase || null;
189
+ }, [customCheckPurchase]);
190
+ (0, import_react.useEffect)(() => {
191
+ createPaymentRef.current = customCreatePayment || null;
192
+ }, [customCreatePayment]);
193
+ (0, import_react.useEffect)(() => {
194
+ processPaymentRef.current = customProcessPayment || null;
195
+ }, [customProcessPayment]);
196
+ const buildDefaultCheckPurchase = (0, import_react.useCallback)(async () => {
197
+ const currentConfig = configRef.current;
198
+ const adapter = getAuthAdapter(currentConfig);
199
+ const token = await adapter.getToken();
200
+ const detectedUserId = await adapter.getUserId();
201
+ const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
202
+ const fetchFn = currentConfig?.fetch || fetch;
203
+ const cachedRef = getCachedCustomerRef(detectedUserId);
204
+ const headers = {
205
+ "Content-Type": "application/json"
206
+ };
207
+ if (token) {
208
+ headers["Authorization"] = `Bearer ${token}`;
140
209
  }
141
- if (!force && lastFetchedRef.current === currentCustomerRef && inFlightRef.current !== currentCustomerRef) {
142
- return;
210
+ if (cachedRef) {
211
+ headers["x-solvapay-customer-ref"] = cachedRef;
143
212
  }
144
- if (inFlightRef.current === currentCustomerRef) {
145
- return;
213
+ if (currentConfig?.headers) {
214
+ const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
215
+ Object.assign(headers, customHeaders);
146
216
  }
147
- inFlightRef.current = currentCustomerRef;
148
- setLoading(true);
149
- try {
150
- const data = await checkSubscriptionRef.current(currentCustomerRef);
151
- if (inFlightRef.current === currentCustomerRef) {
152
- const filteredData = {
153
- ...data,
154
- subscriptions: filterSubscriptions(data.subscriptions || [])
155
- };
156
- setSubscriptionData(filteredData);
157
- lastFetchedRef.current = currentCustomerRef;
158
- }
159
- } catch (error) {
160
- console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
161
- if (inFlightRef.current === currentCustomerRef) {
162
- setSubscriptionData({
163
- customerRef: currentCustomerRef,
164
- subscriptions: []
165
- });
166
- lastFetchedRef.current = currentCustomerRef;
217
+ const res = await fetchFn(route, {
218
+ method: "GET",
219
+ headers
220
+ });
221
+ if (!res.ok) {
222
+ const error = new Error(`Failed to check purchase: ${res.statusText}`);
223
+ currentConfig?.onError?.(error, "checkPurchase");
224
+ throw error;
225
+ }
226
+ return res.json();
227
+ }, []);
228
+ (0, import_react.useEffect)(() => {
229
+ buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
230
+ }, [buildDefaultCheckPurchase]);
231
+ const buildDefaultCreatePayment = (0, import_react.useCallback)(
232
+ async (params) => {
233
+ const currentConfig = configRef.current;
234
+ const adapter = getAuthAdapter(currentConfig);
235
+ const token = await adapter.getToken();
236
+ const detectedUserId = await adapter.getUserId();
237
+ const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
238
+ const fetchFn = currentConfig?.fetch || fetch;
239
+ const cachedRef = getCachedCustomerRef(detectedUserId);
240
+ const headers = {
241
+ "Content-Type": "application/json"
242
+ };
243
+ if (token) {
244
+ headers["Authorization"] = `Bearer ${token}`;
167
245
  }
168
- } finally {
169
- if (inFlightRef.current === currentCustomerRef) {
170
- setLoading(false);
171
- inFlightRef.current = null;
246
+ if (cachedRef) {
247
+ headers["x-solvapay-customer-ref"] = cachedRef;
248
+ }
249
+ if (currentConfig?.headers) {
250
+ const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
251
+ Object.assign(headers, customHeaders);
252
+ }
253
+ const body = { planRef: params.planRef };
254
+ if (params.productRef) {
255
+ body.productRef = params.productRef;
256
+ }
257
+ const res = await fetchFn(route, {
258
+ method: "POST",
259
+ headers,
260
+ body: JSON.stringify(body)
261
+ });
262
+ if (!res.ok) {
263
+ const error = new Error(`Failed to create payment: ${res.statusText}`);
264
+ currentConfig?.onError?.(error, "createPayment");
265
+ throw error;
266
+ }
267
+ return res.json();
268
+ },
269
+ []
270
+ );
271
+ const buildDefaultProcessPayment = (0, import_react.useCallback)(
272
+ async (params) => {
273
+ const currentConfig = configRef.current;
274
+ const adapter = getAuthAdapter(currentConfig);
275
+ const token = await adapter.getToken();
276
+ const detectedUserId = await adapter.getUserId();
277
+ const route = currentConfig?.api?.processPayment || "/api/process-payment";
278
+ const fetchFn = currentConfig?.fetch || fetch;
279
+ const cachedRef = getCachedCustomerRef(detectedUserId);
280
+ const headers = {
281
+ "Content-Type": "application/json"
282
+ };
283
+ if (token) {
284
+ headers["Authorization"] = `Bearer ${token}`;
172
285
  }
286
+ if (cachedRef) {
287
+ headers["x-solvapay-customer-ref"] = cachedRef;
288
+ }
289
+ if (currentConfig?.headers) {
290
+ const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
291
+ Object.assign(headers, customHeaders);
292
+ }
293
+ const res = await fetchFn(route, {
294
+ method: "POST",
295
+ headers,
296
+ body: JSON.stringify(params)
297
+ });
298
+ if (!res.ok) {
299
+ const error = new Error(`Failed to process payment: ${res.statusText}`);
300
+ currentConfig?.onError?.(error, "processPayment");
301
+ throw error;
302
+ }
303
+ return res.json();
304
+ },
305
+ []
306
+ );
307
+ const _checkPurchase = (0, import_react.useCallback)(async () => {
308
+ if (checkPurchaseRef.current) {
309
+ return checkPurchaseRef.current();
173
310
  }
174
- }, [internalCustomerRef]);
175
- const refetchSubscription = (0, import_react.useCallback)(async () => {
176
- const currentCustomerRef = internalCustomerRef;
177
- lastFetchedRef.current = null;
178
- inFlightRef.current = null;
179
- await fetchSubscription(true);
180
- }, [fetchSubscription, internalCustomerRef]);
181
- (0, import_react.useEffect)(() => {
182
- if (customerRef !== internalCustomerRef) {
183
- setInternalCustomerRef(customerRef);
311
+ if (buildDefaultCheckPurchaseRef.current) {
312
+ return buildDefaultCheckPurchaseRef.current();
184
313
  }
185
- }, [customerRef, internalCustomerRef]);
314
+ return buildDefaultCheckPurchase();
315
+ }, [buildDefaultCheckPurchase]);
316
+ const createPayment = (0, import_react.useCallback)(
317
+ async (params) => {
318
+ if (createPaymentRef.current) {
319
+ return createPaymentRef.current(params);
320
+ }
321
+ return buildDefaultCreatePayment(params);
322
+ },
323
+ [buildDefaultCreatePayment]
324
+ );
325
+ const processPayment = (0, import_react.useCallback)(
326
+ async (params) => {
327
+ if (processPaymentRef.current) {
328
+ return processPaymentRef.current(params);
329
+ }
330
+ return buildDefaultProcessPayment(params);
331
+ },
332
+ [buildDefaultProcessPayment]
333
+ );
186
334
  (0, import_react.useEffect)(() => {
187
- if (!customerRef) {
188
- setSubscriptionData({ subscriptions: [] });
189
- setLoading(false);
190
- return;
191
- }
192
- if (customerRef === lastFetchedRef.current || customerRef === inFlightRef.current) {
193
- return;
194
- }
195
- const doFetch = async () => {
196
- inFlightRef.current = customerRef;
335
+ const detectAuth = async () => {
336
+ const currentConfig = configRef.current;
337
+ const adapter = getAuthAdapter(currentConfig);
338
+ const token = await adapter.getToken();
339
+ const detectedUserId = await adapter.getUserId();
340
+ const prevUserId = userId;
341
+ setIsAuthenticated(!!token);
342
+ setUserId(detectedUserId);
343
+ if (prevUserId !== null && detectedUserId !== prevUserId) {
344
+ clearCachedCustomerRef();
345
+ setInternalCustomerRef(void 0);
346
+ return;
347
+ }
348
+ const cachedRef = getCachedCustomerRef(detectedUserId);
349
+ if (cachedRef && token) {
350
+ setInternalCustomerRef(cachedRef);
351
+ } else if (!token) {
352
+ clearCachedCustomerRef();
353
+ setInternalCustomerRef(void 0);
354
+ } else if (token && !cachedRef) {
355
+ setInternalCustomerRef(void 0);
356
+ }
357
+ };
358
+ detectAuth();
359
+ const interval = setInterval(detectAuth, 5e3);
360
+ return () => clearInterval(interval);
361
+ }, [userId]);
362
+ const fetchPurchase = (0, import_react.useCallback)(
363
+ async (force = false) => {
364
+ if (!isAuthenticated && !internalCustomerRef) {
365
+ setPurchaseData({ purchases: [] });
366
+ setLoading(false);
367
+ inFlightRef.current = null;
368
+ lastFetchedRef.current = null;
369
+ return;
370
+ }
371
+ const cacheKey = internalCustomerRef || userId || "anonymous";
372
+ if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
373
+ return;
374
+ }
375
+ if (inFlightRef.current === cacheKey) {
376
+ return;
377
+ }
378
+ inFlightRef.current = cacheKey;
197
379
  setLoading(true);
198
380
  try {
199
- const data = await checkSubscriptionRef.current(customerRef);
200
- if (inFlightRef.current === customerRef) {
381
+ const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
382
+ if (!checkFn) {
383
+ throw new Error("checkPurchase function not available");
384
+ }
385
+ const data = await checkFn();
386
+ if (data.customerRef) {
387
+ setInternalCustomerRef(data.customerRef);
388
+ const currentAdapter = getAuthAdapter(configRef.current);
389
+ const currentUserId = await currentAdapter.getUserId();
390
+ setCachedCustomerRef(data.customerRef, currentUserId);
391
+ }
392
+ if (inFlightRef.current === cacheKey) {
201
393
  const filteredData = {
202
394
  ...data,
203
- subscriptions: filterSubscriptions(data.subscriptions || [])
395
+ purchases: filterPurchases(data.purchases || [])
204
396
  };
205
- setSubscriptionData(filteredData);
206
- lastFetchedRef.current = customerRef;
397
+ setPurchaseData(filteredData);
398
+ lastFetchedRef.current = cacheKey;
207
399
  }
208
400
  } catch (error) {
209
- console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
210
- if (inFlightRef.current === customerRef) {
211
- setSubscriptionData({
212
- customerRef,
213
- subscriptions: []
401
+ console.error("[SolvaPayProvider] Failed to fetch purchase:", error);
402
+ if (inFlightRef.current === cacheKey) {
403
+ setPurchaseData({
404
+ purchases: []
214
405
  });
215
- lastFetchedRef.current = customerRef;
406
+ lastFetchedRef.current = cacheKey;
216
407
  }
217
408
  } finally {
218
- if (inFlightRef.current === customerRef) {
409
+ if (inFlightRef.current === cacheKey) {
219
410
  setLoading(false);
220
411
  inFlightRef.current = null;
221
412
  }
222
413
  }
223
- };
224
- doFetch();
225
- }, [customerRef]);
226
- const updateCustomerRef = (0, import_react.useCallback)((newCustomerRef) => {
227
- const previousRef = internalCustomerRef;
228
- setInternalCustomerRef(newCustomerRef);
229
- if (onCustomerRefUpdate) {
230
- onCustomerRefUpdate(newCustomerRef);
231
- }
232
- }, [onCustomerRefUpdate, internalCustomerRef]);
233
- const subscription = (0, import_react.useMemo)(() => ({
234
- loading,
235
- customerRef: subscriptionData.customerRef,
236
- email: subscriptionData.email,
237
- name: subscriptionData.name,
238
- subscriptions: subscriptionData.subscriptions,
239
- hasActiveSubscription: subscriptionData.subscriptions.some(
240
- (sub) => sub.status === "active"
241
- ),
242
- hasPlan: (planName) => {
243
- return subscriptionData.subscriptions.some(
244
- (sub) => sub.planName.toLowerCase() === planName.toLowerCase() && sub.status === "active"
245
- );
414
+ },
415
+ [isAuthenticated, internalCustomerRef, userId]
416
+ );
417
+ const fetchPurchaseRef = (0, import_react.useRef)(fetchPurchase);
418
+ (0, import_react.useEffect)(() => {
419
+ fetchPurchaseRef.current = fetchPurchase;
420
+ }, [fetchPurchase]);
421
+ const refetchPurchase = (0, import_react.useCallback)(async () => {
422
+ lastFetchedRef.current = null;
423
+ inFlightRef.current = null;
424
+ setPurchaseData({ purchases: [] });
425
+ await fetchPurchaseRef.current(true);
426
+ }, []);
427
+ (0, import_react.useEffect)(() => {
428
+ lastFetchedRef.current = null;
429
+ inFlightRef.current = null;
430
+ if (isAuthenticated || internalCustomerRef) {
431
+ fetchPurchase();
432
+ } else {
433
+ setPurchaseData({ purchases: [] });
434
+ setLoading(false);
246
435
  }
247
- }), [loading, subscriptionData]);
248
- const contextValue = (0, import_react.useMemo)(() => ({
249
- subscription,
250
- refetchSubscription,
251
- createPayment,
252
- processPayment,
253
- customerRef: internalCustomerRef,
254
- updateCustomerRef
255
- }), [subscription, refetchSubscription, createPayment, processPayment, internalCustomerRef, updateCustomerRef]);
436
+ }, [isAuthenticated, internalCustomerRef, userId]);
437
+ const updateCustomerRef = (0, import_react.useCallback)(
438
+ (newCustomerRef) => {
439
+ setInternalCustomerRef(newCustomerRef);
440
+ setCachedCustomerRef(newCustomerRef, userId);
441
+ fetchPurchase(true);
442
+ },
443
+ [fetchPurchase, userId]
444
+ );
445
+ const purchase = (0, import_react.useMemo)(() => {
446
+ const activePurchase = getPrimaryPurchase(purchaseData.purchases);
447
+ const activePaidPurchases = purchaseData.purchases.filter(
448
+ (p) => p.status === "active" && isPaidPurchase(p)
449
+ );
450
+ const activePaidPurchase = activePaidPurchases.sort(
451
+ (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
452
+ )[0] || null;
453
+ return {
454
+ loading,
455
+ customerRef: purchaseData.customerRef || internalCustomerRef,
456
+ email: purchaseData.email,
457
+ name: purchaseData.name,
458
+ purchases: purchaseData.purchases,
459
+ hasProduct: (productName) => {
460
+ return purchaseData.purchases.some(
461
+ (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
462
+ );
463
+ },
464
+ hasPlan: (productName) => {
465
+ return purchaseData.purchases.some(
466
+ (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
467
+ );
468
+ },
469
+ activePurchase,
470
+ hasPaidPurchase: activePaidPurchases.length > 0,
471
+ activePaidPurchase
472
+ };
473
+ }, [loading, purchaseData, internalCustomerRef]);
474
+ const contextValue = (0, import_react.useMemo)(
475
+ () => ({
476
+ purchase,
477
+ refetchPurchase,
478
+ createPayment,
479
+ processPayment,
480
+ customerRef: purchaseData.customerRef || internalCustomerRef,
481
+ updateCustomerRef
482
+ }),
483
+ [
484
+ purchase,
485
+ refetchPurchase,
486
+ createPayment,
487
+ processPayment,
488
+ purchaseData.customerRef,
489
+ internalCustomerRef,
490
+ updateCustomerRef
491
+ ]
492
+ );
256
493
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SolvaPayContext.Provider, { value: contextValue, children });
257
494
  };
258
495
 
@@ -281,7 +518,8 @@ var stripePromiseCache = /* @__PURE__ */ new Map();
281
518
  function getStripeCacheKey(publishableKey, accountId) {
282
519
  return accountId ? `${publishableKey}:${accountId}` : publishableKey;
283
520
  }
284
- function useCheckout(planRef) {
521
+ function useCheckout(options) {
522
+ const { planRef, productRef } = options;
285
523
  const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
286
524
  const [loading, setLoading] = (0, import_react3.useState)(false);
287
525
  const [error, setError] = (0, import_react3.useState)(
@@ -298,15 +536,11 @@ function useCheckout(planRef) {
298
536
  setError(new Error("useCheckout: planRef parameter is required and must be a string"));
299
537
  return;
300
538
  }
301
- if (!customerRef) {
302
- setError(new Error("No customer reference available. Please ensure you are logged in."));
303
- return;
304
- }
305
539
  isStartingRef.current = true;
306
540
  setLoading(true);
307
541
  setError(null);
308
542
  try {
309
- const result = await createPayment({ planRef, customerRef });
543
+ const result = await createPayment({ planRef, productRef });
310
544
  if (!result || typeof result !== "object") {
311
545
  throw new Error("Invalid payment intent response from server");
312
546
  }
@@ -335,7 +569,7 @@ function useCheckout(planRef) {
335
569
  setLoading(false);
336
570
  isStartingRef.current = false;
337
571
  }
338
- }, [planRef, customerRef, createPayment, updateCustomerRef, loading]);
572
+ }, [planRef, productRef, createPayment, updateCustomerRef, loading]);
339
573
  const reset = (0, import_react3.useCallback)(() => {
340
574
  isStartingRef.current = false;
341
575
  setLoading(false);
@@ -353,12 +587,12 @@ function useCheckout(planRef) {
353
587
  };
354
588
  }
355
589
 
356
- // src/hooks/useSubscription.ts
357
- function useSubscription() {
358
- const { subscription, refetchSubscription } = useSolvaPay();
590
+ // src/hooks/usePurchase.ts
591
+ function usePurchase() {
592
+ const { purchase, refetchPurchase } = useSolvaPay();
359
593
  return {
360
- ...subscription,
361
- refetch: refetchSubscription
594
+ ...purchase,
595
+ refetch: refetchPurchase
362
596
  };
363
597
  }
364
598
 
@@ -369,16 +603,30 @@ var Spinner = ({
369
603
  size = "md"
370
604
  }) => {
371
605
  const sizeClasses = {
372
- sm: "w-4 h-4 border-2",
373
- md: "w-6 h-6 border-2",
374
- lg: "w-8 h-8 border-2"
606
+ sm: "h-4 w-4",
607
+ md: "h-5 w-5",
608
+ lg: "h-6 w-6"
375
609
  };
376
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
377
- "div",
610
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
611
+ "svg",
378
612
  {
379
- className: `inline-block border-gray-300 border-t-gray-600 rounded-full animate-spin ${sizeClasses[size]} ${className}`,
613
+ className: `animate-spin ${sizeClasses[size]} ${className}`,
614
+ xmlns: "http://www.w3.org/2000/svg",
615
+ fill: "none",
616
+ viewBox: "0 0 24 24",
380
617
  role: "status",
381
- "aria-busy": "true"
618
+ "aria-busy": "true",
619
+ children: [
620
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
621
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
622
+ "path",
623
+ {
624
+ className: "opacity-75",
625
+ fill: "currentColor",
626
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
627
+ }
628
+ )
629
+ ]
382
630
  }
383
631
  );
384
632
  };
@@ -386,19 +634,34 @@ var Spinner = ({
386
634
  // src/components/StripePaymentFormWrapper.tsx
387
635
  var import_react4 = require("react");
388
636
  var import_react_stripe_js = require("@stripe/react-stripe-js");
637
+
638
+ // src/hooks/useCustomer.ts
639
+ function useCustomer() {
640
+ const { purchase, customerRef } = useSolvaPay();
641
+ return {
642
+ customerRef: purchase.customerRef || customerRef,
643
+ email: purchase.email,
644
+ name: purchase.name,
645
+ loading: purchase.loading
646
+ };
647
+ }
648
+
649
+ // src/components/StripePaymentFormWrapper.tsx
389
650
  var import_jsx_runtime3 = require("react/jsx-runtime");
390
651
  var StripePaymentFormWrapper = ({
391
652
  onSuccess,
392
653
  onError,
393
- returnUrl,
654
+ returnUrl: _returnUrl,
394
655
  submitButtonText = "Pay Now",
395
- buttonClassName
656
+ buttonClassName,
657
+ clientSecret
396
658
  }) => {
397
659
  const stripe = (0, import_react_stripe_js.useStripe)();
398
660
  const elements = (0, import_react_stripe_js.useElements)();
661
+ const customer = useCustomer();
399
662
  const [isProcessing, setIsProcessing] = (0, import_react4.useState)(false);
400
663
  const [message, setMessage] = (0, import_react4.useState)(null);
401
- const [isPaymentElementReady, setIsPaymentElementReady] = (0, import_react4.useState)(false);
664
+ const [cardComplete, setCardComplete] = (0, import_react4.useState)(false);
402
665
  const handleSubmit = async (event) => {
403
666
  event.preventDefault();
404
667
  if (!stripe || !elements) {
@@ -409,31 +672,67 @@ var StripePaymentFormWrapper = ({
409
672
  }
410
673
  return;
411
674
  }
675
+ if (!clientSecret) {
676
+ const errorMessage = "Payment intent not available. Please refresh the page.";
677
+ setMessage(errorMessage);
678
+ if (onError) {
679
+ onError(new Error(errorMessage));
680
+ }
681
+ return;
682
+ }
412
683
  setIsProcessing(true);
413
684
  setMessage(null);
414
685
  try {
415
- const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
416
- const { error, paymentIntent } = await stripe.confirmPayment({
417
- elements,
418
- confirmParams: {
419
- return_url: finalReturnUrl
420
- },
421
- redirect: "if_required"
422
- // Only redirect if required by payment method
686
+ const cardElement = elements.getElement(import_react_stripe_js.CardElement);
687
+ if (!cardElement) {
688
+ const errorMessage = "Card element not found";
689
+ setMessage(errorMessage);
690
+ setIsProcessing(false);
691
+ if (onError) {
692
+ onError(new Error(errorMessage));
693
+ }
694
+ return;
695
+ }
696
+ const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
697
+ payment_method: {
698
+ card: cardElement,
699
+ billing_details: {
700
+ email: customer.email,
701
+ name: customer.name
702
+ }
703
+ }
423
704
  });
424
705
  if (error) {
425
706
  const errorMessage = error.message || "An unexpected error occurred.";
426
707
  setMessage(errorMessage);
708
+ setIsProcessing(false);
427
709
  if (onError) {
428
710
  onError(new Error(errorMessage));
429
711
  }
430
712
  } else if (paymentIntent && paymentIntent.status === "succeeded") {
431
- setMessage("Payment successful!");
713
+ setMessage(null);
432
714
  if (onSuccess) {
433
- onSuccess(paymentIntent);
715
+ try {
716
+ await onSuccess(paymentIntent);
717
+ setMessage("Payment successful!");
718
+ } catch (err) {
719
+ const error2 = err instanceof Error ? err : new Error("Payment processing failed");
720
+ setMessage("Payment processing failed. Please try again or contact support.");
721
+ setIsProcessing(false);
722
+ if (onError) {
723
+ onError(error2);
724
+ }
725
+ return;
726
+ }
727
+ } else {
728
+ setMessage("Payment successful!");
434
729
  }
730
+ } else if (paymentIntent && paymentIntent.status === "requires_action") {
731
+ setMessage("Payment requires additional authentication. Please complete the verification.");
732
+ setIsProcessing(false);
435
733
  } else if (paymentIntent) {
436
734
  setMessage(`Payment status: ${paymentIntent.status || "processing"}`);
735
+ setIsProcessing(false);
437
736
  }
438
737
  } catch (err) {
439
738
  const error = err instanceof Error ? err : new Error("Unknown error occurred");
@@ -447,53 +746,76 @@ var StripePaymentFormWrapper = ({
447
746
  };
448
747
  const isSuccess = message?.includes("successful") ?? false;
449
748
  const isReady = !!(stripe && elements);
450
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
451
- "form",
452
- {
453
- onSubmit: handleSubmit,
454
- className: "space-y-6",
455
- noValidate: true,
456
- children: [
457
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
458
- (!isPaymentElementReady || !isReady) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute inset-0 z-10 p-4 bg-white border border-gray-300 rounded-lg min-h-[200px] flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "md" }) }),
459
- isReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: isPaymentElementReady ? "block" : "opacity-0 pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
460
- import_react_stripe_js.PaymentElement,
461
- {
462
- onReady: () => setIsPaymentElementReady(true)
463
- }
464
- ) })
465
- ] }),
466
- message && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
467
- "div",
468
- {
469
- 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"}`,
470
- role: isSuccess ? "status" : "alert",
471
- "aria-live": isSuccess ? "polite" : "assertive",
472
- "aria-atomic": "true",
473
- children: message
474
- }
475
- ),
476
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
477
- "button",
478
- {
479
- type: "submit",
480
- disabled: !isReady || !isPaymentElementReady || isProcessing,
481
- className: buttonClassName,
482
- "aria-busy": isProcessing || !isPaymentElementReady || !isReady,
483
- "aria-disabled": !isReady || !isPaymentElementReady || isProcessing,
484
- children: isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" }) }) : !isReady || !isPaymentElementReady ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" }) }) : submitButtonText
749
+ const cardElementOptions = {
750
+ style: {
751
+ base: {
752
+ fontSize: "16px",
753
+ color: "#424770",
754
+ "::placeholder": {
755
+ color: "#aab7c4"
756
+ }
757
+ },
758
+ invalid: {
759
+ color: "#9e2146"
760
+ }
761
+ },
762
+ hidePostalCode: true
763
+ };
764
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
765
+ isReady ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
766
+ import_react_stripe_js.CardElement,
767
+ {
768
+ options: cardElementOptions,
769
+ onChange: (e) => {
770
+ if (e.error) {
771
+ setMessage(e.error.message);
772
+ setCardComplete(false);
773
+ } else {
774
+ setMessage(null);
775
+ setCardComplete(e.complete);
485
776
  }
486
- )
487
- ]
488
- }
489
- );
777
+ }
778
+ }
779
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
780
+ "div",
781
+ {
782
+ style: {
783
+ padding: "12px",
784
+ border: "1px solid #cbd5e1",
785
+ borderRadius: "0.375rem",
786
+ backgroundColor: "white",
787
+ display: "flex",
788
+ alignItems: "center",
789
+ justifyContent: "center",
790
+ minHeight: "40px"
791
+ },
792
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" })
793
+ }
794
+ ),
795
+ message && !isSuccess && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
796
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
797
+ "button",
798
+ {
799
+ type: "submit",
800
+ disabled: !isReady || !cardComplete || isProcessing || !clientSecret,
801
+ className: buttonClassName,
802
+ "aria-busy": isProcessing,
803
+ "aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
804
+ onClick: handleSubmit,
805
+ children: isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
806
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Processing..." })
808
+ ] }) : submitButtonText
809
+ }
810
+ )
811
+ ] });
490
812
  };
491
813
 
492
814
  // src/PaymentForm.tsx
493
815
  var import_jsx_runtime4 = require("react/jsx-runtime");
494
816
  var PaymentForm = ({
495
817
  planRef,
496
- agentRef,
818
+ productRef,
497
819
  onSuccess,
498
820
  onError,
499
821
  returnUrl,
@@ -502,72 +824,121 @@ var PaymentForm = ({
502
824
  buttonClassName
503
825
  }) => {
504
826
  const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
505
- const checkout = useCheckout(validPlanRef);
506
- const { refetch } = useSubscription();
507
- const { processPayment, customerRef } = useSolvaPay();
827
+ const {
828
+ loading: checkoutLoading,
829
+ error: checkoutError,
830
+ clientSecret,
831
+ startCheckout,
832
+ stripePromise
833
+ } = useCheckout({ planRef: validPlanRef, productRef });
834
+ const { refetch } = usePurchase();
835
+ const { processPayment } = useSolvaPay();
508
836
  const hasInitializedRef = (0, import_react5.useRef)(false);
509
837
  (0, import_react5.useEffect)(() => {
510
- if (!hasInitializedRef.current && validPlanRef && !checkout.loading && !checkout.error && !checkout.clientSecret) {
838
+ if (!hasInitializedRef.current && validPlanRef && !checkoutLoading && !checkoutError && !clientSecret) {
511
839
  hasInitializedRef.current = true;
512
- checkout.startCheckout().catch(() => {
840
+ startCheckout().catch(() => {
513
841
  hasInitializedRef.current = false;
514
842
  });
515
843
  }
516
- if (validPlanRef && checkout.clientSecret) {
844
+ if (validPlanRef && clientSecret) {
517
845
  hasInitializedRef.current = true;
518
846
  }
519
- }, [validPlanRef, checkout.loading, checkout.error, checkout.clientSecret, checkout.startCheckout]);
520
- const handleSuccess = (0, import_react5.useCallback)(async (paymentIntent) => {
521
- if (processPayment && customerRef && agentRef) {
522
- try {
523
- await processPayment({
524
- paymentIntentId: paymentIntent.id,
525
- agentRef,
526
- customerRef,
527
- planRef
528
- });
529
- await refetch();
530
- } catch (error) {
531
- console.error("[PaymentForm] Failed to process payment:", error);
847
+ }, [validPlanRef, checkoutLoading, checkoutError, clientSecret, startCheckout]);
848
+ const handleSuccess = (0, import_react5.useCallback)(
849
+ async (paymentIntent) => {
850
+ let processingTimeout = false;
851
+ let processingResult = null;
852
+ const paymentIntentAny = paymentIntent;
853
+ if (processPayment && productRef) {
854
+ try {
855
+ const result = await processPayment({
856
+ paymentIntentId: paymentIntentAny.id,
857
+ productRef,
858
+ planRef
859
+ });
860
+ processingResult = result;
861
+ const isTimeout = result?.status === "timeout";
862
+ processingTimeout = isTimeout;
863
+ if (isTimeout) {
864
+ for (let attempt = 1; attempt <= 5; attempt++) {
865
+ const delay = attempt * 1e3;
866
+ await new Promise((resolve) => setTimeout(resolve, delay));
867
+ await refetch();
868
+ }
869
+ if (onSuccess) {
870
+ await onSuccess({
871
+ ...paymentIntentAny,
872
+ _processingTimeout: processingTimeout,
873
+ _processingResult: processingResult
874
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
875
+ });
876
+ }
877
+ throw new Error("Payment processing timed out");
878
+ } else {
879
+ await refetch();
880
+ }
881
+ } catch (error) {
882
+ console.error("[PaymentForm] Failed to process payment:", error);
883
+ if (onSuccess) {
884
+ try {
885
+ await onSuccess({
886
+ ...paymentIntentAny,
887
+ _processingError: error
888
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
889
+ });
890
+ } catch {
891
+ }
892
+ }
893
+ throw error;
894
+ }
895
+ } else {
532
896
  await refetch();
533
897
  }
534
- } else {
535
- await refetch();
536
- }
537
- if (onSuccess) {
538
- onSuccess(paymentIntent);
539
- }
540
- }, [processPayment, customerRef, agentRef, planRef, refetch, onSuccess]);
541
- const handleError = (0, import_react5.useCallback)((err) => {
542
- if (onError) {
543
- onError(err);
544
- }
545
- }, [onError]);
898
+ if (onSuccess && !processingTimeout) {
899
+ await onSuccess({
900
+ ...paymentIntentAny,
901
+ _processingTimeout: processingTimeout,
902
+ _processingResult: processingResult
903
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
904
+ });
905
+ }
906
+ },
907
+ [processPayment, productRef, planRef, refetch, onSuccess]
908
+ );
909
+ const handleError = (0, import_react5.useCallback)(
910
+ (err) => {
911
+ if (onError) {
912
+ onError(err);
913
+ }
914
+ },
915
+ [onError]
916
+ );
546
917
  const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
547
918
  const isValidPlanRef = planRef && typeof planRef === "string";
548
- const hasError = !!checkout.error;
549
- const hasStripeData = !!(checkout.stripePromise && checkout.clientSecret);
919
+ const hasError = !!checkoutError;
920
+ const hasStripeData = !!(stripePromise && clientSecret);
550
921
  const elementsOptions = (0, import_react5.useMemo)(() => {
551
- if (!checkout.clientSecret) return void 0;
552
- return { clientSecret: checkout.clientSecret };
553
- }, [checkout.clientSecret]);
922
+ if (!clientSecret) return void 0;
923
+ return { clientSecret };
924
+ }, [clientSecret]);
554
925
  const [hasMountedElements, setHasMountedElements] = (0, import_react5.useState)(false);
555
926
  (0, import_react5.useEffect)(() => {
556
927
  if (hasStripeData) {
557
928
  setHasMountedElements(true);
558
929
  }
559
930
  }, [hasStripeData]);
560
- const shouldRenderElements = hasStripeData || hasMountedElements && checkout.stripePromise && checkout.clientSecret;
561
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: !isValidPlanRef ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: "PaymentForm: planRef is required and must be a string" }) : hasError ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: [
562
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "font-medium mb-1", children: "Payment initialization failed" }),
563
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-sm", children: checkout.error?.message || "Unknown error" })
564
- ] }) : shouldRenderElements && checkout.stripePromise && elementsOptions ? (
931
+ const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
932
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: !isValidPlanRef ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "PaymentForm: planRef is required and must be a string" }) : hasError ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
933
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "Payment initialization failed" }),
934
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: checkoutError?.message || "Unknown error" })
935
+ ] }) : shouldRenderElements && elementsOptions ? (
565
936
  // Once we have Stripe data, always render Elements to maintain hook consistency
566
937
  // This prevents hook count mismatches when transitioning between states
567
938
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
568
939
  import_react_stripe_js2.Elements,
569
940
  {
570
- stripe: checkout.stripePromise,
941
+ stripe: stripePromise,
571
942
  options: elementsOptions,
572
943
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
573
944
  StripePaymentFormWrapper,
@@ -576,168 +947,212 @@ var PaymentForm = ({
576
947
  onError: handleError,
577
948
  returnUrl: finalReturnUrl,
578
949
  submitButtonText,
579
- buttonClassName
950
+ buttonClassName,
951
+ clientSecret
580
952
  }
581
953
  )
582
954
  },
583
- checkout.clientSecret
955
+ clientSecret
584
956
  )
585
957
  ) : (
586
958
  // Loading state before Stripe data is available
587
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-4", children: [
588
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg flex items-center justify-center min-h-[200px]", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "md" }) }),
959
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
960
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
961
+ "div",
962
+ {
963
+ style: {
964
+ minHeight: "40px",
965
+ display: "flex",
966
+ alignItems: "center",
967
+ justifyContent: "center"
968
+ },
969
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "md" })
970
+ }
971
+ ),
589
972
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
590
973
  "button",
591
974
  {
975
+ type: "submit",
592
976
  disabled: true,
593
977
  className: buttonClassName,
594
- "aria-busy": "true",
595
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "sm" }) })
978
+ "aria-busy": "false",
979
+ "aria-disabled": "true",
980
+ children: submitButtonText
596
981
  }
597
982
  )
598
983
  ] })
599
984
  ) });
600
985
  };
601
986
 
602
- // src/components/PlanBadge.tsx
603
- var import_react6 = require("react");
987
+ // src/components/ProductBadge.tsx
604
988
  var import_jsx_runtime5 = require("react/jsx-runtime");
605
- var PlanBadge = ({
989
+ var ProductBadge = ({
606
990
  children,
607
991
  as: Component = "div",
608
992
  className
609
993
  }) => {
610
- const { subscriptions, loading } = useSubscription();
611
- const [displayPlan, setDisplayPlan] = (0, import_react6.useState)(null);
612
- const [hasLoadedOnce, setHasLoadedOnce] = (0, import_react6.useState)(false);
613
- const lastPlanRef = (0, import_react6.useRef)(null);
614
- const lastLoadingRef = (0, import_react6.useRef)(true);
615
- const previousSubscriptionsRef = (0, import_react6.useRef)(subscriptions);
616
- const primarySubscription = getPrimarySubscription(subscriptions);
617
- const currentPlanName = primarySubscription?.planName || null;
618
- const fallbackPlanName = subscriptions.length > 0 && !currentPlanName ? subscriptions[0]?.planName || null : null;
619
- const effectivePlanName = currentPlanName || fallbackPlanName;
620
- (0, import_react6.useEffect)(() => {
621
- if (!loading && !hasLoadedOnce) {
622
- setHasLoadedOnce(true);
623
- }
624
- lastLoadingRef.current = loading;
625
- }, [loading, hasLoadedOnce]);
626
- (0, import_react6.useEffect)(() => {
627
- const previousSubs = previousSubscriptionsRef.current;
628
- if (previousSubs !== subscriptions) {
629
- if (loading) {
630
- setHasLoadedOnce(false);
631
- }
632
- setDisplayPlan(null);
633
- lastPlanRef.current = null;
634
- previousSubscriptionsRef.current = subscriptions;
635
- }
636
- }, [subscriptions, loading]);
637
- (0, import_react6.useEffect)(() => {
638
- const currentPlan = effectivePlanName;
639
- const previousPlan = lastPlanRef.current;
640
- if (currentPlan !== previousPlan) {
641
- if (currentPlan !== null) {
642
- lastPlanRef.current = currentPlan;
643
- setDisplayPlan(currentPlan);
644
- } else {
645
- lastPlanRef.current = null;
646
- setDisplayPlan(null);
647
- }
648
- } else if (currentPlan !== null && displayPlan === null) {
649
- setDisplayPlan(currentPlan);
650
- }
651
- }, [effectivePlanName, displayPlan]);
652
- const shouldShow = effectivePlanName !== null && hasLoadedOnce;
653
- const planToDisplay = displayPlan ?? effectivePlanName;
994
+ const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
995
+ const planToDisplay = activePurchase?.productName || null;
996
+ const shouldShow = !loading && planToDisplay !== null;
654
997
  if (children) {
655
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: children({ subscriptions, loading, displayPlan: planToDisplay, shouldShow }) });
998
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
656
999
  }
657
1000
  if (!shouldShow) {
658
1001
  return null;
659
1002
  }
660
- const computedClassName = typeof className === "function" ? className({ subscriptions }) : className;
1003
+ const computedClassName = typeof className === "function" ? className({ purchases }) : className;
661
1004
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
662
1005
  Component,
663
1006
  {
664
1007
  className: computedClassName,
665
1008
  "data-loading": loading,
666
- "data-has-subscription": !!primarySubscription,
1009
+ "data-has-purchase": !!activePurchase,
1010
+ "data-has-paid-purchase": hasPaidPurchase,
667
1011
  role: "status",
668
1012
  "aria-live": "polite",
669
1013
  "aria-busy": loading,
670
- "aria-label": `Current plan: ${planToDisplay}`,
1014
+ "aria-label": `Current product: ${planToDisplay}`,
671
1015
  children: planToDisplay
672
1016
  }
673
1017
  );
674
1018
  };
1019
+ var PlanBadge = ProductBadge;
675
1020
 
676
- // src/components/SubscriptionGate.tsx
1021
+ // src/components/PurchaseGate.tsx
677
1022
  var import_jsx_runtime6 = require("react/jsx-runtime");
678
- var SubscriptionGate = ({
679
- requirePlan,
680
- children
681
- }) => {
682
- const { subscriptions, loading, hasPlan } = useSubscription();
683
- const hasAccess = requirePlan ? hasPlan(requirePlan) : subscriptions.some((sub) => sub.status === "active");
1023
+ var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
1024
+ const { purchases, loading, hasProduct } = usePurchase();
1025
+ const productToCheck = requireProduct || requirePlan;
1026
+ const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
684
1027
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: children({
685
1028
  hasAccess,
686
- subscriptions,
1029
+ purchases,
687
1030
  loading
688
1031
  }) });
689
1032
  };
690
1033
 
691
- // src/components/PlanSelector.tsx
692
- var import_react8 = require("react");
1034
+ // src/components/PricingSelector.tsx
1035
+ var import_react7 = require("react");
693
1036
 
694
1037
  // src/hooks/usePlans.ts
695
- var import_react7 = require("react");
1038
+ var import_react6 = require("react");
1039
+ var plansCache = /* @__PURE__ */ new Map();
1040
+ var CACHE_DURATION2 = 5 * 60 * 1e3;
696
1041
  function usePlans(options) {
697
- const { fetcher, agentRef, filter, sortBy, autoSelectFirstPaid = false } = options;
698
- const [plans, setPlans] = (0, import_react7.useState)([]);
699
- const [loading, setLoading] = (0, import_react7.useState)(true);
700
- const [error, setError] = (0, import_react7.useState)(null);
701
- const [selectedPlanIndex, setSelectedPlanIndex] = (0, import_react7.useState)(0);
702
- const fetchPlans = (0, import_react7.useCallback)(async () => {
703
- if (!agentRef) {
704
- setError(new Error("Agent reference not configured"));
705
- setLoading(false);
706
- return;
707
- }
708
- try {
709
- setLoading(true);
710
- setError(null);
711
- const fetchedPlans = await fetcher(agentRef);
712
- let processedPlans = filter ? fetchedPlans.filter(filter) : fetchedPlans;
713
- if (sortBy) {
714
- processedPlans = [...processedPlans].sort(sortBy);
715
- }
716
- setPlans(processedPlans);
717
- if (autoSelectFirstPaid && processedPlans.length > 0) {
718
- const firstPaidIndex = processedPlans.findIndex(
719
- (plan) => plan.price && plan.price > 0
720
- );
721
- setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1042
+ const { fetcher, productRef, filter, sortBy, autoSelectFirstPaid = false } = options;
1043
+ const [plans, setPlans] = (0, import_react6.useState)([]);
1044
+ const [loading, setLoading] = (0, import_react6.useState)(true);
1045
+ const [error, setError] = (0, import_react6.useState)(null);
1046
+ const [selectedPlanIndex, setSelectedPlanIndex] = (0, import_react6.useState)(0);
1047
+ const fetcherRef = (0, import_react6.useRef)(fetcher);
1048
+ const filterRef = (0, import_react6.useRef)(filter);
1049
+ const sortByRef = (0, import_react6.useRef)(sortBy);
1050
+ const autoSelectFirstPaidRef = (0, import_react6.useRef)(autoSelectFirstPaid);
1051
+ (0, import_react6.useEffect)(() => {
1052
+ fetcherRef.current = fetcher;
1053
+ }, [fetcher]);
1054
+ (0, import_react6.useEffect)(() => {
1055
+ filterRef.current = filter;
1056
+ }, [filter]);
1057
+ (0, import_react6.useEffect)(() => {
1058
+ sortByRef.current = sortBy;
1059
+ }, [sortBy]);
1060
+ (0, import_react6.useEffect)(() => {
1061
+ autoSelectFirstPaidRef.current = autoSelectFirstPaid;
1062
+ }, [autoSelectFirstPaid]);
1063
+ const fetchPlans = (0, import_react6.useCallback)(
1064
+ async (force = false) => {
1065
+ if (!productRef) {
1066
+ setError(new Error("Product reference not configured"));
1067
+ setLoading(false);
1068
+ return;
722
1069
  }
723
- } catch (err) {
724
- setError(err instanceof Error ? err : new Error("Failed to load plans"));
725
- } finally {
726
- setLoading(false);
727
- }
728
- }, [agentRef, fetcher, filter, sortBy, autoSelectFirstPaid]);
729
- (0, import_react7.useEffect)(() => {
1070
+ const cached = plansCache.get(productRef);
1071
+ const now = Date.now();
1072
+ if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
1073
+ const cachedPlans = cached.plans;
1074
+ let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
1075
+ if (sortByRef.current) {
1076
+ processedPlans = [...processedPlans].sort(sortByRef.current);
1077
+ }
1078
+ setPlans(processedPlans);
1079
+ setLoading(false);
1080
+ setError(null);
1081
+ if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
1082
+ const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
1083
+ setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1084
+ }
1085
+ return;
1086
+ }
1087
+ if (cached?.promise) {
1088
+ try {
1089
+ setLoading(true);
1090
+ const fetchedPlans = await cached.promise;
1091
+ let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
1092
+ if (sortByRef.current) {
1093
+ processedPlans = [...processedPlans].sort(sortByRef.current);
1094
+ }
1095
+ setPlans(processedPlans);
1096
+ setError(null);
1097
+ if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
1098
+ const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
1099
+ setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1100
+ }
1101
+ } catch (err) {
1102
+ setError(err instanceof Error ? err : new Error("Failed to load plans"));
1103
+ } finally {
1104
+ setLoading(false);
1105
+ }
1106
+ return;
1107
+ }
1108
+ try {
1109
+ setLoading(true);
1110
+ setError(null);
1111
+ const fetchPromise = fetcherRef.current(productRef);
1112
+ plansCache.set(productRef, {
1113
+ plans: [],
1114
+ timestamp: now,
1115
+ promise: fetchPromise
1116
+ });
1117
+ const fetchedPlans = await fetchPromise;
1118
+ plansCache.set(productRef, {
1119
+ plans: fetchedPlans,
1120
+ timestamp: now,
1121
+ promise: null
1122
+ });
1123
+ let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
1124
+ if (sortByRef.current) {
1125
+ processedPlans = [...processedPlans].sort(sortByRef.current);
1126
+ }
1127
+ setPlans(processedPlans);
1128
+ if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
1129
+ const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
1130
+ setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1131
+ }
1132
+ } catch (err) {
1133
+ plansCache.delete(productRef);
1134
+ setError(err instanceof Error ? err : new Error("Failed to load plans"));
1135
+ } finally {
1136
+ setLoading(false);
1137
+ }
1138
+ },
1139
+ [productRef]
1140
+ );
1141
+ (0, import_react6.useEffect)(() => {
730
1142
  fetchPlans();
731
1143
  }, [fetchPlans]);
732
- const selectedPlan = (0, import_react7.useMemo)(() => {
1144
+ const selectedPlan = (0, import_react6.useMemo)(() => {
733
1145
  return plans[selectedPlanIndex] || null;
734
1146
  }, [plans, selectedPlanIndex]);
735
- const selectPlan = (0, import_react7.useCallback)((planRef) => {
736
- const index = plans.findIndex((p) => p.reference === planRef);
737
- if (index >= 0) {
738
- setSelectedPlanIndex(index);
739
- }
740
- }, [plans]);
1147
+ const selectPlan = (0, import_react6.useCallback)(
1148
+ (planRef) => {
1149
+ const index = plans.findIndex((p) => p.reference === planRef);
1150
+ if (index >= 0) {
1151
+ setSelectedPlanIndex(index);
1152
+ }
1153
+ },
1154
+ [plans]
1155
+ );
741
1156
  return {
742
1157
  plans,
743
1158
  loading,
@@ -746,78 +1161,79 @@ function usePlans(options) {
746
1161
  selectedPlan,
747
1162
  setSelectedPlanIndex,
748
1163
  selectPlan,
749
- refetch: fetchPlans
1164
+ refetch: () => fetchPlans(true)
1165
+ // Force refetch
750
1166
  };
751
1167
  }
752
1168
 
753
- // src/components/PlanSelector.tsx
1169
+ // src/components/PricingSelector.tsx
754
1170
  var import_jsx_runtime7 = require("react/jsx-runtime");
755
- var PlanSelector = ({
756
- agentRef,
1171
+ var PricingSelector = ({
1172
+ productRef,
757
1173
  fetcher,
758
1174
  filter,
759
1175
  sortBy,
760
1176
  autoSelectFirstPaid,
761
1177
  children
762
1178
  }) => {
763
- const { subscriptions } = useSubscription();
1179
+ const { purchases } = usePurchase();
764
1180
  const plansHook = usePlans({
765
- agentRef,
1181
+ productRef,
766
1182
  fetcher,
767
1183
  filter,
768
1184
  sortBy,
769
1185
  autoSelectFirstPaid
770
1186
  });
771
1187
  const { plans } = plansHook;
772
- const isPaidPlan = (0, import_react8.useCallback)((planName) => {
773
- const plan = plans.find((p) => p.name === planName);
774
- return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
775
- }, [plans]);
776
- const activeSubscription = (0, import_react8.useMemo)(() => {
777
- const activeSubs = subscriptions.filter((sub) => sub.status === "active");
778
- return activeSubs.sort(
1188
+ const isPaidPlan = (0, import_react7.useCallback)(
1189
+ (planRef) => {
1190
+ const plan = plans.find((p) => p.reference === planRef);
1191
+ return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
1192
+ },
1193
+ [plans]
1194
+ );
1195
+ const activePurchase = (0, import_react7.useMemo)(() => {
1196
+ const activePurchases = purchases.filter((p) => p.status === "active");
1197
+ return activePurchases.sort(
779
1198
  (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
780
1199
  )[0] || null;
781
- }, [subscriptions]);
782
- const isCurrentPlan = (0, import_react8.useCallback)((planName) => {
783
- return activeSubscription?.planName === planName;
784
- }, [activeSubscription]);
1200
+ }, [purchases]);
1201
+ const isCurrentPlan = (0, import_react7.useCallback)(
1202
+ (planRef) => {
1203
+ return activePurchase?.planSnapshot?.reference === planRef;
1204
+ },
1205
+ [activePurchase]
1206
+ );
785
1207
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: children({
786
1208
  ...plansHook,
787
- subscriptions,
1209
+ purchases,
788
1210
  isPaidPlan,
789
1211
  isCurrentPlan
790
1212
  }) });
791
1213
  };
1214
+ var PlanSelector = PricingSelector;
792
1215
 
793
- // src/hooks/useSubscriptionHelpers.ts
794
- var import_react9 = require("react");
795
- function useSubscriptionHelpers(plans) {
796
- const { subscriptions } = useSubscription();
797
- const isPaidPlan = (0, import_react9.useCallback)((planName) => {
798
- const plan = plans.find((p) => p.name === planName);
799
- return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
800
- }, [plans]);
801
- const subscriptionData = (0, import_react9.useMemo)(() => {
802
- const activePaidSubscriptions = subscriptions.filter(
803
- (sub) => sub.status === "active" && isPaidPlan(sub.planName)
804
- );
805
- const activePaidSubscription = activePaidSubscriptions.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0] || null;
806
- const cancelledPaidSubscriptions = subscriptions.filter(
807
- (sub) => sub.status === "cancelled" && isPaidPlan(sub.planName)
808
- );
809
- const cancelledSubscription = cancelledPaidSubscriptions.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0] || null;
810
- const hasActiveSubscription = subscriptions.some((sub) => sub.status === "active");
811
- const shouldShowCancelledNotice = !!(cancelledSubscription && (!hasActiveSubscription || cancelledSubscription.endDate));
1216
+ // src/hooks/usePurchaseStatus.ts
1217
+ var import_react8 = require("react");
1218
+ function usePurchaseStatus() {
1219
+ const { purchases } = usePurchase();
1220
+ const isPaidPurchase2 = (0, import_react8.useCallback)((p) => {
1221
+ return (p.amount ?? 0) > 0;
1222
+ }, []);
1223
+ const purchaseData = (0, import_react8.useMemo)(() => {
1224
+ const cancelledPaidPurchases = purchases.filter((p) => {
1225
+ return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
1226
+ });
1227
+ const cancelledPurchase = cancelledPaidPurchases.sort(
1228
+ (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
1229
+ )[0] || null;
1230
+ const shouldShowCancelledNotice = !!cancelledPurchase;
812
1231
  return {
813
- activePaidSubscription,
814
- activePlanName: activePaidSubscription?.planName || null,
815
- cancelledSubscription,
816
- hasPaidSubscription: activePaidSubscriptions.length > 0,
1232
+ cancelledPurchase,
817
1233
  shouldShowCancelledNotice
818
1234
  };
819
- }, [subscriptions, isPaidPlan]);
820
- const formatDate = (0, import_react9.useCallback)((dateString) => {
1235
+ }, [purchases, isPaidPurchase2]);
1236
+ const formatDate = (0, import_react8.useCallback)((dateString) => {
821
1237
  if (!dateString) return null;
822
1238
  return new Date(dateString).toLocaleDateString("en-US", {
823
1239
  year: "numeric",
@@ -825,7 +1241,7 @@ function useSubscriptionHelpers(plans) {
825
1241
  day: "numeric"
826
1242
  });
827
1243
  }, []);
828
- const getDaysUntilExpiration = (0, import_react9.useCallback)((endDate) => {
1244
+ const getDaysUntilExpiration = (0, import_react8.useCallback)((endDate) => {
829
1245
  if (!endDate) return null;
830
1246
  const now = /* @__PURE__ */ new Date();
831
1247
  const expiration = new Date(endDate);
@@ -834,12 +1250,8 @@ function useSubscriptionHelpers(plans) {
834
1250
  return diffDays > 0 ? diffDays : 0;
835
1251
  }, []);
836
1252
  return {
837
- isPaidPlan,
838
- activePaidSubscription: subscriptionData.activePaidSubscription,
839
- cancelledSubscription: subscriptionData.cancelledSubscription,
840
- hasPaidSubscription: subscriptionData.hasPaidSubscription,
841
- shouldShowCancelledNotice: subscriptionData.shouldShowCancelledNotice,
842
- activePlanName: subscriptionData.activePlanName,
1253
+ cancelledPurchase: purchaseData.cancelledPurchase,
1254
+ shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
843
1255
  formatDate,
844
1256
  getDaysUntilExpiration
845
1257
  };
@@ -849,19 +1261,23 @@ function useSubscriptionHelpers(plans) {
849
1261
  PaymentForm,
850
1262
  PlanBadge,
851
1263
  PlanSelector,
1264
+ PricingSelector,
1265
+ ProductBadge,
1266
+ PurchaseGate,
852
1267
  SolvaPayProvider,
853
1268
  Spinner,
854
1269
  StripePaymentFormWrapper,
855
- SubscriptionGate,
856
- filterSubscriptions,
857
- getActiveSubscriptions,
858
- getCancelledSubscriptionsWithEndDate,
859
- getMostRecentSubscription,
860
- getPrimarySubscription,
861
- hasActivePaidSubscription,
1270
+ defaultAuthAdapter,
1271
+ filterPurchases,
1272
+ getActivePurchases,
1273
+ getCancelledPurchasesWithEndDate,
1274
+ getMostRecentPurchase,
1275
+ getPrimaryPurchase,
1276
+ isPaidPurchase,
862
1277
  useCheckout,
1278
+ useCustomer,
863
1279
  usePlans,
864
- useSolvaPay,
865
- useSubscription,
866
- useSubscriptionHelpers
1280
+ usePurchase,
1281
+ usePurchaseStatus,
1282
+ useSolvaPay
867
1283
  });