From 9ab5771f46b952308eb0ba7a3a1d4297580b64ad Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 15 Nov 2025 19:30:27 +0000 Subject: [PATCH] Create Stripe payment intent for NEXUS contract cgen-00207dcba184460dabd24603c97461bf --- api/nexus/payments/create-intent.ts | 127 ++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 api/nexus/payments/create-intent.ts diff --git a/api/nexus/payments/create-intent.ts b/api/nexus/payments/create-intent.ts new file mode 100644 index 00000000..5007c084 --- /dev/null +++ b/api/nexus/payments/create-intent.ts @@ -0,0 +1,127 @@ +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", +}); + +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 { opportunityId, creatorId, amount, contractType } = req.body; + + if (!opportunityId || !creatorId || !amount || !contractType) { + return res.status(400).json({ + error: + "Missing required fields: opportunityId, creatorId, amount, contractType", + }); + } + + // Verify the user is the client who posted the opportunity + const { data: opportunity, error: oppError } = await admin + .from("nexus_opportunities") + .select("posted_by, title") + .eq("id", opportunityId) + .single(); + + if (oppError || !opportunity || opportunity.posted_by !== user.id) { + return res + .status(403) + .json({ error: "Not authorized to post contracts for this opportunity" }); + } + + // Verify the creator has applied + const { data: application, error: appError } = await admin + .from("nexus_applications") + .select("id, status") + .eq("opportunity_id", opportunityId) + .eq("creator_id", creatorId) + .single(); + + if (appError || !application || application.status !== "accepted") { + return res + .status(400) + .json({ error: "Creator application not accepted" }); + } + + // Calculate commission (20% to AeThex) + const commissionPercent = 20; + const commissionAmount = Math.round((amount * commissionPercent) / 100); + const creatorPayout = Math.round(amount - commissionAmount); + + // Create Stripe payment intent + const paymentIntent = await stripe.paymentIntents.create({ + amount: Math.round(amount * 100), // Convert to cents + currency: "usd", + metadata: { + opportunityId, + creatorId, + clientId: user.id, + contractType, + }, + description: `NEXUS Contract: ${opportunity.title}`, + }); + + // Create contract record + const { data: contract, error: contractError } = await admin + .from("nexus_contracts") + .insert({ + opportunity_id: opportunityId, + creator_id: creatorId, + client_id: user.id, + title: opportunity.title, + contract_type: contractType, + total_amount: amount, + aethex_commission_percent: commissionPercent, + aethex_commission_amount: commissionAmount, + creator_payout_amount: creatorPayout, + status: "pending", + }) + .select() + .single(); + + if (contractError) { + return res.status(500).json({ error: contractError.message }); + } + + // Store payment intent ID in contract + await admin + .from("nexus_contracts") + .update({ stripe_payment_intent_id: paymentIntent.id }) + .eq("id", contract.id); + + return res.status(200).json({ + clientSecret: paymentIntent.client_secret, + contractId: contract.id, + paymentIntentId: paymentIntent.id, + amount, + commissionAmount, + creatorPayout, + }); + } catch (error: any) { + console.error("Payment intent error:", error); + return res.status(500).json({ error: error?.message || "Server error" }); + } +}