@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/CLAUDE.md +45 -0
- package/README.md +105 -0
- package/dist/chunk-LJAXLA7O.js +301 -0
- package/dist/chunk-LJAXLA7O.js.map +1 -0
- package/dist/index.cjs +736 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +392 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +481 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +24 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.js +166 -0
- package/dist/server.js.map +1 -0
- package/dist/types-PiTA3r6t.d.cts +233 -0
- package/dist/types-PiTA3r6t.d.ts +233 -0
- package/package.json +53 -0
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
|