juntekim.com/stripe_to_invoice/app/api/xero/callback/route.ts
2026-01-18 16:53:06 +00:00

102 lines
2.4 KiB
TypeScript

import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { xeroConnections } from "@/lib/schema/xeroConnections";
import { eq } from "drizzle-orm";
type XeroTokenResponse = {
access_token: string;
refresh_token: string;
};
type XeroTenant = {
tenantId: string;
};
export async function GET(req: NextRequest) {
const cookieStore = await cookies();
const session = cookieStore.get("session");
// Must be logged in
if (!session) {
return NextResponse.redirect(
new URL("/login", process.env.APP_URL)
);
}
const userId = session.value;
const { searchParams } = new URL(req.url);
const code = searchParams.get("code");
if (!code) {
return NextResponse.json(
{ error: "Missing OAuth code" },
{ status: 400 }
);
}
// Exchange code for token
const tokenRes = await fetch("https://identity.xero.com/connect/token", {
method: "POST",
headers: {
Authorization:
"Basic " +
Buffer.from(
`${process.env.XERO_CLIENT_ID}:${process.env.XERO_CLIENT_SECRET}`
).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: process.env.XERO_REDIRECT_URI!,
}),
});
if (!tokenRes.ok) {
const text = await tokenRes.text();
console.error("Xero token exchange failed:", text);
return NextResponse.redirect(
new URL("/connect/xero?error=token_failed", process.env.APP_URL)
);
}
const tokenData = (await tokenRes.json()) as XeroTokenResponse;
// Fetch connected tenants (organisations)
const tenantRes = await fetch(
"https://api.xero.com/connections",
{
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
},
}
);
const tenants = (await tenantRes.json()) as XeroTenant[];
const tenantId = tenants[0]?.tenantId;
if (!tenantId) {
return NextResponse.json(
{ error: "No Xero organisation found" },
{ status: 400 }
);
}
// Save user ↔ tenant (minimal MVP)
await db
.insert(xeroConnections)
.values({
userId,
tenantId,
})
.onConflictDoUpdate({
target: xeroConnections.userId,
set: { tenantId },
});
return NextResponse.redirect(
new URL("/connect/xero/success", process.env.APP_URL)
);
}