@openrails/billing-ui 0.237.0 → 0.238.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 CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## Public reads name their merchant; token source
4
+
5
+ - Breaking: `listProducts({ merchant, keys })`, `useProducts({ merchant, keys
6
+ })` and `<BuyButton merchant>` take the merchant's id, and `<Offers>` takes
7
+ it with `keys`: `GET /catalog/products?merchant=<id>&keys=…`. The read sends
8
+ no bearer, signed in or out.
9
+ - Breaking: `getConfig()` and `useConfig()` are the deployment's part alone
10
+ (`capabilities`, `currencies`, `captcha`, `network`); `PublicConfig` loses
11
+ `payment` and `merchant`. `getCustomerConfig()` and `useCustomerConfig()`
12
+ read the customer's merchant (`GET /me/config`, `CustomerConfig`: `payment`
13
+ and `merchant`); `<OrderCheckout>`, `<AccountBilling>` and
14
+ `<PaymentMethodsPanel>` take their PSPs from it.
15
+ - Breaking: `createConfigStore(read)` takes the read it shares, and
16
+ `ConfigStore` and `ConfigState` name what they hold (`ConfigStore<T>`).
17
+ - `tokenSource({ fetchToken, skew = 60 })` is a `getToken` that keeps the
18
+ customer's access token in memory (never in browser storage), replaces it
19
+ `skew` seconds before it expires with one request in flight, and has
20
+ `invalidate()`. With one, the client sends a call answered 401
21
+ `credential_expired` once more with a new token.
22
+ - `redeemAssertion({ tokenEndpoint, assertion, fetch })` redeems the
23
+ assertion a merchant's backend signed for its customer (RFC 7523,
24
+ `scope=openrails:self`) with the fetch it is given, so a DPoP fetch binds
25
+ the token to the browser's key.
26
+
27
+ ## Payment operations removed
28
+
29
+ - Breaking: `getPaymentAuthentication`, `confirmPaymentAuthentication`,
30
+ `authenticatePayment`, `PaymentOperation` and `PaymentAuthentication` are
31
+ removed, with `operation_id` on `SubscriptionChange` and `operation` on
32
+ `PaymentRecovery`. A change the issuer challenges answers an `authenticate`
33
+ `next_action`; `authenticateChange(client, id, change, psp)` completes it
34
+ and confirms the change (`confirmSubscriptionChange`).
35
+ - `NextAction` is one shape for orders, changes and subscriptions (`type`,
36
+ `url`, `psp_id`, `transactions`, `payload`); `OrderNextAction` is gone.
37
+
3
38
  ## Running charges
4
39
 
5
40
  - `Balance` gains `accrued_amount`, `accrual_rate_per_hour`, `as_of` and
@@ -235,15 +270,6 @@
235
270
  product on sale with its current prices. Filter by currency, product or
236
271
  renewal in the browser.
237
272
 
238
- ## Paying on a customer surface
239
-
240
- - `client.checkoutSource(id, { customerBase })` reads and pays a checkout
241
- session as its signed-in customer at a customer surface's
242
- `/checkout-sessions/{id}` (for example a host's
243
- `/api/v1/merchants/acme/billing/me`), so its saved cards pay. Without
244
- `customerBase` the session id alone pays at `/checkout-sessions/{id}` as
245
- before. `<CheckoutPage customerBase>` passes it through.
246
-
247
273
  ## 1.0.0
248
274
 
249
275
  Ships with OpenRails v1.0.0; use the two at the same version. Everything a host
package/README.md CHANGED
@@ -42,11 +42,33 @@ const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFe
42
42
  </BillingProvider>
43
43
  ```
44
44
 
45
+ Where your backend mints the customer's token, `tokenSource` keeps it: in
46
+ memory only, replaced `skew` seconds (60) before it expires with one request
47
+ however many calls wait, and once more when a call answers 401
48
+ `credential_expired`.
49
+
50
+ ```ts
51
+ import { createBillingClient, redeemAssertion, tokenSource } from "@openrails/billing-ui"
52
+
53
+ const billing = createBillingClient({
54
+ baseUrl: "https://billing.example/v1",
55
+ getToken: tokenSource({
56
+ // { access_token, expires_in } from your backend, or its signed assertion redeemed here.
57
+ fetchToken: async () => {
58
+ const { assertion } = await (await fetch("/api/billing/assertion", { method: "POST" })).json()
59
+ return redeemAssertion({ tokenEndpoint: "https://billing.example/oauth2/token", assertion })
60
+ },
61
+ }),
62
+ })
63
+ ```
64
+
45
65
  `<BuyButton>` buys one price, `<Offers>` the products your server names by
46
66
  key (a course, its bundle and the membership that also unlocks it; the Go
47
67
  `Client.ListOffers` finds the products granting an entitlement): a
48
68
  `BuyButton` per price ("$4.99", "Rent for 3 days, $1.99",
49
- "$10.00 every 30 days"). The purchase is theirs from start to payment: a
69
+ "$10.00 every 30 days"). Both look prices up in the public catalog of the
70
+ merchant whose id you pass as `merchant`, signed in or out. The purchase is
71
+ theirs from start to payment: a
50
72
  button creates the customer's order and pays it in a dialog
51
73
  (`<OrderCheckout>`); a second click before it is paid opens the same order.
52
74
  Signing in is the host's: with `signedIn={false}`, or when OpenRails answers
@@ -56,6 +78,7 @@ granting anything.
56
78
 
57
79
  ```tsx
58
80
  <BuyButton
81
+ merchant={merchantId} // your merchant's id, from your server
59
82
  product="course-101"
60
83
  price="rent" // labelled from the catalog unless you pass label
61
84
  signedIn={signedIn}
@@ -64,6 +87,7 @@ granting anything.
64
87
  />
65
88
 
66
89
  <Offers
90
+ merchant={merchantId}
67
91
  keys={["course-101", "course-bundle", "channel-membership"]} // from your server, or products={...} from its Client.ListOffers
68
92
  signedIn={signedIn}
69
93
  onSignInRequired={() => openSignIn()}
@@ -87,7 +111,7 @@ const amount = decimalToAmount(enteredAmount, currency.decimals)
87
111
  const bounds = depositPrice.customer_amount
88
112
  const valid = amount && bounds && BigInt(amount) >= BigInt(bounds.min_amount) && BigInt(amount) <= BigInt(bounds.max_amount)
89
113
 
90
- <BuyButton product="api-credits" price="deposit" amount={valid ? amount : undefined} label="Add credits" {...handlers} />
114
+ <BuyButton merchant={merchantId} product="api-credits" price="deposit" amount={valid ? amount : undefined} label="Add credits" {...handlers} />
91
115
  ```
92
116
 
93
117
  OpenRails validates the bounds and whole currency minor units before accepting
@@ -140,23 +164,25 @@ const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFe
140
164
  </BillingProvider>
141
165
  ```
142
166
 
143
- The panels read OpenRails' public configuration (`GET /config`) themselves,
144
- once per `BillingProvider`: "Add card" offers each PSP of its `payment` that
145
- saves a card in the page, and says so while the configuration fails or its
146
- card PSPs are temporarily unavailable; amounts use its currency registry over
147
- the pinned copy. `useConfig()` and `useCurrencyScales()` read the same copy
148
- for host components.
167
+ The panels read OpenRails' configuration themselves, once per
168
+ `BillingProvider`: the customer's merchant's (`GET /me/config`), where "Add
169
+ card" offers each PSP of its `payment` that saves a card in the page, and says
170
+ so while the read fails or its card PSPs are temporarily unavailable; and the
171
+ deployment's (`GET /config`), whose currency registry amounts use over the
172
+ pinned copy. `useCustomerConfig()`, `useConfig()` and `useCurrencyScales()`
173
+ read the same copies for host components.
149
174
 
150
175
  ## Catalog and plan changes
151
176
 
152
177
  The client also reads the public catalog and changes a subscription's plan.
153
- The catalog read is a public route, served only when the host mounts
154
- OpenRails' checkout routes; `GET /config` is always served.
178
+ The catalog read and `GET /config` are public routes: the client sends them
179
+ no bearer.
155
180
 
156
181
  | Call | Route |
157
182
  | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
158
- | `listProducts({ keys })`: the products on sale named by `keys` (1 to 100 product keys), each with its prices, in one page; `useProducts` reads the same | `GET /catalog/products` |
159
- | `getConfig()` (capabilities, `currencies`, `payment` with PSPs, `solana.network` and tokens; `client.currencies` is the pinned registry) | `GET /config` |
183
+ | `listProducts({ merchant, keys })`: the products `merchant` (its id) has on sale named by `keys` (1 to 100 product keys), each with its prices, in one page; `useProducts` reads the same | `GET /catalog/products` |
184
+ | `getConfig()` (capabilities, `currencies`, `captcha`; `client.currencies` is the pinned registry) | `GET /config` |
185
+ | `getCustomerConfig()` (the customer's merchant: `payment` with PSPs, `solana.network` and tokens, and `merchant`, its name) | `GET /me/config` |
160
186
  | `previewSubscriptionChange(id, { priceId?, quantity? })` | `POST /me/subscriptions/{id}/change/preview` |
161
187
  | `changeSubscription(id, { priceId?, quantity?, idempotencyKey, signature? })` | `POST /me/subscriptions/{id}/change` |
162
188
 
@@ -173,10 +199,11 @@ const change = await billing.changeSubscription(sub.id, {
173
199
  charge now; fewer seats and a downgrade apply at the next renewal.
174
200
  - `changeSubscription` is a money write. Mint one `idempotencyKey` per attempt and
175
201
  reuse it until the change resolves (`status: "processing"`, a
176
- `subscription_change_in_flight` refusal naming `metadata.operation_id`, a 5xx or a
177
- lost response), so OpenRails replays the stored result instead of charging
178
- twice. `requires_action` carries `operation_id` for `authenticatePayment`;
179
- replay the same key afterwards.
202
+ `subscription_change_in_flight` refusal, a 5xx or a lost response), so
203
+ OpenRails replays the stored result instead of charging twice. A change the
204
+ card issuer challenges answers `requires_action` with an `authenticate`
205
+ `next_action`: `authenticateChange(client, id, change, psp)` runs it in the
206
+ page and confirms the change (`confirmSubscriptionChange`).
180
207
  - A rail that needs the customer's own step answers `requires_action` with a
181
208
  `next_action`. Solana's is `solana_sign_transactions`:
182
209
  `signWalletAction(change.next_action, send)` signs and sends its
@@ -188,7 +215,7 @@ const change = await billing.changeSubscription(sub.id, {
188
215
 
189
216
  ## Provider-neutral flows
190
217
 
191
- OpenRails's browser PSP configs (`GET /config`'s `payment.psps`) pick the
218
+ OpenRails's browser PSP configs (`GET /me/config`'s `payment.psps`) pick the
192
219
  browser flow from each PSP's `flow` and public `config`
193
220
  (Collect.js, native card inputs, Stripe Elements, redirect, wallet). Hosts never branch on a
194
221
  provider:
@@ -208,8 +235,6 @@ provider:
208
235
  page (needs `BillingProvider`); `canSavePaymentMethod(psp)` filters PSPs
209
236
  that support it. After an off-page verification the provider returns to
210
237
  `returnURL(setupId)`; confirm with `client.confirmCardSetup(id)`.
211
- - `authenticatePayment(client, operationId, psp)` completes a pending
212
- payment's 3-D Secure challenge when `canAuthenticatePayment(psp)`.
213
238
  - `defaultCountry` (SavePaymentMethod, AccountBilling)
214
239
  preselects the billing country, else the browser locale's region.
215
240
 
@@ -13,6 +13,6 @@ export interface PaymentMethodsPanelProps {
13
13
  }
14
14
  /**
15
15
  * The customer's saved cards. "Add card" offers each PSP of the merchant's
16
- * payment setup (`GET /config`) that saves a card in the page.
16
+ * payment setup (`GET /me/config`) that saves a card in the page.
17
17
  */
18
18
  export declare function PaymentMethodsPanel({ defaultCurrency, defaultCountry, appearance, className, }: PaymentMethodsPanelProps): React.JSX.Element;
@@ -1,8 +1,9 @@
1
1
  import { BillingClient } from './client/client';
2
+ import { SubscriptionChange } from './client/types';
2
3
  import { PspConfig } from './psp';
3
4
  /**
4
- * Completes the provider challenge (3-D Secure) a pending payment operation
5
- * waits on. "not_required" when the provider has none outstanding; the
6
- * operation's own status remains the result.
5
+ * Completes the issuer's authentication (3-D Secure) a subscription change
6
+ * waits on, then confirms the change and answers it. A change with no
7
+ * `authenticate` next action is answered as it is.
7
8
  */
8
- export declare function authenticatePayment(client: BillingClient, operationId: string, psp: PspConfig): Promise<"authenticated" | "not_required">;
9
+ export declare function authenticateChange(client: BillingClient, subscriptionId: string, change: SubscriptionChange, psp: PspConfig): Promise<SubscriptionChange>;
@@ -2,6 +2,8 @@ import { CheckoutAppearance } from './appearance';
2
2
  import { Order } from './client/types';
3
3
  import * as React from "react";
4
4
  export interface BuyButtonProps {
5
+ /** The id of the merchant that sells the product. */
6
+ merchant: string;
5
7
  /** The product's catalog key. */
6
8
  product: string;
7
9
  /** The price's key within the product. */
@@ -30,8 +32,8 @@ export interface BuyButtonProps {
30
32
  appearance?: CheckoutAppearance;
31
33
  className?: string;
32
34
  }
33
- export declare function BuyButton(props: BuyButtonProps): React.JSX.Element;
34
- export type PurchaseProps = Omit<BuyButtonProps, "product" | "price"> & {
35
+ export declare function BuyButton({ merchant, product, price: priceKey, ...props }: BuyButtonProps): React.JSX.Element;
36
+ export type PurchaseProps = Omit<BuyButtonProps, "merchant" | "product" | "price"> & {
35
37
  /** The price to buy; the button waits while it is unknown. */
36
38
  priceId?: string;
37
39
  label: string;
@@ -1,5 +1,5 @@
1
1
  import { BillingError } from './errors';
2
- import { Account, CustomerLink, NetworkLinkChallenge, PublicConfig, PaymentAuthentication, CurrencyScales, ListPage, NewCard, NextAction, Cart, Order, OrderPaymentInput, Page, Payment, PaymentMethod, Product, Subscription, SubscriptionChange, SubscriptionChangePreview } from './types';
2
+ import { Account, CustomerConfig, CustomerLink, NetworkLinkChallenge, PublicConfig, CurrencyScales, ListPage, NewCard, NextAction, Cart, Order, OrderPaymentInput, Page, Payment, PaymentMethod, Product, Subscription, SubscriptionChange, SubscriptionChangePreview } from './types';
3
3
  export interface BillingClientOptions {
4
4
  /** OpenRails embedded mount. Default "/billing/v1". */
5
5
  baseUrl?: string;
@@ -8,7 +8,12 @@ export interface BillingClientOptions {
8
8
  * or combine the default with `getToken`.
9
9
  */
10
10
  fetch?: (input: string, init: RequestInit) => Promise<Response>;
11
- /** Bearer for each request; omit when `fetch` authenticates. */
11
+ /**
12
+ * The customer's access token, sent as the bearer of every request but
13
+ * the two public reads (`getConfig`, `listProducts`); omit when `fetch`
14
+ * authenticates. A `tokenSource` keeps it fresh: a call answered 401
15
+ * `credential_expired` is then retried once with a new token.
16
+ */
12
17
  getToken?: () => string | null | undefined | Promise<string | null | undefined>;
13
18
  /** Sent as Accept-Language. */
14
19
  language?: () => string | null | undefined;
@@ -19,10 +24,11 @@ export interface BillingClientOptions {
19
24
  currencies?: CurrencyScales;
20
25
  }
21
26
  /**
22
- * A public catalog lookup: the products named by `keys` (at most 100 product
23
- * keys), in one page.
27
+ * A public catalog lookup: the products of `merchant` (its id) named by
28
+ * `keys` (at most 100 product keys), in one page.
24
29
  */
25
30
  export interface ProductListOptions {
31
+ merchant: string;
26
32
  keys: string[];
27
33
  signal?: AbortSignal;
28
34
  }
@@ -99,6 +105,12 @@ export declare function createBillingClient(options?: BillingClientOptions): {
99
105
  setSubscriptionPaymentMethod(subscriptionId: string, paymentMethodId: string | null): Promise<Subscription>;
100
106
  /** What `changeSubscription` would charge now and at the next renewal. */
101
107
  previewSubscriptionChange(subscriptionId: string, change: SubscriptionChangeInput, signal?: AbortSignal): Promise<SubscriptionChangePreview>;
108
+ /**
109
+ * Reads the provider for a change awaiting the customer, after they
110
+ * completed its `next_action` (the issuer's authentication), and answers
111
+ * the change as it now stands.
112
+ */
113
+ confirmSubscriptionChange(subscriptionId: string): Promise<SubscriptionChange>;
102
114
  /**
103
115
  * Moves the subscription to another price of its tier group, to other
104
116
  * seats of a per-seat price, or both. An upgrade and more seats charge
@@ -106,7 +118,8 @@ export declare function createBillingClient(options?: BillingClientOptions): {
106
118
  * Reuse `idempotencyKey` until the change resolves (`processing`, a
107
119
  * `subscription_change_in_flight` refusal or a lost response) so the
108
120
  * stored result replays instead of charging twice. A Solana wallet next
109
- * action is completed with `signWalletAction`, then repeated with `signature`.
121
+ * action is completed with `signWalletAction`, then repeated with
122
+ * `signature`; an `authenticate` one with `authenticateChange`.
110
123
  */
111
124
  changeSubscription(subscriptionId: string, input: SubscriptionChangeInput & {
112
125
  idempotencyKey: string;
@@ -180,9 +193,6 @@ export declare function createBillingClient(options?: BillingClientOptions): {
180
193
  * script answered its `next_action`.
181
194
  */
182
195
  confirmPaymentMethod(paymentMethodId: string): Promise<PaymentMethod>;
183
- /** The provider challenge a pending payment operation is waiting on. */
184
- getPaymentAuthentication(operationId: string): Promise<PaymentAuthentication>;
185
- confirmPaymentAuthentication(operationId: string): Promise<void>;
186
196
  /** `pending` when the provider is still converging (202). */
187
197
  removePaymentMethod(paymentMethodId: string): Promise<"removed" | "pending">;
188
198
  /**
@@ -203,8 +213,9 @@ export declare function createBillingClient(options?: BillingClientOptions): {
203
213
  */
204
214
  getAccount(signal?: AbortSignal): Promise<Account>;
205
215
  /**
206
- * The products on sale named by `keys`, each with its current prices.
207
- * No keys name no products: nothing is fetched.
216
+ * The products `merchant` has on sale named by `keys`, each with its
217
+ * current prices: a public read, the same signed in or out. No keys
218
+ * name no products: nothing is fetched.
208
219
  */
209
220
  listProducts(opts: ProductListOptions): Promise<Page<Product>>;
210
221
  /**
@@ -221,11 +232,17 @@ export declare function createBillingClient(options?: BillingClientOptions): {
221
232
  idToken: string;
222
233
  }): Promise<CustomerLink>;
223
234
  /**
224
- * The deployment's public configuration: what the mount serves, the
225
- * currency registry and the merchant's payment setup. React components
226
- * share one copy through `useConfig()`.
235
+ * The deployment's configuration, a public read: what the mount serves,
236
+ * the currency registry and the captcha. React components share one
237
+ * copy through `useConfig()`.
227
238
  */
228
239
  getConfig(signal?: AbortSignal): Promise<PublicConfig>;
240
+ /**
241
+ * The customer's merchant: its payment setup (PSPs, Solana network and
242
+ * tokens) and how it presents itself. React components share one copy
243
+ * through `useCustomerConfig()`.
244
+ */
245
+ getCustomerConfig(signal?: AbortSignal): Promise<CustomerConfig>;
229
246
  /** Currency code (upper case) to native-unit decimals. */
230
247
  currencies: Readonly<Record<string, number>>;
231
248
  };
@@ -3,7 +3,10 @@ export type OpenRailsRoute = {
3
3
  path: string;
4
4
  group: string;
5
5
  auth: string;
6
- permission: string | null;
6
+ permission: {
7
+ merchant: string | null;
8
+ root: string;
9
+ } | null;
7
10
  sensitive: boolean;
8
11
  mountedWhen: string | null;
9
12
  request: string | null;
@@ -53,6 +56,20 @@ export declare const OPENRAILS_ROUTES: readonly [{
53
56
  };
54
57
  readonly errors: readonly [];
55
58
  readonly errorSets: readonly ["tier:public"];
59
+ }, {
60
+ readonly method: "GET";
61
+ readonly path: "/v1/me/config";
62
+ readonly group: "customer";
63
+ readonly auth: "customer";
64
+ readonly permission: null;
65
+ readonly sensitive: false;
66
+ readonly mountedWhen: null;
67
+ readonly request: null;
68
+ readonly responses: {
69
+ readonly 200: "CustomerConfig";
70
+ };
71
+ readonly errors: readonly ["service_unavailable"];
72
+ readonly errorSets: readonly ["tier:customer"];
56
73
  }, {
57
74
  readonly method: "POST";
58
75
  readonly path: "/v1/me/orders/preview";
@@ -234,7 +251,7 @@ export declare const OPENRAILS_ROUTES: readonly [{
234
251
  readonly responses: {
235
252
  readonly 200: "ListPage<Product>";
236
253
  };
237
- readonly errors: readonly ["invalid_query"];
254
+ readonly errors: readonly ["invalid_query", "resource_not_found"];
238
255
  readonly errorSets: readonly ["tier:public", "request"];
239
256
  }, {
240
257
  readonly method: "POST";
@@ -336,6 +353,21 @@ export declare const OPENRAILS_ROUTES: readonly [{
336
353
  };
337
354
  readonly errors: readonly ["authentication_required", "card_declined", "customer_action_required", "customer_email_required", "invalid_param", "payment_duplicate_refused", "payment_method_stale", "payment_provider_rejected", "price_change_currency_mismatch", "price_not_found", "product_not_found", "quantity_not_allowed", "rebill_terms_committed", "resource_conflict", "resource_not_found", "scheduled_change_exists", "service_unavailable", "solana_rpc_unavailable", "solana_transaction_refused", "subscription_change_already_scheduled", "subscription_change_credit_exceeds_price", "subscription_change_cycle_unknown", "subscription_change_idempotency_conflict", "subscription_change_idempotency_key_required", "subscription_change_in_flight", "subscription_change_period_unknown", "subscription_change_provider_conflict", "subscription_change_refused", "subscription_change_renewal_due", "subscription_change_requires_linked_plan", "subscription_change_target_inactive", "subscription_change_unsupported_on_rail", "subscription_not_active", "subscription_not_found"];
338
355
  readonly errorSets: readonly ["tier:customer", "request"];
356
+ }, {
357
+ readonly method: "POST";
358
+ readonly path: "/v1/me/subscriptions/{id}/confirm";
359
+ readonly group: "customer";
360
+ readonly auth: "customer";
361
+ readonly permission: null;
362
+ readonly sensitive: false;
363
+ readonly mountedWhen: null;
364
+ readonly request: null;
365
+ readonly responses: {
366
+ readonly 200: "SubscriptionChange";
367
+ readonly 202: "SubscriptionChange";
368
+ };
369
+ readonly errors: readonly ["authentication_required", "card_declined", "customer_session_required", "invalid_param", "payment_not_found", "payment_provider_rejected", "resource_conflict", "resource_not_found", "service_unavailable", "subscription_change_refused", "subscription_not_found"];
370
+ readonly errorSets: readonly ["tier:customer"];
339
371
  }, {
340
372
  readonly method: "POST";
341
373
  readonly path: "/v1/me/subscriptions/{id}/change/preview";
@@ -406,34 +438,6 @@ export declare const OPENRAILS_ROUTES: readonly [{
406
438
  };
407
439
  readonly errors: readonly ["currency_unsupported"];
408
440
  readonly errorSets: readonly ["tier:customer", "request"];
409
- }, {
410
- readonly method: "GET";
411
- readonly path: "/v1/me/payment-operations/{id}/authentication";
412
- readonly group: "customer";
413
- readonly auth: "customer";
414
- readonly permission: null;
415
- readonly sensitive: false;
416
- readonly mountedWhen: null;
417
- readonly request: null;
418
- readonly responses: {
419
- readonly 200: "StripeEngineAuthentication";
420
- };
421
- readonly errors: readonly ["card_attempts_blocked", "card_declined", "card_not_saved", "custodian_capture_unavailable", "customer_session_required", "idempotency_key_reused", "insufficient_funds", "invalid_param", "payment_method_required", "payment_method_stale", "payment_not_found", "payment_provider_rejected", "resource_access_denied", "resource_conflict", "resource_not_found", "service_unavailable"];
422
- readonly errorSets: readonly ["tier:customer"];
423
- }, {
424
- readonly method: "POST";
425
- readonly path: "/v1/me/payment-operations/{id}/authentication/confirm";
426
- readonly group: "customer";
427
- readonly auth: "customer";
428
- readonly permission: null;
429
- readonly sensitive: false;
430
- readonly mountedWhen: null;
431
- readonly request: null;
432
- readonly responses: {
433
- readonly 200: "PaymentOperation";
434
- };
435
- readonly errors: readonly ["card_attempts_blocked", "card_declined", "card_not_saved", "custodian_capture_unavailable", "customer_session_required", "idempotency_key_reused", "insufficient_funds", "invalid_param", "payment_method_required", "payment_method_stale", "payment_not_found", "payment_provider_rejected", "resource_access_denied", "resource_conflict", "resource_not_found", "service_unavailable"];
436
- readonly errorSets: readonly ["tier:customer"];
437
441
  }, {
438
442
  readonly method: "GET";
439
443
  readonly path: "/v1/me/payments";
@@ -120,6 +120,10 @@ export type CustomerChangeSubscriptionParams = {
120
120
  quantity?: number | null;
121
121
  signature?: string;
122
122
  };
123
+ export type CustomerConfig = {
124
+ payment: PaymentConfig;
125
+ merchant: PublicMerchant;
126
+ };
123
127
  export type CustomerEntitlement = {
124
128
  customer_id: string;
125
129
  entitlement: string;
@@ -442,15 +446,10 @@ export type PaymentMethodSubscription = {
442
446
  display_name: string;
443
447
  created_at: string;
444
448
  };
445
- export type PaymentOperation = {
446
- id: string;
447
- status: string;
448
- };
449
449
  export type PaymentRecovery = {
450
450
  last_failure_reason?: string;
451
451
  retryable: boolean;
452
452
  blocked_reason?: string;
453
- operation?: PaymentOperation;
454
453
  order_id?: string;
455
454
  };
456
455
  export type PendingAction = {
@@ -516,8 +515,6 @@ export type PublicConfig = {
516
515
  capabilities: Capabilities;
517
516
  currencies: CurrencyUnits[];
518
517
  rails: RailDefinition[];
519
- payment: PaymentConfig | null;
520
- merchant: PublicMerchant | null;
521
518
  captcha: CaptchaConfig | null;
522
519
  network: PublicNetwork | null;
523
520
  };
@@ -589,12 +586,6 @@ export type SolanaPaymentToken = {
589
586
  preferred: boolean;
590
587
  recurring_eligible: boolean;
591
588
  };
592
- export type StripeEngineAuthentication = {
593
- operation: PaymentOperation;
594
- payment_intent_id?: string;
595
- client_secret?: string;
596
- provider_payment_method_id?: string;
597
- };
598
589
  export type StripePortalSession = {
599
590
  url: string;
600
591
  };
@@ -659,7 +650,6 @@ export type SubscriptionChange = {
659
650
  amount_due_now: string;
660
651
  next_charge_amount: string;
661
652
  next_charge_date?: string;
662
- operation_id?: string;
663
653
  };
664
654
  export type SubscriptionChangePreview = {
665
655
  effective: string;
@@ -683,7 +673,7 @@ export type SubscriptionDunning = {
683
673
  };
684
674
  export type SubscriptionRetryNowResult = {
685
675
  subscription: Subscription;
686
- operation: PaymentOperation;
676
+ status: string;
687
677
  replayed: boolean;
688
678
  };
689
679
  export type UpdatePaymentMethodParams = {
@@ -1,4 +1,5 @@
1
1
  export { CANCEL_REASON_MAX, CANCEL_REASON_MIN, createBillingClient, isWalletAction, isWalletRejection, RETRY_BUDGET_MS, signWalletAction, WalletRejectedError, type BillingClient, type BillingClientOptions, type ListOptions, type OrderResult, type ProductListOptions, type SendSolanaTransaction, type SubscriptionChangeInput, } from './client';
2
+ export { redeemAssertion, tokenSource, type RedeemAssertionOptions, type TokenResponse, type TokenSource, type TokenSourceOptions, } from './token-source';
2
3
  export { BillingError, isBillingError, isServerError, readBillingError, toBillingError, type BillingErrorBody, } from './errors';
3
- export type { Account, Balance, CaptchaConfig, CardSummary, Cart, Currency, CurrencyScales, CustomerLink, PaymentConfig, PaymentFailure, PublicConfig, PublicMerchant, ListPage, NetworkLinkChallenge, NewCard, NextAction, Order, OrderLine, OrderLineRefusal, OrderNextAction, OrderPaymentInput, OrderPreviewLine, Page, Payment, PaymentAuthentication, PaymentMethod, PaymentOperation, PaymentRecovery, Price, Product, Subscription, SubscriptionDunning, SubscriptionProduct, SubscriptionStatus, SubscriptionChange, SubscriptionChangePreview, } from './types';
4
+ export type { Account, Balance, CaptchaConfig, CardSummary, Cart, Currency, CurrencyScales, CustomerConfig, CustomerLink, PaymentConfig, PaymentFailure, PublicConfig, PublicMerchant, ListPage, NetworkLinkChallenge, NewCard, NextAction, Order, OrderLine, OrderLineRefusal, OrderPaymentInput, OrderPreviewLine, Page, Payment, PaymentMethod, PaymentRecovery, Price, Product, Subscription, SubscriptionDunning, SubscriptionProduct, SubscriptionStatus, SubscriptionChange, SubscriptionChangePreview, } from './types';
4
5
  export { amountToDecimal, decimalToAmount, formatAmount, type Amount, } from '../lib/money';
@@ -0,0 +1,60 @@
1
+ /** A token endpoint's answer (RFC 6749 §5.1). */
2
+ export interface TokenResponse {
3
+ access_token: string;
4
+ /** Seconds until the token expires. */
5
+ expires_in: number;
6
+ }
7
+ export interface TokenSourceOptions {
8
+ /**
9
+ * Asks the host for the customer's access token: one bound to the
10
+ * merchant, from the host's own backend or `redeemAssertion`.
11
+ */
12
+ fetchToken: () => Promise<TokenResponse>;
13
+ /** Seconds before its expiry at which a token is replaced. Default 60. */
14
+ skew?: number;
15
+ }
16
+ /**
17
+ * A `getToken` for `createBillingClient`: the current token, fetched when
18
+ * there is none or the cached one is about to expire.
19
+ */
20
+ export interface TokenSource {
21
+ (): Promise<string>;
22
+ /**
23
+ * Drops the cached token, so the next call fetches a new one. With
24
+ * `token`, only when that is the one cached.
25
+ */
26
+ invalidate(token?: string): void;
27
+ }
28
+ /**
29
+ * Keeps the customer's access token for a billing client: it caches the
30
+ * token until `skew` seconds before it expires and fetches the next one with
31
+ * a single request, however many calls are waiting. A failed fetch is not
32
+ * cached. Tokens stay in memory, never in browser storage, so a reload
33
+ * fetches again. The client retries a call answered 401 `credential_expired`
34
+ * once with a new token.
35
+ */
36
+ export declare function tokenSource({ fetchToken, skew, }: TokenSourceOptions): TokenSource;
37
+ /** Whether `getToken` is a `tokenSource`, whose token the client can replace. */
38
+ export declare const isTokenSource: (getToken: unknown) => getToken is TokenSource;
39
+ export interface RedeemAssertionOptions {
40
+ /** The authorization server's token endpoint, such as `https://billing.example/oauth2/token`. */
41
+ tokenEndpoint: string;
42
+ /**
43
+ * The assertion the merchant's backend signed for this customer (RFC
44
+ * 7523): single use and minutes long, so redeem each one once.
45
+ */
46
+ assertion: string;
47
+ /**
48
+ * Transport. Pass a fetch that adds a DPoP proof (auth-ui's `dpopFetch`)
49
+ * to bind the token to the browser's key.
50
+ */
51
+ fetch?: (input: string, init: RequestInit) => Promise<Response>;
52
+ }
53
+ /**
54
+ * Redeems a customer assertion for an access token to OpenRails
55
+ * (`scope=openrails:self`): a `fetchToken` for `tokenSource`, after the
56
+ * host's backend signed the assertion.
57
+ */
58
+ export declare function redeemAssertion(options: RedeemAssertionOptions): Promise<TokenResponse & {
59
+ token_type: string;
60
+ }>;