mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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" } });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user