From 8d86a493f0da5d4f941e8c1477dbfd2b4d0ad8ae Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 15 Nov 2025 19:30:59 +0000 Subject: [PATCH] Stripe Connect onboarding for creator payouts cgen-ae9d1b4d6da44948955ebc6432921f89 --- api/nexus/payments/payout-setup.ts | 128 +++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 api/nexus/payments/payout-setup.ts diff --git a/api/nexus/payments/payout-setup.ts b/api/nexus/payments/payout-setup.ts new file mode 100644 index 00000000..5d934e0e --- /dev/null +++ b/api/nexus/payments/payout-setup.ts @@ -0,0 +1,128 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import Stripe from "stripe"; +import { getAdminClient } from "../../_supabase"; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "", { + apiVersion: "2024-11-20", +}); + +const apiBase = process.env.VITE_API_BASE || "https://aethex.dev"; + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== "POST") { + return res.status(405).json({ error: "Method not allowed" }); + } + + const admin = getAdminClient(); + + // Only authenticated requests + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ error: "Unauthorized" }); + } + + const token = authHeader.replace("Bearer ", ""); + const { + data: { user }, + error: authError, + } = await admin.auth.getUser(token); + + if (authError || !user) { + return res.status(401).json({ error: "Invalid token" }); + } + + try { + const { type } = req.body; + + if (!type || !["create", "complete"].includes(type)) { + return res.status(400).json({ + error: "Invalid type. Must be 'create' or 'complete'", + }); + } + + if (type === "create") { + // Check if creator already has a Stripe Connect account + const { data: creatorProfile } = await admin + .from("nexus_creator_profiles") + .select("stripe_connect_account_id") + .eq("user_id", user.id) + .single(); + + let accountId = creatorProfile?.stripe_connect_account_id; + + if (!accountId) { + // Create a new Stripe Connect account + const account = await stripe.accounts.create({ + type: "express", + country: "US", + email: user.email || "", + metadata: { + aethex_user_id: user.id, + }, + }); + accountId = account.id; + + // Save account ID + await admin + .from("nexus_creator_profiles") + .update({ stripe_connect_account_id: accountId }) + .eq("user_id", user.id); + } + + // Create onboarding link + const onboardingLink = await stripe.accountLinks.create({ + account: accountId, + type: "account_onboarding", + refresh_url: `${apiBase}/dashboard/nexus?tab=profile&stripe_refresh=true`, + return_url: `${apiBase}/dashboard/nexus?tab=profile&stripe_complete=true`, + }); + + return res.status(200).json({ + onboardingUrl: onboardingLink.url, + accountId, + }); + } + + if (type === "complete") { + // Verify onboarding is complete + const { data: creatorProfile } = await admin + .from("nexus_creator_profiles") + .select("stripe_connect_account_id") + .eq("user_id", user.id) + .single(); + + if (!creatorProfile?.stripe_connect_account_id) { + return res + .status(400) + .json({ error: "No Stripe Connect account found" }); + } + + const account = await stripe.accounts.retrieve( + creatorProfile.stripe_connect_account_id, + ); + + if (!account.charges_enabled || !account.payouts_enabled) { + return res.status(400).json({ + error: "Account setup not complete. Please complete all required steps.", + chargesEnabled: account.charges_enabled, + payoutsEnabled: account.payouts_enabled, + }); + } + + // Update creator profile to mark as verified + await admin + .from("nexus_creator_profiles") + .update({ verified: true, stripe_account_verified: true }) + .eq("user_id", user.id); + + return res.status(200).json({ + success: true, + verified: true, + message: "Stripe Connect account setup complete", + }); + } + } catch (error: any) { + console.error("Payout setup error:", error); + return res.status(500).json({ error: error?.message || "Server error" }); + } +}