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", }; 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 { booking_id, amount_cents } = await req.json(); if (!booking_id || !amount_cents || amount_cents <= 0) { return new Response(JSON.stringify({ error: "booking_id and positive amount_cents required" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } const { data: booking, error: bErr } = await supabase .from("amenity_bookings") .select("*, amenities(name), associations(name)") .eq("id", booking_id).single(); if (bErr || !booking) return new Response(JSON.stringify({ error: "Booking not found" }), { status: 404, headers: { ...corsHeaders, "Content-Type": "application/json" } }); if (!booking.guest_email) return new Response(JSON.stringify({ error: "Booking has no contact email" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }); // Find Stripe mapping let { data: mapping } = await supabase.from("stripe_account_mappings").select("*").eq("association_id", booking.association_id).eq("is_active", true).maybeSingle(); if (!mapping) { const { data: company } = await supabase.from("stripe_account_mappings").select("*").is("association_id", null).eq("is_active", true).maybeSingle(); mapping = company; } if (!mapping?.stripe_secret_key) return new Response(JSON.stringify({ error: "No Stripe gateway configured" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }); // Gross-up to pass processing fees to payer const totalCents = Math.round((amount_cents + 30) / (1 - 0.029)); const origin = req.headers.get("origin") || "https://avria.cloud"; const params = new URLSearchParams(); params.append("mode", "payment"); params.append("line_items[0][price_data][currency]", "usd"); params.append("line_items[0][price_data][unit_amount]", String(totalCents)); params.append("line_items[0][price_data][product_data][name]", `Booking: ${booking.title || booking.amenities?.name || "Amenity"}`); params.append("line_items[0][quantity]", "1"); params.append("customer_email", booking.guest_email); params.append("success_url", `${origin}/booking/${booking.id}?paid=1`); params.append("cancel_url", `${origin}/booking/${booking.id}`); params.append("metadata[booking_id]", booking.id); params.append("metadata[type]", "amenity_booking_link"); const stripeResp = await fetch("https://api.stripe.com/v1/checkout/sessions", { method: "POST", headers: { Authorization: `Bearer ${mapping.stripe_secret_key}`, "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString(), }); const session = await stripeResp.json(); if (!stripeResp.ok) return new Response(JSON.stringify({ error: session.error?.message || "Stripe error" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }); await supabase.from("amenity_bookings").update({ payment_status: "link_sent", payment_amount_cents: amount_cents, payment_link_url: session.url, }).eq("id", booking.id); // Send email with link via existing transactional template try { await supabase.functions.invoke("send-transactional-email", { body: { templateName: "amenity-booking-confirmation", recipientEmail: booking.guest_email, idempotencyKey: `booking-payment-link-${booking.id}-${Date.now()}`, templateData: { guestName: booking.guest_name, amenityName: booking.amenities?.name || "Amenity", associationName: booking.associations?.name || "", bookingDate: booking.booking_date, startTime: booking.start_time || "", status: "payment_due", confirmationLink: session.url, }, }, }); } catch (e) { console.error("email send failed", e); } return new Response(JSON.stringify({ success: true, checkout_url: session.url }), { headers: { ...corsHeaders, "Content-Type": "application/json" } }); } catch (e: any) { return new Response(JSON.stringify({ error: e.message || "Internal error" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } });