@rio-pay/sdk 0.0.0-stage → 0.0.1-alpha.2

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.
@@ -0,0 +1,58 @@
1
+ import type { PaymentOption } from "./payments-options";
2
+ import type { TxOutcome } from "./payments";
3
+ export interface StdAccount {
4
+ address: string;
5
+ publicKey?: Uint8Array;
6
+ chains?: readonly string[];
7
+ }
8
+ export interface StdWallet {
9
+ name: string;
10
+ chains: readonly string[];
11
+ accounts: readonly StdAccount[];
12
+ features: Record<string, unknown>;
13
+ }
14
+ export interface SolanaSession {
15
+ wallet: StdWallet;
16
+ account: StdAccount;
17
+ /** The base58 public key: what the service calls `buyer`. */
18
+ address: string;
19
+ }
20
+ export interface WalletRegistry {
21
+ get(): StdWallet[];
22
+ on(fn: () => void): () => void;
23
+ }
24
+ type EventTargetLike = Pick<EventTarget, "addEventListener" | "dispatchEvent">;
25
+ /**
26
+ * Discovers Wallet Standard wallets. Wallets that loaded first announce themselves on
27
+ * `wallet-standard:register-wallet`; ones that load later wait for our `wallet-standard:app-ready`.
28
+ */
29
+ export declare function createWalletRegistry(target: EventTargetLike): WalletRegistry;
30
+ /** One registry per page. Null on the server. */
31
+ export declare function getWalletRegistry(): WalletRegistry | null;
32
+ /** A wallet that can connect and sign-and-send a Solana transaction. */
33
+ export declare function isSolanaWallet(w: StdWallet): boolean;
34
+ /** Solana-capable wallets; the one the buyer used last (`preferred`) first. */
35
+ export declare function solanaWallets(registry: WalletRegistry | null, preferred?: string): StdWallet[];
36
+ /** Connects `wallet`. `silent` only reuses an existing approval (no popup) and resolves null if there is none. */
37
+ export declare function connectSolana(wallet: StdWallet, opts?: {
38
+ silent?: boolean;
39
+ }): Promise<SolanaSession | null>;
40
+ export declare function base58Encode(bytes: Uint8Array): string;
41
+ export declare function bytesToBase64(bytes: Uint8Array): string;
42
+ export declare function base64ToBytes(b64: string): Uint8Array;
43
+ /** Hands the service-built transaction to the wallet to sign and send; returns the base58 signature. */
44
+ export declare function signAndSendSolana(session: SolanaSession, transactionBase64: string, cluster: string): Promise<string>;
45
+ /** Has the wallet sign, not send, a service-built transaction whose fee the service pays; returns it base64 for the service to send. */
46
+ export declare function signSolana(session: SolanaSession, transactionBase64: string, cluster: string): Promise<string>;
47
+ /** USDC (summed over the buyer's token accounts for the mint) and SOL in base units, through the option's RPC endpoints. */
48
+ export declare function readSolanaBalances(option: PaymentOption, address: string): Promise<{
49
+ usdc: bigint;
50
+ native: bigint | null;
51
+ }>;
52
+ /** Polls until the signature is confirmed. A failed read is retried; `unknown` when it does not show in time or the wait is cancelled. */
53
+ export declare function waitForSolanaSignature(rpcUrls: string[], signature: string, opts?: {
54
+ intervalMs?: number;
55
+ timeoutMs?: number;
56
+ signal?: AbortSignal;
57
+ }): Promise<TxOutcome>;
58
+ export {};
@@ -0,0 +1,26 @@
1
+ export interface SseFrame {
2
+ readonly event: string;
3
+ readonly data: string;
4
+ }
5
+ export interface SseState {
6
+ /** Text received but not yet terminated by a line ending. */
7
+ readonly pending: string;
8
+ readonly event: string;
9
+ /** `data:` lines of the frame being built; empty means no `data` field has been seen. */
10
+ readonly data: readonly string[];
11
+ readonly started: boolean;
12
+ }
13
+ export declare const SSE_START: SseState;
14
+ /**
15
+ * Feeds one decoded chunk to the parser and returns the frames it completed plus the state to carry on with.
16
+ * A frame is delivered only once its blank line has arrived: input that ends mid-frame never yields a partial one.
17
+ */
18
+ export declare function parseSse(state: SseState, chunk: string): {
19
+ state: SseState;
20
+ frames: SseFrame[];
21
+ };
22
+ /**
23
+ * Reads `body` to its end, calling `onFrame` for each complete frame. Resolves when the stream ends or `signal`
24
+ * aborts (the stream is cancelled); rejects if the underlying read fails.
25
+ */
26
+ export declare function readSse(body: ReadableStream<Uint8Array>, onFrame: (f: SseFrame) => void, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,80 @@
1
+ import type { PaymentOption } from "./payments-options";
2
+ import { type AddChainParams, type AuthorizationBody, type TxOutcome, type TypedDataPayload } from "./payments";
3
+ export interface Eip1193Provider {
4
+ request(args: {
5
+ method: string;
6
+ params?: unknown[] | object;
7
+ }): Promise<unknown>;
8
+ }
9
+ /** A short, non-sensitive name for the wallet (remembered as a preference only). */
10
+ export declare function walletName(p: Eip1193Provider): string;
11
+ /** The injected provider; with several wallets installed, prefers the one the buyer used last. */
12
+ export declare function getInjectedProvider(preferred?: string): Eip1193Provider | null;
13
+ /** One installed wallet the buyer can pick. `icon` is a data: image the wallet itself announced, or null. */
14
+ export interface WalletInfo {
15
+ id: string;
16
+ name: string;
17
+ icon: string | null;
18
+ provider: Eip1193Provider;
19
+ }
20
+ /** Validates one `eip6963:announceProvider` event detail. Icons that are not data: images are dropped (never fetched). */
21
+ export declare function parseAnnouncement(detail: unknown): WalletInfo | null;
22
+ /**
23
+ * Lists the wallets the buyer has installed, with no dependency: EIP-6963 announcements first, and the plain
24
+ * `window.ethereum` (or its `providers` array) when nothing announced within `timeoutMs`. `onChange` gets the
25
+ * whole current list every time it changes; the returned function stops listening.
26
+ */
27
+ export declare function watchWallets(onChange: (wallets: WalletInfo[]) => void, opts?: {
28
+ target?: EventTarget;
29
+ legacy?: () => Eip1193Provider[];
30
+ timeoutMs?: number;
31
+ }): () => void;
32
+ /** Asks the wallet to let the buyer pick another account for this site; falls back to a plain connect. */
33
+ export declare function requestAccountSwitch(provider: Eip1193Provider): Promise<string>;
34
+ /** Accounts the site is already authorized for. Never opens a popup. */
35
+ export declare function getAuthorizedAccount(provider: Eip1193Provider): Promise<string | null>;
36
+ export declare function currentChainId(provider: Eip1193Provider): Promise<number | null>;
37
+ /** The buyer's USDC balance in base units, read from the wallet's current chain via eth_call. */
38
+ export declare function readUsdcBalance(provider: Eip1193Provider, usdc: string, account: string): Promise<bigint>;
39
+ /** Native (gas) balance in base units from the wallet's current chain. */
40
+ export declare function readNativeBalance(provider: Eip1193Provider, account: string): Promise<bigint>;
41
+ /**
42
+ * Read-only balances for an EVM option through its public RPC endpoints (no chain switch, no wallet
43
+ * prompt). USDC must be readable; the native balance is best effort (null when it could not be read).
44
+ */
45
+ export declare function readEvmBalancesRpc(option: PaymentOption, account: string): Promise<{
46
+ usdc: bigint;
47
+ native: bigint | null;
48
+ }>;
49
+ export declare function connectWallet(provider: Eip1193Provider): Promise<string>;
50
+ /**
51
+ * Switch to `chainId` (a no-op if already there); if the wallet doesn't know the chain (4902), add it with `addChain`
52
+ * and switch to it (EIP-3085: adding a chain need not select it). Then checks the wallet really is on `chainId`, so no
53
+ * balance is read and nothing is sent on another chain; throws when it is not (shown as a wrong network).
54
+ */
55
+ export declare function ensureChain(provider: Eip1193Provider, chainId: number, addChain?: AddChainParams | null): Promise<void>;
56
+ export declare function signAuthorization(provider: Eip1193Provider, account: string, typedData: TypedDataPayload): Promise<AuthorizationBody>;
57
+ /**
58
+ * Polls for the receipt. A failed read is retried (a flaky node is not a failed payment); `unknown` when none shows
59
+ * within the timeout or the wait is cancelled.
60
+ */
61
+ export declare function waitForReceipt(provider: Eip1193Provider, hash: string, opts?: {
62
+ intervalMs?: number;
63
+ timeoutMs?: number;
64
+ signal?: AbortSignal;
65
+ }): Promise<TxOutcome>;
66
+ /**
67
+ * Sends each tx in order, waiting for each one's receipt before the next (an approval must land before the payment
68
+ * that spends it), and returns the last tx's hash as soon as the wallet hands it back. From then on the payment may
69
+ * land whatever a node says next, so its receipt is the caller's to follow, never a reason to send it again.
70
+ */
71
+ export declare function sendTransactions(provider: Eip1193Provider, from: string, txs: {
72
+ description: string;
73
+ to: string;
74
+ data: string;
75
+ value: string;
76
+ }[], hooks?: {
77
+ onStep?: (index: number, description: string) => void;
78
+ signal?: AbortSignal;
79
+ intervalMs?: number;
80
+ }): Promise<string>;
@@ -0,0 +1,348 @@
1
+ export type ChargeStatus = "created" | "payment_pending" | "funded" | "settled" | "underpaid" | "expired";
2
+ export type Confirmation = "pending" | "provisional" | "final";
3
+ export interface AddChainParams {
4
+ chainId: string;
5
+ chainName: string;
6
+ nativeCurrency: {
7
+ name: string;
8
+ symbol: string;
9
+ decimals: number;
10
+ };
11
+ rpcUrls: string[];
12
+ blockExplorerUrls?: string[];
13
+ }
14
+ /** The fields the widget reads from the service's Charge object (see toChargeView). */
15
+ export interface ChargeView {
16
+ status: ChargeStatus;
17
+ confirmed: boolean;
18
+ confirmation: Confirmation;
19
+ funded_amount?: string | null;
20
+ }
21
+ /** The created charge. Your HTTP create route returns exactly this object as its JSON body; the widget validates it (`parseCreatedCharge`). */
22
+ export interface CreatedCharge {
23
+ chargeId: string;
24
+ clientSecret: string;
25
+ serviceUrl: string;
26
+ amount: string;
27
+ chain: {
28
+ id: number;
29
+ name: string;
30
+ usdc: string;
31
+ add_chain: AddChainParams | null;
32
+ };
33
+ chargeAddress: string;
34
+ status: ChargeStatus;
35
+ confirmed: boolean;
36
+ confirmation: Confirmation;
37
+ expiresAt: string | null;
38
+ }
39
+ /**
40
+ * What the `createCharge` callback resolves to (not an HTTP body): `postCreateCharge` or your own code builds it from your route's
41
+ * response, where success is the flat `CreatedCharge` JSON and failure is an error status with
42
+ * `{ error, code? }`. `status` is the HTTP status to surface (409 = this order already has a charge, or the
43
+ * amount differs from the existing one). `error` and `code` are for your `onError` handler only: the buyer
44
+ * never sees them.
45
+ */
46
+ export type ChargeCreateResult = {
47
+ ok: true;
48
+ charge: CreatedCharge;
49
+ } | {
50
+ ok: false;
51
+ status: number;
52
+ error: string;
53
+ code?: string;
54
+ };
55
+ /** Host route response for the status poll. Authoritative for the host. */
56
+ export interface PlatformChargeStatus {
57
+ chargeId: string;
58
+ orderId: string | null;
59
+ status: ChargeStatus;
60
+ confirmed: boolean;
61
+ confirmation: Confirmation;
62
+ collectedAmount: string | null;
63
+ tags?: string[];
64
+ }
65
+ export interface TypedDataPayload {
66
+ domain: Record<string, unknown>;
67
+ types: Record<string, {
68
+ name: string;
69
+ type: string;
70
+ }[]>;
71
+ primaryType: string;
72
+ message: Record<string, unknown>;
73
+ }
74
+ export declare function formatUsdc(base: string | bigint): string;
75
+ export declare function shortAddress(a: string): string;
76
+ export declare function toHexChainId(id: number): string;
77
+ export declare function toHexValue(value: string | undefined): string;
78
+ /**
79
+ * eth_signTypedData_v4 takes a JSON string whose `types` includes EIP712Domain.
80
+ * The vendor omits it, so derive it from the domain's own fields. Integer
81
+ * message values stay as decimal strings, which wallets accept.
82
+ */
83
+ export declare function buildSignTypedDataJson(td: TypedDataPayload): string;
84
+ /**
85
+ * What a sent transaction came to, as far as the wallet's (or a public) node can tell. `unknown` is not a failure: the
86
+ * transaction may still land, so a payment in that state is never offered to the buyer to send again.
87
+ */
88
+ export type TxOutcome = "confirmed" | "failed" | "unknown";
89
+ export interface AuthorizationBody {
90
+ from: string;
91
+ value: string;
92
+ validAfter: string;
93
+ validBefore: string;
94
+ nonce: string;
95
+ signature: string;
96
+ }
97
+ export declare function buildAuthorizationBody(td: TypedDataPayload, fallbackFrom: string, signature: string): AuthorizationBody;
98
+ /** Reads `confirmed` / `confirmation` from a service or host object. */
99
+ export declare function normalizeConfirmation(raw: Record<string, unknown>): {
100
+ confirmed: boolean;
101
+ confirmation: Confirmation;
102
+ };
103
+ /** Narrows a service Charge JSON (GET /v1/charges/:id or an SSE frame) to what the widget reads. */
104
+ export declare function toChargeView(raw: unknown): ChargeView | null;
105
+ /** Validates the host's create-charge response; null if any load-bearing field is missing or the service URL is unsafe. */
106
+ export declare function parseCreatedCharge(raw: unknown): CreatedCharge | null;
107
+ export declare function parsePlatformStatus(raw: unknown): PlatformChargeStatus | null;
108
+ /** URL the widget polls for the host's authoritative status, or null when the host disabled polling (`statusUrl={null}`). */
109
+ export declare function platformStatusUrl(chargeId: string, statusUrl: ((chargeId: string) => string) | null): string | null;
110
+ /**
111
+ * Resolve a caller-supplied return URL against `origin` and keep it only if it
112
+ * stays on that origin (http/https). The widget uses it before rendering a link;
113
+ * a host route can use it before forwarding a return URL to the service.
114
+ */
115
+ export declare function sameOriginReturnUrl(returnUrl: unknown, origin: string): string | null;
116
+ /**
117
+ * Every user-facing string. `{name}` placeholders are filled by `fill`. Override any subset via the `copy` prop.
118
+ * Network names are never copy: they come from the service as data and appear only on the token chip.
119
+ */
120
+ export interface CheckoutCopy {
121
+ trigger: string;
122
+ title: string;
123
+ /** Header when the host names the merchant: `{merchant}` is filled in. */
124
+ titleFor: string;
125
+ close: string;
126
+ done: string;
127
+ amountDue: string;
128
+ payWith: string;
129
+ chooseUsdc: string;
130
+ connectWallet: string;
131
+ connectingWallet: string;
132
+ pay: string;
133
+ opening: string;
134
+ confirm: string;
135
+ confirmTx: string;
136
+ confirming: string;
137
+ paid: string;
138
+ preparing: string;
139
+ walletName: string;
140
+ balanceLabel: string;
141
+ gasLabel: string;
142
+ lowGas: string;
143
+ notEnough: string;
144
+ checking: string;
145
+ noUsdc: string;
146
+ insufficientAll: string;
147
+ checkAgain: string;
148
+ payAnotherWay: string;
149
+ back: string;
150
+ tryAgain: string;
151
+ startNew: string;
152
+ details: string;
153
+ rejected: string;
154
+ rejectedConnect: string;
155
+ rejectedPick: string;
156
+ noWallet: string;
157
+ noWalletMobile: string;
158
+ noWalletForOption: string;
159
+ openInWalletApp: string;
160
+ wrongNetwork: string;
161
+ insufficient: string;
162
+ noGas: string;
163
+ walletBusy: string;
164
+ txFailed: string;
165
+ network: string;
166
+ service: string;
167
+ unknown: string;
168
+ expired: string;
169
+ createFailed: string;
170
+ /** The order changed after its charge was made (a 409 from your route). */
171
+ conflict: string;
172
+ slow: string;
173
+ /** Final confirmation headline. */
174
+ received: string;
175
+ /** Headline while the payment is accepted but final settlement is still pending. */
176
+ receivedProvisional: string;
177
+ finalizing: string;
178
+ receiptLabel: string;
179
+ returnToStore: string;
180
+ stagesLabel: string;
181
+ stageWallet: string;
182
+ stageSent: string;
183
+ stageConfirmed: string;
184
+ /** Shown with the address: `{amount}` is filled in; covers an exchange deducting its fee. */
185
+ manualIntro: string;
186
+ /** Above the address: `{network}` is the network to choose when withdrawing (its chip's name, service data). */
187
+ manualWarning: string;
188
+ /** The checkbox that reveals the address: `{network}` as in `manualWarning`. */
189
+ manualConfirm: string;
190
+ manualWaiting: string;
191
+ /** For a buyer whose exchange cannot send on the network: `{network}` as in `manualWarning`. Withdraw to their own wallet and pay with it here. */
192
+ manualOwnWallet: string;
193
+ copyAddress: string;
194
+ copied: string;
195
+ openInWallet: string;
196
+ partial: string;
197
+ confirmNamed: string;
198
+ confirmTxNamed: string;
199
+ openingNamed: string;
200
+ sending: string;
201
+ notEnoughUsdc: string;
202
+ needsGas: string;
203
+ needsGasReason: string;
204
+ needsOtherWallet: string;
205
+ couldntCheck: string;
206
+ balanceUpdated: string;
207
+ walletMenu: string;
208
+ copyWalletAddress: string;
209
+ switchWallet: string;
210
+ disconnect: string;
211
+ chooseWallet: string;
212
+ lastUsed: string;
213
+ closeConfirm: string;
214
+ closeAnyway: string;
215
+ stay: string;
216
+ pauseBalances: string;
217
+ openInPhantom: string;
218
+ /** Decorator label for another token: `{symbol}` is filled in. */
219
+ balanceLabelFor: string;
220
+ /** What the buyer pays in their own token: `{amount}` is "0.0004 ETH". */
221
+ youPay: string;
222
+ youPayChecking: string;
223
+ youPayUpdating: string;
224
+ /** `{minutes}` is filled in. */
225
+ youPayTakes: string;
226
+ optionUnavailable: string;
227
+ optionUnavailableReason: string;
228
+ /** Button text when the wallet holds too little of the chosen token: `{symbol}` is filled in. */
229
+ notEnoughToken: string;
230
+ priceChanged: string;
231
+ }
232
+ export declare const DEFAULT_COPY: CheckoutCopy;
233
+ export declare function fill(template: string, vars?: Record<string, string | number>): string;
234
+ export declare function resolveCopy(overrides?: Partial<CheckoutCopy>): CheckoutCopy;
235
+ /** "$0.05", "$12.50", "$1,234.56": USDC base units shown as dollars. */
236
+ export declare function formatUsd(base: string | bigint): string;
237
+ /** "base-sepolia" -> "Base Sepolia". Fallback label when the service sends no display name. */
238
+ export declare function prettyChainName(name: string): string;
239
+ /** "ch_01HXABCDEF12" -> "#ABCDEF12": short, human-quotable; the full id stays in Details. */
240
+ export declare function shortReceiptId(chargeId: string): string;
241
+ export declare function isAddress(a: unknown): a is string;
242
+ /** Deep link that reopens `href` inside the MetaMask mobile browser; null for non-http(s) hrefs. */
243
+ export declare function walletDeepLink(href: string): string | null;
244
+ /**
245
+ * Phantom's documented "browse" deeplink (https://docs.phantom.com/phantom-deeplinks/other-methods/browse):
246
+ * `https://phantom.com/ul/browse/<url>?ref=<ref>`, both URL-encoded; `ref` is the requesting app's URL.
247
+ */
248
+ export declare function phantomBrowseLink(href: string): string | null;
249
+ export declare function isMobileUserAgent(ua: string): boolean;
250
+ /** `balanceOf(address)` calldata. */
251
+ export declare function encodeBalanceOf(account: string): string;
252
+ export declare function parseHexUint(hex: unknown): bigint;
253
+ export declare function amountRemaining(amount: string, funded: string | null | undefined): bigint;
254
+ export type ProblemKind = "rejected" | "no_wallet" | "wrong_network" | "wallet_busy" | "insufficient" | "no_gas" | "tx_failed" | "expired" | "conflict" | "network" | "service" | "unknown";
255
+ export interface Problem {
256
+ kind: ProblemKind;
257
+ message: string;
258
+ /** Controlled technical detail for support, shown collapsed: only an HTTP status and an allowlisted code. */
259
+ detail?: string;
260
+ /** Untrusted text from a host, the service or a wallet. Never rendered; reaches the host through `onError` only. */
261
+ cause?: string;
262
+ /** Present for `insufficient`. */
263
+ balance?: string;
264
+ needed?: string;
265
+ }
266
+ /** Thrown by the payment flow for a failure that already knows its buyer-facing problem. */
267
+ export declare class PaymentProblem extends Error {
268
+ problem: Problem;
269
+ constructor(problem: Problem);
270
+ }
271
+ export interface ErrorContext {
272
+ /** The pay step that was running when the error happened. */
273
+ step?: PayStep;
274
+ copy?: CheckoutCopy;
275
+ }
276
+ /** The only fields a buyer may see under "Show details": an HTTP status and a code from a fixed list, never free text. */
277
+ export declare function safeDetail(parts: {
278
+ status?: unknown;
279
+ code?: unknown;
280
+ }): string | undefined;
281
+ /** Maps a failed create-charge result to controlled copy by status and code; the host's `error` text goes to `cause` only. */
282
+ export declare function mapCreateFailure(r: {
283
+ status: number;
284
+ error?: string;
285
+ code?: string;
286
+ }, copy?: CheckoutCopy): Problem;
287
+ /** Turns any wallet/provider/service error into one controlled message plus a collapsed detail of controlled fields; the raw text goes to `cause` (host only). */
288
+ export declare function mapError(e: unknown, ctx?: ErrorContext): Problem;
289
+ /** True when a raw error means the charge/authorization can no longer be used and a fresh one should be made. */
290
+ export declare function isExpiredError(e: unknown): boolean;
291
+ /** True when the service refused because another payment of this charge is already on its way (this tab's earlier try, or another tab's). */
292
+ export declare function isPaymentInFlight(e: unknown): boolean;
293
+ export type PayStep = "idle" | "connecting" | "switching" | "checking" | "signing" | "submitting" | "processing" | "paid";
294
+ export interface PayState {
295
+ step: PayStep;
296
+ problem: Problem | null;
297
+ }
298
+ export type PayEvent = {
299
+ type: "start";
300
+ } | {
301
+ type: "step";
302
+ step: Exclude<PayStep, "idle" | "processing" | "paid">;
303
+ } | {
304
+ type: "submitted";
305
+ } | {
306
+ type: "failed";
307
+ problem: Problem;
308
+ } | {
309
+ type: "paid";
310
+ } | {
311
+ type: "reset";
312
+ };
313
+ export declare const INITIAL_PAY_STATE: PayState;
314
+ export declare function isBusy(step: PayStep): boolean;
315
+ /** Steps only move forward while a payment is in flight; a failure always returns to idle with the problem. */
316
+ export declare function payReducer(state: PayState, ev: PayEvent): PayState;
317
+ export type StageState = "done" | "active" | "pending";
318
+ export interface Stage {
319
+ key: "wallet" | "sent" | "confirmed";
320
+ state: StageState;
321
+ }
322
+ /**
323
+ * The three buyer-visible stages for a pay step: 1 wallet signature, 2 on-chain receipt,
324
+ * 3 confirmation from the host's status route. Preparation (connect, switch, balance check) all sit under stage 1.
325
+ */
326
+ export declare function deriveStages(step: PayStep): Stage[];
327
+ /**
328
+ * Text for the exact step, used by the live region, the busy button and the store-page trigger.
329
+ * `wallet` is the connected wallet's name when it is known ("your wallet" is the fallback).
330
+ */
331
+ export declare function primaryLabel(step: PayStep, amountText: string, copy?: CheckoutCopy, wallet?: string | null): string;
332
+ /** "Confirm in MetaMask (1 of 2)" / "Confirm in your wallet (1 of 2)". */
333
+ export declare function confirmTxLabel(copy: CheckoutCopy, tx: {
334
+ i: number;
335
+ total: number;
336
+ }, wallet?: string | null): string;
337
+ /** Header text: "Pay" or "Pay {merchant}" when the host names the store. */
338
+ export declare function headerTitle(copy: CheckoutCopy, merchantName?: string | null): string;
339
+ /** Coarse state surfaced through `onStatusChange`. */
340
+ export type CheckoutState = "loading" | "ready" | "connecting" | "confirming" | "processing" | "paid" | "settled" | "problem" | "failed";
341
+ export interface Prefs {
342
+ wallet?: string;
343
+ }
344
+ type StorageLike = Pick<Storage, "getItem" | "setItem">;
345
+ /** Never throws: storage may be blocked. Only a wallet name is ever stored. */
346
+ export declare function loadPrefs(storage?: StorageLike | null): Prefs;
347
+ export declare function savePrefs(patch: Prefs, storage?: StorageLike | null): void;
348
+ export {};
@@ -0,0 +1,48 @@
1
+ import type { CreatedCharge, PlatformChargeStatus } from "./payments";
2
+ export interface RioClientOptions {
3
+ /** A secret API key (`sk_…`), from the dashboard's Developers page. Keep it on your server. */
4
+ apiKey: string;
5
+ /** The API base URL from the dashboard. Default: the `RIO_SERVICE_URL` environment variable, where there is one. */
6
+ serviceUrl?: string;
7
+ /** A custom `fetch`. Default: the global `fetch`, looked up at call time. */
8
+ fetch?: typeof fetch;
9
+ }
10
+ export interface CreateChargeInput {
11
+ /** USDC base units, as a string: `"12500000"` is $12.50. Decide it on your server, never from the browser. */
12
+ amount: string;
13
+ /** Your order id. */
14
+ externalRef: string;
15
+ /** Your storefront's web origin. A browser can only use the charge from there. Leave it out for a charge only a server handles. */
16
+ origin?: string;
17
+ merchantId?: string;
18
+ returnUrl?: string;
19
+ expiresInSeconds?: number;
20
+ requireConfirmation?: "provisional" | "final";
21
+ /** Settlement speed: `"standard"` (the default, no fee) or `"fast"`. */
22
+ speed?: "standard" | "fast";
23
+ /** Labels for filtering and reporting (at most 10, lowercase `[a-z0-9][a-z0-9:_./-]{0,63}`). */
24
+ tags?: readonly string[];
25
+ /** Private values to keep with the charge (strings, numbers or booleans, never nested). Returned to you, never to the buyer. */
26
+ metadata?: Readonly<Record<string, string | number | boolean>>;
27
+ }
28
+ export interface CreateChargeOptions {
29
+ /** Default: the order id, so a retried request returns the same charge instead of making another. */
30
+ idempotencyKey?: string;
31
+ }
32
+ /** Why a call failed. `status` is the HTTP status, or 0 when the service could not be reached. */
33
+ export declare class RioError extends Error {
34
+ readonly status: number;
35
+ readonly code: string;
36
+ constructor(status: number, code: string, message: string);
37
+ }
38
+ /** A client for your server's side of the API. Make one with `createRioClient`. */
39
+ export interface RioClient {
40
+ readonly charges: {
41
+ /** Creates the order's charge and returns it as the checkout widget takes it: your route can `Response.json(charge)`. A retry of the same order returns the same charge with a replacement client secret. */
42
+ readonly create: (input: CreateChargeInput, options?: CreateChargeOptions) => Promise<CreatedCharge>;
43
+ /** A charge's status, in the shape the widget's status poll takes: your status route can `Response.json(status)`. */
44
+ readonly retrieve: (id: string) => Promise<PlatformChargeStatus>;
45
+ };
46
+ }
47
+ /** Makes a client: `createRioClient({ apiKey })`. Throws a `TypeError` for a missing key or an unusable service URL. */
48
+ export declare function createRioClient(options: RioClientOptions): RioClient;
@@ -0,0 +1,32 @@
1
+ export declare const SIGNATURE_HEADER = "Payments-Signature";
2
+ /** How far a signed timestamp may be from the verifier's clock, either way. */
3
+ export declare const DEFAULT_TOLERANCE_SECONDS = 300;
4
+ export type SignWebhookInput = {
5
+ readonly secret: string;
6
+ /** Unix seconds. */
7
+ readonly timestamp: number;
8
+ /** The exact body string that will be sent. */
9
+ readonly body: string;
10
+ };
11
+ /** Build a header value the way the service does. Useful in your own tests; the service signs real events. */
12
+ export declare function signWebhook({ secret, timestamp, body }: SignWebhookInput): Promise<string>;
13
+ export type VerifyWebhookInput = {
14
+ /** The endpoint's signing secret (`whsec_...`). */
15
+ readonly secret: string;
16
+ /** The `Payments-Signature` header value. */
17
+ readonly header: string;
18
+ /** The RAW request body, exactly as received. Not `JSON.stringify(JSON.parse(raw))`: re-serializing changes the bytes. */
19
+ readonly body: string;
20
+ /** Default 300. */
21
+ readonly toleranceSeconds?: number;
22
+ /** The verifier's clock in ms since the epoch; default `Date.now()`. For tests. */
23
+ readonly now?: number;
24
+ };
25
+ /**
26
+ * True when the header is well formed, its timestamp is within tolerance of `now`, and at least one `v1` value is
27
+ * the HMAC of `"<t>.<body>"` under `secret` (several `v1` values appear during a secret rotation). Never throws on
28
+ * bad input: a malformed or forged header is just `false`. The comparison is constant time (`crypto.subtle.verify`).
29
+ *
30
+ * Delivery is at-least-once: also dedupe on the event `id`.
31
+ */
32
+ export declare function verifyWebhook({ secret, header, body, toleranceSeconds, now, }: VerifyWebhookInput): Promise<boolean>;
@@ -0,0 +1,2 @@
1
+ export { DEFAULT_TOLERANCE_SECONDS, SIGNATURE_HEADER, signWebhook, verifyWebhook } from "./lib/webhook";
2
+ export type { SignWebhookInput, VerifyWebhookInput } from "./lib/webhook";
package/dist/server.js ADDED
@@ -0,0 +1 @@
1
+ var f="Payments-Signature",p=300,i=new TextEncoder,u=(t)=>crypto.subtle.importKey("raw",i.encode(t),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"]),m=(t)=>[...new Uint8Array(t)].map((r)=>r.toString(16).padStart(2,"0")).join("");function d(t){if(!/^[0-9a-fA-F]{64}$/.test(t))return null;let r=new Uint8Array(new ArrayBuffer(32));return r.set(t.match(/../g).map((n)=>parseInt(n,16))),r}function g(t){let r=t.split(",").map((e)=>{let s=e.indexOf("=");return s<0?null:[e.slice(0,s).trim(),e.slice(s+1).trim()]}),n=r.filter((e)=>e?.[0]==="t");if(n.length!==1||!/^\d+$/.test(n[0][1]))return null;let o=Number(n[0][1]);if(!Number.isSafeInteger(o))return null;let a=r.flatMap((e)=>e?.[0]==="v1"&&e[1]?[e[1]]:[]);return a.length?{timestamp:o,signatures:a}:null}async function b({secret:t,timestamp:r,body:n}){let o=await crypto.subtle.sign("HMAC",await u(t),i.encode(`${r}.${n}`));return`t=${r},v1=${m(o)}`}async function A({secret:t,header:r,body:n,toleranceSeconds:o=300,now:a=Date.now()}){if(!t)return!1;let e=g(r);if(!e)return!1;if(Math.abs(a/1000-e.timestamp)>o)return!1;let s=await u(t),c=i.encode(`${e.timestamp}.${n}`);return(await Promise.all(e.signatures.map((y)=>{let l=d(y);return l?crypto.subtle.verify("HMAC",s,l,c):!1}))).some(Boolean)}export{p as DEFAULT_TOLERANCE_SECONDS,f as SIGNATURE_HEADER,b as signWebhook,A as verifyWebhook};