openpay-x402-sdk 0.4.0 → 0.5.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 +19 -0
- package/README.md +70 -19
- package/index.d.ts +113 -0
- package/package.json +1 -1
- package/src/catalog.mjs +27 -3
- package/src/client.mjs +16 -1
- package/src/executor.mjs +399 -57
- package/src/gate.mjs +151 -6
- package/src/guards.mjs +109 -24
- package/src/index.mjs +2 -0
- package/src/network.mjs +254 -0
- package/src/payment.mjs +20 -1
- package/src/receipt.mjs +201 -0
- package/src/spendStore.mjs +251 -32
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
- Reserve session and daily capacity immediately before exposing a signed
|
|
6
|
+
authorization. Non-2xx responses, timeouts, and connection failures retain
|
|
7
|
+
the reservation; successful 2xx responses keep the existing confirmed-spend
|
|
8
|
+
accounting.
|
|
9
|
+
- Make the file daily store cross-process atomic with an exclusive lock, reject
|
|
10
|
+
UTC-crossing authorizations, and fail closed when a configured store is
|
|
11
|
+
unavailable.
|
|
12
|
+
- Enforce host/catalog admission before target I/O, block private and rebinding
|
|
13
|
+
destinations, require exact catalog URLs, stop redirects, and bound buyer
|
|
14
|
+
requests with a timeout.
|
|
15
|
+
- Bind supported networks to the canonical JPYC v3 contract/domain, cap
|
|
16
|
+
seller-declared authorization lifetimes, bind signature destinations to a
|
|
17
|
+
known or catalog-reviewed forwarder, and locally reserve seller-gate
|
|
18
|
+
authorizations across verify and settle without requiring facilitator tokens.
|
|
19
|
+
- Verify facilitator-signed payment receipts against the advertised signer and
|
|
20
|
+
bind every money field and authorization nonce before returning them.
|
|
21
|
+
|
|
3
22
|
## 0.4.0
|
|
4
23
|
|
|
5
24
|
- Add an opt-in persistent daily buyer limit with UTC signer/date keys, file and
|
package/README.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
Node.js 20+ SDK for discovering, quoting, and buying OpenPay x402 resources priced
|
|
4
4
|
in JPYC. It ships as plain ESM and has no build step.
|
|
5
5
|
|
|
6
|
+
Wire compatibility: x402 v1 transport (JSON 402 body with `x402Version: 1`, plus the
|
|
7
|
+
`X-PAYMENT` / `X-PAYMENT-RESPONSE` headers) with the OpenPay `extra.openpay`
|
|
8
|
+
forwarder-split extension. OpenPay's first-party resources also accept the v2 header
|
|
9
|
+
transport; this SDK speaks v1.
|
|
10
|
+
|
|
6
11
|
## Quick start
|
|
7
12
|
|
|
8
13
|
```bash
|
|
@@ -17,6 +22,7 @@ const client = createOpenPayClient({
|
|
|
17
22
|
maxPerCallJpyc: '10',
|
|
18
23
|
maxSessionJpyc: '100',
|
|
19
24
|
maxDailyJpyc: '250',
|
|
25
|
+
maxTimeoutSeconds: 600,
|
|
20
26
|
allowedHosts: 'open-pay.jp',
|
|
21
27
|
});
|
|
22
28
|
|
|
@@ -35,6 +41,12 @@ concurrent calls so every call sees the latest session total.
|
|
|
35
41
|
|
|
36
42
|
## Sell with the SDK
|
|
37
43
|
|
|
44
|
+
**An AI model is never the security boundary for payment state.** Do not unlock on any
|
|
45
|
+
model- or tool-produced claim of payment — a "paid" string, a success message, or a
|
|
46
|
+
transaction id appearing in text. The gate unlocks only on the facilitator's `verify` and
|
|
47
|
+
`settle` responses, which are backed by on-chain settlement; replayed or already-used
|
|
48
|
+
authorizations are refused at that layer.
|
|
49
|
+
|
|
38
50
|
Create a gate with the exact resource URL registered in OpenPay discovery. For
|
|
39
51
|
inexpensive content, `handle()` verifies and settles the payment in one call:
|
|
40
52
|
|
|
@@ -43,6 +55,7 @@ import { createJpycGate } from 'openpay-x402-sdk';
|
|
|
43
55
|
|
|
44
56
|
const gate = createJpycGate({
|
|
45
57
|
resourceUrl: process.env.MY_RESOURCE_URL,
|
|
58
|
+
maxUpstreamSeconds: 60,
|
|
46
59
|
});
|
|
47
60
|
|
|
48
61
|
export async function GET(request) {
|
|
@@ -91,6 +104,20 @@ minutes. Until `resourceUrl` is listed with a non-empty `accepts`, `handle()` an
|
|
|
91
104
|
`verify()` throw; map that bootstrap condition to an HTTP 500 response. Pass
|
|
92
105
|
`openpayOrigin` to use an origin other than `https://open-pay.jp`.
|
|
93
106
|
|
|
107
|
+
Before `verify()` contacts the facilitator, each gate instance claims a canonical
|
|
108
|
+
authorization identity until its `validBefore` time. Set `maxUpstreamSeconds`
|
|
109
|
+
(default `60`) to the seller's worst-case upstream duration; the gate also
|
|
110
|
+
retains `settlementGraceSeconds` (default `30`) and rejects a duplicate before
|
|
111
|
+
upstream work begins. A failed facilitator verification releases the tentative
|
|
112
|
+
claim, while a successful verification keeps it through settlement so an
|
|
113
|
+
unknown settlement result cannot expose the same authorization twice.
|
|
114
|
+
|
|
115
|
+
The facilitator reservation is an optional additional defense. The gate forwards
|
|
116
|
+
its token when one is returned and continues on the established token-less wire
|
|
117
|
+
when it is not. The local claim covers concurrent requests sharing one gate
|
|
118
|
+
instance; separate processes or serverless isolates do not share its in-memory
|
|
119
|
+
ledger.
|
|
120
|
+
|
|
94
121
|
The copy-paste paywall snippet generated by OpenPay provides the same one-shot
|
|
95
122
|
gate; `createJpycGate` is its importable SDK counterpart with split settlement.
|
|
96
123
|
|
|
@@ -99,37 +126,61 @@ gate; `createJpycGate` is its importable SDK counterpart with split settlement.
|
|
|
99
126
|
| Option | Default | Guard |
|
|
100
127
|
|---|---:|---|
|
|
101
128
|
| `maxPerCallJpyc` | `10` | Upper bound for the caller-provided `maxTotalJpyc`. |
|
|
102
|
-
| `maxSessionJpyc` | `100` | Cumulative cap for successful payments
|
|
129
|
+
| `maxSessionJpyc` | `100` | Cumulative cap for successful payments plus authorizations exposed to a seller. |
|
|
103
130
|
| `maxDailyJpyc` | Not set | Persistent cumulative cap per signer and UTC calendar date. |
|
|
131
|
+
| `maxTimeoutSeconds` | `600` | Reject seller-declared authorization lifetimes above this many seconds (maximum configurable value: `1200`). |
|
|
104
132
|
| `allowedHosts` | `open-pay.jp` | Comma-separated bare host allowlist. |
|
|
105
|
-
| `catalogTrust` | `true` | Also allows catalog URLs after the live challenge matches the catalog challenge. |
|
|
133
|
+
| `catalogTrust` | `true` | Also allows exact catalog URLs after the live challenge matches the catalog challenge. |
|
|
106
134
|
| `discoveryUrl` | `https://open-pay.jp/api/discovery` | Catalog and OpenPay origin used by the client. |
|
|
107
135
|
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
Catalog admission is exact URL only, including the query string. A query
|
|
137
|
+
variant must have its own reviewed listing or an explicitly allowlisted host.
|
|
110
138
|
|
|
111
139
|
`pay(url, { maxTotalJpyc })` always requires `maxTotalJpyc`. It is the maximum
|
|
112
140
|
total—including the resource price and x402 fee—that this individual call is
|
|
113
141
|
authorized to pay. It does not disable or raise `maxPerCallJpyc` or
|
|
114
142
|
`maxSessionJpyc`; every configured limit must allow the payment.
|
|
115
143
|
|
|
116
|
-
`maxDailyJpyc` is opt-in. When set, the client
|
|
117
|
-
`~/.openpay-x402/spend.json
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
`maxDailyJpyc` is opt-in. When set, the client atomically reserves the amount in
|
|
145
|
+
`~/.openpay-x402/spend.json` immediately before sending a signed authorization,
|
|
146
|
+
keyed by the lower-cased signer address and UTC date. A non-2xx response,
|
|
147
|
+
connection loss, or timeout does not release that reservation: the seller may
|
|
148
|
+
already have settled it. Authorizations that could remain valid across UTC
|
|
149
|
+
midnight are refused instead of being charged to the wrong day.
|
|
150
|
+
|
|
151
|
+
A missing entry starts at zero. A corrupt/unreadable store, a failed reservation,
|
|
152
|
+
or a custom store returning `null` rejects payments with
|
|
153
|
+
`daily_spend_unavailable` (fail-closed). The built-in file store uses an
|
|
154
|
+
exclusive lock plus atomic replacement, so separate processes sharing a signer
|
|
155
|
+
cannot both reserve the same remaining daily capacity. Custom stores can add an
|
|
156
|
+
atomic `reserve(key, amount, limit, reservation)` method; legacy
|
|
157
|
+
`{ load, save }` stores remain accepted with a verified pre-send write.
|
|
158
|
+
`MAX_DAILY_JPYC` is the equivalent optional setting for the exported environment
|
|
159
|
+
config readers.
|
|
160
|
+
|
|
161
|
+
An abrupt process or machine stop can leave `spend.json.lock`; reservations then
|
|
162
|
+
fail closed instead of paying without a limit. Stop every process that uses the
|
|
163
|
+
same store before inspecting and manually removing a stale lock. Never remove
|
|
164
|
+
the lock while another process may still own it.
|
|
129
165
|
|
|
130
166
|
The client also rejects non-JPYC metadata, unsupported networks or schemes,
|
|
131
|
-
non-
|
|
132
|
-
|
|
167
|
+
non-canonical JPYC contracts or EIP-712 domains, seller timeouts above
|
|
168
|
+
`maxTimeoutSeconds`, unreviewed forwarder destinations, non-OpenPay forwarder
|
|
169
|
+
splits, amount inconsistencies,
|
|
170
|
+
resource URL mismatches, and catalog bait-and-switches before requesting a
|
|
171
|
+
signature. `MAX_TIMEOUT_SECONDS` is the equivalent setting for the exported
|
|
172
|
+
environment config readers. Target host/catalog admission and private-address
|
|
173
|
+
checks run before buyer target requests. Those requests require HTTPS, do not
|
|
174
|
+
follow redirects, and have a 15-second timeout. The default Node transport also
|
|
175
|
+
validates DNS before and during connection to block rebinding; an explicitly
|
|
176
|
+
injected custom `fetchImpl` is a trusted transport boundary and is responsible
|
|
177
|
+
for equivalent connect-time enforcement.
|
|
178
|
+
|
|
179
|
+
`pay()` returns a non-null `receipt` only when the facilitator signer advertised
|
|
180
|
+
by `/api/facilitator/supported` signed it and the transaction, payer, network,
|
|
181
|
+
asset, merchant amount, fee, chain, and authorization nonce all match this
|
|
182
|
+
payment. A missing, malformed, forged, or mismatched seller response header
|
|
183
|
+
becomes `receipt: null` without discarding the unlocked response body.
|
|
133
184
|
|
|
134
185
|
## Signers
|
|
135
186
|
|
package/index.d.ts
CHANGED
|
@@ -44,10 +44,43 @@ export interface SpendStore {
|
|
|
44
44
|
* so an absent entry must be reported as `'0'`, never `null`.
|
|
45
45
|
*/
|
|
46
46
|
load(key: string): Promise<string | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Atomically check the limit and reserve an authorization before it is sent.
|
|
49
|
+
* Custom legacy stores may omit this method; new cross-process stores should
|
|
50
|
+
* implement it to avoid lost updates.
|
|
51
|
+
*/
|
|
52
|
+
reserve?(
|
|
53
|
+
key: string,
|
|
54
|
+
amountAtomic: string,
|
|
55
|
+
limitAtomic: string,
|
|
56
|
+
reservation: SpendReservation,
|
|
57
|
+
): Promise<SpendReservationResult>;
|
|
58
|
+
/** Mark reservation metadata confirmed without reducing the reserved total. */
|
|
59
|
+
confirm?(id: string): Promise<boolean>;
|
|
47
60
|
/** Persist the new cumulative atomic amount. Failures must not throw. */
|
|
48
61
|
save(key: string, atomicString: string): Promise<void>;
|
|
49
62
|
}
|
|
50
63
|
|
|
64
|
+
export interface SpendReservation {
|
|
65
|
+
id: string;
|
|
66
|
+
payer: Address;
|
|
67
|
+
network: string;
|
|
68
|
+
asset: Address;
|
|
69
|
+
validBefore: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type SpendReservationResult =
|
|
73
|
+
| { ok: true; totalAtomic: string }
|
|
74
|
+
| {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: 'limit_exceeded' | 'unavailable';
|
|
77
|
+
totalAtomic?: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type PaymentLookup = (
|
|
81
|
+
hostname: string,
|
|
82
|
+
) => Promise<Array<{ address: string; family?: number }>>;
|
|
83
|
+
|
|
51
84
|
export interface FileSpendStoreOptions {
|
|
52
85
|
path?: string;
|
|
53
86
|
fsImpl?: {
|
|
@@ -58,6 +91,12 @@ export interface FileSpendStoreOptions {
|
|
|
58
91
|
data: string,
|
|
59
92
|
encoding: 'utf8',
|
|
60
93
|
): Promise<unknown>;
|
|
94
|
+
open?(
|
|
95
|
+
path: string,
|
|
96
|
+
flags: 'wx',
|
|
97
|
+
): Promise<{ close(): Promise<unknown> }>;
|
|
98
|
+
rename?(from: string, to: string): Promise<unknown>;
|
|
99
|
+
unlink?(path: string): Promise<unknown>;
|
|
61
100
|
};
|
|
62
101
|
}
|
|
63
102
|
|
|
@@ -65,11 +104,14 @@ interface ClientCommonOptions {
|
|
|
65
104
|
maxPerCallJpyc?: JpycAmount;
|
|
66
105
|
maxSessionJpyc?: JpycAmount;
|
|
67
106
|
maxDailyJpyc?: JpycAmount;
|
|
107
|
+
maxTimeoutSeconds?: number;
|
|
68
108
|
spendStore?: SpendStore;
|
|
69
109
|
allowedHosts?: string;
|
|
70
110
|
catalogTrust?: boolean;
|
|
71
111
|
discoveryUrl?: string;
|
|
72
112
|
fetchImpl?: typeof globalThis.fetch;
|
|
113
|
+
lookup?: PaymentLookup;
|
|
114
|
+
requestTimeoutMs?: number;
|
|
73
115
|
nowSec?: () => number;
|
|
74
116
|
now?: () => Date | number;
|
|
75
117
|
}
|
|
@@ -114,6 +156,11 @@ export interface RuntimeConfig {
|
|
|
114
156
|
maxPerCallAtomic: bigint;
|
|
115
157
|
maxSessionAtomic: bigint;
|
|
116
158
|
maxDailyAtomic: bigint | null;
|
|
159
|
+
/**
|
|
160
|
+
* Maximum seller-declared authorization lifetime. Optional here so existing
|
|
161
|
+
* consumers that construct RuntimeConfig manually remain source-compatible.
|
|
162
|
+
*/
|
|
163
|
+
maxTimeoutSeconds?: number;
|
|
117
164
|
allowedHosts: string[];
|
|
118
165
|
catalogTrust: boolean;
|
|
119
166
|
discoveryUrl: string;
|
|
@@ -228,6 +275,10 @@ export interface JpycGateOptions {
|
|
|
228
275
|
openpayOrigin?: string;
|
|
229
276
|
fetchImpl?: typeof globalThis.fetch;
|
|
230
277
|
now?: () => number;
|
|
278
|
+
/** Worst-case seller upstream duration protected by the local claim. Default: 60. */
|
|
279
|
+
maxUpstreamSeconds?: number;
|
|
280
|
+
/** Extra validity retained for settlement after upstream work. Default: 30. */
|
|
281
|
+
settlementGraceSeconds?: number;
|
|
231
282
|
}
|
|
232
283
|
|
|
233
284
|
export interface JpycGatePaymentResponse {
|
|
@@ -248,6 +299,7 @@ export function createJpycGate(options: JpycGateOptions): JpycGate;
|
|
|
248
299
|
export const RECEIVE_WITH_AUTHORIZATION_TYPES: {
|
|
249
300
|
ReceiveWithAuthorization: Array<{ name: string; type: string }>;
|
|
250
301
|
};
|
|
302
|
+
export const MAX_AUTHORIZATION_TIMEOUT_SECONDS: 1200;
|
|
251
303
|
|
|
252
304
|
export interface NormalizedPaymentRequirements {
|
|
253
305
|
scheme: 'exact';
|
|
@@ -331,6 +383,8 @@ export function paymentPayloadFor(
|
|
|
331
383
|
export const JPYC_DECIMALS: 18;
|
|
332
384
|
export const DEFAULT_MAX_PER_CALL_JPYC: '10';
|
|
333
385
|
export const DEFAULT_MAX_SESSION_JPYC: '100';
|
|
386
|
+
export const DEFAULT_MAX_TIMEOUT_SECONDS: 600;
|
|
387
|
+
export const MAX_SUPPORTED_TIMEOUT_SECONDS: 1200;
|
|
334
388
|
export const DEFAULT_ALLOWED_HOSTS: 'open-pay.jp';
|
|
335
389
|
export const DEFAULT_CATALOG_TRUST: true;
|
|
336
390
|
export const DEFAULT_DISCOVERY_URL: 'https://open-pay.jp/api/discovery';
|
|
@@ -340,8 +394,10 @@ export const REASONS: {
|
|
|
340
394
|
unsupportedScheme: 'unsupported_scheme';
|
|
341
395
|
unsupportedNetwork: 'unsupported_network';
|
|
342
396
|
invalidOpenpayMode: 'invalid_openpay_mode';
|
|
397
|
+
invalidOpenpayForwarder: 'invalid_openpay_forwarder';
|
|
343
398
|
amountMismatch: 'amount_mismatch';
|
|
344
399
|
invalidJpycAsset: 'invalid_jpyc_asset';
|
|
400
|
+
timeoutTooLong: 'timeout_too_long';
|
|
345
401
|
resourceMismatch: 'resource_mismatch';
|
|
346
402
|
invalidAccept: 'invalid_accept';
|
|
347
403
|
maxTotalRequired: 'max_total_required';
|
|
@@ -352,12 +408,62 @@ export const REASONS: {
|
|
|
352
408
|
sessionLimitExceeded: 'session_limit_exceeded';
|
|
353
409
|
dailyLimitExceeded: 'daily_limit_exceeded';
|
|
354
410
|
dailySpendUnavailable: 'daily_spend_unavailable';
|
|
411
|
+
dailyAuthorizationCrossesUtcDay: 'daily_authorization_crosses_utc_day';
|
|
355
412
|
buyerPrivateKeyMissing: 'buyer_private_key_missing';
|
|
356
413
|
stewardSignerUnconfigured: 'steward_signer_unconfigured';
|
|
357
414
|
catalogAcceptMismatch: 'catalog_accept_mismatch';
|
|
358
415
|
};
|
|
416
|
+
export const SUPPORTED_JPYC_ASSETS: Readonly<
|
|
417
|
+
Record<
|
|
418
|
+
string,
|
|
419
|
+
Readonly<{
|
|
420
|
+
address: Address;
|
|
421
|
+
name: 'JPY Coin';
|
|
422
|
+
version: '1';
|
|
423
|
+
decimals: 18;
|
|
424
|
+
}>
|
|
425
|
+
>
|
|
426
|
+
>;
|
|
427
|
+
export const SUPPORTED_JPYC_FORWARDERS: Readonly<
|
|
428
|
+
Partial<Record<string, Address>>
|
|
429
|
+
>;
|
|
359
430
|
export const SUPPORTED_NETWORKS: Set<string>;
|
|
360
431
|
|
|
432
|
+
export const DEFAULT_PAYMENT_FETCH_TIMEOUT_MS: 15000;
|
|
433
|
+
export function isPrivatePaymentHost(hostname: string): boolean;
|
|
434
|
+
export function parseSafePaymentUrl(raw: unknown): URL | null;
|
|
435
|
+
export function fetchPaymentTarget(
|
|
436
|
+
url: string,
|
|
437
|
+
options?: {
|
|
438
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
439
|
+
headers?: HeadersInit;
|
|
440
|
+
lookup?: PaymentLookup;
|
|
441
|
+
timeoutMs?: number;
|
|
442
|
+
},
|
|
443
|
+
): Promise<Response>;
|
|
444
|
+
|
|
445
|
+
export type ReceiptSignerResolver = () => Promise<Address | null>;
|
|
446
|
+
export function createReceiptSignerResolver(options: {
|
|
447
|
+
discoveryUrl: string;
|
|
448
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
449
|
+
lookup?: PaymentLookup;
|
|
450
|
+
requestTimeoutMs?: number;
|
|
451
|
+
}): ReceiptSignerResolver;
|
|
452
|
+
export function verifyBoundPaymentResponse(
|
|
453
|
+
paymentResponse: unknown,
|
|
454
|
+
expected: {
|
|
455
|
+
expectedSigner: Address;
|
|
456
|
+
payer: Address;
|
|
457
|
+
network: string;
|
|
458
|
+
asset: Address;
|
|
459
|
+
chainId: number;
|
|
460
|
+
merchant: Address;
|
|
461
|
+
merchantValue: bigint;
|
|
462
|
+
feeValue: bigint;
|
|
463
|
+
nonce: Hex;
|
|
464
|
+
},
|
|
465
|
+
): Promise<boolean>;
|
|
466
|
+
|
|
361
467
|
export interface AcceptSummary {
|
|
362
468
|
priceAtomic: bigint;
|
|
363
469
|
feeAtomic: bigint;
|
|
@@ -459,12 +565,16 @@ export function createCatalogCache(): CatalogCache;
|
|
|
459
565
|
export function resolveCatalogListings(options: {
|
|
460
566
|
config: Pick<RuntimeConfig, 'catalogTrust' | 'discoveryUrl'>;
|
|
461
567
|
fetchImpl?: typeof globalThis.fetch;
|
|
568
|
+
lookup?: PaymentLookup;
|
|
569
|
+
requestTimeoutMs?: number;
|
|
462
570
|
now?: () => number;
|
|
463
571
|
cache?: CatalogCache;
|
|
464
572
|
}): Promise<Map<string, unknown> | null>;
|
|
465
573
|
export function createCatalogResolver(options: {
|
|
466
574
|
config: Pick<RuntimeConfig, 'catalogTrust' | 'discoveryUrl'>;
|
|
467
575
|
fetchImpl?: typeof globalThis.fetch;
|
|
576
|
+
lookup?: PaymentLookup;
|
|
577
|
+
requestTimeoutMs?: number;
|
|
468
578
|
now?: () => number;
|
|
469
579
|
}): () => Promise<Map<string, unknown> | null>;
|
|
470
580
|
|
|
@@ -482,7 +592,10 @@ export function createPaymentExecutor(options: {
|
|
|
482
592
|
signerAddress?: Address | null;
|
|
483
593
|
spendStore?: SpendStore | null;
|
|
484
594
|
fetchImpl?: typeof globalThis.fetch;
|
|
595
|
+
lookup?: PaymentLookup;
|
|
596
|
+
requestTimeoutMs?: number;
|
|
485
597
|
nowSec?: () => number;
|
|
486
598
|
now?: () => Date | number;
|
|
487
599
|
resolveCatalogListings?: () => Promise<Map<string, unknown> | null>;
|
|
600
|
+
resolveReceiptSigner?: ReceiptSignerResolver;
|
|
488
601
|
}): PaymentExecutor;
|
package/package.json
CHANGED
package/src/catalog.mjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_PAYMENT_FETCH_TIMEOUT_MS,
|
|
3
|
+
fetchPaymentTarget,
|
|
4
|
+
} from './network.mjs';
|
|
5
|
+
|
|
1
6
|
export const CATALOG_CACHE_MS = 5 * 60_000;
|
|
2
7
|
|
|
3
8
|
function isObject(value) {
|
|
@@ -21,6 +26,8 @@ export function createCatalogCache() {
|
|
|
21
26
|
export async function resolveCatalogListings({
|
|
22
27
|
config,
|
|
23
28
|
fetchImpl = fetch,
|
|
29
|
+
lookup,
|
|
30
|
+
requestTimeoutMs = DEFAULT_PAYMENT_FETCH_TIMEOUT_MS,
|
|
24
31
|
now = Date.now,
|
|
25
32
|
cache = createCatalogCache(),
|
|
26
33
|
}) {
|
|
@@ -33,7 +40,10 @@ export async function resolveCatalogListings({
|
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
try {
|
|
36
|
-
const response = await
|
|
43
|
+
const response = await fetchPaymentTarget(config.discoveryUrl, {
|
|
44
|
+
fetchImpl,
|
|
45
|
+
lookup,
|
|
46
|
+
timeoutMs: requestTimeoutMs,
|
|
37
47
|
headers: { accept: 'application/json' },
|
|
38
48
|
});
|
|
39
49
|
const body = await readJson(response);
|
|
@@ -64,7 +74,21 @@ export async function resolveCatalogListings({
|
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
export function createCatalogResolver({
|
|
77
|
+
export function createCatalogResolver({
|
|
78
|
+
config,
|
|
79
|
+
fetchImpl = fetch,
|
|
80
|
+
lookup,
|
|
81
|
+
requestTimeoutMs = DEFAULT_PAYMENT_FETCH_TIMEOUT_MS,
|
|
82
|
+
now = Date.now,
|
|
83
|
+
}) {
|
|
68
84
|
const cache = createCatalogCache();
|
|
69
|
-
return () =>
|
|
85
|
+
return () =>
|
|
86
|
+
resolveCatalogListings({
|
|
87
|
+
config,
|
|
88
|
+
fetchImpl,
|
|
89
|
+
lookup,
|
|
90
|
+
requestTimeoutMs,
|
|
91
|
+
now,
|
|
92
|
+
cache,
|
|
93
|
+
});
|
|
70
94
|
}
|
package/src/client.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from './guards.mjs';
|
|
9
9
|
import { createSignerFromOptions } from './signer.mjs';
|
|
10
10
|
import { createFileSpendStore } from './spendStore.mjs';
|
|
11
|
+
import { createReceiptSignerResolver } from './receipt.mjs';
|
|
11
12
|
|
|
12
13
|
function isObject(value) {
|
|
13
14
|
return typeof value === 'object' && value !== null;
|
|
@@ -68,7 +69,18 @@ export function createOpenPayClient(options = {}) {
|
|
|
68
69
|
config.maxDailyAtomic === null
|
|
69
70
|
? null
|
|
70
71
|
: options.spendStore ?? createFileSpendStore();
|
|
71
|
-
const resolveCatalogListings = createCatalogResolver({
|
|
72
|
+
const resolveCatalogListings = createCatalogResolver({
|
|
73
|
+
config,
|
|
74
|
+
fetchImpl,
|
|
75
|
+
lookup: options.lookup,
|
|
76
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
77
|
+
});
|
|
78
|
+
const resolveReceiptSigner = createReceiptSignerResolver({
|
|
79
|
+
discoveryUrl: config.discoveryUrl,
|
|
80
|
+
fetchImpl,
|
|
81
|
+
lookup: options.lookup,
|
|
82
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
83
|
+
});
|
|
72
84
|
const executor = createPaymentExecutor({
|
|
73
85
|
config,
|
|
74
86
|
session,
|
|
@@ -76,9 +88,12 @@ export function createOpenPayClient(options = {}) {
|
|
|
76
88
|
signerAddress: signer?.address ?? null,
|
|
77
89
|
spendStore,
|
|
78
90
|
fetchImpl,
|
|
91
|
+
lookup: options.lookup,
|
|
92
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
79
93
|
nowSec: options.nowSec,
|
|
80
94
|
now: options.now,
|
|
81
95
|
resolveCatalogListings,
|
|
96
|
+
resolveReceiptSigner,
|
|
82
97
|
});
|
|
83
98
|
|
|
84
99
|
async function withSafeErrors(operation) {
|