291 lines
11 KiB
JavaScript
291 lines
11 KiB
JavaScript
/* 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>© ${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" },
|
||
});
|
||
}
|
||
});
|