@openrails/billing-ui 0.236.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +87 -9
  2. package/README.md +78 -83
  3. package/dist/account/account-billing.d.ts +1 -1
  4. package/dist/account/payment-methods-panel.d.ts +2 -2
  5. package/dist/authenticate.d.ts +5 -4
  6. package/dist/buy-button.d.ts +17 -10
  7. package/dist/client/client.d.ts +52 -23
  8. package/dist/client/generated/error-codes.d.ts +1 -1
  9. package/dist/client/generated/routes.d.ts +24 -218
  10. package/dist/client/generated/wire.d.ts +39 -200
  11. package/dist/client/index.d.ts +2 -1
  12. package/dist/client/token-source.d.ts +60 -0
  13. package/dist/client/types.d.ts +108 -199
  14. package/dist/client-Bi_lP_M0.js +932 -0
  15. package/dist/client-Bi_lP_M0.js.map +1 -0
  16. package/dist/client.js +2 -2
  17. package/dist/components/eyebrow.d.ts +5 -0
  18. package/dist/embedded-checkout.d.ts +22 -0
  19. package/dist/{hooks-2d-XjpuI.js → hooks-Bk80rNv8.js} +99 -90
  20. package/dist/hooks-Bk80rNv8.js.map +1 -0
  21. package/dist/index.d.ts +5 -8
  22. package/dist/index.js +1149 -2163
  23. package/dist/index.js.map +1 -1
  24. package/dist/modal.d.ts +3 -9
  25. package/dist/offers.d.ts +10 -9
  26. package/dist/psp.d.ts +0 -8
  27. package/dist/react/config.d.ts +17 -12
  28. package/dist/react/context.d.ts +3 -1
  29. package/dist/react/hooks.d.ts +5 -5
  30. package/dist/react/index.d.ts +1 -1
  31. package/dist/react.js +2 -2
  32. package/dist/styles.css +1 -1
  33. package/dist/tokenized-card-form.d.ts +5 -1
  34. package/dist/types.d.ts +12 -250
  35. package/docs/payment-form-contract.md +31 -74
  36. package/package.json +1 -2
  37. package/dist/checkout-frame.d.ts +0 -17
  38. package/dist/checkout-page.d.ts +0 -21
  39. package/dist/checkout.d.ts +0 -22
  40. package/dist/client/checkout.d.ts +0 -28
  41. package/dist/client-BuvolBPT.js +0 -1092
  42. package/dist/client-BuvolBPT.js.map +0 -1
  43. package/dist/components/ccbill-fields.d.ts +0 -8
  44. package/dist/components/methods.d.ts +0 -13
  45. package/dist/components/summary.d.ts +0 -12
  46. package/dist/fixtures.d.ts +0 -9
  47. package/dist/frame.d.ts +0 -47
  48. package/dist/hooks-2d-XjpuI.js.map +0 -1
  49. package/dist/lib/rail-meta.d.ts +0 -15
  50. package/dist/lib/redirect.d.ts +0 -2
  51. package/dist/source.d.ts +0 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
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
+
38
+ ## Running charges
39
+
40
+ - `Balance` gains `accrued_amount`, `accrual_rate_per_hour`, `as_of` and
41
+ `depletes_at`: what the customer's running charges have charged and not yet
42
+ posted, the rate they draw the balance at, and when it runs out.
43
+ `available_amount` is net of the accrual.
44
+
45
+ ## Orders replace checkout sessions
46
+
47
+ - Breaking: `<BuyButton>` and `<Offers>` create the customer's order and pay
48
+ it in `<OrderCheckout>`; `onPaid` receives the completed `Order`.
49
+ `returnUrl` replaces `successUrl`, and `autoRenew` is gone.
50
+ - Breaking: `<CheckoutPage>`, `<CheckoutFrame>` and the shared payment page
51
+ are removed with checkout sessions; so are `CheckoutLayout`,
52
+ `CheckoutFrameTheme`, `CheckoutSession`, `CheckoutSessionStatus`,
53
+ `CheckoutPlan`, `CheckoutLineItem`, `CheckoutPhase`, `PaymentOption`,
54
+ `PayRequest`, `PayResult`, their schemas, `amountSchema`,
55
+ `unitDecimalsSchema`, `isCardRail`, `savedMethodsFor` and the client's
56
+ `CheckoutSessionLink`. `NextAction` and `PaymentFailure` are the order's,
57
+ from `@openrails/billing-ui/client`.
58
+ - A page that paid a server-minted session pays the server's order instead:
59
+ the Go `Client.CreateOrder` creates it, and `<OrderCheckout orderId>` pays
60
+ it, a subscription line included.
61
+ - `SavedPaymentMethod` names its `psp_id` (was `option_id`).
62
+
63
+ ## Usage orders
64
+
65
+ - Breaking: `listInvoices`, `getInvoice` and `Invoice` are removed. Usage
66
+ billed in arrears is an order (`origin: usage`): `listOrders({ origin:
67
+ "usage" })` lists them, and `<OrderCheckout>` pays one with a saved card.
68
+ - `Order` gains `usage` (its period, `due_at`, `amount_paid`, `amount_due`,
69
+ collection and `delinquent`) and `uncollectible_at`; `status` may be
70
+ `uncollectible`. A usage line names its `source`; its `price_id` and
71
+ `product_id` are null.
72
+ - `Order.checkout.expires_at` is read again.
73
+
74
+ ## Embedded checkout
75
+
76
+ - `<EmbeddedCheckout url onComplete?>` frames a merchant order's embedded
77
+ checkout (`checkout.ui_mode: embedded`): it follows the page's height and
78
+ hears its completion only from its own frame on the checkout host's origin;
79
+ without `onComplete` it goes to the checkout's `success_url`.
80
+ - `Order.checkout.ui_mode`; `PublicMerchant.embed_origins`;
81
+ `PublicConfig.network` (the checkout page's sign-in to the customer network).
82
+ - `createNetworkLinkChallenge()` and `createNetworkLink({challengeId, idToken})`
83
+ link the customer to a customer-network account.
84
+
85
+ ## No Solana token list
86
+
87
+ - `listSolanaTokens` and `SolanaToken` are gone with `GET /v1/solana/tokens`:
88
+ the accepted tokens are `getConfig()`'s `payment.solana.tokens`.
89
+
3
90
  ## Apple Pay and Google Pay
4
91
 
5
92
  - `<OrderCheckout wallets>` offers Apple Pay and Google Pay through a Stripe
@@ -183,15 +270,6 @@
183
270
  product on sale with its current prices. Filter by currency, product or
184
271
  renewal in the browser.
185
272
 
186
- ## Paying on a customer surface
187
-
188
- - `client.checkoutSource(id, { customerBase })` reads and pays a checkout
189
- session as its signed-in customer at a customer surface's
190
- `/checkout-sessions/{id}` (for example a host's
191
- `/api/v1/merchants/acme/billing/me`), so its saved cards pay. Without
192
- `customerBase` the session id alone pays at `/checkout-sessions/{id}` as
193
- before. `<CheckoutPage customerBase>` passes it through.
194
-
195
273
  ## 1.0.0
196
274
 
197
275
  Ships with OpenRails v1.0.0; use the two at the same version. Everything a host
package/README.md CHANGED
@@ -10,15 +10,13 @@ billing (subscriptions, saved cards, payment history).
10
10
  | `@openrails/billing-ui` | `BillingProvider`, styled checkout and account components, i18n |
11
11
  | `@openrails/billing-ui/locales/*` | `en de es ja ko zh` message bundles |
12
12
 
13
- The checkout owns the browser payment flow, behind `<BuyButton>` and
14
- `<Offers>`. OpenRails holds the checkout session: the signed-in customer mints
15
- one for a price, and its `ocs_` id alone reads and pays it. Card data is tokenized in NMI-hosted Collect.js iframes and
16
- never enters the host application, unless the PSP takes cards on OpenRails
17
- itself (`card_entry: server`, driver `card`).
13
+ The checkout owns the browser payment flow, behind `<BuyButton>`, `<Offers>`
14
+ and `<OrderCheckout>`: the signed-in customer's order, read and paid with
15
+ their own credential. Card data is tokenized in the PSP's own fields
16
+ (Collect.js, Stripe Elements) and never enters the host application.
18
17
 
19
- See [Payment form contract](docs/payment-form-contract.md) for the exact-money
20
- session document, billing fields, browser-autofill behavior, and the checkout
21
- request schema.
18
+ See [Payment form contract](docs/payment-form-contract.md) for exact money,
19
+ billing fields, browser-autofill behavior and the payment request.
22
20
 
23
21
  The package lives in the OpenRails repository and shares its version: each
24
22
  OpenRails release `vX.Y.Z` publishes `@openrails/billing-ui@X.Y.Z` to npm, with
@@ -44,20 +42,43 @@ const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFe
44
42
  </BillingProvider>
45
43
  ```
46
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
+
47
65
  `<BuyButton>` buys one price, `<Offers>` the products your server names by
48
66
  key (a course, its bundle and the membership that also unlocks it; the Go
49
67
  `Client.ListOffers` finds the products granting an entitlement): a
50
68
  `BuyButton` per price ("$4.99", "Rent for 3 days, $1.99",
51
- "$10.00 every 30 days"). The purchase is theirs from start to payment, so the
52
- app never handles a checkout session: today a button opens one in a checkout
53
- dialog; when orders take a new card it will create an order instead,
54
- and apps won't change. Signing in is the host's: with `signedIn={false}`, or
55
- when OpenRails answers 401, they call `onSignInRequired` instead. `onPaid`
56
- follows a successful payment only; it is a hint, so confirm access from your
57
- own API (your gate) before granting anything.
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
72
+ button creates the customer's order and pays it in a dialog
73
+ (`<OrderCheckout>`); a second click before it is paid opens the same order.
74
+ Signing in is the host's: with `signedIn={false}`, or when OpenRails answers
75
+ 401, they call `onSignInRequired` instead. `onPaid` receives the completed
76
+ order; it is a hint, so confirm access from your own API (your gate) before
77
+ granting anything.
58
78
 
59
79
  ```tsx
60
80
  <BuyButton
81
+ merchant={merchantId} // your merchant's id, from your server
61
82
  product="course-101"
62
83
  price="rent" // labelled from the catalog unless you pass label
63
84
  signedIn={signedIn}
@@ -66,6 +87,7 @@ own API (your gate) before granting anything.
66
87
  />
67
88
 
68
89
  <Offers
90
+ merchant={merchantId}
69
91
  keys={["course-101", "course-bundle", "channel-membership"]} // from your server, or products={...} from its Client.ListOffers
70
92
  signedIn={signedIn}
71
93
  onSignInRequired={() => openSignIn()}
@@ -73,9 +95,8 @@ own API (your gate) before granting anything.
73
95
  />
74
96
  ```
75
97
 
76
- `successUrl` returns the buyer from a redirect step (Stripe's hosted page); it
77
- must be on one of your app's return origins. `autoRenew={false}` buys a
78
- recurring price's first term only.
98
+ `returnUrl` returns the buyer from a redirect step (Stripe's hosted page); it
99
+ must be on one of your app's return origins.
79
100
 
80
101
  For prepaid credits, a fixed pack is another product/price. A
81
102
  `customer_amount` price takes the buyer's `amount`: collect it on your product
@@ -90,7 +111,7 @@ const amount = decimalToAmount(enteredAmount, currency.decimals)
90
111
  const bounds = depositPrice.customer_amount
91
112
  const valid = amount && bounds && BigInt(amount) >= BigInt(bounds.min_amount) && BigInt(amount) <= BigInt(bounds.max_amount)
92
113
 
93
- <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} />
94
115
  ```
95
116
 
96
117
  OpenRails validates the bounds and whole currency minor units before accepting
@@ -98,44 +119,22 @@ payment. Fixed prices reject `amount`; their pack benefit can be greater than
98
119
  the purchase price for a bulk discount. Customer-selected deposits currently
99
120
  use NMI or Stripe.
100
121
 
101
- A rail's own step is the pay result's `next_action`: `redirect_to_url` (an
102
- https page, opened in the top window) or `solana_pay` (a `solana:` link shown
103
- as a QR code). A card challenge is its `operation`, authenticated in the page.
122
+ A rail's own step is the order's `payment.next_action`: `redirect_to_url` (an
123
+ https page, opened in the top window), `solana_pay` (a `solana:` link shown
124
+ as a QR code) or `authenticate` (the bank's challenge, run in the page).
104
125
 
105
- ### A shared payment page
126
+ ### An order your server creates
106
127
 
107
- Several sites selling for one merchant can share one payment page
108
- (`Config.Checkout.PageURL`). A server mints the session with the Go
109
- `Client.CreateCheckoutSession` and hands the browser its `url`, which the site
110
- frames:
128
+ A server that decides what its customer buys creates the order with the Go
129
+ `Client.CreateOrder` (`POST /v1/app/orders`) and hands its id to the
130
+ customer's browser, which pays it signed in:
111
131
 
112
132
  ```tsx
113
- <CheckoutFrame url={session.url} theme="dark" onComplete={() => refetchAccess()} />
114
- ```
115
-
116
- Stripe Elements needs the buyer's own client, so the shared page offers token
117
- rails only.
118
-
119
- The payment host serves `<CheckoutPage>` from one HTML entry at `PageURL`,
120
- behind its adapter's `CheckoutFramePolicy` (only the sites in
121
- `Config.Checkout.EmbedOrigins` may frame it). No customer signs in there, so
122
- its `BillingProvider` has no client:
123
-
124
- ```tsx
125
- import { BillingProvider, CheckoutPage } from "@openrails/billing-ui"
126
-
127
- createRoot(root).render(
128
- <BillingProvider>
129
- <CheckoutPage appearance={{ variables: brand }} />
130
- </BillingProvider>
131
- )
133
+ <OrderCheckout orderId={orderId} returnUrl={returnUrl} onSettled={(order) => refetchAccess(order)} />
132
134
  ```
133
135
 
134
- The page reads the session id from its URL fragment and talks to its frame
135
- with `postMessage`, origin-checked both ways: page to app `ready`,
136
- `resize {height}`, `complete {status}` and `redirect {url}` (a nested frame
137
- cannot navigate the top window, so the app does); app to page
138
- `init {theme}`. The page messages only the app origin recorded on its session.
136
+ `onSettled` follows a complete order, or one processing on an asynchronous
137
+ rail. A subscription line names its subscription once paid.
139
138
 
140
139
  ## Account billing
141
140
 
@@ -165,26 +164,27 @@ const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFe
165
164
  </BillingProvider>
166
165
  ```
167
166
 
168
- The panels read OpenRails' public configuration (`GET /config`) themselves,
169
- once per `BillingProvider`: "Add card" offers each PSP of its `payment` that
170
- saves a card in the page, and says so while the configuration fails or its
171
- card PSPs are temporarily unavailable; amounts use its currency registry over
172
- the pinned copy. `useConfig()` and `useCurrencyScales()` read the same copy
173
- 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.
174
174
 
175
175
  ## Catalog and plan changes
176
176
 
177
177
  The client also reads the public catalog and changes a subscription's plan.
178
- The catalog and Solana reads are public routes, served only when the host
179
- mounts 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.
180
180
 
181
181
  | Call | Route |
182
182
  | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
183
- | `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` |
184
- | `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` |
185
186
  | `previewSubscriptionChange(id, { priceId?, quantity? })` | `POST /me/subscriptions/{id}/change/preview` |
186
187
  | `changeSubscription(id, { priceId?, quantity?, idempotencyKey, signature? })` | `POST /me/subscriptions/{id}/change` |
187
- | `listSolanaTokens({ priceId, wallet })` | `GET /solana/tokens` |
188
188
 
189
189
  ```ts
190
190
  const preview = await billing.previewSubscriptionChange(sub.id, { quantity: 5 }) // amount_due_now, effective
@@ -199,10 +199,11 @@ const change = await billing.changeSubscription(sub.id, {
199
199
  charge now; fewer seats and a downgrade apply at the next renewal.
200
200
  - `changeSubscription` is a money write. Mint one `idempotencyKey` per attempt and
201
201
  reuse it until the change resolves (`status: "processing"`, a
202
- `subscription_change_in_flight` refusal naming `metadata.operation_id`, a 5xx or a
203
- lost response), so OpenRails replays the stored result instead of charging
204
- twice. `requires_action` carries `operation_id` for `authenticatePayment`;
205
- 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`).
206
207
  - A rail that needs the customer's own step answers `requires_action` with a
207
208
  `next_action`. Solana's is `solana_sign_transactions`:
208
209
  `signWalletAction(change.next_action, send)` signs and sends its
@@ -214,33 +215,27 @@ const change = await billing.changeSubscription(sub.id, {
214
215
 
215
216
  ## Provider-neutral flows
216
217
 
217
- 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
218
219
  browser flow from each PSP's `flow` and public `config`
219
220
  (Collect.js, native card inputs, Stripe Elements, redirect, wallet). Hosts never branch on a
220
221
  provider:
221
222
 
222
- - A session's `options` are exactly the armed PSPs whose rail can make this
223
- sale (Solana when it is configured, never CCBill),
224
- each with its `driver` and `public_config`; `saved_methods` are the buyer's
225
- cards on them, newest first (paying sends the chosen card's id explicitly).
226
- Card rails (`collect_js`, `card`, `stripe_elements`) render one panel: saved cards,
227
- an inline new card and one Pay/Subscribe button, which is the customer's
228
- confirmation of the displayed terms. No provider chooser with one rail.
229
- - Inside a `BillingProvider` with a client, a new card is always saved to the
230
- account first and the session is paid with `payment_method_id` on the
231
- customer surface; `requires_action` results
232
- carrying an `operation` run 3-D Secure in the page. A `failed` result stays
233
- on the panel with `failure.message` next to its `field`, so the buyer can
234
- pick another card; the host gives each new attempt a new idempotency key.
223
+ - An order's `payment_options` are exactly the armed PSPs whose rail can make
224
+ this sale (Solana for one-time lines when it is configured, never CCBill),
225
+ each with what it `accepts`. `OrderCheckout` renders one panel: the
226
+ customer's saved cards on them, newest first, an inline new card and one Pay
227
+ button. No provider chooser with one rail.
228
+ - A new card is charged and saved in one pay; an `authenticate` next action
229
+ runs 3-D Secure in the page, then `confirm`. A decline stays on the panel
230
+ with its message, so the buyer can pick another card under a new
231
+ idempotency key.
235
232
  - Only PSPs with `checkout !== false` (`checkoutPsps`) take new cards; others
236
233
  stay listed so existing cards and subscriptions keep working.
237
234
  - `<SavePaymentMethod psp={psp} onSaved={(id) => ...} />` saves a card in the
238
235
  page (needs `BillingProvider`); `canSavePaymentMethod(psp)` filters PSPs
239
236
  that support it. After an off-page verification the provider returns to
240
237
  `returnURL(setupId)`; confirm with `client.confirmCardSetup(id)`.
241
- - `authenticatePayment(client, operationId, psp)` completes a pending
242
- payment's 3-D Secure challenge when `canAuthenticatePayment(psp)`.
243
- - `defaultCountry` (CheckoutPage, SavePaymentMethod, AccountBilling)
238
+ - `defaultCountry` (SavePaymentMethod, AccountBilling)
244
239
  preselects the billing country, else the browser locale's region.
245
240
 
246
241
  - Panels: `SubscriptionsPanel` (cancel, resume, change card),
@@ -4,7 +4,7 @@ import { SubscriptionsPanelProps } from './subscriptions-panel';
4
4
  export interface AccountBillingProps {
5
5
  /**
6
6
  * Offers to make a card the default for this currency: it pays the
7
- * currency's invoices and every subscription in it without its own card.
7
+ * currency's usage orders and every subscription in it without its own card.
8
8
  */
9
9
  defaultCurrency?: string;
10
10
  /** Billing country to preselect when adding a card. */
@@ -3,7 +3,7 @@ import * as React from "react";
3
3
  export interface PaymentMethodsPanelProps {
4
4
  /**
5
5
  * Offers to make a card the default for this currency: it pays the
6
- * currency's invoices and every subscription in it without its own card.
6
+ * currency's usage orders and every subscription in it without its own card.
7
7
  */
8
8
  defaultCurrency?: string;
9
9
  /** Billing country to preselect when adding a card. */
@@ -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>;
@@ -1,7 +1,9 @@
1
1
  import { CheckoutAppearance } from './appearance';
2
- import { PayResult } from './types';
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. */
@@ -10,18 +12,16 @@ export interface BuyButtonProps {
10
12
  label?: string;
11
13
  /**
12
14
  * A customer-chosen deposit, in the price currency's native units, for a
13
- * price with `customer_amount`; the checkout charges exactly this.
15
+ * price with `customer_amount`; the order charges exactly this.
14
16
  */
15
17
  amount?: string;
16
- /** false buys a recurring price's first term only; default: renews. */
17
- autoRenew?: boolean;
18
- /** Where a redirect rail (Stripe's hosted page) returns the buyer: one of the app's return origins. */
19
- successUrl?: string;
18
+ /** Where a redirect PSP (Stripe's hosted page) returns the buyer: one of the app's return origins. */
19
+ returnUrl?: string;
20
20
  /**
21
- * The purchase succeeded. A hint: the host's own gate decides access, so
22
- * send the buyer to the content it guards.
21
+ * The order is paid. A hint: the host's own gate decides access, so send
22
+ * the buyer to the content it guards.
23
23
  */
24
- onPaid: (result: PayResult) => void;
24
+ onPaid: (order: Order) => void;
25
25
  /**
26
26
  * Buying needs a signed-in customer: called instead of the checkout when
27
27
  * `signedIn` is false or OpenRails answers 401.
@@ -32,4 +32,11 @@ export interface BuyButtonProps {
32
32
  appearance?: CheckoutAppearance;
33
33
  className?: string;
34
34
  }
35
- export declare function BuyButton(props: BuyButtonProps): React.JSX.Element;
35
+ export declare function BuyButton({ merchant, product, price: priceKey, ...props }: BuyButtonProps): React.JSX.Element;
36
+ export type PurchaseProps = Omit<BuyButtonProps, "merchant" | "product" | "price"> & {
37
+ /** The price to buy; the button waits while it is unknown. */
38
+ priceId?: string;
39
+ label: string;
40
+ };
41
+ /** One price's button and its order. Requires `BillingProvider`. */
42
+ export declare function Purchase({ priceId, label, amount, returnUrl, onPaid, onSignInRequired, signedIn, appearance, className, }: PurchaseProps): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { BillingError } from './errors';
2
- import { Account, PublicConfig, PaymentAuthentication, CurrencyScales, Invoice, ListPage, NewCard, NextAction, Cart, Order, OrderPaymentInput, Page, Payment, PaymentMethod, Product, SolanaToken, 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;
@@ -149,6 +162,16 @@ export declare function createBillingClient(options?: BillingClientOptions): {
149
162
  reusable?: boolean;
150
163
  idempotencyKey: string;
151
164
  }): Promise<OrderResult>;
165
+ /**
166
+ * The customer's orders, newest first: `origin: "usage"` their usage
167
+ * orders, `overdue` those still owed past their due date.
168
+ */
169
+ listOrders(opts?: CursorOptions & {
170
+ origin?: string;
171
+ status?: string;
172
+ subscriptionId?: string;
173
+ overdue?: boolean;
174
+ }): Promise<ListPage<Order>>;
152
175
  /** One of the customer's orders: the resumable handle of a purchase. */
153
176
  getOrder(orderId: string, signal?: AbortSignal): Promise<Order>;
154
177
  /**
@@ -170,14 +193,11 @@ export declare function createBillingClient(options?: BillingClientOptions): {
170
193
  * script answered its `next_action`.
171
194
  */
172
195
  confirmPaymentMethod(paymentMethodId: string): Promise<PaymentMethod>;
173
- /** The provider challenge a pending payment operation is waiting on. */
174
- getPaymentAuthentication(operationId: string): Promise<PaymentAuthentication>;
175
- confirmPaymentAuthentication(operationId: string): Promise<void>;
176
196
  /** `pending` when the provider is still converging (202). */
177
197
  removePaymentMethod(paymentMethodId: string): Promise<"removed" | "pending">;
178
198
  /**
179
199
  * Makes the card the default for one currency: it pays the currency's
180
- * invoices and every subscription in it without its own card.
200
+ * usage orders and every subscription in it without its own card.
181
201
  */
182
202
  setDefaultPaymentMethod(input: {
183
203
  currency: string;
@@ -187,33 +207,42 @@ export declare function createBillingClient(options?: BillingClientOptions): {
187
207
  listPayments(opts?: CursorOptions & {
188
208
  rail?: string;
189
209
  }): Promise<ListPage<Payment>>;
190
- listInvoices(opts?: CursorOptions): Promise<ListPage<Invoice>>;
191
210
  /**
192
211
  * The customer's summary: balance and amount owed per currency, the card
193
212
  * that pays each currency, and unread notices.
194
213
  */
195
214
  getAccount(signal?: AbortSignal): Promise<Account>;
196
- getInvoice(invoiceId: string, signal?: AbortSignal): Promise<Invoice>;
197
215
  /**
198
- * The products on sale named by `keys`, each with its current prices.
199
- * 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.
200
219
  */
201
220
  listProducts(opts: ProductListOptions): Promise<Page<Product>>;
202
221
  /**
203
- * The deployment's public configuration: what the mount serves, the
204
- * currency registry and the merchant's payment setup. React components
205
- * share one copy through `useConfig()`.
222
+ * A challenge to link the customer to an openrails.dev account: its
223
+ * `nonce` goes in the network sign-in's authorization request.
224
+ */
225
+ createNetworkLinkChallenge(): Promise<NetworkLinkChallenge>;
226
+ /**
227
+ * Links the customer to the openrails.dev account whose fresh sign-in
228
+ * answered the challenge (its ID token).
229
+ */
230
+ createNetworkLink(params: {
231
+ challengeId: string;
232
+ idToken: string;
233
+ }): Promise<CustomerLink>;
234
+ /**
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()`.
206
238
  */
207
239
  getConfig(signal?: AbortSignal): Promise<PublicConfig>;
208
240
  /**
209
- * Accepted tokens with live prices. `priceId` adds each token's `quote`;
210
- * `wallet` adds its `balance`.
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()`.
211
244
  */
212
- listSolanaTokens(opts?: {
213
- priceId?: string;
214
- wallet?: string;
215
- signal?: AbortSignal;
216
- }): Promise<SolanaToken[]>;
245
+ getCustomerConfig(signal?: AbortSignal): Promise<CustomerConfig>;
217
246
  /** Currency code (upper case) to native-unit decimals. */
218
247
  currencies: Readonly<Record<string, number>>;
219
248
  };