@billkit-eu/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.
- package/CHANGELOG.md +71 -0
- package/LICENSE +201 -0
- package/README.md +299 -0
- package/dist/index.cjs +996 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +911 -0
- package/dist/index.d.ts +911 -0
- package/dist/index.js +977 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
- package/src/client.ts +88 -0
- package/src/errors.ts +168 -0
- package/src/index.ts +103 -0
- package/src/logging.ts +72 -0
- package/src/pagination.ts +77 -0
- package/src/resources.ts +993 -0
- package/src/retry.ts +61 -0
- package/src/transport.ts +300 -0
- package/src/version.ts +1 -0
- package/src/webhooks.ts +171 -0
package/src/resources.ts
ADDED
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource accessors mirroring the BillKit API surface.
|
|
3
|
+
*
|
|
4
|
+
* Each resource exposes the public verbs from `/v1/<resource>`. The
|
|
5
|
+
* return type defaults to `unknown`; the SDK doesn't ship runtime
|
|
6
|
+
* schemas (zod / valibot) because the API is Stripe-shape and tenants
|
|
7
|
+
* typically forward the JSON through their own data layer unchanged.
|
|
8
|
+
* Callers who want strong types parameterise each call with their
|
|
9
|
+
* own generic:
|
|
10
|
+
*
|
|
11
|
+
* interface Customer { id: string; email: string }
|
|
12
|
+
* const c = await client.customers.create<Customer>({ email: "..." });
|
|
13
|
+
*
|
|
14
|
+
* Every list-returning resource also exposes an `iter()` method that
|
|
15
|
+
* walks every page via the Stripe-shape `has_more` + `starting_after`
|
|
16
|
+
* cursor protocol. Iterate with `for await`:
|
|
17
|
+
*
|
|
18
|
+
* for await (const customer of client.customers.iter()) { ... }
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { paginate, type ListResponseEnvelope } from "./pagination.js";
|
|
22
|
+
import type { QueryValue, Transport } from "./transport.js";
|
|
23
|
+
|
|
24
|
+
// ─── Shared parameter shapes ───────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cursor-pagination knobs shared by every `list()` method.
|
|
28
|
+
*
|
|
29
|
+
* The index signature is what lets a resource-specific extension
|
|
30
|
+
* (e.g. `EventsListParams` adds `type?: string`) flow through the
|
|
31
|
+
* Transport's `query` shape without a cast. Excess fields are
|
|
32
|
+
* tolerated; `undefined` values are pruned before serialisation.
|
|
33
|
+
*/
|
|
34
|
+
export interface BaseListParams {
|
|
35
|
+
limit?: number;
|
|
36
|
+
starting_after?: string;
|
|
37
|
+
ending_before?: string;
|
|
38
|
+
readonly [key: string]: QueryValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `prices.list` params. Adds the server-side `product_id` filter on top of
|
|
43
|
+
* the usual cursor knobs. `GET /v1/prices?product_id=...` narrows to one
|
|
44
|
+
* product's prices, which beats listing everything and filtering client-side
|
|
45
|
+
* once a tenant has more than a page of prices.
|
|
46
|
+
*/
|
|
47
|
+
export interface PricesListParams extends BaseListParams {
|
|
48
|
+
product_id?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Optional idempotency knob carried by every mutating call. */
|
|
52
|
+
export interface IdempotencyOptions {
|
|
53
|
+
/** Coalesces retries across process restarts. The SDK generates
|
|
54
|
+
* a random `sdk-<uuid>` key for every mutating call if you don't
|
|
55
|
+
* supply one. Pass your own when you want retries from a different
|
|
56
|
+
* process to converge on the same server-side result. */
|
|
57
|
+
idempotencyKey?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Keep a type alias for backward compatibility with the previous
|
|
61
|
+
// loosely-typed ListParams. New code should reach for the
|
|
62
|
+
// resource-specific *ListParams (e.g. `EventsListParams`).
|
|
63
|
+
export type ListParams = BaseListParams & {
|
|
64
|
+
readonly [key: string]: QueryValue;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ─── Per-resource parameter shapes ─────────────────────────────────
|
|
68
|
+
//
|
|
69
|
+
// We keep one exported interface per public surface so callers can
|
|
70
|
+
// import the shape, build it ahead of time, and pass it in. Inlined
|
|
71
|
+
// shapes were inconsistent across resources; the named-interface
|
|
72
|
+
// form is greppable and survives editor "go to definition".
|
|
73
|
+
|
|
74
|
+
export interface CreateCustomerParams extends IdempotencyOptions {
|
|
75
|
+
email?: string;
|
|
76
|
+
name?: string;
|
|
77
|
+
country_code?: string;
|
|
78
|
+
metadata?: Record<string, string>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface UpdateCustomerParams extends IdempotencyOptions {
|
|
82
|
+
email?: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
country_code?: string;
|
|
85
|
+
metadata?: Record<string, string>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Body for `POST /v1/customers/{id}/vat_number`. The VAT number is
|
|
90
|
+
* sent through VIES server-side; the response carries
|
|
91
|
+
* `vat_number_validated` reflecting the outcome.
|
|
92
|
+
*/
|
|
93
|
+
export interface SetCustomerVatNumberParams extends IdempotencyOptions {
|
|
94
|
+
vat_number: string;
|
|
95
|
+
country_code?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Body for `POST /v1/customers/{id}/purge`. The server requires
|
|
100
|
+
* `confirmed: true` as a fat-finger guard against accidental purges
|
|
101
|
+
* fired from a DELETE that meant to soft-delete. The SDK defaults
|
|
102
|
+
* `confirmed` to `true` so the caller doesn't have to opt in twice.
|
|
103
|
+
*/
|
|
104
|
+
export interface PurgeCustomerParams extends IdempotencyOptions {
|
|
105
|
+
confirmed?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface CreateProductParams extends IdempotencyOptions {
|
|
109
|
+
/** Customer-facing name, for example "Pro" or "Enterprise". */
|
|
110
|
+
name: string;
|
|
111
|
+
/** Optional long-form description shown in your own catalog UI. */
|
|
112
|
+
description?: string;
|
|
113
|
+
/** Ordered bullets suitable for pricing tables and checkout pages. */
|
|
114
|
+
marketing_features?: string[];
|
|
115
|
+
/** Small string metadata map echoed back on the Product object. */
|
|
116
|
+
metadata?: Record<string, string>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface UpdateProductParams extends IdempotencyOptions {
|
|
120
|
+
name?: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
marketing_features?: string[];
|
|
123
|
+
metadata?: Record<string, string>;
|
|
124
|
+
/** Set false to stop selling a product without deleting history. */
|
|
125
|
+
active?: boolean;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface CreatePriceParams extends IdempotencyOptions {
|
|
129
|
+
/** Existing Product id returned from `client.products.create`. */
|
|
130
|
+
product_id: string;
|
|
131
|
+
amount_cents: number;
|
|
132
|
+
currency: string;
|
|
133
|
+
interval: "month" | "year" | (string & {});
|
|
134
|
+
metadata?: Record<string, string>;
|
|
135
|
+
trial_days?: number;
|
|
136
|
+
trial_verification_cents?: number;
|
|
137
|
+
payment_methods?: Array<"creditcard" | "directdebit" | (string & {})>;
|
|
138
|
+
/**
|
|
139
|
+
* Per-Price refund-window override (`POST /v1/prices`). `undefined`
|
|
140
|
+
* inherits the default policy table (7d / 30d initial, 3d renewal);
|
|
141
|
+
* `0` disables refunds for that charge type; `N > 0` is an N-day
|
|
142
|
+
* window (capped server-side at 365). Useful for "Pro Bundle has a
|
|
143
|
+
* 14-day money back" or "Lifetime: no refunds" product decisions.
|
|
144
|
+
*/
|
|
145
|
+
refund_window_initial_days?: number;
|
|
146
|
+
refund_window_renewal_days?: number;
|
|
147
|
+
/**
|
|
148
|
+
* Whether `amount_cents` is quoted gross (`"inclusive"`, VAT is
|
|
149
|
+
* backed out of it) or net (`"exclusive"`, VAT is added on top at
|
|
150
|
+
* charge time). `undefined` inherits `"unspecified"`, which defers to
|
|
151
|
+
* the tax rate configured for the buyer's country. Set it explicitly
|
|
152
|
+
* when the amount you advertise has to be the amount charged,
|
|
153
|
+
* regardless of what tax rates exist now or later.
|
|
154
|
+
*/
|
|
155
|
+
tax_behavior?: "inclusive" | "exclusive" | "unspecified";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface CreateCheckoutSessionParams extends IdempotencyOptions {
|
|
159
|
+
/**
|
|
160
|
+
* Existing Customer to attach the session to. Mutually exclusive
|
|
161
|
+
* with `customer_email`; exactly one of the two must be set.
|
|
162
|
+
*/
|
|
163
|
+
customer_id?: string;
|
|
164
|
+
/**
|
|
165
|
+
* Stripe-compatible shortcut: BillKit creates a fresh Customer row
|
|
166
|
+
* in the same transaction as the checkout. Never dedupes by email
|
|
167
|
+
* (emails are not unique identifiers in BillKit). Mutually exclusive
|
|
168
|
+
* with `customer_id`.
|
|
169
|
+
*/
|
|
170
|
+
customer_email?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Optional friendly name carried onto the auto-created Customer when
|
|
173
|
+
* using `customer_email`. Rejected with `422` if supplied alongside
|
|
174
|
+
* `customer_id` (rename existing customers via `customers.update`).
|
|
175
|
+
*/
|
|
176
|
+
customer_name?: string;
|
|
177
|
+
price_id: string;
|
|
178
|
+
success_url: string;
|
|
179
|
+
cancel_url: string;
|
|
180
|
+
/**
|
|
181
|
+
* Pin the Mollie payment method. `undefined` lets Mollie pick from
|
|
182
|
+
* the customer's available methods; when set, must be in the price's
|
|
183
|
+
* `payment_methods` allowlist.
|
|
184
|
+
*/
|
|
185
|
+
method?: "creditcard" | "directdebit" | (string & {});
|
|
186
|
+
/** Optional coupon code applied at checkout; atomically claimed. */
|
|
187
|
+
coupon_code?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Per-session trial override. Replaces the price's `trial_days`
|
|
190
|
+
* for this checkout. Capped server-side at `2 × max(price.trial_days, 14)`.
|
|
191
|
+
* `0` disables a trial that the price would otherwise grant.
|
|
192
|
+
*/
|
|
193
|
+
trial_days_override?: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface CreateRefundParams extends IdempotencyOptions {
|
|
197
|
+
payment_id?: string;
|
|
198
|
+
subscription_id?: string;
|
|
199
|
+
/**
|
|
200
|
+
* Refund a mandate-less one-shot payment (`oneShotPayments.create`).
|
|
201
|
+
* Mutually exclusive with `payment_id` / `subscription_id`; pass
|
|
202
|
+
* exactly one target or the server rejects with `400`.
|
|
203
|
+
*/
|
|
204
|
+
one_shot_payment_id?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Partial-refund amount in minor units. Omit to refund the whole remaining
|
|
207
|
+
* balance (the full charge when nothing has been refunded yet). A payment
|
|
208
|
+
* may carry several partial refunds up to the charged amount; the one that
|
|
209
|
+
* brings the cumulative total to the full charge cancels the bound
|
|
210
|
+
* subscription (or flips a one-shot to `refunded`).
|
|
211
|
+
*/
|
|
212
|
+
amount_cents?: number;
|
|
213
|
+
reason?: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Body for `POST /v1/checkout/one_shot`, a single mandate-less charge
|
|
218
|
+
* (the Stripe PaymentIntent shape, mapped onto Mollie). No subscription,
|
|
219
|
+
* no mandate, no renewals: it settles once against your `success_url`.
|
|
220
|
+
*/
|
|
221
|
+
export interface CreateOneShotPaymentParams extends IdempotencyOptions {
|
|
222
|
+
/** Existing Customer to charge. */
|
|
223
|
+
customer_id: string;
|
|
224
|
+
amount_cents: number;
|
|
225
|
+
/** ISO-4217, e.g. `"EUR"`. Validated against the tenant allowlist. */
|
|
226
|
+
currency: string;
|
|
227
|
+
/**
|
|
228
|
+
* Concrete Mollie method to charge with. Required, because a one-shot commits
|
|
229
|
+
* up front). Validated against the tenant's capability allowlist for
|
|
230
|
+
* `currency`; one-off methods like `bancontact`/`eps` are allowed here
|
|
231
|
+
* even though they can't back a subscription.
|
|
232
|
+
*
|
|
233
|
+
* `giropay` was removed: the scheme shut down at the end of 2024 and the
|
|
234
|
+
* server now 422s it. The `(string & {})` tail keeps this open on
|
|
235
|
+
* purpose: unlike the console's read-side `PaymentMethodKind`, this is a
|
|
236
|
+
* *request* type the server validates, so an SDK that lags a newly-added
|
|
237
|
+
* method should not be the thing that blocks the call.
|
|
238
|
+
*/
|
|
239
|
+
method: "creditcard" | "directdebit" | "ideal" | "bancontact" | "eps" | (string & {});
|
|
240
|
+
/** Where Mollie returns the payer after the hosted checkout. */
|
|
241
|
+
success_url: string;
|
|
242
|
+
/** Optional page for an abandoned/cancelled payment. */
|
|
243
|
+
cancel_url?: string;
|
|
244
|
+
/** Shown on the Mollie page + the payer's bank statement. */
|
|
245
|
+
description?: string;
|
|
246
|
+
/**
|
|
247
|
+
* Per-payment refund-window override in days. `undefined` inherits the
|
|
248
|
+
* one-shot default (30 days); `0` disables refunds for this payment;
|
|
249
|
+
* `N > 0` is an N-day window (capped server-side at 365).
|
|
250
|
+
*/
|
|
251
|
+
refund_window_days?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Whether `amount_cents` is quoted gross or net.
|
|
254
|
+
*
|
|
255
|
+
* `"inclusive"` (the default when omitted) charges `amount_cents` and
|
|
256
|
+
* backs the VAT out of it. `"exclusive"` reads it as a net figure and
|
|
257
|
+
* charges the payer `amount_cents + tax`, so the response's
|
|
258
|
+
* `amount_cents` comes back *larger* than the one you sent, because it
|
|
259
|
+
* is always what was actually charged. Reconcile against `net_cents` /
|
|
260
|
+
* `tax_cents` on the response.
|
|
261
|
+
*
|
|
262
|
+
* Omit to inherit the country default from your configured tax rate.
|
|
263
|
+
*/
|
|
264
|
+
tax_behavior?: "inclusive" | "exclusive";
|
|
265
|
+
metadata?: Record<string, string>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface CreateWebhookEndpointParams extends IdempotencyOptions {
|
|
269
|
+
url: string;
|
|
270
|
+
enabled_events?: string[];
|
|
271
|
+
description?: string;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface UpdateWebhookEndpointParams extends IdempotencyOptions {
|
|
275
|
+
url?: string;
|
|
276
|
+
enabled_events?: string[];
|
|
277
|
+
description?: string;
|
|
278
|
+
status?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface EventsListParams extends BaseListParams {
|
|
282
|
+
/** Server-side filter, e.g. `customer.created`. */
|
|
283
|
+
type?: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface SetPortalBrandingParams extends IdempotencyOptions {
|
|
287
|
+
business_name?: string;
|
|
288
|
+
support_email?: string;
|
|
289
|
+
logo_url?: string;
|
|
290
|
+
theme?: Record<string, unknown>;
|
|
291
|
+
capabilities?: Record<string, unknown>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface RotateProviderCredentialParams extends IdempotencyOptions {
|
|
295
|
+
/** New raw provider API key. Encrypted server-side; never logged. */
|
|
296
|
+
api_key: string;
|
|
297
|
+
/** Defaults to the calling key's mode. */
|
|
298
|
+
mode?: "test" | "live" | (string & {});
|
|
299
|
+
/** Currently only `"mollie"`. */
|
|
300
|
+
provider?: "mollie" | (string & {});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface CreateCouponParams extends IdempotencyOptions {
|
|
304
|
+
code: string;
|
|
305
|
+
discount_type: "percentage" | "amount" | (string & {});
|
|
306
|
+
discount_value: number;
|
|
307
|
+
duration: "once" | "repeating" | "forever" | (string & {});
|
|
308
|
+
duration_in_months?: number;
|
|
309
|
+
max_redemptions?: number;
|
|
310
|
+
redeem_by?: number;
|
|
311
|
+
applies_to_price_ids?: string[];
|
|
312
|
+
min_amount_cents?: number;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface UpdateCouponParams extends IdempotencyOptions {
|
|
316
|
+
active?: boolean;
|
|
317
|
+
max_redemptions?: number;
|
|
318
|
+
redeem_by?: number;
|
|
319
|
+
applies_to_price_ids?: string[];
|
|
320
|
+
min_amount_cents?: number;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export interface ValidateCouponParams {
|
|
324
|
+
code: string;
|
|
325
|
+
/**
|
|
326
|
+
* Scope the dry-run to one price. Optional: omit to validate the code
|
|
327
|
+
* on its own (existence, active, not exhausted, not expired). Supply it
|
|
328
|
+
* to also check the coupon's `applies_to_price_ids` restriction.
|
|
329
|
+
*/
|
|
330
|
+
price_id?: string;
|
|
331
|
+
/**
|
|
332
|
+
* Base amount the discount is computed against, in minor units.
|
|
333
|
+
* Optional: omit to skip the discount math and the `min_amount_cents`
|
|
334
|
+
* check. `POST /v1/coupons/validate` treats both fields as nullable.
|
|
335
|
+
*/
|
|
336
|
+
amount_cents?: number;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export interface CreateTaxRateParams extends IdempotencyOptions {
|
|
340
|
+
country_code: string;
|
|
341
|
+
rate_basis_points: number;
|
|
342
|
+
display_name?: string;
|
|
343
|
+
inclusive?: boolean;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export interface UpdateTaxRateParams extends IdempotencyOptions {
|
|
347
|
+
rate_basis_points?: number;
|
|
348
|
+
display_name?: string;
|
|
349
|
+
inclusive?: boolean;
|
|
350
|
+
active?: boolean;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface AuditLogsListParams extends BaseListParams {
|
|
354
|
+
action?: string;
|
|
355
|
+
resource_type?: string;
|
|
356
|
+
actor_id?: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface CreateBillingPortalSessionParams extends IdempotencyOptions {
|
|
360
|
+
subscription_id: string;
|
|
361
|
+
return_url: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ─── Internals ─────────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
function dropUndefined<T extends Record<string, unknown>>(obj: T): Record<string, unknown> {
|
|
367
|
+
const out: Record<string, unknown> = {};
|
|
368
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
369
|
+
if (v !== undefined) out[k] = v;
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Strips the `idempotencyKey` carrier from a mutating-call body and
|
|
376
|
+
* returns `{ body, idempotencyKey }`. Pulled out so the per-resource
|
|
377
|
+
* methods read as "describe the verb" instead of "shuffle keys".
|
|
378
|
+
*/
|
|
379
|
+
function splitIdempotency<P extends IdempotencyOptions>(
|
|
380
|
+
params: P,
|
|
381
|
+
): { body: Record<string, unknown>; idempotencyKey: string | undefined } {
|
|
382
|
+
const { idempotencyKey, ...rest } = params;
|
|
383
|
+
return { body: dropUndefined(rest as Record<string, unknown>), idempotencyKey };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Shared transport wrapper. Resources subclass this so each method
|
|
388
|
+
* reads as a single line, "verb to path with params", instead of
|
|
389
|
+
* the four-line `this.t.request({ ... })` boilerplate the previous
|
|
390
|
+
* draft repeated everywhere.
|
|
391
|
+
*/
|
|
392
|
+
abstract class BaseResource {
|
|
393
|
+
constructor(protected readonly t: Transport) {}
|
|
394
|
+
|
|
395
|
+
protected get<T>(path: string, query?: BaseListParams & Record<string, QueryValue>): Promise<T> {
|
|
396
|
+
return this.t.request<T>({ method: "GET", path, query });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
protected post<T, P extends IdempotencyOptions>(path: string, params: P): Promise<T> {
|
|
400
|
+
const { body, idempotencyKey } = splitIdempotency(params);
|
|
401
|
+
return this.t.request<T>({ method: "POST", path, body, idempotencyKey });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** POST with no body, used by lifecycle verbs (cancel, resume, revoke ...). */
|
|
405
|
+
protected postEmpty<T>(path: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
406
|
+
return this.t.request<T>({
|
|
407
|
+
method: "POST",
|
|
408
|
+
path,
|
|
409
|
+
idempotencyKey: params.idempotencyKey,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** POST with a fixed body and no idempotency stripping (used by
|
|
414
|
+
* endpoints whose body is fully specified by the caller's args
|
|
415
|
+
* and not optional, e.g. `preview_update`). */
|
|
416
|
+
protected postFixed<T>(
|
|
417
|
+
path: string,
|
|
418
|
+
body: Record<string, unknown>,
|
|
419
|
+
params: IdempotencyOptions = {},
|
|
420
|
+
): Promise<T> {
|
|
421
|
+
return this.t.request<T>({
|
|
422
|
+
method: "POST",
|
|
423
|
+
path,
|
|
424
|
+
body,
|
|
425
|
+
idempotencyKey: params.idempotencyKey,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
protected del<T>(path: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
430
|
+
return this.t.request<T>({
|
|
431
|
+
method: "DELETE",
|
|
432
|
+
path,
|
|
433
|
+
idempotencyKey: params.idempotencyKey,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ─── Resources ─────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
export class Customers extends BaseResource {
|
|
441
|
+
/** Create a tenant-scoped buyer record. */
|
|
442
|
+
create<T = unknown>(params: CreateCustomerParams = {}): Promise<T> {
|
|
443
|
+
return this.post<T, CreateCustomerParams>("/v1/customers", params);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
447
|
+
return this.get<T>(`/v1/customers/${id}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
update<T = unknown>(id: string, params: UpdateCustomerParams = {}): Promise<T> {
|
|
451
|
+
return this.post<T, UpdateCustomerParams>(`/v1/customers/${id}`, params);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
455
|
+
return this.del<T>(`/v1/customers/${id}`, params);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
459
|
+
return this.get<ListResponseEnvelope<T>>("/v1/customers", params);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Walk every page of `list()` and yield each customer. */
|
|
463
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
464
|
+
return paginate<T>((p) => this.get("/v1/customers", p), { pageSize: options.pageSize });
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Attach or replace the customer's VAT number; triggers server-side
|
|
469
|
+
* VIES validation. The response carries `vat_number_validated`
|
|
470
|
+
* reflecting whether VIES confirmed the number.
|
|
471
|
+
*/
|
|
472
|
+
setVatNumber<T = unknown>(id: string, params: SetCustomerVatNumberParams): Promise<T> {
|
|
473
|
+
return this.post<T, SetCustomerVatNumberParams>(`/v1/customers/${id}/vat_number`, params);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Hard-purge a customer's PII for GDPR erasure. Distinct from
|
|
478
|
+
* `delete()` (soft delete): purge nulls email/name/country/VAT/
|
|
479
|
+
* metadata, sets `purged_at`, and is irreversible.
|
|
480
|
+
*
|
|
481
|
+
* The server requires `confirmed: true` as a fat-finger guard; the
|
|
482
|
+
* SDK defaults it to `true` so callers don't have to opt in twice.
|
|
483
|
+
*/
|
|
484
|
+
purge<T = unknown>(id: string, params: PurgeCustomerParams = {}): Promise<T> {
|
|
485
|
+
const { confirmed = true, idempotencyKey } = params;
|
|
486
|
+
return this.postFixed<T>(`/v1/customers/${id}/purge`, { confirmed }, { idempotencyKey });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export class Products extends BaseResource {
|
|
491
|
+
/** Create a catalog Product, then attach one or more Prices to it. */
|
|
492
|
+
create<T = unknown>(params: CreateProductParams): Promise<T> {
|
|
493
|
+
return this.post<T, CreateProductParams>("/v1/products", params);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
497
|
+
return this.get<T>(`/v1/products/${id}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Patch mutable Product fields. */
|
|
501
|
+
update<T = unknown>(id: string, params: UpdateProductParams): Promise<T> {
|
|
502
|
+
return this.post<T, UpdateProductParams>(`/v1/products/${id}`, params);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Archive a Product. */
|
|
506
|
+
delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
507
|
+
return this.del<T>(`/v1/products/${id}`, params);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
511
|
+
return this.get<ListResponseEnvelope<T>>("/v1/products", params);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
515
|
+
return paginate<T>((p) => this.get("/v1/products", p), { pageSize: options.pageSize });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export class Prices extends BaseResource {
|
|
520
|
+
/** Create immutable billing terms for an existing Product. */
|
|
521
|
+
create<T = unknown>(params: CreatePriceParams): Promise<T> {
|
|
522
|
+
return this.post<T, CreatePriceParams>("/v1/prices", params);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
526
|
+
return this.get<T>(`/v1/prices/${id}`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
list<T = unknown>(params: PricesListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
530
|
+
return this.get<ListResponseEnvelope<T>>("/v1/prices", params);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
iter<T = unknown>(
|
|
534
|
+
options: { pageSize?: number; product_id?: string } = {},
|
|
535
|
+
): AsyncIterableIterator<T> {
|
|
536
|
+
const filter = options.product_id === undefined ? {} : { product_id: options.product_id };
|
|
537
|
+
return paginate<T>((p) => this.get("/v1/prices", { ...filter, ...p }), {
|
|
538
|
+
pageSize: options.pageSize,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export class CheckoutSessions extends BaseResource {
|
|
544
|
+
create<T = unknown>(params: CreateCheckoutSessionParams): Promise<T> {
|
|
545
|
+
return this.post<T, CreateCheckoutSessionParams>("/v1/checkout/sessions", params);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
549
|
+
return this.get<T>(`/v1/checkout/sessions/${id}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Mandate-less one-shot payments (`/v1/checkout/one_shot`).
|
|
555
|
+
*
|
|
556
|
+
* A one-shot is the Stripe PaymentIntent shape mapped onto Mollie: a
|
|
557
|
+
* single `sequenceType=oneoff` charge that provisions nothing: no
|
|
558
|
+
* subscription, no mandate, no renewals. Drive terminal state via the
|
|
559
|
+
* `one_shot_payment.succeeded` / `.failed` webhook events; refund one
|
|
560
|
+
* with `client.refunds.create({ one_shot_payment_id })`.
|
|
561
|
+
*/
|
|
562
|
+
export class OneShotPayments extends BaseResource {
|
|
563
|
+
/** Create a one-off charge; returns the object with a `redirect_url`. */
|
|
564
|
+
create<T = unknown>(params: CreateOneShotPaymentParams): Promise<T> {
|
|
565
|
+
return this.post<T, CreateOneShotPaymentParams>("/v1/checkout/one_shot", params);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
569
|
+
return this.get<T>(`/v1/checkout/one_shot/${id}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export class Subscriptions extends BaseResource {
|
|
574
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
575
|
+
return this.get<T>(`/v1/subscriptions/${id}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
579
|
+
return this.get<ListResponseEnvelope<T>>("/v1/subscriptions", params);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
583
|
+
return paginate<T>((p) => this.get("/v1/subscriptions", p), { pageSize: options.pageSize });
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
cancel<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
587
|
+
return this.postEmpty<T>(`/v1/subscriptions/${id}/cancel`, params);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
pause<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
591
|
+
return this.postEmpty<T>(`/v1/subscriptions/${id}/pause`, params);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
resume<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
595
|
+
return this.postEmpty<T>(`/v1/subscriptions/${id}/resume`, params);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Reactivate a canceled-but-still-in-period subscription.
|
|
600
|
+
*
|
|
601
|
+
* Distinct from `resume()` (paused → active): `reactivate()` flips
|
|
602
|
+
* `canceled` back to `active` for the remainder of the current
|
|
603
|
+
* period, so the customer keeps service without a new checkout.
|
|
604
|
+
* Returns `409` if the period has already elapsed.
|
|
605
|
+
*/
|
|
606
|
+
reactivate<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
607
|
+
return this.postEmpty<T>(`/v1/subscriptions/${id}/reactivate`, params);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
previewUpdate<T = unknown>(id: string, params: { target_price_id: string }): Promise<T> {
|
|
611
|
+
return this.postFixed<T>(`/v1/subscriptions/${id}/preview_update`, {
|
|
612
|
+
target_price_id: params.target_price_id,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
update<T = unknown>(
|
|
617
|
+
id: string,
|
|
618
|
+
params: { target_price_id: string } & IdempotencyOptions,
|
|
619
|
+
): Promise<T> {
|
|
620
|
+
return this.postFixed<T>(
|
|
621
|
+
`/v1/subscriptions/${id}/update`,
|
|
622
|
+
{ target_price_id: params.target_price_id },
|
|
623
|
+
{ idempotencyKey: params.idempotencyKey },
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
reauthorizePaymentMethod<T = unknown>(
|
|
628
|
+
id: string,
|
|
629
|
+
params: { return_url: string } & IdempotencyOptions,
|
|
630
|
+
): Promise<T> {
|
|
631
|
+
return this.postFixed<T>(
|
|
632
|
+
`/v1/subscriptions/${id}/reauthorize_payment_method`,
|
|
633
|
+
{ return_url: params.return_url },
|
|
634
|
+
{ idempotencyKey: params.idempotencyKey },
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export class Refunds extends BaseResource {
|
|
640
|
+
create<T = unknown>(params: CreateRefundParams): Promise<T> {
|
|
641
|
+
return this.post<T, CreateRefundParams>("/v1/refunds", params);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
645
|
+
return this.get<T>(`/v1/refunds/${id}`);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
649
|
+
return this.get<ListResponseEnvelope<T>>("/v1/refunds", params);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
653
|
+
return paginate<T>((p) => this.get("/v1/refunds", p), { pageSize: options.pageSize });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Chargebacks / disputes (`/v1/disputes`).
|
|
659
|
+
*
|
|
660
|
+
* Read-only. Disputes are provider-originated (opened by the cardholder's
|
|
661
|
+
* bank) and surfaced via the `dispute.created` / `dispute.closed` webhook
|
|
662
|
+
* events. There is no create/update. A dispute's `status` is `open` or `won`
|
|
663
|
+
* (chargeback reversed); Mollie exposes no "lost" signal, so an upheld
|
|
664
|
+
* chargeback stays `open` (treat any non-`won` dispute as unresolved).
|
|
665
|
+
*/
|
|
666
|
+
export class Disputes extends BaseResource {
|
|
667
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
668
|
+
return this.get<T>(`/v1/disputes/${id}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
672
|
+
return this.get<ListResponseEnvelope<T>>("/v1/disputes", params);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
676
|
+
return paginate<T>((p) => this.get("/v1/disputes", p), { pageSize: options.pageSize });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export class WebhookEndpoints extends BaseResource {
|
|
681
|
+
create<T = unknown>(params: CreateWebhookEndpointParams): Promise<T> {
|
|
682
|
+
return this.post<T, CreateWebhookEndpointParams>("/v1/webhook_endpoints", params);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
686
|
+
return this.get<T>(`/v1/webhook_endpoints/${id}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
update<T = unknown>(id: string, params: UpdateWebhookEndpointParams): Promise<T> {
|
|
690
|
+
return this.post<T, UpdateWebhookEndpointParams>(`/v1/webhook_endpoints/${id}`, params);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
694
|
+
return this.del<T>(`/v1/webhook_endpoints/${id}`, params);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** Rotate the signing secret. The new `whsec_...` is returned once. */
|
|
698
|
+
rotateSecret<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
699
|
+
return this.postEmpty<T>(`/v1/webhook_endpoints/${id}/rotate_secret`, params);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
703
|
+
return this.get<ListResponseEnvelope<T>>("/v1/webhook_endpoints", params);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
707
|
+
return paginate<T>((p) => this.get("/v1/webhook_endpoints", p), {
|
|
708
|
+
pageSize: options.pageSize,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* List per-attempt delivery records for one endpoint.
|
|
714
|
+
*
|
|
715
|
+
* Useful when a tenant's receiver is failing. Surfaces the status
|
|
716
|
+
* code, response body excerpt, error, and next-attempt timestamp
|
|
717
|
+
* for each event × endpoint pair.
|
|
718
|
+
*/
|
|
719
|
+
listDeliveries<T = unknown>(
|
|
720
|
+
endpointId: string,
|
|
721
|
+
params: BaseListParams = {},
|
|
722
|
+
): Promise<ListResponseEnvelope<T>> {
|
|
723
|
+
return this.get<ListResponseEnvelope<T>>(
|
|
724
|
+
`/v1/webhook_endpoints/${endpointId}/deliveries`,
|
|
725
|
+
params,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Walk every page of `listDeliveries()` for one endpoint. */
|
|
730
|
+
iterDeliveries<T = unknown>(
|
|
731
|
+
endpointId: string,
|
|
732
|
+
options: { pageSize?: number } = {},
|
|
733
|
+
): AsyncIterableIterator<T> {
|
|
734
|
+
return paginate<T>(
|
|
735
|
+
(p) => this.get(`/v1/webhook_endpoints/${endpointId}/deliveries`, p),
|
|
736
|
+
{ pageSize: options.pageSize },
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/** Fetch one delivery row for inspection before deciding to redeliver. */
|
|
741
|
+
getDelivery<T = unknown>(endpointId: string, deliveryId: string): Promise<T> {
|
|
742
|
+
return this.get<T>(`/v1/webhook_endpoints/${endpointId}/deliveries/${deliveryId}`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Re-enqueue a delivery row for the dispatcher.
|
|
747
|
+
*
|
|
748
|
+
* Idempotent: a row already in `delivered` returns unchanged. A
|
|
749
|
+
* `pending` / `failed` row flips to `pending` with
|
|
750
|
+
* `next_attempt_at = now()`; `attempt_count` is preserved.
|
|
751
|
+
*/
|
|
752
|
+
redeliver<T = unknown>(
|
|
753
|
+
endpointId: string,
|
|
754
|
+
deliveryId: string,
|
|
755
|
+
params: IdempotencyOptions = {},
|
|
756
|
+
): Promise<T> {
|
|
757
|
+
return this.postEmpty<T>(
|
|
758
|
+
`/v1/webhook_endpoints/${endpointId}/deliveries/${deliveryId}/redeliver`,
|
|
759
|
+
params,
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export class Events extends BaseResource {
|
|
765
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
766
|
+
return this.get<T>(`/v1/events/${id}`);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
list<T = unknown>(params: EventsListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
770
|
+
return this.get<ListResponseEnvelope<T>>("/v1/events", params);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** Walk every page of `list()`. Pass `type` to filter at the server. */
|
|
774
|
+
iter<T = unknown>(
|
|
775
|
+
options: { pageSize?: number; type?: string } = {},
|
|
776
|
+
): AsyncIterableIterator<T> {
|
|
777
|
+
return paginate<T>((p) => this.get("/v1/events", p), {
|
|
778
|
+
pageSize: options.pageSize,
|
|
779
|
+
filters: { type: options.type },
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Read + mutate tenant-level configuration.
|
|
786
|
+
*
|
|
787
|
+
* Exposes the Mollie capability cache, the portal-branding row, and
|
|
788
|
+
* the encrypted Mollie API key. None of these are per-resource;
|
|
789
|
+
* they're tenant-wide knobs.
|
|
790
|
+
*/
|
|
791
|
+
export class Tenant extends BaseResource {
|
|
792
|
+
/** Cached Mollie profile shape (enabled methods, country, currency). */
|
|
793
|
+
capabilities<T = unknown>(): Promise<T> {
|
|
794
|
+
return this.get<T>("/v1/tenant/capabilities");
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** Current portal branding row (business name, theme, capability flags). */
|
|
798
|
+
portalBranding<T = unknown>(): Promise<T> {
|
|
799
|
+
return this.get<T>("/v1/tenant/portal_branding");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Partial-update the portal branding row.
|
|
804
|
+
*
|
|
805
|
+
* Only fields you set are sent. Pass `undefined` to leave a field
|
|
806
|
+
* untouched; sending an empty string explicitly clears it.
|
|
807
|
+
*/
|
|
808
|
+
setPortalBranding<T = unknown>(params: SetPortalBrandingParams = {}): Promise<T> {
|
|
809
|
+
return this.post<T, SetPortalBrandingParams>("/v1/tenant/portal_branding", params);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Rotate the encrypted provider credential for this tenant.
|
|
814
|
+
*
|
|
815
|
+
* The new `api_key` is encrypted server-side; nothing is logged.
|
|
816
|
+
* `mode` defaults to the calling key's mode; prefix-mismatch
|
|
817
|
+
* (`test_...` under live, `live_...` under test) is rejected at the
|
|
818
|
+
* API boundary.
|
|
819
|
+
*/
|
|
820
|
+
rotateProviderCredential<T = unknown>(params: RotateProviderCredentialParams): Promise<T> {
|
|
821
|
+
return this.post<T, RotateProviderCredentialParams>(
|
|
822
|
+
"/v1/tenant/provider_credential",
|
|
823
|
+
params,
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
export class Coupons extends BaseResource {
|
|
829
|
+
create<T = unknown>(params: CreateCouponParams): Promise<T> {
|
|
830
|
+
return this.post<T, CreateCouponParams>("/v1/coupons", params);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
834
|
+
return this.get<T>(`/v1/coupons/${id}`);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
update<T = unknown>(id: string, params: UpdateCouponParams): Promise<T> {
|
|
838
|
+
return this.post<T, UpdateCouponParams>(`/v1/coupons/${id}`, params);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
842
|
+
return this.del<T>(`/v1/coupons/${id}`, params);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Server-side dry-run of a coupon redemption.
|
|
847
|
+
*
|
|
848
|
+
* Returns the discount math without atomically claiming the coupon,
|
|
849
|
+
* which is useful for "preview before checkout" UX.
|
|
850
|
+
*/
|
|
851
|
+
validate<T = unknown>(params: ValidateCouponParams): Promise<T> {
|
|
852
|
+
// Omit the optional fields rather than sending explicit nulls, so the
|
|
853
|
+
// request body matches what a caller who only has a code would hand
|
|
854
|
+
// written by hand, and so `extra="forbid"` schemas stay happy.
|
|
855
|
+
const body: Record<string, unknown> = { code: params.code };
|
|
856
|
+
if (params.price_id !== undefined) body["price_id"] = params.price_id;
|
|
857
|
+
if (params.amount_cents !== undefined) body["amount_cents"] = params.amount_cents;
|
|
858
|
+
return this.postFixed<T>("/v1/coupons/validate", body);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
862
|
+
return this.get<ListResponseEnvelope<T>>("/v1/coupons", params);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
866
|
+
return paginate<T>((p) => this.get("/v1/coupons", p), { pageSize: options.pageSize });
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export class TaxRates extends BaseResource {
|
|
871
|
+
create<T = unknown>(params: CreateTaxRateParams): Promise<T> {
|
|
872
|
+
return this.post<T, CreateTaxRateParams>("/v1/tax_rates", params);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
876
|
+
return this.get<T>(`/v1/tax_rates/${id}`);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
update<T = unknown>(id: string, params: UpdateTaxRateParams): Promise<T> {
|
|
880
|
+
return this.post<T, UpdateTaxRateParams>(`/v1/tax_rates/${id}`, params);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
884
|
+
return this.del<T>(`/v1/tax_rates/${id}`, params);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
888
|
+
return this.get<ListResponseEnvelope<T>>("/v1/tax_rates", params);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
892
|
+
return paginate<T>((p) => this.get("/v1/tax_rates", p), { pageSize: options.pageSize });
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Read-only access to generated invoices.
|
|
898
|
+
*
|
|
899
|
+
* Invoices are produced by the billing pipeline; tenants don't create
|
|
900
|
+
* them directly. PDF retrieval returns a 302 redirect to the storage
|
|
901
|
+
* adapter's signed URL. Follow it transparently with the runtime's
|
|
902
|
+
* fetch settings.
|
|
903
|
+
*/
|
|
904
|
+
export class Invoices extends BaseResource {
|
|
905
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
906
|
+
return this.get<T>(`/v1/invoices/${id}`);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
910
|
+
return this.get<ListResponseEnvelope<T>>("/v1/invoices", params);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
914
|
+
return paginate<T>((p) => this.get("/v1/invoices", p), { pageSize: options.pageSize });
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Read-only access to the per-tenant audit log.
|
|
920
|
+
*
|
|
921
|
+
* Supports server-side filters: `action`, `resource_type`, `actor_id`.
|
|
922
|
+
* The filters are forwarded through to `iter()` so an audit walk can
|
|
923
|
+
* scope to a single actor or action without client-side filtering.
|
|
924
|
+
*/
|
|
925
|
+
export class AuditLogs extends BaseResource {
|
|
926
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
927
|
+
return this.get<T>(`/v1/audit_logs/${id}`);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
list<T = unknown>(params: AuditLogsListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
931
|
+
return this.get<ListResponseEnvelope<T>>("/v1/audit_logs", params);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
iter<T = unknown>(
|
|
935
|
+
options: { pageSize?: number; action?: string; resource_type?: string; actor_id?: string } = {},
|
|
936
|
+
): AsyncIterableIterator<T> {
|
|
937
|
+
return paginate<T>((p) => this.get("/v1/audit_logs", p), {
|
|
938
|
+
pageSize: options.pageSize,
|
|
939
|
+
filters: {
|
|
940
|
+
action: options.action,
|
|
941
|
+
resource_type: options.resource_type,
|
|
942
|
+
actor_id: options.actor_id,
|
|
943
|
+
},
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Read-only access to the payment ledger.
|
|
950
|
+
*
|
|
951
|
+
* Payments are written by the billing pipeline (checkout, renewal,
|
|
952
|
+
* reauthorize). Inspect attempts and their Mollie-side metadata here;
|
|
953
|
+
* refunds and disputes are separate flows.
|
|
954
|
+
*/
|
|
955
|
+
export class Payments extends BaseResource {
|
|
956
|
+
retrieve<T = unknown>(id: string): Promise<T> {
|
|
957
|
+
return this.get<T>(`/v1/payments/${id}`);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
|
|
961
|
+
return this.get<ListResponseEnvelope<T>>("/v1/payments", params);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
|
|
965
|
+
return paginate<T>((p) => this.get("/v1/payments", p), { pageSize: options.pageSize });
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Mint and revoke customer-facing billing-portal sessions.
|
|
971
|
+
*
|
|
972
|
+
* Each session token is scoped to a single subscription with a
|
|
973
|
+
* sliding 30-minute idle window and a 2-hour hard cap. The raw token
|
|
974
|
+
* is returned **once** on mint; the response also includes the URL
|
|
975
|
+
* the tenant embeds in their app.
|
|
976
|
+
*/
|
|
977
|
+
export class BillingPortalSessions extends BaseResource {
|
|
978
|
+
create<T = unknown>(params: CreateBillingPortalSessionParams): Promise<T> {
|
|
979
|
+
return this.postFixed<T>(
|
|
980
|
+
"/v1/billing_portal/sessions",
|
|
981
|
+
{
|
|
982
|
+
subscription_id: params.subscription_id,
|
|
983
|
+
return_url: params.return_url,
|
|
984
|
+
},
|
|
985
|
+
{ idempotencyKey: params.idempotencyKey },
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Kill an in-the-wild portal session. Idempotent. */
|
|
990
|
+
revoke<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
|
|
991
|
+
return this.postEmpty<T>(`/v1/billing_portal/sessions/${id}/revoke`, params);
|
|
992
|
+
}
|
|
993
|
+
}
|