import { sendEmailToCustomer, sendEmailToAdmin } from "@/lib/resend"; import { OrderConfirmation } from "@/emails/OrderConfirmation"; import { OrderShipped } from "@/emails/OrderShipped"; import { OrderCancelled } from "@/emails/OrderCancelled"; import { OrderPaid } from "@/emails/OrderPaid"; import { formatPrice } from "@/app/api/webhooks/saleor/utils"; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com"; const DASHBOARD_URL = process.env.DASHBOARD_URL || "https://dashboard.manoonoils.com"; // Translation helper for email subjects function getOrderConfirmationSubject(language: string, orderNumber: string): string { const subjects: Record = { sr: `Potvrda narudžbine #${orderNumber}`, de: `Bestellbestätigung #${orderNumber}`, fr: `Confirmation de commande #${orderNumber}`, en: `Order Confirmation #${orderNumber}`, }; return subjects[language] || subjects.en; } function getOrderShippedSubject(language: string, orderNumber: string): string { const subjects: Record = { sr: `Vaša narudžbina #${orderNumber} je poslata!`, de: `Ihre Bestellung #${orderNumber} wurde versendet!`, fr: `Votre commande #${orderNumber} a été expédiée!`, en: `Your Order #${orderNumber} Has Shipped!`, }; return subjects[language] || subjects.en; } function getOrderCancelledSubject(language: string, orderNumber: string): string { const subjects: Record = { sr: `Vaša narudžbina #${orderNumber} je otkazana`, de: `Ihre Bestellung #${orderNumber} wurde storniert`, fr: `Votre commande #${orderNumber} a été annulée`, en: `Your Order #${orderNumber} Has Been Cancelled`, }; return subjects[language] || subjects.en; } function getOrderPaidSubject(language: string, orderNumber: string): string { const subjects: Record = { sr: `Plaćanje za narudžbinu #${orderNumber} je primljeno!`, de: `Zahlung für Bestellung #${orderNumber} erhalten!`, fr: `Paiement reçu pour la commande #${orderNumber}!`, en: `Payment Received for Order #${orderNumber}!`, }; return subjects[language] || subjects.en; } // Interfaces interface OrderItem { id: string; productName: string; variantName?: string; quantity: number; totalPrice: { gross: { amount: number; currency: string; }; }; } interface OrderAddress { firstName?: string; lastName?: string; streetAddress1?: string; streetAddress2?: string; city?: string; postalCode?: string; country?: string; phone?: string; } interface Order { id: string; number: string; userEmail: string; user?: { firstName?: string; lastName?: string; }; billingAddress?: OrderAddress; shippingAddress?: OrderAddress; lines: OrderItem[]; total: { gross: { amount: number; currency: string; }; }; languageCode?: string; metadata?: Array<{ key: string; value: string }>; } interface OrderEmailItem { id: string; name: string; quantity: number; price: string; } class OrderNotificationService { private static instance: OrderNotificationService; static getInstance(): OrderNotificationService { if (!OrderNotificationService.instance) { OrderNotificationService.instance = new OrderNotificationService(); } return OrderNotificationService.instance; } private parseOrderItems(lines: OrderItem[], currency: string): OrderEmailItem[] { return lines.map((line) => ({ id: line.id, name: line.variantName ? `${line.productName} (${line.variantName})` : line.productName, quantity: line.quantity, price: formatPrice(line.totalPrice.gross.amount, currency), })); } private formatAddress(address?: OrderAddress): string { if (!address) return ""; const parts = [ address.firstName, address.lastName, address.streetAddress1, address.streetAddress2, address.city, address.postalCode, address.country, ].filter(Boolean); return parts.join(", "); } private getCustomerName(order: Order): string { if (order.user?.firstName || order.user?.lastName) { return `${order.user.firstName || ""} ${order.user.lastName || ""}`.trim(); } if (order.shippingAddress?.firstName || order.shippingAddress?.lastName) { return `${order.shippingAddress.firstName || ""} ${order.shippingAddress.lastName || ""}`.trim(); } return "Customer"; } private getCustomerLanguage(order: Order): string { const LANGUAGE_CODE_MAP: Record = { SR: "sr", EN: "en", DE: "de", FR: "fr", }; if (order.languageCode && LANGUAGE_CODE_MAP[order.languageCode]) { return LANGUAGE_CODE_MAP[order.languageCode]; } if (order.metadata) { const langMeta = order.metadata.find((m) => m.key === "language"); if (langMeta && LANGUAGE_CODE_MAP[langMeta.value.toUpperCase()]) { return LANGUAGE_CODE_MAP[langMeta.value.toUpperCase()]; } } return "en"; } async sendOrderConfirmation(order: Order): Promise { const language = this.getCustomerLanguage(order); const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); const customerEmail = order.userEmail; const phone = order.shippingAddress?.phone || order.billingAddress?.phone; await sendEmailToCustomer({ to: customerEmail, subject: getOrderConfirmationSubject(language, order.number), react: OrderConfirmation({ language, orderId: order.id, orderNumber: order.number, customerEmail, customerName, items: this.parseOrderItems(order.lines, currency), total: formatPrice(order.total.gross.amount, currency), shippingAddress: this.formatAddress(order.shippingAddress), siteUrl: SITE_URL, }), language, idempotencyKey: `order-confirmed/${order.id}`, }); } async sendOrderConfirmationToAdmin(order: Order): Promise { const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); const customerEmail = order.userEmail; const phone = order.shippingAddress?.phone || order.billingAddress?.phone; await sendEmailToAdmin({ subject: `🎉 New Order #${order.number} - ${formatPrice(order.total.gross.amount, currency)}`, react: OrderConfirmation({ language: "en", orderId: order.id, orderNumber: order.number, customerEmail, customerName, items: this.parseOrderItems(order.lines, currency), total: formatPrice(order.total.gross.amount, currency), shippingAddress: this.formatAddress(order.shippingAddress), billingAddress: this.formatAddress(order.billingAddress), phone, siteUrl: SITE_URL, dashboardUrl: DASHBOARD_URL, isAdmin: true, }), eventType: "ORDER_CONFIRMED", orderId: order.id, }); } async sendOrderShipped(order: Order, trackingNumber?: string, trackingUrl?: string): Promise { const language = this.getCustomerLanguage(order); const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); const customerEmail = order.userEmail; await sendEmailToCustomer({ to: customerEmail, subject: getOrderShippedSubject(language, order.number), react: OrderShipped({ language, orderId: order.id, orderNumber: order.number, customerName, items: this.parseOrderItems(order.lines, currency), trackingNumber, trackingUrl, siteUrl: SITE_URL, }), language, idempotencyKey: `order-fulfilled/${order.id}`, }); } async sendOrderShippedToAdmin(order: Order, trackingNumber?: string, trackingUrl?: string): Promise { const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); await sendEmailToAdmin({ subject: `Order Shipped #${order.number} - ${customerName}`, react: OrderShipped({ language: "en", orderId: order.id, orderNumber: order.number, customerName, items: this.parseOrderItems(order.lines, currency), trackingNumber, trackingUrl, siteUrl: SITE_URL, }), eventType: "ORDER_FULFILLED", orderId: order.id, }); } async sendOrderCancelled(order: Order, reason?: string): Promise { const language = this.getCustomerLanguage(order); const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); const customerEmail = order.userEmail; await sendEmailToCustomer({ to: customerEmail, subject: getOrderCancelledSubject(language, order.number), react: OrderCancelled({ language, orderId: order.id, orderNumber: order.number, customerName, items: this.parseOrderItems(order.lines, currency), total: formatPrice(order.total.gross.amount, currency), reason, siteUrl: SITE_URL, }), language, idempotencyKey: `order-cancelled/${order.id}`, }); } async sendOrderCancelledToAdmin(order: Order, reason?: string): Promise { const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); await sendEmailToAdmin({ subject: `Order Cancelled #${order.number} - ${customerName}`, react: OrderCancelled({ language: "en", orderId: order.id, orderNumber: order.number, customerName, items: this.parseOrderItems(order.lines, currency), total: formatPrice(order.total.gross.amount, currency), reason, siteUrl: SITE_URL, }), eventType: "ORDER_CANCELLED", orderId: order.id, }); } async sendOrderPaid(order: Order): Promise { const language = this.getCustomerLanguage(order); const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); const customerEmail = order.userEmail; await sendEmailToCustomer({ to: customerEmail, subject: getOrderPaidSubject(language, order.number), react: OrderPaid({ language, orderId: order.id, orderNumber: order.number, customerName, items: this.parseOrderItems(order.lines, currency), total: formatPrice(order.total.gross.amount, currency), siteUrl: SITE_URL, }), language, idempotencyKey: `order-paid/${order.id}`, }); } async sendOrderPaidToAdmin(order: Order): Promise { const currency = order.total.gross.currency; const customerName = this.getCustomerName(order); await sendEmailToAdmin({ subject: `Payment Received #${order.number} - ${customerName} - ${formatPrice(order.total.gross.amount, currency)}`, react: OrderPaid({ language: "en", orderId: order.id, orderNumber: order.number, customerName, items: this.parseOrderItems(order.lines, currency), total: formatPrice(order.total.gross.amount, currency), siteUrl: SITE_URL, }), eventType: "ORDER_FULLY_PAID", orderId: order.id, }); } } export const orderNotificationService = OrderNotificationService.getInstance(); export default orderNotificationService;