import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.39.3"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version", }; serve(async (req) => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } try { const supabaseUrl = Deno.env.get("SUPABASE_URL")!; const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; const supabase = createClient(supabaseUrl, supabaseServiceKey); // Verify the calling user const authHeader = req.headers.get("Authorization"); if (!authHeader) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const token = authHeader.replace("Bearer ", ""); const { data: { user }, error: authError } = await supabase.auth.getUser(token); if (authError || !user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const body = await req.json(); const { amount_cents, association_id, owner_id, unit_id, description, payment_method_type, } = body; if (!amount_cents || amount_cents <= 0) { return new Response(JSON.stringify({ error: "Invalid amount" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (!association_id) { return new Response(JSON.stringify({ error: "association_id is required" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } // Get Stripe mapping for this association const { data: mapping, error: mappingError } = await supabase .from("stripe_account_mappings") .select("*") .eq("association_id", association_id) .eq("is_active", true) .maybeSingle(); if (mappingError || !mapping) { return new Response( JSON.stringify({ error: "No active Stripe configuration found for this association." }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } const stripeSecretKey = mapping.stripe_secret_key; if (!stripeSecretKey) { return new Response( JSON.stringify({ error: "Stripe secret key is not configured for this association." }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } const stripeHeaders: Record = { Authorization: `Bearer ${stripeSecretKey}`, "Content-Type": "application/x-www-form-urlencoded", }; if (mapping.stripe_account_id?.startsWith("acct_")) { stripeHeaders["Stripe-Account"] = mapping.stripe_account_id; } // Calculate fees if pass_processing_fee is on let feeCents = 0; const isAch = payment_method_type === "us_bank_account"; if (mapping.pass_processing_fee) { if (isAch) { feeCents = Math.min(Math.ceil(amount_cents * 0.008), 500); } else { const feePercent = Number(mapping.processing_fee_percent) || 0.029; const feeFixed = Number(mapping.processing_fee_fixed_cents) || 30; const grossed = Math.ceil((amount_cents + feeFixed) / (1 - feePercent)); feeCents = grossed - amount_cents; } } const totalCents = amount_cents + feeCents; // Create PaymentIntent via Stripe API const stripeParams = new URLSearchParams(); stripeParams.append("amount", String(totalCents)); stripeParams.append("currency", "usd"); stripeParams.append("description", description || "HOA Assessment Payment"); stripeParams.append("metadata[association_id]", association_id); stripeParams.append("metadata[user_id]", user.id); if (owner_id) stripeParams.append("metadata[owner_id]", owner_id); if (unit_id) stripeParams.append("metadata[unit_id]", unit_id); stripeParams.append("metadata[net_amount_cents]", String(amount_cents)); stripeParams.append("metadata[fee_cents]", String(feeCents)); if (isAch) { stripeParams.append("payment_method_types[]", "us_bank_account"); } else { stripeParams.append("automatic_payment_methods[enabled]", "true"); } const stripeRes = await fetch("https://api.stripe.com/v1/payment_intents", { method: "POST", headers: stripeHeaders, body: stripeParams.toString(), }); const stripeData = await stripeRes.json(); if (!stripeRes.ok) { console.error("Stripe error:", stripeData); return new Response( JSON.stringify({ error: stripeData.error?.message || "Stripe API error" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Record the payment in our database await supabase.from("stripe_payments").insert({ association_id, owner_id: owner_id || null, unit_id: unit_id || null, stripe_payment_intent_id: stripeData.id, amount_cents, fee_cents: feeCents, total_cents: totalCents, payment_method_type: isAch ? "us_bank_account" : "card", status: "pending", description: description || "HOA Assessment Payment", }); return new Response( JSON.stringify({ clientSecret: stripeData.client_secret, paymentIntentId: stripeData.id, totalCents, feeCents, }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } catch (err) { console.error("Error in create-payment-intent:", err); return new Response( JSON.stringify({ error: err.message || "Internal server error" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } });