@usethrottle/subscriptions 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,736 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DunningBanner: () => DunningBanner,
24
+ SubscriptionProvider: () => SubscriptionProvider,
25
+ SubscriptionProviderWithClient: () => SubscriptionProviderWithClient,
26
+ SubscriptionStatusBadge: () => SubscriptionStatusBadge,
27
+ SubscriptionsClient: () => SubscriptionsClient,
28
+ ThrottleSubscriptionsError: () => ThrottleSubscriptionsError,
29
+ TrialCountdown: () => TrialCountdown,
30
+ canCancelSubscription: () => canCancelSubscription,
31
+ canPauseSubscription: () => canPauseSubscription,
32
+ canResumeSubscription: () => canResumeSubscription,
33
+ createSubscriptionFetcher: () => createSubscriptionFetcher,
34
+ createSubscriptionsClient: () => createSubscriptionsClient,
35
+ daysUntil: () => daysUntil,
36
+ isSubscriptionPastDue: () => isSubscriptionPastDue,
37
+ isSubscriptionTrialing: () => isSubscriptionTrialing,
38
+ isTerminalSubscription: () => isTerminalSubscription,
39
+ useCancelSubscription: () => useCancelSubscription,
40
+ useChangePlan: () => useChangePlan,
41
+ useCreateCheckoutSession: () => useCreateCheckoutSession,
42
+ useCreateSubscription: () => useCreateSubscription,
43
+ useCustomerByExternalId: () => useCustomerByExternalId,
44
+ usePauseSubscription: () => usePauseSubscription,
45
+ useResumeSubscription: () => useResumeSubscription,
46
+ useSubscription: () => useSubscription,
47
+ useSubscriptionClient: () => useSubscriptionClient,
48
+ useSubscriptionInvalidation: () => useSubscriptionInvalidation,
49
+ useSubscriptions: () => useSubscriptions,
50
+ useTrialEligibility: () => useTrialEligibility,
51
+ useUpdateSubscription: () => useUpdateSubscription
52
+ });
53
+ module.exports = __toCommonJS(index_exports);
54
+
55
+ // src/errors.ts
56
+ var ThrottleSubscriptionsError = class extends Error {
57
+ code;
58
+ statusCode;
59
+ details;
60
+ constructor(args) {
61
+ super(args.message);
62
+ this.name = "ThrottleSubscriptionsError";
63
+ this.code = args.code;
64
+ this.statusCode = args.statusCode;
65
+ this.details = args.details;
66
+ }
67
+ };
68
+
69
+ // src/normalize.ts
70
+ function normalizeSubscription(raw) {
71
+ return {
72
+ id: requiredString(raw?.id, "subscription.id"),
73
+ merchantId: optionalString(raw?.merchantId ?? raw?.merchant_id),
74
+ storeId: optionalString(raw?.storeId ?? raw?.store_id),
75
+ customerId: requiredString(raw?.customerId ?? raw?.customer_id, "subscription.customerId"),
76
+ status: raw?.status ?? "active",
77
+ planReference: requiredString(
78
+ raw?.planReference ?? raw?.plan_reference,
79
+ "subscription.planReference"
80
+ ),
81
+ planName: optionalString(raw?.planName ?? raw?.plan_name),
82
+ interval: raw?.interval ?? "monthly",
83
+ amount: Number(raw?.amount ?? 0),
84
+ currency: optionalString(raw?.currency) ?? "USD",
85
+ currentPeriodStart: requiredDateString(
86
+ raw?.currentPeriodStart ?? raw?.current_period_start,
87
+ "subscription.currentPeriodStart"
88
+ ),
89
+ currentPeriodEnd: requiredDateString(
90
+ raw?.currentPeriodEnd ?? raw?.current_period_end,
91
+ "subscription.currentPeriodEnd"
92
+ ),
93
+ trialEnd: optionalDateString(raw?.trialEnd ?? raw?.trial_end),
94
+ cancelAtPeriodEnd: Boolean(raw?.cancelAtPeriodEnd ?? raw?.cancel_at_period_end ?? false),
95
+ failureCount: Number(raw?.failureCount ?? raw?.failure_count ?? 0),
96
+ lastPaymentAt: optionalDateString(raw?.lastPaymentAt ?? raw?.last_payment_at),
97
+ pausedAt: optionalDateString(raw?.pausedAt ?? raw?.paused_at),
98
+ cancelledAt: optionalDateString(raw?.cancelledAt ?? raw?.cancelled_at),
99
+ metadata: objectOrEmpty(raw?.metadata),
100
+ createdAt: optionalDateString(raw?.createdAt ?? raw?.created_at) ?? void 0,
101
+ updatedAt: optionalDateString(raw?.updatedAt ?? raw?.updated_at) ?? void 0
102
+ };
103
+ }
104
+ function normalizeCustomer(raw) {
105
+ if (!raw) return null;
106
+ return {
107
+ id: requiredString(raw?.id, "customer.id"),
108
+ externalId: optionalString(raw?.externalId ?? raw?.external_id),
109
+ email: optionalString(raw?.email),
110
+ firstName: optionalString(raw?.firstName ?? raw?.first_name),
111
+ lastName: optionalString(raw?.lastName ?? raw?.last_name),
112
+ phone: optionalString(raw?.phone),
113
+ metadata: objectOrEmpty(raw?.metadata),
114
+ createdAt: optionalDateString(raw?.createdAt ?? raw?.created_at) ?? void 0,
115
+ updatedAt: optionalDateString(raw?.updatedAt ?? raw?.updated_at) ?? void 0
116
+ };
117
+ }
118
+ function normalizeCheckoutSession(raw) {
119
+ const sessionId = raw?.sessionId ?? raw?.session_id ?? raw?.id;
120
+ return {
121
+ ...raw,
122
+ id: optionalString(raw?.id),
123
+ sessionId: requiredString(sessionId, "checkoutSession.sessionId"),
124
+ checkoutUrl: optionalString(raw?.checkoutUrl ?? raw?.checkout_url),
125
+ embedToken: optionalString(raw?.embedToken ?? raw?.embed_token),
126
+ expiresAt: optionalDateString(raw?.expiresAt ?? raw?.expires_at) ?? void 0
127
+ };
128
+ }
129
+ function normalizeTrialEligibility(raw) {
130
+ return {
131
+ eligible: Boolean(raw?.eligible),
132
+ reason: raw?.reason
133
+ };
134
+ }
135
+ function normalizePagination(raw) {
136
+ if (!raw) return void 0;
137
+ return {
138
+ cursor: raw.cursor ?? null,
139
+ hasMore: Boolean(raw.hasMore ?? raw.has_more ?? false)
140
+ };
141
+ }
142
+ function toIso(value) {
143
+ if (value === void 0) return void 0;
144
+ return value instanceof Date ? value.toISOString() : value;
145
+ }
146
+ function requiredString(value, label) {
147
+ if (typeof value === "string" && value.length > 0) return value;
148
+ throw new Error(`Invalid Throttle response: missing ${label}`);
149
+ }
150
+ function optionalString(value) {
151
+ return typeof value === "string" ? value : null;
152
+ }
153
+ function requiredDateString(value, label) {
154
+ if (value instanceof Date) return value.toISOString();
155
+ if (typeof value === "string" && value.length > 0) return value;
156
+ throw new Error(`Invalid Throttle response: missing ${label}`);
157
+ }
158
+ function optionalDateString(value) {
159
+ if (value instanceof Date) return value.toISOString();
160
+ if (typeof value === "string" && value.length > 0) return value;
161
+ return null;
162
+ }
163
+ function objectOrEmpty(value) {
164
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
165
+ }
166
+
167
+ // src/client.ts
168
+ var SubscriptionsClient = class {
169
+ apiKey;
170
+ baseUrl;
171
+ fetchImpl;
172
+ fetcher;
173
+ timeoutMs;
174
+ constructor(opts = {}) {
175
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
176
+ if (!fetchImpl) throw new Error("SubscriptionsClient: fetch is required");
177
+ if (!opts.apiKey && !opts.fetcher) {
178
+ throw new Error("SubscriptionsClient: apiKey or fetcher is required");
179
+ }
180
+ this.apiKey = opts.apiKey;
181
+ this.baseUrl = (opts.baseUrl ?? "https://api.usethrottle.dev").replace(/\/+$/, "");
182
+ this.fetchImpl = fetchImpl;
183
+ this.fetcher = opts.fetcher;
184
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
185
+ }
186
+ async request(method, path, body) {
187
+ const envelope = await this.requestEnvelope(method, path, body);
188
+ return envelope.data;
189
+ }
190
+ async requestEnvelope(method, path, body) {
191
+ const ctrl = new AbortController();
192
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
193
+ try {
194
+ const init = {
195
+ method,
196
+ headers: {
197
+ "content-type": "application/json",
198
+ ...this.apiKey ? { "x-api-key": this.apiKey } : {}
199
+ },
200
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
201
+ signal: ctrl.signal
202
+ };
203
+ const res = this.fetcher ? await this.fetcher(path, init) : await this.fetchImpl(toAbsoluteUrl(this.baseUrl, path), init);
204
+ if (res.status === 204) return { data: void 0 };
205
+ const json = await res.json().catch(() => ({}));
206
+ if (!res.ok) {
207
+ throw new ThrottleSubscriptionsError({
208
+ code: json.error?.code ?? "unknown_error",
209
+ message: json.error?.message ?? `HTTP ${res.status}`,
210
+ statusCode: res.status,
211
+ details: json.error?.details
212
+ });
213
+ }
214
+ return json;
215
+ } finally {
216
+ clearTimeout(timer);
217
+ }
218
+ }
219
+ async create(input) {
220
+ const customerId = await this.resolveCustomerId(input.customerId, input.externalCustomerId);
221
+ const data = await this.request("POST", "/api/v1/subscriptions", {
222
+ storeId: input.storeId,
223
+ customerId,
224
+ planReference: input.planReference,
225
+ planName: input.planName,
226
+ interval: input.interval,
227
+ amount: input.amount,
228
+ currency: input.currency,
229
+ currentPeriodStart: toIso(input.currentPeriodStart),
230
+ currentPeriodEnd: toIso(input.currentPeriodEnd),
231
+ trialEnd: toIso(input.trialEnd),
232
+ metadata: input.metadata ?? {}
233
+ });
234
+ return normalizeSubscription(data);
235
+ }
236
+ async list(params = {}) {
237
+ const query = new URLSearchParams();
238
+ setQuery(query, "customerId", params.customerId);
239
+ setQuery(query, "externalCustomerId", params.externalCustomerId);
240
+ setQuery(query, "status", params.status);
241
+ setQuery(query, "interval", params.interval);
242
+ setQuery(query, "q", params.q);
243
+ setQuery(query, "cursor", params.cursor);
244
+ setQuery(query, "limit", params.limit);
245
+ setQuery(query, "sort", params.sort);
246
+ setQuery(query, "createdAfter", toIso(params.createdAfter));
247
+ setQuery(query, "createdBefore", toIso(params.createdBefore));
248
+ const suffix = query.toString() ? `?${query}` : "";
249
+ const envelope = await this.requestEnvelope("GET", `/api/v1/subscriptions${suffix}`);
250
+ return {
251
+ data: (envelope.data ?? []).map(normalizeSubscription),
252
+ pagination: normalizePagination(envelope.meta?.pagination)
253
+ };
254
+ }
255
+ async get(id) {
256
+ const data = await this.request(
257
+ "GET",
258
+ `/api/v1/subscriptions/${encodeURIComponent(id)}`
259
+ );
260
+ return normalizeSubscription(data);
261
+ }
262
+ async update(id, input) {
263
+ const data = await this.request(
264
+ "PATCH",
265
+ `/api/v1/subscriptions/${encodeURIComponent(id)}`,
266
+ input
267
+ );
268
+ return normalizeSubscription(data);
269
+ }
270
+ changePlan(input) {
271
+ const { id, ...body } = input;
272
+ return this.update(id, body);
273
+ }
274
+ async pause(id) {
275
+ const data = await this.request(
276
+ "POST",
277
+ `/api/v1/subscriptions/${encodeURIComponent(id)}/pause`
278
+ );
279
+ return normalizeSubscription(data);
280
+ }
281
+ async resume(id) {
282
+ const data = await this.request(
283
+ "POST",
284
+ `/api/v1/subscriptions/${encodeURIComponent(id)}/resume`
285
+ );
286
+ return normalizeSubscription(data);
287
+ }
288
+ async cancel(id, input = {}) {
289
+ const data = await this.request(
290
+ "POST",
291
+ `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`,
292
+ { atPeriodEnd: input.atPeriodEnd ?? false }
293
+ );
294
+ return normalizeSubscription(data);
295
+ }
296
+ async createCheckoutSession(input) {
297
+ const data = await this.request("POST", "/api/v1/checkout/sessions", input);
298
+ return normalizeCheckoutSession(data);
299
+ }
300
+ async checkTrialEligibility(input) {
301
+ const data = await this.request("POST", "/api/v1/subscriptions/eligibility-check", {
302
+ payment_method_id: input.paymentMethodId
303
+ });
304
+ return normalizeTrialEligibility(data);
305
+ }
306
+ async getCustomerByExternalId(externalId) {
307
+ try {
308
+ const data = await this.request(
309
+ "GET",
310
+ `/api/v1/customers/by-external/${encodeURIComponent(externalId)}`
311
+ );
312
+ return normalizeCustomer(data);
313
+ } catch (err) {
314
+ if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;
315
+ throw err;
316
+ }
317
+ }
318
+ async resolveCustomerId(customerId, externalCustomerId) {
319
+ if (customerId) return customerId;
320
+ if (!externalCustomerId) {
321
+ throw new ThrottleSubscriptionsError({
322
+ code: "invalid_request",
323
+ message: "customerId or externalCustomerId is required",
324
+ statusCode: 400
325
+ });
326
+ }
327
+ const customer = await this.getCustomerByExternalId(externalCustomerId);
328
+ if (!customer) {
329
+ throw new ThrottleSubscriptionsError({
330
+ code: "customer_not_found",
331
+ message: "Customer not found",
332
+ statusCode: 404
333
+ });
334
+ }
335
+ return customer.id;
336
+ }
337
+ };
338
+ function createSubscriptionsClient(opts) {
339
+ return new SubscriptionsClient(opts);
340
+ }
341
+ function toAbsoluteUrl(baseUrl, path) {
342
+ if (/^https?:\/\//i.test(path)) return path;
343
+ return `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
344
+ }
345
+ function setQuery(query, key, value) {
346
+ if (value === void 0 || value === null || value === "") return;
347
+ query.set(key, String(value));
348
+ }
349
+
350
+ // src/provider.tsx
351
+ var import_react = require("react");
352
+ var import_jsx_runtime = require("react/jsx-runtime");
353
+ var SubscriptionContext = (0, import_react.createContext)(null);
354
+ function SubscriptionProvider({
355
+ children,
356
+ client,
357
+ fetcher,
358
+ baseUrl,
359
+ timeoutMs
360
+ }) {
361
+ const [cacheVersion, invalidate] = (0, import_react.useReducer)((value2) => value2 + 1, 0);
362
+ const resolvedClient = (0, import_react.useMemo)(() => {
363
+ if (client) return client;
364
+ return new SubscriptionsClient({
365
+ fetcher: fetcher ?? createSubscriptionFetcher("/api/throttle"),
366
+ baseUrl,
367
+ timeoutMs
368
+ });
369
+ }, [baseUrl, client, fetcher, timeoutMs]);
370
+ const value = (0, import_react.useMemo)(
371
+ () => ({ client: resolvedClient, cacheVersion, invalidate }),
372
+ [cacheVersion, resolvedClient]
373
+ );
374
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubscriptionContext.Provider, { value, children });
375
+ }
376
+ function useSubscriptionClient() {
377
+ return useSubscriptionContext().client;
378
+ }
379
+ function useSubscriptionInvalidation() {
380
+ return useSubscriptionContext().invalidate;
381
+ }
382
+ function useSubscriptionCacheVersion() {
383
+ return useSubscriptionContext().cacheVersion;
384
+ }
385
+ function createSubscriptionFetcher(basePath) {
386
+ const cleanBase = basePath.replace(/\/+$/, "");
387
+ return (path, init) => {
388
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
389
+ return fetch(`${cleanBase}${cleanPath}`, init);
390
+ };
391
+ }
392
+ function useSubscriptionContext() {
393
+ const ctx = (0, import_react.useContext)(SubscriptionContext);
394
+ if (!ctx) {
395
+ throw new Error(
396
+ "@usethrottle/subscriptions hooks must be used inside <SubscriptionProvider />"
397
+ );
398
+ }
399
+ return ctx;
400
+ }
401
+ function SubscriptionProviderWithClient({
402
+ children,
403
+ client
404
+ }) {
405
+ const [cacheVersion, invalidate] = (0, import_react.useReducer)((value2) => value2 + 1, 0);
406
+ const value = (0, import_react.useMemo)(() => ({ client, cacheVersion, invalidate }), [cacheVersion, client]);
407
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubscriptionContext.Provider, { value, children });
408
+ }
409
+
410
+ // src/hooks.ts
411
+ var import_react2 = require("react");
412
+ function useSubscriptions(params = {}, options = {}) {
413
+ const client = useSubscriptionClient();
414
+ const cacheVersion = useSubscriptionCacheVersion();
415
+ const stableParams = useStableValue(params);
416
+ return useAsyncQuery(
417
+ () => client.list(stableParams),
418
+ [client, stableParams, cacheVersion],
419
+ options
420
+ );
421
+ }
422
+ function useSubscription(id, options = {}) {
423
+ const client = useSubscriptionClient();
424
+ const cacheVersion = useSubscriptionCacheVersion();
425
+ return useAsyncQuery(
426
+ () => id ? client.get(id) : Promise.resolve(null),
427
+ [client, id, cacheVersion],
428
+ { ...options, enabled: options.enabled ?? Boolean(id) }
429
+ );
430
+ }
431
+ function useCustomerByExternalId(externalId, options = {}) {
432
+ const client = useSubscriptionClient();
433
+ const cacheVersion = useSubscriptionCacheVersion();
434
+ return useAsyncQuery(
435
+ () => externalId ? client.getCustomerByExternalId(externalId) : Promise.resolve(null),
436
+ [client, externalId, cacheVersion],
437
+ { ...options, enabled: options.enabled ?? Boolean(externalId) }
438
+ );
439
+ }
440
+ function useTrialEligibility() {
441
+ const client = useSubscriptionClient();
442
+ return useAsyncMutation((input) => client.checkTrialEligibility(input));
443
+ }
444
+ function useCreateSubscription() {
445
+ const client = useSubscriptionClient();
446
+ const invalidate = useSubscriptionInvalidation();
447
+ return useAsyncMutation((input) => client.create(input), invalidate);
448
+ }
449
+ function useUpdateSubscription() {
450
+ const client = useSubscriptionClient();
451
+ const invalidate = useSubscriptionInvalidation();
452
+ return useAsyncMutation(({ id, data }) => client.update(id, data), invalidate);
453
+ }
454
+ function useChangePlan() {
455
+ const client = useSubscriptionClient();
456
+ const invalidate = useSubscriptionInvalidation();
457
+ return useAsyncMutation((input) => client.changePlan(input), invalidate);
458
+ }
459
+ function useCancelSubscription() {
460
+ const client = useSubscriptionClient();
461
+ const invalidate = useSubscriptionInvalidation();
462
+ return useAsyncMutation(
463
+ (input) => client.cancel(input.id, { atPeriodEnd: input.atPeriodEnd }),
464
+ invalidate
465
+ );
466
+ }
467
+ function usePauseSubscription() {
468
+ const client = useSubscriptionClient();
469
+ const invalidate = useSubscriptionInvalidation();
470
+ return useAsyncMutation((input) => client.pause(input.id), invalidate);
471
+ }
472
+ function useResumeSubscription() {
473
+ const client = useSubscriptionClient();
474
+ const invalidate = useSubscriptionInvalidation();
475
+ return useAsyncMutation((input) => client.resume(input.id), invalidate);
476
+ }
477
+ function useCreateCheckoutSession() {
478
+ const client = useSubscriptionClient();
479
+ return useAsyncMutation((input) => client.createCheckoutSession(input));
480
+ }
481
+ function useAsyncQuery(load, deps, options) {
482
+ const enabled = options.enabled ?? true;
483
+ const [data, setData] = (0, import_react2.useState)(options.initialData ?? null);
484
+ const [error, setError] = (0, import_react2.useState)(null);
485
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(enabled && options.initialData === void 0);
486
+ const [isFetching, setIsFetching] = (0, import_react2.useState)(false);
487
+ const loadRef = (0, import_react2.useRef)(load);
488
+ loadRef.current = load;
489
+ const refetch = (0, import_react2.useCallback)(async () => {
490
+ if (!enabled) return data;
491
+ setIsFetching(true);
492
+ setError(null);
493
+ try {
494
+ const next = await loadRef.current();
495
+ setData(next);
496
+ return next;
497
+ } catch (err) {
498
+ setError(err);
499
+ throw err;
500
+ } finally {
501
+ setIsFetching(false);
502
+ setIsLoading(false);
503
+ }
504
+ }, [data, enabled]);
505
+ (0, import_react2.useEffect)(() => {
506
+ if (!enabled) {
507
+ setIsLoading(false);
508
+ return;
509
+ }
510
+ let cancelled = false;
511
+ setIsFetching(true);
512
+ setError(null);
513
+ load().then((next) => {
514
+ if (!cancelled) setData(next);
515
+ }).catch((err) => {
516
+ if (!cancelled) setError(err);
517
+ }).finally(() => {
518
+ if (!cancelled) {
519
+ setIsLoading(false);
520
+ setIsFetching(false);
521
+ }
522
+ });
523
+ return () => {
524
+ cancelled = true;
525
+ };
526
+ }, [enabled, ...deps]);
527
+ return { data, error, isLoading, isFetching, refetch };
528
+ }
529
+ function useAsyncMutation(action, onSuccess) {
530
+ const [data, setData] = (0, import_react2.useState)(null);
531
+ const [error, setError] = (0, import_react2.useState)(null);
532
+ const [isPending, setIsPending] = (0, import_react2.useState)(false);
533
+ const mutateAsync = (0, import_react2.useCallback)(
534
+ async (variables) => {
535
+ setIsPending(true);
536
+ setError(null);
537
+ try {
538
+ const next = await action(variables);
539
+ setData(next);
540
+ onSuccess?.();
541
+ return next;
542
+ } catch (err) {
543
+ setError(err);
544
+ throw err;
545
+ } finally {
546
+ setIsPending(false);
547
+ }
548
+ },
549
+ [action, onSuccess]
550
+ );
551
+ const mutate = (0, import_react2.useCallback)(
552
+ (variables) => {
553
+ void mutateAsync(variables).catch(() => void 0);
554
+ },
555
+ [mutateAsync]
556
+ );
557
+ const reset = (0, import_react2.useCallback)(() => {
558
+ setData(null);
559
+ setError(null);
560
+ setIsPending(false);
561
+ }, []);
562
+ return { data, error, isPending, mutate, mutateAsync, reset };
563
+ }
564
+ function useStableValue(value) {
565
+ const key = JSON.stringify(value);
566
+ return (0, import_react2.useMemo)(() => value, [key]);
567
+ }
568
+
569
+ // src/helpers.ts
570
+ function isTerminalSubscription(status) {
571
+ return getStatus(status) === "cancelled";
572
+ }
573
+ function canPauseSubscription(status) {
574
+ return getStatus(status) === "active";
575
+ }
576
+ function canResumeSubscription(status) {
577
+ return getStatus(status) === "paused";
578
+ }
579
+ function canCancelSubscription(status) {
580
+ return getStatus(status) !== "cancelled";
581
+ }
582
+ function isSubscriptionPastDue(status) {
583
+ return getStatus(status) === "past_due";
584
+ }
585
+ function isSubscriptionTrialing(status) {
586
+ return getStatus(status) === "trialing";
587
+ }
588
+ function daysUntil(date, now = /* @__PURE__ */ new Date()) {
589
+ const end = date instanceof Date ? date : new Date(date);
590
+ const ms = end.getTime() - now.getTime();
591
+ if (!Number.isFinite(ms)) return 0;
592
+ return Math.max(0, Math.ceil(ms / 864e5));
593
+ }
594
+ function getStatus(input) {
595
+ return typeof input === "string" ? input : input.status;
596
+ }
597
+
598
+ // src/components.tsx
599
+ var import_jsx_runtime2 = require("react/jsx-runtime");
600
+ var STATUS_LABELS = {
601
+ active: "Active",
602
+ paused: "Paused",
603
+ cancelled: "Cancelled",
604
+ past_due: "Past due",
605
+ trialing: "Trialing"
606
+ };
607
+ var STATUS_STYLES = {
608
+ active: { color: "#035c4f", background: "#dff8ef", borderColor: "#a8e8d2" },
609
+ paused: { color: "#735c00", background: "#fff4ca", borderColor: "#f3d46b" },
610
+ cancelled: { color: "#6c2632", background: "#ffe1e7", borderColor: "#f4a9b8" },
611
+ past_due: { color: "#7a2d00", background: "#ffe8d6", borderColor: "#f5b47f" },
612
+ trialing: { color: "#124c73", background: "#dff1ff", borderColor: "#9ed3fb" }
613
+ };
614
+ function SubscriptionStatusBadge({
615
+ status,
616
+ className,
617
+ style,
618
+ labels
619
+ }) {
620
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
621
+ "span",
622
+ {
623
+ className,
624
+ style: {
625
+ display: "inline-flex",
626
+ alignItems: "center",
627
+ border: "1px solid",
628
+ borderRadius: 999,
629
+ padding: "2px 8px",
630
+ fontSize: 12,
631
+ fontWeight: 700,
632
+ lineHeight: 1.6,
633
+ whiteSpace: "nowrap",
634
+ ...STATUS_STYLES[status],
635
+ ...style
636
+ },
637
+ children: labels?.[status] ?? STATUS_LABELS[status]
638
+ }
639
+ );
640
+ }
641
+ function TrialCountdown({
642
+ endDate,
643
+ now,
644
+ className,
645
+ style,
646
+ expiredLabel = "Trial ended",
647
+ prefix = "Trial ends in"
648
+ }) {
649
+ const days = daysUntil(endDate, now);
650
+ const label = days <= 0 ? expiredLabel : `${prefix} ${days} ${days === 1 ? "day" : "days"}`;
651
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className, style, children: label });
652
+ }
653
+ function DunningBanner({
654
+ subscription,
655
+ onUpdateCard,
656
+ title = "Payment needs attention",
657
+ message,
658
+ actionLabel = "Update card",
659
+ className,
660
+ style
661
+ }) {
662
+ if (subscription.status !== "past_due") return null;
663
+ const fallbackMessage = subscription.failureCount > 0 ? `We could not renew this subscription after ${subscription.failureCount} failed payment attempt${subscription.failureCount === 1 ? "" : "s"}.` : "We could not renew this subscription with the saved payment method.";
664
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
665
+ "div",
666
+ {
667
+ role: "status",
668
+ className,
669
+ style: {
670
+ border: "1px solid #f5b47f",
671
+ borderRadius: 8,
672
+ background: "#fff7ed",
673
+ color: "#7a2d00",
674
+ padding: 16,
675
+ display: "grid",
676
+ gap: 10,
677
+ ...style
678
+ },
679
+ children: [
680
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: title }),
681
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: message ?? fallbackMessage }),
682
+ onUpdateCard ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
683
+ "button",
684
+ {
685
+ type: "button",
686
+ onClick: onUpdateCard,
687
+ style: {
688
+ width: "fit-content",
689
+ border: "1px solid #7a2d00",
690
+ borderRadius: 6,
691
+ background: "#7a2d00",
692
+ color: "#fff",
693
+ padding: "8px 12px",
694
+ fontWeight: 700,
695
+ cursor: "pointer"
696
+ },
697
+ children: actionLabel
698
+ }
699
+ ) : null
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ // Annotate the CommonJS export names for ESM import in node:
705
+ 0 && (module.exports = {
706
+ DunningBanner,
707
+ SubscriptionProvider,
708
+ SubscriptionProviderWithClient,
709
+ SubscriptionStatusBadge,
710
+ SubscriptionsClient,
711
+ ThrottleSubscriptionsError,
712
+ TrialCountdown,
713
+ canCancelSubscription,
714
+ canPauseSubscription,
715
+ canResumeSubscription,
716
+ createSubscriptionFetcher,
717
+ createSubscriptionsClient,
718
+ daysUntil,
719
+ isSubscriptionPastDue,
720
+ isSubscriptionTrialing,
721
+ isTerminalSubscription,
722
+ useCancelSubscription,
723
+ useChangePlan,
724
+ useCreateCheckoutSession,
725
+ useCreateSubscription,
726
+ useCustomerByExternalId,
727
+ usePauseSubscription,
728
+ useResumeSubscription,
729
+ useSubscription,
730
+ useSubscriptionClient,
731
+ useSubscriptionInvalidation,
732
+ useSubscriptions,
733
+ useTrialEligibility,
734
+ useUpdateSubscription
735
+ });
736
+ //# sourceMappingURL=index.cjs.map