@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/CLAUDE.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# `@usethrottle/subscriptions`
|
|
2
|
+
|
|
3
|
+
Production public package for Throttle subscription integrations.
|
|
4
|
+
|
|
5
|
+
## Mission
|
|
6
|
+
|
|
7
|
+
Layer over the public Throttle REST API and `@usethrottle/checkout-react`. Provide a complete buyer-portal integration surface: typed client, React provider/hooks, primitive UI components, and server proxy helpers that keep merchant secret keys off the browser.
|
|
8
|
+
|
|
9
|
+
## Boundary with sibling packages
|
|
10
|
+
|
|
11
|
+
| Package | Responsibility | Does NOT do |
|
|
12
|
+
| ----------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
13
|
+
| `@usethrottle/checkout-react` | Hosted/embedded payment collection and first subscribe payment | Manage existing subscriptions |
|
|
14
|
+
| `@usethrottle/subscriptions` | Manage subscriptions, create recurring checkout sessions, trial checks, buyer portal hooks/primitives | Collect card details |
|
|
15
|
+
| `@usethrottle/api-client` | Generated server-side REST client for the whole API | React buyer portal state |
|
|
16
|
+
|
|
17
|
+
## Auth model
|
|
18
|
+
|
|
19
|
+
v1 is proxy-first. Browser code calls the merchant backend through `SubscriptionProvider fetcher={...}`. The merchant backend holds the `sk_*` key and can use `createSubscriptionProxyHandler` from `@usethrottle/subscriptions/server`.
|
|
20
|
+
|
|
21
|
+
Direct customer-scoped browser tokens are intentionally not in v1 because the API does not expose customer session tokens yet.
|
|
22
|
+
|
|
23
|
+
## Public exports
|
|
24
|
+
|
|
25
|
+
- `SubscriptionsClient`, `createSubscriptionsClient`
|
|
26
|
+
- `SubscriptionProvider`, `createSubscriptionFetcher`
|
|
27
|
+
- Hooks: `useSubscriptions`, `useSubscription`, `useCustomerByExternalId`, `useCreateSubscription`, `useUpdateSubscription`, `useChangePlan`, `useCancelSubscription`, `usePauseSubscription`, `useResumeSubscription`, `useCreateCheckoutSession`, `useTrialEligibility`
|
|
28
|
+
- Components: `SubscriptionStatusBadge`, `TrialCountdown`, `DunningBanner`
|
|
29
|
+
- Helpers: `canPauseSubscription`, `canResumeSubscription`, `canCancelSubscription`, `isTerminalSubscription`, `isSubscriptionPastDue`, `isSubscriptionTrialing`, `daysUntil`
|
|
30
|
+
- Server subpath: `createSubscriptionProxyHandler`, `handleSubscriptionProxyRequest`
|
|
31
|
+
|
|
32
|
+
## Conventions
|
|
33
|
+
|
|
34
|
+
- Keep API payloads camelCase on writes because the live Fastify validators expect camelCase.
|
|
35
|
+
- `create()` accepts either `customerId` or `externalCustomerId`; resolve external ids to Throttle customer rows before posting to `/api/v1/subscriptions`.
|
|
36
|
+
- Normalize both camelCase and snake_case response fields. Some OpenAPI schemas use snake_case while service rows currently serialize camelCase.
|
|
37
|
+
- The proxy helper must pin buyer-scoped reads, customer lookups, subscription creates, and checkout-session creates to `getExternalCustomerId()`.
|
|
38
|
+
- Mutations invalidate provider queries by bumping the provider cache version. Avoid adding a heavy query dependency unless product needs become more complex.
|
|
39
|
+
- Components are primitive and minimally styled. They accept `className` and `style` so merchants can bring their own design system.
|
|
40
|
+
|
|
41
|
+
## Validation
|
|
42
|
+
|
|
43
|
+
- `pnpm --filter @usethrottle/subscriptions typecheck`
|
|
44
|
+
- `pnpm --filter @usethrottle/subscriptions test`
|
|
45
|
+
- `pnpm --filter @usethrottle/subscriptions build`
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @usethrottle/subscriptions
|
|
2
|
+
|
|
3
|
+
Production React hooks, server proxy helpers, and primitive components for Throttle subscriptions.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @usethrottle/subscriptions @usethrottle/checkout-react
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## What it covers
|
|
10
|
+
|
|
11
|
+
- Subscription lifecycle reads and mutations: list, get, create, update, change plan, pause, resume, cancel.
|
|
12
|
+
- Recurring checkout-session creation for subscribe flows.
|
|
13
|
+
- Trial eligibility checks for trial-fraud protection.
|
|
14
|
+
- Buyer-portal proxy helpers so browser code never sees an `sk_*` key.
|
|
15
|
+
- Primitive UI components for status, trial countdowns, and past-due dunning.
|
|
16
|
+
|
|
17
|
+
## Browser usage
|
|
18
|
+
|
|
19
|
+
Mount the provider with a fetcher that points at your own backend proxy:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
'use client';
|
|
23
|
+
import { SubscriptionProvider } from '@usethrottle/subscriptions';
|
|
24
|
+
|
|
25
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
26
|
+
return (
|
|
27
|
+
<SubscriptionProvider fetcher={(path, init) => fetch(`/api/throttle${path}`, init)}>
|
|
28
|
+
{children}
|
|
29
|
+
</SubscriptionProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then use hooks inside your buyer portal:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import {
|
|
38
|
+
DunningBanner,
|
|
39
|
+
SubscriptionStatusBadge,
|
|
40
|
+
useCancelSubscription,
|
|
41
|
+
useSubscriptions,
|
|
42
|
+
} from '@usethrottle/subscriptions';
|
|
43
|
+
|
|
44
|
+
export function Subscriptions({ userId }: { userId: string }) {
|
|
45
|
+
const { data, isLoading } = useSubscriptions({ externalCustomerId: userId });
|
|
46
|
+
const cancel = useCancelSubscription();
|
|
47
|
+
|
|
48
|
+
if (isLoading) return null;
|
|
49
|
+
|
|
50
|
+
return data?.data.map((sub) => (
|
|
51
|
+
<article key={sub.id}>
|
|
52
|
+
<SubscriptionStatusBadge status={sub.status} />
|
|
53
|
+
<strong>{sub.planName ?? sub.planReference}</strong>
|
|
54
|
+
<DunningBanner subscription={sub} onUpdateCard={() => openUpdateCard(sub)} />
|
|
55
|
+
<button onClick={() => cancel.mutate({ id: sub.id, atPeriodEnd: true })}>
|
|
56
|
+
Cancel at period end
|
|
57
|
+
</button>
|
|
58
|
+
</article>
|
|
59
|
+
));
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Server proxy helper
|
|
64
|
+
|
|
65
|
+
For Next.js App Router:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// app/api/throttle/[...path]/route.ts
|
|
69
|
+
import { createSubscriptionProxyHandler } from '@usethrottle/subscriptions/server';
|
|
70
|
+
import { auth } from '@/lib/auth';
|
|
71
|
+
|
|
72
|
+
const handler = createSubscriptionProxyHandler({
|
|
73
|
+
apiKey: process.env.THROTTLE_SECRET_KEY!,
|
|
74
|
+
async getExternalCustomerId() {
|
|
75
|
+
const user = await auth();
|
|
76
|
+
return user?.id ?? null;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export { handler as GET, handler as POST, handler as PATCH };
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The helper allowlists subscription, customer-by-external, trial eligibility, and checkout-session paths. It pins subscription list reads, customer lookups, direct subscription creates, and checkout-session creates to the authenticated buyer's `externalCustomerId`, and verifies ownership before forwarding subscription mutations.
|
|
84
|
+
|
|
85
|
+
## Server-side API client
|
|
86
|
+
|
|
87
|
+
You can also use the client directly from backend code:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { createSubscriptionsClient } from '@usethrottle/subscriptions';
|
|
91
|
+
|
|
92
|
+
const subscriptions = createSubscriptionsClient({
|
|
93
|
+
apiKey: process.env.THROTTLE_SECRET_KEY!,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await subscriptions.cancel('sub_123', { atPeriodEnd: true });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`create()` accepts either a Throttle `customerId` or your own `externalCustomerId`. When you pass `externalCustomerId`, the client resolves the Throttle customer row before calling the subscription create endpoint.
|
|
100
|
+
|
|
101
|
+
## Boundaries
|
|
102
|
+
|
|
103
|
+
- Customer-scoped browser tokens are not part of v1. Use the proxy helper.
|
|
104
|
+
- Payment collection remains in `@usethrottle/checkout-react`.
|
|
105
|
+
- Composite portal layouts are application code; this package ships hooks and primitives.
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ThrottleSubscriptionsError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
statusCode;
|
|
5
|
+
details;
|
|
6
|
+
constructor(args) {
|
|
7
|
+
super(args.message);
|
|
8
|
+
this.name = "ThrottleSubscriptionsError";
|
|
9
|
+
this.code = args.code;
|
|
10
|
+
this.statusCode = args.statusCode;
|
|
11
|
+
this.details = args.details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/normalize.ts
|
|
16
|
+
function normalizeSubscription(raw) {
|
|
17
|
+
return {
|
|
18
|
+
id: requiredString(raw?.id, "subscription.id"),
|
|
19
|
+
merchantId: optionalString(raw?.merchantId ?? raw?.merchant_id),
|
|
20
|
+
storeId: optionalString(raw?.storeId ?? raw?.store_id),
|
|
21
|
+
customerId: requiredString(raw?.customerId ?? raw?.customer_id, "subscription.customerId"),
|
|
22
|
+
status: raw?.status ?? "active",
|
|
23
|
+
planReference: requiredString(
|
|
24
|
+
raw?.planReference ?? raw?.plan_reference,
|
|
25
|
+
"subscription.planReference"
|
|
26
|
+
),
|
|
27
|
+
planName: optionalString(raw?.planName ?? raw?.plan_name),
|
|
28
|
+
interval: raw?.interval ?? "monthly",
|
|
29
|
+
amount: Number(raw?.amount ?? 0),
|
|
30
|
+
currency: optionalString(raw?.currency) ?? "USD",
|
|
31
|
+
currentPeriodStart: requiredDateString(
|
|
32
|
+
raw?.currentPeriodStart ?? raw?.current_period_start,
|
|
33
|
+
"subscription.currentPeriodStart"
|
|
34
|
+
),
|
|
35
|
+
currentPeriodEnd: requiredDateString(
|
|
36
|
+
raw?.currentPeriodEnd ?? raw?.current_period_end,
|
|
37
|
+
"subscription.currentPeriodEnd"
|
|
38
|
+
),
|
|
39
|
+
trialEnd: optionalDateString(raw?.trialEnd ?? raw?.trial_end),
|
|
40
|
+
cancelAtPeriodEnd: Boolean(raw?.cancelAtPeriodEnd ?? raw?.cancel_at_period_end ?? false),
|
|
41
|
+
failureCount: Number(raw?.failureCount ?? raw?.failure_count ?? 0),
|
|
42
|
+
lastPaymentAt: optionalDateString(raw?.lastPaymentAt ?? raw?.last_payment_at),
|
|
43
|
+
pausedAt: optionalDateString(raw?.pausedAt ?? raw?.paused_at),
|
|
44
|
+
cancelledAt: optionalDateString(raw?.cancelledAt ?? raw?.cancelled_at),
|
|
45
|
+
metadata: objectOrEmpty(raw?.metadata),
|
|
46
|
+
createdAt: optionalDateString(raw?.createdAt ?? raw?.created_at) ?? void 0,
|
|
47
|
+
updatedAt: optionalDateString(raw?.updatedAt ?? raw?.updated_at) ?? void 0
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function normalizeCustomer(raw) {
|
|
51
|
+
if (!raw) return null;
|
|
52
|
+
return {
|
|
53
|
+
id: requiredString(raw?.id, "customer.id"),
|
|
54
|
+
externalId: optionalString(raw?.externalId ?? raw?.external_id),
|
|
55
|
+
email: optionalString(raw?.email),
|
|
56
|
+
firstName: optionalString(raw?.firstName ?? raw?.first_name),
|
|
57
|
+
lastName: optionalString(raw?.lastName ?? raw?.last_name),
|
|
58
|
+
phone: optionalString(raw?.phone),
|
|
59
|
+
metadata: objectOrEmpty(raw?.metadata),
|
|
60
|
+
createdAt: optionalDateString(raw?.createdAt ?? raw?.created_at) ?? void 0,
|
|
61
|
+
updatedAt: optionalDateString(raw?.updatedAt ?? raw?.updated_at) ?? void 0
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function normalizeCheckoutSession(raw) {
|
|
65
|
+
const sessionId = raw?.sessionId ?? raw?.session_id ?? raw?.id;
|
|
66
|
+
return {
|
|
67
|
+
...raw,
|
|
68
|
+
id: optionalString(raw?.id),
|
|
69
|
+
sessionId: requiredString(sessionId, "checkoutSession.sessionId"),
|
|
70
|
+
checkoutUrl: optionalString(raw?.checkoutUrl ?? raw?.checkout_url),
|
|
71
|
+
embedToken: optionalString(raw?.embedToken ?? raw?.embed_token),
|
|
72
|
+
expiresAt: optionalDateString(raw?.expiresAt ?? raw?.expires_at) ?? void 0
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function normalizeTrialEligibility(raw) {
|
|
76
|
+
return {
|
|
77
|
+
eligible: Boolean(raw?.eligible),
|
|
78
|
+
reason: raw?.reason
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function normalizePagination(raw) {
|
|
82
|
+
if (!raw) return void 0;
|
|
83
|
+
return {
|
|
84
|
+
cursor: raw.cursor ?? null,
|
|
85
|
+
hasMore: Boolean(raw.hasMore ?? raw.has_more ?? false)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function toIso(value) {
|
|
89
|
+
if (value === void 0) return void 0;
|
|
90
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
91
|
+
}
|
|
92
|
+
function requiredString(value, label) {
|
|
93
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
94
|
+
throw new Error(`Invalid Throttle response: missing ${label}`);
|
|
95
|
+
}
|
|
96
|
+
function optionalString(value) {
|
|
97
|
+
return typeof value === "string" ? value : null;
|
|
98
|
+
}
|
|
99
|
+
function requiredDateString(value, label) {
|
|
100
|
+
if (value instanceof Date) return value.toISOString();
|
|
101
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
102
|
+
throw new Error(`Invalid Throttle response: missing ${label}`);
|
|
103
|
+
}
|
|
104
|
+
function optionalDateString(value) {
|
|
105
|
+
if (value instanceof Date) return value.toISOString();
|
|
106
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function objectOrEmpty(value) {
|
|
110
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/client.ts
|
|
114
|
+
var SubscriptionsClient = class {
|
|
115
|
+
apiKey;
|
|
116
|
+
baseUrl;
|
|
117
|
+
fetchImpl;
|
|
118
|
+
fetcher;
|
|
119
|
+
timeoutMs;
|
|
120
|
+
constructor(opts = {}) {
|
|
121
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
122
|
+
if (!fetchImpl) throw new Error("SubscriptionsClient: fetch is required");
|
|
123
|
+
if (!opts.apiKey && !opts.fetcher) {
|
|
124
|
+
throw new Error("SubscriptionsClient: apiKey or fetcher is required");
|
|
125
|
+
}
|
|
126
|
+
this.apiKey = opts.apiKey;
|
|
127
|
+
this.baseUrl = (opts.baseUrl ?? "https://api.usethrottle.dev").replace(/\/+$/, "");
|
|
128
|
+
this.fetchImpl = fetchImpl;
|
|
129
|
+
this.fetcher = opts.fetcher;
|
|
130
|
+
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
131
|
+
}
|
|
132
|
+
async request(method, path, body) {
|
|
133
|
+
const envelope = await this.requestEnvelope(method, path, body);
|
|
134
|
+
return envelope.data;
|
|
135
|
+
}
|
|
136
|
+
async requestEnvelope(method, path, body) {
|
|
137
|
+
const ctrl = new AbortController();
|
|
138
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
139
|
+
try {
|
|
140
|
+
const init = {
|
|
141
|
+
method,
|
|
142
|
+
headers: {
|
|
143
|
+
"content-type": "application/json",
|
|
144
|
+
...this.apiKey ? { "x-api-key": this.apiKey } : {}
|
|
145
|
+
},
|
|
146
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
147
|
+
signal: ctrl.signal
|
|
148
|
+
};
|
|
149
|
+
const res = this.fetcher ? await this.fetcher(path, init) : await this.fetchImpl(toAbsoluteUrl(this.baseUrl, path), init);
|
|
150
|
+
if (res.status === 204) return { data: void 0 };
|
|
151
|
+
const json = await res.json().catch(() => ({}));
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
throw new ThrottleSubscriptionsError({
|
|
154
|
+
code: json.error?.code ?? "unknown_error",
|
|
155
|
+
message: json.error?.message ?? `HTTP ${res.status}`,
|
|
156
|
+
statusCode: res.status,
|
|
157
|
+
details: json.error?.details
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return json;
|
|
161
|
+
} finally {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async create(input) {
|
|
166
|
+
const customerId = await this.resolveCustomerId(input.customerId, input.externalCustomerId);
|
|
167
|
+
const data = await this.request("POST", "/api/v1/subscriptions", {
|
|
168
|
+
storeId: input.storeId,
|
|
169
|
+
customerId,
|
|
170
|
+
planReference: input.planReference,
|
|
171
|
+
planName: input.planName,
|
|
172
|
+
interval: input.interval,
|
|
173
|
+
amount: input.amount,
|
|
174
|
+
currency: input.currency,
|
|
175
|
+
currentPeriodStart: toIso(input.currentPeriodStart),
|
|
176
|
+
currentPeriodEnd: toIso(input.currentPeriodEnd),
|
|
177
|
+
trialEnd: toIso(input.trialEnd),
|
|
178
|
+
metadata: input.metadata ?? {}
|
|
179
|
+
});
|
|
180
|
+
return normalizeSubscription(data);
|
|
181
|
+
}
|
|
182
|
+
async list(params = {}) {
|
|
183
|
+
const query = new URLSearchParams();
|
|
184
|
+
setQuery(query, "customerId", params.customerId);
|
|
185
|
+
setQuery(query, "externalCustomerId", params.externalCustomerId);
|
|
186
|
+
setQuery(query, "status", params.status);
|
|
187
|
+
setQuery(query, "interval", params.interval);
|
|
188
|
+
setQuery(query, "q", params.q);
|
|
189
|
+
setQuery(query, "cursor", params.cursor);
|
|
190
|
+
setQuery(query, "limit", params.limit);
|
|
191
|
+
setQuery(query, "sort", params.sort);
|
|
192
|
+
setQuery(query, "createdAfter", toIso(params.createdAfter));
|
|
193
|
+
setQuery(query, "createdBefore", toIso(params.createdBefore));
|
|
194
|
+
const suffix = query.toString() ? `?${query}` : "";
|
|
195
|
+
const envelope = await this.requestEnvelope("GET", `/api/v1/subscriptions${suffix}`);
|
|
196
|
+
return {
|
|
197
|
+
data: (envelope.data ?? []).map(normalizeSubscription),
|
|
198
|
+
pagination: normalizePagination(envelope.meta?.pagination)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async get(id) {
|
|
202
|
+
const data = await this.request(
|
|
203
|
+
"GET",
|
|
204
|
+
`/api/v1/subscriptions/${encodeURIComponent(id)}`
|
|
205
|
+
);
|
|
206
|
+
return normalizeSubscription(data);
|
|
207
|
+
}
|
|
208
|
+
async update(id, input) {
|
|
209
|
+
const data = await this.request(
|
|
210
|
+
"PATCH",
|
|
211
|
+
`/api/v1/subscriptions/${encodeURIComponent(id)}`,
|
|
212
|
+
input
|
|
213
|
+
);
|
|
214
|
+
return normalizeSubscription(data);
|
|
215
|
+
}
|
|
216
|
+
changePlan(input) {
|
|
217
|
+
const { id, ...body } = input;
|
|
218
|
+
return this.update(id, body);
|
|
219
|
+
}
|
|
220
|
+
async pause(id) {
|
|
221
|
+
const data = await this.request(
|
|
222
|
+
"POST",
|
|
223
|
+
`/api/v1/subscriptions/${encodeURIComponent(id)}/pause`
|
|
224
|
+
);
|
|
225
|
+
return normalizeSubscription(data);
|
|
226
|
+
}
|
|
227
|
+
async resume(id) {
|
|
228
|
+
const data = await this.request(
|
|
229
|
+
"POST",
|
|
230
|
+
`/api/v1/subscriptions/${encodeURIComponent(id)}/resume`
|
|
231
|
+
);
|
|
232
|
+
return normalizeSubscription(data);
|
|
233
|
+
}
|
|
234
|
+
async cancel(id, input = {}) {
|
|
235
|
+
const data = await this.request(
|
|
236
|
+
"POST",
|
|
237
|
+
`/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`,
|
|
238
|
+
{ atPeriodEnd: input.atPeriodEnd ?? false }
|
|
239
|
+
);
|
|
240
|
+
return normalizeSubscription(data);
|
|
241
|
+
}
|
|
242
|
+
async createCheckoutSession(input) {
|
|
243
|
+
const data = await this.request("POST", "/api/v1/checkout/sessions", input);
|
|
244
|
+
return normalizeCheckoutSession(data);
|
|
245
|
+
}
|
|
246
|
+
async checkTrialEligibility(input) {
|
|
247
|
+
const data = await this.request("POST", "/api/v1/subscriptions/eligibility-check", {
|
|
248
|
+
payment_method_id: input.paymentMethodId
|
|
249
|
+
});
|
|
250
|
+
return normalizeTrialEligibility(data);
|
|
251
|
+
}
|
|
252
|
+
async getCustomerByExternalId(externalId) {
|
|
253
|
+
try {
|
|
254
|
+
const data = await this.request(
|
|
255
|
+
"GET",
|
|
256
|
+
`/api/v1/customers/by-external/${encodeURIComponent(externalId)}`
|
|
257
|
+
);
|
|
258
|
+
return normalizeCustomer(data);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async resolveCustomerId(customerId, externalCustomerId) {
|
|
265
|
+
if (customerId) return customerId;
|
|
266
|
+
if (!externalCustomerId) {
|
|
267
|
+
throw new ThrottleSubscriptionsError({
|
|
268
|
+
code: "invalid_request",
|
|
269
|
+
message: "customerId or externalCustomerId is required",
|
|
270
|
+
statusCode: 400
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const customer = await this.getCustomerByExternalId(externalCustomerId);
|
|
274
|
+
if (!customer) {
|
|
275
|
+
throw new ThrottleSubscriptionsError({
|
|
276
|
+
code: "customer_not_found",
|
|
277
|
+
message: "Customer not found",
|
|
278
|
+
statusCode: 404
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return customer.id;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
function createSubscriptionsClient(opts) {
|
|
285
|
+
return new SubscriptionsClient(opts);
|
|
286
|
+
}
|
|
287
|
+
function toAbsoluteUrl(baseUrl, path) {
|
|
288
|
+
if (/^https?:\/\//i.test(path)) return path;
|
|
289
|
+
return `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
290
|
+
}
|
|
291
|
+
function setQuery(query, key, value) {
|
|
292
|
+
if (value === void 0 || value === null || value === "") return;
|
|
293
|
+
query.set(key, String(value));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export {
|
|
297
|
+
ThrottleSubscriptionsError,
|
|
298
|
+
SubscriptionsClient,
|
|
299
|
+
createSubscriptionsClient
|
|
300
|
+
};
|
|
301
|
+
//# sourceMappingURL=chunk-LJAXLA7O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/normalize.ts","../src/client.ts"],"sourcesContent":["export class ThrottleSubscriptionsError extends Error {\n readonly code: string;\n readonly statusCode: number;\n readonly details?: unknown;\n\n constructor(args: { code: string; message: string; statusCode: number; details?: unknown }) {\n super(args.message);\n this.name = 'ThrottleSubscriptionsError';\n this.code = args.code;\n this.statusCode = args.statusCode;\n this.details = args.details;\n }\n}\n","import type {\n CheckoutSession,\n Customer,\n Pagination,\n Subscription,\n SubscriptionInterval,\n SubscriptionStatus,\n TrialEligibilityResult,\n} from './types';\n\nexport function normalizeSubscription(raw: any): Subscription {\n return {\n id: requiredString(raw?.id, 'subscription.id'),\n merchantId: optionalString(raw?.merchantId ?? raw?.merchant_id),\n storeId: optionalString(raw?.storeId ?? raw?.store_id),\n customerId: requiredString(raw?.customerId ?? raw?.customer_id, 'subscription.customerId'),\n status: (raw?.status ?? 'active') as SubscriptionStatus,\n planReference: requiredString(\n raw?.planReference ?? raw?.plan_reference,\n 'subscription.planReference',\n ),\n planName: optionalString(raw?.planName ?? raw?.plan_name),\n interval: (raw?.interval ?? 'monthly') as SubscriptionInterval,\n amount: Number(raw?.amount ?? 0),\n currency: optionalString(raw?.currency) ?? 'USD',\n currentPeriodStart: requiredDateString(\n raw?.currentPeriodStart ?? raw?.current_period_start,\n 'subscription.currentPeriodStart',\n ),\n currentPeriodEnd: requiredDateString(\n raw?.currentPeriodEnd ?? raw?.current_period_end,\n 'subscription.currentPeriodEnd',\n ),\n trialEnd: optionalDateString(raw?.trialEnd ?? raw?.trial_end),\n cancelAtPeriodEnd: Boolean(raw?.cancelAtPeriodEnd ?? raw?.cancel_at_period_end ?? false),\n failureCount: Number(raw?.failureCount ?? raw?.failure_count ?? 0),\n lastPaymentAt: optionalDateString(raw?.lastPaymentAt ?? raw?.last_payment_at),\n pausedAt: optionalDateString(raw?.pausedAt ?? raw?.paused_at),\n cancelledAt: optionalDateString(raw?.cancelledAt ?? raw?.cancelled_at),\n metadata: objectOrEmpty(raw?.metadata),\n createdAt: optionalDateString(raw?.createdAt ?? raw?.created_at) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt ?? raw?.updated_at) ?? undefined,\n };\n}\n\nexport function normalizeCustomer(raw: any): Customer | null {\n if (!raw) return null;\n return {\n id: requiredString(raw?.id, 'customer.id'),\n externalId: optionalString(raw?.externalId ?? raw?.external_id),\n email: optionalString(raw?.email),\n firstName: optionalString(raw?.firstName ?? raw?.first_name),\n lastName: optionalString(raw?.lastName ?? raw?.last_name),\n phone: optionalString(raw?.phone),\n metadata: objectOrEmpty(raw?.metadata),\n createdAt: optionalDateString(raw?.createdAt ?? raw?.created_at) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt ?? raw?.updated_at) ?? undefined,\n };\n}\n\nexport function normalizeCheckoutSession(raw: any): CheckoutSession {\n const sessionId = raw?.sessionId ?? raw?.session_id ?? raw?.id;\n return {\n ...raw,\n id: optionalString(raw?.id),\n sessionId: requiredString(sessionId, 'checkoutSession.sessionId'),\n checkoutUrl: optionalString(raw?.checkoutUrl ?? raw?.checkout_url),\n embedToken: optionalString(raw?.embedToken ?? raw?.embed_token),\n expiresAt: optionalDateString(raw?.expiresAt ?? raw?.expires_at) ?? undefined,\n };\n}\n\nexport function normalizeTrialEligibility(raw: any): TrialEligibilityResult {\n return {\n eligible: Boolean(raw?.eligible),\n reason: raw?.reason,\n };\n}\n\nexport function normalizePagination(raw: any): Pagination | undefined {\n if (!raw) return undefined;\n return {\n cursor: raw.cursor ?? null,\n hasMore: Boolean(raw.hasMore ?? raw.has_more ?? false),\n };\n}\n\nexport function toIso(value: string | Date | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value === 'string' && value.length > 0) return value;\n throw new Error(`Invalid Throttle response: missing ${label}`);\n}\n\nfunction optionalString(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\nfunction requiredDateString(value: unknown, label: string): string {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n throw new Error(`Invalid Throttle response: missing ${label}`);\n}\n\nfunction optionalDateString(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n return null;\n}\n\nfunction objectOrEmpty(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n","import { ThrottleSubscriptionsError } from './errors';\nimport {\n normalizeCheckoutSession,\n normalizeCustomer,\n normalizePagination,\n normalizeSubscription,\n normalizeTrialEligibility,\n toIso,\n} from './normalize';\nimport type {\n ChangePlanInput,\n CheckoutSession,\n CreateCheckoutSessionInput,\n CreateSubscriptionInput,\n Customer,\n ListSubscriptionsParams,\n PaginatedSubscriptions,\n Subscription,\n SubscriptionsClientContract,\n SubscriptionsClientOptions,\n ThrottleEnvelope,\n TrialEligibilityInput,\n TrialEligibilityResult,\n UpdateSubscriptionInput,\n} from './types';\n\nexport class SubscriptionsClient implements SubscriptionsClientContract {\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly fetcher?: SubscriptionsClientOptions['fetcher'];\n private readonly timeoutMs: number;\n\n constructor(opts: SubscriptionsClientOptions = {}) {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!fetchImpl) throw new Error('SubscriptionsClient: fetch is required');\n if (!opts.apiKey && !opts.fetcher) {\n throw new Error('SubscriptionsClient: apiKey or fetcher is required');\n }\n\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? 'https://api.usethrottle.dev').replace(/\\/+$/, '');\n this.fetchImpl = fetchImpl;\n this.fetcher = opts.fetcher;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n async request<T = unknown>(method: string, path: string, body?: unknown): Promise<T> {\n const envelope = await this.requestEnvelope<T>(method, path, body);\n return envelope.data as T;\n }\n\n async requestEnvelope<T = unknown>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<ThrottleEnvelope<T>> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n\n try {\n const init: RequestInit = {\n method,\n headers: {\n 'content-type': 'application/json',\n ...(this.apiKey ? { 'x-api-key': this.apiKey } : {}),\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: ctrl.signal,\n };\n\n const res = this.fetcher\n ? await this.fetcher(path, init)\n : await this.fetchImpl(toAbsoluteUrl(this.baseUrl, path), init);\n\n if (res.status === 204) return { data: undefined as T };\n\n const json = (await res.json().catch(() => ({}))) as ThrottleEnvelope<T>;\n if (!res.ok) {\n throw new ThrottleSubscriptionsError({\n code: json.error?.code ?? 'unknown_error',\n message: json.error?.message ?? `HTTP ${res.status}`,\n statusCode: res.status,\n details: json.error?.details,\n });\n }\n return json;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async create(input: CreateSubscriptionInput): Promise<Subscription> {\n const customerId = await this.resolveCustomerId(input.customerId, input.externalCustomerId);\n const data = await this.request<unknown>('POST', '/api/v1/subscriptions', {\n storeId: input.storeId,\n customerId,\n planReference: input.planReference,\n planName: input.planName,\n interval: input.interval,\n amount: input.amount,\n currency: input.currency,\n currentPeriodStart: toIso(input.currentPeriodStart),\n currentPeriodEnd: toIso(input.currentPeriodEnd),\n trialEnd: toIso(input.trialEnd),\n metadata: input.metadata ?? {},\n });\n return normalizeSubscription(data);\n }\n\n async list(params: ListSubscriptionsParams = {}): Promise<PaginatedSubscriptions> {\n const query = new URLSearchParams();\n setQuery(query, 'customerId', params.customerId);\n setQuery(query, 'externalCustomerId', params.externalCustomerId);\n setQuery(query, 'status', params.status);\n setQuery(query, 'interval', params.interval);\n setQuery(query, 'q', params.q);\n setQuery(query, 'cursor', params.cursor);\n setQuery(query, 'limit', params.limit);\n setQuery(query, 'sort', params.sort);\n setQuery(query, 'createdAfter', toIso(params.createdAfter));\n setQuery(query, 'createdBefore', toIso(params.createdBefore));\n\n const suffix = query.toString() ? `?${query}` : '';\n const envelope = await this.requestEnvelope<unknown[]>('GET', `/api/v1/subscriptions${suffix}`);\n return {\n data: (envelope.data ?? []).map(normalizeSubscription),\n pagination: normalizePagination(envelope.meta?.pagination),\n };\n }\n\n async get(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'GET',\n `/api/v1/subscriptions/${encodeURIComponent(id)}`,\n );\n return normalizeSubscription(data);\n }\n\n async update(id: string, input: UpdateSubscriptionInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'PATCH',\n `/api/v1/subscriptions/${encodeURIComponent(id)}`,\n input,\n );\n return normalizeSubscription(data);\n }\n\n changePlan(input: ChangePlanInput): Promise<Subscription> {\n const { id, ...body } = input;\n return this.update(id, body);\n }\n\n async pause(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/pause`,\n );\n return normalizeSubscription(data);\n }\n\n async resume(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/resume`,\n );\n return normalizeSubscription(data);\n }\n\n async cancel(id: string, input: { atPeriodEnd?: boolean } = {}): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`,\n { atPeriodEnd: input.atPeriodEnd ?? false },\n );\n return normalizeSubscription(data);\n }\n\n async createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession> {\n const data = await this.request<unknown>('POST', '/api/v1/checkout/sessions', input);\n return normalizeCheckoutSession(data);\n }\n\n async checkTrialEligibility(input: TrialEligibilityInput): Promise<TrialEligibilityResult> {\n const data = await this.request<unknown>('POST', '/api/v1/subscriptions/eligibility-check', {\n payment_method_id: input.paymentMethodId,\n });\n return normalizeTrialEligibility(data);\n }\n\n async getCustomerByExternalId(externalId: string): Promise<Customer | null> {\n try {\n const data = await this.request<unknown>(\n 'GET',\n `/api/v1/customers/by-external/${encodeURIComponent(externalId)}`,\n );\n return normalizeCustomer(data);\n } catch (err) {\n if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;\n throw err;\n }\n }\n\n private async resolveCustomerId(\n customerId?: string,\n externalCustomerId?: string,\n ): Promise<string> {\n if (customerId) return customerId;\n if (!externalCustomerId) {\n throw new ThrottleSubscriptionsError({\n code: 'invalid_request',\n message: 'customerId or externalCustomerId is required',\n statusCode: 400,\n });\n }\n\n const customer = await this.getCustomerByExternalId(externalCustomerId);\n if (!customer) {\n throw new ThrottleSubscriptionsError({\n code: 'customer_not_found',\n message: 'Customer not found',\n statusCode: 404,\n });\n }\n return customer.id;\n }\n}\n\nexport function createSubscriptionsClient(opts: SubscriptionsClientOptions): SubscriptionsClient {\n return new SubscriptionsClient(opts);\n}\n\nfunction toAbsoluteUrl(baseUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path;\n return `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n}\n\nfunction setQuery(query: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null || value === '') return;\n query.set(key, String(value));\n}\n"],"mappings":"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","names":[]}
|