acp-handler
Version:
Vercel handler for Agentic Commerce Protocol (ACP) - Build checkout APIs that AI agents like ChatGPT can use to complete purchases
66 lines (64 loc) • 2.22 kB
JavaScript
//#region src/checkout/crypto.ts
function timingSafeEqual(a, b) {
if (a.length !== b.length) return false;
let res = 0;
for (let i = 0; i < a.length; i++) res |= a[i] ^ b[i];
return res === 0;
}
async function hmacSign(body, secret) {
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
name: "HMAC",
hash: "SHA-256"
}, false, ["sign"]);
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(body));
return Buffer.from(new Uint8Array(sig)).toString("hex");
}
async function hmacVerify({ body, secret, signature }) {
const expected = await hmacSign(body, secret);
return timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"));
}
function assertFreshTimestamp(ts, { skewSec = 300 } = {}) {
if (!ts) throw new Error("missing timestamp");
const now = Math.floor(Date.now() / 1e3);
if (Math.abs(now - ts) > skewSec) throw new Error("stale or future timestamp");
}
//#endregion
//#region src/checkout/webhooks/outbound.ts
/**
* Creates an outbound webhook sender with HMAC signing
*
* This is the baseline implementation. For production, consider:
* - Wrapping with Next.js `after()` to avoid blocking responses
* - Using a queue (Vercel Queues, Upstash, etc.) for retry logic
* - Logging failures to a monitoring service
*/
function createOutboundWebhook(config) {
async function sendWebhook(evt) {
const body = JSON.stringify(evt);
const signature = await hmacSign(body, config.secret);
const timestamp = Math.floor(Date.now() / 1e3);
const response = await fetch(config.webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
[`${config.merchantName || "Merchant"}-Signature`]: signature,
"X-Timestamp": String(timestamp)
},
body
});
if (!response.ok) throw new Error(`Webhook failed: ${response.status} ${await response.text()}`);
}
return {
orderCreated: (evt) => sendWebhook({
...evt,
event: "order_created"
}),
orderUpdated: (evt) => sendWebhook({
...evt,
event: "order_updated"
})
};
}
//#endregion
export { assertFreshTimestamp, createOutboundWebhook, hmacSign, hmacVerify, timingSafeEqual };
//# sourceMappingURL=outbound-DPQQzjHi.js.map