@openrails/billing-ui 0.235.0 → 0.237.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 (74) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +103 -103
  3. package/dist/account/account-billing.d.ts +2 -4
  4. package/dist/account/payment-methods-panel.d.ts +2 -4
  5. package/dist/buy-button.d.ts +40 -0
  6. package/dist/cart.d.ts +18 -0
  7. package/dist/client/client.d.ts +95 -59
  8. package/dist/client/errors.d.ts +4 -0
  9. package/dist/client/generated/error-codes.d.ts +1 -1
  10. package/dist/client/generated/routes.d.ts +103 -272
  11. package/dist/client/generated/wire.d.ts +117 -246
  12. package/dist/client/index.d.ts +2 -2
  13. package/dist/client/types.d.ts +286 -142
  14. package/dist/client-D9G1Tgox.js +888 -0
  15. package/dist/client-D9G1Tgox.js.map +1 -0
  16. package/dist/client.js +2 -2
  17. package/dist/components/eyebrow.d.ts +5 -0
  18. package/dist/components/stripe-card.d.ts +8 -2
  19. package/dist/components/stripe-wallets.d.ts +12 -0
  20. package/dist/components/ui/badge.d.ts +1 -1
  21. package/dist/components/ui/button.d.ts +1 -1
  22. package/dist/embedded-checkout.d.ts +22 -0
  23. package/dist/hooks-DYepYVMX.js +526 -0
  24. package/dist/hooks-DYepYVMX.js.map +1 -0
  25. package/dist/i18n/context.d.ts +1 -1
  26. package/dist/index.d.ts +9 -11
  27. package/dist/index.js +1470 -2178
  28. package/dist/index.js.map +1 -1
  29. package/dist/lib/money.d.ts +1 -0
  30. package/dist/lib/offer-label.d.ts +4 -0
  31. package/dist/locales/de.js +20 -0
  32. package/dist/locales/de.js.map +1 -1
  33. package/dist/locales/en.d.ts +20 -0
  34. package/dist/locales/en.js +20 -0
  35. package/dist/locales/en.js.map +1 -1
  36. package/dist/locales/es.js +20 -0
  37. package/dist/locales/es.js.map +1 -1
  38. package/dist/locales/ja.js +11 -0
  39. package/dist/locales/ja.js.map +1 -1
  40. package/dist/locales/ko.js +11 -0
  41. package/dist/locales/ko.js.map +1 -1
  42. package/dist/locales/zh.js +11 -0
  43. package/dist/locales/zh.js.map +1 -1
  44. package/dist/modal.d.ts +3 -9
  45. package/dist/offers.d.ts +32 -0
  46. package/dist/order-checkout.d.ts +30 -0
  47. package/dist/psp.d.ts +0 -8
  48. package/dist/react/hooks.d.ts +8 -7
  49. package/dist/react/index.d.ts +1 -1
  50. package/dist/react/provider.d.ts +24 -2
  51. package/dist/react.js +2 -33
  52. package/dist/save-payment-method.d.ts +0 -6
  53. package/dist/styles.css +1 -1
  54. package/dist/tokenized-card-form.d.ts +5 -1
  55. package/dist/types.d.ts +12 -250
  56. package/docs/payment-form-contract.md +31 -74
  57. package/package.json +1 -2
  58. package/dist/checkout-frame.d.ts +0 -17
  59. package/dist/checkout-page.d.ts +0 -21
  60. package/dist/checkout.d.ts +0 -24
  61. package/dist/client-BpzCZes3.js +0 -931
  62. package/dist/client-BpzCZes3.js.map +0 -1
  63. package/dist/components/ccbill-fields.d.ts +0 -8
  64. package/dist/components/methods.d.ts +0 -13
  65. package/dist/components/summary.d.ts +0 -12
  66. package/dist/config-qLcHvK0Q.js +0 -341
  67. package/dist/config-qLcHvK0Q.js.map +0 -1
  68. package/dist/fixtures.d.ts +0 -9
  69. package/dist/frame.d.ts +0 -47
  70. package/dist/lib/rail-meta.d.ts +0 -15
  71. package/dist/lib/redirect.d.ts +0 -2
  72. package/dist/provider.d.ts +0 -18
  73. package/dist/react.js.map +0 -1
  74. package/dist/source.d.ts +0 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,160 @@
1
1
  # Changelog
2
2
 
3
+ ## Running charges
4
+
5
+ - `Balance` gains `accrued_amount`, `accrual_rate_per_hour`, `as_of` and
6
+ `depletes_at`: what the customer's running charges have charged and not yet
7
+ posted, the rate they draw the balance at, and when it runs out.
8
+ `available_amount` is net of the accrual.
9
+
10
+ ## Orders replace checkout sessions
11
+
12
+ - Breaking: `<BuyButton>` and `<Offers>` create the customer's order and pay
13
+ it in `<OrderCheckout>`; `onPaid` receives the completed `Order`.
14
+ `returnUrl` replaces `successUrl`, and `autoRenew` is gone.
15
+ - Breaking: `<CheckoutPage>`, `<CheckoutFrame>` and the shared payment page
16
+ are removed with checkout sessions; so are `CheckoutLayout`,
17
+ `CheckoutFrameTheme`, `CheckoutSession`, `CheckoutSessionStatus`,
18
+ `CheckoutPlan`, `CheckoutLineItem`, `CheckoutPhase`, `PaymentOption`,
19
+ `PayRequest`, `PayResult`, their schemas, `amountSchema`,
20
+ `unitDecimalsSchema`, `isCardRail`, `savedMethodsFor` and the client's
21
+ `CheckoutSessionLink`. `NextAction` and `PaymentFailure` are the order's,
22
+ from `@openrails/billing-ui/client`.
23
+ - A page that paid a server-minted session pays the server's order instead:
24
+ the Go `Client.CreateOrder` creates it, and `<OrderCheckout orderId>` pays
25
+ it, a subscription line included.
26
+ - `SavedPaymentMethod` names its `psp_id` (was `option_id`).
27
+
28
+ ## Usage orders
29
+
30
+ - Breaking: `listInvoices`, `getInvoice` and `Invoice` are removed. Usage
31
+ billed in arrears is an order (`origin: usage`): `listOrders({ origin:
32
+ "usage" })` lists them, and `<OrderCheckout>` pays one with a saved card.
33
+ - `Order` gains `usage` (its period, `due_at`, `amount_paid`, `amount_due`,
34
+ collection and `delinquent`) and `uncollectible_at`; `status` may be
35
+ `uncollectible`. A usage line names its `source`; its `price_id` and
36
+ `product_id` are null.
37
+ - `Order.checkout.expires_at` is read again.
38
+
39
+ ## Embedded checkout
40
+
41
+ - `<EmbeddedCheckout url onComplete?>` frames a merchant order's embedded
42
+ checkout (`checkout.ui_mode: embedded`): it follows the page's height and
43
+ hears its completion only from its own frame on the checkout host's origin;
44
+ without `onComplete` it goes to the checkout's `success_url`.
45
+ - `Order.checkout.ui_mode`; `PublicMerchant.embed_origins`;
46
+ `PublicConfig.network` (the checkout page's sign-in to the customer network).
47
+ - `createNetworkLinkChallenge()` and `createNetworkLink({challengeId, idToken})`
48
+ link the customer to a customer-network account.
49
+
50
+ ## No Solana token list
51
+
52
+ - `listSolanaTokens` and `SolanaToken` are gone with `GET /v1/solana/tokens`:
53
+ the accepted tokens are `getConfig()`'s `payment.solana.tokens`.
54
+
55
+ ## Apple Pay and Google Pay
56
+
57
+ - `<OrderCheckout wallets>` offers Apple Pay and Google Pay through a Stripe
58
+ PSP's Express Checkout Element, above the saved and new cards. Off by
59
+ default: the page's host must be a payment-method domain registered on that
60
+ Stripe account.
61
+
62
+ ## Renewals are orders
63
+
64
+ - `Order` gains `renewal` (a subscription renewal's period and `due_at`) and
65
+ `voided_at`; `expires_at` is null on a renewal; `status` may be `void`.
66
+ - `PaymentRecovery` gains `order_id`: an engine-owned subscription's unpaid
67
+ renewal, paid through `<OrderCheckout>`.
68
+ - `<OrderCheckout>` offers new card entry only where the order takes one (a
69
+ renewal takes saved cards), and lets the customer authenticate a payment
70
+ their bank challenged while they were away.
71
+
72
+ ## Hosted checkout
73
+
74
+ - `getConfig()` returns `merchant` (`PublicMerchant`: `display_name`, and
75
+ `logo_url` and `support_url` when they are https), null when the request
76
+ resolves no merchant.
77
+ - `Order` carries `checkout` (its hosted checkout: `expires_at`,
78
+ `success_url`, `cancel_url`, `saved_payment_methods`; `url` only on the
79
+ answer that made it), null without one.
80
+
81
+ ## Orders take Solana and hosted pages
82
+
83
+ - `<OrderCheckout>` offers an order's Solana option: one button per token,
84
+ then the Solana Pay QR of the order's `solana_pay` next action, confirmed
85
+ until the transfer lands.
86
+ - A PSP with no fields in the page (Stripe without a publishable key) takes
87
+ the order on its hosted page: `<OrderCheckout>` sends the customer there and
88
+ back to `returnUrl` (default: the current page).
89
+ - `OrderPaymentInput` gains `{ token_symbol, psp_id? }` and
90
+ `{ return_url, psp_id? }`.
91
+
92
+ ## OrderCheckout pays an order
93
+
94
+ - `<OrderCheckout orderId>` shows an order and pays it with a saved card or a
95
+ new card in the PSP's fields, running any `next_action` and `confirm`. It
96
+ takes an optional `client` and offers only cards kept for one-click buys
97
+ (`PaymentMethod.reusable`).
98
+ - The client gains `getOrder`, and `payOrder` and `confirmOrder`, which answer
99
+ `{ order, error? }`: a declined payment (402) is the order with
100
+ `payment.last_payment_error`, not a throw. `BillingError` carries `order`.
101
+ - `StripeCardEntry`'s handle gains `tokenize()` and `handleNextAction()`.
102
+
103
+ ## A card is saved in one call
104
+
105
+ - `addPaymentMethod` takes `token` (was `payment_token`). A Stripe card is a
106
+ `pm_` from Stripe's fields, saved by the same call; a card the bank wants to
107
+ authenticate answers `status: "requires_action"` with `next_action`, and
108
+ `confirmPaymentMethod(id)` finishes it after Stripe.js `handleNextAction`.
109
+ `StripeCardEntry` and `SavePaymentMethod` do this themselves.
110
+ - `createCardSetup`, `getCardSetup`, `confirmCardSetup`, the `CardSetup` type
111
+ and the `returnURL` / `cardSetupReturnURL` props are gone: 3-D Secure runs in
112
+ the page.
113
+
114
+ ## Offers sell products by key
115
+
116
+ - The public catalog is a lookup by product key: `listProducts({ keys })` and
117
+ `useProducts({ keys })` take the keys alone (1 to 100) and read one page;
118
+ `entitlements`, `limit` and `cursor` are gone, and so is
119
+ `ProductsState.nextCursor`. No keys fetch nothing.
120
+ - `<Offers keys>` replaces `<Offers entitlements>`: your server names the
121
+ products, with the Go `Client.ListOffers` when it finds them by
122
+ entitlement.
123
+
124
+ ## Apps buy through BuyButton and Offers
125
+
126
+ - `Checkout` and `CheckoutModal` (and their props), the fixture sources and
127
+ the `CheckoutSource` type are no longer exported; the client no longer has
128
+ `createCheckoutSession` or `checkoutSource`. Buy with `<BuyButton>` (it takes
129
+ `amount`, `autoRenew` and `successUrl`) or `<Offers>`. `CheckoutPage` and
130
+ `CheckoutFrame` stay, for the shared payment page.
131
+ - The main entry also exports `createBillingClient` (with `BillingClient`,
132
+ `BillingClientOptions` and `ProductListOptions`) beside `formatAmount`, so a
133
+ React app imports from `@openrails/billing-ui` alone; `./client` and
134
+ `./react` are unchanged.
135
+
136
+ ## One provider
137
+
138
+ - `BillingProvider` takes what `BillingUiProvider` took (`appearance`,
139
+ `messages`, `t`, `locale`, `navigate`) beside `client` and `onChange`, and
140
+ `BillingUiProvider` is gone. The main entry exports it; `./react` exports the
141
+ same component for hook-only apps, without the stylesheet. `client` is
142
+ optional: leave it out where no customer signs in, such as `CheckoutPage`.
143
+ - Inside a provider with a client, the checkout saves a new card to the
144
+ customer first and pays on the customer surface.
145
+
146
+ ## Offers sell an entitlement
147
+
148
+ - `<BuyButton product price onPaid onSignInRequired>` buys one price, labelled
149
+ from the catalog unless given `label`. The app never touches a checkout
150
+ session: when orders take a new card (#1168) it creates an order instead.
151
+ - `<Offers entitlements onPaid onSignInRequired>` lists every product on sale
152
+ granting any of the entitlements, a `BuyButton` per price; `products` feeds
153
+ it from the host's server instead.
154
+ - `listProducts({ entitlements, keys })` and `useProducts({ entitlements, keys })`
155
+ filter the public catalog. One of them is required: OpenRails no longer
156
+ lists the whole catalog publicly.
157
+
3
158
  ## Checkout saves a new card under its PSP
4
159
 
5
160
  - Inside a `BillingProvider`, `Checkout` saved a new card with the session
package/README.md CHANGED
@@ -6,19 +6,17 @@ billing (subscriptions, saved cards, payment history).
6
6
  | Import | Contents |
7
7
  | --------------------------------- | ---------------------------------------------------------------------------------- |
8
8
  | `@openrails/billing-ui/client` | Framework-free typed client for `/billing/v1/me/*` and the catalog, `BillingError` |
9
- | `@openrails/billing-ui/react` | `BillingProvider` and headless hooks |
10
- | `@openrails/billing-ui` | Styled checkout and account components, `BillingUiProvider`, i18n |
9
+ | `@openrails/billing-ui/react` | `BillingProvider` and headless hooks, without styles |
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. OpenRails holds the checkout
14
- session: the signed-in customer mints one for a price, and its `ocs_` id alone
15
- 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
@@ -28,35 +26,57 @@ provenance.
28
26
  pnpm add @openrails/billing-ui@X.Y.Z
29
27
  ```
30
28
 
31
- ## Checkout
29
+ ## Buying
30
+
31
+ A React app imports from one place:
32
32
 
33
33
  ```tsx
34
- import { createBillingClient } from "@openrails/billing-ui/client"
35
- import { Checkout, CheckoutFrame } from "@openrails/billing-ui"
36
- import "@openrails/billing-ui/styles.css"
37
-
38
- const billing = createBillingClient({ fetch: auth.authFetch })
39
- const session = await billing.createCheckoutSession({
40
- productKey: "pro",
41
- priceKey: "monthly",
42
- })
34
+ import { BillingProvider, BuyButton, Offers, createBillingClient, formatAmount } from "@openrails/billing-ui"
43
35
 
44
- // A shared payment page (Config.Checkout.PageURL) answers with a url:
45
- <CheckoutFrame url={session.url} theme="dark" onComplete={() => refetchAccess()} />
46
- // Without one, the app renders the checkout itself:
47
- <Checkout source={billing.checkoutSource(session.id)} onComplete={() => refetchAccess()} />
36
+ // auth-ui's authFetch attaches the bearer and retries once after a refresh;
37
+ // `getToken: () => token` works for any other auth.
38
+ const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFetch })
39
+
40
+ <BillingProvider client={billing} appearance={{ theme: "auto" }}>
41
+ <App />
42
+ </BillingProvider>
48
43
  ```
49
44
 
50
- `onComplete` is a hint: confirm access from your own authenticated API before
51
- granting anything. Pass `successUrl` to return the buyer from a redirect step
52
- (Stripe's hosted page); it must be on one of your app's return origins. A
53
- server that starts checkout itself mints the session with the Go
54
- `Client.CreateCheckoutSession` and hands the browser its `id` and `url`.
45
+ `<BuyButton>` buys one price, `<Offers>` the products your server names by
46
+ key (a course, its bundle and the membership that also unlocks it; the Go
47
+ `Client.ListOffers` finds the products granting an entitlement): a
48
+ `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
50
+ button creates the customer's order and pays it in a dialog
51
+ (`<OrderCheckout>`); a second click before it is paid opens the same order.
52
+ Signing in is the host's: with `signedIn={false}`, or when OpenRails answers
53
+ 401, they call `onSignInRequired` instead. `onPaid` receives the completed
54
+ order; it is a hint, so confirm access from your own API (your gate) before
55
+ granting anything.
56
+
57
+ ```tsx
58
+ <BuyButton
59
+ product="course-101"
60
+ price="rent" // labelled from the catalog unless you pass label
61
+ signedIn={signedIn}
62
+ onSignInRequired={() => openSignIn()}
63
+ onPaid={() => navigate("/courses/css-101")} // your gate now admits them
64
+ />
65
+
66
+ <Offers
67
+ keys={["course-101", "course-bundle", "channel-membership"]} // from your server, or products={...} from its Client.ListOffers
68
+ signedIn={signedIn}
69
+ onSignInRequired={() => openSignIn()}
70
+ onPaid={() => navigate("/courses/css-101")}
71
+ />
72
+ ```
55
73
 
56
- For prepaid credits, a fixed pack is another product/price selection. A
57
- `customer_amount` price also accepts `amount` when minting. Collect the amount
58
- on your product page before opening checkout; the resulting session shows and
59
- charges that exact amount, and its payment form cannot change it:
74
+ `returnUrl` returns the buyer from a redirect step (Stripe's hosted page); it
75
+ must be on one of your app's return origins.
76
+
77
+ For prepaid credits, a fixed pack is another product/price. A
78
+ `customer_amount` price takes the buyer's `amount`: collect it on your product
79
+ page, and the checkout charges exactly that:
60
80
 
61
81
  ```tsx
62
82
  import { decimalToAmount } from "@openrails/billing-ui/client"
@@ -65,72 +85,59 @@ import { decimalToAmount } from "@openrails/billing-ui/client"
65
85
  // `enteredAmount` is the text from an <input inputMode="decimal">, e.g. "100".
66
86
  const amount = decimalToAmount(enteredAmount, currency.decimals)
67
87
  const bounds = depositPrice.customer_amount
68
- if (
69
- !amount ||
70
- !bounds ||
71
- BigInt(amount) < BigInt(bounds.min_amount) ||
72
- BigInt(amount) > BigInt(bounds.max_amount)
73
- ) {
74
- throw new Error("Choose an amount within the deposit limits")
75
- }
76
- const session = await billing.createCheckoutSession({
77
- priceId: depositPrice.id,
78
- amount, // USD 100 becomes "100000000"; no floating-point conversion
79
- })
88
+ const valid = amount && bounds && BigInt(amount) >= BigInt(bounds.min_amount) && BigInt(amount) <= BigInt(bounds.max_amount)
89
+
90
+ <BuyButton product="api-credits" price="deposit" amount={valid ? amount : undefined} label="Add credits" {...handlers} />
80
91
  ```
81
92
 
82
93
  OpenRails validates the bounds and whole currency minor units before accepting
83
94
  payment. Fixed prices reject `amount`; their pack benefit can be greater than
84
95
  the purchase price for a bulk discount. Customer-selected deposits currently
85
- use NMI or Stripe. Changing the selected amount requires minting a new session.
96
+ use NMI or Stripe.
86
97
 
87
- A rail's own step is the pay result's `next_action`: `redirect_to_url` (an
88
- https page, opened in the top window) or `solana_pay` (a `solana:` link shown
89
- as a QR code). A card challenge is its `operation`, authenticated in the page.
90
- Stripe Elements needs the buyer's billing client (`<Checkout>` inside a
91
- `BillingProvider`): the shared payment page offers token rails only.
98
+ A rail's own step is the order's `payment.next_action`: `redirect_to_url` (an
99
+ https page, opened in the top window), `solana_pay` (a `solana:` link shown
100
+ as a QR code) or `authenticate` (the bank's challenge, run in the page).
92
101
 
93
- The payment host serves `<CheckoutPage>` from one HTML entry at `PageURL`,
94
- behind its adapter's `CheckoutFramePolicy` (only the sites in
95
- `Config.Checkout.EmbedOrigins` may frame it):
102
+ ### An order your server creates
96
103
 
97
- ```tsx
98
- import { BillingUiProvider, CheckoutPage } from "@openrails/billing-ui"
104
+ A server that decides what its customer buys creates the order with the Go
105
+ `Client.CreateOrder` (`POST /v1/app/orders`) and hands its id to the
106
+ customer's browser, which pays it signed in:
99
107
 
100
- createRoot(root).render(
101
- <BillingUiProvider>
102
- <CheckoutPage appearance={{ variables: brand }} />
103
- </BillingUiProvider>
104
- )
108
+ ```tsx
109
+ <OrderCheckout orderId={orderId} returnUrl={returnUrl} onSettled={(order) => refetchAccess(order)} />
105
110
  ```
106
111
 
107
- The page reads the session id from its URL fragment and talks to its frame
108
- with `postMessage`, origin-checked both ways: page to app `ready`,
109
- `resize {height}`, `complete {status}` and `redirect {url}` (a nested frame
110
- cannot navigate the top window, so the app does); app to page
111
- `init {theme}`. The page messages only the app origin recorded on its session.
112
+ `onSettled` follows a complete order, or one processing on an asynchronous
113
+ rail. A subscription line names its subscription once paid.
112
114
 
113
115
  ## Account billing
114
116
 
117
+ One `BillingProvider`, usually at the app's root, gives every component the
118
+ client, the appearance and the words. `@openrails/billing-ui/react` exports the
119
+ same provider for an app that uses only the hooks; it loads no stylesheet.
120
+
115
121
  ```tsx
116
- import { createBillingClient } from "@openrails/billing-ui/client"
117
- import { BillingProvider } from "@openrails/billing-ui/react"
118
- import { AccountBilling, BillingUiProvider } from "@openrails/billing-ui"
122
+ import { AccountBilling, BillingProvider, createBillingClient } from "@openrails/billing-ui"
119
123
  import { de } from "@openrails/billing-ui/locales/de"
120
124
 
121
- // auth-ui's authFetch attaches the bearer and retries once after a refresh;
122
- // `getToken: () => token` works for any other auth.
123
125
  const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFetch })
124
126
 
125
- <BillingUiProvider appearance={{ theme: "auto" }} messages={de} locale="de" navigate={navigate}>
126
- <BillingProvider client={billing} onChange={() => queryClient.invalidateQueries({ queryKey: ["billing"] })}>
127
- <AccountBilling
128
- plansHref="/plans"
129
- defaultCurrency="USD" // offers "Make default"
130
- sendSolanaTransaction={(tx) => wallet.signAndSend(tx)} // signs a Solana cancel
131
- />
132
- </BillingProvider>
133
- </BillingUiProvider>
127
+ <BillingProvider
128
+ client={billing}
129
+ appearance={{ theme: "auto" }}
130
+ messages={de}
131
+ locale="de"
132
+ navigate={navigate}
133
+ onChange={() => queryClient.invalidateQueries({ queryKey: ["billing"] })}
134
+ >
135
+ <AccountBilling
136
+ plansHref="/plans"
137
+ defaultCurrency="USD" // offers "Make default"
138
+ sendSolanaTransaction={(tx) => wallet.signAndSend(tx)} // signs a Solana cancel
139
+ />
140
+ </BillingProvider>
134
141
  ```
135
142
 
136
143
  The panels read OpenRails' public configuration (`GET /config`) themselves,
@@ -143,17 +150,15 @@ for host components.
143
150
  ## Catalog and plan changes
144
151
 
145
152
  The client also reads the public catalog and changes a subscription's plan.
146
- No UI ships for these; hosts render their own. The catalog and Solana reads
147
- are public routes, served only when the host mounts OpenRails' checkout
148
- routes; `GET /config` is always served.
153
+ The catalog read is a public route, served only when the host mounts
154
+ OpenRails' checkout routes; `GET /config` is always served.
149
155
 
150
156
  | Call | Route |
151
157
  | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
152
- | `listProducts()` (each product embeds its prices on sale) | `GET /catalog/products` |
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` |
153
159
  | `getConfig()` (capabilities, `currencies`, `payment` with PSPs, `solana.network` and tokens; `client.currencies` is the pinned registry) | `GET /config` |
154
160
  | `previewSubscriptionChange(id, { priceId?, quantity? })` | `POST /me/subscriptions/{id}/change/preview` |
155
161
  | `changeSubscription(id, { priceId?, quantity?, idempotencyKey, signature? })` | `POST /me/subscriptions/{id}/change` |
156
- | `listSolanaTokens({ priceId, wallet })` | `GET /solana/tokens` |
157
162
 
158
163
  ```ts
159
164
  const preview = await billing.previewSubscriptionChange(sub.id, { quantity: 5 }) // amount_due_now, effective
@@ -188,18 +193,15 @@ browser flow from each PSP's `flow` and public `config`
188
193
  (Collect.js, native card inputs, Stripe Elements, redirect, wallet). Hosts never branch on a
189
194
  provider:
190
195
 
191
- - A session's `options` are exactly the armed PSPs whose rail can make this
192
- sale (Solana when it is configured, never CCBill),
193
- each with its `driver` and `public_config`; `saved_methods` are the buyer's
194
- cards on them, newest first (paying sends the chosen card's id explicitly).
195
- Card rails (`collect_js`, `card`, `stripe_elements`) render one panel: saved cards,
196
- an inline new card and one Pay/Subscribe button, which is the customer's
197
- confirmation of the displayed terms. No provider chooser with one rail.
198
- - Inside a `BillingProvider`, a new card is always saved to the account first
199
- and the source is paid with `payment_method_id`; `requires_action` results
200
- carrying an `operation` run 3-D Secure in the page. A `failed` result stays
201
- on the panel with `failure.message` next to its `field`, so the buyer can
202
- pick another card; the host gives each new attempt a new idempotency key.
196
+ - An order's `payment_options` are exactly the armed PSPs whose rail can make
197
+ this sale (Solana for one-time lines when it is configured, never CCBill),
198
+ each with what it `accepts`. `OrderCheckout` renders one panel: the
199
+ customer's saved cards on them, newest first, an inline new card and one Pay
200
+ button. No provider chooser with one rail.
201
+ - A new card is charged and saved in one pay; an `authenticate` next action
202
+ runs 3-D Secure in the page, then `confirm`. A decline stays on the panel
203
+ with its message, so the buyer can pick another card under a new
204
+ idempotency key.
203
205
  - Only PSPs with `checkout !== false` (`checkoutPsps`) take new cards; others
204
206
  stay listed so existing cards and subscriptions keep working.
205
207
  - `<SavePaymentMethod psp={psp} onSaved={(id) => ...} />` saves a card in the
@@ -208,10 +210,8 @@ provider:
208
210
  `returnURL(setupId)`; confirm with `client.confirmCardSetup(id)`.
209
211
  - `authenticatePayment(client, operationId, psp)` completes a pending
210
212
  payment's 3-D Secure challenge when `canAuthenticatePayment(psp)`.
211
- - `defaultCountry` (Checkout, SavePaymentMethod, AccountBilling) preselects
212
- the billing country, else the browser locale's region.
213
- - `CheckoutModal gate={<SignIn />}` renders host content (e.g. sign-in) in
214
- the modal instead of the checkout until cleared.
213
+ - `defaultCountry` (SavePaymentMethod, AccountBilling)
214
+ preselects the billing country, else the browser locale's region.
215
215
 
216
216
  - Panels: `SubscriptionsPanel` (cancel, resume, change card),
217
217
  `PaymentMethodsPanel`, `PaymentHistory`, `BillingStatusBadge`,
@@ -220,8 +220,8 @@ provider:
220
220
  subscription row; `useBillingRefresh()` refetches after host-side changes.
221
221
  - Hooks: `useSubscriptions` (`cancel`, `resume`, `setPaymentMethod`,
222
222
  `changeSubscription`, per-row `pending`, `nextCursor`), `usePaymentMethods` (`add`,
223
- `remove`, `setDefault`), `usePayments` (cursor pages), `useProducts` (the
224
- catalog).
223
+ `remove`, `setDefault`), `usePayments` (cursor pages), `useProducts` (a
224
+ catalog lookup by product key).
225
225
  Actions resolve to `null` or a `BillingError`; they never throw.
226
226
  `changeSubscription` resolves to the `SubscriptionChange` instead of `null`. Cancel, resume
227
227
  and the card change answer the subscription, which replaces the row.
@@ -1,12 +1,10 @@
1
1
  import { CheckoutAppearance } from '../appearance';
2
2
  import { SendSolanaTransaction } from '../client/client';
3
- import { PaymentMethodsPanelProps } from './payment-methods-panel';
4
3
  import { SubscriptionsPanelProps } from './subscriptions-panel';
5
4
  export interface AccountBillingProps {
6
- cardSetupReturnURL?: PaymentMethodsPanelProps["cardSetupReturnURL"];
7
5
  /**
8
6
  * Offers to make a card the default for this currency: it pays the
9
- * 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.
10
8
  */
11
9
  defaultCurrency?: string;
12
10
  /** Billing country to preselect when adding a card. */
@@ -19,4 +17,4 @@ export interface AccountBillingProps {
19
17
  className?: string;
20
18
  }
21
19
  /** Subscriptions, payment methods and history in one column. */
22
- export declare function AccountBilling({ cardSetupReturnURL, defaultCurrency, defaultCountry, sendSolanaTransaction, plansHref, renderSubscriptionFooter, historyPageSize, appearance, className, }: AccountBillingProps): import("react").JSX.Element;
20
+ export declare function AccountBilling({ defaultCurrency, defaultCountry, sendSolanaTransaction, plansHref, renderSubscriptionFooter, historyPageSize, appearance, className, }: AccountBillingProps): import("react").JSX.Element;
@@ -1,11 +1,9 @@
1
1
  import { CheckoutAppearance } from '../appearance';
2
2
  import * as React from "react";
3
3
  export interface PaymentMethodsPanelProps {
4
- /** Return target after off-page card verification; see `SavePaymentMethod`. */
5
- cardSetupReturnURL?: (setupId: string) => string;
6
4
  /**
7
5
  * Offers to make a card the default for this currency: it pays the
8
- * 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.
9
7
  */
10
8
  defaultCurrency?: string;
11
9
  /** Billing country to preselect when adding a card. */
@@ -17,4 +15,4 @@ export interface PaymentMethodsPanelProps {
17
15
  * The customer's saved cards. "Add card" offers each PSP of the merchant's
18
16
  * payment setup (`GET /config`) that saves a card in the page.
19
17
  */
20
- export declare function PaymentMethodsPanel({ cardSetupReturnURL, defaultCurrency, defaultCountry, appearance, className, }: PaymentMethodsPanelProps): React.JSX.Element;
18
+ export declare function PaymentMethodsPanel({ defaultCurrency, defaultCountry, appearance, className, }: PaymentMethodsPanelProps): React.JSX.Element;
@@ -0,0 +1,40 @@
1
+ import { CheckoutAppearance } from './appearance';
2
+ import { Order } from './client/types';
3
+ import * as React from "react";
4
+ export interface BuyButtonProps {
5
+ /** The product's catalog key. */
6
+ product: string;
7
+ /** The price's key within the product. */
8
+ price: string;
9
+ /** The button's text; by default the price's amount and terms, from the catalog. */
10
+ label?: string;
11
+ /**
12
+ * A customer-chosen deposit, in the price currency's native units, for a
13
+ * price with `customer_amount`; the order charges exactly this.
14
+ */
15
+ amount?: string;
16
+ /** Where a redirect PSP (Stripe's hosted page) returns the buyer: one of the app's return origins. */
17
+ returnUrl?: string;
18
+ /**
19
+ * The order is paid. A hint: the host's own gate decides access, so send
20
+ * the buyer to the content it guards.
21
+ */
22
+ onPaid: (order: Order) => void;
23
+ /**
24
+ * Buying needs a signed-in customer: called instead of the checkout when
25
+ * `signedIn` is false or OpenRails answers 401.
26
+ */
27
+ onSignInRequired: () => void;
28
+ /** false when the host knows the visitor is signed out. */
29
+ signedIn?: boolean;
30
+ appearance?: CheckoutAppearance;
31
+ className?: string;
32
+ }
33
+ export declare function BuyButton(props: BuyButtonProps): React.JSX.Element;
34
+ export type PurchaseProps = Omit<BuyButtonProps, "product" | "price"> & {
35
+ /** The price to buy; the button waits while it is unknown. */
36
+ priceId?: string;
37
+ label: string;
38
+ };
39
+ /** One price's button and its order. Requires `BillingProvider`. */
40
+ export declare function Purchase({ priceId, label, amount, returnUrl, onPaid, onSignInRequired, signedIn, appearance, className, }: PurchaseProps): React.JSX.Element;
package/dist/cart.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { CheckoutAppearance } from './appearance';
2
+ import { BillingClient } from './client/client';
3
+ import { Order } from './client/types';
4
+ import * as React from "react";
5
+ export interface CartProps {
6
+ /** The client to read and buy through; default: the provider's. */
7
+ client?: BillingClient;
8
+ /**
9
+ * The order made from the cart. Default: pay it here with OrderCheckout.
10
+ */
11
+ onOrder?: (order: Order) => void;
12
+ /** Passed to OrderCheckout: the order is complete, or processing. */
13
+ onSettled?: (order: Order) => void;
14
+ appearance?: CheckoutAppearance;
15
+ className?: string;
16
+ }
17
+ /** The customer's cart. Requires `BillingProvider`. */
18
+ export declare function Cart({ client: given, onOrder, onSettled, appearance, className, }: CartProps): React.JSX.Element;