@elapse/sdk 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.
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Event types (FR-SDK-023; detailed doc §5.1, §5.3). The union is exhaustive
3
+ * for the six MVP types and keeps a fallback member for types this SDK version
4
+ * does not know: a valid signature on a new type is still a genuine event.
5
+ */
6
+ type EventType = "checkout.session.completed" | "subscription.created" | "subscription.updated" | "subscription.canceled" | "invoice.settled" | "invoice.payment_failed";
7
+ type SubscriptionStatus = "incomplete" | "active" | "paused" | "canceled";
8
+ interface EventBase {
9
+ id: `evt_${string}` | string;
10
+ object: "event";
11
+ created: number;
12
+ livemode: boolean;
13
+ pending_webhooks: number;
14
+ request?: {
15
+ id: string | null;
16
+ idempotency_key: string | null;
17
+ };
18
+ }
19
+ interface SubscriptionObject {
20
+ id: string;
21
+ object: "subscription";
22
+ status: SubscriptionStatus;
23
+ product: string;
24
+ customer: string;
25
+ rate_usd_per_second: string;
26
+ started_at: number | null;
27
+ paused_at: number | null;
28
+ canceled_at: number | null;
29
+ ended_reason: "canceled" | "cap_reached" | null;
30
+ max_duration_seconds: number | null;
31
+ max_escrow_usd: string | null;
32
+ funded_usd: string;
33
+ settled_usd: string;
34
+ seconds_elapsed: number;
35
+ currency: "ausd";
36
+ livemode: boolean;
37
+ created: number;
38
+ [extra: string]: unknown;
39
+ }
40
+ /** §5.3: cumulative totals for the subscription (BR-API-008). */
41
+ interface CanceledSubscriptionObject extends SubscriptionObject {
42
+ status: "canceled";
43
+ seconds_elapsed: number;
44
+ amount_settled: string;
45
+ }
46
+ interface InvoiceObject {
47
+ id: string;
48
+ object: "invoice";
49
+ subscription: string;
50
+ period_start: number;
51
+ period_end: number;
52
+ seconds: number;
53
+ amount_settled: string;
54
+ gross: string;
55
+ fee: string;
56
+ net: string;
57
+ currency: "ausd";
58
+ status: "paid" | "failed";
59
+ livemode: boolean;
60
+ created: number;
61
+ [extra: string]: unknown;
62
+ }
63
+ interface CheckoutSessionCompletedObject {
64
+ id: string;
65
+ object: "checkout.session";
66
+ status: "complete";
67
+ subscription: string;
68
+ customer: string;
69
+ product: string;
70
+ livemode: boolean;
71
+ created: number;
72
+ [extra: string]: unknown;
73
+ }
74
+ type ElapseEvent = (EventBase & {
75
+ type: "checkout.session.completed";
76
+ data: {
77
+ object: CheckoutSessionCompletedObject;
78
+ };
79
+ }) | (EventBase & {
80
+ type: "subscription.created";
81
+ data: {
82
+ object: SubscriptionObject;
83
+ };
84
+ }) | (EventBase & {
85
+ type: "subscription.updated";
86
+ data: {
87
+ object: SubscriptionObject;
88
+ };
89
+ }) | (EventBase & {
90
+ type: "subscription.canceled";
91
+ data: {
92
+ object: CanceledSubscriptionObject;
93
+ };
94
+ }) | (EventBase & {
95
+ type: "invoice.settled";
96
+ data: {
97
+ object: InvoiceObject;
98
+ };
99
+ }) | (EventBase & {
100
+ type: "invoice.payment_failed";
101
+ data: {
102
+ object: InvoiceObject;
103
+ };
104
+ })
105
+ /**
106
+ * A type newer than this SDK version. It is verified and returned all the
107
+ * same (a valid signature is what makes it genuine); switch on `type` and
108
+ * ignore what you do not handle. `data.object` is typed `never` here so that
109
+ * narrowing on a known `type` still gives you the known shape; in a
110
+ * `default` branch cast it, e.g. `evt.data.object as Record<string, unknown>`.
111
+ */
112
+ | (EventBase & {
113
+ type: string;
114
+ data: {
115
+ object: never;
116
+ };
117
+ });
118
+
119
+ /** Per-request options accepted as the last argument of every method (FR-SDK-014). */
120
+ interface RequestOptions {
121
+ /** Reused across this call's retries. Generated as a UUID for create/cancel when absent (FR-SDK-013). */
122
+ idempotencyKey?: string;
123
+ /** Overrides the client default (30 s). */
124
+ timeoutMs?: number;
125
+ }
126
+ interface TransportConfig {
127
+ secretKey: string;
128
+ baseUrl: string;
129
+ maxRetries: number;
130
+ timeoutMs: number;
131
+ fetchImpl?: typeof fetch;
132
+ }
133
+ /**
134
+ * The one HTTP path (FR-SDK-010–014). Retries network errors, 429 and 5xx
135
+ * with `500 ms × 2ⁿ ± 25 %` jitter up to `maxRetries`, honouring `Retry-After`
136
+ * on 429. 4xx other than 429 are thrown at once. Non-2xx bodies in the
137
+ * FR-API-082 shape map to the error classes.
138
+ */
139
+ declare class Transport {
140
+ #private;
141
+ constructor(cfg: TransportConfig);
142
+ /** Overridable for tests. */
143
+ _sleep(ms: number): Promise<void>;
144
+ request<T>(method: "GET" | "POST" | "DELETE", path: string, body?: Record<string, unknown>, opts?: RequestOptions): Promise<T>;
145
+ }
146
+
147
+ /** Stripe-style list page (API FR-API-080). */
148
+ interface List<T> {
149
+ object: "list";
150
+ data: T[];
151
+ has_more: boolean;
152
+ url: string;
153
+ }
154
+ interface ListParams {
155
+ /** 1–100, default 10. */
156
+ limit?: number;
157
+ /** Id of the last object of the previous page. */
158
+ startingAfter?: string;
159
+ }
160
+ interface Product {
161
+ id: string;
162
+ object: "product";
163
+ name: string;
164
+ description: string | null;
165
+ /** USD per second, decimal string. Never parse it to a number for money math. */
166
+ rate_usd_per_second: string;
167
+ rate_per_second_wei: string;
168
+ currency: "ausd";
169
+ allow_pause: boolean;
170
+ active: boolean;
171
+ livemode: boolean;
172
+ created: number;
173
+ }
174
+ interface CheckoutSession {
175
+ id: string;
176
+ object: "checkout.session";
177
+ status: "open" | "complete" | "expired";
178
+ /** Send the subscriber here. */
179
+ url: string;
180
+ livemode: boolean;
181
+ created: number;
182
+ expires_at: number;
183
+ success_url: string;
184
+ cancel_url: string;
185
+ product: Product;
186
+ merchant: {
187
+ name: string;
188
+ logo_url: string | null;
189
+ accent: string | null;
190
+ support_url: string | null;
191
+ };
192
+ customer: string | null;
193
+ subscription: string | null;
194
+ max_duration_seconds: number | null;
195
+ max_escrow_usd: string | null;
196
+ }
197
+ type Subscription = SubscriptionObject;
198
+ interface Customer {
199
+ id: string;
200
+ object: "customer";
201
+ email: string | null;
202
+ livemode: boolean;
203
+ created: number;
204
+ [extra: string]: unknown;
205
+ }
206
+ interface Invoice {
207
+ id: string;
208
+ object: "invoice";
209
+ subscription: string;
210
+ period_start: number;
211
+ period_end: number;
212
+ seconds: number;
213
+ amount_settled: string;
214
+ gross: string;
215
+ fee: string;
216
+ net: string;
217
+ currency: "ausd";
218
+ status: "paid" | "failed";
219
+ livemode: boolean;
220
+ created: number;
221
+ }
222
+ /** `products.create/retrieve/list` (FR-SDK-002, FR-SDK-003). */
223
+ declare function products(t: Transport): {
224
+ create(params: {
225
+ name: string;
226
+ rateUsdPerSecond: string;
227
+ allowPause?: boolean;
228
+ description?: string;
229
+ }, opts?: RequestOptions): Promise<Product>;
230
+ retrieve(id: string, opts?: RequestOptions): Promise<Product>;
231
+ list(params?: ListParams, opts?: RequestOptions): Promise<List<Product>>;
232
+ };
233
+ /** `checkout.sessions.create` (FR-SDK-004). */
234
+ declare function checkout(t: Transport): {
235
+ sessions: {
236
+ create(params: {
237
+ product: string;
238
+ successUrl: string;
239
+ cancelUrl: string;
240
+ maxDurationSeconds?: number;
241
+ }, opts?: RequestOptions): Promise<CheckoutSession>;
242
+ };
243
+ };
244
+ /** `subscriptions.retrieve/list/cancel` (FR-SDK-005, FR-SDK-008). No pause or resume. */
245
+ declare function subscriptions(t: Transport): {
246
+ retrieve(id: string, opts?: RequestOptions): Promise<Subscription>;
247
+ list(params?: ListParams & {
248
+ customer?: string;
249
+ product?: string;
250
+ status?: SubscriptionStatus;
251
+ }, opts?: RequestOptions): Promise<List<Subscription>>;
252
+ /** Asks the platform to end the meter; the `canceled` status arrives via webhook once the chain confirms (API FR-API-042). */
253
+ cancel(id: string, opts?: RequestOptions): Promise<Subscription>;
254
+ };
255
+ /** `customers.retrieve` (FR-SDK-006). */
256
+ declare function customers(t: Transport): {
257
+ retrieve(id: string, opts?: RequestOptions): Promise<Customer>;
258
+ };
259
+ /** `invoices.list` (FR-SDK-006). */
260
+ declare function invoices(t: Transport): {
261
+ list(params?: ListParams & {
262
+ subscription?: string;
263
+ customer?: string;
264
+ }, opts?: RequestOptions): Promise<List<Invoice>>;
265
+ };
266
+
267
+ /** FR-SDK-022: test hooks. Defaults: 300 s tolerance, wall clock. */
268
+ interface ConstructEventOptions {
269
+ /** Seconds. `Infinity` disables the age check (tests only). */
270
+ tolerance?: number;
271
+ /** Unix seconds. */
272
+ now?: () => number;
273
+ }
274
+ /**
275
+ * Verify `X-Elapse-Signature` and parse the event (FR-SDK-020, FR-SDK-021;
276
+ * detailed doc §4.4). Header: `t=<unix>,v1=<hex>[,v1=<hex>…]`. The signed
277
+ * string is `${t}.${rawBody}`. Every `v1` is collected and every
278
+ * (secret, v1) pair is compared in constant time with no early exit, so a
279
+ * merchant mid secret-roll verifies against either secret and timing reveals
280
+ * nothing about which matched. Always pass the raw request body: never a
281
+ * re-serialised object (BR-SDK-003).
282
+ */
283
+ declare function constructEvent(rawBody: string | Uint8Array, header: string | undefined, secret: string | readonly string[] | undefined, options?: ConstructEventOptions): ElapseEvent;
284
+
285
+ /**
286
+ * Error classes (FR-SDK-011, FR-SDK-021). Every error the SDK throws extends
287
+ * `ElapseError`, so `catch (e) { if (e instanceof ElapseError) … }` is enough.
288
+ * Messages never contain the secret key, a webhook secret, or a payload body.
289
+ */
290
+ declare class ElapseError extends Error {
291
+ /** HTTP status, when the error came from a response. */
292
+ readonly status: number | undefined;
293
+ /** The API's `error.type` (`invalid_request_error`, …), when present. */
294
+ readonly type: string | undefined;
295
+ readonly code: string | undefined;
296
+ readonly param: string | undefined;
297
+ readonly requestId: string | undefined;
298
+ constructor(message: string, fields?: {
299
+ status?: number;
300
+ type?: string;
301
+ code?: string;
302
+ param?: string;
303
+ requestId?: string;
304
+ });
305
+ }
306
+ /** 401/403: bad, missing or revoked key. */
307
+ declare class ElapseAuthenticationError extends ElapseError {
308
+ }
309
+ /** 400/404/422, and client-side validation before a request is sent. */
310
+ declare class ElapseInvalidRequestError extends ElapseError {
311
+ }
312
+ /** 429. `retryAfter` in seconds when the API sent it. */
313
+ declare class ElapseRateLimitError extends ElapseError {
314
+ readonly retryAfter: number | undefined;
315
+ constructor(message: string, fields?: ConstructorParameters<typeof ElapseError>[1] & {
316
+ retryAfter?: number;
317
+ });
318
+ }
319
+ /** 5xx, unparseable bodies, network failures and timeouts (`code: "timeout"`). */
320
+ declare class ElapseAPIError extends ElapseError {
321
+ }
322
+ /** `constructEvent` refused the payload; the reason is in the message, never the body. */
323
+ declare class ElapseSignatureVerificationError extends ElapseError {
324
+ }
325
+
326
+ interface ElapseConfig {
327
+ /** `sk_test_…` or `sk_live_…`, usually `process.env.ELAPSE_SECRET_KEY`. Server-side only. `undefined` throws at construction. */
328
+ secretKey: string | undefined;
329
+ /** Defaults to `https://api.elapse.dev`. */
330
+ baseUrl?: string;
331
+ /** Transient-failure retries (network, 429, 5xx). Default 2. */
332
+ maxRetries?: number;
333
+ /** Per-request timeout in ms. Default 30 000. */
334
+ timeoutMs?: number;
335
+ }
336
+ declare class Elapse {
337
+ #private;
338
+ readonly baseUrl: string;
339
+ readonly maxRetries: number;
340
+ readonly timeoutMs: number;
341
+ readonly products: ReturnType<typeof products>;
342
+ readonly checkout: ReturnType<typeof checkout>;
343
+ readonly subscriptions: ReturnType<typeof subscriptions>;
344
+ readonly customers: ReturnType<typeof customers>;
345
+ readonly invoices: ReturnType<typeof invoices>;
346
+ readonly webhooks: {
347
+ /** Verify a webhook and parse the event. Pass the raw body, never a parsed object. */
348
+ constructEvent: (rawBody: string | Uint8Array, header: string | undefined, secret: string | readonly string[] | undefined, options?: ConstructEventOptions) => ElapseEvent;
349
+ };
350
+ constructor(config: ElapseConfig);
351
+ /** Test hook: override the retry sleep. Not part of the public surface. */
352
+ set _sleep(fn: (ms: number) => Promise<void>);
353
+ /** Never leak the key through logging (BR-SDK-002). */
354
+ toJSON(): {
355
+ baseUrl: string;
356
+ };
357
+ }
358
+
359
+ export { type CanceledSubscriptionObject, type CheckoutSession, type CheckoutSessionCompletedObject, type ConstructEventOptions, type Customer, Elapse, ElapseAPIError, ElapseAuthenticationError, type ElapseConfig, ElapseError, type ElapseEvent, ElapseInvalidRequestError, ElapseRateLimitError, ElapseSignatureVerificationError, type EventType, type Invoice, type InvoiceObject, type List, type ListParams, type Product, type RequestOptions, type Subscription, type SubscriptionObject, type SubscriptionStatus, constructEvent };