@billkit-eu/sdk 0.1.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -65,9 +65,12 @@ declare const NOOP_LOGGER: BillKitLogger;
65
65
  * Retry policy for transient failures.
66
66
  *
67
67
  * Retries 5xx + network errors with jittered exponential backoff.
68
- * 4xx (including 409 Idempotency-Key conflicts) are caller-fault and
69
- * never retried. The SDK auto-generates an `Idempotency-Key` for every
70
- * mutating call so retrying a 5xx never double-charges.
68
+ * 4xx are caller-fault and never retried, with one deliberate
69
+ * exception: `409 idempotency_in_progress`. See
70
+ * {@link IN_PROGRESS_CODE}.
71
+ *
72
+ * The SDK auto-generates an `Idempotency-Key` for every mutating call
73
+ * and reuses it across attempts, so retrying never double-charges.
71
74
  */
72
75
  interface RetryPolicy {
73
76
  readonly maxAttempts: number;
@@ -98,6 +101,14 @@ interface RequestOptions {
98
101
  body?: Record<string, unknown> | undefined;
99
102
  idempotencyKey?: string | undefined;
100
103
  extraHeaders?: Record<string, string>;
104
+ /**
105
+ * How to read a **successful** response body. `"json"` (the default)
106
+ * parses it; `"binary"` hands back the raw `ArrayBuffer`, for
107
+ * endpoints that serve a document rather than a resource (the invoice
108
+ * PDF). Error responses are always read as JSON either way, so the
109
+ * typed error hierarchy behaves identically on both paths.
110
+ */
111
+ responseType?: "json" | "binary";
101
112
  }
102
113
  interface TransportConfig {
103
114
  apiKey: string;
@@ -122,6 +133,17 @@ declare class Transport {
122
133
  private readonly fetchFn;
123
134
  private readonly logger;
124
135
  constructor(config: TransportConfig);
136
+ /**
137
+ * Fetch a binary document (currently only the invoice PDF).
138
+ *
139
+ * Same retry policy, same timeout, same typed errors as
140
+ * {@link Transport.request}; only the success-path decoding differs.
141
+ * `fetch` follows the storage adapter's `302` to the signed URL by
142
+ * itself, and the WHATWG spec drops the `Authorization` header on that
143
+ * cross-origin hop — which is correct, since a presigned URL carries
144
+ * its own credential and must not be handed BillKit's API key.
145
+ */
146
+ requestBinary(options: Omit<RequestOptions, "responseType">): Promise<ArrayBuffer>;
125
147
  request<T = unknown>(options: RequestOptions): Promise<T>;
126
148
  }
127
149
 
@@ -203,7 +225,6 @@ declare function paginate<T>(listFn: ListFn<T>, options?: PaginateOptions): Asyn
203
225
  interface BaseListParams {
204
226
  limit?: number;
205
227
  starting_after?: string;
206
- ending_before?: string;
207
228
  readonly [key: string]: QueryValue;
208
229
  }
209
230
  /**
@@ -215,6 +236,26 @@ interface BaseListParams {
215
236
  interface PricesListParams extends BaseListParams {
216
237
  product_id?: string;
217
238
  }
239
+ /**
240
+ * `subscriptions.list` params. Both filters take a comma-separated
241
+ * list (`"active,past_due"`); an unrecognised value is rejected with
242
+ * `400 parameter_invalid` rather than silently ignored.
243
+ *
244
+ * The two answer different questions, and mixing them up is the most
245
+ * common mistake against this route. `status` is where the subscription
246
+ * stands with its payments. `renewal_state` is what happens at the end
247
+ * of the current period. A paused subscription keeps `status: "active"`,
248
+ * because the customer has paid for the period they are in, so
249
+ * `renewal_state: "paused"` is the only way to find paused ones —
250
+ * `status: "paused"` is not an accepted value and is rejected.
251
+ */
252
+ interface SubscriptionsListParams extends BaseListParams {
253
+ customer_id?: string;
254
+ /** `incomplete` | `trialing` | `active` | `past_due` | `canceled`, CSV. */
255
+ status?: string;
256
+ /** `auto_renew` | `paused` | `canceling` | `stopped`, CSV. */
257
+ renewal_state?: string;
258
+ }
218
259
  /** Optional idempotency knob carried by every mutating call. */
219
260
  interface IdempotencyOptions {
220
261
  /** Coalesces retries across process restarts. The SDK generates
@@ -274,6 +315,15 @@ interface UpdateProductParams extends IdempotencyOptions {
274
315
  /** Set false to stop selling a product without deleting history. */
275
316
  active?: boolean;
276
317
  }
318
+ /**
319
+ * Body for `POST /v1/prices/{id}`. `active` is the only field a price
320
+ * accepts, and it moves both ways: `false` withdraws the price from sale,
321
+ * `true` puts it back. The amount, currency and interval are fixed at
322
+ * creation, so neither direction changes what anyone was charged.
323
+ */
324
+ interface UpdatePriceParams extends IdempotencyOptions {
325
+ active: boolean;
326
+ }
277
327
  interface CreatePriceParams extends IdempotencyOptions {
278
328
  /** Existing Product id returned from `client.products.create`. */
279
329
  product_id: string;
@@ -302,6 +352,42 @@ interface CreatePriceParams extends IdempotencyOptions {
302
352
  * regardless of what tax rates exist now or later.
303
353
  */
304
354
  tax_behavior?: "inclusive" | "exclusive" | "unspecified";
355
+ /**
356
+ * `"licensed"` (the default when omitted) bills `amount_cents` per
357
+ * period regardless of consumption. `"metered"` bills
358
+ * `amount_cents` **per reported unit**: post consumption with
359
+ * `subscriptions.createUsageRecord` and the renewal invoice charges
360
+ * `amount_cents × sum(quantity)` for the period. Metered prices
361
+ * must be `interval: "month"`, carry `amount_cents > 0`, and cannot
362
+ * have `trial_days`.
363
+ */
364
+ usage_type?: "licensed" | "metered";
365
+ }
366
+ /**
367
+ * Body for `POST /v1/subscriptions/{id}/usage_records`. Only valid
368
+ * against a subscription whose price is `usage_type: "metered"`; the
369
+ * server rejects a licensed subscription with `400 parameter_invalid`.
370
+ */
371
+ interface CreateUsageRecordParams extends IdempotencyOptions {
372
+ /** Units consumed, `1..1_000_000`. Post multiple records to accumulate. */
373
+ quantity: number;
374
+ /**
375
+ * Epoch seconds when the consumption happened. Omit to let the
376
+ * server stamp receipt time. Useful when reporting is batched and
377
+ * the record must land in the period the usage occurred.
378
+ */
379
+ occurred_at?: number;
380
+ /** Small string metadata map echoed back on the record. */
381
+ metadata?: Record<string, string>;
382
+ }
383
+ /**
384
+ * `subscriptions.listUsageRecords` params. `invoice_id` filters by
385
+ * billing state: `"pending"` selects records not yet rolled into an
386
+ * invoice, and a concrete `inv_...` id selects the records that
387
+ * invoice billed. Omit it to list everything.
388
+ */
389
+ interface UsageRecordsListParams extends BaseListParams {
390
+ invoice_id?: "pending" | (string & {});
305
391
  }
306
392
  interface CreateCheckoutSessionParams extends IdempotencyOptions {
307
393
  /**
@@ -339,6 +425,19 @@ interface CreateCheckoutSessionParams extends IdempotencyOptions {
339
425
  * `0` disables a trial that the price would otherwise grant.
340
426
  */
341
427
  trial_days_override?: number;
428
+ /**
429
+ * `"hosted"` (the default) returns a `url` you redirect the buyer to.
430
+ * `"embedded"` returns a `client_secret` instead, for
431
+ * `mountCheckoutElement()` / `<CheckoutElement/>` from
432
+ * `@billkit-eu/js` — the card fields then render in a cross-origin
433
+ * iframe on your own page.
434
+ */
435
+ ui_mode?: "hosted" | "embedded";
436
+ /**
437
+ * Small string map carried onto the session. Up to 50 keys, key ≤ 40
438
+ * chars, value ≤ 500 chars.
439
+ */
440
+ metadata?: Record<string, string>;
342
441
  }
343
442
  interface CreateRefundParams extends IdempotencyOptions {
344
443
  payment_id?: string;
@@ -518,6 +617,16 @@ declare class Customers extends BaseResource {
518
617
  create<T = unknown>(params?: CreateCustomerParams): Promise<T>;
519
618
  retrieve<T = unknown>(id: string): Promise<T>;
520
619
  update<T = unknown>(id: string, params?: UpdateCustomerParams): Promise<T>;
620
+ /**
621
+ * Delete a customer. Resolves to `{ id, object: "customer", deleted:
622
+ * true }`, not the customer.
623
+ *
624
+ * The customer leaves the API: `retrieve()` 404s and they drop out of
625
+ * `list()`. Their payments, invoices and refunds are untouched, and so
626
+ * is their personal data — use {@link Customers.purge} for a GDPR
627
+ * erasure. Refused while they hold a subscription that can still
628
+ * charge them.
629
+ */
521
630
  delete<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
522
631
  list<T = unknown>(params?: BaseListParams): Promise<ListResponseEnvelope<T>>;
523
632
  /** Walk every page of `list()` and yield each customer. */
@@ -544,10 +653,16 @@ declare class Products extends BaseResource {
544
653
  /** Create a catalog Product, then attach one or more Prices to it. */
545
654
  create<T = unknown>(params: CreateProductParams): Promise<T>;
546
655
  retrieve<T = unknown>(id: string): Promise<T>;
547
- /** Patch mutable Product fields. */
656
+ /**
657
+ * Patch mutable Product fields, or archive it with `active: false`.
658
+ *
659
+ * Archiving is how you stop offering something. The product keeps its
660
+ * id and still comes back from `retrieve()` and `list()`, because what
661
+ * was sold under it has to stay readable, so there is no `delete()`.
662
+ * A checkout against any of its prices is refused from then on, and
663
+ * `active: true` un-archives.
664
+ */
548
665
  update<T = unknown>(id: string, params: UpdateProductParams): Promise<T>;
549
- /** Archive a Product. */
550
- delete<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
551
666
  list<T = unknown>(params?: BaseListParams): Promise<ListResponseEnvelope<T>>;
552
667
  iter<T = unknown>(options?: {
553
668
  pageSize?: number;
@@ -557,6 +672,27 @@ declare class Prices extends BaseResource {
557
672
  /** Create immutable billing terms for an existing Product. */
558
673
  create<T = unknown>(params: CreatePriceParams): Promise<T>;
559
674
  retrieve<T = unknown>(id: string): Promise<T>;
675
+ /**
676
+ * Archive a Price so it stops selling, or put it back on sale.
677
+ *
678
+ * `update(id, { active: false })` archives. The price keeps its id and
679
+ * is still returned by `retrieve()` and by `list()`, because
680
+ * subscriptions renew against it by id and what they are charged has to
681
+ * stay readable. Subscriptions already on it keep renewing at it. What
682
+ * stops is new business: a checkout session against the price is
683
+ * refused and it is no longer offered as a plan change.
684
+ *
685
+ * `{ active: true }` undoes that. `active` is the only field because
686
+ * `amount_cents`, `currency` and `interval` are fixed at creation, and
687
+ * since none of them move here neither direction can change what a past
688
+ * charge was made under. To charge something different, create a new
689
+ * price.
690
+ *
691
+ * Sending the value a price already has returns it unchanged and emits
692
+ * no second event, so a retry is safe. Archiving emits
693
+ * `price.archived`; putting one back emits `price.updated`.
694
+ */
695
+ update<T = unknown>(id: string, params: UpdatePriceParams): Promise<T>;
560
696
  list<T = unknown>(params?: PricesListParams): Promise<ListResponseEnvelope<T>>;
561
697
  iter<T = unknown>(options?: {
562
698
  pageSize?: number;
@@ -583,9 +719,23 @@ declare class OneShotPayments extends BaseResource {
583
719
  }
584
720
  declare class Subscriptions extends BaseResource {
585
721
  retrieve<T = unknown>(id: string): Promise<T>;
586
- list<T = unknown>(params?: BaseListParams): Promise<ListResponseEnvelope<T>>;
722
+ /**
723
+ * List subscriptions, newest first, optionally filtered.
724
+ *
725
+ * Reach for `renewal_state: "paused"` rather than `status: "paused"`
726
+ * to find paused subscriptions; see `SubscriptionsListParams`.
727
+ */
728
+ list<T = unknown>(params?: SubscriptionsListParams): Promise<ListResponseEnvelope<T>>;
729
+ /**
730
+ * Walk every page of `list()`. Filters are carried onto each page
731
+ * request, so a filtered walk narrows server-side instead of paging
732
+ * the whole history and discarding rows client-side.
733
+ */
587
734
  iter<T = unknown>(options?: {
588
735
  pageSize?: number;
736
+ customer_id?: string;
737
+ status?: string;
738
+ renewal_state?: string;
589
739
  }): AsyncIterableIterator<T>;
590
740
  cancel<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
591
741
  pause<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
@@ -608,6 +758,33 @@ declare class Subscriptions extends BaseResource {
608
758
  reauthorizePaymentMethod<T = unknown>(id: string, params: {
609
759
  return_url: string;
610
760
  } & IdempotencyOptions): Promise<T>;
761
+ /**
762
+ * Report consumption against a metered subscription.
763
+ *
764
+ * Only valid when the subscription's price is `usage_type:
765
+ * "metered"`; a licensed subscription is rejected with `400
766
+ * parameter_invalid`. Records accumulate until the renewal invoice
767
+ * rolls them up (`amount_cents × sum(quantity)`); the record's
768
+ * `invoice_id` stays `null` until then.
769
+ *
770
+ * Supports `Idempotency-Key` replay: retrying with the same key
771
+ * returns the same record instead of double-counting the usage,
772
+ * which is what makes at-least-once reporting pipelines safe.
773
+ */
774
+ createUsageRecord<T = unknown>(id: string, params: CreateUsageRecordParams): Promise<T>;
775
+ /**
776
+ * List usage records for one subscription.
777
+ *
778
+ * Pass `invoice_id: "pending"` to reconcile what has been reported
779
+ * but not yet billed, or a concrete invoice id to see what that
780
+ * invoice charged for.
781
+ */
782
+ listUsageRecords<T = unknown>(id: string, params?: UsageRecordsListParams): Promise<ListResponseEnvelope<T>>;
783
+ /** Walk every page of `listUsageRecords()` for one subscription. */
784
+ iterUsageRecords<T = unknown>(id: string, options?: {
785
+ pageSize?: number;
786
+ invoice_id?: string;
787
+ }): AsyncIterableIterator<T>;
611
788
  }
612
789
  declare class Refunds extends BaseResource {
613
790
  create<T = unknown>(params: CreateRefundParams): Promise<T>;
@@ -636,7 +813,26 @@ declare class Disputes extends BaseResource {
636
813
  declare class WebhookEndpoints extends BaseResource {
637
814
  create<T = unknown>(params: CreateWebhookEndpointParams): Promise<T>;
638
815
  retrieve<T = unknown>(id: string): Promise<T>;
816
+ /**
817
+ * Update an endpoint, or stop delivery with `status: "disabled"`.
818
+ *
819
+ * Disabling keeps the endpoint, its signing secret and its delivery
820
+ * history, and `status: "enabled"` resumes. Use {@link
821
+ * WebhookEndpoints.delete} when the endpoint should not exist at all:
822
+ * disabling is reversible and deleting is not.
823
+ */
639
824
  update<T = unknown>(id: string, params: UpdateWebhookEndpointParams): Promise<T>;
825
+ /**
826
+ * Delete an endpoint. Resolves to `{ id, object: "webhook_endpoint",
827
+ * deleted: true }`, not the endpoint.
828
+ *
829
+ * A URL registered by mistake should not be a permanent fixture of the
830
+ * account, so this removes it: `retrieve()` 404s afterwards and it is
831
+ * gone from `list()`. Its delivery attempts go with it, because they
832
+ * are readable only through the endpoint that owns them. The events
833
+ * themselves are untouched and still in `client.events`, so what you
834
+ * were sent stays on record.
835
+ */
640
836
  delete<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
641
837
  /** Rotate the signing secret. The new `whsec_...` is returned once. */
642
838
  rotateSecret<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
@@ -708,8 +904,15 @@ declare class Tenant extends BaseResource {
708
904
  declare class Coupons extends BaseResource {
709
905
  create<T = unknown>(params: CreateCouponParams): Promise<T>;
710
906
  retrieve<T = unknown>(id: string): Promise<T>;
907
+ /**
908
+ * Update a coupon's limits, or withdraw it with `active: false`.
909
+ *
910
+ * A withdrawn code is refused at checkout while the coupon stays
911
+ * readable and discounts already applied keep working out, so there is
912
+ * no `delete()`: a coupon that has been redeemed is part of what a
913
+ * customer was charged. `active: true` brings the campaign back.
914
+ */
711
915
  update<T = unknown>(id: string, params: UpdateCouponParams): Promise<T>;
712
- delete<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
713
916
  /**
714
917
  * Server-side dry-run of a coupon redemption.
715
918
  *
@@ -725,8 +928,15 @@ declare class Coupons extends BaseResource {
725
928
  declare class TaxRates extends BaseResource {
726
929
  create<T = unknown>(params: CreateTaxRateParams): Promise<T>;
727
930
  retrieve<T = unknown>(id: string): Promise<T>;
931
+ /**
932
+ * Correct a rate, retire it with `active: false`, or bring one back.
933
+ *
934
+ * Retiring is how you stop charging VAT in a country. The rate stays
935
+ * readable, because an invoice records the percentage it charged and
936
+ * you have to be able to point at the rate that produced it, so there
937
+ * is no `delete()`.
938
+ */
728
939
  update<T = unknown>(id: string, params: UpdateTaxRateParams): Promise<T>;
729
- delete<T = unknown>(id: string, params?: IdempotencyOptions): Promise<T>;
730
940
  list<T = unknown>(params?: BaseListParams): Promise<ListResponseEnvelope<T>>;
731
941
  iter<T = unknown>(options?: {
732
942
  pageSize?: number;
@@ -736,12 +946,30 @@ declare class TaxRates extends BaseResource {
736
946
  * Read-only access to generated invoices.
737
947
  *
738
948
  * Invoices are produced by the billing pipeline; tenants don't create
739
- * them directly. PDF retrieval returns a 302 redirect to the storage
740
- * adapter's signed URL. Follow it transparently with the runtime's
741
- * fetch settings.
949
+ * them directly. Fetch the rendered document with
950
+ * {@link Invoices.retrievePdf}.
742
951
  */
743
952
  declare class Invoices extends BaseResource {
744
953
  retrieve<T = unknown>(id: string): Promise<T>;
954
+ /**
955
+ * Download the rendered invoice PDF as raw bytes.
956
+ *
957
+ * ```ts
958
+ * const pdf = await client.invoices.retrievePdf("inv_123");
959
+ * await writeFile("invoice.pdf", Buffer.from(pdf));
960
+ * ```
961
+ *
962
+ * Blob-backed deployments stream the bytes inline; S3-backed ones
963
+ * answer `302` to a presigned URL, which `fetch` follows for us under
964
+ * the SDK's own timeout and retry policy — so both storage adapters
965
+ * look identical from here.
966
+ *
967
+ * Deployments with `INVOICE_PDF_ENABLED=false` never render one and
968
+ * answer `501 rendering_pending`, which surfaces as a `ServerError`
969
+ * whose `code` is `"rendering_pending"`; `retrieve()` still returns the
970
+ * structured invoice for tenants who render their own.
971
+ */
972
+ retrievePdf(id: string): Promise<ArrayBuffer>;
745
973
  list<T = unknown>(params?: BaseListParams): Promise<ListResponseEnvelope<T>>;
746
974
  iter<T = unknown>(options?: {
747
975
  pageSize?: number;
@@ -876,7 +1104,7 @@ declare class RateLimitError extends BillKitError {
876
1104
  });
877
1105
  }
878
1106
 
879
- declare const VERSION = "0.1.0";
1107
+ declare const VERSION = "0.2.0";
880
1108
 
881
1109
  /**
882
1110
  * Verify `BillKit-Signature: t=<unix>,v1=<hex>` headers.
@@ -908,4 +1136,4 @@ interface VerifyWebhookOptions {
908
1136
  }
909
1137
  declare function verifyWebhookSignature<T = unknown>(options: VerifyWebhookOptions): Promise<T>;
910
1138
 
911
- export { APIConnectionError, APIError, type AuditLogsListParams, AuthenticationError, type BaseListParams, BillKit, BillKitError, type BillKitLogger, type BillKitOptions, ConflictError, type CreateBillingPortalSessionParams, type CreateCheckoutSessionParams, type CreateCouponParams, type CreateCustomerParams, type CreateOneShotPaymentParams, type CreatePriceParams, type CreateProductParams, type CreateRefundParams, type CreateTaxRateParams, type CreateWebhookEndpointParams, DEFAULT_RETRY_POLICY, DEFAULT_WEBHOOK_TOLERANCE_SECONDS, type EventsListParams, type IdempotencyOptions, InvalidRequestError, type ListParams, type ListResponseEnvelope, type LogContext, NOOP_LOGGER, type PaginateOptions, PermissionError, type PricesListParams, RateLimitError, ResourceMissingError, type RetryPolicy, type RotateProviderCredentialParams, ServerError, type SetPortalBrandingParams, type UpdateCouponParams, type UpdateCustomerParams, type UpdateProductParams, type UpdateTaxRateParams, type UpdateWebhookEndpointParams, VERSION, type ValidateCouponParams, type VerifyWebhookOptions, WebhookVerificationError, paginate, verifyWebhookSignature };
1139
+ export { APIConnectionError, APIError, type AuditLogsListParams, AuthenticationError, type BaseListParams, BillKit, BillKitError, type BillKitLogger, type BillKitOptions, ConflictError, type CreateBillingPortalSessionParams, type CreateCheckoutSessionParams, type CreateCouponParams, type CreateCustomerParams, type CreateOneShotPaymentParams, type CreatePriceParams, type CreateProductParams, type CreateRefundParams, type CreateTaxRateParams, type CreateUsageRecordParams, type CreateWebhookEndpointParams, DEFAULT_RETRY_POLICY, DEFAULT_WEBHOOK_TOLERANCE_SECONDS, type EventsListParams, type IdempotencyOptions, InvalidRequestError, type ListParams, type ListResponseEnvelope, type LogContext, NOOP_LOGGER, type PaginateOptions, PermissionError, type PricesListParams, RateLimitError, ResourceMissingError, type RetryPolicy, type RotateProviderCredentialParams, ServerError, type SetPortalBrandingParams, type SubscriptionsListParams, type UpdateCouponParams, type UpdateCustomerParams, type UpdateProductParams, type UpdateTaxRateParams, type UpdateWebhookEndpointParams, type UsageRecordsListParams, VERSION, type ValidateCouponParams, type VerifyWebhookOptions, WebhookVerificationError, paginate, verifyWebhookSignature };