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 supabase = createClient( Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, ); const authHeader = req.headers.get("Authorization"); if (!authHeader) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const { data: { user }, error: authError } = await supabase.auth.getUser( authHeader.replace("Bearer ", ""), ); if (authError || !user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const { payment_intent_id } = await req.json(); if (!payment_intent_id) { return new Response(JSON.stringify({ error: "payment_intent_id required" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } // Load the stripe_payments row created when intent was made const { data: payment, error: pErr } = await supabase .from("stripe_payments") .select("*") .eq("stripe_payment_intent_id", payment_intent_id) .maybeSingle(); if (pErr || !payment) { return new Response(JSON.stringify({ error: "Payment record not found" }), { status: 404, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } // Get Stripe secret key for the association const { data: mapping } = await supabase .from("stripe_account_mappings") .select("stripe_secret_key, stripe_account_id") .eq("association_id", payment.association_id) .eq("is_active", true) .maybeSingle(); if (!mapping?.stripe_secret_key) { return new Response(JSON.stringify({ error: "Stripe not configured for association" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } // Verify with Stripe that the PaymentIntent succeeded (or is processing for ACH) const piRes = await fetch(`https://api.stripe.com/v1/payment_intents/${payment_intent_id}`, { headers: { Authorization: `Bearer ${mapping.stripe_secret_key}`, ...(mapping.stripe_account_id?.startsWith("acct_") ? { "Stripe-Account": mapping.stripe_account_id } : {}), }, }); const pi = await piRes.json(); if (!piRes.ok) { console.error("Stripe lookup error:", pi); return new Response(JSON.stringify({ error: pi.error?.message || "Stripe error" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const status = pi.status as string; const recordable = status === "succeeded" || status === "processing"; if (!recordable) { await supabase.from("stripe_payments") .update({ status, updated_at: new Date().toISOString() }) .eq("id", payment.id); return new Response(JSON.stringify({ ok: false, status }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } // Update stripe_payments status await supabase.from("stripe_payments") .update({ status, updated_at: new Date().toISOString() }) .eq("id", payment.id); // Idempotency: skip if a ledger entry already exists for this PI const { data: existing } = await supabase .from("owner_ledger_entries") .select("id") .eq("reference_type", "stripe_payment") .eq("reference_id", payment_intent_id) .maybeSingle(); if (existing) { return new Response(JSON.stringify({ ok: true, already_recorded: true }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (!payment.owner_id) { return new Response(JSON.stringify({ ok: true, skipped: "no owner" }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const netAmount = Number(payment.amount_cents) / 100; const today = new Date().toISOString().split("T")[0]; const methodLabel = payment.payment_method_type === "us_bank_account" ? "ACH" : "Card"; const { error: ledgerErr } = await supabase.from("owner_ledger_entries").insert({ owner_id: payment.owner_id, association_id: payment.association_id, unit_id: payment.unit_id, date: today, transaction_type: "payment", description: `Online Payment (${methodLabel}) — ${payment.description || "Assessment"}`, debit: 0, credit: netAmount, reference_type: "stripe_payment", reference_id: payment_intent_id, }); if (ledgerErr) { console.error("Ledger insert error:", ledgerErr); return new Response(JSON.stringify({ error: ledgerErr.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ ok: true, recorded: true, status }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } catch (err) { console.error("record-stripe-payment error:", err); return new Response(JSON.stringify({ error: (err as Error).message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } });