@billkit-eu/sdk 0.6.0 → 0.7.1

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/src/resources.ts CHANGED
@@ -19,17 +19,18 @@
19
19
  */
20
20
 
21
21
  import { paginate, type ListResponseEnvelope } from "./pagination.js";
22
- import type { QueryValue, Transport } from "./transport.js";
22
+ import type { Transport } from "./transport.js";
23
23
 
24
24
  // ─── Shared parameter shapes ───────────────────────────────────────
25
25
 
26
26
  /**
27
27
  * Cursor-pagination knobs shared by every `list()` method.
28
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.
29
+ * Closed on purpose: there is no index signature, so a misspelled filter
30
+ * (`provisonal`) is a compile error instead of a query parameter the
31
+ * server ignores. The Transport takes `query` as a plain `object` and
32
+ * prunes/stringifies it, which is what lets these interfaces through
33
+ * without a cast.
33
34
  */
34
35
  export interface BaseListParams {
35
36
  limit?: number;
@@ -38,7 +39,15 @@ export interface BaseListParams {
38
39
  // `limit` + `starting_after` only (see `api/billkit/api/pagination.py`).
39
40
  // Advertising a backwards cursor the server ignores is worse than not
40
41
  // having one — the request succeeds and silently re-serves page 1.
41
- readonly [key: string]: QueryValue;
42
+ }
43
+
44
+ /**
45
+ * `?expand=` on a single-object GET. The relations a route accepts differ
46
+ * per resource and are listed on each `retrieve()`; an unknown one is a
47
+ * `400` naming the ones that work.
48
+ */
49
+ export interface ExpandOptions {
50
+ expand?: string[];
42
51
  }
43
52
 
44
53
  /**
@@ -51,6 +60,43 @@ export interface PricesListParams extends BaseListParams {
51
60
  product_id?: string;
52
61
  }
53
62
 
63
+ /**
64
+ * `payments.list` params. `customer_id` narrows to one customer's
65
+ * charges. Mandate verifications are never listed, so every row is a
66
+ * real purchase attempt; check `status` before treating one as revenue.
67
+ */
68
+ export interface PaymentsListParams extends BaseListParams {
69
+ customer_id?: string;
70
+ /** Expandable here: `customer`, `subscription`. */
71
+ expand?: string[];
72
+ }
73
+
74
+ /**
75
+ * `invoices.list` params. The three id filters each narrow to one row's
76
+ * worth of invoices: `payment_id` answers "which invoice did this charge
77
+ * produce".
78
+ */
79
+ export interface InvoicesListParams extends BaseListParams {
80
+ customer_id?: string;
81
+ subscription_id?: string;
82
+ payment_id?: string;
83
+ /** One of `draft`, `open`, `paid`, `void`, `uncollectible`. */
84
+ status?: string;
85
+ /** Expandable here: `customer`. */
86
+ expand?: string[];
87
+ }
88
+
89
+ /**
90
+ * `disputes.list` params. `status` takes a comma-separated list of `open`
91
+ * / `won`. There is no `lost`, because the provider gives no signal for
92
+ * one. `payment_id` matches subscription payments only, not one-off
93
+ * charges.
94
+ */
95
+ export interface DisputesListParams extends BaseListParams {
96
+ status?: string;
97
+ payment_id?: string;
98
+ }
99
+
54
100
  /**
55
101
  * `subscriptions.list` params. Both filters take a comma-separated
56
102
  * list (`"active,past_due"`); an unrecognised value is rejected with
@@ -70,6 +116,8 @@ export interface SubscriptionsListParams extends BaseListParams {
70
116
  status?: string;
71
117
  /** `auto_renew` | `paused` | `canceling` | `stopped`, CSV. */
72
118
  renewal_state?: string;
119
+ /** Expandable here: `customer`, `price`, `refund_eligibility`. */
120
+ expand?: string[];
73
121
  }
74
122
 
75
123
  /** Optional idempotency knob carried by every mutating call. */
@@ -81,12 +129,12 @@ export interface IdempotencyOptions {
81
129
  idempotencyKey?: string;
82
130
  }
83
131
 
84
- // Keep a type alias for backward compatibility with the previous
85
- // loosely-typed ListParams. New code should reach for the
86
- // resource-specific *ListParams (e.g. `EventsListParams`).
87
- export type ListParams = BaseListParams & {
88
- readonly [key: string]: QueryValue;
89
- };
132
+ /**
133
+ * @deprecated Alias of {@link BaseListParams}, kept for callers that
134
+ * imported the older name. Reach for the resource-specific `*ListParams`
135
+ * (e.g. `EventsListParams`) instead.
136
+ */
137
+ export type ListParams = BaseListParams;
90
138
 
91
139
  // ─── Per-resource parameter shapes ─────────────────────────────────
92
140
  //
@@ -116,15 +164,27 @@ export interface CustomerListParams extends BaseListParams {
116
164
  * omitted for both.
117
165
  */
118
166
  provisional?: boolean;
167
+ /** Expandable here: `stats`. Sent as `expand=a,b`. */
168
+ expand?: string[];
169
+ }
170
+
171
+ /** Query parameters accepted by `GET /v1/products`. */
172
+ export interface ProductsListParams extends BaseListParams {
173
+ /** Expandable here: `prices`, `stats`, `default_price`. */
174
+ expand?: string[];
119
175
  }
120
176
 
121
177
  /**
122
178
  * Body for `POST /v1/customers/{id}/vat_number`. The VAT number is
123
179
  * sent through VIES server-side; the response carries
124
180
  * `vat_number_validated` reflecting the outcome.
181
+ *
182
+ * `vat_number: null` **clears** the registration, and is sent as an
183
+ * explicit null rather than pruned: only `undefined` is dropped.
125
184
  */
126
185
  export interface SetCustomerVatNumberParams extends IdempotencyOptions {
127
- vat_number: string;
186
+ vat_number: string | null;
187
+ /** VIES needs a country; send it when the customer has none yet. */
128
188
  country_code?: string;
129
189
  }
130
190
 
@@ -168,16 +228,47 @@ export interface UpdateProductParams extends IdempotencyOptions {
168
228
  active?: boolean;
169
229
  /** See {@link CreateProductParams.allow_promotion_codes}. */
170
230
  allow_promotion_codes?: boolean;
231
+ /**
232
+ * The price the billing portal offers on that price's interval. Must be
233
+ * an active price of this product; anything else is a 400 on
234
+ * `default_price_id`. An explicit `null` **clears** the default and is
235
+ * sent as a JSON null rather than pruned (only `undefined` is dropped);
236
+ * omit the field to leave the default alone.
237
+ */
238
+ default_price_id?: string | null;
171
239
  }
172
240
 
173
241
  /**
174
- * Body for `POST /v1/prices/{id}`. `active` is the only field a price
175
- * accepts, and it moves both ways: `false` withdraws the price from sale,
176
- * `true` puts it back. The amount, currency and interval are fixed at
177
- * creation, so neither direction changes what anyone was charged.
242
+ * Body for `POST /v1/prices/{id}`. Every field is optional; omitted ones
243
+ * are left alone.
244
+ *
245
+ * The dividing line is what a field decides. `amount_cents`, `currency`,
246
+ * `interval` and `usage_type` decide **what a past charge was**, so they
247
+ * are fixed at creation and absent here, because subscriptions renew
248
+ * against a price by id and editing one would re-price live customers.
249
+ * Everything
250
+ * below decides **what happens next**, which is why it is editable:
251
+ * setting `refund_on_cancel` covers the customers already on the price.
252
+ *
253
+ * `tax_behavior` is the exception and moves one way. It can be set while
254
+ * the price is still `"unspecified"` and never changed again, because
255
+ * flipping it would restate whether tax was inside or on top of an amount
256
+ * somebody has already paid.
178
257
  */
179
258
  export interface UpdatePriceParams extends IdempotencyOptions {
180
- active: boolean;
259
+ /** `false` withdraws the price from sale, `true` puts it back. */
260
+ active?: boolean;
261
+ metadata?: Record<string, string>;
262
+ /** Settable once, while the price is still `"unspecified"`. */
263
+ tax_behavior?: "inclusive" | "exclusive";
264
+ /** Read when a checkout opens. At least one entry. */
265
+ payment_methods?: Array<
266
+ "creditcard" | "directdebit" | "ideal" | "eps" | "applepay" | "paypal" | (string & {})
267
+ >;
268
+ refund_on_cancel?: "none" | "full" | "prorated";
269
+ /** `0` disables refunds for that charge type; `N > 0` is an N-day window. */
270
+ refund_window_initial_days?: number;
271
+ refund_window_renewal_days?: number;
181
272
  }
182
273
 
183
274
  /**
@@ -369,6 +460,14 @@ export interface CreateCheckoutSessionParams extends IdempotencyOptions {
369
460
  price_id: string;
370
461
  success_url: string;
371
462
  cancel_url: string;
463
+ /**
464
+ * The buyer's ISO-3166-1 alpha-2 country, when you already know it.
465
+ * Stored on the customer if they do not have one yet, which is what
466
+ * lets VAT apply to the very first charge. On the hosted flow the buyer
467
+ * only reaches a country-collecting page after the charge exists.
468
+ * Never overwrites a country the customer already has.
469
+ */
470
+ country?: string;
372
471
  /**
373
472
  * Pin the Mollie payment method. `undefined` lets Mollie pick from
374
473
  * the customer's available methods; when set, must be in the price's
@@ -512,6 +611,8 @@ export interface UpdateWebhookEndpointParams extends IdempotencyOptions {
512
611
  export interface EventsListParams extends BaseListParams {
513
612
  /** Server-side filter, e.g. `customer.created`. */
514
613
  type?: string;
614
+ /** Expandable here: `customer`. `events.retrieve` accepts none. */
615
+ expand?: string[];
515
616
  }
516
617
 
517
618
  export interface SetPortalBrandingParams extends IdempotencyOptions {
@@ -533,7 +634,12 @@ export interface RotateProviderCredentialParams extends IdempotencyOptions {
533
634
 
534
635
  export interface CreateCouponParams extends IdempotencyOptions {
535
636
  code: string;
536
- discount_type: "percentage" | "amount" | (string & {});
637
+ /**
638
+ * `"percent"` reads `discount_value` as whole percent; `"fixed_cents"`
639
+ * reads it as minor units off the charge. Those are the only two the
640
+ * API accepts (`schemas/coupon.py`); anything else is a `422`.
641
+ */
642
+ discount_type: "percent" | "fixed_cents" | (string & {});
537
643
  discount_value: number;
538
644
  duration: "once" | "repeating" | "forever" | (string & {});
539
645
  duration_in_months?: number;
@@ -608,6 +714,47 @@ export interface CreditNotesListParams extends BaseListParams {
608
714
  customer_id?: string;
609
715
  }
610
716
 
717
+ /**
718
+ * Body for `POST /v1/tenant/billing_profile`: the seller identity that
719
+ * VAT is decided against and that an invoice prints.
720
+ *
721
+ * `country_code` is required on every call: there is nothing to leave
722
+ * alone about a jurisdiction. The address fields and
723
+ * `registration_number` are partial-update: omit one to leave the stored
724
+ * value alone, or pass an explicit `null` to clear it, because moving
725
+ * office is a real event.
726
+ *
727
+ * `vat_id` can be set once. After that, a different value or `null` is
728
+ * refused with a 400 (`param: "vat_id"`, reason `vat_id_locked`) and the
729
+ * call writes nothing; re-sending the stored number is accepted. BillKit
730
+ * invoices you reverse-charged against it, so support changes it.
731
+ */
732
+ export interface SetTenantBillingProfileParams extends IdempotencyOptions {
733
+ /** ISO-3166-1 alpha-2, e.g. `"NL"`. */
734
+ country_code: string;
735
+ /** Your own EU VAT registration. Set once; support changes or clears it. */
736
+ vat_id?: string | null;
737
+ address_line1?: string | null;
738
+ address_line2?: string | null;
739
+ postal_code?: string | null;
740
+ city?: string | null;
741
+ registration_number?: string | null;
742
+ }
743
+
744
+ /**
745
+ * Body for `POST /v1/api_keys`. The full key is returned **once**, on the
746
+ * create response, and is never retrievable again.
747
+ */
748
+ export interface CreateApiKeyParams extends IdempotencyOptions {
749
+ /** Human label, so a key can be identified before it is revoked. */
750
+ label?: string;
751
+ /**
752
+ * Narrow what the key may do. Omit to inherit the calling key's own
753
+ * scopes; a key can never grant more than it holds.
754
+ */
755
+ scopes?: string[];
756
+ }
757
+
611
758
  /** `invoices.void` params. `reason` is recorded on the audit row only. */
612
759
  export interface VoidInvoiceParams extends IdempotencyOptions {
613
760
  reason?: string;
@@ -616,10 +763,29 @@ export interface VoidInvoiceParams extends IdempotencyOptions {
616
763
  export interface CreateBillingPortalSessionParams extends IdempotencyOptions {
617
764
  subscription_id: string;
618
765
  return_url: string;
766
+ /**
767
+ * Also email the portal link to the subscription's customer, at the
768
+ * address on their record, as a tenant-branded message. Defaults to
769
+ * `false`: without it you distribute the returned `url` yourself.
770
+ */
771
+ deliver_email?: boolean;
619
772
  }
620
773
 
621
774
  // ─── Internals ─────────────────────────────────────────────────────
622
775
 
776
+ /**
777
+ * Percent-encode a caller-supplied id before it becomes a path segment.
778
+ *
779
+ * Ids reach the SDK from the caller's own storage, and one carrying `/`,
780
+ * `?` or `#` would otherwise rewrite the request: `#` truncates the path,
781
+ * `?` turns the tail into a query string, and `/` walks to a different
782
+ * route entirely. Encoding keeps the request on the route the method
783
+ * names, so a bad id is a clean `404` rather than a call somewhere else.
784
+ */
785
+ function p(id: string): string {
786
+ return encodeURIComponent(id);
787
+ }
788
+
623
789
  function dropUndefined<T extends Record<string, unknown>>(obj: T): Record<string, unknown> {
624
790
  const out: Record<string, unknown> = {};
625
791
  for (const [k, v] of Object.entries(obj)) {
@@ -683,7 +849,13 @@ function assertPriceRatesAreStrings(params: CreatePriceParams): CreatePriceParam
683
849
  abstract class BaseResource {
684
850
  constructor(protected readonly t: Transport) {}
685
851
 
686
- protected get<T>(path: string, query?: BaseListParams & Record<string, QueryValue>): Promise<T> {
852
+ /**
853
+ * `query` is a plain object rather than an index-signature type: TypeScript
854
+ * only gives an implicit index signature to type *aliases*, so a closed
855
+ * `*ListParams` interface would otherwise need a cast at every call site.
856
+ * The transport prunes `undefined`/`null` and joins arrays with commas.
857
+ */
858
+ protected get<T>(path: string, query?: object): Promise<T> {
687
859
  return this.t.request<T>({ method: "GET", path, query });
688
860
  }
689
861
 
@@ -735,11 +907,11 @@ export class Customers extends BaseResource {
735
907
  }
736
908
 
737
909
  retrieve<T = unknown>(id: string): Promise<T> {
738
- return this.get<T>(`/v1/customers/${id}`);
910
+ return this.get<T>(`/v1/customers/${p(id)}`);
739
911
  }
740
912
 
741
913
  update<T = unknown>(id: string, params: UpdateCustomerParams = {}): Promise<T> {
742
- return this.post<T, UpdateCustomerParams>(`/v1/customers/${id}`, params);
914
+ return this.post<T, UpdateCustomerParams>(`/v1/customers/${p(id)}`, params);
743
915
  }
744
916
 
745
917
  /**
@@ -753,7 +925,7 @@ export class Customers extends BaseResource {
753
925
  * charge them.
754
926
  */
755
927
  delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
756
- return this.del<T>(`/v1/customers/${id}`, params);
928
+ return this.del<T>(`/v1/customers/${p(id)}`, params);
757
929
  }
758
930
 
759
931
  /**
@@ -772,7 +944,7 @@ export class Customers extends BaseResource {
772
944
 
773
945
  /** Walk every page of `list()` and yield each customer. */
774
946
  iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
775
- return paginate<T>((p) => this.get("/v1/customers", p), { pageSize: options.pageSize });
947
+ return paginate<T>((page) => this.get("/v1/customers", page), { pageSize: options.pageSize });
776
948
  }
777
949
 
778
950
  /**
@@ -781,7 +953,7 @@ export class Customers extends BaseResource {
781
953
  * reflecting whether VIES confirmed the number.
782
954
  */
783
955
  setVatNumber<T = unknown>(id: string, params: SetCustomerVatNumberParams): Promise<T> {
784
- return this.post<T, SetCustomerVatNumberParams>(`/v1/customers/${id}/vat_number`, params);
956
+ return this.post<T, SetCustomerVatNumberParams>(`/v1/customers/${p(id)}/vat_number`, params);
785
957
  }
786
958
 
787
959
  /**
@@ -794,7 +966,7 @@ export class Customers extends BaseResource {
794
966
  */
795
967
  purge<T = unknown>(id: string, params: PurgeCustomerParams = {}): Promise<T> {
796
968
  const { confirmed = true, idempotencyKey } = params;
797
- return this.postFixed<T>(`/v1/customers/${id}/purge`, { confirmed }, { idempotencyKey });
969
+ return this.postFixed<T>(`/v1/customers/${p(id)}/purge`, { confirmed }, { idempotencyKey });
798
970
  }
799
971
  }
800
972
 
@@ -804,8 +976,12 @@ export class Products extends BaseResource {
804
976
  return this.post<T, CreateProductParams>("/v1/products", params);
805
977
  }
806
978
 
807
- retrieve<T = unknown>(id: string): Promise<T> {
808
- return this.get<T>(`/v1/products/${id}`);
979
+ /**
980
+ * Expandable: `prices` (every price on the product), `stats`, and
981
+ * `default_price` (the price `default_price_id` names).
982
+ */
983
+ retrieve<T = unknown>(id: string, options: ExpandOptions = {}): Promise<T> {
984
+ return this.get<T>(`/v1/products/${p(id)}`, options);
809
985
  }
810
986
 
811
987
  /**
@@ -818,15 +994,15 @@ export class Products extends BaseResource {
818
994
  * `active: true` un-archives.
819
995
  */
820
996
  update<T = unknown>(id: string, params: UpdateProductParams): Promise<T> {
821
- return this.post<T, UpdateProductParams>(`/v1/products/${id}`, params);
997
+ return this.post<T, UpdateProductParams>(`/v1/products/${p(id)}`, params);
822
998
  }
823
999
 
824
- list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
1000
+ list<T = unknown>(params: ProductsListParams = {}): Promise<ListResponseEnvelope<T>> {
825
1001
  return this.get<ListResponseEnvelope<T>>("/v1/products", params);
826
1002
  }
827
1003
 
828
1004
  iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
829
- return paginate<T>((p) => this.get("/v1/products", p), { pageSize: options.pageSize });
1005
+ return paginate<T>((page) => this.get("/v1/products", page), { pageSize: options.pageSize });
830
1006
  }
831
1007
  }
832
1008
 
@@ -851,7 +1027,7 @@ export class Prices extends BaseResource {
851
1027
  }
852
1028
 
853
1029
  retrieve<T = unknown>(id: string): Promise<T> {
854
- return this.get<T>(`/v1/prices/${id}`);
1030
+ return this.get<T>(`/v1/prices/${p(id)}`);
855
1031
  }
856
1032
 
857
1033
  /**
@@ -875,7 +1051,7 @@ export class Prices extends BaseResource {
875
1051
  * `price.archived`; putting one back emits `price.updated`.
876
1052
  */
877
1053
  update<T = unknown>(id: string, params: UpdatePriceParams): Promise<T> {
878
- return this.post<T, UpdatePriceParams>(`/v1/prices/${id}`, params);
1054
+ return this.post<T, UpdatePriceParams>(`/v1/prices/${p(id)}`, params);
879
1055
  }
880
1056
 
881
1057
  list<T = unknown>(params: PricesListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -886,7 +1062,7 @@ export class Prices extends BaseResource {
886
1062
  options: { pageSize?: number; product_id?: string } = {},
887
1063
  ): AsyncIterableIterator<T> {
888
1064
  const filter = options.product_id === undefined ? {} : { product_id: options.product_id };
889
- return paginate<T>((p) => this.get("/v1/prices", { ...filter, ...p }), {
1065
+ return paginate<T>((page) => this.get("/v1/prices", { ...filter, ...page }), {
890
1066
  pageSize: options.pageSize,
891
1067
  });
892
1068
  }
@@ -898,7 +1074,7 @@ export class CheckoutSessions extends BaseResource {
898
1074
  }
899
1075
 
900
1076
  retrieve<T = unknown>(id: string): Promise<T> {
901
- return this.get<T>(`/v1/checkout/sessions/${id}`);
1077
+ return this.get<T>(`/v1/checkout/sessions/${p(id)}`);
902
1078
  }
903
1079
  }
904
1080
 
@@ -918,13 +1094,14 @@ export class OneShotPayments extends BaseResource {
918
1094
  }
919
1095
 
920
1096
  retrieve<T = unknown>(id: string): Promise<T> {
921
- return this.get<T>(`/v1/checkout/one_shot/${id}`);
1097
+ return this.get<T>(`/v1/checkout/one_shot/${p(id)}`);
922
1098
  }
923
1099
  }
924
1100
 
925
1101
  export class Subscriptions extends BaseResource {
926
- retrieve<T = unknown>(id: string): Promise<T> {
927
- return this.get<T>(`/v1/subscriptions/${id}`);
1102
+ /** Expandable: `customer`, `price`, `refund_eligibility`. */
1103
+ retrieve<T = unknown>(id: string, options: ExpandOptions = {}): Promise<T> {
1104
+ return this.get<T>(`/v1/subscriptions/${p(id)}`, options);
928
1105
  }
929
1106
 
930
1107
  /**
@@ -953,19 +1130,19 @@ export class Subscriptions extends BaseResource {
953
1130
  // `undefined` query values are pruned by the transport, so the
954
1131
  // filter can be spread as-is without a conditional per key.
955
1132
  const { pageSize, ...filter } = options;
956
- return paginate<T>((p) => this.get("/v1/subscriptions", { ...filter, ...p }), { pageSize });
1133
+ return paginate<T>((page) => this.get("/v1/subscriptions", { ...filter, ...page }), { pageSize });
957
1134
  }
958
1135
 
959
1136
  cancel<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
960
- return this.postEmpty<T>(`/v1/subscriptions/${id}/cancel`, params);
1137
+ return this.postEmpty<T>(`/v1/subscriptions/${p(id)}/cancel`, params);
961
1138
  }
962
1139
 
963
1140
  pause<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
964
- return this.postEmpty<T>(`/v1/subscriptions/${id}/pause`, params);
1141
+ return this.postEmpty<T>(`/v1/subscriptions/${p(id)}/pause`, params);
965
1142
  }
966
1143
 
967
1144
  resume<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
968
- return this.postEmpty<T>(`/v1/subscriptions/${id}/resume`, params);
1145
+ return this.postEmpty<T>(`/v1/subscriptions/${p(id)}/resume`, params);
969
1146
  }
970
1147
 
971
1148
  /**
@@ -977,11 +1154,11 @@ export class Subscriptions extends BaseResource {
977
1154
  * Returns `409` if the period has already elapsed.
978
1155
  */
979
1156
  reactivate<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
980
- return this.postEmpty<T>(`/v1/subscriptions/${id}/reactivate`, params);
1157
+ return this.postEmpty<T>(`/v1/subscriptions/${p(id)}/reactivate`, params);
981
1158
  }
982
1159
 
983
1160
  previewUpdate<T = unknown>(id: string, params: { target_price_id: string }): Promise<T> {
984
- return this.postFixed<T>(`/v1/subscriptions/${id}/preview_update`, {
1161
+ return this.postFixed<T>(`/v1/subscriptions/${p(id)}/preview_update`, {
985
1162
  target_price_id: params.target_price_id,
986
1163
  });
987
1164
  }
@@ -991,7 +1168,7 @@ export class Subscriptions extends BaseResource {
991
1168
  params: { target_price_id: string } & IdempotencyOptions,
992
1169
  ): Promise<T> {
993
1170
  return this.postFixed<T>(
994
- `/v1/subscriptions/${id}/update`,
1171
+ `/v1/subscriptions/${p(id)}/update`,
995
1172
  { target_price_id: params.target_price_id },
996
1173
  { idempotencyKey: params.idempotencyKey },
997
1174
  );
@@ -1002,7 +1179,7 @@ export class Subscriptions extends BaseResource {
1002
1179
  params: { return_url: string } & IdempotencyOptions,
1003
1180
  ): Promise<T> {
1004
1181
  return this.postFixed<T>(
1005
- `/v1/subscriptions/${id}/reauthorize_payment_method`,
1182
+ `/v1/subscriptions/${p(id)}/reauthorize_payment_method`,
1006
1183
  { return_url: params.return_url },
1007
1184
  { idempotencyKey: params.idempotencyKey },
1008
1185
  );
@@ -1025,7 +1202,7 @@ export class Subscriptions extends BaseResource {
1025
1202
  * See {@link CreateUsageRecordParams.identifier}.
1026
1203
  */
1027
1204
  createUsageRecord<T = unknown>(id: string, params: CreateUsageRecordParams): Promise<T> {
1028
- return this.post<T, CreateUsageRecordParams>(`/v1/subscriptions/${id}/usage_records`, params);
1205
+ return this.post<T, CreateUsageRecordParams>(`/v1/subscriptions/${p(id)}/usage_records`, params);
1029
1206
  }
1030
1207
 
1031
1208
  /**
@@ -1039,7 +1216,7 @@ export class Subscriptions extends BaseResource {
1039
1216
  id: string,
1040
1217
  params: UsageRecordsListParams = {},
1041
1218
  ): Promise<ListResponseEnvelope<T>> {
1042
- return this.get<ListResponseEnvelope<T>>(`/v1/subscriptions/${id}/usage_records`, params);
1219
+ return this.get<ListResponseEnvelope<T>>(`/v1/subscriptions/${p(id)}/usage_records`, params);
1043
1220
  }
1044
1221
 
1045
1222
  /** Walk every page of `listUsageRecords()` for one subscription. */
@@ -1047,7 +1224,7 @@ export class Subscriptions extends BaseResource {
1047
1224
  id: string,
1048
1225
  options: { pageSize?: number; invoice_id?: string } = {},
1049
1226
  ): AsyncIterableIterator<T> {
1050
- return paginate<T>((p) => this.get(`/v1/subscriptions/${id}/usage_records`, p), {
1227
+ return paginate<T>((page) => this.get(`/v1/subscriptions/${p(id)}/usage_records`, page), {
1051
1228
  pageSize: options.pageSize,
1052
1229
  filters: { invoice_id: options.invoice_id },
1053
1230
  });
@@ -1072,7 +1249,7 @@ export class Subscriptions extends BaseResource {
1072
1249
  * unsettled; while one is open, this period cannot be charged.
1073
1250
  */
1074
1251
  retrieveUsageSummary<T = unknown>(id: string): Promise<T> {
1075
- return this.get<T>(`/v1/subscriptions/${id}/usage_summary`);
1252
+ return this.get<T>(`/v1/subscriptions/${p(id)}/usage_summary`);
1076
1253
  }
1077
1254
  }
1078
1255
 
@@ -1082,7 +1259,7 @@ export class Refunds extends BaseResource {
1082
1259
  }
1083
1260
 
1084
1261
  retrieve<T = unknown>(id: string): Promise<T> {
1085
- return this.get<T>(`/v1/refunds/${id}`);
1262
+ return this.get<T>(`/v1/refunds/${p(id)}`);
1086
1263
  }
1087
1264
 
1088
1265
  list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -1090,7 +1267,7 @@ export class Refunds extends BaseResource {
1090
1267
  }
1091
1268
 
1092
1269
  iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1093
- return paginate<T>((p) => this.get("/v1/refunds", p), { pageSize: options.pageSize });
1270
+ return paginate<T>((page) => this.get("/v1/refunds", page), { pageSize: options.pageSize });
1094
1271
  }
1095
1272
  }
1096
1273
 
@@ -1105,25 +1282,41 @@ export class Refunds extends BaseResource {
1105
1282
  */
1106
1283
  export class Disputes extends BaseResource {
1107
1284
  retrieve<T = unknown>(id: string): Promise<T> {
1108
- return this.get<T>(`/v1/disputes/${id}`);
1285
+ return this.get<T>(`/v1/disputes/${p(id)}`);
1109
1286
  }
1110
1287
 
1111
- list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
1288
+ list<T = unknown>(params: DisputesListParams = {}): Promise<ListResponseEnvelope<T>> {
1112
1289
  return this.get<ListResponseEnvelope<T>>("/v1/disputes", params);
1113
1290
  }
1114
1291
 
1115
- iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1116
- return paginate<T>((p) => this.get("/v1/disputes", p), { pageSize: options.pageSize });
1292
+ iter<T = unknown>(
1293
+ options: { pageSize?: number; status?: string; payment_id?: string } = {},
1294
+ ): AsyncIterableIterator<T> {
1295
+ return paginate<T>((page) => this.get("/v1/disputes", page), {
1296
+ pageSize: options.pageSize,
1297
+ filters: { status: options.status, payment_id: options.payment_id },
1298
+ });
1117
1299
  }
1118
1300
  }
1119
1301
 
1120
1302
  export class WebhookEndpoints extends BaseResource {
1303
+ /**
1304
+ * Every event type this deployment can deliver, plus the wildcard.
1305
+ *
1306
+ * `enabled_events` rejects anything not on this list, so read it rather
1307
+ * than hard-coding a set: a name that is not on it fails at
1308
+ * registration and leaves you with an endpoint that never fires.
1309
+ */
1310
+ listEventTypes<T = unknown>(): Promise<T> {
1311
+ return this.get<T>("/v1/webhook_endpoints/event_types");
1312
+ }
1313
+
1121
1314
  create<T = unknown>(params: CreateWebhookEndpointParams): Promise<T> {
1122
1315
  return this.post<T, CreateWebhookEndpointParams>("/v1/webhook_endpoints", params);
1123
1316
  }
1124
1317
 
1125
1318
  retrieve<T = unknown>(id: string): Promise<T> {
1126
- return this.get<T>(`/v1/webhook_endpoints/${id}`);
1319
+ return this.get<T>(`/v1/webhook_endpoints/${p(id)}`);
1127
1320
  }
1128
1321
 
1129
1322
  /**
@@ -1135,7 +1328,7 @@ export class WebhookEndpoints extends BaseResource {
1135
1328
  * disabling is reversible and deleting is not.
1136
1329
  */
1137
1330
  update<T = unknown>(id: string, params: UpdateWebhookEndpointParams): Promise<T> {
1138
- return this.post<T, UpdateWebhookEndpointParams>(`/v1/webhook_endpoints/${id}`, params);
1331
+ return this.post<T, UpdateWebhookEndpointParams>(`/v1/webhook_endpoints/${p(id)}`, params);
1139
1332
  }
1140
1333
 
1141
1334
  /**
@@ -1150,12 +1343,12 @@ export class WebhookEndpoints extends BaseResource {
1150
1343
  * were sent stays on record.
1151
1344
  */
1152
1345
  delete<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
1153
- return this.del<T>(`/v1/webhook_endpoints/${id}`, params);
1346
+ return this.del<T>(`/v1/webhook_endpoints/${p(id)}`, params);
1154
1347
  }
1155
1348
 
1156
1349
  /** Rotate the signing secret. The new `bkwhsec_...` is returned once. */
1157
1350
  rotateSecret<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
1158
- return this.postEmpty<T>(`/v1/webhook_endpoints/${id}/rotate_secret`, params);
1351
+ return this.postEmpty<T>(`/v1/webhook_endpoints/${p(id)}/rotate_secret`, params);
1159
1352
  }
1160
1353
 
1161
1354
  list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -1163,7 +1356,7 @@ export class WebhookEndpoints extends BaseResource {
1163
1356
  }
1164
1357
 
1165
1358
  iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1166
- return paginate<T>((p) => this.get("/v1/webhook_endpoints", p), {
1359
+ return paginate<T>((page) => this.get("/v1/webhook_endpoints", page), {
1167
1360
  pageSize: options.pageSize,
1168
1361
  });
1169
1362
  }
@@ -1180,7 +1373,7 @@ export class WebhookEndpoints extends BaseResource {
1180
1373
  params: BaseListParams = {},
1181
1374
  ): Promise<ListResponseEnvelope<T>> {
1182
1375
  return this.get<ListResponseEnvelope<T>>(
1183
- `/v1/webhook_endpoints/${endpointId}/deliveries`,
1376
+ `/v1/webhook_endpoints/${p(endpointId)}/deliveries`,
1184
1377
  params,
1185
1378
  );
1186
1379
  }
@@ -1191,14 +1384,14 @@ export class WebhookEndpoints extends BaseResource {
1191
1384
  options: { pageSize?: number } = {},
1192
1385
  ): AsyncIterableIterator<T> {
1193
1386
  return paginate<T>(
1194
- (p) => this.get(`/v1/webhook_endpoints/${endpointId}/deliveries`, p),
1387
+ (page) => this.get(`/v1/webhook_endpoints/${p(endpointId)}/deliveries`, page),
1195
1388
  { pageSize: options.pageSize },
1196
1389
  );
1197
1390
  }
1198
1391
 
1199
1392
  /** Fetch one delivery row for inspection before deciding to redeliver. */
1200
1393
  retrieveDelivery<T = unknown>(endpointId: string, deliveryId: string): Promise<T> {
1201
- return this.get<T>(`/v1/webhook_endpoints/${endpointId}/deliveries/${deliveryId}`);
1394
+ return this.get<T>(`/v1/webhook_endpoints/${p(endpointId)}/deliveries/${p(deliveryId)}`);
1202
1395
  }
1203
1396
 
1204
1397
  /**
@@ -1229,7 +1422,7 @@ export class WebhookEndpoints extends BaseResource {
1229
1422
  params: IdempotencyOptions = {},
1230
1423
  ): Promise<T> {
1231
1424
  return this.postEmpty<T>(
1232
- `/v1/webhook_endpoints/${endpointId}/deliveries/${deliveryId}/redeliver`,
1425
+ `/v1/webhook_endpoints/${p(endpointId)}/deliveries/${p(deliveryId)}/redeliver`,
1233
1426
  params,
1234
1427
  );
1235
1428
  }
@@ -1237,7 +1430,7 @@ export class WebhookEndpoints extends BaseResource {
1237
1430
 
1238
1431
  export class Events extends BaseResource {
1239
1432
  retrieve<T = unknown>(id: string): Promise<T> {
1240
- return this.get<T>(`/v1/events/${id}`);
1433
+ return this.get<T>(`/v1/events/${p(id)}`);
1241
1434
  }
1242
1435
 
1243
1436
  list<T = unknown>(params: EventsListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -1248,7 +1441,7 @@ export class Events extends BaseResource {
1248
1441
  iter<T = unknown>(
1249
1442
  options: { pageSize?: number; type?: string } = {},
1250
1443
  ): AsyncIterableIterator<T> {
1251
- return paginate<T>((p) => this.get("/v1/events", p), {
1444
+ return paginate<T>((page) => this.get("/v1/events", page), {
1252
1445
  pageSize: options.pageSize,
1253
1446
  filters: { type: options.type },
1254
1447
  });
@@ -1268,6 +1461,49 @@ export class Tenant extends BaseResource {
1268
1461
  return this.get<T>("/v1/tenant/capabilities");
1269
1462
  }
1270
1463
 
1464
+ /**
1465
+ * Your registered country and VAT number: what your customers' VAT is
1466
+ * decided against.
1467
+ *
1468
+ * `country_code` is what you have stored and can be `null`;
1469
+ * `effective_country_code` is what the next charge will really use.
1470
+ * The two differ only when you have stored nothing, which is exactly
1471
+ * the case worth spotting before a first live payment.
1472
+ */
1473
+ billingProfile<T = unknown>(): Promise<T> {
1474
+ return this.get<T>("/v1/tenant/billing_profile");
1475
+ }
1476
+
1477
+ /**
1478
+ * Set the seller identity. `country_code` is required on every call;
1479
+ * every other field is partial-update, with an explicit `null` to
1480
+ * clear. Changes take effect on the next charge only. Tax is written
1481
+ * onto a payment and its invoice before money moves, and nothing goes
1482
+ * back and recalculates it.
1483
+ */
1484
+ setBillingProfile<T = unknown>(params: SetTenantBillingProfileParams): Promise<T> {
1485
+ return this.post<T, SetTenantBillingProfileParams>("/v1/tenant/billing_profile", params);
1486
+ }
1487
+
1488
+ /**
1489
+ * Download everything in the account as one JSON document, as raw bytes.
1490
+ *
1491
+ * ```ts
1492
+ * await writeFile("export.json", Buffer.from(await client.tenant.export()));
1493
+ * ```
1494
+ *
1495
+ * The GDPR Article 20 portability route, and the way to take a backup.
1496
+ * It is `application/json` streamed inline, with no redirect, and each
1497
+ * record has the same shape its `GET` route returns, with
1498
+ * `billkit_export_version` naming the shape. It can be large, so write
1499
+ * it to a file rather than holding it in memory. Test and live data
1500
+ * export separately: you get whichever mode the key belongs to. The
1501
+ * access is recorded in your audit log.
1502
+ */
1503
+ export(): Promise<ArrayBuffer> {
1504
+ return this.t.requestBinary({ method: "GET", path: "/v1/tenant/export" });
1505
+ }
1506
+
1271
1507
  /** Current portal branding row (business name, theme, capability flags). */
1272
1508
  portalBranding<T = unknown>(): Promise<T> {
1273
1509
  return this.get<T>("/v1/tenant/portal_branding");
@@ -1305,7 +1541,7 @@ export class Coupons extends BaseResource {
1305
1541
  }
1306
1542
 
1307
1543
  retrieve<T = unknown>(id: string): Promise<T> {
1308
- return this.get<T>(`/v1/coupons/${id}`);
1544
+ return this.get<T>(`/v1/coupons/${p(id)}`);
1309
1545
  }
1310
1546
 
1311
1547
  /**
@@ -1317,7 +1553,7 @@ export class Coupons extends BaseResource {
1317
1553
  * customer was charged. `active: true` brings the campaign back.
1318
1554
  */
1319
1555
  update<T = unknown>(id: string, params: UpdateCouponParams): Promise<T> {
1320
- return this.post<T, UpdateCouponParams>(`/v1/coupons/${id}`, params);
1556
+ return this.post<T, UpdateCouponParams>(`/v1/coupons/${p(id)}`, params);
1321
1557
  }
1322
1558
 
1323
1559
  /**
@@ -1341,7 +1577,7 @@ export class Coupons extends BaseResource {
1341
1577
  }
1342
1578
 
1343
1579
  iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1344
- return paginate<T>((p) => this.get("/v1/coupons", p), { pageSize: options.pageSize });
1580
+ return paginate<T>((page) => this.get("/v1/coupons", page), { pageSize: options.pageSize });
1345
1581
  }
1346
1582
  }
1347
1583
 
@@ -1351,7 +1587,7 @@ export class TaxRates extends BaseResource {
1351
1587
  }
1352
1588
 
1353
1589
  retrieve<T = unknown>(id: string): Promise<T> {
1354
- return this.get<T>(`/v1/tax_rates/${id}`);
1590
+ return this.get<T>(`/v1/tax_rates/${p(id)}`);
1355
1591
  }
1356
1592
 
1357
1593
  /**
@@ -1363,7 +1599,7 @@ export class TaxRates extends BaseResource {
1363
1599
  * is no `delete()`.
1364
1600
  */
1365
1601
  update<T = unknown>(id: string, params: UpdateTaxRateParams): Promise<T> {
1366
- return this.post<T, UpdateTaxRateParams>(`/v1/tax_rates/${id}`, params);
1602
+ return this.post<T, UpdateTaxRateParams>(`/v1/tax_rates/${p(id)}`, params);
1367
1603
  }
1368
1604
 
1369
1605
  list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -1371,7 +1607,7 @@ export class TaxRates extends BaseResource {
1371
1607
  }
1372
1608
 
1373
1609
  iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1374
- return paginate<T>((p) => this.get("/v1/tax_rates", p), { pageSize: options.pageSize });
1610
+ return paginate<T>((page) => this.get("/v1/tax_rates", page), { pageSize: options.pageSize });
1375
1611
  }
1376
1612
  }
1377
1613
 
@@ -1383,8 +1619,9 @@ export class TaxRates extends BaseResource {
1383
1619
  * {@link Invoices.retrievePdf}.
1384
1620
  */
1385
1621
  export class Invoices extends BaseResource {
1386
- retrieve<T = unknown>(id: string): Promise<T> {
1387
- return this.get<T>(`/v1/invoices/${id}`);
1622
+ /** Expandable: `customer`. */
1623
+ retrieve<T = unknown>(id: string, options: ExpandOptions = {}): Promise<T> {
1624
+ return this.get<T>(`/v1/invoices/${p(id)}`, options);
1388
1625
  }
1389
1626
 
1390
1627
  /**
@@ -1406,15 +1643,38 @@ export class Invoices extends BaseResource {
1406
1643
  * structured invoice for tenants who render their own.
1407
1644
  */
1408
1645
  retrievePdf(id: string): Promise<ArrayBuffer> {
1409
- return this.t.requestBinary({ method: "GET", path: `/v1/invoices/${id}/pdf` });
1646
+ return this.t.requestBinary({ method: "GET", path: `/v1/invoices/${p(id)}/pdf` });
1410
1647
  }
1411
1648
 
1412
- list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
1649
+ /**
1650
+ * Send the customer their invoice again.
1651
+ *
1652
+ * The same tenant-branded "your invoice is ready" email, with a fresh
1653
+ * portal link, because the one in the original may have expired. It
1654
+ * goes to the address captured **on the invoice**, not the customer's
1655
+ * current one: this is a copy of a document that was issued to
1656
+ * somebody. An invoice with no address on file is a
1657
+ * `InvalidRequestError` rather than a send that did not happen.
1658
+ */
1659
+ sendEmail<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
1660
+ return this.postEmpty<T>(`/v1/invoices/${p(id)}/email`, params);
1661
+ }
1662
+
1663
+ list<T = unknown>(params: InvoicesListParams = {}): Promise<ListResponseEnvelope<T>> {
1413
1664
  return this.get<ListResponseEnvelope<T>>("/v1/invoices", params);
1414
1665
  }
1415
1666
 
1416
- iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1417
- return paginate<T>((p) => this.get("/v1/invoices", p), { pageSize: options.pageSize });
1667
+ iter<T = unknown>(
1668
+ options: {
1669
+ pageSize?: number;
1670
+ customer_id?: string;
1671
+ subscription_id?: string;
1672
+ payment_id?: string;
1673
+ status?: string;
1674
+ } = {},
1675
+ ): AsyncIterableIterator<T> {
1676
+ const { pageSize, ...filters } = options;
1677
+ return paginate<T>((page) => this.get("/v1/invoices", page), { pageSize, filters });
1418
1678
  }
1419
1679
 
1420
1680
  /**
@@ -1433,7 +1693,7 @@ export class Invoices extends BaseResource {
1433
1693
  * Idempotent: re-voiding an already-void invoice returns it unchanged.
1434
1694
  */
1435
1695
  void<T = unknown>(id: string, params: VoidInvoiceParams = {}): Promise<T> {
1436
- return this.post<T, VoidInvoiceParams>(`/v1/invoices/${id}/void`, params);
1696
+ return this.post<T, VoidInvoiceParams>(`/v1/invoices/${p(id)}/void`, params);
1437
1697
  }
1438
1698
  }
1439
1699
 
@@ -1449,7 +1709,7 @@ export class Invoices extends BaseResource {
1449
1709
  */
1450
1710
  export class CreditNotes extends BaseResource {
1451
1711
  retrieve<T = unknown>(id: string): Promise<T> {
1452
- return this.get<T>(`/v1/credit_notes/${id}`);
1712
+ return this.get<T>(`/v1/credit_notes/${p(id)}`);
1453
1713
  }
1454
1714
 
1455
1715
  /**
@@ -1458,7 +1718,7 @@ export class CreditNotes extends BaseResource {
1458
1718
  * `302`, and `501 rendering_pending` on a deployment with no renderer.
1459
1719
  */
1460
1720
  retrievePdf(id: string): Promise<ArrayBuffer> {
1461
- return this.t.requestBinary({ method: "GET", path: `/v1/credit_notes/${id}/pdf` });
1721
+ return this.t.requestBinary({ method: "GET", path: `/v1/credit_notes/${p(id)}/pdf` });
1462
1722
  }
1463
1723
 
1464
1724
  list<T = unknown>(params: CreditNotesListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -1468,7 +1728,7 @@ export class CreditNotes extends BaseResource {
1468
1728
  iter<T = unknown>(
1469
1729
  options: { pageSize?: number; invoice_id?: string; customer_id?: string } = {},
1470
1730
  ): AsyncIterableIterator<T> {
1471
- return paginate<T>((p) => this.get("/v1/credit_notes", p), {
1731
+ return paginate<T>((page) => this.get("/v1/credit_notes", page), {
1472
1732
  pageSize: options.pageSize,
1473
1733
  filters: { invoice_id: options.invoice_id, customer_id: options.customer_id },
1474
1734
  });
@@ -1484,7 +1744,7 @@ export class CreditNotes extends BaseResource {
1484
1744
  */
1485
1745
  export class AuditLogs extends BaseResource {
1486
1746
  retrieve<T = unknown>(id: string): Promise<T> {
1487
- return this.get<T>(`/v1/audit_logs/${id}`);
1747
+ return this.get<T>(`/v1/audit_logs/${p(id)}`);
1488
1748
  }
1489
1749
 
1490
1750
  list<T = unknown>(params: AuditLogsListParams = {}): Promise<ListResponseEnvelope<T>> {
@@ -1500,7 +1760,7 @@ export class AuditLogs extends BaseResource {
1500
1760
  actor_id?: string;
1501
1761
  } = {},
1502
1762
  ): AsyncIterableIterator<T> {
1503
- return paginate<T>((p) => this.get("/v1/audit_logs", p), {
1763
+ return paginate<T>((page) => this.get("/v1/audit_logs", page), {
1504
1764
  pageSize: options.pageSize,
1505
1765
  filters: {
1506
1766
  action: options.action,
@@ -1520,16 +1780,36 @@ export class AuditLogs extends BaseResource {
1520
1780
  * refunds and disputes are separate flows.
1521
1781
  */
1522
1782
  export class Payments extends BaseResource {
1523
- retrieve<T = unknown>(id: string): Promise<T> {
1524
- return this.get<T>(`/v1/payments/${id}`);
1783
+ /** Expandable: `customer`, `subscription`. */
1784
+ retrieve<T = unknown>(id: string, options: ExpandOptions = {}): Promise<T> {
1785
+ return this.get<T>(`/v1/payments/${p(id)}`, options);
1525
1786
  }
1526
1787
 
1527
- list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
1788
+ /**
1789
+ * Fetch the provider's own record of this charge, live.
1790
+ *
1791
+ * Reads Mollie at request time rather than a stored copy, so it carries
1792
+ * what BillKit deliberately does not keep: the card BIN, the iDEAL
1793
+ * bank, the provider's own status string. Reading live means it can
1794
+ * fail: a provider outage or a charge old enough to have aged out
1795
+ * answers `200` with `available: false` and a short reason, so render
1796
+ * the rest of the page regardless.
1797
+ */
1798
+ retrieveProvider<T = unknown>(id: string): Promise<T> {
1799
+ return this.get<T>(`/v1/payments/${p(id)}/provider`);
1800
+ }
1801
+
1802
+ list<T = unknown>(params: PaymentsListParams = {}): Promise<ListResponseEnvelope<T>> {
1528
1803
  return this.get<ListResponseEnvelope<T>>("/v1/payments", params);
1529
1804
  }
1530
1805
 
1531
- iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1532
- return paginate<T>((p) => this.get("/v1/payments", p), { pageSize: options.pageSize });
1806
+ iter<T = unknown>(
1807
+ options: { pageSize?: number; customer_id?: string } = {},
1808
+ ): AsyncIterableIterator<T> {
1809
+ return paginate<T>((page) => this.get("/v1/payments", page), {
1810
+ pageSize: options.pageSize,
1811
+ filters: { customer_id: options.customer_id },
1812
+ });
1533
1813
  }
1534
1814
  }
1535
1815
 
@@ -1543,18 +1823,56 @@ export class Payments extends BaseResource {
1543
1823
  */
1544
1824
  export class BillingPortalSessions extends BaseResource {
1545
1825
  create<T = unknown>(params: CreateBillingPortalSessionParams): Promise<T> {
1546
- return this.postFixed<T>(
1547
- "/v1/billing_portal/sessions",
1548
- {
1549
- subscription_id: params.subscription_id,
1550
- return_url: params.return_url,
1551
- },
1552
- { idempotencyKey: params.idempotencyKey },
1553
- );
1826
+ return this.post<T, CreateBillingPortalSessionParams>("/v1/billing_portal/sessions", params);
1554
1827
  }
1555
1828
 
1556
1829
  /** Kill an in-the-wild portal session. Idempotent. */
1557
1830
  revoke<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
1558
- return this.postEmpty<T>(`/v1/billing_portal/sessions/${id}/revoke`, params);
1831
+ return this.postEmpty<T>(`/v1/billing_portal/sessions/${p(id)}/revoke`, params);
1832
+ }
1833
+ }
1834
+
1835
+ /**
1836
+ * Issue, inspect and revoke API keys.
1837
+ *
1838
+ * A key is issued in the same mode as the key that created it, so a test
1839
+ * key can only mint test keys, and it can never grant scopes it does not
1840
+ * hold itself. The secret is returned **once**, on
1841
+ * {@link ApiKeys.create}; every later read carries only the prefix.
1842
+ */
1843
+ export class ApiKeys extends BaseResource {
1844
+ /**
1845
+ * Issue a new key. The response's `secret` is the only time the full
1846
+ * key exists outside the caller's own storage, so record it now.
1847
+ */
1848
+ create<T = unknown>(params: CreateApiKeyParams = {}): Promise<T> {
1849
+ return this.post<T, CreateApiKeyParams>("/v1/api_keys", params);
1850
+ }
1851
+
1852
+ /**
1853
+ * One key's metadata: prefix, label, scopes, `revoked_at`, and
1854
+ * `last_used_at`, which is the field to read before revoking one.
1855
+ */
1856
+ retrieve<T = unknown>(id: string): Promise<T> {
1857
+ return this.get<T>(`/v1/api_keys/${p(id)}`);
1858
+ }
1859
+
1860
+ /**
1861
+ * Revoke a key so it stops working. Immediate and irreversible; issue a
1862
+ * new key instead. Revoking an already-revoked key returns it
1863
+ * unchanged, so a retry is safe, and a key may revoke itself, which is
1864
+ * what you want when the leaked key is the one you are calling with.
1865
+ */
1866
+ revoke<T = unknown>(id: string, params: IdempotencyOptions = {}): Promise<T> {
1867
+ return this.postEmpty<T>(`/v1/api_keys/${p(id)}/revoke`, params);
1868
+ }
1869
+
1870
+ /** List keys, newest first. Revoked ones are included; check `revoked_at`. */
1871
+ list<T = unknown>(params: BaseListParams = {}): Promise<ListResponseEnvelope<T>> {
1872
+ return this.get<ListResponseEnvelope<T>>("/v1/api_keys", params);
1873
+ }
1874
+
1875
+ iter<T = unknown>(options: { pageSize?: number } = {}): AsyncIterableIterator<T> {
1876
+ return paginate<T>((page) => this.get("/v1/api_keys", page), { pageSize: options.pageSize });
1559
1877
  }
1560
1878
  }