Add order_created email with QR code generation
Enhanced email notification system to send order confirmation emails immediately after order creation with embedded QR code for payment.
Changes:
- Checkout.tsx: Added send-notification call after payment creation with comprehensive logging
- send-notification: Added QRCode library integration for generating base64 QR images for order_created emails
- NotifikasiTab.tsx: Added QR code section to default order_created template and updated shortcodes list
Technical details:
- QR code generated as base64 data URL for email client compatibility
- Fire-and-forget pattern ensures checkout flow isn't blocked
- Added detailed console logging for debugging email send issues
- New shortcodes: {qr_code_image}, {qr_expiry_time}
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ interface NotificationTemplate {
|
|||||||
const RELEVANT_SHORTCODES = {
|
const RELEVANT_SHORTCODES = {
|
||||||
'payment_success': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{produk}', '{link_akses}', '{thank_you_page}'],
|
'payment_success': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{produk}', '{link_akses}', '{thank_you_page}'],
|
||||||
'access_granted': ['{nama}', '{email}', '{produk}', '{link_akses}', '{username_akses}', '{password_akses}', '{kadaluarsa_akses}'],
|
'access_granted': ['{nama}', '{email}', '{produk}', '{link_akses}', '{username_akses}', '{password_akses}', '{kadaluarsa_akses}'],
|
||||||
'order_created': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{produk}', '{payment_link}', '{thank_you_page}'],
|
'order_created': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{produk}', '{payment_link}', '{thank_you_page}', '{qr_code_image}', '{qr_expiry_time}'],
|
||||||
'payment_reminder': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{batas_pembayaran}', '{jumlah_pembayaran}', '{bank_tujuan}', '{nomor_rekening}', '{payment_link}', '{thank_you_page}'],
|
'payment_reminder': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{batas_pembayaran}', '{jumlah_pembayaran}', '{bank_tujuan}', '{nomor_rekening}', '{payment_link}', '{thank_you_page}'],
|
||||||
'consulting_scheduled': ['{nama}', '{email}', '{tanggal_konsultasi}', '{jam_konsultasi}', '{durasi_konsultasi}', '{link_meet}', '{jenis_konsultasi}', '{topik_konsultasi}'],
|
'consulting_scheduled': ['{nama}', '{email}', '{tanggal_konsultasi}', '{jam_konsultasi}', '{durasi_konsultasi}', '{link_meet}', '{jenis_konsultasi}', '{topik_konsultasi}'],
|
||||||
'event_reminder': ['{nama}', '{email}', '{judul_event}', '{tanggal_event}', '{jam_event}', '{link_event}', '{lokasi_event}', '{kapasitas_event}'],
|
'event_reminder': ['{nama}', '{email}', '{judul_event}', '{tanggal_event}', '{jam_event}', '{link_event}', '{lokasi_event}', '{kapasitas_event}'],
|
||||||
@@ -143,6 +143,16 @@ const DEFAULT_TEMPLATES: { key: string; name: string; defaultSubject: string; de
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin: 30px 0; padding: 20px; background-color: #f5f5f5; border-radius: 8px;">
|
||||||
|
<h3 style="margin: 0 0 15px 0; font-size: 18px; color: #333;">Scan QR untuk Pembayaran</h3>
|
||||||
|
<img src="{qr_code_image}" alt="QRIS Payment QR Code" style="width: 200px; height: 200px; border: 2px solid #000; padding: 10px; background-color: #fff; display: inline-block;">
|
||||||
|
<p style="margin: 15px 0 5px 0; font-size: 14px; color: #666;">Scan dengan aplikasi e-wallet atau mobile banking Anda</p>
|
||||||
|
<p style="margin: 5px 0 0 0; font-size: 12px; color: #999;">Berlaku hingga: {qr_expiry_time}</p>
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<a href="{payment_link}" style="display: inline-block; padding: 12px 24px; background-color: #000; color: #fff; text-decoration: none; border-radius: 4px; font-weight: bold;">Bayar Sekarang</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Langkah Selanjutnya:</h3>
|
<h3>Langkah Selanjutnya:</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Selesaikan pembayaran sebelum batas waktu</li>
|
<li>Selesaikan pembayaran sebelum batas waktu</li>
|
||||||
|
|||||||
@@ -116,6 +116,44 @@ export default function Checkout() {
|
|||||||
throw new Error(paymentError.message || 'Gagal membuat pembayaran');
|
throw new Error(paymentError.message || 'Gagal membuat pembayaran');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send order_created email with QR code (non-blocking, fire-and-forget)
|
||||||
|
console.log('[CHECKOUT] About to send order_created email for order:', order.id);
|
||||||
|
console.log('[CHECKOUT] User email:', user.email);
|
||||||
|
console.log('[CHECKOUT] Payment data QR string:', paymentData?.qr_string);
|
||||||
|
|
||||||
|
supabase.functions.invoke('send-notification', {
|
||||||
|
body: {
|
||||||
|
template_key: 'order_created',
|
||||||
|
recipient_email: user.email,
|
||||||
|
recipient_name: user.user_metadata.name || user.email?.split('@')[0] || 'Pelanggan',
|
||||||
|
variables: {
|
||||||
|
nama: user.user_metadata.name || user.email?.split('@')[0] || 'Pelanggan',
|
||||||
|
email: user.email,
|
||||||
|
order_id: order.id,
|
||||||
|
tanggal_pesanan: new Date().toLocaleDateString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric'
|
||||||
|
}),
|
||||||
|
total: formatIDR(total),
|
||||||
|
metode_pembayaran: 'QRIS',
|
||||||
|
produk: items.map(item => item.title).join(', '),
|
||||||
|
payment_link: `${window.location.origin}/orders/${order.id}`,
|
||||||
|
thank_you_page: `${window.location.origin}/orders/${order.id}`,
|
||||||
|
qr_string: paymentData?.qr_string || '',
|
||||||
|
qr_expiry_time: paymentData?.expired_at ? new Date(paymentData.expired_at).toLocaleString('id-ID') : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).then(result => {
|
||||||
|
console.log('[CHECKOUT] send-notification called successfully:', result);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('[CHECKOUT] Failed to send order_created email:', err);
|
||||||
|
console.error('[CHECKOUT] Error details:', JSON.stringify(err));
|
||||||
|
// Don't block checkout flow if email fails
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[CHECKOUT] Order creation email call initiated');
|
||||||
|
|
||||||
// Clear cart and redirect to order detail page to show QR code
|
// Clear cart and redirect to order detail page to show QR code
|
||||||
clearCart();
|
clearCart();
|
||||||
navigate(`/orders/${order.id}`);
|
navigate(`/orders/${order.id}`);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||||
import { EmailTemplateRenderer } from "../shared/email-template-renderer.ts";
|
import { EmailTemplateRenderer } from "../shared/email-template-renderer.ts";
|
||||||
|
import QRCode from 'https://esm.sh/qrcode@1.5.3';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
@@ -246,6 +247,24 @@ serve(async (req: Request): Promise<Response> => {
|
|||||||
...variables,
|
...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.subject, allVariables);
|
const subject = replaceVariables(template.subject, allVariables);
|
||||||
const htmlContent = replaceVariables(template.body_html || template.body_text || "", allVariables);
|
const htmlContent = replaceVariables(template.body_html || template.body_text || "", allVariables);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user