@astrasyncai/verification-gateway 5.17.2 → 5.18.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 +1878 -0
- package/README.md +141 -7
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +30 -12
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +30 -12
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/kit-express.d.mts +63 -0
- package/dist/adapters/kit-express.d.ts +63 -0
- package/dist/adapters/kit-express.js +88 -0
- package/dist/adapters/kit-express.js.map +1 -0
- package/dist/adapters/kit-express.mjs +61 -0
- package/dist/adapters/kit-express.mjs.map +1 -0
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.d.ts +1 -1
- package/dist/adapters/mcp.js +1 -1
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +1 -1
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +18 -8
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +18 -8
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +1 -1
- package/dist/adapters/sdk.d.ts +1 -1
- package/dist/adapters/sdk.js +1 -1
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +1 -1
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +1 -1
- package/dist/bin/astrasync-codex-hook.js +1 -1
- package/dist/bin/astrasync-guard.js +330 -112
- package/dist/bin/astrasync.js +359 -113
- package/dist/browser/background.js +1 -1
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +1 -1
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.js +1 -1
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +1 -1
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +1 -1
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +1 -1
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +5 -4
- package/dist/edge-core/index.d.ts +5 -4
- package/dist/edge-core/index.js +18 -8
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +18 -8
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +1 -1
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +1 -1
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +391 -13
- package/dist/index.d.ts +391 -13
- package/dist/index.js +766 -230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +749 -222
- package/dist/index.mjs.map +1 -1
- package/dist/registration/index.d.mts +1 -1
- package/dist/registration/index.d.ts +1 -1
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/transport/index.d.mts +4 -3
- package/dist/transport/index.d.ts +4 -3
- package/dist/transport/index.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-vNBvYD7Z.d.mts → types-3ObBpsnf.d.mts} +7 -0
- package/dist/{types-DA8LzAOB.d.ts → types-BoxZEYPS.d.ts} +7 -0
- package/dist/{types-DRb_plOh.d.mts → types-Cns4l8gX.d.mts} +17 -0
- package/dist/{types-DRb_plOh.d.ts → types-Cns4l8gX.d.ts} +17 -0
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
package/dist/index.mjs
CHANGED
|
@@ -30,7 +30,7 @@ function getTrustLevel(score) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// src/version.ts
|
|
33
|
-
var SDK_VERSION = "5.
|
|
33
|
+
var SDK_VERSION = "5.18.1";
|
|
34
34
|
|
|
35
35
|
// src/http.ts
|
|
36
36
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -1172,17 +1172,17 @@ function normalizeDeclaredSource(value) {
|
|
|
1172
1172
|
return trimmed && DECLARED_SOURCE_PATTERN.test(trimmed) ? trimmed : void 0;
|
|
1173
1173
|
}
|
|
1174
1174
|
function byteLength(s) {
|
|
1175
|
-
let
|
|
1175
|
+
let bytes2 = 0;
|
|
1176
1176
|
for (let i = 0; i < s.length; i++) {
|
|
1177
1177
|
const code = s.charCodeAt(i);
|
|
1178
|
-
if (code < 128)
|
|
1179
|
-
else if (code < 2048)
|
|
1178
|
+
if (code < 128) bytes2 += 1;
|
|
1179
|
+
else if (code < 2048) bytes2 += 2;
|
|
1180
1180
|
else if (code >= 55296 && code <= 56319) {
|
|
1181
|
-
|
|
1181
|
+
bytes2 += 4;
|
|
1182
1182
|
i++;
|
|
1183
|
-
} else
|
|
1183
|
+
} else bytes2 += 3;
|
|
1184
1184
|
}
|
|
1185
|
-
return
|
|
1185
|
+
return bytes2;
|
|
1186
1186
|
}
|
|
1187
1187
|
function extractApiKeyFormat(rawValue) {
|
|
1188
1188
|
if (!rawValue) return void 0;
|
|
@@ -1359,9 +1359,9 @@ function decodeBase64Url(seg) {
|
|
|
1359
1359
|
const b642 = seg.replace(/-/g, "+").replace(/_/g, "/");
|
|
1360
1360
|
const padded = b642 + "=".repeat((4 - b642.length % 4) % 4);
|
|
1361
1361
|
const binary2 = atob(padded);
|
|
1362
|
-
const
|
|
1363
|
-
for (let i = 0; i < binary2.length; i++)
|
|
1364
|
-
return new TextDecoder().decode(
|
|
1362
|
+
const bytes2 = new Uint8Array(binary2.length);
|
|
1363
|
+
for (let i = 0; i < binary2.length; i++) bytes2[i] = binary2.charCodeAt(i);
|
|
1364
|
+
return new TextDecoder().decode(bytes2);
|
|
1365
1365
|
}
|
|
1366
1366
|
function sniffJwtHeader(compact) {
|
|
1367
1367
|
try {
|
|
@@ -1400,6 +1400,23 @@ function detectCommerce(req, host) {
|
|
|
1400
1400
|
}
|
|
1401
1401
|
const signature = headerValue(req, "signature");
|
|
1402
1402
|
const signatureInput = headerValue(req, "signature-input");
|
|
1403
|
+
if (/\/checkout[-_]sessions(\/|$)/.test(req.uri) && headerValue(req, "ucp-agent")) {
|
|
1404
|
+
const signed = Boolean(signature && signatureInput);
|
|
1405
|
+
return {
|
|
1406
|
+
protocol: "ucp",
|
|
1407
|
+
artifacts: {
|
|
1408
|
+
ucpRequest: { method, url, ...jsonBody && { body: jsonBody } },
|
|
1409
|
+
...signed && {
|
|
1410
|
+
rfc9421: { headers, method, url, ...req.bodyText && { body: req.bodyText } }
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
evidence: {
|
|
1414
|
+
path: req.uri,
|
|
1415
|
+
...signed && { keyid: signatureInput.match(/keyid="([^"]+)"/)?.[1] },
|
|
1416
|
+
...truncated
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1403
1420
|
if (signature && signatureInput) {
|
|
1404
1421
|
const signatureAgent = headerValue(req, "signature-agent");
|
|
1405
1422
|
const registryHint = headerValue(req, "x-agent-registry");
|
|
@@ -1434,13 +1451,6 @@ function detectCommerce(req, host) {
|
|
|
1434
1451
|
evidence: { hasTimestamp: Boolean(acpTimestamp), ...truncated }
|
|
1435
1452
|
};
|
|
1436
1453
|
}
|
|
1437
|
-
if (/\/checkout[-_]sessions(\/|$)/.test(req.uri) && headerValue(req, "ucp-agent")) {
|
|
1438
|
-
return {
|
|
1439
|
-
protocol: "ucp",
|
|
1440
|
-
artifacts: { ucpRequest: { method, url, ...jsonBody && { body: jsonBody } } },
|
|
1441
|
-
evidence: { path: req.uri, ...truncated }
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
1454
|
if (jsonBody) {
|
|
1445
1455
|
const intent = jsonBody.intent_mandate ?? jsonBody.intentMandate;
|
|
1446
1456
|
const cart = jsonBody.cart_mandate ?? jsonBody.cartMandate;
|
|
@@ -2539,6 +2549,7 @@ function createMiddleware(options) {
|
|
|
2539
2549
|
anonymousBeacon = true,
|
|
2540
2550
|
trustVerifiedHop = false,
|
|
2541
2551
|
verifiedHopMaxAgeMs,
|
|
2552
|
+
defaultRoutePolicy = "pass",
|
|
2542
2553
|
...config
|
|
2543
2554
|
} = options;
|
|
2544
2555
|
if (config.apiBaseUrl) {
|
|
@@ -2604,12 +2615,15 @@ function createMiddleware(options) {
|
|
|
2604
2615
|
});
|
|
2605
2616
|
}
|
|
2606
2617
|
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
|
|
2607
|
-
const
|
|
2618
|
+
const matchedRoute = findRouteConfig(cachedRoutes, req.path, req.method, {
|
|
2608
2619
|
caseInsensitive: caseInsensitiveRouteMatch,
|
|
2609
2620
|
logShadowDivergence: true,
|
|
2610
2621
|
correlationId
|
|
2611
2622
|
});
|
|
2612
|
-
|
|
2623
|
+
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
2624
|
+
const credentialed = Boolean(credentials.astraId || credentials.apiKey || credentials.jwt);
|
|
2625
|
+
const observeUnmatched = !matchedRoute && (config.evaluateAlwaysIfCredentialed === true && Boolean(credentials.astraId) || defaultRoutePolicy === "observe" && credentialed);
|
|
2626
|
+
if (!matchedRoute && !observeUnmatched) {
|
|
2613
2627
|
if (config.setPassThroughHeader) {
|
|
2614
2628
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
2615
2629
|
res.setHeader(
|
|
@@ -2620,10 +2634,14 @@ function createMiddleware(options) {
|
|
|
2620
2634
|
beaconPassThrough?.(req);
|
|
2621
2635
|
return next();
|
|
2622
2636
|
}
|
|
2637
|
+
const routeConfig = matchedRoute ?? {
|
|
2638
|
+
pattern: req.path,
|
|
2639
|
+
method: req.method,
|
|
2640
|
+
observe: true
|
|
2641
|
+
};
|
|
2623
2642
|
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
2624
|
-
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
2625
2643
|
const shouldEnforce = !routeConfig.observe;
|
|
2626
|
-
if (routeConfig.observe && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
|
|
2644
|
+
if (routeConfig.observe && !observeUnmatched && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
|
|
2627
2645
|
if (config.setPassThroughHeader) {
|
|
2628
2646
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
2629
2647
|
res.setHeader("X-Astra-Gateway-Reason", "route-observe");
|
|
@@ -4105,8 +4123,8 @@ function extractBase64(value) {
|
|
|
4105
4123
|
}
|
|
4106
4124
|
return null;
|
|
4107
4125
|
}
|
|
4108
|
-
function bufferToBase64(
|
|
4109
|
-
return Buffer.from(
|
|
4126
|
+
function bufferToBase64(bytes2) {
|
|
4127
|
+
return Buffer.from(bytes2).toString("base64");
|
|
4110
4128
|
}
|
|
4111
4129
|
|
|
4112
4130
|
// src/transport/rfc9421-verify.ts
|
|
@@ -5273,9 +5291,9 @@ async function tryVerify(subtle, jwk, signature, body, alg) {
|
|
|
5273
5291
|
}
|
|
5274
5292
|
return false;
|
|
5275
5293
|
}
|
|
5276
|
-
function toBuf(
|
|
5277
|
-
const out = new ArrayBuffer(
|
|
5278
|
-
new Uint8Array(out).set(
|
|
5294
|
+
function toBuf(bytes2) {
|
|
5295
|
+
const out = new ArrayBuffer(bytes2.byteLength);
|
|
5296
|
+
new Uint8Array(out).set(bytes2);
|
|
5279
5297
|
return out;
|
|
5280
5298
|
}
|
|
5281
5299
|
function checkTimestamp(headerValue3, toleranceSec, nowFn) {
|
|
@@ -5801,11 +5819,11 @@ async function thumbprintsMatch(a, b) {
|
|
|
5801
5819
|
}
|
|
5802
5820
|
async function jwkThumbprint(jwk) {
|
|
5803
5821
|
const canonical2 = canonicalJwk(jwk);
|
|
5804
|
-
const
|
|
5822
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(canonical2));
|
|
5805
5823
|
const subtle = webcrypto.subtle;
|
|
5806
5824
|
const buffer = await new Promise((resolve, reject) => {
|
|
5807
|
-
const source = new ArrayBuffer(
|
|
5808
|
-
new Uint8Array(source).set(
|
|
5825
|
+
const source = new ArrayBuffer(bytes2.byteLength);
|
|
5826
|
+
new Uint8Array(source).set(bytes2);
|
|
5809
5827
|
subtle.digest("SHA-256", source).then(resolve).catch(reject);
|
|
5810
5828
|
});
|
|
5811
5829
|
return Buffer.from(new Uint8Array(buffer)).toString("base64url").replace(/=+$/, "");
|
|
@@ -6198,14 +6216,14 @@ function createMastercardRegistry(options = {}) {
|
|
|
6198
6216
|
const cache4 = /* @__PURE__ */ new Map();
|
|
6199
6217
|
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
6200
6218
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
6201
|
-
let
|
|
6219
|
+
let warned2 = false;
|
|
6202
6220
|
return {
|
|
6203
6221
|
name: "mastercard",
|
|
6204
6222
|
async resolve(kid) {
|
|
6205
6223
|
if (!kid) return null;
|
|
6206
6224
|
if (!options.registryUrl) {
|
|
6207
|
-
if (!
|
|
6208
|
-
|
|
6225
|
+
if (!warned2 && !options.silent) {
|
|
6226
|
+
warned2 = true;
|
|
6209
6227
|
console.warn(
|
|
6210
6228
|
"[mastercard-registry] registryUrl not configured \u2014 key resolution disabled. Kid lookups will return null until a partnership registry is supplied."
|
|
6211
6229
|
);
|
|
@@ -10758,6 +10776,7 @@ function astrasyncPaymentHandlers(handlers, specBase = "https://astrasync.ai/ucp
|
|
|
10758
10776
|
}
|
|
10759
10777
|
return out;
|
|
10760
10778
|
}
|
|
10779
|
+
var UCP_ORDER_CAPABILITY = "dev.ucp.shopping.order";
|
|
10761
10780
|
function buildUcpProfile(opts) {
|
|
10762
10781
|
const capabilities = {
|
|
10763
10782
|
"dev.ucp.shopping.checkout": [
|
|
@@ -10778,6 +10797,15 @@ function buildUcpProfile(opts) {
|
|
|
10778
10797
|
}
|
|
10779
10798
|
]
|
|
10780
10799
|
},
|
|
10800
|
+
...opts.orders && {
|
|
10801
|
+
[UCP_ORDER_CAPABILITY]: [
|
|
10802
|
+
{
|
|
10803
|
+
version: UCP_VERSION,
|
|
10804
|
+
spec: `${SPEC}/shopping/order`,
|
|
10805
|
+
schema: `${SCHEMAS}/shopping/order.json`
|
|
10806
|
+
}
|
|
10807
|
+
]
|
|
10808
|
+
},
|
|
10781
10809
|
...opts.capabilities ?? {}
|
|
10782
10810
|
};
|
|
10783
10811
|
return {
|
|
@@ -11154,7 +11182,7 @@ async function sendUcpWebhook(opts) {
|
|
|
11154
11182
|
}
|
|
11155
11183
|
|
|
11156
11184
|
// src/ucp/checkout-kit.ts
|
|
11157
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
11185
|
+
import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
|
|
11158
11186
|
import { CompactSign as CompactSign2 } from "jose";
|
|
11159
11187
|
|
|
11160
11188
|
// src/mandates/sd-jwt.ts
|
|
@@ -11559,8 +11587,8 @@ function requestFingerprint(body, profile = "ucp") {
|
|
|
11559
11587
|
}
|
|
11560
11588
|
return createHash8("sha256").update(text).digest("hex");
|
|
11561
11589
|
}
|
|
11562
|
-
function scopeOf(
|
|
11563
|
-
return
|
|
11590
|
+
function scopeOf(caller) {
|
|
11591
|
+
return caller ? createHash8("sha256").update(caller).digest("hex").slice(0, 16) : null;
|
|
11564
11592
|
}
|
|
11565
11593
|
async function beginIdempotent(opts, req) {
|
|
11566
11594
|
const methods = opts.methods ?? ["POST", "PUT", "PATCH", "DELETE"];
|
|
@@ -11581,7 +11609,16 @@ async function beginIdempotent(opts, req) {
|
|
|
11581
11609
|
message: `Idempotency-Key must be at most ${MAX_KEY_LENGTH} characters.`
|
|
11582
11610
|
};
|
|
11583
11611
|
}
|
|
11584
|
-
const
|
|
11612
|
+
const scope = scopeOf(req.auth);
|
|
11613
|
+
if (!scope) {
|
|
11614
|
+
return opts.required ? {
|
|
11615
|
+
kind: "reject",
|
|
11616
|
+
status: 401,
|
|
11617
|
+
code: "idempotency_caller_required",
|
|
11618
|
+
message: "Idempotency keys are scoped to the caller: send Authorization."
|
|
11619
|
+
} : { kind: "skip" };
|
|
11620
|
+
}
|
|
11621
|
+
const storeKey = `${scope}:${req.method.toUpperCase()} ${req.path}:${req.key}`;
|
|
11585
11622
|
const requestHash = requestFingerprint(req.body, opts.profile ?? "ucp");
|
|
11586
11623
|
const ttl = opts.ttlMs ?? DEFAULT_IDEMPOTENCY_TTL_MS;
|
|
11587
11624
|
try {
|
|
@@ -11657,7 +11694,7 @@ function idempotencyMiddleware(opts) {
|
|
|
11657
11694
|
path: (req.originalUrl ?? req.url).split("?")[0],
|
|
11658
11695
|
body,
|
|
11659
11696
|
key: headerOf(req.headers[header]),
|
|
11660
|
-
auth: headerOf(req.headers.authorization) ?? headerOf(req.headers["x-api-key"])
|
|
11697
|
+
auth: headerOf(req.headers.authorization) ?? headerOf(req.headers["x-api-key"]) ?? headerOf(req.headers["x-astra-id"])
|
|
11661
11698
|
});
|
|
11662
11699
|
if (decision.kind === "skip") return next();
|
|
11663
11700
|
const key = headerOf(req.headers[header]);
|
|
@@ -11696,7 +11733,7 @@ function withIdempotency(opts, handler) {
|
|
|
11696
11733
|
path: new URL(req.url).pathname,
|
|
11697
11734
|
body,
|
|
11698
11735
|
key: req.headers.get(header) ?? void 0,
|
|
11699
|
-
auth: req.headers.get("authorization") ?? req.headers.get("x-api-key") ?? void 0
|
|
11736
|
+
auth: req.headers.get("authorization") ?? req.headers.get("x-api-key") ?? req.headers.get("x-astra-id") ?? void 0
|
|
11700
11737
|
});
|
|
11701
11738
|
if (decision.kind === "skip") return handler(req);
|
|
11702
11739
|
const key = req.headers.get(header) ?? void 0;
|
|
@@ -11753,18 +11790,95 @@ function orderReportAmount(result, fallback) {
|
|
|
11753
11790
|
return fallback;
|
|
11754
11791
|
}
|
|
11755
11792
|
|
|
11793
|
+
// src/ucp/kit-shared.ts
|
|
11794
|
+
import { createHash as createHash9 } from "crypto";
|
|
11795
|
+
function isHeld(result) {
|
|
11796
|
+
const r = result;
|
|
11797
|
+
return Boolean(
|
|
11798
|
+
r.requiresStepUp || r.requiresApproval || r.recommendation === "step_up_required" || r.settlementOutcome?.status === "requires_approval" || r.stepUpApproval && r.stepUpApproval.status !== "consumed"
|
|
11799
|
+
);
|
|
11800
|
+
}
|
|
11801
|
+
var scopeHash = (kind, value) => `${kind}:${createHash9("sha256").update(`${kind}
|
|
11802
|
+
${value}`).digest("hex").slice(0, 32)}`;
|
|
11803
|
+
async function resolveCallerScope(req, ctx, resolve) {
|
|
11804
|
+
const verified = ctx?.callerId ?? (resolve ? await resolve(req) : void 0);
|
|
11805
|
+
if (verified) return scopeHash("astra", verified);
|
|
11806
|
+
const auth = req.headers.get("authorization");
|
|
11807
|
+
if (auth) return scopeHash("auth", auth);
|
|
11808
|
+
const astraId = req.headers.get("x-astra-id");
|
|
11809
|
+
if (astraId) return scopeHash("astra-header", astraId);
|
|
11810
|
+
return void 0;
|
|
11811
|
+
}
|
|
11812
|
+
var fallbackScope = (kind, value) => scopeHash(kind, value);
|
|
11813
|
+
function pendingResult(result) {
|
|
11814
|
+
const {
|
|
11815
|
+
settlementToken: _t,
|
|
11816
|
+
settlement: _s,
|
|
11817
|
+
mandates: _m,
|
|
11818
|
+
kyaToken: _k,
|
|
11819
|
+
aep: _a,
|
|
11820
|
+
...rest
|
|
11821
|
+
} = result;
|
|
11822
|
+
return rest;
|
|
11823
|
+
}
|
|
11824
|
+
var warned = /* @__PURE__ */ new Set();
|
|
11825
|
+
function warnOnce(key, message) {
|
|
11826
|
+
if (warned.has(key) || process.env.NODE_ENV === "test") return;
|
|
11827
|
+
warned.add(key);
|
|
11828
|
+
console.warn(message);
|
|
11829
|
+
}
|
|
11830
|
+
function warnInMemoryStores(kit, missing) {
|
|
11831
|
+
if (!missing.length || process.env.NODE_ENV === "test") return;
|
|
11832
|
+
console.warn(
|
|
11833
|
+
`[astrasync] ${kit} checkout kit is using the in-memory ${missing.join(" and ")} (sessions are lost on restart and not shared across instances). Pass ${missing.map((m) => m.startsWith("idempotency") ? "idempotencyStore" : "store").join(" and ")} in production.`
|
|
11834
|
+
);
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11756
11837
|
// src/ucp/checkout-kit.ts
|
|
11757
11838
|
var MemoryCheckoutStore = class {
|
|
11758
11839
|
constructor() {
|
|
11759
11840
|
this.m = /* @__PURE__ */ new Map();
|
|
11841
|
+
this.orders = /* @__PURE__ */ new Map();
|
|
11760
11842
|
}
|
|
11761
11843
|
async get(id) {
|
|
11762
11844
|
return this.m.get(id) ?? null;
|
|
11763
11845
|
}
|
|
11764
11846
|
async put(s) {
|
|
11765
11847
|
this.m.set(s.id, s);
|
|
11848
|
+
if (s.order) this.orders.set(s.order.id, s.id);
|
|
11849
|
+
}
|
|
11850
|
+
async claim(id, from, to) {
|
|
11851
|
+
const s = this.m.get(id);
|
|
11852
|
+
if (!s || s.status !== from) return false;
|
|
11853
|
+
s.status = to;
|
|
11854
|
+
return true;
|
|
11855
|
+
}
|
|
11856
|
+
async findByOrderId(orderId) {
|
|
11857
|
+
const id = this.orders.get(orderId);
|
|
11858
|
+
return id ? this.m.get(id) ?? null : null;
|
|
11766
11859
|
}
|
|
11767
11860
|
};
|
|
11861
|
+
function buildUcpOrder(session, opts = {}) {
|
|
11862
|
+
if (!session.order) return null;
|
|
11863
|
+
const version = opts.version ?? UCP_VERSION;
|
|
11864
|
+
return {
|
|
11865
|
+
ucp: { version, capabilities: { [UCP_ORDER_CAPABILITY]: [{ version }] } },
|
|
11866
|
+
id: session.order.id,
|
|
11867
|
+
checkout_id: session.id,
|
|
11868
|
+
...session.order.permalink_url && { permalink_url: session.order.permalink_url },
|
|
11869
|
+
currency: session.currency,
|
|
11870
|
+
line_items: session.line_items.map((l) => ({
|
|
11871
|
+
id: l.id,
|
|
11872
|
+
item: l.item,
|
|
11873
|
+
quantity: { total: l.quantity, fulfilled: 0 },
|
|
11874
|
+
totals: l.totals,
|
|
11875
|
+
status: "processing"
|
|
11876
|
+
})),
|
|
11877
|
+
fulfillment: { expectations: [], events: [] },
|
|
11878
|
+
adjustments: [],
|
|
11879
|
+
totals: session.totals
|
|
11880
|
+
};
|
|
11881
|
+
}
|
|
11768
11882
|
var json2 = (status, body, headers = {}) => new Response(JSON.stringify(body), {
|
|
11769
11883
|
status,
|
|
11770
11884
|
headers: { "content-type": "application/json", ...headers }
|
|
@@ -11777,6 +11891,24 @@ function createUcpCheckoutKit(opts) {
|
|
|
11777
11891
|
store: opts.idempotencyStore ?? new MemoryIdempotencyStore(),
|
|
11778
11892
|
profile: "ucp"
|
|
11779
11893
|
};
|
|
11894
|
+
warnInMemoryStores("UCP", [
|
|
11895
|
+
...opts.store ? [] : ["checkout store"],
|
|
11896
|
+
...opts.idempotencyStore ? [] : ["idempotency store"]
|
|
11897
|
+
]);
|
|
11898
|
+
if (!store.claim) {
|
|
11899
|
+
warnOnce(
|
|
11900
|
+
"ucp-claim",
|
|
11901
|
+
"[astrasync] UCP checkout kit: your CheckoutStore has no claim() \u2014 concurrent completes of one checkout aren't locked. Implement claim(id, from, to) as an atomic compare-and-set."
|
|
11902
|
+
);
|
|
11903
|
+
}
|
|
11904
|
+
const orderIndex = /* @__PURE__ */ new Map();
|
|
11905
|
+
const ordersOn = Boolean(profile.ucp.capabilities?.[UCP_ORDER_CAPABILITY]);
|
|
11906
|
+
const handlerIds = new Set(
|
|
11907
|
+
Object.entries(profile.ucp.payment_handlers).flatMap(([name, entries]) => [
|
|
11908
|
+
name,
|
|
11909
|
+
...entries.map((e) => e.id)
|
|
11910
|
+
])
|
|
11911
|
+
);
|
|
11780
11912
|
const fetchProfile = opts.fetchProfile ?? (async (url, fresh) => {
|
|
11781
11913
|
const r = await fetchUcpProfile(url, { fresh });
|
|
11782
11914
|
return r.ok ? r.profile : r;
|
|
@@ -11788,12 +11920,12 @@ function createUcpCheckoutKit(opts) {
|
|
|
11788
11920
|
const [h, , sig] = attached.split(".");
|
|
11789
11921
|
return { detached: `${h}..${sig}`, attached };
|
|
11790
11922
|
}
|
|
11791
|
-
async function render(s, status = 200) {
|
|
11923
|
+
async function render(s, status = 200, mode = "persist") {
|
|
11792
11924
|
const view = {
|
|
11793
11925
|
ucp: {
|
|
11794
11926
|
version: profile.ucp.version,
|
|
11795
11927
|
capabilities: Object.fromEntries(
|
|
11796
|
-
Object.keys(profile.ucp.capabilities ?? {}).filter((k) => k !== "dev.ucp.common.payment.ap2_mandate" || s._ap2).map((k) => [k, [{ version: profile.ucp.version }]])
|
|
11928
|
+
Object.keys(profile.ucp.capabilities ?? {}).filter((k) => k !== "dev.ucp.common.payment.ap2_mandate" || s._ap2).filter((k) => k !== UCP_ORDER_CAPABILITY).map((k) => [k, [{ version: profile.ucp.version }]])
|
|
11797
11929
|
),
|
|
11798
11930
|
payment_handlers: profile.ucp.payment_handlers
|
|
11799
11931
|
},
|
|
@@ -11802,21 +11934,132 @@ function createUcpCheckoutKit(opts) {
|
|
|
11802
11934
|
currency: s.currency,
|
|
11803
11935
|
line_items: s.line_items,
|
|
11804
11936
|
totals: s.totals,
|
|
11937
|
+
...opts.links?.length && { links: opts.links },
|
|
11805
11938
|
...s.buyer && { buyer: s.buyer },
|
|
11806
11939
|
...s.messages?.length && { messages: s.messages },
|
|
11807
11940
|
...s.continue_url && s.status === "requires_escalation" && { continue_url: s.continue_url },
|
|
11808
11941
|
...s.order && { order: s.order }
|
|
11809
11942
|
};
|
|
11810
|
-
if (s._ap2) {
|
|
11811
|
-
const
|
|
11812
|
-
if (
|
|
11813
|
-
view.ap2 = { merchant_authorization:
|
|
11814
|
-
|
|
11815
|
-
await
|
|
11943
|
+
if (s._ap2 && opts.signingKey) {
|
|
11944
|
+
const viewHash = createHash10("sha256").update(canonicalJson2(view)).digest("hex");
|
|
11945
|
+
if (mode === "reuse" && s._merchantAuth && s._merchantAuthFor === viewHash) {
|
|
11946
|
+
view.ap2 = { merchant_authorization: s._merchantAuth };
|
|
11947
|
+
} else {
|
|
11948
|
+
const auth = await merchantAuthorization(view);
|
|
11949
|
+
if (auth) {
|
|
11950
|
+
view.ap2 = { merchant_authorization: auth.detached };
|
|
11951
|
+
if (mode !== "ephemeral") {
|
|
11952
|
+
s._checkoutJwt = auth.attached;
|
|
11953
|
+
s._merchantAuth = auth.detached;
|
|
11954
|
+
s._merchantAuthFor = viewHash;
|
|
11955
|
+
await store.put(s);
|
|
11956
|
+
}
|
|
11957
|
+
}
|
|
11816
11958
|
}
|
|
11817
11959
|
}
|
|
11818
11960
|
return json2(status, view);
|
|
11819
11961
|
}
|
|
11962
|
+
function busy(s) {
|
|
11963
|
+
return render(
|
|
11964
|
+
{
|
|
11965
|
+
...s,
|
|
11966
|
+
messages: [
|
|
11967
|
+
...s.messages ?? [],
|
|
11968
|
+
ucpError(
|
|
11969
|
+
"checkout_in_progress",
|
|
11970
|
+
"This checkout is already being completed. Retry shortly.",
|
|
11971
|
+
"recoverable"
|
|
11972
|
+
)
|
|
11973
|
+
]
|
|
11974
|
+
},
|
|
11975
|
+
200,
|
|
11976
|
+
"ephemeral"
|
|
11977
|
+
);
|
|
11978
|
+
}
|
|
11979
|
+
async function save(s) {
|
|
11980
|
+
if (s.order) orderIndex.set(s.order.id, s.id);
|
|
11981
|
+
await store.put(s);
|
|
11982
|
+
}
|
|
11983
|
+
function webhookTarget(s) {
|
|
11984
|
+
const w = opts.webhook;
|
|
11985
|
+
if (!w) return null;
|
|
11986
|
+
const url = w.url ?? s._webhookUrl;
|
|
11987
|
+
const privateKey = w.privateKey ?? opts.signingKey?.privateKey ?? void 0;
|
|
11988
|
+
const keyId = w.keyId ?? opts.signingKey?.kid;
|
|
11989
|
+
if (!url || !privateKey || !keyId) return null;
|
|
11990
|
+
return { w, url, privateKey, keyId };
|
|
11991
|
+
}
|
|
11992
|
+
async function deliverOrder(s) {
|
|
11993
|
+
const target = webhookTarget(s);
|
|
11994
|
+
const order = buildUcpOrder(s, { version: profile.ucp.version });
|
|
11995
|
+
if (!target || !order) return null;
|
|
11996
|
+
const r = await sendUcpWebhook({
|
|
11997
|
+
url: target.url,
|
|
11998
|
+
body: order,
|
|
11999
|
+
privateKey: target.privateKey,
|
|
12000
|
+
keyId: target.keyId,
|
|
12001
|
+
businessProfileUrl: target.w.businessProfileUrl ?? new URL("/.well-known/ucp", opts.profile.endpoint).toString(),
|
|
12002
|
+
...target.w.attempts && { attempts: target.w.attempts },
|
|
12003
|
+
...target.w.fetch && { fetch: target.w.fetch }
|
|
12004
|
+
});
|
|
12005
|
+
target.w.onResult?.(r, order);
|
|
12006
|
+
return r;
|
|
12007
|
+
}
|
|
12008
|
+
function emitOrder(s) {
|
|
12009
|
+
void deliverOrder(s).catch(() => void 0);
|
|
12010
|
+
}
|
|
12011
|
+
async function reportOutcome(p, settled) {
|
|
12012
|
+
if (!opts.reportSettlement || !p.orderId) return;
|
|
12013
|
+
await opts.reportSettlement({
|
|
12014
|
+
orderId: p.orderId,
|
|
12015
|
+
status: settled.status,
|
|
12016
|
+
...p.report,
|
|
12017
|
+
...settled.status === "settled" && settled.processorRef && { processorRef: settled.processorRef },
|
|
12018
|
+
...settled.status === "settled" && settled.txHash && { txHash: settled.txHash, chainId: settled.chainId },
|
|
12019
|
+
...settled.status === "failed" && settled.failureCode && { failureCode: settled.failureCode }
|
|
12020
|
+
}).catch(() => void 0);
|
|
12021
|
+
}
|
|
12022
|
+
async function finish(s, result, p, settled) {
|
|
12023
|
+
await reportOutcome(p, settled);
|
|
12024
|
+
delete s._pending;
|
|
12025
|
+
if (settled.status === "failed") {
|
|
12026
|
+
s.status = "ready_for_complete";
|
|
12027
|
+
s.messages = [
|
|
12028
|
+
ucpError("payment_failed", "The payment did not go through.", "requires_buyer_input")
|
|
12029
|
+
];
|
|
12030
|
+
await save(s);
|
|
12031
|
+
return;
|
|
12032
|
+
}
|
|
12033
|
+
const order = await opts.fulfil({ session: s, result });
|
|
12034
|
+
s.order = {
|
|
12035
|
+
...order,
|
|
12036
|
+
...!order.permalink_url && opts.permalinkBase && {
|
|
12037
|
+
permalink_url: `${opts.permalinkBase.replace(/\/+$/, "")}/${encodeURIComponent(order.id)}`
|
|
12038
|
+
}
|
|
12039
|
+
};
|
|
12040
|
+
s.status = "completed";
|
|
12041
|
+
s.messages = [];
|
|
12042
|
+
await save(s);
|
|
12043
|
+
emitOrder(s);
|
|
12044
|
+
}
|
|
12045
|
+
async function claim2(s, from) {
|
|
12046
|
+
if (store.claim) {
|
|
12047
|
+
if (!await store.claim(s.id, from, "complete_in_progress")) return false;
|
|
12048
|
+
s.status = "complete_in_progress";
|
|
12049
|
+
return true;
|
|
12050
|
+
}
|
|
12051
|
+
s.status = "complete_in_progress";
|
|
12052
|
+
await store.put(s);
|
|
12053
|
+
return true;
|
|
12054
|
+
}
|
|
12055
|
+
async function bindAgent(req, result) {
|
|
12056
|
+
const mode = opts.ucpAgentBinding ?? "registered";
|
|
12057
|
+
if (mode === "off") return null;
|
|
12058
|
+
const identities = opts.resolveIdentities ? await opts.resolveIdentities(req, result) : result.agent?.protocolIdentities;
|
|
12059
|
+
if (mode === "registered" && !identities?.ucpProfile) return null;
|
|
12060
|
+
const b = checkUcpAgentBinding(req.headers.get("ucp-agent"), identities);
|
|
12061
|
+
return b.ok ? null : b;
|
|
12062
|
+
}
|
|
11820
12063
|
async function priceLines(lines) {
|
|
11821
12064
|
const out = [];
|
|
11822
12065
|
const messages = [];
|
|
@@ -11909,13 +12152,13 @@ function createUcpCheckoutKit(opts) {
|
|
|
11909
12152
|
ap2: Boolean(neg.capabilities["dev.ucp.common.payment.ap2_mandate"])
|
|
11910
12153
|
};
|
|
11911
12154
|
}
|
|
11912
|
-
async function withIdempotency2(req, body, path, run) {
|
|
12155
|
+
async function withIdempotency2(req, body, path, caller, run) {
|
|
11913
12156
|
const d = await beginIdempotent(idem, {
|
|
11914
12157
|
method: req.method,
|
|
11915
12158
|
path,
|
|
11916
12159
|
body,
|
|
11917
12160
|
key: req.headers.get("idempotency-key") ?? void 0,
|
|
11918
|
-
auth:
|
|
12161
|
+
auth: caller
|
|
11919
12162
|
});
|
|
11920
12163
|
if (d.kind === "reject")
|
|
11921
12164
|
return json2(d.status, { type: "invalid_request", code: d.code, message: d.message });
|
|
@@ -11936,6 +12179,7 @@ function createUcpCheckoutKit(opts) {
|
|
|
11936
12179
|
}
|
|
11937
12180
|
async function complete(req, s, body) {
|
|
11938
12181
|
if (s.status === "completed" || s.status === "canceled") return render(s);
|
|
12182
|
+
if (s.status === "complete_in_progress") return busy(s);
|
|
11939
12183
|
const ap2 = body.ap2;
|
|
11940
12184
|
const checkoutJwt = s._ap2 ? s._checkoutJwt : void 0;
|
|
11941
12185
|
if (s._ap2) {
|
|
@@ -11947,6 +12191,7 @@ function createUcpCheckoutKit(opts) {
|
|
|
11947
12191
|
"unrecoverable"
|
|
11948
12192
|
)
|
|
11949
12193
|
];
|
|
12194
|
+
await store.put(s);
|
|
11950
12195
|
return render(s);
|
|
11951
12196
|
}
|
|
11952
12197
|
const kidResolver = async (kid) => opts.mandateKey ? opts.mandateKey(kid, s) : (s._platformKeys ?? []).find((k) => k.kid === kid) ?? null;
|
|
@@ -11957,71 +12202,106 @@ function createUcpCheckoutKit(opts) {
|
|
|
11957
12202
|
});
|
|
11958
12203
|
if (!v.ok) {
|
|
11959
12204
|
s.messages = [ucpError(v.code, v.error, "unrecoverable")];
|
|
12205
|
+
await store.put(s);
|
|
11960
12206
|
return render(s);
|
|
11961
12207
|
}
|
|
11962
12208
|
}
|
|
11963
|
-
const
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
astraId: req.headers.get("x-astra-id") ?? void 0,
|
|
11967
|
-
attemptId: req.headers.get("x-astra-attempt-id") ?? void 0,
|
|
11968
|
-
...checkoutJwt && { checkoutJwt },
|
|
11969
|
-
valueMajor: total(s) / 100
|
|
11970
|
-
});
|
|
11971
|
-
const held = result.settlementOutcome?.status === "requires_approval" || result.stepUpApproval && result.stepUpApproval.status === "pending";
|
|
11972
|
-
if (held) {
|
|
11973
|
-
s.status = "requires_escalation";
|
|
11974
|
-
s.continue_url = result.stepUpApproval?.approvalUrl ?? opts.fallbackContinueUrl;
|
|
11975
|
-
s.messages = [
|
|
11976
|
-
ucpError(
|
|
11977
|
-
"buyer_approval_required",
|
|
11978
|
-
"The buyer needs to approve this purchase.",
|
|
11979
|
-
"requires_buyer_review"
|
|
11980
|
-
)
|
|
11981
|
-
];
|
|
11982
|
-
await store.put(s);
|
|
11983
|
-
return render(s);
|
|
11984
|
-
}
|
|
11985
|
-
if (!result.identityVerified || !result.policyAllowed) {
|
|
12209
|
+
const instruments = body.payment?.instruments;
|
|
12210
|
+
const instrument = Array.isArray(instruments) && instruments.length === 1 ? instruments[0] : null;
|
|
12211
|
+
if (!instrument || typeof instrument.handler_id !== "string" || !handlerIds.has(instrument.handler_id)) {
|
|
11986
12212
|
s.messages = [
|
|
11987
12213
|
ucpError(
|
|
11988
|
-
"
|
|
11989
|
-
"
|
|
11990
|
-
"
|
|
12214
|
+
"payment_failed",
|
|
12215
|
+
"Send exactly one payment instrument whose handler_id is one of this business\u2019s payment_handlers.",
|
|
12216
|
+
"recoverable",
|
|
12217
|
+
"$.payment.instruments"
|
|
11991
12218
|
)
|
|
11992
12219
|
];
|
|
11993
12220
|
await store.put(s);
|
|
11994
12221
|
return render(s);
|
|
11995
12222
|
}
|
|
11996
|
-
s.status
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
if (opts.reportSettlement && orderId && settled.status !== "pending") {
|
|
12001
|
-
await opts.reportSettlement({
|
|
12002
|
-
orderId,
|
|
12003
|
-
status: settled.status === "settled" ? "settled" : "failed",
|
|
12004
|
-
...orderReportAmount(result, { amountMinor: total(s), currency: s.currency }),
|
|
12005
|
-
...settled.status === "settled" && settled.processorRef && { processorRef: settled.processorRef },
|
|
12006
|
-
...settled.status === "settled" && settled.txHash && { txHash: settled.txHash, chainId: settled.chainId },
|
|
12007
|
-
...settled.status === "failed" && settled.failureCode && { failureCode: settled.failureCode }
|
|
12008
|
-
}).catch(() => void 0);
|
|
12223
|
+
const from = s.status;
|
|
12224
|
+
if (!await claim2(s, from)) {
|
|
12225
|
+
const current = await store.get(s.id);
|
|
12226
|
+
return busy(current ?? s);
|
|
12009
12227
|
}
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12228
|
+
try {
|
|
12229
|
+
const result = await opts.verifyPurchase({
|
|
12230
|
+
session: s,
|
|
12231
|
+
checkoutSessionId: s._astraCheckoutSessionId ?? s.id,
|
|
12232
|
+
astraId: req.headers.get("x-astra-id") ?? void 0,
|
|
12233
|
+
attemptId: req.headers.get("x-astra-attempt-id") ?? void 0,
|
|
12234
|
+
...checkoutJwt && { checkoutJwt },
|
|
12235
|
+
valueMajor: total(s) / 100
|
|
12236
|
+
});
|
|
12237
|
+
const binding2 = await bindAgent(req, result);
|
|
12238
|
+
if (binding2) {
|
|
12239
|
+
s.status = from;
|
|
12240
|
+
s.messages = [ucpError(binding2.code, binding2.message, "unrecoverable")];
|
|
12241
|
+
await store.put(s);
|
|
12242
|
+
return render(s);
|
|
12243
|
+
}
|
|
12244
|
+
if (isHeld(result)) {
|
|
12245
|
+
s.status = "requires_escalation";
|
|
12246
|
+
s.continue_url = result.stepUpApproval?.approvalUrl ?? opts.fallbackContinueUrl;
|
|
12247
|
+
s.messages = [
|
|
12248
|
+
ucpError(
|
|
12249
|
+
"buyer_approval_required",
|
|
12250
|
+
"The buyer needs to approve this purchase.",
|
|
12251
|
+
"requires_buyer_review"
|
|
12252
|
+
)
|
|
12253
|
+
];
|
|
12254
|
+
await store.put(s);
|
|
12255
|
+
return render(s);
|
|
12256
|
+
}
|
|
12257
|
+
if (!result.identityVerified || !result.policyAllowed) {
|
|
12258
|
+
s.status = from;
|
|
12259
|
+
s.messages = [
|
|
12260
|
+
ucpError(
|
|
12261
|
+
"agent_not_authorized",
|
|
12262
|
+
"The agent is not authorized for this purchase.",
|
|
12263
|
+
"unrecoverable"
|
|
12264
|
+
)
|
|
12265
|
+
];
|
|
12266
|
+
await store.put(s);
|
|
12267
|
+
return render(s);
|
|
12268
|
+
}
|
|
12269
|
+
const settled = await opts.settle({ session: s, result, instrument });
|
|
12270
|
+
const pending = {
|
|
12271
|
+
...result.settlementOutcome?.orderId && {
|
|
12272
|
+
orderId: result.settlementOutcome.orderId
|
|
12273
|
+
},
|
|
12274
|
+
report: orderReportAmount(result, { amountMinor: total(s), currency: s.currency }),
|
|
12275
|
+
result: pendingResult(result)
|
|
12276
|
+
};
|
|
12277
|
+
if (settled.status === "pending") {
|
|
12278
|
+
s._pending = pending;
|
|
12279
|
+
s.messages = [];
|
|
12280
|
+
await store.put(s);
|
|
12281
|
+
return render(s);
|
|
12282
|
+
}
|
|
12283
|
+
await finish(s, result, pending, settled);
|
|
12016
12284
|
return render(s);
|
|
12285
|
+
} catch (e) {
|
|
12286
|
+
if (s.status === "complete_in_progress" && !s._pending) {
|
|
12287
|
+
s.status = from;
|
|
12288
|
+
await store.put(s).catch(() => void 0);
|
|
12289
|
+
}
|
|
12290
|
+
throw e;
|
|
12017
12291
|
}
|
|
12018
|
-
s.order = await opts.fulfil({ session: s, result });
|
|
12019
|
-
s.status = "completed";
|
|
12020
|
-
s.messages = [];
|
|
12021
|
-
await store.put(s);
|
|
12022
|
-
return render(s);
|
|
12023
12292
|
}
|
|
12024
|
-
async function
|
|
12293
|
+
async function resolvePending(sessionId, outcome) {
|
|
12294
|
+
const s = await store.get(sessionId);
|
|
12295
|
+
if (!s) return null;
|
|
12296
|
+
if (s.status !== "complete_in_progress" || !s._pending) return s;
|
|
12297
|
+
await finish(s, s._pending.result, s._pending, outcome);
|
|
12298
|
+
return s;
|
|
12299
|
+
}
|
|
12300
|
+
async function sendOrderWebhook(sessionId) {
|
|
12301
|
+
const s = await store.get(sessionId);
|
|
12302
|
+
return s ? deliverOrder(s) : null;
|
|
12303
|
+
}
|
|
12304
|
+
async function handle(req, reqCtx) {
|
|
12025
12305
|
const base = new URL(opts.profile.endpoint).pathname.replace(/\/+$/, "");
|
|
12026
12306
|
const url = new URL(req.url);
|
|
12027
12307
|
const path = url.pathname.startsWith(base) ? url.pathname.slice(base.length) : url.pathname;
|
|
@@ -12035,9 +12315,11 @@ function createUcpCheckoutKit(opts) {
|
|
|
12035
12315
|
const p = await platform(req, body);
|
|
12036
12316
|
if ("error" in p && p.error) return p.error;
|
|
12037
12317
|
const ctx = p;
|
|
12318
|
+
const caller = await resolveCallerScope(req, reqCtx, opts.resolveCallerId) ?? fallbackScope("ucp-profile", ctx.profileUrl);
|
|
12038
12319
|
if (req.method === "POST" && path === "/checkout-sessions") {
|
|
12039
|
-
return withIdempotency2(req, body, path, async () => {
|
|
12320
|
+
return withIdempotency2(req, body, path, caller, async () => {
|
|
12040
12321
|
const priced = await priceLines(parsed.line_items ?? []);
|
|
12322
|
+
const webhookUrl = ctx.plat.ucp.capabilities?.[UCP_ORDER_CAPABILITY]?.[0]?.config?.webhook_url;
|
|
12041
12323
|
const s2 = {
|
|
12042
12324
|
id: `chk_${randomUUID2().replace(/-/g, "")}`,
|
|
12043
12325
|
status: priced.lines.length && !priced.messages.length ? "ready_for_complete" : "incomplete",
|
|
@@ -12046,9 +12328,11 @@ function createUcpCheckoutKit(opts) {
|
|
|
12046
12328
|
totals: priced.totals,
|
|
12047
12329
|
messages: priced.messages,
|
|
12048
12330
|
...parsed.buyer ? { buyer: parsed.buyer } : {},
|
|
12331
|
+
_caller: caller,
|
|
12049
12332
|
_platformProfile: ctx.profileUrl,
|
|
12050
12333
|
_ap2: ctx.ap2,
|
|
12051
12334
|
_platformKeys: ctx.plat.keys ?? [],
|
|
12335
|
+
...typeof webhookUrl === "string" && /^https:\/\//.test(webhookUrl) && { _webhookUrl: webhookUrl },
|
|
12052
12336
|
...req.headers.get("x-astra-checkout-session") && {
|
|
12053
12337
|
_astraCheckoutSessionId: req.headers.get("x-astra-checkout-session").slice(0, 64)
|
|
12054
12338
|
}
|
|
@@ -12067,16 +12351,28 @@ function createUcpCheckoutKit(opts) {
|
|
|
12067
12351
|
return render(s2, 201);
|
|
12068
12352
|
});
|
|
12069
12353
|
}
|
|
12354
|
+
const notFound = (what = "checkout session") => json2(404, { code: "not_found", content: `Unknown ${what}.` });
|
|
12355
|
+
const om = /^\/orders\/([^/]+)$/.exec(path);
|
|
12356
|
+
if (om && ordersOn) {
|
|
12357
|
+
if (req.method !== "GET")
|
|
12358
|
+
return json2(405, { code: "method_not_allowed", content: "Method not allowed." });
|
|
12359
|
+
const orderId = decodeURIComponent(om[1]);
|
|
12360
|
+
const viaIndex = orderIndex.get(orderId);
|
|
12361
|
+
const s2 = store.findByOrderId ? await store.findByOrderId(orderId) : viaIndex ? await store.get(viaIndex) : null;
|
|
12362
|
+
if (!s2 || s2._caller !== caller || s2._platformProfile !== ctx.profileUrl)
|
|
12363
|
+
return notFound("order");
|
|
12364
|
+
const order = buildUcpOrder(s2, { version: profile.ucp.version });
|
|
12365
|
+
return order ? json2(200, order) : notFound("order");
|
|
12366
|
+
}
|
|
12070
12367
|
const m = /^\/checkout-sessions\/([^/]+)(\/complete|\/cancel)?$/.exec(path);
|
|
12071
12368
|
if (!m) return json2(404, { code: "not_found", content: "Unknown operation." });
|
|
12072
12369
|
const s = await store.get(m[1]);
|
|
12073
|
-
if (!s
|
|
12074
|
-
if (
|
|
12075
|
-
return json2(404, { code: "not_found", content: "Unknown checkout session." });
|
|
12076
|
-
if (req.method === "GET" && !m[2]) return render(s);
|
|
12370
|
+
if (!s || s._caller !== caller || s._platformProfile !== ctx.profileUrl) return notFound();
|
|
12371
|
+
if (req.method === "GET" && !m[2]) return render(s, 200, "reuse");
|
|
12077
12372
|
if (req.method === "PUT" && !m[2]) {
|
|
12078
|
-
return withIdempotency2(req, body, path, async () => {
|
|
12373
|
+
return withIdempotency2(req, body, path, caller, async () => {
|
|
12079
12374
|
if (s.status === "completed" || s.status === "canceled") return render(s);
|
|
12375
|
+
if (s.status === "complete_in_progress") return busy(s);
|
|
12080
12376
|
if (parsed.line_items) {
|
|
12081
12377
|
const priced = await priceLines(parsed.line_items);
|
|
12082
12378
|
s.line_items = priced.lines;
|
|
@@ -12090,11 +12386,17 @@ function createUcpCheckoutKit(opts) {
|
|
|
12090
12386
|
});
|
|
12091
12387
|
}
|
|
12092
12388
|
if (req.method === "POST" && m[2] === "/complete") {
|
|
12093
|
-
return withIdempotency2(req, body, path, () => complete(req, s, parsed));
|
|
12389
|
+
return withIdempotency2(req, body, path, caller, () => complete(req, s, parsed));
|
|
12094
12390
|
}
|
|
12095
12391
|
if (req.method === "POST" && m[2] === "/cancel") {
|
|
12096
|
-
return withIdempotency2(req, body, path, async () => {
|
|
12097
|
-
if (s.status
|
|
12392
|
+
return withIdempotency2(req, body, path, caller, async () => {
|
|
12393
|
+
if (s.status === "completed" || s.status === "canceled")
|
|
12394
|
+
return json2(405, {
|
|
12395
|
+
code: "method_not_allowed",
|
|
12396
|
+
content: `A ${s.status} checkout can't be canceled.`
|
|
12397
|
+
});
|
|
12398
|
+
if (s.status === "complete_in_progress") return busy(s);
|
|
12399
|
+
s.status = "canceled";
|
|
12098
12400
|
await store.put(s);
|
|
12099
12401
|
return render(s);
|
|
12100
12402
|
});
|
|
@@ -12104,7 +12406,9 @@ function createUcpCheckoutKit(opts) {
|
|
|
12104
12406
|
return {
|
|
12105
12407
|
handle,
|
|
12106
12408
|
profile,
|
|
12107
|
-
profileHandler: () => json2(200, profile, { "cache-control": "public, max-age=300" })
|
|
12409
|
+
profileHandler: () => json2(200, profile, { "cache-control": "public, max-age=300" }),
|
|
12410
|
+
resolvePending,
|
|
12411
|
+
sendOrderWebhook
|
|
12108
12412
|
};
|
|
12109
12413
|
}
|
|
12110
12414
|
|
|
@@ -12157,7 +12461,7 @@ function x402PaymentMatchesOffer(payment, offered) {
|
|
|
12157
12461
|
}
|
|
12158
12462
|
|
|
12159
12463
|
// src/payments/mpp.ts
|
|
12160
|
-
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
12464
|
+
import { createHash as createHash11, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
12161
12465
|
var b64url = (s) => Buffer.from(s, "utf8").toString("base64url");
|
|
12162
12466
|
function canonical(value) {
|
|
12163
12467
|
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
@@ -12166,27 +12470,29 @@ function canonical(value) {
|
|
|
12166
12470
|
}
|
|
12167
12471
|
return JSON.stringify(value);
|
|
12168
12472
|
}
|
|
12169
|
-
var
|
|
12170
|
-
"realm",
|
|
12171
|
-
"method",
|
|
12172
|
-
"intent",
|
|
12173
|
-
"request",
|
|
12174
|
-
"expires",
|
|
12175
|
-
"digest",
|
|
12176
|
-
"opaque",
|
|
12177
|
-
"header"
|
|
12178
|
-
];
|
|
12473
|
+
var requestSlot = (request) => typeof request === "string" ? request : b64url(canonical(request ?? {}));
|
|
12179
12474
|
function binding(c, secret) {
|
|
12180
|
-
const
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12475
|
+
const slots = [
|
|
12476
|
+
c.realm,
|
|
12477
|
+
c.method,
|
|
12478
|
+
c.intent,
|
|
12479
|
+
requestSlot(c.request),
|
|
12480
|
+
c.expires ?? "",
|
|
12481
|
+
c.digest ?? ""
|
|
12482
|
+
];
|
|
12483
|
+
if (c.header && c.header.toLowerCase() !== "authorization") slots.push(c.header);
|
|
12484
|
+
slots.push(c.opaque ?? "");
|
|
12485
|
+
return createHmac2("sha256", secret).update(slots.join("|")).digest("base64url");
|
|
12184
12486
|
}
|
|
12185
12487
|
var quote = (v) => `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
12186
12488
|
function buildMppChallenge(input, hmacSecret) {
|
|
12187
12489
|
if (!/^[a-z]+$/.test(input.method)) throw new Error("mpp: method must be lowercase ASCII");
|
|
12188
|
-
const { request, ...rest } = input;
|
|
12189
|
-
const c = {
|
|
12490
|
+
const { request, meta, ...rest } = input;
|
|
12491
|
+
const c = {
|
|
12492
|
+
...rest,
|
|
12493
|
+
request: b64url(canonical(request)),
|
|
12494
|
+
...rest.opaque === void 0 && meta !== void 0 && { opaque: b64url(canonical(meta)) }
|
|
12495
|
+
};
|
|
12190
12496
|
const challenge = { id: binding(c, hmacSecret), ...c };
|
|
12191
12497
|
const parts = Object.keys(challenge).filter((k) => challenge[k] !== void 0).map((k) => `${k}=${quote(String(challenge[k]))}`);
|
|
12192
12498
|
return { challenge, header: `Payment ${parts.join(", ")}` };
|
|
@@ -12203,13 +12509,71 @@ function parseMppCredential(header) {
|
|
|
12203
12509
|
return null;
|
|
12204
12510
|
}
|
|
12205
12511
|
}
|
|
12206
|
-
|
|
12512
|
+
var MemoryMppChallengeStore = class {
|
|
12513
|
+
constructor(defaultTtlMs = 60 * 60 * 1e3, now = Date.now) {
|
|
12514
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
12515
|
+
this.now = now;
|
|
12516
|
+
this.seen = /* @__PURE__ */ new Map();
|
|
12517
|
+
}
|
|
12518
|
+
async consume(id, expiresAt) {
|
|
12519
|
+
const t = this.now();
|
|
12520
|
+
for (const [k, exp] of this.seen) if (exp <= t) this.seen.delete(k);
|
|
12521
|
+
if (this.seen.has(id)) return false;
|
|
12522
|
+
this.seen.set(id, Number.isFinite(expiresAt) ? expiresAt : t + this.defaultTtlMs);
|
|
12523
|
+
return true;
|
|
12524
|
+
}
|
|
12525
|
+
};
|
|
12526
|
+
function mppBodyDigest(body) {
|
|
12527
|
+
return `sha-256=${createHash11("sha256").update(body).digest("base64")}`;
|
|
12528
|
+
}
|
|
12529
|
+
function checkChallenge(challenge, hmacSecret, now) {
|
|
12207
12530
|
const { id, ...rest } = challenge;
|
|
12208
12531
|
const expected = Buffer.from(binding(rest, hmacSecret));
|
|
12209
12532
|
const got = Buffer.from(String(id ?? ""));
|
|
12210
|
-
if (expected.length !== got.length || !timingSafeEqual2(expected, got))
|
|
12211
|
-
|
|
12212
|
-
|
|
12533
|
+
if (expected.length !== got.length || !timingSafeEqual2(expected, got))
|
|
12534
|
+
return { ok: false, reason: "invalid_id" };
|
|
12535
|
+
if (challenge.expires && Date.parse(challenge.expires) < now.getTime())
|
|
12536
|
+
return { ok: false, reason: "expired" };
|
|
12537
|
+
return { ok: true };
|
|
12538
|
+
}
|
|
12539
|
+
function verifyMppChallenge(challenge, hmacSecret, nowOrOptions = /* @__PURE__ */ new Date()) {
|
|
12540
|
+
if (nowOrOptions instanceof Date) return checkChallenge(challenge, hmacSecret, nowOrOptions).ok;
|
|
12541
|
+
const opts = nowOrOptions;
|
|
12542
|
+
return (async () => {
|
|
12543
|
+
const base = checkChallenge(challenge, hmacSecret, opts.now ?? /* @__PURE__ */ new Date());
|
|
12544
|
+
if (!base.ok) return base;
|
|
12545
|
+
if (challenge.digest) {
|
|
12546
|
+
if (opts.body === void 0) return { ok: false, reason: "body_required" };
|
|
12547
|
+
const want = Buffer.from(challenge.digest);
|
|
12548
|
+
const got = Buffer.from(mppBodyDigest(opts.body));
|
|
12549
|
+
if (want.length !== got.length || !timingSafeEqual2(want, got))
|
|
12550
|
+
return { ok: false, reason: "digest_mismatch" };
|
|
12551
|
+
}
|
|
12552
|
+
if (opts.store) {
|
|
12553
|
+
const expiresAt = challenge.expires ? Date.parse(challenge.expires) : NaN;
|
|
12554
|
+
if (!await opts.store.consume(challenge.id, expiresAt))
|
|
12555
|
+
return { ok: false, reason: "already_used" };
|
|
12556
|
+
}
|
|
12557
|
+
return { ok: true };
|
|
12558
|
+
})();
|
|
12559
|
+
}
|
|
12560
|
+
function buildMpp402Response(challenges, problem = {}) {
|
|
12561
|
+
const list = Array.isArray(challenges) ? challenges : [challenges];
|
|
12562
|
+
const headers = new Headers({
|
|
12563
|
+
"content-type": "application/problem+json",
|
|
12564
|
+
"cache-control": "no-store"
|
|
12565
|
+
});
|
|
12566
|
+
for (const c of list) headers.append("www-authenticate", typeof c === "string" ? c : c.header);
|
|
12567
|
+
return new Response(
|
|
12568
|
+
JSON.stringify({
|
|
12569
|
+
type: problem.type ?? "about:blank",
|
|
12570
|
+
title: problem.title ?? "Payment Required",
|
|
12571
|
+
status: 402,
|
|
12572
|
+
...problem.detail && { detail: problem.detail },
|
|
12573
|
+
...problem.instance && { instance: problem.instance }
|
|
12574
|
+
}),
|
|
12575
|
+
{ status: 402, headers }
|
|
12576
|
+
);
|
|
12213
12577
|
}
|
|
12214
12578
|
function decodeMppRequest(challenge) {
|
|
12215
12579
|
try {
|
|
@@ -12292,7 +12656,7 @@ async function verifyKyaPayTokens(header, opts) {
|
|
|
12292
12656
|
|
|
12293
12657
|
// src/aep/core.ts
|
|
12294
12658
|
import {
|
|
12295
|
-
createHash as
|
|
12659
|
+
createHash as createHash12,
|
|
12296
12660
|
createPrivateKey,
|
|
12297
12661
|
createPublicKey,
|
|
12298
12662
|
sign,
|
|
@@ -12318,7 +12682,7 @@ function aepCanonicalJson(value) {
|
|
|
12318
12682
|
throw new Error(`AEP canonical JSON: unsupported value of type ${typeof value}`);
|
|
12319
12683
|
}
|
|
12320
12684
|
function digest(input) {
|
|
12321
|
-
return
|
|
12685
|
+
return createHash12("sha256").update(aepCanonicalJson(input), "utf8").digest();
|
|
12322
12686
|
}
|
|
12323
12687
|
function aepHash(input) {
|
|
12324
12688
|
return `sha256:${digest(input).toString("hex")}`;
|
|
@@ -12338,10 +12702,10 @@ function aepPrivateKeyFromSeed(seedBase64) {
|
|
|
12338
12702
|
});
|
|
12339
12703
|
}
|
|
12340
12704
|
function aepPublicKeyFromBase64(raw) {
|
|
12341
|
-
const
|
|
12342
|
-
if (
|
|
12705
|
+
const bytes2 = Buffer.from(raw, "base64");
|
|
12706
|
+
if (bytes2.length !== 32) throw new Error("Ed25519 public key must be 32 bytes");
|
|
12343
12707
|
return createPublicKey({
|
|
12344
|
-
key: Buffer.concat([ED25519_SPKI_PREFIX,
|
|
12708
|
+
key: Buffer.concat([ED25519_SPKI_PREFIX, bytes2]),
|
|
12345
12709
|
format: "der",
|
|
12346
12710
|
type: "spki"
|
|
12347
12711
|
});
|
|
@@ -12460,6 +12824,31 @@ function aepPublicKeyDocument(keys, revocations = []) {
|
|
|
12460
12824
|
// src/acp/checkout-kit.ts
|
|
12461
12825
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12462
12826
|
var ACP_API_VERSION = "2026-04-17";
|
|
12827
|
+
var ACP_SUPPORTED_API_VERSIONS = [ACP_API_VERSION];
|
|
12828
|
+
function acpPaymentHandlers(names) {
|
|
12829
|
+
return names.map((name, i) => ({
|
|
12830
|
+
id: name.split(".").pop(),
|
|
12831
|
+
name,
|
|
12832
|
+
version: ACP_API_VERSION,
|
|
12833
|
+
spec: `https://astrasync.ai/ucp/handlers/${name}`,
|
|
12834
|
+
requires_delegate_payment: false,
|
|
12835
|
+
requires_pci_compliance: false,
|
|
12836
|
+
psp: "astrasync",
|
|
12837
|
+
config_schema: `https://astrasync.ai/ucp/handlers/${name}.json`,
|
|
12838
|
+
instrument_schemas: [],
|
|
12839
|
+
config: {},
|
|
12840
|
+
display_order: i
|
|
12841
|
+
}));
|
|
12842
|
+
}
|
|
12843
|
+
function buildAcpWellKnown(opts) {
|
|
12844
|
+
return {
|
|
12845
|
+
protocol: "acp",
|
|
12846
|
+
api_versions: [...opts.apiVersions ?? ACP_SUPPORTED_API_VERSIONS],
|
|
12847
|
+
endpoint: opts.endpoint,
|
|
12848
|
+
capabilities: { payment: { handlers: acpPaymentHandlers(opts.paymentHandlers) } },
|
|
12849
|
+
...opts.links?.length && { links: opts.links }
|
|
12850
|
+
};
|
|
12851
|
+
}
|
|
12463
12852
|
var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
12464
12853
|
var apiError = (status, code, message, param) => json3(status, { type: "invalid_request", code, message, ...param && { param } });
|
|
12465
12854
|
var total2 = (s) => s.totals.find((t) => t.type === "total")?.amount ?? 0;
|
|
@@ -12467,26 +12856,30 @@ function createAcpCheckoutKit(opts) {
|
|
|
12467
12856
|
const mem = /* @__PURE__ */ new Map();
|
|
12468
12857
|
const store = opts.store ?? {
|
|
12469
12858
|
get: async (id) => mem.get(id) ?? null,
|
|
12470
|
-
put: async (s) => void mem.set(s.id, s)
|
|
12859
|
+
put: async (s) => void mem.set(s.id, s),
|
|
12860
|
+
claim: async (id, from, to) => {
|
|
12861
|
+
const s = mem.get(id);
|
|
12862
|
+
if (!s || s.status !== from) return false;
|
|
12863
|
+
s.status = to;
|
|
12864
|
+
return true;
|
|
12865
|
+
}
|
|
12471
12866
|
};
|
|
12472
12867
|
const idem = {
|
|
12473
12868
|
store: opts.idempotencyStore ?? new MemoryIdempotencyStore(),
|
|
12474
12869
|
profile: "acp",
|
|
12475
12870
|
required: true
|
|
12476
12871
|
};
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
display_order: i
|
|
12489
|
-
}));
|
|
12872
|
+
warnInMemoryStores("ACP", [
|
|
12873
|
+
...opts.store ? [] : ["checkout store"],
|
|
12874
|
+
...opts.idempotencyStore ? [] : ["idempotency store"]
|
|
12875
|
+
]);
|
|
12876
|
+
if (!store.claim) {
|
|
12877
|
+
warnOnce(
|
|
12878
|
+
"acp-claim",
|
|
12879
|
+
"[astrasync] ACP checkout kit: your store has no claim() \u2014 concurrent completes of one session aren't locked. Implement claim(id, from, to) as an atomic compare-and-set."
|
|
12880
|
+
);
|
|
12881
|
+
}
|
|
12882
|
+
const handlers = acpPaymentHandlers(opts.paymentHandlers);
|
|
12490
12883
|
function view(s) {
|
|
12491
12884
|
return {
|
|
12492
12885
|
id: s.id,
|
|
@@ -12541,13 +12934,13 @@ function createAcpCheckoutKit(opts) {
|
|
|
12541
12934
|
]
|
|
12542
12935
|
};
|
|
12543
12936
|
}
|
|
12544
|
-
async function idempotent(req, body, path, run) {
|
|
12937
|
+
async function idempotent(req, body, path, caller, run) {
|
|
12545
12938
|
const d = await beginIdempotent(idem, {
|
|
12546
12939
|
method: req.method,
|
|
12547
12940
|
path,
|
|
12548
12941
|
body,
|
|
12549
12942
|
key: req.headers.get("idempotency-key") ?? void 0,
|
|
12550
|
-
auth:
|
|
12943
|
+
auth: caller
|
|
12551
12944
|
});
|
|
12552
12945
|
if (d.kind === "reject") return apiError(d.status, d.code, d.message);
|
|
12553
12946
|
if (d.kind === "replay")
|
|
@@ -12564,63 +12957,21 @@ function createAcpCheckoutKit(opts) {
|
|
|
12564
12957
|
});
|
|
12565
12958
|
return res;
|
|
12566
12959
|
}
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
if (!
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
});
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
s.messages = [
|
|
12583
|
-
{
|
|
12584
|
-
type: "error",
|
|
12585
|
-
code: "approval_required",
|
|
12586
|
-
content: "The buyer needs to approve this purchase.",
|
|
12587
|
-
severity: "medium",
|
|
12588
|
-
resolution: "requires_buyer_review"
|
|
12589
|
-
}
|
|
12590
|
-
];
|
|
12591
|
-
await store.put(s);
|
|
12592
|
-
return json3(200, view(s));
|
|
12593
|
-
}
|
|
12594
|
-
if (!result.identityVerified || !result.policyAllowed) {
|
|
12595
|
-
s.messages = [
|
|
12596
|
-
{
|
|
12597
|
-
type: "error",
|
|
12598
|
-
code: "intervention_required",
|
|
12599
|
-
content: "The agent is not authorized for this purchase.",
|
|
12600
|
-
severity: "high",
|
|
12601
|
-
resolution: "requires_buyer_review"
|
|
12602
|
-
}
|
|
12603
|
-
];
|
|
12604
|
-
await store.put(s);
|
|
12605
|
-
return json3(200, view(s));
|
|
12606
|
-
}
|
|
12607
|
-
s.status = "complete_in_progress";
|
|
12608
|
-
const settled = await opts.settle({
|
|
12609
|
-
session: s,
|
|
12610
|
-
result,
|
|
12611
|
-
paymentData: body.payment_data
|
|
12612
|
-
});
|
|
12613
|
-
const orderId = result.settlementOutcome?.orderId;
|
|
12614
|
-
if (opts.reportSettlement && orderId && settled.status !== "pending") {
|
|
12615
|
-
await opts.reportSettlement({
|
|
12616
|
-
orderId,
|
|
12617
|
-
status: settled.status === "settled" ? "settled" : "failed",
|
|
12618
|
-
...orderReportAmount(result, { amountMinor: total2(s), currency: s.currency }),
|
|
12619
|
-
...settled.status === "settled" && settled.processorRef && { processorRef: settled.processorRef },
|
|
12620
|
-
...settled.status === "settled" && settled.txHash && { txHash: settled.txHash, chainId: settled.chainId },
|
|
12621
|
-
...settled.status === "failed" && settled.failureCode && { failureCode: settled.failureCode }
|
|
12622
|
-
}).catch(() => void 0);
|
|
12623
|
-
}
|
|
12960
|
+
const inProgress = () => apiError(409, "checkout_in_progress", "This checkout session is already being completed.");
|
|
12961
|
+
async function reportOutcome(p, settled) {
|
|
12962
|
+
if (!opts.reportSettlement || !p.orderId) return;
|
|
12963
|
+
await opts.reportSettlement({
|
|
12964
|
+
orderId: p.orderId,
|
|
12965
|
+
status: settled.status,
|
|
12966
|
+
...p.report,
|
|
12967
|
+
...settled.status === "settled" && settled.processorRef && { processorRef: settled.processorRef },
|
|
12968
|
+
...settled.status === "settled" && settled.txHash && { txHash: settled.txHash, chainId: settled.chainId },
|
|
12969
|
+
...settled.status === "failed" && settled.failureCode && { failureCode: settled.failureCode }
|
|
12970
|
+
}).catch(() => void 0);
|
|
12971
|
+
}
|
|
12972
|
+
async function finish(s, result, p, settled) {
|
|
12973
|
+
await reportOutcome(p, settled);
|
|
12974
|
+
delete s._pending;
|
|
12624
12975
|
if (settled.status === "failed") {
|
|
12625
12976
|
s.status = "ready_for_payment";
|
|
12626
12977
|
s.messages = [
|
|
@@ -12633,26 +12984,126 @@ function createAcpCheckoutKit(opts) {
|
|
|
12633
12984
|
}
|
|
12634
12985
|
];
|
|
12635
12986
|
await store.put(s);
|
|
12636
|
-
return
|
|
12987
|
+
return;
|
|
12637
12988
|
}
|
|
12638
12989
|
const order = await opts.fulfil({ session: s, result });
|
|
12990
|
+
const permalink = order.permalink_url ?? (opts.permalinkBase && `${opts.permalinkBase.replace(/\/+$/, "")}/${encodeURIComponent(order.id)}`);
|
|
12639
12991
|
s.order = {
|
|
12640
12992
|
id: order.id,
|
|
12993
|
+
checkout_session_id: s.id,
|
|
12641
12994
|
status: "confirmed",
|
|
12642
12995
|
currency: s.currency.toLowerCase(),
|
|
12643
12996
|
total: total2(s),
|
|
12644
|
-
line_items:
|
|
12997
|
+
line_items: s.line_items.map((l) => ({
|
|
12998
|
+
id: l.id,
|
|
12999
|
+
item: l.item,
|
|
13000
|
+
name: l.name,
|
|
13001
|
+
quantity: l.quantity,
|
|
13002
|
+
unit_amount: l.unit_amount,
|
|
13003
|
+
totals: l.totals
|
|
13004
|
+
})),
|
|
12645
13005
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12646
|
-
...
|
|
13006
|
+
...permalink && { permalink_url: permalink }
|
|
12647
13007
|
};
|
|
12648
13008
|
s.status = "completed";
|
|
12649
13009
|
s.messages = [];
|
|
12650
13010
|
await store.put(s);
|
|
12651
|
-
return json3(200, view(s));
|
|
12652
13011
|
}
|
|
12653
|
-
async function
|
|
12654
|
-
if (
|
|
12655
|
-
|
|
13012
|
+
async function complete(req, s, body) {
|
|
13013
|
+
if (s.status === "completed" || s.status === "canceled") return json3(200, view(s));
|
|
13014
|
+
if (s.status === "complete_in_progress") return inProgress();
|
|
13015
|
+
if (!body.payment_data)
|
|
13016
|
+
return apiError(400, "missing", "payment_data is required.", "$.payment_data");
|
|
13017
|
+
const from = s.status;
|
|
13018
|
+
if (store.claim) {
|
|
13019
|
+
if (!await store.claim(s.id, from, "complete_in_progress")) return inProgress();
|
|
13020
|
+
s.status = "complete_in_progress";
|
|
13021
|
+
} else {
|
|
13022
|
+
s.status = "complete_in_progress";
|
|
13023
|
+
await store.put(s);
|
|
13024
|
+
}
|
|
13025
|
+
try {
|
|
13026
|
+
const result = await opts.verifyPurchase({
|
|
13027
|
+
session: s,
|
|
13028
|
+
checkoutSessionId: s._astraCheckoutSessionId ?? s.id,
|
|
13029
|
+
astraId: req.headers.get("x-astra-id") ?? void 0,
|
|
13030
|
+
attemptId: req.headers.get("x-astra-attempt-id") ?? void 0,
|
|
13031
|
+
valueMajor: total2(s) / 100
|
|
13032
|
+
});
|
|
13033
|
+
if (isHeld(result)) {
|
|
13034
|
+
s.status = "pending_approval";
|
|
13035
|
+
s.continue_url = result.stepUpApproval?.approvalUrl ?? opts.fallbackContinueUrl;
|
|
13036
|
+
s.messages = [
|
|
13037
|
+
{
|
|
13038
|
+
type: "error",
|
|
13039
|
+
code: "approval_required",
|
|
13040
|
+
content: "The buyer needs to approve this purchase.",
|
|
13041
|
+
severity: "medium",
|
|
13042
|
+
resolution: "requires_buyer_review"
|
|
13043
|
+
}
|
|
13044
|
+
];
|
|
13045
|
+
await store.put(s);
|
|
13046
|
+
return json3(200, view(s));
|
|
13047
|
+
}
|
|
13048
|
+
if (!result.identityVerified || !result.policyAllowed) {
|
|
13049
|
+
s.status = from;
|
|
13050
|
+
s.messages = [
|
|
13051
|
+
{
|
|
13052
|
+
type: "error",
|
|
13053
|
+
code: "intervention_required",
|
|
13054
|
+
content: "The agent is not authorized for this purchase.",
|
|
13055
|
+
severity: "high",
|
|
13056
|
+
resolution: "requires_buyer_review"
|
|
13057
|
+
}
|
|
13058
|
+
];
|
|
13059
|
+
await store.put(s);
|
|
13060
|
+
return json3(200, view(s));
|
|
13061
|
+
}
|
|
13062
|
+
const settled = await opts.settle({
|
|
13063
|
+
session: s,
|
|
13064
|
+
result,
|
|
13065
|
+
paymentData: body.payment_data
|
|
13066
|
+
});
|
|
13067
|
+
const orderId = result.settlementOutcome?.orderId;
|
|
13068
|
+
const pending = {
|
|
13069
|
+
...orderId && { orderId },
|
|
13070
|
+
report: orderReportAmount(result, { amountMinor: total2(s), currency: s.currency }),
|
|
13071
|
+
result: pendingResult(result)
|
|
13072
|
+
};
|
|
13073
|
+
if (settled.status === "pending") {
|
|
13074
|
+
s._pending = pending;
|
|
13075
|
+
s.messages = [];
|
|
13076
|
+
await store.put(s);
|
|
13077
|
+
return json3(200, view(s));
|
|
13078
|
+
}
|
|
13079
|
+
await finish(s, result, pending, settled);
|
|
13080
|
+
return json3(200, view(s));
|
|
13081
|
+
} catch (e) {
|
|
13082
|
+
if (s.status === "complete_in_progress" && !s._pending) {
|
|
13083
|
+
s.status = from;
|
|
13084
|
+
await store.put(s).catch(() => void 0);
|
|
13085
|
+
}
|
|
13086
|
+
throw e;
|
|
13087
|
+
}
|
|
13088
|
+
}
|
|
13089
|
+
async function resolvePending(sessionId, outcome) {
|
|
13090
|
+
const s = await store.get(sessionId);
|
|
13091
|
+
if (!s) return null;
|
|
13092
|
+
if (s.status !== "complete_in_progress" || !s._pending) return s;
|
|
13093
|
+
await finish(s, s._pending.result, s._pending, outcome);
|
|
13094
|
+
return s;
|
|
13095
|
+
}
|
|
13096
|
+
async function route(req, reqCtx) {
|
|
13097
|
+
const version = req.headers.get("api-version");
|
|
13098
|
+
if (!version) return apiError(400, "missing", "The API-Version header is required.");
|
|
13099
|
+
if (!ACP_SUPPORTED_API_VERSIONS.includes(version)) {
|
|
13100
|
+
return json3(400, {
|
|
13101
|
+
type: "invalid_request",
|
|
13102
|
+
code: "unsupported_api_version",
|
|
13103
|
+
message: `API-Version ${version} is not supported.`,
|
|
13104
|
+
supported_versions: [...ACP_SUPPORTED_API_VERSIONS]
|
|
13105
|
+
});
|
|
13106
|
+
}
|
|
12656
13107
|
const url = new URL(req.url);
|
|
12657
13108
|
const base = (opts.basePath ?? "").replace(/\/+$/, "");
|
|
12658
13109
|
const path = url.pathname.startsWith(base) ? url.pathname.slice(base.length) : url.pathname;
|
|
@@ -12663,9 +13114,11 @@ function createAcpCheckoutKit(opts) {
|
|
|
12663
13114
|
} catch {
|
|
12664
13115
|
return apiError(400, "invalid", "Body is not JSON.");
|
|
12665
13116
|
}
|
|
12666
|
-
const caller = req.
|
|
13117
|
+
const caller = await resolveCallerScope(req, reqCtx, opts.resolveCallerId);
|
|
13118
|
+
if (!caller)
|
|
13119
|
+
return apiError(401, "unauthorized", "Send an Authorization header (or X-Astra-Id).");
|
|
12667
13120
|
if (req.method === "POST" && path === "/checkout_sessions") {
|
|
12668
|
-
return idempotent(req, body, path, async () => {
|
|
13121
|
+
return idempotent(req, body, path, caller, async () => {
|
|
12669
13122
|
const items = parsed.line_items ?? parsed.items;
|
|
12670
13123
|
if (!items?.length)
|
|
12671
13124
|
return apiError(400, "missing", "line_items is required.", "$.line_items");
|
|
@@ -12693,8 +13146,9 @@ function createAcpCheckoutKit(opts) {
|
|
|
12693
13146
|
if (req.method === "GET" && !m[2]) return json3(200, view(s));
|
|
12694
13147
|
if (req.method !== "POST") return apiError(405, "unsupported", "Method not allowed.");
|
|
12695
13148
|
if (!m[2]) {
|
|
12696
|
-
return idempotent(req, body, path, async () => {
|
|
13149
|
+
return idempotent(req, body, path, caller, async () => {
|
|
12697
13150
|
if (s.status === "completed" || s.status === "canceled") return json3(200, view(s));
|
|
13151
|
+
if (s.status === "complete_in_progress") return inProgress();
|
|
12698
13152
|
const items = parsed.line_items ?? parsed.items;
|
|
12699
13153
|
if (items) {
|
|
12700
13154
|
const p = await price(items);
|
|
@@ -12709,17 +13163,82 @@ function createAcpCheckoutKit(opts) {
|
|
|
12709
13163
|
return json3(200, view(s));
|
|
12710
13164
|
});
|
|
12711
13165
|
}
|
|
12712
|
-
if (m[2] === "/complete")
|
|
12713
|
-
|
|
12714
|
-
|
|
13166
|
+
if (m[2] === "/complete")
|
|
13167
|
+
return idempotent(req, body, path, caller, () => complete(req, s, parsed));
|
|
13168
|
+
return idempotent(req, body, path, caller, async () => {
|
|
13169
|
+
if (s.status === "completed" || s.status === "canceled")
|
|
13170
|
+
return apiError(405, "method_not_allowed", `A ${s.status} session can't be canceled.`);
|
|
13171
|
+
if (s.status === "complete_in_progress") return inProgress();
|
|
13172
|
+
s.status = "canceled";
|
|
12715
13173
|
await store.put(s);
|
|
12716
13174
|
return json3(200, view(s));
|
|
12717
13175
|
});
|
|
12718
13176
|
}
|
|
12719
|
-
|
|
13177
|
+
async function handle(req, reqCtx) {
|
|
13178
|
+
const res = await route(req, reqCtx);
|
|
13179
|
+
const key = req.headers.get("idempotency-key");
|
|
13180
|
+
const requestId = req.headers.get("request-id");
|
|
13181
|
+
if (!key && !requestId) return res;
|
|
13182
|
+
const headers = new Headers(res.headers);
|
|
13183
|
+
if (key) headers.set("idempotency-key", key);
|
|
13184
|
+
if (requestId) headers.set("request-id", requestId);
|
|
13185
|
+
return new Response(res.body, { status: res.status, headers });
|
|
13186
|
+
}
|
|
13187
|
+
return { handle, resolvePending };
|
|
13188
|
+
}
|
|
13189
|
+
|
|
13190
|
+
// src/adapters/kit-express.ts
|
|
13191
|
+
var bytes = (b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
|
|
13192
|
+
function rawBodyOf(req) {
|
|
13193
|
+
if (req.method === "GET" || req.method === "HEAD") return void 0;
|
|
13194
|
+
const raw = req.rawBody;
|
|
13195
|
+
if (Buffer.isBuffer(raw)) return bytes(raw);
|
|
13196
|
+
if (typeof raw === "string") return raw;
|
|
13197
|
+
if (Buffer.isBuffer(req.body)) return bytes(req.body);
|
|
13198
|
+
if (typeof req.body === "string") return req.body;
|
|
13199
|
+
if (req.body !== void 0 && req.body !== null && typeof req.body === "object") {
|
|
13200
|
+
if (Object.keys(req.body).length === 0) return void 0;
|
|
13201
|
+
warnOnce(
|
|
13202
|
+
"kit-express-parsed-body",
|
|
13203
|
+
"[astrasync] expressHandler: the request body was already parsed \u2014 re-serialising it. Mount express.raw() on the checkout routes (or keep req.rawBody) so signatures and idempotency fingerprints see the exact bytes."
|
|
13204
|
+
);
|
|
13205
|
+
return JSON.stringify(req.body);
|
|
13206
|
+
}
|
|
13207
|
+
return void 0;
|
|
13208
|
+
}
|
|
13209
|
+
function expressHandler(kit, options = {}) {
|
|
13210
|
+
return async (req, res, next) => {
|
|
13211
|
+
try {
|
|
13212
|
+
const host = req.get?.("host") ?? req.headers.host ?? "localhost";
|
|
13213
|
+
const url = `${req.protocol ?? "http"}://${host}${req.originalUrl ?? req.url}`;
|
|
13214
|
+
const headers = new Headers();
|
|
13215
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
13216
|
+
if (v === void 0) continue;
|
|
13217
|
+
if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
|
|
13218
|
+
else headers.set(k, v);
|
|
13219
|
+
}
|
|
13220
|
+
const body = rawBodyOf(req);
|
|
13221
|
+
const request = new Request(url, {
|
|
13222
|
+
method: req.method,
|
|
13223
|
+
headers,
|
|
13224
|
+
...body !== void 0 && { body }
|
|
13225
|
+
});
|
|
13226
|
+
const verified = req.agentVerification?.identityVerified ? req.agentVerification.agent?.astraId : void 0;
|
|
13227
|
+
const callerId = options.callerId ? options.callerId(req) : verified;
|
|
13228
|
+
const response = await kit.handle(request, callerId ? { callerId } : void 0);
|
|
13229
|
+
res.status(response.status);
|
|
13230
|
+
response.headers.forEach((value, name) => {
|
|
13231
|
+
res.setHeader(name, value);
|
|
13232
|
+
});
|
|
13233
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
13234
|
+
} catch (err) {
|
|
13235
|
+
next(err);
|
|
13236
|
+
}
|
|
13237
|
+
};
|
|
12720
13238
|
}
|
|
12721
13239
|
export {
|
|
12722
13240
|
ACP_API_VERSION,
|
|
13241
|
+
ACP_SUPPORTED_API_VERSIONS,
|
|
12723
13242
|
AEP_SPEC_VERSION,
|
|
12724
13243
|
AP2_VCT,
|
|
12725
13244
|
AepChain,
|
|
@@ -12740,6 +13259,7 @@ export {
|
|
|
12740
13259
|
MCP_VERIFIED_HOP_MAX_AGE_MS,
|
|
12741
13260
|
MemoryCheckoutStore,
|
|
12742
13261
|
MemoryIdempotencyStore,
|
|
13262
|
+
MemoryMppChallengeStore,
|
|
12743
13263
|
PLATFORM_AGENT_SIGNATURES,
|
|
12744
13264
|
RUNTIME_SIGNATURES,
|
|
12745
13265
|
RedisIdempotencyStore,
|
|
@@ -12749,10 +13269,12 @@ export {
|
|
|
12749
13269
|
SDK_USER_AGENT,
|
|
12750
13270
|
SettlementReportError,
|
|
12751
13271
|
TRUST_LEVEL_RANGES,
|
|
13272
|
+
UCP_ORDER_CAPABILITY,
|
|
12752
13273
|
UCP_VERSION,
|
|
12753
13274
|
VERIFICATION_STATUS_HEADER,
|
|
12754
13275
|
SDK_VERSION as VERSION,
|
|
12755
13276
|
_resetEdgeConfigCache,
|
|
13277
|
+
acpPaymentHandlers,
|
|
12756
13278
|
aepCanonicalJson,
|
|
12757
13279
|
aepHash,
|
|
12758
13280
|
aepPrivateKeyFromSeed,
|
|
@@ -12767,9 +13289,12 @@ export {
|
|
|
12767
13289
|
authorizeSettlement,
|
|
12768
13290
|
awaitStepUpApproval,
|
|
12769
13291
|
beginIdempotent,
|
|
13292
|
+
buildAcpWellKnown,
|
|
12770
13293
|
buildGuidance,
|
|
13294
|
+
buildMpp402Response,
|
|
12771
13295
|
buildMppChallenge,
|
|
12772
13296
|
buildSdkObservedMetadata,
|
|
13297
|
+
buildUcpOrder,
|
|
12773
13298
|
buildUcpProfile,
|
|
12774
13299
|
buildVerificationUnavailableBody,
|
|
12775
13300
|
buildX402Challenge,
|
|
@@ -12793,6 +13318,7 @@ export {
|
|
|
12793
13318
|
encodeMppReceipt,
|
|
12794
13319
|
encodeX402SettlementResponse,
|
|
12795
13320
|
express_exports as express,
|
|
13321
|
+
expressHandler,
|
|
12796
13322
|
extractApiKeyFormat,
|
|
12797
13323
|
extractCredentials,
|
|
12798
13324
|
extractMcpCredentials,
|
|
@@ -12817,6 +13343,7 @@ export {
|
|
|
12817
13343
|
matchEdgePathRule,
|
|
12818
13344
|
matchPlatformSignature,
|
|
12819
13345
|
matchRoutePattern,
|
|
13346
|
+
mppBodyDigest,
|
|
12820
13347
|
negotiateUcp,
|
|
12821
13348
|
nextjs_exports as nextjs,
|
|
12822
13349
|
normalizeDeclaredSource,
|