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