@usethrottle/payment-methods 0.1.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 ADDED
@@ -0,0 +1,44 @@
1
+ # `@usethrottle/payment-methods`
2
+
3
+ Drop-in saved-card wallet (list, add, set default, remove) for Throttle storefronts. Headless core + React component.
4
+
5
+ ## Package purpose
6
+
7
+ Wraps the Plan-1 `/api/v1/me/payment-methods*` browser routes and the `/api/v1/payment-methods/client-token` + `/api/v1/customers/:id/payment-methods*` server routes behind a typed SDK.
8
+
9
+ ## Entry points
10
+
11
+ | Import path | Environment | What it exports |
12
+ |--------------------------------------------|--------------|---------------------------------------------------------------------------------|
13
+ | `@usethrottle/payment-methods` | browser/React | `createPaymentMethods`, `usePaymentMethods`, `<PaymentMethods>`, types, errors |
14
+ | `@usethrottle/payment-methods/server` | Node.js only | `mintClientToken`, `createServerPaymentMethods` |
15
+
16
+ Never import from `./server` in browser bundles — it only uses `./request`, `./types`, and `./errors`, but the path convention signals server-only intent to bundlers and reviewers.
17
+
18
+ ## SSR isolation rule
19
+
20
+ `@gr4vy/embed-react` accesses browser globals at module evaluation time. It must NEVER be imported eagerly.
21
+
22
+ - `PaymentMethods.tsx` loads `Gr4vyAddCard` via `React.lazy()` only. Do not hoist that import.
23
+ - In Next.js, consumers must wrap `<PaymentMethods>` with `dynamic(() => import(...), { ssr: false })` at any route that SSRs.
24
+ - Tests use jsdom (vitest), which does not provide real browser globals — mock `@gr4vy/embed-react` as needed.
25
+
26
+ ## Test conventions
27
+
28
+ - Runner: **vitest** + **jsdom** (`vitest.config.ts`).
29
+ - Run: `pnpm --filter @usethrottle/payment-methods test`.
30
+ - Typecheck: `pnpm --filter @usethrottle/payment-methods typecheck`.
31
+ - Build: `pnpm --filter @usethrottle/payment-methods build` (tsup, dual CJS + ESM, server subpath).
32
+
33
+ ## API routes consumed
34
+
35
+ | Route | Auth | Used by |
36
+ |----------------------------------------------------|------------------------------|---------------------------------|
37
+ | `POST /api/v1/payment-methods/client-token` | `X-API-Key` (`sk_*`) | `mintClientToken` |
38
+ | `GET /api/v1/me/payment-methods` | `X-Throttle-PM-Token` | `client.list`, hook, component |
39
+ | `PATCH /api/v1/me/payment-methods/:id` | `X-Throttle-PM-Token` | `client.setDefault` |
40
+ | `DELETE /api/v1/me/payment-methods/:id` | `X-Throttle-PM-Token` | `client.remove` |
41
+ | `POST /api/v1/me/payment-methods/embed-token` | `X-Throttle-PM-Token` | `client.startAddCard` |
42
+ | `GET /api/v1/customers/:id/payment-methods` | `X-API-Key` (`sk_*`) | `createServerPaymentMethods.list` |
43
+ | `PATCH /api/v1/customers/:id/payment-methods/:id` | `X-API-Key` (`sk_*`) | `createServerPaymentMethods.setDefault` |
44
+ | `DELETE /api/v1/customers/:id/payment-methods/:id` | `X-API-Key` (`sk_*`) | `createServerPaymentMethods.remove` |
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # `@usethrottle/payment-methods`
2
+
3
+ Drop-in saved-card wallet for Throttle storefronts. Headless core + React component + server helpers.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @usethrottle/payment-methods
9
+ # or
10
+ pnpm add @usethrottle/payment-methods
11
+ ```
12
+
13
+ React and `react-dom` are peer dependencies (>=18).
14
+
15
+ ---
16
+
17
+ ## How it works
18
+
19
+ 1. **Backend** calls `mintClientToken` (from `@usethrottle/payment-methods/server`) using your secret key. It returns a short-lived JWT scoped to one customer.
20
+ 2. **Frontend** passes that token to `<PaymentMethods clientToken={token} />` (or the headless `createPaymentMethods` / `usePaymentMethods`). The token authorises all card management operations — your secret key never reaches the browser.
21
+
22
+ ```
23
+ Your Backend ──POST /api/v1/payment-methods/client-token──► Throttle API
24
+ ◄── { token, expiresAt } ──────────────────────
25
+ Your Backend ──{ token }──────────────────────────────────► Browser
26
+ Browser ──<PaymentMethods clientToken={token} />──────► wallet UI
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Backend: mint a client token
32
+
33
+ ```ts
34
+ // app/api/pm-token/route.ts (Next.js App Router)
35
+ import { mintClientToken } from '@usethrottle/payment-methods/server';
36
+
37
+ export async function POST(request: Request) {
38
+ const user = await getAuthUser(request); // your own auth
39
+ if (!user) return new Response(null, { status: 401 });
40
+
41
+ const customerId = await resolveThrottleCustomerId(user.id); // cus_*
42
+
43
+ const { token, expiresAt } = await mintClientToken({
44
+ apiKey: process.env.THROTTLE_SECRET_KEY!,
45
+ customerId,
46
+ // baseUrl?: 'https://api.usethrottle.dev' (default)
47
+ // environmentId?: 'env_...' (omit to use key default)
48
+ });
49
+
50
+ return Response.json({ token, expiresAt });
51
+ }
52
+ ```
53
+
54
+ `mintClientToken` options:
55
+
56
+ | Option | Type | Required | Description |
57
+ |-----------------|--------|----------|----------------------------------------------------------|
58
+ | `apiKey` | string | yes | Your `sk_*` secret key. |
59
+ | `customerId` | string | yes | Throttle customer ID (`cus_*`). |
60
+ | `baseUrl` | string | no | API base URL (defaults to `https://api.usethrottle.dev`).|
61
+ | `environmentId` | string | no | Override the environment derived from the key. |
62
+
63
+ Returns `{ token: string; expiresAt: string }`.
64
+
65
+ ---
66
+
67
+ ## Frontend: React component
68
+
69
+ ```tsx
70
+ // components/Wallet.tsx
71
+ 'use client';
72
+
73
+ import dynamic from 'next/dynamic';
74
+
75
+ const PaymentMethods = dynamic(
76
+ () => import('@usethrottle/payment-methods').then((m) => m.PaymentMethods),
77
+ { ssr: false },
78
+ );
79
+
80
+ export function Wallet({ clientToken }: { clientToken: string }) {
81
+ return (
82
+ <PaymentMethods
83
+ clientToken={clientToken}
84
+ theme={{
85
+ colorPrimary: '#1D56E8',
86
+ colorText: '#111827',
87
+ colorBorder: '#E5E7EB',
88
+ borderRadius: '8px',
89
+ }}
90
+ onChange={(cards) => console.log('cards updated', cards)}
91
+ />
92
+ );
93
+ }
94
+ ```
95
+
96
+ ### `<PaymentMethods>` props
97
+
98
+ | Prop | Type | Required | Description |
99
+ |---------------|-------------------------|----------|------------------------------------------------------------------------|
100
+ | `clientToken` | string | yes | Short-lived token from `mintClientToken`. |
101
+ | `baseUrl` | string | no | API base URL override. |
102
+ | `theme` | `PaymentMethodsTheme` | no | Colour + radius tokens (see table below). |
103
+ | `onChange` | `(cards: SavedCard[]) => void` | no | Fires after every list change (initial load, set default, remove, add card). |
104
+
105
+ ### `PaymentMethodsTheme` tokens
106
+
107
+ | Token | Default | Description |
108
+ |-----------------|-------------|-------------------------|
109
+ | `colorPrimary` | `#1D56E8` | Accent / button colour. |
110
+ | `colorText` | `#111827` | Body text colour. |
111
+ | `colorBorder` | `#E5E7EB` | Card border colour. |
112
+ | `borderRadius` | `8px` | Corner radius. |
113
+
114
+ ### SSR note
115
+
116
+ `@gr4vy/embed-react` uses browser globals. Always render `<PaymentMethods>` in a client component (`'use client'`) **and** wrap it with `dynamic(..., { ssr: false })` at any route that server-renders. The component itself lazy-loads the Gr4vy embed internally, so it is safe inside a client component; the `dynamic` guard prevents the module from being evaluated during SSR.
117
+
118
+ ---
119
+
120
+ ## Frontend: headless hook
121
+
122
+ ```tsx
123
+ 'use client';
124
+
125
+ import { usePaymentMethods } from '@usethrottle/payment-methods';
126
+
127
+ export function CardList({ clientToken }: { clientToken: string }) {
128
+ const { cards, isLoading, error, setDefault, remove, addCard } =
129
+ usePaymentMethods({ clientToken });
130
+
131
+ if (isLoading) return <p>Loading...</p>;
132
+ if (error) return <p style={{ color: 'red' }}>{error.message}</p>;
133
+
134
+ return (
135
+ <ul>
136
+ {cards.map((card) => (
137
+ <li key={card.id}>
138
+ {card.cardBrand} .... {card.cardLastFour}
139
+ {!card.isDefault && (
140
+ <>
141
+ <button onClick={() => setDefault(card.id)}>Make default</button>
142
+ <button onClick={() => remove(card.id)}>Remove</button>
143
+ </>
144
+ )}
145
+ </li>
146
+ ))}
147
+ <li>
148
+ <button onClick={() => addCard().then((payload) => console.log(payload)).catch(console.error)}>
149
+ + Add card
150
+ </button>
151
+ </li>
152
+ </ul>
153
+ );
154
+ }
155
+ ```
156
+
157
+ `usePaymentMethods` returns:
158
+
159
+ | Key | Type | Description |
160
+ |-------------|---------------------------------------|---------------------------------------------------------------------|
161
+ | `cards` | `SavedCard[]` | Current list of saved cards. |
162
+ | `isLoading` | boolean | True while the initial list is fetching. |
163
+ | `error` | `ThrottlePaymentMethodsError \| null` | Last error, or `null`. |
164
+ | `setDefault`| `(id: string) => Promise<void>` | Promote a card to default; refetches list on success. |
165
+ | `remove` | `(id: string) => Promise<void>` | Remove a card; refetches list on success. |
166
+ | `addCard` | `() => Promise<EmbedPayload>` | Fetches a Gr4vy add-only embed payload for the card-add UI. Unlike `setDefault`/`remove`, it does **not** route failures into `error` — `await` it in a `try/catch` (the `<PaymentMethods>` component does this for you). |
167
+ | `refetch` | `() => Promise<void>` | Manually re-fetches the list. |
168
+
169
+ ---
170
+
171
+ ## Frontend: headless client (no React)
172
+
173
+ ```ts
174
+ import { createPaymentMethods } from '@usethrottle/payment-methods';
175
+
176
+ const client = createPaymentMethods({ clientToken: token });
177
+
178
+ const cards = await client.list();
179
+ await client.setDefault('cpm_...');
180
+ await client.remove('cpm_...');
181
+ const embedPayload = await client.startAddCard();
182
+ ```
183
+
184
+ `createPaymentMethods({ clientToken, baseUrl? })` returns `{ list, setDefault, remove, startAddCard }`.
185
+
186
+ ---
187
+
188
+ ## Backend: server CRUD
189
+
190
+ Use `createServerPaymentMethods` when your backend needs to manage a customer's cards directly (e.g. admin tooling), without a browser session.
191
+
192
+ ```ts
193
+ import { createServerPaymentMethods } from '@usethrottle/payment-methods/server';
194
+
195
+ const server = createServerPaymentMethods({
196
+ apiKey: process.env.THROTTLE_SECRET_KEY!,
197
+ customerId: 'cus_...',
198
+ });
199
+
200
+ const cards = await server.list();
201
+ await server.setDefault('cpm_...');
202
+ await server.remove('cpm_...');
203
+ // Note: startAddCard is not available on the server client —
204
+ // the Gr4vy embed flow is inherently browser-side.
205
+ ```
206
+
207
+ `createServerPaymentMethods` options match `mintClientToken` (`apiKey`, `customerId`, `baseUrl?`, `environmentId?`).
208
+
209
+ ---
210
+
211
+ ## Error handling
212
+
213
+ All errors thrown by this package are instances of `ThrottlePaymentMethodsError` (extends `ThrottleError`):
214
+
215
+ ```ts
216
+ import { isThrottleError, ThrottlePaymentMethodsError } from '@usethrottle/payment-methods';
217
+
218
+ try {
219
+ await client.remove(cardId);
220
+ } catch (err) {
221
+ if (err instanceof ThrottlePaymentMethodsError) {
222
+ if (err.code === 'cannot_remove_default') {
223
+ // 409 — buyer must set another card as default first
224
+ alert('Set another card as default before removing this one.');
225
+ } else {
226
+ console.error(err.code, err.message, err.statusCode);
227
+ }
228
+ }
229
+ }
230
+
231
+ // isThrottleError also works (catches both ThrottleError and ThrottlePaymentMethodsError):
232
+ if (isThrottleError(err)) { /* … */ }
233
+ ```
234
+
235
+ Common error codes:
236
+
237
+ | Status | Code | When |
238
+ |--------|-------------------------|----------------------------------------------------------------------|
239
+ | 401 | `unauthorized` | Token missing, malformed, or expired. Mint a fresh token and retry. |
240
+ | 403 | `forbidden` | PM token used outside the `/me/payment-methods` surface. |
241
+ | 404 | `not_found` | Card ID not found or already removed. |
242
+ | 409 | `cannot_remove_default` | Removing the default card while an active subscription is attached. |
243
+
244
+ ---
245
+
246
+ ## `SavedCard` shape
247
+
248
+ ```ts
249
+ interface SavedCard {
250
+ id: string; // "cpm_*"
251
+ methodType: string; // "card" in v1
252
+ cardBrand: string | null; // "visa" | "mastercard" | "amex" | ...
253
+ cardLastFour: string | null; // "4242"
254
+ cardExpMonth: number | null; // 1-12
255
+ cardExpYear: number | null; // 4-digit year
256
+ isDefault: boolean;
257
+ }
258
+ ```
@@ -0,0 +1,87 @@
1
+ // src/Gr4vyAddCard.tsx
2
+ import { useRef } from "react";
3
+ import Gr4vyEmbed from "@gr4vy/embed-react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ function Gr4vyAddCard({ payload, theme, onAdded, onCancel, onError }) {
6
+ const embedRef = useRef(null);
7
+ const colors = theme ? {
8
+ ...theme.colorPrimary !== void 0 ? { primary: theme.colorPrimary } : {},
9
+ ...theme.colorText !== void 0 ? { text: theme.colorText } : {},
10
+ ...theme.colorBorder !== void 0 ? { inputBorder: theme.colorBorder } : {}
11
+ } : {};
12
+ const gr4vyTheme = Object.keys(colors).length ? { colors } : void 0;
13
+ return /* @__PURE__ */ jsxs("div", { children: [
14
+ /* @__PURE__ */ jsx(
15
+ Gr4vyEmbed,
16
+ {
17
+ ref: embedRef,
18
+ gr4vyId: payload.gr4vyId,
19
+ environment: payload.environment,
20
+ token: payload.embedToken,
21
+ amount: payload.amount,
22
+ currency: payload.currency,
23
+ merchantAccountId: payload.merchantAccountId,
24
+ intent: "capture",
25
+ paymentSource: "recurring",
26
+ store: true,
27
+ display: "addOnly",
28
+ ...gr4vyTheme !== void 0 ? { theme: gr4vyTheme } : {},
29
+ onComplete: (txn) => {
30
+ onAdded(txn.paymentMethod?.id);
31
+ },
32
+ onEvent: (eventName, data) => {
33
+ if (eventName === "transactionFailed") {
34
+ onError(data);
35
+ }
36
+ }
37
+ }
38
+ ),
39
+ /* @__PURE__ */ jsx(
40
+ "button",
41
+ {
42
+ type: "button",
43
+ onClick: () => {
44
+ embedRef.current?.submit();
45
+ },
46
+ style: {
47
+ marginTop: "12px",
48
+ padding: "8px 16px",
49
+ backgroundColor: theme?.colorPrimary ?? "#1D56E8",
50
+ color: "#fff",
51
+ border: "none",
52
+ borderRadius: "8px",
53
+ fontSize: "14px",
54
+ fontWeight: 600,
55
+ cursor: "pointer",
56
+ width: "100%"
57
+ },
58
+ children: "Save card"
59
+ }
60
+ ),
61
+ /* @__PURE__ */ jsx(
62
+ "button",
63
+ {
64
+ type: "button",
65
+ onClick: onCancel,
66
+ style: {
67
+ marginTop: "8px",
68
+ padding: "8px 16px",
69
+ backgroundColor: "transparent",
70
+ color: theme?.colorText ?? "#111827",
71
+ border: `1px solid ${theme?.colorBorder ?? "#E5E7EB"}`,
72
+ borderRadius: "8px",
73
+ fontSize: "14px",
74
+ cursor: "pointer",
75
+ width: "100%"
76
+ },
77
+ children: "Cancel"
78
+ }
79
+ )
80
+ ] });
81
+ }
82
+ var Gr4vyAddCard_default = Gr4vyAddCard;
83
+ export {
84
+ Gr4vyAddCard,
85
+ Gr4vyAddCard_default as default
86
+ };
87
+ //# sourceMappingURL=Gr4vyAddCard-J3HC6KTK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Gr4vyAddCard.tsx"],"sourcesContent":["/**\n * Gr4vyAddCard — lazy-loaded vault-only embed wrapper.\n *\n * THIS FILE MUST ONLY BE LOADED VIA React.lazy(() => import('./Gr4vyAddCard'))\n * from PaymentMethods.tsx. Never import it at the module top-level of any file\n * that is part of the server bundle — @gr4vy/embed-react touches `window` at\n * module evaluation time and will throw `ReferenceError: window is not defined`\n * in SSR contexts.\n */\nimport React, { useRef } from 'react';\nimport Gr4vyEmbed, { type EmbedInstance } from '@gr4vy/embed-react';\nimport type { EmbedPayload, PaymentMethodsTheme } from './types';\n\ninterface Gr4vyTransaction {\n id: string;\n status: string;\n paymentMethod?: { id?: string };\n}\n\nexport interface Gr4vyAddCardProps {\n payload: EmbedPayload;\n theme?: PaymentMethodsTheme;\n onAdded: (paymentMethodId?: string) => void;\n onCancel: () => void;\n onError: (err: unknown) => void;\n}\n\nexport function Gr4vyAddCard({ payload, theme, onAdded, onCancel, onError }: Gr4vyAddCardProps) {\n const embedRef = useRef<EmbedInstance>(null);\n\n const colors = theme\n ? {\n ...(theme.colorPrimary !== undefined ? { primary: theme.colorPrimary } : {}),\n ...(theme.colorText !== undefined ? { text: theme.colorText } : {}),\n ...(theme.colorBorder !== undefined ? { inputBorder: theme.colorBorder } : {}),\n }\n : {};\n const gr4vyTheme = Object.keys(colors).length ? { colors } : undefined;\n\n return (\n <div>\n <Gr4vyEmbed\n ref={embedRef}\n gr4vyId={payload.gr4vyId}\n environment={payload.environment}\n token={payload.embedToken}\n amount={payload.amount}\n currency={payload.currency}\n merchantAccountId={payload.merchantAccountId}\n intent=\"capture\"\n paymentSource=\"recurring\"\n store={true}\n display=\"addOnly\"\n {...(gr4vyTheme !== undefined ? { theme: gr4vyTheme } : {})}\n onComplete={(txn: Gr4vyTransaction) => {\n onAdded(txn.paymentMethod?.id);\n }}\n onEvent={(eventName: string, data: unknown) => {\n if (eventName === 'transactionFailed') {\n onError(data);\n }\n }}\n />\n\n <button\n type=\"button\"\n onClick={() => {\n embedRef.current?.submit();\n }}\n style={{\n marginTop: '12px',\n padding: '8px 16px',\n backgroundColor: theme?.colorPrimary ?? '#1D56E8',\n color: '#fff',\n border: 'none',\n borderRadius: '8px',\n fontSize: '14px',\n fontWeight: 600,\n cursor: 'pointer',\n width: '100%',\n }}\n >\n Save card\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n style={{\n marginTop: '8px',\n padding: '8px 16px',\n backgroundColor: 'transparent',\n color: theme?.colorText ?? '#111827',\n border: `1px solid ${theme?.colorBorder ?? '#E5E7EB'}`,\n borderRadius: '8px',\n fontSize: '14px',\n cursor: 'pointer',\n width: '100%',\n }}\n >\n Cancel\n </button>\n </div>\n );\n}\n\nexport default Gr4vyAddCard;\n"],"mappings":";AASA,SAAgB,cAAc;AAC9B,OAAO,gBAAwC;AA8B3C,SACE,KADF;AAbG,SAAS,aAAa,EAAE,SAAS,OAAO,SAAS,UAAU,QAAQ,GAAsB;AAC9F,QAAM,WAAW,OAAsB,IAAI;AAE3C,QAAM,SAAS,QACX;AAAA,IACE,GAAI,MAAM,iBAAiB,SAAY,EAAE,SAAS,MAAM,aAAa,IAAI,CAAC;AAAA,IAC1E,GAAI,MAAM,cAAc,SAAY,EAAE,MAAM,MAAM,UAAU,IAAI,CAAC;AAAA,IACjE,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,EAC9E,IACA,CAAC;AACL,QAAM,aAAa,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,OAAO,IAAI;AAE7D,SACE,qBAAC,SACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,SAAS,QAAQ;AAAA,QACjB,aAAa,QAAQ;AAAA,QACrB,OAAO,QAAQ;AAAA,QACf,QAAQ,QAAQ;AAAA,QAChB,UAAU,QAAQ;AAAA,QAClB,mBAAmB,QAAQ;AAAA,QAC3B,QAAO;AAAA,QACP,eAAc;AAAA,QACd,OAAO;AAAA,QACP,SAAQ;AAAA,QACP,GAAI,eAAe,SAAY,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,QACzD,YAAY,CAAC,QAA0B;AACrC,kBAAQ,IAAI,eAAe,EAAE;AAAA,QAC/B;AAAA,QACA,SAAS,CAAC,WAAmB,SAAkB;AAC7C,cAAI,cAAc,qBAAqB;AACrC,oBAAQ,IAAI;AAAA,UACd;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM;AACb,mBAAS,SAAS,OAAO;AAAA,QAC3B;AAAA,QACA,OAAO;AAAA,UACL,WAAW;AAAA,UACX,SAAS;AAAA,UACT,iBAAiB,OAAO,gBAAgB;AAAA,UACxC,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,UACL,WAAW;AAAA,UACX,SAAS;AAAA,UACT,iBAAiB;AAAA,UACjB,OAAO,OAAO,aAAa;AAAA,UAC3B,QAAQ,aAAa,OAAO,eAAe,SAAS;AAAA,UACpD,cAAc;AAAA,UACd,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,QACD;AAAA;AAAA,IAED;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;","names":[]}
@@ -0,0 +1,49 @@
1
+ // src/errors.ts
2
+ import { ThrottleError } from "@usethrottle/errors";
3
+ import { ThrottleError as ThrottleError2, isThrottleError } from "@usethrottle/errors";
4
+ var ThrottlePaymentMethodsError = class extends ThrottleError {
5
+ constructor(args) {
6
+ super(args);
7
+ this.name = "ThrottlePaymentMethodsError";
8
+ }
9
+ };
10
+
11
+ // src/types.ts
12
+ var DEFAULT_BASE_URL = "https://api.usethrottle.dev";
13
+
14
+ // src/request.ts
15
+ async function request(input) {
16
+ const res = await fetch(`${input.baseUrl}${input.path}`, {
17
+ method: input.method,
18
+ headers: {
19
+ ...input.body !== void 0 ? { "Content-Type": "application/json" } : {},
20
+ ...input.headers ?? {}
21
+ },
22
+ ...input.body !== void 0 ? { body: JSON.stringify(input.body) } : {}
23
+ });
24
+ const text = await res.text();
25
+ let parsed = {};
26
+ try {
27
+ parsed = text ? JSON.parse(text) : {};
28
+ } catch {
29
+ }
30
+ if (!res.ok) {
31
+ const e = parsed?.error ?? {};
32
+ throw new ThrottlePaymentMethodsError({
33
+ code: e.code ?? "request_failed",
34
+ message: e.message ?? `Request failed with ${res.status}`,
35
+ statusCode: res.status,
36
+ details: e.details
37
+ });
38
+ }
39
+ return parsed;
40
+ }
41
+
42
+ export {
43
+ ThrottlePaymentMethodsError,
44
+ ThrottleError2 as ThrottleError,
45
+ isThrottleError,
46
+ DEFAULT_BASE_URL,
47
+ request
48
+ };
49
+ //# sourceMappingURL=chunk-T2CV3T5K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/request.ts"],"sourcesContent":["import { ThrottleError } from '@usethrottle/errors';\n\nexport class ThrottlePaymentMethodsError extends ThrottleError {\n constructor(args: { code: string; message: string; statusCode: number; details?: unknown }) {\n super(args);\n this.name = 'ThrottlePaymentMethodsError';\n }\n}\n\nexport { ThrottleError, isThrottleError } from '@usethrottle/errors';\n","export interface SavedCard {\n id: string;\n methodType: string;\n cardBrand: string | null;\n cardLastFour: string | null;\n cardExpMonth: number | null;\n cardExpYear: number | null;\n isDefault: boolean;\n}\n\nexport interface EmbedPayload {\n embedToken: string;\n checkoutSessionId: string;\n amount: number;\n currency: string;\n gr4vyId: string;\n merchantAccountId?: string;\n environment: 'sandbox' | 'production';\n hostedUrl?: string;\n embedUrl?: string;\n}\n\nexport interface MintedClientToken {\n token: string;\n expiresAt: string;\n}\n\nexport interface PaymentMethodsTheme {\n colorPrimary?: string;\n colorText?: string;\n colorBorder?: string;\n borderRadius?: string;\n}\n\nexport const DEFAULT_BASE_URL = 'https://api.usethrottle.dev';\n","import { ThrottlePaymentMethodsError } from './errors';\n\nexport async function request(input: {\n method: string;\n path: string;\n baseUrl: string;\n headers?: Record<string, string>;\n body?: unknown;\n}): Promise<any> {\n const res = await fetch(`${input.baseUrl}${input.path}`, {\n method: input.method,\n headers: {\n ...(input.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...(input.headers ?? {}),\n },\n ...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}),\n });\n const text = await res.text();\n let parsed: any = {};\n try {\n parsed = text ? JSON.parse(text) : {};\n } catch {\n // Non-JSON body (e.g. an HTML 502/503 gateway page). Leave parsed as {} so\n // the non-2xx branch below still throws a typed ThrottlePaymentMethodsError\n // (code 'request_failed') rather than leaking a raw SyntaxError.\n }\n if (!res.ok) {\n const e = parsed?.error ?? {};\n throw new ThrottlePaymentMethodsError({\n code: e.code ?? 'request_failed',\n message: e.message ?? `Request failed with ${res.status}`,\n statusCode: res.status,\n details: e.details,\n });\n }\n return parsed;\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAS9B,SAAS,iBAAAA,gBAAe,uBAAuB;AAPxC,IAAM,8BAAN,cAA0C,cAAc;AAAA,EAC7D,YAAY,MAAgF;AAC1F,UAAM,IAAI;AACV,SAAK,OAAO;AAAA,EACd;AACF;;;AC2BO,IAAM,mBAAmB;;;AChChC,eAAsB,QAAQ,OAMb;AACf,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,GAAG,MAAM,IAAI,IAAI;AAAA,IACvD,QAAQ,MAAM;AAAA,IACd,SAAS;AAAA,MACP,GAAI,MAAM,SAAS,SAAY,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,MACzE,GAAI,MAAM,WAAW,CAAC;AAAA,IACxB;AAAA,IACA,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,MAAM,IAAI,EAAE,IAAI,CAAC;AAAA,EACzE,CAAC;AACD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,SAAc,CAAC;AACnB,MAAI;AACF,aAAS,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,EACtC,QAAQ;AAAA,EAIR;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,QAAQ,SAAS,CAAC;AAC5B,UAAM,IAAI,4BAA4B;AAAA,MACpC,MAAM,EAAE,QAAQ;AAAA,MAChB,SAAS,EAAE,WAAW,uBAAuB,IAAI,MAAM;AAAA,MACvD,YAAY,IAAI;AAAA,MAChB,SAAS,EAAE;AAAA,IACb,CAAC;AAAA,EACH;AACA,SAAO;AACT;","names":["ThrottleError"]}