@openrails/billing-ui 0.230.0 → 0.234.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 +117 -9
- package/README.md +88 -39
- package/dist/account/account-billing.d.ts +3 -3
- package/dist/account/cancel-dialog.d.ts +2 -2
- package/dist/account/format.d.ts +4 -4
- package/dist/account/payment-methods-panel.d.ts +7 -9
- package/dist/account/section.d.ts +3 -1
- package/dist/checkout-page.d.ts +6 -1
- package/dist/client/client.d.ts +81 -62
- package/dist/client/generated/currencies.d.ts +1 -0
- package/dist/client/generated/error-codes.d.ts +7 -0
- package/dist/client/generated/routes.d.ts +764 -0
- package/dist/client/generated/wire.d.ts +772 -0
- package/dist/client/index.d.ts +3 -3
- package/dist/client/types.d.ts +498 -393
- package/dist/client-CWX6QpLa.js +920 -0
- package/dist/client-CWX6QpLa.js.map +1 -0
- package/dist/client.js +2 -3
- package/dist/components/methods.d.ts +3 -3
- package/dist/config-DZ97SQG2.js +341 -0
- package/dist/config-DZ97SQG2.js.map +1 -0
- package/dist/frame.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1447 -1539
- package/dist/index.js.map +1 -1
- package/dist/lib/billing.d.ts +14 -0
- package/dist/lib/money.d.ts +1 -0
- package/dist/lib/period.d.ts +0 -2
- package/dist/lib/rail-meta.d.ts +4 -4
- package/dist/locales/de.js +11 -8
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +8 -5
- package/dist/locales/en.js +11 -8
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +11 -8
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +11 -8
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +11 -8
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +11 -8
- package/dist/locales/zh.js.map +1 -1
- package/dist/psp.d.ts +10 -21
- package/dist/react/config.d.ts +34 -0
- package/dist/react/context.d.ts +5 -8
- package/dist/react/hooks.d.ts +20 -14
- package/dist/react/index.d.ts +1 -0
- package/dist/react/provider.d.ts +1 -9
- package/dist/react/remote.d.ts +0 -1
- package/dist/react.js +24 -28
- package/dist/react.js.map +1 -1
- package/dist/save-payment-method.d.ts +1 -1
- package/dist/source.d.ts +0 -9
- package/dist/styles.css +1 -1
- package/dist/types.d.ts +114 -55
- package/docs/payment-form-contract.md +23 -40
- package/package.json +3 -3
- package/dist/client/generated/openrails-routes.d.ts +0 -2
- package/dist/client-DhG2jv34.js +0 -771
- package/dist/client-DhG2jv34.js.map +0 -1
- package/dist/errors-T-PRm3vV.js +0 -54
- package/dist/errors-T-PRm3vV.js.map +0 -1
- package/dist/hooks-DLFF-zx5.js +0 -285
- package/dist/hooks-DLFF-zx5.js.map +0 -1
- package/docs/1055-nmi-host-integration.md +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,115 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## The customer's summary
|
|
4
|
+
|
|
5
|
+
- `getAccount()` reads `GET /me`: the balance per currency, the card that
|
|
6
|
+
pays each currency, and the unread notice count.
|
|
7
|
+
- `Invoice.delinquent`: overdue past grace; new usage in its currency is
|
|
8
|
+
refused until it is paid.
|
|
9
|
+
|
|
10
|
+
## A subscription's dunning is one object
|
|
11
|
+
|
|
12
|
+
- `Subscription.dunning` (`SubscriptionDunning`) replaces `grace_ends_at` and
|
|
13
|
+
`next_retry_at`: the declines so far, the retries left, the next and the
|
|
14
|
+
final retry, and whether it waits for a new card. Null unless the
|
|
15
|
+
subscription is `past_due` or `awaiting_method`.
|
|
16
|
+
|
|
17
|
+
## The public catalog is under /catalog
|
|
18
|
+
|
|
19
|
+
- `listProducts()` reads `GET /catalog/products` (was `GET /products`).
|
|
20
|
+
|
|
21
|
+
## One public configuration
|
|
22
|
+
|
|
23
|
+
- `getConfig()` reads `GET /config`: the mount's `capabilities`, the currency
|
|
24
|
+
registry (`currencies`) and the merchant's `payment` setup (its PSPs and
|
|
25
|
+
Solana network and tokens; null without a merchant). It replaces
|
|
26
|
+
`getCheckoutConfig()` and `listCurrencies()`: `checkoutConfigSchema` and
|
|
27
|
+
`CheckoutConfig` are `publicConfigSchema` and `PublicConfig` (its `payment`
|
|
28
|
+
is a `PaymentConfig`), and `currencyRegistrySchema` is removed.
|
|
29
|
+
`client.currencies` stays the pinned registry.
|
|
30
|
+
- `BillingProvider` fetches it once and shares it: `useConfig()` reads it,
|
|
31
|
+
`useCurrencyScales()` layers the pinned registry and host overrides over its
|
|
32
|
+
currencies, and the account panels use both.
|
|
33
|
+
- `AccountBilling` and `PaymentMethodsPanel` take their PSPs from it; their
|
|
34
|
+
`psps` prop is removed. "Add card" waits for it, says so when it fails (with
|
|
35
|
+
a retry) or every card PSP is temporarily unavailable, and asks again after
|
|
36
|
+
the PSP's `retry_after`.
|
|
37
|
+
- `PspConfig` has `status` and `retry_after`; a temporarily unavailable PSP
|
|
38
|
+
saves no card (`cardSetupDriver` is null).
|
|
39
|
+
|
|
40
|
+
## Prices come with their products
|
|
41
|
+
|
|
42
|
+
- `listPrices` is removed with `GET /prices`: `listProducts()` returns every
|
|
43
|
+
product on sale with its current prices. Filter by currency, product or
|
|
44
|
+
renewal in the browser.
|
|
45
|
+
|
|
46
|
+
## Paying on a customer surface
|
|
47
|
+
|
|
48
|
+
- `client.checkoutSource(id, { customerBase })` reads and pays a checkout
|
|
49
|
+
session as its signed-in customer at a customer surface's
|
|
50
|
+
`/checkout-sessions/{id}` (for example a host's
|
|
51
|
+
`/api/v1/merchants/acme/billing/me`), so its saved cards pay. Without
|
|
52
|
+
`customerBase` the session id alone pays at `/checkout-sessions/{id}` as
|
|
53
|
+
before. `<CheckoutPage customerBase>` passes it through.
|
|
54
|
+
|
|
55
|
+
## 1.0.0
|
|
56
|
+
|
|
57
|
+
Ships with OpenRails v1.0.0; use the two at the same version. Everything a host
|
|
58
|
+
changes is in [Migrating to v1](../../docs/migrating-to-v1.md).
|
|
59
|
+
|
|
60
|
+
Checkout
|
|
61
|
+
|
|
62
|
+
- A checkout session is the only browser purchase. `createCheckoutSession()`
|
|
63
|
+
mints at `/me/checkout-sessions` and returns a `CheckoutSessionLink` (`url`
|
|
64
|
+
null without a payment page); `client.checkoutSource(id)` is the source of
|
|
65
|
+
`<Checkout>`, `<CheckoutModal>` and `<CheckoutPage>`.
|
|
66
|
+
- The session document's `rails` is `options` (`PaymentOption`, with the
|
|
67
|
+
option's `psp_id`). A rail's own step is one `next_action`
|
|
68
|
+
(`redirect_to_url`, `solana_pay`); a card challenge is `operation`. Saved
|
|
69
|
+
methods carry `card` (`brand`, `last4`, `exp_month`, `exp_year`) and no
|
|
70
|
+
`default`. Fields OpenRails sends as null read as `undefined`.
|
|
71
|
+
- `PayRequest` carries one `billing_details` (`name`, `email`, `phone`,
|
|
72
|
+
`address` with `postal_code` and `country`) in place of the flat
|
|
73
|
+
`name_on_card`, `zip`, `country`, `last_four`, `card_type` and `expiry_date`.
|
|
74
|
+
- Stripe Elements runs on a session inside a `BillingProvider`: the card is
|
|
75
|
+
saved through the customer's client and the session pays it by id.
|
|
76
|
+
- Removed: `createHttpSource`, `CheckoutSourceError`, `checkoutRails`,
|
|
77
|
+
`CheckoutRailOffer`, `PaymentRailOption`.
|
|
78
|
+
|
|
79
|
+
Client
|
|
80
|
+
|
|
81
|
+
- Lists are cursor pages: `listSubscriptions`, `listPaymentMethods`,
|
|
82
|
+
`listPayments` and `listInvoices` take `cursor` and answer `next_cursor`;
|
|
83
|
+
there is no `total`. The hooks page with `nextCursor`.
|
|
84
|
+
- `cancelSubscription`, `resumeSubscription` and
|
|
85
|
+
`setSubscriptionPaymentMethod` resolve to the `Subscription`. A Solana cancel
|
|
86
|
+
or tier change answers `next_action` (`solana_sign_transactions`); sign it
|
|
87
|
+
with `signWalletAction` and repeat the call with `signature`. The separate
|
|
88
|
+
Solana cancel and tier-change methods are removed.
|
|
89
|
+
- `addPaymentMethod` takes `NewCard` with `psp_id`, a `payment_token` or
|
|
90
|
+
`card`, and `billing_details`.
|
|
91
|
+
- There is no default card: `setDefaultPaymentMethod` is
|
|
92
|
+
`setCollectionPaymentMethod`, the hook's `setDefault` is `setCollection`, the
|
|
93
|
+
change event is `payment_method.collection_changed`, and the
|
|
94
|
+
`AccountBilling` / `PaymentMethodsPanel` prop `defaultCurrency` is
|
|
95
|
+
`collectionCurrency`. `PaymentMethod` lists `collection_currencies`.
|
|
96
|
+
- `getSolanaConfig()` is `getCheckoutConfig()` (the network is
|
|
97
|
+
`solana.network`); `listSolanaTokens` takes `{ priceId, wallet }`.
|
|
98
|
+
- `getStatus()` and `BillingStatus` are removed: read `/me/entitlements` and
|
|
99
|
+
`listSubscriptions()`.
|
|
100
|
+
- `listPrices` filters by `productId` and `autoRenew`. A `Price` has
|
|
101
|
+
`product_id`, `archived`, `access_duration_hours` (null: for good) and
|
|
102
|
+
`auto_renew`; `type`, `recurring.interval` and `active` are gone.
|
|
103
|
+
- `Payment` has `kind`, a typed `status` and the `price` it bought; a
|
|
104
|
+
subscription's status is `canceled`.
|
|
105
|
+
- A `TierChange` has no `mode`, `url` or `payment`: a redirect is
|
|
106
|
+
`next_action.url`. Generated list fields are `T[]`, never `null`.
|
|
107
|
+
- A subscription's `access` windows are `starts_at` and `ends_at`.
|
|
108
|
+
- A `Product` has no `catalog_id`. A refusal's code fixes its status:
|
|
109
|
+
`changeTier` answers a registered code for each condition, and a declined
|
|
110
|
+
tier-change charge is `card_declined` with `decline_reason`.
|
|
111
|
+
|
|
112
|
+
## Hosted checkout sessions
|
|
4
113
|
|
|
5
114
|
- OpenRails serves the checkout session; hosts no longer write
|
|
6
115
|
`/api/v1/checkout/sessions` routes. `client.createCheckoutSession()` mints
|
|
@@ -14,7 +123,7 @@
|
|
|
14
123
|
- `<Checkout onRedirect>` replaces the top-window navigation of redirect rails.
|
|
15
124
|
- The session document gains `embed_origin`.
|
|
16
125
|
|
|
17
|
-
## Server card entry
|
|
126
|
+
## Server card entry
|
|
18
127
|
|
|
19
128
|
- Driver `card` (a PSP declared `card_entry: server`, flow `card`): `<Checkout>`
|
|
20
129
|
and `SavePaymentMethod` render plain card inputs with the Collect.js form's
|
|
@@ -26,7 +135,7 @@
|
|
|
26
135
|
- `cardSetupDriver` may return `"card"`; `NewCard` takes `payment_token` or
|
|
27
136
|
`card`, never both.
|
|
28
137
|
|
|
29
|
-
## Catalog and plan-change client
|
|
138
|
+
## Catalog and plan-change client
|
|
30
139
|
|
|
31
140
|
- `@openrails/billing-ui/client` gains the public catalog (`listProducts`,
|
|
32
141
|
`listPrices`, `listCurrencies`), plan change (`previewTierChange`,
|
|
@@ -40,7 +149,7 @@
|
|
|
40
149
|
`"change_tier"`, `onChange` `subscription.tier_changed`), which resolves to
|
|
41
150
|
the `TierChange` or a `BillingError`.
|
|
42
151
|
|
|
43
|
-
## Explicit payment method, fail-fast errors
|
|
152
|
+
## Explicit payment method, fail-fast errors
|
|
44
153
|
|
|
45
154
|
- The checkout pre-selects the customer's default card (`PaymentMethod.default`,
|
|
46
155
|
`SavedPaymentMethod.default`) and always sends the chosen card's id.
|
|
@@ -55,13 +164,13 @@
|
|
|
55
164
|
(2 s). New `isServerError`; a 5xx without an error envelope has code
|
|
56
165
|
`server_error`.
|
|
57
166
|
|
|
58
|
-
## One-click card subscriptions
|
|
167
|
+
## One-click card subscriptions
|
|
59
168
|
|
|
60
169
|
- A checkout host relays a card subscription's pay request with OpenRails'
|
|
61
170
|
`CreateCheckoutSessionRequest.Confirm`; a new Collect.js token subscribes in
|
|
62
171
|
one call. No package API change.
|
|
63
172
|
|
|
64
|
-
## Advertised checkout rails
|
|
173
|
+
## Advertised checkout rails
|
|
65
174
|
|
|
66
175
|
- Breaking: `checkoutRails(offers)` takes OpenRails' checkout options alone.
|
|
67
176
|
Each option carries the `driver` and `public_config` OpenRails derived from
|
|
@@ -80,7 +189,7 @@ generated contract.
|
|
|
80
189
|
## 0.9.0
|
|
81
190
|
|
|
82
191
|
One embedded card panel for one-time purchases and subscriptions, on every
|
|
83
|
-
card PSP
|
|
192
|
+
card PSP. Requires OpenRails v0.164.0.
|
|
84
193
|
|
|
85
194
|
- Stripe Elements checkout rail (`driver: "stripe_elements"`, from a PSP with
|
|
86
195
|
`flow: "elements"`): saved Stripe cards or a new card in the page, 3-D
|
|
@@ -127,8 +236,7 @@ and never branches on Stripe, NMI or any other provider.
|
|
|
127
236
|
`getPaymentAuthentication`, `confirmPaymentAuthentication`.
|
|
128
237
|
- Breaking: `AccountBilling`/`PaymentMethodsPanel` take `psps` (and optional
|
|
129
238
|
`cardSetupReturnURL`) instead of `cardSetup`; `CardSetupConfig` is removed.
|
|
130
|
-
Stripe card setup needs OpenRails serving Stripe's `publishable_key
|
|
131
|
-
(openrails#1062).
|
|
239
|
+
Stripe card setup needs OpenRails serving Stripe's `publishable_key`.
|
|
132
240
|
|
|
133
241
|
## 0.7.0
|
|
134
242
|
|
package/README.md
CHANGED
|
@@ -35,21 +35,63 @@ import { Checkout, CheckoutFrame } from "@openrails/billing-ui"
|
|
|
35
35
|
import "@openrails/billing-ui/styles.css"
|
|
36
36
|
|
|
37
37
|
const billing = createBillingClient({ fetch: auth.authFetch })
|
|
38
|
-
const session = await billing.createCheckoutSession({
|
|
38
|
+
const session = await billing.createCheckoutSession({
|
|
39
|
+
productKey: "pro",
|
|
40
|
+
priceKey: "monthly",
|
|
41
|
+
})
|
|
39
42
|
|
|
40
|
-
// A shared payment page (Config.
|
|
43
|
+
// A shared payment page (Config.Checkout.PageURL) answers with a url:
|
|
41
44
|
<CheckoutFrame url={session.url} theme="dark" onComplete={() => refetchAccess()} />
|
|
42
45
|
// Without one, the app renders the checkout itself:
|
|
43
46
|
<Checkout source={billing.checkoutSource(session.id)} onComplete={() => refetchAccess()} />
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
`onComplete` is a hint: confirm access from your own authenticated API before
|
|
47
|
-
granting anything. Pass `successUrl` to return the buyer from a redirect
|
|
48
|
-
(
|
|
50
|
+
granting anything. Pass `successUrl` to return the buyer from a redirect step
|
|
51
|
+
(Stripe's hosted page); it must be on one of your app's return origins. A
|
|
52
|
+
server that starts checkout itself mints the session with the Go
|
|
53
|
+
`Client.CreateCheckoutSession` and hands the browser its `id` and `url`.
|
|
54
|
+
|
|
55
|
+
For prepaid credits, a fixed pack is another product/price selection. A
|
|
56
|
+
`customer_amount` price also accepts `amount` when minting. Collect the amount
|
|
57
|
+
on your product page before opening checkout; the resulting session shows and
|
|
58
|
+
charges that exact amount, and its payment form cannot change it:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { decimalToAmount } from "@openrails/billing-ui/client"
|
|
62
|
+
|
|
63
|
+
// `depositPrice` is in a product from listProducts; `currency` from getConfig().currencies.
|
|
64
|
+
// `enteredAmount` is the text from an <input inputMode="decimal">, e.g. "100".
|
|
65
|
+
const amount = decimalToAmount(enteredAmount, currency.decimals)
|
|
66
|
+
const bounds = depositPrice.customer_amount
|
|
67
|
+
if (
|
|
68
|
+
!amount ||
|
|
69
|
+
!bounds ||
|
|
70
|
+
BigInt(amount) < BigInt(bounds.min_amount) ||
|
|
71
|
+
BigInt(amount) > BigInt(bounds.max_amount)
|
|
72
|
+
) {
|
|
73
|
+
throw new Error("Choose an amount within the deposit limits")
|
|
74
|
+
}
|
|
75
|
+
const session = await billing.createCheckoutSession({
|
|
76
|
+
priceId: depositPrice.id,
|
|
77
|
+
amount, // USD 100 becomes "100000000"; no floating-point conversion
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
OpenRails validates the bounds and whole currency minor units before accepting
|
|
82
|
+
payment. Fixed prices reject `amount`; their pack benefit can be greater than
|
|
83
|
+
the purchase price for a bulk discount. Customer-selected deposits currently
|
|
84
|
+
use NMI or Stripe. Changing the selected amount requires minting a new session.
|
|
85
|
+
|
|
86
|
+
A rail's own step is the pay result's `next_action`: `redirect_to_url` (an
|
|
87
|
+
https page, opened in the top window) or `solana_pay` (a `solana:` link shown
|
|
88
|
+
as a QR code). A card challenge is its `operation`, authenticated in the page.
|
|
89
|
+
Stripe Elements needs the buyer's billing client (`<Checkout>` inside a
|
|
90
|
+
`BillingProvider`): the shared payment page offers token rails only.
|
|
49
91
|
|
|
50
92
|
The payment host serves `<CheckoutPage>` from one HTML entry at `PageURL`,
|
|
51
93
|
behind its adapter's `CheckoutFramePolicy` (only the sites in
|
|
52
|
-
`Config.
|
|
94
|
+
`Config.Checkout.EmbedOrigins` may frame it):
|
|
53
95
|
|
|
54
96
|
```tsx
|
|
55
97
|
import { BillingUiProvider, CheckoutPage } from "@openrails/billing-ui"
|
|
@@ -83,29 +125,34 @@ const billing = createBillingClient({ baseUrl: "/billing/v1", fetch: auth.authFe
|
|
|
83
125
|
<BillingProvider client={billing} onChange={() => queryClient.invalidateQueries({ queryKey: ["billing"] })}>
|
|
84
126
|
<AccountBilling
|
|
85
127
|
plansHref="/plans"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
sendSolanaTransaction={(tx) => wallet.signAndSend(tx)} // Solana-rail cancel
|
|
128
|
+
collectionCurrency="USD" // offers "Use for invoices"
|
|
129
|
+
sendSolanaTransaction={(tx) => wallet.signAndSend(tx)} // signs a Solana cancel
|
|
89
130
|
/>
|
|
90
131
|
</BillingProvider>
|
|
91
132
|
</BillingUiProvider>
|
|
92
133
|
```
|
|
93
134
|
|
|
135
|
+
The panels read OpenRails' public configuration (`GET /config`) themselves,
|
|
136
|
+
once per `BillingProvider`: "Add card" offers each PSP of its `payment` that
|
|
137
|
+
saves a card in the page, and says so while the configuration fails or its
|
|
138
|
+
card PSPs are temporarily unavailable; amounts use its currency registry over
|
|
139
|
+
the pinned copy. `useConfig()` and `useCurrencyScales()` read the same copy
|
|
140
|
+
for host components.
|
|
141
|
+
|
|
94
142
|
## Catalog and plan changes
|
|
95
143
|
|
|
96
144
|
The client also reads the public catalog and changes a subscription's plan.
|
|
97
|
-
No UI ships for these; hosts render their own. The catalog
|
|
98
|
-
|
|
99
|
-
|
|
145
|
+
No UI ships for these; hosts render their own. The catalog and Solana reads
|
|
146
|
+
are public routes, served only when the host mounts OpenRails' checkout
|
|
147
|
+
routes; `GET /config` is always served.
|
|
100
148
|
|
|
101
149
|
| Call | Route |
|
|
102
150
|
| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
103
|
-
| `listProducts()
|
|
104
|
-
| `
|
|
151
|
+
| `listProducts()` (each product embeds its prices on sale) | `GET /catalog/products` |
|
|
152
|
+
| `getConfig()` (capabilities, `currencies`, `payment` with PSPs, `solana.network` and tokens; `client.currencies` is the pinned registry) | `GET /config` |
|
|
105
153
|
| `previewTierChange(id, priceId)` | `POST /me/subscriptions/{id}/change-tier/preview` |
|
|
106
|
-
| `changeTier(id, { priceId, idempotencyKey })`
|
|
107
|
-
| `
|
|
108
|
-
| `prepareSolanaTierChange(id, newPriceId)`, `confirmSolanaTierChange(id, { signature, newPriceId })` | `POST /me/subscriptions/{id}/solana-tier-change`, `.../confirm` |
|
|
154
|
+
| `changeTier(id, { priceId, idempotencyKey, signature? })` | `POST /me/subscriptions/{id}/change-tier` |
|
|
155
|
+
| `listSolanaTokens({ priceId, wallet })` | `GET /solana/tokens` |
|
|
109
156
|
|
|
110
157
|
```ts
|
|
111
158
|
const preview = await billing.previewTierChange(sub.id, price.id) // amount_due_now, effective
|
|
@@ -121,30 +168,32 @@ const change = await billing.changeTier(sub.id, {
|
|
|
121
168
|
lost response), so OpenRails replays the stored result instead of charging
|
|
122
169
|
twice. `requires_action` carries `operation_id` for `authenticatePayment`;
|
|
123
170
|
replay the same key afterwards.
|
|
124
|
-
-
|
|
125
|
-
`
|
|
126
|
-
|
|
171
|
+
- A rail that needs the customer's own step answers `requires_action` with a
|
|
172
|
+
`next_action`. Solana's is `solana_sign_transactions`:
|
|
173
|
+
`signWalletAction(change.next_action, send)` signs and sends its
|
|
174
|
+
transactions and returns the signature; repeat `changeTier` with the same
|
|
175
|
+
key and `signature`. `cancelSubscription(id, { reason, signature? })` works
|
|
176
|
+
the same way: its answer is the subscription, unchanged and carrying
|
|
177
|
+
`next_action` until the signed cancel lands. The hooks run this when given
|
|
178
|
+
`sendTransaction`.
|
|
127
179
|
|
|
128
180
|
## Provider-neutral flows
|
|
129
181
|
|
|
130
|
-
|
|
131
|
-
|
|
182
|
+
OpenRails's browser PSP configs (`GET /config`'s `payment.psps`) pick the
|
|
183
|
+
browser flow from each PSP's `flow` and public `config`
|
|
132
184
|
(Collect.js, native card inputs, Stripe Elements, redirect, wallet). Hosts never branch on a
|
|
133
185
|
provider:
|
|
134
186
|
|
|
135
|
-
- `
|
|
136
|
-
|
|
137
|
-
the
|
|
138
|
-
|
|
139
|
-
lists exactly the armed PSPs whose rail can make this sale (Solana when it is
|
|
140
|
-
configured, never CCBill for a new subscription), each with its `driver` and
|
|
141
|
-
`public_config`. An offer without a driver is not rendered.
|
|
187
|
+
- A session's `options` are exactly the armed PSPs whose rail can make this
|
|
188
|
+
sale (Solana when it is configured, never CCBill),
|
|
189
|
+
each with its `driver` and `public_config`; `saved_methods` are the buyer's
|
|
190
|
+
cards on them, newest first (paying sends the chosen card's id explicitly).
|
|
142
191
|
Card rails (`collect_js`, `card`, `stripe_elements`) render one panel: saved cards,
|
|
143
|
-
an inline new card and one Pay/Subscribe button, which is the
|
|
192
|
+
an inline new card and one Pay/Subscribe button, which is the customer's
|
|
144
193
|
confirmation of the displayed terms. No provider chooser with one rail.
|
|
145
194
|
- Inside a `BillingProvider`, a new card is always saved to the account first
|
|
146
195
|
and the source is paid with `payment_method_id`; `requires_action` results
|
|
147
|
-
carrying `
|
|
196
|
+
carrying an `operation` run 3-D Secure in the page. A `failed` result stays
|
|
148
197
|
on the panel with `failure.message` next to its `field`, so the buyer can
|
|
149
198
|
pick another card; the host gives each new attempt a new idempotency key.
|
|
150
199
|
- Only PSPs with `checkout !== false` (`checkoutPsps`) take new cards; others
|
|
@@ -165,13 +214,13 @@ provider:
|
|
|
165
214
|
`CancelSubscriptionDialog`; `AccountBilling` stacks them.
|
|
166
215
|
- `renderSubscriptionFooter={(s) => ...}` adds host content under a
|
|
167
216
|
subscription row; `useBillingRefresh()` refetches after host-side changes.
|
|
168
|
-
- Hooks: `useSubscriptions` (`cancel`, `
|
|
169
|
-
`
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
217
|
+
- Hooks: `useSubscriptions` (`cancel`, `resume`, `setPaymentMethod`,
|
|
218
|
+
`changeTier`, per-row `pending`, `nextCursor`), `usePaymentMethods` (`add`,
|
|
219
|
+
`remove`, `setCollection`), `usePayments` (cursor pages), `useProducts` (the
|
|
220
|
+
catalog).
|
|
221
|
+
Actions resolve to `null` or a `BillingError`; they never throw.
|
|
222
|
+
`changeTier` resolves to the `TierChange` instead of `null`. Cancel, resume
|
|
223
|
+
and the card change answer the subscription, which replaces the row.
|
|
175
224
|
- Money is exact: `/me` amounts are int64 native-unit strings, scaled by the
|
|
176
225
|
OpenRails currency registry (`currencies` option to extend it).
|
|
177
226
|
- Messages: English is complete and the fallback; `messages` layers bundles,
|
|
@@ -201,8 +250,8 @@ sale and saved card on a credential-less NMI PSP).
|
|
|
201
250
|
|
|
202
251
|
```sh
|
|
203
252
|
pnpm test:e2e # Playwright against the real server
|
|
204
|
-
pnpm contract # regenerate src/client/generated
|
|
205
|
-
pnpm contract:check # fail if
|
|
253
|
+
pnpm contract # regenerate OpenRails' contract files, src/client/generated among them
|
|
254
|
+
pnpm contract:check # fail if a generated contract file is stale
|
|
206
255
|
```
|
|
207
256
|
|
|
208
257
|
`e2e/openrails/account.spec.ts` drives the packaged `AccountBilling` (built from
|
|
@@ -3,9 +3,9 @@ import { SendSolanaTransaction } from '../client/client';
|
|
|
3
3
|
import { PaymentMethodsPanelProps } from './payment-methods-panel';
|
|
4
4
|
import { SubscriptionsPanelProps } from './subscriptions-panel';
|
|
5
5
|
export interface AccountBillingProps {
|
|
6
|
-
psps?: PaymentMethodsPanelProps["psps"];
|
|
7
6
|
cardSetupReturnURL?: PaymentMethodsPanelProps["cardSetupReturnURL"];
|
|
8
|
-
|
|
7
|
+
/** Offers to make a card the one that collects this currency's invoices. */
|
|
8
|
+
collectionCurrency?: string;
|
|
9
9
|
/** Billing country to preselect when adding a card. */
|
|
10
10
|
defaultCountry?: string;
|
|
11
11
|
sendSolanaTransaction?: SendSolanaTransaction;
|
|
@@ -16,4 +16,4 @@ export interface AccountBillingProps {
|
|
|
16
16
|
className?: string;
|
|
17
17
|
}
|
|
18
18
|
/** Subscriptions, payment methods and history in one column. */
|
|
19
|
-
export declare function AccountBilling({
|
|
19
|
+
export declare function AccountBilling({ cardSetupReturnURL, collectionCurrency, defaultCountry, sendSolanaTransaction, plansHref, renderSubscriptionFooter, historyPageSize, appearance, className, }: AccountBillingProps): import("react").JSX.Element;
|
|
@@ -7,12 +7,12 @@ export interface CancelSubscriptionDialogProps {
|
|
|
7
7
|
onOpenChange: (open: boolean) => void;
|
|
8
8
|
/** Subscription display name. */
|
|
9
9
|
name: string;
|
|
10
|
-
/** On-chain (Solana) cancel:
|
|
10
|
+
/** On-chain (Solana) cancel: the wallet signs after the reason. */
|
|
11
11
|
onChain?: boolean;
|
|
12
12
|
/** In-flight stage, when cancelling. */
|
|
13
13
|
pending?: SubscriptionAction;
|
|
14
14
|
/** Resolves null on success; the dialog then closes. */
|
|
15
|
-
onConfirm: (
|
|
15
|
+
onConfirm: (reason: string) => Promise<BillingError | null>;
|
|
16
16
|
appearance?: CheckoutAppearance;
|
|
17
17
|
}
|
|
18
18
|
export declare function CancelSubscriptionDialog({ open, onOpenChange, name, onChain, pending, onConfirm, appearance, }: CancelSubscriptionDialogProps): React.JSX.Element;
|
package/dist/account/format.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { CardSummary, CurrencyScales, Payment,
|
|
1
|
+
import { CardSummary, CurrencyScales, Payment, Price, Subscription } from '../client/types';
|
|
2
2
|
import { Translator } from '../i18n/messages';
|
|
3
3
|
export declare function formatDate(value: string | null | undefined, locale?: string): string | null;
|
|
4
4
|
/** Exact money, or null while the currency's scale is unknown. */
|
|
5
5
|
export declare function formatMoney(amount: string | null | undefined, currency: string, scales: CurrencyScales, locale?: string): string | null;
|
|
6
6
|
/** "every 30 days", "30 days of access" or "one-time". */
|
|
7
|
-
export declare function intervalLabel(price:
|
|
7
|
+
export declare function intervalLabel(price: Pick<Price, "access_duration_hours" | "billing_interval_hours"> | null | undefined, m: Translator): string | null;
|
|
8
8
|
export declare function subscriptionName(s: Subscription, { t }: Translator): string;
|
|
9
9
|
/** What a payment bought: the product's name, else its kind, plus its cadence. */
|
|
10
10
|
export declare function paymentItem(p: Payment, m: Translator): {
|
|
@@ -16,9 +16,9 @@ export declare function brandName(brand: string | null | undefined, fallback: st
|
|
|
16
16
|
export declare function cardText(card: CardSummary | null | undefined, m: Translator): string;
|
|
17
17
|
export declare function expiry(card: CardSummary | null | undefined): string | null;
|
|
18
18
|
export declare const LIVE_STATUSES: Set<string>;
|
|
19
|
-
/**
|
|
19
|
+
/** A current subscription, or a canceled subscription retaining paid access. */
|
|
20
20
|
export declare const isLive: (s: Subscription) => boolean;
|
|
21
|
-
/**
|
|
21
|
+
/** Future billing has stopped or cancellation is scheduled. */
|
|
22
22
|
export declare const isEnding: (s: Subscription) => boolean;
|
|
23
23
|
export type StatusTone = "success" | "warning" | "destructive" | "neutral";
|
|
24
24
|
export declare const statusTone: (status: string) => StatusTone;
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { CheckoutAppearance } from '../appearance';
|
|
2
|
-
import { PspConfig } from '../psp';
|
|
3
2
|
import * as React from "react";
|
|
4
3
|
export interface PaymentMethodsPanelProps {
|
|
5
|
-
/**
|
|
6
|
-
* OpenRails's browser PSP configs (checkout config). "Add card" offers
|
|
7
|
-
* each one a card can be saved with in the page.
|
|
8
|
-
*/
|
|
9
|
-
psps?: readonly PspConfig[];
|
|
10
4
|
/** Return target after off-page card verification; see `SavePaymentMethod`. */
|
|
11
5
|
cardSetupReturnURL?: (setupId: string) => string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
6
|
+
/** Offers to make a card the one that collects this currency's invoices. */
|
|
7
|
+
collectionCurrency?: string;
|
|
14
8
|
/** Billing country to preselect when adding a card. */
|
|
15
9
|
defaultCountry?: string;
|
|
16
10
|
appearance?: CheckoutAppearance;
|
|
17
11
|
className?: string;
|
|
18
12
|
}
|
|
19
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The customer's saved cards. "Add card" offers each PSP of the merchant's
|
|
15
|
+
* payment setup (`GET /config`) that saves a card in the page.
|
|
16
|
+
*/
|
|
17
|
+
export declare function PaymentMethodsPanel({ cardSetupReturnURL, collectionCurrency, defaultCountry, appearance, className, }: PaymentMethodsPanelProps): React.JSX.Element;
|
|
@@ -19,7 +19,9 @@ export declare function EmptyState({ title, description, action, }: {
|
|
|
19
19
|
description?: string;
|
|
20
20
|
action?: React.ReactNode;
|
|
21
21
|
}): React.JSX.Element;
|
|
22
|
-
export declare function ErrorState({ error, onRetry, }: {
|
|
22
|
+
export declare function ErrorState({ error, message, onRetry, }: {
|
|
23
23
|
error: BillingError;
|
|
24
|
+
/** Replaces the error's own message. */
|
|
25
|
+
message?: string;
|
|
24
26
|
onRetry: () => void;
|
|
25
27
|
}): React.JSX.Element;
|
package/dist/checkout-page.d.ts
CHANGED
|
@@ -7,10 +7,15 @@ export interface CheckoutPageProps {
|
|
|
7
7
|
baseUrl?: string;
|
|
8
8
|
/** Replaces the client built from `baseUrl`. */
|
|
9
9
|
client?: BillingClient;
|
|
10
|
+
/**
|
|
11
|
+
* Pay as the signed-in customer on this customer surface (its `/me`
|
|
12
|
+
* prefix) instead of with the session id alone; see `checkoutSource`.
|
|
13
|
+
*/
|
|
14
|
+
customerBase?: string;
|
|
10
15
|
/** This host's branding; the framing app chooses the theme. */
|
|
11
16
|
appearance?: CheckoutAppearance;
|
|
12
17
|
layout?: CheckoutLayout;
|
|
13
18
|
defaultCountry?: string;
|
|
14
19
|
className?: string;
|
|
15
20
|
}
|
|
16
|
-
export declare function CheckoutPage({ baseUrl, client, appearance, layout, defaultCountry, className, }: CheckoutPageProps): React.JSX.Element;
|
|
21
|
+
export declare function CheckoutPage({ baseUrl, client, customerBase, appearance, layout, defaultCountry, className, }: CheckoutPageProps): React.JSX.Element;
|