Rocky_Mountain_Vending/app/api/ebay/notifications/route.ts

159 lines
4 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server"
import {
computeEbayChallengeResponse,
getEbayNotificationEndpoint,
getEbayNotificationVerificationToken,
verifyEbayNotificationSignature,
} from "@/lib/ebay-notifications"
export const runtime = "nodejs"
type EbayNotificationPayload = {
metadata?: {
topic?: string
schemaVersion?: string
deprecated?: boolean
}
notification?: {
notificationId?: string
eventDate?: string
publishDate?: string
publishAttemptCount?: number
data?: {
username?: string
userId?: string
eiasToken?: string
}
}
}
function parseNotificationBody(rawBody: string) {
if (!rawBody.trim()) {
return null
}
try {
return JSON.parse(rawBody) as EbayNotificationPayload
} catch {
return null
}
}
function getChallengeCode(request: NextRequest) {
return (
request.nextUrl.searchParams.get("challenge_code") ||
request.nextUrl.searchParams.get("challengeCode") ||
""
).trim()
}
export async function GET(request: NextRequest) {
const challengeCode = getChallengeCode(request)
if (!challengeCode) {
return NextResponse.json(
{ error: "Missing challenge_code query parameter." },
{ status: 400 }
)
}
const verificationToken = getEbayNotificationVerificationToken()
if (!verificationToken) {
return NextResponse.json(
{ error: "EBAY_NOTIFICATION_VERIFICATION_TOKEN is not configured." },
{ status: 500 }
)
}
const endpoint = getEbayNotificationEndpoint(request.url)
if (!endpoint) {
return NextResponse.json(
{ error: "EBAY_NOTIFICATION_ENDPOINT is not configured." },
{ status: 500 }
)
}
const challengeResponse = computeEbayChallengeResponse({
challengeCode,
endpoint,
verificationToken,
})
return NextResponse.json(
{ challengeResponse },
{
headers: {
"Cache-Control": "no-store",
},
}
)
}
export async function POST(request: NextRequest) {
const body = await request.text()
const signatureHeader = request.headers.get("x-ebay-signature")
try {
const verification = await verifyEbayNotificationSignature({
body,
signatureHeader,
})
if (!verification.verified) {
if (
verification.reason ===
"Notification verification credentials are not configured."
) {
console.warn(
"[ebay/notifications] accepted notification without signature verification",
{
reason: verification.reason,
}
)
const payload = parseNotificationBody(body)
const notification = payload?.notification
console.info(
"[ebay/notifications] accepted notification without verification",
{
topic: payload?.metadata?.topic || "unknown",
notificationId: notification?.notificationId || "unknown",
publishAttemptCount: notification?.publishAttemptCount ?? null,
}
)
return new NextResponse(null, { status: 204 })
}
console.warn("[ebay/notifications] signature rejected", {
reason: verification.reason,
})
return NextResponse.json({ error: verification.reason }, { status: 412 })
}
const payload = parseNotificationBody(body)
const notification = payload?.notification
console.info("[ebay/notifications] accepted notification", {
keyId: verification.keyId,
topic: payload?.metadata?.topic || "unknown",
notificationId: notification?.notificationId || "unknown",
publishAttemptCount: notification?.publishAttemptCount ?? null,
})
return new NextResponse(null, { status: 204 })
} catch (error) {
console.error("[ebay/notifications] failed to process notification", {
error: error instanceof Error ? error.message : String(error),
})
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "Failed to verify eBay notification.",
},
{ status: 500 }
)
}
}