@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/server.js ADDED
@@ -0,0 +1,166 @@
1
+ import {
2
+ SubscriptionsClient,
3
+ ThrottleSubscriptionsError
4
+ } from "./chunk-LJAXLA7O.js";
5
+
6
+ // src/server.ts
7
+ var ALLOWED_PATHS = [
8
+ /^\/api\/v1\/subscriptions(?:\/[\w.-]+)?(?:\/(?:pause|resume|cancel))?$/,
9
+ /^\/api\/v1\/subscriptions\/eligibility-check$/,
10
+ /^\/api\/v1\/customers\/by-external\/[\w.:%-]+$/,
11
+ /^\/api\/v1\/checkout\/sessions$/
12
+ ];
13
+ function createSubscriptionProxyHandler(options) {
14
+ return async function subscriptionProxyHandler(request, context = {}) {
15
+ const path = await pathFromContext(context);
16
+ return handleSubscriptionProxyRequest(request, path, options);
17
+ };
18
+ }
19
+ async function handleSubscriptionProxyRequest(request, pathParts, options) {
20
+ const externalCustomerId = await options.getExternalCustomerId(request);
21
+ if (!externalCustomerId) return jsonError(401, "unauthorized", "Unauthorized");
22
+ const path = normalizePath(pathParts);
23
+ if (!ALLOWED_PATHS.some((pattern) => pattern.test(path))) {
24
+ return jsonError(403, "forbidden", "Path is not allowed by the subscription proxy");
25
+ }
26
+ const method = request.method.toUpperCase();
27
+ if (!["GET", "POST", "PATCH"].includes(method)) {
28
+ return jsonError(405, "method_not_allowed", "Method not allowed");
29
+ }
30
+ const client = new SubscriptionsClient({
31
+ apiKey: options.apiKey,
32
+ baseUrl: options.baseUrl,
33
+ fetch: options.fetch
34
+ });
35
+ if (isSubscriptionMutation(path, method)) {
36
+ const subId = subscriptionIdFromPath(path);
37
+ if (subId) {
38
+ const subscription = await client.get(subId).catch((err) => {
39
+ if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;
40
+ throw err;
41
+ });
42
+ if (!subscription) return jsonError(404, "not_found", "Subscription not found");
43
+ const defaultAuthorized = await defaultMutationAuthorization(
44
+ client,
45
+ subscription,
46
+ externalCustomerId
47
+ );
48
+ const customAuthorized = options.authorizeMutation ? await options.authorizeMutation({ request, subscription, externalCustomerId }) : true;
49
+ if (!defaultAuthorized || !customAuthorized) {
50
+ return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
51
+ }
52
+ }
53
+ }
54
+ const targetPath = await buildPinnedPathAndBody(request, path, externalCustomerId, client);
55
+ if (targetPath instanceof Response) return targetPath;
56
+ const upstream = await fetchWithApiKey(request, targetPath.path, {
57
+ apiKey: options.apiKey,
58
+ baseUrl: options.baseUrl,
59
+ fetch: options.fetch,
60
+ body: targetPath.body
61
+ });
62
+ return new Response(upstream.body, {
63
+ status: upstream.status,
64
+ headers: filterHeaders(upstream.headers)
65
+ });
66
+ }
67
+ async function buildPinnedPathAndBody(request, path, externalCustomerId, client) {
68
+ const url = new URL(request.url);
69
+ const query = new URLSearchParams(url.search);
70
+ if (request.method.toUpperCase() === "GET" && path === "/api/v1/subscriptions") {
71
+ query.set("externalCustomerId", externalCustomerId);
72
+ query.delete("customerId");
73
+ query.delete("customer_id");
74
+ query.delete("external_customer_id");
75
+ }
76
+ if (request.method.toUpperCase() === "GET" && isCustomerByExternalPath(path)) {
77
+ return {
78
+ path: withQuery(
79
+ `/api/v1/customers/by-external/${encodeURIComponent(externalCustomerId)}`,
80
+ query
81
+ )
82
+ };
83
+ }
84
+ if (request.method.toUpperCase() !== "GET") {
85
+ const raw = await request.text();
86
+ if (path === "/api/v1/subscriptions" && request.method.toUpperCase() === "POST") {
87
+ const customer = await client.getCustomerByExternalId(externalCustomerId);
88
+ if (!customer) return jsonError(404, "not_found", "Customer not found");
89
+ const body = raw ? JSON.parse(raw) : {};
90
+ body.customerId = customer.id;
91
+ delete body.customer_id;
92
+ delete body.externalCustomerId;
93
+ delete body.external_customer_id;
94
+ return { path: withQuery(path, query), body: JSON.stringify(body) };
95
+ }
96
+ if (path === "/api/v1/checkout/sessions" && raw) {
97
+ const body = JSON.parse(raw);
98
+ body.customer = {
99
+ ...body.customer ?? {},
100
+ externalCustomerId
101
+ };
102
+ delete body.customer.customerId;
103
+ return { path: withQuery(path, query), body: JSON.stringify(body) };
104
+ }
105
+ return { path: withQuery(path, query), body: raw || void 0 };
106
+ }
107
+ return { path: withQuery(path, query) };
108
+ }
109
+ async function fetchWithApiKey(request, path, opts) {
110
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
111
+ if (!fetchImpl) throw new Error("fetch is required");
112
+ const baseUrl = (opts.baseUrl ?? "https://api.usethrottle.dev").replace(/\/+$/, "");
113
+ return fetchImpl(`${baseUrl}${path}`, {
114
+ method: request.method,
115
+ headers: {
116
+ "x-api-key": opts.apiKey,
117
+ "content-type": request.headers.get("content-type") ?? "application/json"
118
+ },
119
+ body: request.method.toUpperCase() === "GET" ? void 0 : opts.body
120
+ });
121
+ }
122
+ async function defaultMutationAuthorization(client, subscription, externalCustomerId) {
123
+ const customer = await client.getCustomerByExternalId(externalCustomerId);
124
+ return Boolean(customer?.id && customer.id === subscription.customerId);
125
+ }
126
+ async function pathFromContext(context) {
127
+ if (context.path) return context.path;
128
+ const path = context.params?.path;
129
+ if (!path) return [];
130
+ return await path;
131
+ }
132
+ function normalizePath(pathParts) {
133
+ if (typeof pathParts === "string") {
134
+ return pathParts.startsWith("/") ? pathParts : `/${pathParts}`;
135
+ }
136
+ return `/${pathParts.map((part) => part.replace(/^\/+|\/+$/g, "")).join("/")}`;
137
+ }
138
+ function withQuery(path, query) {
139
+ const qs = query.toString();
140
+ return qs ? `${path}?${qs}` : path;
141
+ }
142
+ function isSubscriptionMutation(path, method) {
143
+ if (path === "/api/v1/subscriptions/eligibility-check") return false;
144
+ return method !== "GET" && /^\/api\/v1\/subscriptions\/[\w.-]+/.test(path);
145
+ }
146
+ function isCustomerByExternalPath(path) {
147
+ return /^\/api\/v1\/customers\/by-external\/[\w.:%-]+$/.test(path);
148
+ }
149
+ function subscriptionIdFromPath(path) {
150
+ return path.match(/^\/api\/v1\/subscriptions\/([\w.-]+)/)?.[1] ?? null;
151
+ }
152
+ function filterHeaders(headers) {
153
+ const next = new Headers(headers);
154
+ next.delete("content-encoding");
155
+ next.delete("content-length");
156
+ next.delete("transfer-encoding");
157
+ return next;
158
+ }
159
+ function jsonError(status, code, message) {
160
+ return Response.json({ error: { code, message } }, { status });
161
+ }
162
+ export {
163
+ createSubscriptionProxyHandler,
164
+ handleSubscriptionProxyRequest
165
+ };
166
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { SubscriptionsClient } from './client';\nimport { ThrottleSubscriptionsError } from './errors';\nimport type { Subscription, SubscriptionsClientOptions } from './types';\n\nexport interface SubscriptionProxyOptions {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof globalThis.fetch;\n getExternalCustomerId: (request: Request) => string | null | Promise<string | null>;\n authorizeMutation?: (args: {\n request: Request;\n subscription: Subscription;\n externalCustomerId: string;\n }) => boolean | Promise<boolean>;\n}\n\nexport interface SubscriptionProxyContext {\n params?: {\n path?: string[] | Promise<string[]>;\n };\n path?: string[] | string;\n}\n\nconst ALLOWED_PATHS = [\n /^\\/api\\/v1\\/subscriptions(?:\\/[\\w.-]+)?(?:\\/(?:pause|resume|cancel))?$/,\n /^\\/api\\/v1\\/subscriptions\\/eligibility-check$/,\n /^\\/api\\/v1\\/customers\\/by-external\\/[\\w.:%-]+$/,\n /^\\/api\\/v1\\/checkout\\/sessions$/,\n];\n\nexport function createSubscriptionProxyHandler(options: SubscriptionProxyOptions) {\n return async function subscriptionProxyHandler(\n request: Request,\n context: SubscriptionProxyContext = {},\n ): Promise<Response> {\n const path = await pathFromContext(context);\n return handleSubscriptionProxyRequest(request, path, options);\n };\n}\n\nexport async function handleSubscriptionProxyRequest(\n request: Request,\n pathParts: string[] | string,\n options: SubscriptionProxyOptions,\n): Promise<Response> {\n const externalCustomerId = await options.getExternalCustomerId(request);\n if (!externalCustomerId) return jsonError(401, 'unauthorized', 'Unauthorized');\n\n const path = normalizePath(pathParts);\n if (!ALLOWED_PATHS.some((pattern) => pattern.test(path))) {\n return jsonError(403, 'forbidden', 'Path is not allowed by the subscription proxy');\n }\n\n const method = request.method.toUpperCase();\n if (!['GET', 'POST', 'PATCH'].includes(method)) {\n return jsonError(405, 'method_not_allowed', 'Method not allowed');\n }\n\n const client = new SubscriptionsClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n fetch: options.fetch,\n } satisfies SubscriptionsClientOptions);\n\n if (isSubscriptionMutation(path, method)) {\n const subId = subscriptionIdFromPath(path);\n if (subId) {\n const subscription = await client.get(subId).catch((err) => {\n if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;\n throw err;\n });\n if (!subscription) return jsonError(404, 'not_found', 'Subscription not found');\n\n const defaultAuthorized = await defaultMutationAuthorization(\n client,\n subscription,\n externalCustomerId,\n );\n const customAuthorized = options.authorizeMutation\n ? await options.authorizeMutation({ request, subscription, externalCustomerId })\n : true;\n if (!defaultAuthorized || !customAuthorized) {\n return jsonError(403, 'forbidden', 'Subscription does not belong to this buyer');\n }\n }\n }\n\n const targetPath = await buildPinnedPathAndBody(request, path, externalCustomerId, client);\n if (targetPath instanceof Response) return targetPath;\n const upstream = await fetchWithApiKey(request, targetPath.path, {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n fetch: options.fetch,\n body: targetPath.body,\n });\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: filterHeaders(upstream.headers),\n });\n}\n\nasync function buildPinnedPathAndBody(\n request: Request,\n path: string,\n externalCustomerId: string,\n client: SubscriptionsClient,\n): Promise<{ path: string; body?: string } | Response> {\n const url = new URL(request.url);\n const query = new URLSearchParams(url.search);\n\n if (request.method.toUpperCase() === 'GET' && path === '/api/v1/subscriptions') {\n query.set('externalCustomerId', externalCustomerId);\n query.delete('customerId');\n query.delete('customer_id');\n query.delete('external_customer_id');\n }\n\n if (request.method.toUpperCase() === 'GET' && isCustomerByExternalPath(path)) {\n return {\n path: withQuery(\n `/api/v1/customers/by-external/${encodeURIComponent(externalCustomerId)}`,\n query,\n ),\n };\n }\n\n if (request.method.toUpperCase() !== 'GET') {\n const raw = await request.text();\n if (path === '/api/v1/subscriptions' && request.method.toUpperCase() === 'POST') {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n if (!customer) return jsonError(404, 'not_found', 'Customer not found');\n\n const body = raw ? (JSON.parse(raw) as Record<string, any>) : {};\n body.customerId = customer.id;\n delete body.customer_id;\n delete body.externalCustomerId;\n delete body.external_customer_id;\n return { path: withQuery(path, query), body: JSON.stringify(body) };\n }\n\n if (path === '/api/v1/checkout/sessions' && raw) {\n const body = JSON.parse(raw) as Record<string, any>;\n body.customer = {\n ...(body.customer ?? {}),\n externalCustomerId,\n };\n delete body.customer.customerId;\n return { path: withQuery(path, query), body: JSON.stringify(body) };\n }\n return { path: withQuery(path, query), body: raw || undefined };\n }\n\n return { path: withQuery(path, query) };\n}\n\nasync function fetchWithApiKey(\n request: Request,\n path: string,\n opts: {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof globalThis.fetch;\n body?: string;\n },\n) {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!fetchImpl) throw new Error('fetch is required');\n const baseUrl = (opts.baseUrl ?? 'https://api.usethrottle.dev').replace(/\\/+$/, '');\n return fetchImpl(`${baseUrl}${path}`, {\n method: request.method,\n headers: {\n 'x-api-key': opts.apiKey,\n 'content-type': request.headers.get('content-type') ?? 'application/json',\n },\n body: request.method.toUpperCase() === 'GET' ? undefined : opts.body,\n });\n}\n\nasync function defaultMutationAuthorization(\n client: SubscriptionsClient,\n subscription: Subscription,\n externalCustomerId: string,\n): Promise<boolean> {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n return Boolean(customer?.id && customer.id === subscription.customerId);\n}\n\nasync function pathFromContext(context: SubscriptionProxyContext): Promise<string[] | string> {\n if (context.path) return context.path;\n const path = context.params?.path;\n if (!path) return [];\n return await path;\n}\n\nfunction normalizePath(pathParts: string[] | string): string {\n if (typeof pathParts === 'string') {\n return pathParts.startsWith('/') ? pathParts : `/${pathParts}`;\n }\n return `/${pathParts.map((part) => part.replace(/^\\/+|\\/+$/g, '')).join('/')}`;\n}\n\nfunction withQuery(path: string, query: URLSearchParams): string {\n const qs = query.toString();\n return qs ? `${path}?${qs}` : path;\n}\n\nfunction isSubscriptionMutation(path: string, method: string): boolean {\n if (path === '/api/v1/subscriptions/eligibility-check') return false;\n return method !== 'GET' && /^\\/api\\/v1\\/subscriptions\\/[\\w.-]+/.test(path);\n}\n\nfunction isCustomerByExternalPath(path: string): boolean {\n return /^\\/api\\/v1\\/customers\\/by-external\\/[\\w.:%-]+$/.test(path);\n}\n\nfunction subscriptionIdFromPath(path: string): string | null {\n return path.match(/^\\/api\\/v1\\/subscriptions\\/([\\w.-]+)/)?.[1] ?? null;\n}\n\nfunction filterHeaders(headers: Headers): Headers {\n const next = new Headers(headers);\n next.delete('content-encoding');\n next.delete('content-length');\n next.delete('transfer-encoding');\n return next;\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return Response.json({ error: { code, message } }, { status });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,233 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+
3
+ type SubscriptionStatus = 'active' | 'paused' | 'cancelled' | 'past_due' | 'trialing';
4
+ type SubscriptionInterval = 'weekly' | 'monthly' | 'quarterly' | 'yearly';
5
+ type SubscriptionSort = 'asc' | 'desc';
6
+ interface Subscription {
7
+ id: string;
8
+ merchantId?: string | null;
9
+ storeId?: string | null;
10
+ customerId: string;
11
+ status: SubscriptionStatus;
12
+ planReference: string;
13
+ planName?: string | null;
14
+ interval: SubscriptionInterval;
15
+ amount: number;
16
+ currency: string;
17
+ currentPeriodStart: string;
18
+ currentPeriodEnd: string;
19
+ trialEnd?: string | null;
20
+ cancelAtPeriodEnd: boolean;
21
+ failureCount: number;
22
+ lastPaymentAt?: string | null;
23
+ pausedAt?: string | null;
24
+ cancelledAt?: string | null;
25
+ metadata: Record<string, unknown>;
26
+ createdAt?: string;
27
+ updatedAt?: string;
28
+ }
29
+ interface Customer {
30
+ id: string;
31
+ externalId?: string | null;
32
+ email?: string | null;
33
+ firstName?: string | null;
34
+ lastName?: string | null;
35
+ phone?: string | null;
36
+ metadata?: Record<string, unknown>;
37
+ createdAt?: string;
38
+ updatedAt?: string;
39
+ }
40
+ interface Pagination {
41
+ cursor: string | null;
42
+ hasMore: boolean;
43
+ }
44
+ interface PaginatedSubscriptions {
45
+ data: Subscription[];
46
+ pagination?: Pagination;
47
+ }
48
+ interface ListSubscriptionsParams {
49
+ customerId?: string;
50
+ externalCustomerId?: string;
51
+ status?: SubscriptionStatus;
52
+ interval?: SubscriptionInterval;
53
+ q?: string;
54
+ cursor?: string;
55
+ limit?: number;
56
+ sort?: SubscriptionSort;
57
+ createdAfter?: string | Date;
58
+ createdBefore?: string | Date;
59
+ }
60
+ interface CreateSubscriptionInput {
61
+ storeId?: string;
62
+ customerId?: string;
63
+ externalCustomerId?: string;
64
+ planReference: string;
65
+ planName?: string;
66
+ interval: SubscriptionInterval;
67
+ amount: number;
68
+ currency?: string;
69
+ currentPeriodStart: string | Date;
70
+ currentPeriodEnd: string | Date;
71
+ trialEnd?: string | Date;
72
+ metadata?: Record<string, unknown>;
73
+ }
74
+ interface UpdateSubscriptionInput {
75
+ planReference?: string;
76
+ planName?: string;
77
+ interval?: SubscriptionInterval;
78
+ amount?: number;
79
+ metadata?: Record<string, unknown>;
80
+ }
81
+ interface CancelSubscriptionInput {
82
+ id: string;
83
+ atPeriodEnd?: boolean;
84
+ }
85
+ interface ChangePlanInput extends UpdateSubscriptionInput {
86
+ id: string;
87
+ planReference: string;
88
+ }
89
+ interface RecurringIntent {
90
+ plan: string;
91
+ interval: SubscriptionInterval;
92
+ amount?: number;
93
+ trialDays?: number;
94
+ planName?: string;
95
+ create?: 'auto' | 'manual';
96
+ }
97
+ interface CheckoutCustomerInput {
98
+ customerId?: string;
99
+ externalCustomerId?: string;
100
+ email?: string;
101
+ firstName?: string;
102
+ lastName?: string;
103
+ phone?: string;
104
+ shippingAddress?: Record<string, unknown>;
105
+ billingAddress?: Record<string, unknown>;
106
+ metadata?: Record<string, unknown>;
107
+ }
108
+ interface CheckoutCollectInput {
109
+ shippingAddress?: boolean;
110
+ billingAddress?: boolean;
111
+ }
112
+ interface CreateCheckoutSessionInput {
113
+ storeId: string;
114
+ cartId?: string;
115
+ externalCartId?: string;
116
+ customerEmail?: string;
117
+ customer?: CheckoutCustomerInput;
118
+ returnUrl: string;
119
+ cancelUrl: string;
120
+ allowedMethods?: string[];
121
+ recurring: RecurringIntent;
122
+ collect?: CheckoutCollectInput;
123
+ metadata?: Record<string, unknown>;
124
+ }
125
+ interface CheckoutSession {
126
+ sessionId: string;
127
+ id?: string;
128
+ checkoutUrl?: string;
129
+ embedToken?: string;
130
+ expiresAt?: string;
131
+ [key: string]: unknown;
132
+ }
133
+ interface TrialEligibilityInput {
134
+ paymentMethodId: string;
135
+ }
136
+ type TrialEligibilityReason = 'card_already_used_for_trial' | 'payment_method_not_found' | 'no_fingerprint_available';
137
+ interface TrialEligibilityResult {
138
+ eligible: boolean;
139
+ reason?: TrialEligibilityReason;
140
+ }
141
+ interface ThrottleEnvelope<T> {
142
+ data?: T;
143
+ meta?: {
144
+ request_id?: string;
145
+ pagination?: {
146
+ cursor?: string | null;
147
+ has_more?: boolean;
148
+ hasMore?: boolean;
149
+ };
150
+ [key: string]: unknown;
151
+ };
152
+ error?: {
153
+ code?: string;
154
+ message?: string;
155
+ details?: unknown;
156
+ };
157
+ }
158
+ type SubscriptionFetcher = (path: string, init?: RequestInit) => Promise<Response>;
159
+ interface SubscriptionsClientOptions {
160
+ apiKey?: string;
161
+ baseUrl?: string;
162
+ fetch?: typeof globalThis.fetch;
163
+ fetcher?: SubscriptionFetcher;
164
+ timeoutMs?: number;
165
+ }
166
+ interface SubscriptionProviderProps {
167
+ children: ReactNode;
168
+ client?: SubscriptionsClientContract;
169
+ fetcher?: SubscriptionFetcher;
170
+ baseUrl?: string;
171
+ timeoutMs?: number;
172
+ }
173
+ interface QueryState<T> {
174
+ data: T | null;
175
+ isLoading: boolean;
176
+ isFetching: boolean;
177
+ error: unknown;
178
+ refetch: () => Promise<T | null>;
179
+ }
180
+ interface MutationState<TData, TVariables> {
181
+ data: TData | null;
182
+ error: unknown;
183
+ isPending: boolean;
184
+ mutate: (variables: TVariables) => void;
185
+ mutateAsync: (variables: TVariables) => Promise<TData>;
186
+ reset: () => void;
187
+ }
188
+ interface QueryOptions<T> {
189
+ enabled?: boolean;
190
+ initialData?: T | null;
191
+ }
192
+ interface SubscriptionStatusBadgeProps {
193
+ status: SubscriptionStatus;
194
+ className?: string;
195
+ style?: CSSProperties;
196
+ labels?: Partial<Record<SubscriptionStatus, string>>;
197
+ }
198
+ interface TrialCountdownProps {
199
+ endDate: string | Date;
200
+ now?: Date;
201
+ className?: string;
202
+ style?: CSSProperties;
203
+ expiredLabel?: string;
204
+ prefix?: string;
205
+ }
206
+ interface DunningBannerProps {
207
+ subscription: Pick<Subscription, 'status' | 'failureCount' | 'currentPeriodEnd'>;
208
+ onUpdateCard?: () => void;
209
+ title?: string;
210
+ message?: string;
211
+ actionLabel?: string;
212
+ className?: string;
213
+ style?: CSSProperties;
214
+ }
215
+ interface SubscriptionsClientContract {
216
+ request<T>(method: string, path: string, body?: unknown): Promise<T>;
217
+ requestEnvelope<T>(method: string, path: string, body?: unknown): Promise<ThrottleEnvelope<T>>;
218
+ create(input: CreateSubscriptionInput): Promise<Subscription>;
219
+ list(params?: ListSubscriptionsParams): Promise<PaginatedSubscriptions>;
220
+ get(id: string): Promise<Subscription>;
221
+ update(id: string, input: UpdateSubscriptionInput): Promise<Subscription>;
222
+ changePlan(input: ChangePlanInput): Promise<Subscription>;
223
+ pause(id: string): Promise<Subscription>;
224
+ resume(id: string): Promise<Subscription>;
225
+ cancel(id: string, input?: {
226
+ atPeriodEnd?: boolean;
227
+ }): Promise<Subscription>;
228
+ createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession>;
229
+ checkTrialEligibility(input: TrialEligibilityInput): Promise<TrialEligibilityResult>;
230
+ getCustomerByExternalId(externalId: string): Promise<Customer | null>;
231
+ }
232
+
233
+ export type { CreateSubscriptionInput as C, DunningBannerProps as D, ListSubscriptionsParams as L, MutationState as M, PaginatedSubscriptions as P, QueryOptions as Q, RecurringIntent as R, SubscriptionsClientContract as S, ThrottleEnvelope as T, UpdateSubscriptionInput as U, SubscriptionsClientOptions as a, Subscription as b, ChangePlanInput as c, CreateCheckoutSessionInput as d, CheckoutSession as e, TrialEligibilityInput as f, TrialEligibilityResult as g, Customer as h, SubscriptionProviderProps as i, SubscriptionFetcher as j, CancelSubscriptionInput as k, QueryState as l, SubscriptionStatusBadgeProps as m, TrialCountdownProps as n, SubscriptionStatus as o, CheckoutCollectInput as p, CheckoutCustomerInput as q, Pagination as r, SubscriptionInterval as s, SubscriptionSort as t, TrialEligibilityReason as u };
@@ -0,0 +1,233 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+
3
+ type SubscriptionStatus = 'active' | 'paused' | 'cancelled' | 'past_due' | 'trialing';
4
+ type SubscriptionInterval = 'weekly' | 'monthly' | 'quarterly' | 'yearly';
5
+ type SubscriptionSort = 'asc' | 'desc';
6
+ interface Subscription {
7
+ id: string;
8
+ merchantId?: string | null;
9
+ storeId?: string | null;
10
+ customerId: string;
11
+ status: SubscriptionStatus;
12
+ planReference: string;
13
+ planName?: string | null;
14
+ interval: SubscriptionInterval;
15
+ amount: number;
16
+ currency: string;
17
+ currentPeriodStart: string;
18
+ currentPeriodEnd: string;
19
+ trialEnd?: string | null;
20
+ cancelAtPeriodEnd: boolean;
21
+ failureCount: number;
22
+ lastPaymentAt?: string | null;
23
+ pausedAt?: string | null;
24
+ cancelledAt?: string | null;
25
+ metadata: Record<string, unknown>;
26
+ createdAt?: string;
27
+ updatedAt?: string;
28
+ }
29
+ interface Customer {
30
+ id: string;
31
+ externalId?: string | null;
32
+ email?: string | null;
33
+ firstName?: string | null;
34
+ lastName?: string | null;
35
+ phone?: string | null;
36
+ metadata?: Record<string, unknown>;
37
+ createdAt?: string;
38
+ updatedAt?: string;
39
+ }
40
+ interface Pagination {
41
+ cursor: string | null;
42
+ hasMore: boolean;
43
+ }
44
+ interface PaginatedSubscriptions {
45
+ data: Subscription[];
46
+ pagination?: Pagination;
47
+ }
48
+ interface ListSubscriptionsParams {
49
+ customerId?: string;
50
+ externalCustomerId?: string;
51
+ status?: SubscriptionStatus;
52
+ interval?: SubscriptionInterval;
53
+ q?: string;
54
+ cursor?: string;
55
+ limit?: number;
56
+ sort?: SubscriptionSort;
57
+ createdAfter?: string | Date;
58
+ createdBefore?: string | Date;
59
+ }
60
+ interface CreateSubscriptionInput {
61
+ storeId?: string;
62
+ customerId?: string;
63
+ externalCustomerId?: string;
64
+ planReference: string;
65
+ planName?: string;
66
+ interval: SubscriptionInterval;
67
+ amount: number;
68
+ currency?: string;
69
+ currentPeriodStart: string | Date;
70
+ currentPeriodEnd: string | Date;
71
+ trialEnd?: string | Date;
72
+ metadata?: Record<string, unknown>;
73
+ }
74
+ interface UpdateSubscriptionInput {
75
+ planReference?: string;
76
+ planName?: string;
77
+ interval?: SubscriptionInterval;
78
+ amount?: number;
79
+ metadata?: Record<string, unknown>;
80
+ }
81
+ interface CancelSubscriptionInput {
82
+ id: string;
83
+ atPeriodEnd?: boolean;
84
+ }
85
+ interface ChangePlanInput extends UpdateSubscriptionInput {
86
+ id: string;
87
+ planReference: string;
88
+ }
89
+ interface RecurringIntent {
90
+ plan: string;
91
+ interval: SubscriptionInterval;
92
+ amount?: number;
93
+ trialDays?: number;
94
+ planName?: string;
95
+ create?: 'auto' | 'manual';
96
+ }
97
+ interface CheckoutCustomerInput {
98
+ customerId?: string;
99
+ externalCustomerId?: string;
100
+ email?: string;
101
+ firstName?: string;
102
+ lastName?: string;
103
+ phone?: string;
104
+ shippingAddress?: Record<string, unknown>;
105
+ billingAddress?: Record<string, unknown>;
106
+ metadata?: Record<string, unknown>;
107
+ }
108
+ interface CheckoutCollectInput {
109
+ shippingAddress?: boolean;
110
+ billingAddress?: boolean;
111
+ }
112
+ interface CreateCheckoutSessionInput {
113
+ storeId: string;
114
+ cartId?: string;
115
+ externalCartId?: string;
116
+ customerEmail?: string;
117
+ customer?: CheckoutCustomerInput;
118
+ returnUrl: string;
119
+ cancelUrl: string;
120
+ allowedMethods?: string[];
121
+ recurring: RecurringIntent;
122
+ collect?: CheckoutCollectInput;
123
+ metadata?: Record<string, unknown>;
124
+ }
125
+ interface CheckoutSession {
126
+ sessionId: string;
127
+ id?: string;
128
+ checkoutUrl?: string;
129
+ embedToken?: string;
130
+ expiresAt?: string;
131
+ [key: string]: unknown;
132
+ }
133
+ interface TrialEligibilityInput {
134
+ paymentMethodId: string;
135
+ }
136
+ type TrialEligibilityReason = 'card_already_used_for_trial' | 'payment_method_not_found' | 'no_fingerprint_available';
137
+ interface TrialEligibilityResult {
138
+ eligible: boolean;
139
+ reason?: TrialEligibilityReason;
140
+ }
141
+ interface ThrottleEnvelope<T> {
142
+ data?: T;
143
+ meta?: {
144
+ request_id?: string;
145
+ pagination?: {
146
+ cursor?: string | null;
147
+ has_more?: boolean;
148
+ hasMore?: boolean;
149
+ };
150
+ [key: string]: unknown;
151
+ };
152
+ error?: {
153
+ code?: string;
154
+ message?: string;
155
+ details?: unknown;
156
+ };
157
+ }
158
+ type SubscriptionFetcher = (path: string, init?: RequestInit) => Promise<Response>;
159
+ interface SubscriptionsClientOptions {
160
+ apiKey?: string;
161
+ baseUrl?: string;
162
+ fetch?: typeof globalThis.fetch;
163
+ fetcher?: SubscriptionFetcher;
164
+ timeoutMs?: number;
165
+ }
166
+ interface SubscriptionProviderProps {
167
+ children: ReactNode;
168
+ client?: SubscriptionsClientContract;
169
+ fetcher?: SubscriptionFetcher;
170
+ baseUrl?: string;
171
+ timeoutMs?: number;
172
+ }
173
+ interface QueryState<T> {
174
+ data: T | null;
175
+ isLoading: boolean;
176
+ isFetching: boolean;
177
+ error: unknown;
178
+ refetch: () => Promise<T | null>;
179
+ }
180
+ interface MutationState<TData, TVariables> {
181
+ data: TData | null;
182
+ error: unknown;
183
+ isPending: boolean;
184
+ mutate: (variables: TVariables) => void;
185
+ mutateAsync: (variables: TVariables) => Promise<TData>;
186
+ reset: () => void;
187
+ }
188
+ interface QueryOptions<T> {
189
+ enabled?: boolean;
190
+ initialData?: T | null;
191
+ }
192
+ interface SubscriptionStatusBadgeProps {
193
+ status: SubscriptionStatus;
194
+ className?: string;
195
+ style?: CSSProperties;
196
+ labels?: Partial<Record<SubscriptionStatus, string>>;
197
+ }
198
+ interface TrialCountdownProps {
199
+ endDate: string | Date;
200
+ now?: Date;
201
+ className?: string;
202
+ style?: CSSProperties;
203
+ expiredLabel?: string;
204
+ prefix?: string;
205
+ }
206
+ interface DunningBannerProps {
207
+ subscription: Pick<Subscription, 'status' | 'failureCount' | 'currentPeriodEnd'>;
208
+ onUpdateCard?: () => void;
209
+ title?: string;
210
+ message?: string;
211
+ actionLabel?: string;
212
+ className?: string;
213
+ style?: CSSProperties;
214
+ }
215
+ interface SubscriptionsClientContract {
216
+ request<T>(method: string, path: string, body?: unknown): Promise<T>;
217
+ requestEnvelope<T>(method: string, path: string, body?: unknown): Promise<ThrottleEnvelope<T>>;
218
+ create(input: CreateSubscriptionInput): Promise<Subscription>;
219
+ list(params?: ListSubscriptionsParams): Promise<PaginatedSubscriptions>;
220
+ get(id: string): Promise<Subscription>;
221
+ update(id: string, input: UpdateSubscriptionInput): Promise<Subscription>;
222
+ changePlan(input: ChangePlanInput): Promise<Subscription>;
223
+ pause(id: string): Promise<Subscription>;
224
+ resume(id: string): Promise<Subscription>;
225
+ cancel(id: string, input?: {
226
+ atPeriodEnd?: boolean;
227
+ }): Promise<Subscription>;
228
+ createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession>;
229
+ checkTrialEligibility(input: TrialEligibilityInput): Promise<TrialEligibilityResult>;
230
+ getCustomerByExternalId(externalId: string): Promise<Customer | null>;
231
+ }
232
+
233
+ export type { CreateSubscriptionInput as C, DunningBannerProps as D, ListSubscriptionsParams as L, MutationState as M, PaginatedSubscriptions as P, QueryOptions as Q, RecurringIntent as R, SubscriptionsClientContract as S, ThrottleEnvelope as T, UpdateSubscriptionInput as U, SubscriptionsClientOptions as a, Subscription as b, ChangePlanInput as c, CreateCheckoutSessionInput as d, CheckoutSession as e, TrialEligibilityInput as f, TrialEligibilityResult as g, Customer as h, SubscriptionProviderProps as i, SubscriptionFetcher as j, CancelSubscriptionInput as k, QueryState as l, SubscriptionStatusBadgeProps as m, TrialCountdownProps as n, SubscriptionStatus as o, CheckoutCollectInput as p, CheckoutCustomerInput as q, Pagination as r, SubscriptionInterval as s, SubscriptionSort as t, TrialEligibilityReason as u };