Fix email system and implement OTP confirmation flow

Email System Fixes:
- Fix email sending after payment: handle-order-paid now calls send-notification
  instead of send-email-v2 directly, properly processing template variables
- Fix order_created email timing: sent immediately after order creation,
  before payment QR code generation
- Update email templates to use short order ID (8 chars) instead of full UUID
- Add working "Akses Sekarang" buttons to payment_success and access_granted emails
- Add platform_url column to platform_settings for email links

OTP Verification Flow:
- Create dedicated /confirm-otp page for users who close registration modal
- Add link in checkout modal and email to dedicated OTP page
- Update OTP email template with better copywriting and dedicated page link
- Fix send-auth-otp to fetch platform settings for dynamic brand_name and platform_url
- Auto-login users after OTP verification in checkout flow

Admin Features:
- Add delete user functionality with cascade deletion of all related data
- Update IntegrasiTab to read/write email settings from platform_settings only
- Add test email template for email configuration testing

Cleanup:
- Remove obsolete send-consultation-reminder and send-test-email functions
- Update send-email-v2 to read email config from platform_settings
- Remove footer links (Ubah Preferensi/Unsubscribe) from email templates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-01-03 18:02:25 +07:00
parent 4f9a6f4ae3
commit 053465afa3
21 changed files with 1381 additions and 948 deletions

View File

@@ -1,7 +1,6 @@
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
import { EmailTemplateRenderer } from "../shared/email-template-renderer.ts";
import QRCode from 'https://esm.sh/qrcode@1.5.3';
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
@@ -259,44 +258,29 @@ serve(async (req: Request): Promise<Response> => {
);
}
// Get platform settings
const { data: settings } = await supabase
// Get platform settings (includes email configuration)
const { data: platformSettings, error: platformError } = await supabase
.from("platform_settings")
.select("*")
.single();
if (!settings) {
if (platformError || !platformSettings) {
console.error('Error fetching platform settings:', platformError);
return new Response(
JSON.stringify({ success: false, message: "Platform settings not configured" }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const brandName = platformSettings.brand_name || "ACCESS HUB";
// Build email payload
const allVariables = {
recipient_name: recipient_name || "Pelanggan",
platform_name: settings.brand_name || "Platform",
platform_name: brandName,
...variables,
};
// Special handling for order_created: generate QR code image
if (template_key === 'order_created' && allVariables.qr_string) {
console.log('[SEND-NOTIFICATION] Generating QR code for order_created email');
try {
const qrDataUrl = await QRCode.toDataURL(allVariables.qr_string, {
width: 300,
margin: 2,
color: { dark: '#000000', light: '#FFFFFF' }
});
allVariables.qr_code_image = qrDataUrl;
console.log('[SEND-NOTIFICATION] QR code generated successfully');
} catch (qrError) {
console.error('[SEND-NOTIFICATION] Failed to generate QR code:', qrError);
// Continue without QR code - don't fail the email
allVariables.qr_code_image = '';
}
}
const subject = replaceVariables(template.email_subject || template.subject || "", allVariables);
const htmlContent = replaceVariables(template.email_body_html || template.body_html || template.body_text || "", allVariables);
@@ -304,67 +288,30 @@ serve(async (req: Request): Promise<Response> => {
const htmlBody = EmailTemplateRenderer.render({
subject: subject,
content: htmlContent,
brandName: settings.brand_name || "ACCESS HUB",
brandName: brandName,
});
const emailPayload: EmailPayload = {
to: recipient_email,
subject,
html: htmlBody,
from_name: settings.brand_email_from_name || settings.brand_name || "Notifikasi",
from_email: settings.smtp_from_email || "noreply@example.com",
from_name: platformSettings.integration_email_from_name || brandName || "Notifikasi",
from_email: platformSettings.integration_email_from_email || "noreply@example.com",
};
// Determine provider and send
const provider = settings.integration_email_provider || "mailketing";
const provider = platformSettings.integration_email_provider || "mailketing";
console.log(`Sending email via ${provider} to ${recipient_email}`);
switch (provider) {
case "mailketing":
const mailketingToken = settings.mailketing_api_token || settings.api_token;
const mailketingToken = platformSettings.integration_email_api_token;
if (!mailketingToken) throw new Error("Mailketing API token not configured");
await sendViaMailketing(emailPayload, mailketingToken);
break;
case "smtp":
await sendViaSMTP(emailPayload, {
host: settings.smtp_host,
port: settings.smtp_port || 587,
username: settings.smtp_username,
password: settings.smtp_password,
from_name: emailPayload.from_name,
from_email: emailPayload.from_email,
use_tls: settings.smtp_use_tls ?? true,
});
break;
case "resend":
const resendKey = Deno.env.get("RESEND_API_KEY");
if (!resendKey) throw new Error("RESEND_API_KEY not configured");
await sendViaResend(emailPayload, resendKey);
break;
case "elasticemail":
const elasticKey = Deno.env.get("ELASTICEMAIL_API_KEY");
if (!elasticKey) throw new Error("ELASTICEMAIL_API_KEY not configured");
await sendViaElasticEmail(emailPayload, elasticKey);
break;
case "sendgrid":
const sendgridKey = Deno.env.get("SENDGRID_API_KEY");
if (!sendgridKey) throw new Error("SENDGRID_API_KEY not configured");
await sendViaSendGrid(emailPayload, sendgridKey);
break;
case "mailgun":
const mailgunKey = Deno.env.get("MAILGUN_API_KEY");
const mailgunDomain = Deno.env.get("MAILGUN_DOMAIN");
if (!mailgunKey || !mailgunDomain) throw new Error("MAILGUN credentials not configured");
await sendViaMailgun(emailPayload, mailgunKey, mailgunDomain);
break;
default:
throw new Error(`Unknown email provider: ${provider}`);
throw new Error(`Unknown email provider: ${provider}. Only 'mailketing' is supported.`);
}
// Log notification