// payai-auth.ts — zero dependencies, Web Crypto API only
function base64UrlEncode(data: Uint8Array): string {
let binary = "";
for (let i = 0; i < data.byteLength; i++) {
binary += String.fromCharCode(data[i]);
}
return btoa(binary)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
function base64UrlEncodeString(str: string): string {
return base64UrlEncode(new TextEncoder().encode(str));
}
function base64ToUint8Array(base64: string): Uint8Array {
const standardized = base64.replace(/-/g, "+").replace(/_/g, "/");
const binary = atob(standardized);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
function normalizeSecret(secret: string): string {
const trimmed = secret.trim();
return trimmed.startsWith("payai_sk_")
? trimmed.slice("payai_sk_".length)
: trimmed;
}
export async function generatePayAIJwt(
apiKeyId: string,
apiKeySecret: string,
): Promise<string> {
const now = Math.floor(Date.now() / 1000);
const header = JSON.stringify({
alg: "EdDSA",
typ: "JWT",
kid: apiKeyId,
});
const payload = JSON.stringify({
sub: apiKeyId,
iss: "payai-merchant",
iat: now,
exp: now + 120,
jti: crypto.randomUUID(),
});
const headerB64 = base64UrlEncodeString(header);
const payloadB64 = base64UrlEncodeString(payload);
const message = `${headerB64}.${payloadB64}`;
const keyBytes = base64ToUint8Array(normalizeSecret(apiKeySecret));
const privateKey = await crypto.subtle.importKey(
"pkcs8",
keyBytes.buffer,
{ name: "Ed25519" },
false,
["sign"],
);
const signature = await crypto.subtle.sign(
"Ed25519",
privateKey,
new TextEncoder().encode(message),
);
return `${message}.${base64UrlEncode(new Uint8Array(signature))}`;
}
// --- Usage ---
const jwt = await generatePayAIJwt(
"your-api-key-id",
"payai_sk_your-api-key-secret",
);
const response = await fetch(
"https://facilitator.payai.network/verify",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${jwt}`,
},
body: JSON.stringify({
x402Version: 2,
paymentPayload: {
/* ... */
},
paymentRequirements: {
/* ... */
},
}),
},
);