mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
4.7 KiB
TypeScript
87 lines
4.7 KiB
TypeScript
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" } });
|
|
}
|
|
});
|