Inloggade Brevo i Checkout
X-Lovable-Edit-ID: edt-874dad72-93d8-45ce-b8c1-846ed494a65f Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,25 @@ const supabaseAdmin = createClient(
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
|
||||
);
|
||||
|
||||
async function sendOrderEmail(payload: Record<string, unknown>) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${Deno.env.get("SUPABASE_URL")}/functions/v1/send-order-email`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
if (!res.ok) console.error("send-order-email failed:", res.status, await res.text());
|
||||
} catch (e) {
|
||||
console.error("send-order-email error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function insertOrder(row: {
|
||||
method: "card" | "invoice";
|
||||
b: any;
|
||||
@@ -48,6 +67,20 @@ async function insertOrder(row: {
|
||||
],
|
||||
});
|
||||
if (error) console.error("Order insert failed:", error);
|
||||
|
||||
await sendOrderEmail({
|
||||
order_number: orderNumber,
|
||||
customer_email: b.email,
|
||||
customer_name: b.company,
|
||||
kg_per_week: b.kg_per_week,
|
||||
employees: b.employees,
|
||||
monthly_amount_sek: totalKr,
|
||||
method,
|
||||
address: b.address ?? "",
|
||||
postal_code: cleaned,
|
||||
phone: b.phone ?? "",
|
||||
comments: b.comments ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { z } from "npm:zod@3.23.8";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers":
|
||||
"authorization, x-client-info, apikey, content-type",
|
||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||
};
|
||||
|
||||
const GATEWAY_URL = "https://connector-gateway.lovable.dev/brevo";
|
||||
|
||||
const BodySchema = z.object({
|
||||
order_number: z.string().min(1).max(100),
|
||||
customer_email: z.string().email(),
|
||||
customer_name: z.string().min(1).max(200),
|
||||
kg_per_week: z.number().min(0),
|
||||
employees: z.number().int().min(0),
|
||||
monthly_amount_sek: z.number().min(0),
|
||||
method: z.enum(["card", "invoice"]),
|
||||
address: z.string().max(200).optional().default(""),
|
||||
postal_code: z.string().max(20).optional().default(""),
|
||||
phone: z.string().max(30).optional().default(""),
|
||||
comments: z.string().max(1000).optional().default(""),
|
||||
});
|
||||
|
||||
function fmtKr(n: number) {
|
||||
return new Intl.NumberFormat("sv-SE", {
|
||||
style: "currency",
|
||||
currency: "SEK",
|
||||
maximumFractionDigits: 0,
|
||||
}).format(n);
|
||||
}
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });
|
||||
|
||||
try {
|
||||
const LOVABLE_API_KEY = Deno.env.get("LOVABLE_API_KEY");
|
||||
const BREVO_API_KEY = Deno.env.get("BREVO_API_KEY");
|
||||
if (!LOVABLE_API_KEY || !BREVO_API_KEY) {
|
||||
throw new Error("Email service not configured");
|
||||
}
|
||||
|
||||
const raw = await req.json();
|
||||
const parsed = BodySchema.safeParse(raw);
|
||||
if (!parsed.success) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Ogiltiga fält", details: parsed.error.flatten().fieldErrors }),
|
||||
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
const o = parsed.data;
|
||||
|
||||
const senderEmail = Deno.env.get("BREVO_SENDER_EMAIL") ?? "noreply@konditorivaror.se";
|
||||
const senderName = Deno.env.get("BREVO_SENDER_NAME") ?? "Lennart Svensson Konditorivaror";
|
||||
const adminEmail = Deno.env.get("ORDER_NOTIFICATION_EMAIL") ?? senderEmail;
|
||||
|
||||
const methodLabel = o.method === "card" ? "Kort (månadsvis)" : "Faktura (månadsvis)";
|
||||
|
||||
const customerHtml = `
|
||||
<div style="font-family: Georgia, serif; color:#3d2817; max-width:600px; margin:0 auto; padding:24px;">
|
||||
<h1 style="color:#7a4a1f;">Tack för din beställning!</h1>
|
||||
<p>Hej ${o.customer_name},</p>
|
||||
<p>Vi har mottagit din beställning av <strong>Kakabonnemang</strong>. Goda saker är på väg – leverans varje tisdag inom Storstockholm.</p>
|
||||
<h2 style="color:#7a4a1f; margin-top:24px;">Orderdetaljer</h2>
|
||||
<table style="width:100%; border-collapse:collapse;">
|
||||
<tr><td style="padding:6px 0;"><strong>Ordernummer:</strong></td><td>${o.order_number}</td></tr>
|
||||
<tr><td style="padding:6px 0;"><strong>Mängd:</strong></td><td>${o.kg_per_week} kg/vecka</td></tr>
|
||||
<tr><td style="padding:6px 0;"><strong>Antal anställda:</strong></td><td>${o.employees}</td></tr>
|
||||
<tr><td style="padding:6px 0;"><strong>Månadskostnad (exkl. moms):</strong></td><td>${fmtKr(o.monthly_amount_sek)}</td></tr>
|
||||
<tr><td style="padding:6px 0;"><strong>Betalning:</strong></td><td>${methodLabel}</td></tr>
|
||||
</table>
|
||||
<p style="margin-top:24px;">Har du frågor? Svara bara på detta mejl.</p>
|
||||
<p style="color:#7a4a1f; font-style:italic;">Äkta svenskt konditorhantverk</p>
|
||||
</div>`;
|
||||
|
||||
const adminHtml = `
|
||||
<div style="font-family: Arial, sans-serif; color:#111; max-width:600px;">
|
||||
<h2>Ny order: ${o.order_number}</h2>
|
||||
<p><strong>Företag:</strong> ${o.customer_name}<br/>
|
||||
<strong>E-post:</strong> ${o.customer_email}<br/>
|
||||
<strong>Telefon:</strong> ${o.phone || "-"}</p>
|
||||
<p><strong>Mängd:</strong> ${o.kg_per_week} kg/vecka<br/>
|
||||
<strong>Anställda:</strong> ${o.employees}<br/>
|
||||
<strong>Månadskostnad (exkl moms):</strong> ${fmtKr(o.monthly_amount_sek)}<br/>
|
||||
<strong>Betalning:</strong> ${methodLabel}</p>
|
||||
<p><strong>Leverans:</strong> ${o.address || "-"}, ${o.postal_code || "-"}</p>
|
||||
${o.comments ? `<p><strong>Kommentar:</strong> ${o.comments}</p>` : ""}
|
||||
</div>`;
|
||||
|
||||
async function send(to: string, subject: string, html: string) {
|
||||
const res = await fetch(`${GATEWAY_URL}/v3/smtp/email`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${LOVABLE_API_KEY}`,
|
||||
"X-Connection-Api-Key": BREVO_API_KEY,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sender: { name: senderName, email: senderEmail },
|
||||
to: [{ email: to }],
|
||||
subject,
|
||||
htmlContent: html,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
console.error(`Brevo send failed [${res.status}]:`, body);
|
||||
throw new Error(`Brevo ${res.status}: ${body}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
send(o.customer_email, `Orderbekräftelse ${o.order_number} – Lennart Svensson Konditorivaror`, customerHtml),
|
||||
send(adminEmail, `Ny order: ${o.order_number} (${o.customer_name})`, adminHtml),
|
||||
]);
|
||||
|
||||
const errors = results
|
||||
.map((r, i) => (r.status === "rejected" ? { i, reason: String(r.reason) } : null))
|
||||
.filter(Boolean);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success: errors.length === 0, errors }),
|
||||
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } },
|
||||
);
|
||||
} catch (err: any) {
|
||||
console.error("send-order-email error:", err);
|
||||
return new Response(
|
||||
JSON.stringify({ error: err?.message ?? "Kunde inte skicka mejl" }),
|
||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user