@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.
- package/CHANGELOG.md +87 -9
- package/README.md +78 -83
- package/dist/account/account-billing.d.ts +1 -1
- package/dist/account/payment-methods-panel.d.ts +2 -2
- package/dist/authenticate.d.ts +5 -4
- package/dist/buy-button.d.ts +17 -10
- package/dist/client/client.d.ts +52 -23
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/routes.d.ts +24 -218
- package/dist/client/generated/wire.d.ts +39 -200
- package/dist/client/index.d.ts +2 -1
- package/dist/client/token-source.d.ts +60 -0
- package/dist/client/types.d.ts +108 -199
- package/dist/client-Bi_lP_M0.js +932 -0
- package/dist/client-Bi_lP_M0.js.map +1 -0
- package/dist/client.js +2 -2
- package/dist/components/eyebrow.d.ts +5 -0
- package/dist/embedded-checkout.d.ts +22 -0
- package/dist/{hooks-2d-XjpuI.js → hooks-Bk80rNv8.js} +99 -90
- package/dist/hooks-Bk80rNv8.js.map +1 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +1149 -2163
- package/dist/index.js.map +1 -1
- package/dist/modal.d.ts +3 -9
- package/dist/offers.d.ts +10 -9
- package/dist/psp.d.ts +0 -8
- package/dist/react/config.d.ts +17 -12
- package/dist/react/context.d.ts +3 -1
- package/dist/react/hooks.d.ts +5 -5
- package/dist/react/index.d.ts +1 -1
- package/dist/react.js +2 -2
- package/dist/styles.css +1 -1
- package/dist/tokenized-card-form.d.ts +5 -1
- package/dist/types.d.ts +12 -250
- package/docs/payment-form-contract.md +31 -74
- package/package.json +1 -2
- package/dist/checkout-frame.d.ts +0 -17
- package/dist/checkout-page.d.ts +0 -21
- package/dist/checkout.d.ts +0 -22
- package/dist/client/checkout.d.ts +0 -28
- package/dist/client-BuvolBPT.js +0 -1092
- package/dist/client-BuvolBPT.js.map +0 -1
- package/dist/components/ccbill-fields.d.ts +0 -8
- package/dist/components/methods.d.ts +0 -13
- package/dist/components/summary.d.ts +0 -12
- package/dist/fixtures.d.ts +0 -9
- package/dist/frame.d.ts +0 -47
- package/dist/hooks-2d-XjpuI.js.map +0 -1
- package/dist/lib/rail-meta.d.ts +0 -15
- package/dist/lib/redirect.d.ts +0 -2
- 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>`
|
|
14
|
-
`<
|
|
15
|
-
|
|
16
|
-
never enters the host application
|
|
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
|
|
20
|
-
|
|
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").
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
`
|
|
77
|
-
must be on one of your app's return origins.
|
|
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
|
|
102
|
-
https page, opened in the top window)
|
|
103
|
-
as a QR code)
|
|
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
|
-
###
|
|
126
|
+
### An order your server creates
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
135
|
-
|
|
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'
|
|
169
|
-
|
|
170
|
-
saves a card in the page, and says
|
|
171
|
-
card PSPs are temporarily unavailable;
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
179
|
-
|
|
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`, `
|
|
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
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
-
|
|
223
|
-
sale (Solana when it is configured, never CCBill),
|
|
224
|
-
each with
|
|
225
|
-
cards on them, newest first
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
- `
|
|
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
|
|
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
|
|
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;
|
package/dist/authenticate.d.ts
CHANGED
|
@@ -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
|
|
5
|
-
* waits on
|
|
6
|
-
*
|
|
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
|
|
9
|
+
export declare function authenticateChange(client: BillingClient, subscriptionId: string, change: SubscriptionChange, psp: PspConfig): Promise<SubscriptionChange>;
|
package/dist/buy-button.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { CheckoutAppearance } from './appearance';
|
|
2
|
-
import {
|
|
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
|
|
15
|
+
* price with `customer_amount`; the order charges exactly this.
|
|
14
16
|
*/
|
|
15
17
|
amount?: string;
|
|
16
|
-
/**
|
|
17
|
-
|
|
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
|
|
22
|
-
*
|
|
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: (
|
|
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;
|
package/dist/client/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BillingError } from './errors';
|
|
2
|
-
import { Account,
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
199
|
-
*
|
|
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
|
-
*
|
|
204
|
-
*
|
|
205
|
-
|
|
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
|
-
*
|
|
210
|
-
*
|
|
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
|
-
|
|
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
|
};
|