Files
site/supabase/functions/send-order-confirmation-email/index.js
2026-01-03 21:11:17 +01:00

291 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable no-undef */
// Edge Function: send-order-confirmation-email
// Self-host Supabase (Docker)
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
import Stripe from "https://esm.sh/stripe@12.0.0";
const RESEND_API_KEY = Deno.env.get("RESEND_API_KEY");
const STRIPE_SECRET_KEY = Deno.env.get("STRIPE_SECRET_KEY") || "";
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") || "";
const SUPABASE_SERVICE_ROLE_KEY =
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || "";
const ADMIN_EMAIL = Deno.env.get("ADMIN_EMAIL") || "";
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: "2022-11-15",
httpClient: Stripe.createFetchHttpClient(),
});
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("ok", { headers: corsHeaders });
}
try {
const { orderData, sessionId } = await req.json();
// 1⃣ Récupérer l'email vérifié depuis la session Stripe
let customerEmail = null;
if (sessionId && STRIPE_SECRET_KEY) {
try {
const session = await stripe.checkout.sessions.retrieve(sessionId);
// Priorité : customer_details.email puis customer_email
customerEmail =
session?.customer_details?.email || session?.customer_email;
} catch (stripeError) {
console.error("Error retrieving Stripe session:", stripeError);
// Fallback : email envoyé par le front
customerEmail = orderData?.email;
}
} else {
// Sans sessionId (tests par ex.)
customerEmail = orderData?.email;
}
if (!customerEmail) {
console.warn("No email found in Stripe session or order data.");
// On continue quand même : la commande sera enregistrée mais sans email
}
// 2⃣ Client Supabase (SERVICE ROLE pour pouvoir écrire dans la table)
const supabaseClient = createClient(
SUPABASE_URL,
SUPABASE_SERVICE_ROLE_KEY,
{
global: {
headers: {
Authorization: req.headers.get("Authorization") ?? "",
},
},
},
);
// 3⃣ Enregistrer la commande dans Supabase
const finalOrderData = {
...orderData,
customer_email: customerEmail,
session_id: sessionId,
status: "En attente de traitement",
created_at: new Date().toISOString(),
};
const { data: order, error: orderError } = await supabaseClient
.from("orders")
.insert(finalOrderData)
.select()
.single();
if (orderError) {
console.error("Error saving order to Supabase:", orderError);
// on continue quand même pour tenter les mails
}
// 4⃣ Email client (joli template violet)
let customerEmailError = null;
let customerEmailResult = null;
if (RESEND_API_KEY && customerEmail) {
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${RESEND_API_KEY}`,
},
body: JSON.stringify({
from: "Dites le en chanson <contact@dites-le-en-chanson.fr>",
to: [customerEmail],
subject: `Votre commande Dites le en chanson est confirmée ! (ID: ${order?.id?.slice(0, 8) ?? ""})`,
html: `
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Confirmation de votre commande - Dites le en chanson</title>
<style>
body { font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin:0; padding:0; background:#f4f4f9; color:#333; }
.container { max-width:600px; margin:20px auto; background:#fff; border-radius:8px; box-shadow:0 4px 15px rgba(0,0,0,.1); overflow:hidden; }
.header { background:linear-gradient(135deg,#6B46C1,#9F7AEA); color:#fff; padding:30px 20px; text-align:center; }
.header img { max-width:100px; margin-bottom:15px; }
.header h1 { margin:0; font-size:28px; font-weight:600; }
.content { padding:30px; line-height:1.6; }
.content h2 { color:#6B46C1; font-size:22px; margin-top:0; }
.content p { margin-bottom:15px; }
.order-details { background:#f9fafb; padding:20px; border-radius:6px; margin-bottom:20px; border:1px solid #e5e7eb; }
.order-details p { margin:5px 0; }
.order-details strong { color:#555; }
.delivery-info { background:#eef2ff; padding:15px; border-radius:6px; text-align:center; margin-bottom:25px; border-left:4px solid #6366f1; }
.delivery-info p { margin:0; color:#4338ca; }
.button { display:inline-block; background:#6B46C1; color:#fff; padding:12px 25px; text-decoration:none; border-radius:5px; font-size:1em; font-weight:500; }
.button:hover { background:#553C9A; }
.footer { text-align:center; padding:20px; font-size:.9em; color:#777; background:#f1f1f1; }
.footer a { color:#6B46C1; text-decoration:none; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="https://storage.googleapis.com/hostinger-horizons-assets-prod/72f15596-7338-40f3-8565-8548388d2677/4ac040560780878558644b6783d4f976.png" alt="Dites-le en chanson Logo" />
<h1>Merci pour votre commande !</h1>
</div>
<div class="content">
<h2>Bonjour ${orderData?.recipient_name ?? "cher client"},</h2>
<p>
Nous sommes ravis de vous confirmer que votre commande a bien été enregistrée.
Notre équipe de créateurs est déjà prête à composer votre chanson personnalisée !
</p>
<div class="order-details">
<h3>Récapitulatif de votre commande (ID: ${order?.id?.slice(0,8) ?? ""}) :</h3>
<p><strong>Produit:</strong> ${orderData?.product_name ?? "Chanson personnalisée"}</p>
<p><strong>Prix Payé:</strong> ${orderData?.price ? orderData.price + " €" : "—"}</p>
<p><strong>Pour:</strong> ${orderData?.recipient_name ?? "—"}</p>
<p><strong>Langue:</strong> ${orderData?.language ?? "—"}</p>
<p><strong>Voix:</strong> ${orderData?.voice_gender ?? "—"}</p>
<p><strong>Style:</strong> ${orderData?.musical_style ?? "—"}</p>
<p><strong>Ambiance:</strong> ${orderData?.mood ?? "—"}</p>
${orderData?.anecdote1 ? `<p><strong>Anecdote 1:</strong> ${orderData.anecdote1}</p>` : ""}
${orderData?.anecdote2 ? `<p><strong>Anecdote 2:</strong> ${orderData.anecdote2}</p>` : ""}
${orderData?.anecdote3 ? `<p><strong>Anecdote 3:</strong> ${orderData.anecdote3}</p>` : ""}
</div>
<div class="delivery-info">
<p><strong>Délai de création :</strong> Votre chanson unique sera prête et vous sera livrée par email dans les <strong>48 heures</strong>.</p>
</div>
<p>Nous mettons tout notre cœur pour transformer vos histoires en mélodies inoubliables.</p>
<p>Si vous avez la moindre question, n'hésitez pas à nous contacter.</p>
<p style="text-align:center; margin-top:30px;color: white !important;">
<a href="https://dites-le-en-chanson.fr" class="button">Visiter notre site</a>
</p>
</div>
<div class="footer">
<p>&copy; ${new Date().getFullYear()} <a href="https://dites-le-en-chanson.fr">Dites-le en chanson</a>. Tous droits réservés.</p>
</div>
</div>
</body>
</html>
`,
}),
});
const data = await res.json();
if (!res.ok) {
console.error("Resend API Error (Customer):", data);
customerEmailError = data.message || "Unknown Resend Error";
} else {
customerEmailResult = data;
}
} else {
if (!RESEND_API_KEY) console.error("RESEND_API_KEY is missing");
if (!customerEmail) console.error("No customer email available to send confirmation.");
}
// 5⃣ Email admin "Nouvelle Commande Reçue"
let adminEmailError = null;
let adminEmailResult = null;
if (RESEND_API_KEY && ADMIN_EMAIL) {
const adminRes = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${RESEND_API_KEY}`,
},
body: JSON.stringify({
from: "Dites le en chanson <contact@dites-le-en-chanson.fr>",
to: [ADMIN_EMAIL],
subject: `Nouvelle commande reçue (ID: ${order?.id?.slice(0,8) ?? "N/A"})`,
html: `
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Nouvelle Commande Reçue</title>
</head>
<body style="font-family: Arial, sans-serif; color:#333;">
<h1>Nouvelle Commande Reçue !</h1>
<p>Une nouvelle commande a été passée sur votre site.</p>
<p><strong>ID de Commande :</strong> ${order?.id ?? "N/A"}</p>
<p><strong>ID de Session Stripe :</strong> ${sessionId ?? "N/A"}</p>
<p><strong>Email du Client :</strong> ${customerEmail ?? "N/A"}</p>
<p><strong>Statut :</strong> ${finalOrderData.status}</p>
<h2>Détails de la commande :</h2>
<ul>
<li><strong>Recipient Name:</strong> ${orderData?.recipient_name ?? "Non spécifié"}</li>
<li><strong>Song For Whom:</strong> ${orderData?.song_for_whom ?? "Non spécifié"}</li>
<li><strong>Occasion:</strong> ${orderData?.occasion ?? "Non spécifié"}</li>
<li><strong>Language:</strong> ${orderData?.language ?? "Non spécifié"}</li>
<li><strong>Anecdote1:</strong> ${orderData?.anecdote1 ?? "Non spécifié"}</li>
<li><strong>Anecdote2:</strong> ${orderData?.anecdote2 ?? "Non spécifié"}</li>
<li><strong>Anecdote3:</strong> ${orderData?.anecdote3 ?? "Non spécifié"}</li>
<li><strong>Voice Gender:</strong> ${orderData?.voice_gender ?? "Non spécifié"}</li>
<li><strong>Musical Style:</strong> ${orderData?.musical_style ?? "Non spécifié"}</li>
<li><strong>Mood:</strong> ${orderData?.mood ?? "Non spécifié"}</li>
<li><strong>Price:</strong> ${orderData?.price ?? "Non spécifié"}</li>
<li><strong>Product Name:</strong> ${orderData?.product_name ?? "Non spécifié"}</li>
</ul>
</body>
</html>
`,
}),
});
const adminData = await adminRes.json();
if (!adminRes.ok) {
console.error("Resend API Error (Admin):", adminData);
adminEmailError = adminData.message || "Unknown Resend Error";
} else {
adminEmailResult = adminData;
}
} else {
if (!ADMIN_EMAIL) console.error("ADMIN_EMAIL is missing");
}
return new Response(
JSON.stringify({
success: true,
orderId: order?.id || "N/A",
customerEmail,
emailSent: !!customerEmailResult,
customerEmailError,
adminEmailSent: !!adminEmailResult,
adminEmailError,
dbError: orderError ? orderError.message : null,
}),
{
status: 200,
headers: { ...corsHeaders, "Content-Type": "application/json" },
},
);
} catch (error) {
console.error("Function Critical Error:", error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});