130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { eq } from "drizzle-orm";
|
|
|
|
import { db } from "@/lib/db";
|
|
import { stripeAccounts } from "@/lib/schema";
|
|
import { getUserFromSession } from "@/lib/auth/get-user";
|
|
|
|
type StripeOAuthResponse = {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
stripe_user_id: string;
|
|
scope: string;
|
|
};
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const user = await getUserFromSession();
|
|
|
|
if (!user) {
|
|
return NextResponse.redirect(
|
|
new URL("/login", process.env.NEXT_PUBLIC_BASE_URL)
|
|
);
|
|
}
|
|
|
|
const { searchParams } = new URL(req.url);
|
|
const code = searchParams.get("code");
|
|
const error = searchParams.get("error");
|
|
|
|
if (error) {
|
|
console.error("Stripe OAuth error:", error);
|
|
return NextResponse.redirect(
|
|
new URL(
|
|
"/connect/stripe?error=oauth_failed",
|
|
process.env.NEXT_PUBLIC_BASE_URL
|
|
)
|
|
);
|
|
}
|
|
|
|
if (!code) {
|
|
return NextResponse.json(
|
|
{ error: "Missing OAuth code" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
// Exchange OAuth code for Stripe account
|
|
// --------------------------------------------------
|
|
const tokenRes = await fetch("https://connect.stripe.com/oauth/token", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: new URLSearchParams({
|
|
grant_type: "authorization_code",
|
|
code,
|
|
client_secret: process.env.STRIPE_SECRET_KEY!,
|
|
}),
|
|
});
|
|
|
|
if (!tokenRes.ok) {
|
|
const text = await tokenRes.text();
|
|
console.error("Stripe token exchange failed:", text);
|
|
|
|
return NextResponse.redirect(
|
|
new URL(
|
|
"/connect/stripe?error=token_exchange_failed",
|
|
process.env.NEXT_PUBLIC_BASE_URL
|
|
)
|
|
);
|
|
}
|
|
|
|
const data = (await tokenRes.json()) as StripeOAuthResponse;
|
|
const stripeAccountId = data.stripe_user_id;
|
|
|
|
// --------------------------------------------------
|
|
// Check for existing Stripe account connection
|
|
// --------------------------------------------------
|
|
const existing = await db
|
|
.select()
|
|
.from(stripeAccounts)
|
|
.where(eq(stripeAccounts.stripeAccountId, stripeAccountId))
|
|
.limit(1);
|
|
|
|
if (existing.length > 0) {
|
|
const record = existing[0];
|
|
|
|
// Same user → idempotent success
|
|
if (record.userId === user.id) {
|
|
console.log("Stripe already connected for this user", {
|
|
stripeAccountId,
|
|
userId: user.id,
|
|
});
|
|
|
|
return NextResponse.redirect(
|
|
new URL("/connect/stripe/success", process.env.APP_URL)
|
|
);
|
|
}
|
|
|
|
// Different user → block
|
|
console.warn("Stripe account already linked to another user", {
|
|
stripeAccountId,
|
|
existingUserId: record.userId,
|
|
currentUserId: user.id,
|
|
});
|
|
|
|
return NextResponse.redirect(
|
|
new URL(
|
|
"/connect/stripe?error=already_connected",
|
|
process.env.NEXT_PUBLIC_BASE_URL
|
|
)
|
|
);
|
|
}
|
|
|
|
// --------------------------------------------------
|
|
// Insert new Stripe connection
|
|
// --------------------------------------------------
|
|
await db.insert(stripeAccounts).values({
|
|
userId: user.id,
|
|
stripeAccountId,
|
|
// accessToken / refreshToken later if you decide to store them
|
|
});
|
|
|
|
console.log("Stripe OAuth connected", {
|
|
stripeAccountId,
|
|
userId: user.id,
|
|
scope: data.scope,
|
|
});
|
|
|
|
return NextResponse.redirect(
|
|
new URL("/connect/stripe/success", process.env.APP_URL)
|
|
);
|
|
}
|