openpay-x402-sdk 0.1.0 → 0.2.1
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 +15 -0
- package/README.md +4 -1
- package/package.json +1 -1
- package/src/guards.mjs +31 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- Compare `accept.resource` against the requested URL using decoded query
|
|
6
|
+
canonicalization (ordered `URLSearchParams` pairs) instead of byte equality.
|
|
7
|
+
Hosts such as Vercel/Next.js normalize `%20` to `+` before the app sees the
|
|
8
|
+
request, which made honest sellers fail `resource_mismatch`. Distinct decoded
|
|
9
|
+
values (`%2B`, double encoding, reordered or extra params) still mismatch.
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
- Trust query-string variants of a query-free catalog URL after the live
|
|
14
|
+
challenge passes the same catalog money-field verification.
|
|
15
|
+
- Keep exact catalog URL matches, explicit host allowlisting, resource matching,
|
|
16
|
+
and public API declarations unchanged.
|
|
17
|
+
|
|
3
18
|
## 0.1.0
|
|
4
19
|
|
|
5
20
|
- Add the ESM `createOpenPayClient` API for discovery, free shop lookup, quotes,
|
package/README.md
CHANGED
|
@@ -39,9 +39,12 @@ concurrent calls so every call sees the latest session total.
|
|
|
39
39
|
| `maxPerCallJpyc` | `10` | Upper bound for the caller-provided `maxTotalJpyc`. |
|
|
40
40
|
| `maxSessionJpyc` | `100` | Cumulative cap for successful payments made by this client instance. |
|
|
41
41
|
| `allowedHosts` | `open-pay.jp` | Comma-separated bare host allowlist. |
|
|
42
|
-
| `catalogTrust` | `true` | Also allows
|
|
42
|
+
| `catalogTrust` | `true` | Also allows catalog URLs after the live challenge matches the catalog challenge. |
|
|
43
43
|
| `discoveryUrl` | `https://open-pay.jp/api/discovery` | Catalog and OpenPay origin used by the client. |
|
|
44
44
|
|
|
45
|
+
Query string variants of a query-free listed URL are trusted after the same
|
|
46
|
+
money-field verification. Exact query-bearing catalog entries remain exact-only.
|
|
47
|
+
|
|
45
48
|
`pay(url, { maxTotalJpyc })` always requires `maxTotalJpyc`. It is the maximum
|
|
46
49
|
total—including the resource price and x402 fee—that this individual call is
|
|
47
50
|
authorized to pay. It does not disable or raise `maxPerCallJpyc` or
|
package/package.json
CHANGED
package/src/guards.mjs
CHANGED
|
@@ -251,6 +251,19 @@ function addAssetReasons(reasons, rawAccept) {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// クエリの正準比較: URLSearchParams でデコードした (key, value) 列の順序付き一致。
|
|
255
|
+
// Vercel/Next 系ホストは request.url の時点でスペースを `+` に正規化するため (`%20` の原文は
|
|
256
|
+
// サーバー側で復元不可能)、accept.resource と要求 URL のバイト一致要求は正当な売り手を
|
|
257
|
+
// 恒常的に落とす (gateway.open-pay.jp で実害)。`%20` と `+` は form-urlencoding で同一の
|
|
258
|
+
// スペースにデコードされる一方、`%2B` (リテラル +) や二重エンコードは異なる値にデコード
|
|
259
|
+
// されるので、この比較は同義エンコーディングだけを同一視し resource 束縛は緩めない。
|
|
260
|
+
function sameQuery(a, b) {
|
|
261
|
+
const ap = [...a.searchParams];
|
|
262
|
+
const bp = [...b.searchParams];
|
|
263
|
+
if (ap.length !== bp.length) return false;
|
|
264
|
+
return ap.every(([k, v], i) => bp[i][0] === k && bp[i][1] === v);
|
|
265
|
+
}
|
|
266
|
+
|
|
254
267
|
function addResourceReason(reasons, rawAccept, requestUrl) {
|
|
255
268
|
const requested = parseHttpUrl(requestUrl, 'url');
|
|
256
269
|
const resource =
|
|
@@ -261,7 +274,11 @@ function addResourceReason(reasons, rawAccept, requestUrl) {
|
|
|
261
274
|
requested === null ||
|
|
262
275
|
resource === null ||
|
|
263
276
|
resource.hostname.toLowerCase() !== requested.hostname.toLowerCase() ||
|
|
264
|
-
resource.
|
|
277
|
+
resource.origin !== requested.origin ||
|
|
278
|
+
resource.pathname !== requested.pathname ||
|
|
279
|
+
resource.username !== requested.username ||
|
|
280
|
+
resource.password !== requested.password ||
|
|
281
|
+
!sameQuery(resource, requested)
|
|
265
282
|
) {
|
|
266
283
|
reasons.push(REASONS.resourceMismatch);
|
|
267
284
|
}
|
|
@@ -366,11 +383,19 @@ export function evaluatePaymentGuards({
|
|
|
366
383
|
reasons.push(REASONS.invalidUrl);
|
|
367
384
|
} else {
|
|
368
385
|
const hostAllowed = config.allowedHosts.includes(parsedUrl.hostname.toLowerCase());
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
386
|
+
// 完全一致を先に維持し、miss 時だけ query/hash を除いた同一 origin+pathname を引く。
|
|
387
|
+
// userinfo は origin に含まれないため、query variant ではない credential 付き URL へは緩和しない。
|
|
388
|
+
let listedAccept;
|
|
389
|
+
if (config.catalogTrust && catalogListings instanceof Map) {
|
|
390
|
+
listedAccept = catalogListings.get(parsedUrl.toString());
|
|
391
|
+
if (
|
|
392
|
+
listedAccept === undefined &&
|
|
393
|
+
parsedUrl.username === '' &&
|
|
394
|
+
parsedUrl.password === ''
|
|
395
|
+
) {
|
|
396
|
+
listedAccept = catalogListings.get(`${parsedUrl.origin}${parsedUrl.pathname}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
374
399
|
const catalogListed = listedAccept !== undefined;
|
|
375
400
|
if (!hostAllowed && !catalogListed) {
|
|
376
401
|
reasons.push(REASONS.hostNotAllowed);
|