@usebillow/sdk 0.5.0 → 0.7.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.cjs CHANGED
@@ -133,10 +133,10 @@ function resolveBaseUrl(explicit) {
133
133
  const fromEnv = typeof process !== "undefined" ? process.env?.["BILLOW_URL"] : void 0;
134
134
  return (explicit || fromEnv || DEFAULT_BASE_URL).replace(/\/$/, "");
135
135
  }
136
- async function apiRequest(ctx, method, path, body, options) {
137
- return (await apiRequestWithStatus(ctx, method, path, body, options)).data;
136
+ async function apiRequest(ctx, method, path2, body, options) {
137
+ return (await apiRequestWithStatus(ctx, method, path2, body, options)).data;
138
138
  }
139
- async function apiRequestWithStatus(ctx, method, path, body, options) {
139
+ async function apiRequestWithStatus(ctx, method, path2, body, options) {
140
140
  const init = {
141
141
  method,
142
142
  headers: {
@@ -146,7 +146,7 @@ async function apiRequestWithStatus(ctx, method, path, body, options) {
146
146
  }
147
147
  };
148
148
  if (body) init.body = JSON.stringify(body);
149
- const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path}`, init, {
149
+ const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path2}`, init, {
150
150
  timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,
151
151
  safeToRepeat: safeToRepeat(method, !!options?.idempotencyKey),
152
152
  callerSignal: options?.signal
@@ -192,10 +192,10 @@ function errorFrom(res, data) {
192
192
  requestIdOf(res)
193
193
  );
194
194
  }
195
- async function apiRequestBinary(ctx, method, path, options) {
195
+ async function apiRequestBinary(ctx, method, path2, options) {
196
196
  const res = await sendWithResilience(
197
197
  ctx,
198
- `${ctx.baseUrl}${path}`,
198
+ `${ctx.baseUrl}${path2}`,
199
199
  { method, headers: { authorization: `Bearer ${ctx.bearer}` } },
200
200
  {
201
201
  timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,
@@ -534,11 +534,14 @@ function createCreditsResource(ctx) {
534
534
  * checkout to send the buyer to. Credits are granted when the payment succeeds (listen for
535
535
  * `credit_top_up.succeeded`). The `idempotencyKey` is required and must identify this one
536
536
  * purchase: a retry under it answers the same top-up as it now stands (`replayed: true`) and
537
- * buys nothing again; reusing it for another purchase throws `idempotency_conflict` (409).
538
- * A retry can also throw `conflict` (409), with `details.reason`: `checkout_in_progress` -
539
- * the first request is still opening the checkout, so retry the same key shortly - or
540
- * `checkout_unavailable` - its checkout could not be opened and never will be, so buy again
541
- * under a new key.
537
+ * buys nothing again - asking the payment provider again for a checkout the first attempt
538
+ * could not open, within its page's 40 minutes (Stripe only in the first 10, while 30
539
+ * remain: it answers a timed-out first attempt with the checkout it made, or makes one never
540
+ * made; Paymob only if the first attempt never made one); reusing it for another purchase
541
+ * throws `idempotency_conflict` (409). A retry can also throw `conflict` (409), with
542
+ * `details.reason`: `checkout_in_progress` - another request is still opening the
543
+ * checkout, so retry the same key shortly - or `checkout_unavailable` - its checkout could
544
+ * not be opened and never will be, so buy again under a new key.
542
545
  */
543
546
  create: async (input, options) => {
544
547
  const { status, data } = await apiRequestWithStatus(
@@ -620,6 +623,155 @@ function createCreditsResource(ctx) {
620
623
  void 0,
621
624
  options
622
625
  )
626
+ },
627
+ rateCards: {
628
+ /**
629
+ * Publish a new Rate Card version: what each Credit Action costs, as
630
+ * `ceil(units * unitPrice / perUnits)` microcredits a line. Versions are immutable - a price
631
+ * change is a new version - and take effect in order, at `effectiveAt` (now when omitted).
632
+ */
633
+ publish: (input, options) => apiRequest(ctx, "POST", "/v1/credits/rate-cards", input, options),
634
+ /** The version in effect now (`not_found` before the first takes effect). */
635
+ current: (options) => apiRequest(
636
+ ctx,
637
+ "GET",
638
+ "/v1/credits/rate-cards/current",
639
+ void 0,
640
+ options
641
+ ),
642
+ /** One version, by its number. */
643
+ get: (version, options) => apiRequest(
644
+ ctx,
645
+ "GET",
646
+ `/v1/credits/rate-cards/${encodeURIComponent(String(version))}`,
647
+ void 0,
648
+ options
649
+ ),
650
+ /**
651
+ * The published versions, newest first. Auto-paginating by cursor: `await` the first page,
652
+ * `for await (…)` every version, or `.listAll()` to collect them.
653
+ */
654
+ list: (params = {}, options) => makeCursorListPromise(
655
+ (p) => apiRequest(
656
+ ctx,
657
+ "GET",
658
+ `/v1/credits/rate-cards${toQuery(p)}`,
659
+ void 0,
660
+ options
661
+ ),
662
+ params
663
+ )
664
+ },
665
+ quotes: {
666
+ /**
667
+ * Price a customer's Credit Actions (all of one category) at the current Rate Card version
668
+ * and keep the price for 15 minutes: a reservation over the quote holds exactly its
669
+ * `amount`, and its commit prices at the version the quote pinned. A quote holds nothing.
670
+ */
671
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/credits/quotes", input, options),
672
+ /** A quote as it was made. */
673
+ get: (quoteId, options) => apiRequest(
674
+ ctx,
675
+ "GET",
676
+ `/v1/credits/quotes/${encodeURIComponent(quoteId)}`,
677
+ void 0,
678
+ options
679
+ )
680
+ },
681
+ reservations: {
682
+ /**
683
+ * Hold credits for one operation: over a `quoteId`, or over `items` priced at the current
684
+ * Rate Card version. The `idempotencyKey` is required: a retry under it - after a lost
685
+ * reply, even once the quote expired - answers the same reservation (`replayed: true`) and
686
+ * holds nothing again. Reusing it for another request, or a new key with an `operationKey`
687
+ * the customer already used (`details.reason: "operation_key_used"`), throws
688
+ * `idempotency_conflict` (409). Throws `insufficient_credits` (402), `account_frozen`
689
+ * (423), `account_closed` (409), `conflict` (409, `details.reason: "quote_expired"`), or
690
+ * `validation_error` (422) - e.g. `details.reason: "zero_amount"` when the items cost nothing.
691
+ * The `operationKey` is an operation id, never personal data: erasure replaces it.
692
+ */
693
+ create: async (input, options) => {
694
+ const { status, data } = await apiRequestWithStatus(
695
+ ctx,
696
+ "POST",
697
+ "/v1/credits/reservations",
698
+ input,
699
+ options
700
+ );
701
+ return { ...data, replayed: status === 200 };
702
+ },
703
+ /** A reservation as it now stands. */
704
+ get: (reservationId, options) => apiRequest(
705
+ ctx,
706
+ "GET",
707
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}`,
708
+ void 0,
709
+ options
710
+ ),
711
+ /**
712
+ * A customer's reservation (by external id) made with `operationKey` - how to find one whose
713
+ * reply was lost. Throws `not_found` (404) when there is none.
714
+ */
715
+ findByOperationKey: (customer, operationKey, options) => apiRequest(
716
+ ctx,
717
+ "GET",
718
+ `/v1/credits/reservations${toQuery({ customer, operationKey })}`,
719
+ void 0,
720
+ options
721
+ ),
722
+ /**
723
+ * Commit what the operation used: lines priced at the reservation's Rate Card version,
724
+ * consuming at most what it holds (`commit_exceeds_hold`, 409) and releasing the rest; lines
725
+ * that cost nothing in all consume nothing (`consumed: "0"`, `consumptionId: null`). The
726
+ * same commit again answers it again, so it is safe to retry; anything else after it throws
727
+ * `invalid_state_transition` (409) with the reservation in `details` - with
728
+ * `details.reason: "reservation_expired"` once the hold is past its `expiresAt`.
729
+ */
730
+ commit: (reservationId, items, options) => apiRequest(
731
+ ctx,
732
+ "POST",
733
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}/commit`,
734
+ { items },
735
+ options
736
+ ),
737
+ /** Release the whole hold. Safe to retry; after a commit it throws (409). */
738
+ release: (reservationId, options) => apiRequest(
739
+ ctx,
740
+ "POST",
741
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}/release`,
742
+ void 0,
743
+ options
744
+ ),
745
+ /**
746
+ * Protect a held reservation whose operation was paid for and is still being fulfilled: it
747
+ * never expires (commit or release it when done). Safe to retry. A hold already past its
748
+ * `expiresAt` throws `invalid_state_transition` (409, `details.reason: "reservation_expired"`).
749
+ */
750
+ protect: (reservationId, options) => apiRequest(
751
+ ctx,
752
+ "POST",
753
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}/protect`,
754
+ void 0,
755
+ options
756
+ )
757
+ },
758
+ consumptions: {
759
+ /**
760
+ * Give back part or all of a commit's consumption (`consumptionId` on the committed
761
+ * reservation), never more than is left (`reversal_exceeds_consumption`, 409). The
762
+ * `idempotencyKey` is required: a retry under it answers the same reversal
763
+ * (`replayed: true`) and gives nothing back again.
764
+ */
765
+ reverse: async (consumptionId, input, options) => {
766
+ const { status, data } = await apiRequestWithStatus(
767
+ ctx,
768
+ "POST",
769
+ `/v1/credits/consumptions/${encodeURIComponent(consumptionId)}/reverse`,
770
+ input,
771
+ options
772
+ );
773
+ return { ...data, replayed: status === 200 };
774
+ }
623
775
  }
624
776
  };
625
777
  }
@@ -732,6 +884,33 @@ function createDeliverabilityResource(ctx) {
732
884
  };
733
885
  }
734
886
 
887
+ // src/resources/hosted-domains.ts
888
+ var path = (id, action = "") => `/v1/hosted-domains/${encodeURIComponent(id)}${action}`;
889
+ function createHostedDomainsResource(ctx) {
890
+ return {
891
+ /** The Project's domains, and whether its plan allows adding more (`whiteLabel`). */
892
+ list: (options) => apiRequest(ctx, "GET", "/v1/hosted-domains", void 0, options),
893
+ /**
894
+ * Add a subdomain such as `billing.example.com`. Answers the domain with the TXT and CNAME
895
+ * `records` to create. Needs a plan with white-label branding (`permission_denied` otherwise).
896
+ */
897
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/hosted-domains", input, options),
898
+ get: (id, options) => apiRequest(ctx, "GET", path(id), void 0, options),
899
+ /** Check the DNS records now (2 per minute per domain); answers the refreshed domain. */
900
+ verify: (id, options) => apiRequest(ctx, "POST", path(id, "/verify"), void 0, options),
901
+ /** Make an active domain the default host for new portal sessions. */
902
+ makePrimary: (id, options) => apiRequest(ctx, "POST", path(id, "/primary"), void 0, options),
903
+ /** Remove a domain; the portal sessions on it end immediately. */
904
+ remove: (id, options) => apiRequest(
905
+ ctx,
906
+ "DELETE",
907
+ path(id),
908
+ void 0,
909
+ options
910
+ )
911
+ };
912
+ }
913
+
735
914
  // src/resources/invoices.ts
736
915
  function createInvoicesResource(ctx) {
737
916
  return {
@@ -1197,6 +1376,8 @@ var Billow = class {
1197
1376
  portalSessions;
1198
1377
  deliverability;
1199
1378
  marketplace;
1379
+ /** Custom domains for the hosted customer portal (live keys, behind a rollout flag). */
1380
+ hostedDomains;
1200
1381
  /**
1201
1382
  * Organization settings (Phase D3, F) — the configurable dunning schedule, usage settlement grace,
1202
1383
  * and the cross-currency reporting currency + FX-rate registry (ADR-0013).
@@ -1217,10 +1398,11 @@ var Billow = class {
1217
1398
  this.portalSessions = createPortalSessionsResource(this.#ctx);
1218
1399
  this.deliverability = createDeliverabilityResource(this.#ctx);
1219
1400
  this.marketplace = createMarketplaceResource(this.#ctx);
1401
+ this.hostedDomains = createHostedDomainsResource(this.#ctx);
1220
1402
  this.settings = createSettingsResource(this.#ctx);
1221
1403
  }
1222
- #request(method, path, body, options) {
1223
- return apiRequest(this.#ctx, method, path, body, options);
1404
+ #request(method, path2, body, options) {
1405
+ return apiRequest(this.#ctx, method, path2, body, options);
1224
1406
  }
1225
1407
  coupons = {
1226
1408
  /** Define a coupon (a reusable discount template). */
@@ -1437,6 +1619,21 @@ var BillowPortal = class {
1437
1619
  session(options) {
1438
1620
  return apiRequest(this.#ctx, "GET", "/portal/session", void 0, options);
1439
1621
  }
1622
+ /**
1623
+ * The single-use entry exchange (ADR-0039): on a client built with a minted link's entry
1624
+ * token, trade it for the session token to use from now on. Succeeds once per link, and
1625
+ * afterwards only for the browser presenting that exchange's `sessionToken`; anything else is
1626
+ * a 401. For a hosted surface; an integrator's own surface may keep using the entry token.
1627
+ */
1628
+ exchange(input, options) {
1629
+ return apiRequest(
1630
+ this.#ctx,
1631
+ "POST",
1632
+ "/portal/session/exchange",
1633
+ input,
1634
+ options
1635
+ );
1636
+ }
1440
1637
  /** The self-serve home payload: identity + subscriptions + usage + saved cards. */
1441
1638
  me(options) {
1442
1639
  return apiRequest(this.#ctx, "GET", "/portal/me", void 0, options);
@@ -1453,6 +1650,14 @@ var BillowPortal = class {
1453
1650
  ),
1454
1651
  params
1455
1652
  ),
1653
+ /** One of the Customer's invoices, in the list's shape. */
1654
+ get: (id, options) => apiRequest(
1655
+ this.#ctx,
1656
+ "GET",
1657
+ `/portal/invoices/${encodeURIComponent(id)}`,
1658
+ void 0,
1659
+ options
1660
+ ),
1456
1661
  /** Open or resume checkout for this customer's outstanding invoice. */
1457
1662
  pay: (id, input = {}, options) => apiRequest(
1458
1663
  this.#ctx,