blocks) ->filter(fn($b) => $b['enabled'] ?? false) ->sortBy('order') ->values(); $bodyHtml = ''; foreach ($blocks as $block) { $bodyHtml .= $this->renderBlock($block, $invoice, $companyName); } $color = htmlspecialchars($tpl->header_color ?? '#1a56db'); $title = htmlspecialchars($tpl->header_title ?? 'Thông báo hóa đơn'); return $this->wrapLayout($title, $color, $bodyHtml, $companyName); } /** * Render a vm_credentials template using the emailData array from SendVmCredentialsEmailJob. * $emailData keys: company_name, client_name, product_name, product_billing, product_price, * service_host, service_username, service_port, service_password, os_name, service_due_exp */ public function renderVmCredentials(EmailNotificationTemplate $tpl, array $emailData, ?Invoice $invoice = null): string { $blocks = collect($tpl->blocks) ->filter(fn($b) => $b['enabled'] ?? false) ->sortBy('order') ->values(); $companyName = $emailData['company_name'] ?? ''; $bodyHtml = ''; foreach ($blocks as $block) { $bodyHtml .= match ($block['type'] ?? '') { 'greeting' => $this->renderGreetingFromData($emailData), 'vm_credentials' => $this->renderVmBlock($emailData, $block['config'] ?? []), 'product_info' => $this->renderProductInfoBlock($emailData, $block['config'] ?? []), 'invoice_info' => $invoice ? $this->renderInvoiceInfo($invoice, $block['config'] ?? []) : '', 'order_items' => $invoice ? $this->renderOrderItems($invoice) : '', 'attachments' => $invoice ? $this->renderAttachments($invoice) : '', 'custom_text' => $this->renderCustomText($block['config'] ?? []), 'footer' => $this->renderFooterBlock($companyName), default => '', }; } $color = htmlspecialchars($tpl->header_color ?? '#1a56db'); $title = htmlspecialchars($tpl->header_title ?? 'Thông tin truy cập VPS'); return $this->wrapLayout($title, $color, $bodyHtml, $companyName); } public function renderPreview(EmailNotificationTemplate $tpl): string { if ($tpl->type === 'vm_credentials') { return $this->renderVmCredentials($tpl, $this->sampleVmData(), null); } $invoice = $this->sampleInvoice(); return $this->render($tpl, $invoice, 'Công ty Demo'); } // ─── Block renderers ────────────────────────────────────────────────────── private function renderBlock(array $block, Invoice $invoice, string $companyName): string { return match ($block['type'] ?? '') { 'greeting' => $this->renderGreeting($invoice), 'invoice_info' => $this->renderInvoiceInfo($invoice, $block['config'] ?? []), 'customer_info'=> $this->renderCustomerInfo($invoice, $block['config'] ?? []), 'order_items' => $this->renderOrderItems($invoice), 'attachments' => $this->renderAttachments($invoice), 'custom_text' => $this->renderCustomText($block['config'] ?? []), 'footer' => $this->renderFooterBlock($companyName), default => '', }; } private function renderGreeting(Invoice $invoice): string { $name = htmlspecialchars($invoice->customer?->fullname ?? 'Quý khách'); return "

Xin chào {$name},

\n"; } private function renderGreetingFromData(array $data): string { $name = htmlspecialchars($data['client_name'] ?? 'Quý khách'); return "

Xin chào {$name},

\n"; } private function renderVmBlock(array $data, array $config): string { $fields = $config['fields'] ?? ['service_host', 'service_username', 'service_port', 'service_password', 'os_name']; $fieldMap = [ 'service_host' => ['Địa chỉ IP', $data['service_host'] ?? '—'], 'service_username' => ['Username', $data['service_username'] ?? '—'], 'service_port' => ['Port', (string)($data['service_port'] ?? '—')], 'service_password' => ['Mật khẩu', $data['service_password'] ?? '—'], 'os_name' => ['Hệ điều hành', $data['os_name'] ?? '—'], ]; $rows = ''; foreach ($fields as $key) { if (!isset($fieldMap[$key])) continue; [$label, $raw] = $fieldMap[$key]; $value = htmlspecialchars($raw); // Highlight password with monospace $display = $key === 'service_password' ? "{$value}" : $value; $rows .= "{$label}" . "{$display}\n"; } return <<

🖥️ Thông tin truy cập VPS

{$rows}
HTML; } private function renderProductInfoBlock(array $data, array $config): string { $fields = $config['fields'] ?? ['product_name', 'product_billing', 'product_price', 'service_due_exp']; $fieldMap = [ 'product_name' => ['Tên dịch vụ', $data['product_name'] ?? '—'], 'product_billing' => ['Chu kỳ thanh toán', $data['product_billing'] ?? '—'], 'product_price' => ['Giá dịch vụ', ($data['product_price'] ?? '0') . ' VNĐ'], 'service_due_exp' => ['Ngày hết hạn', $data['service_due_exp'] ?? '—'], ]; $rows = ''; foreach ($fields as $key) { if (!isset($fieldMap[$key])) continue; [$label, $value] = $fieldMap[$key]; $value = htmlspecialchars($value); $rows .= "{$label}" . "{$value}\n"; } return <<

Thông tin dịch vụ

{$rows}
HTML; } private function renderInvoiceInfo(Invoice $invoice, array $config): string { $fields = $config['fields'] ?? ['invoice_number', 'created_at', 'due_date', 'total', 'status']; $rows = ''; foreach ($fields as $field) { $rows .= $this->invoiceRow($field, $invoice); } return << {$rows}
HTML; } private function invoiceRow(string $field, Invoice $invoice): string { [$label, $value] = match ($field) { 'invoice_number' => ['Số hóa đơn', '#' . ($invoice->invoice_number ?? '—')], 'created_at' => ['Ngày tạo', $invoice->created_at ? $invoice->created_at->format('d/m/Y') : '—'], 'due_date' => ['Hạn thanh toán', $invoice->due_date ? date('d/m/Y', strtotime($invoice->due_date)) : '—'], 'total' => ['Tổng tiền', number_format((float)$invoice->total) . ' VNĐ'], 'sub_total' => ['Tiền dịch vụ', number_format((float)$invoice->sub_total) . ' VNĐ'], 'tax' => ['Thuế', number_format((float)$invoice->tax) . ' VNĐ'], 'status' => ['Trạng thái', $this->statusBadge((int)$invoice->status)], 'payment_method' => ['Phương thức TT', htmlspecialchars($invoice->payment_method ?? '—')], default => [$field, '—'], }; return "{$label}" . "{$value}\n"; } private function statusBadge(int $status): string { return match ($status) { 2 => 'Đã thanh toán', 1 => 'Chưa thanh toán', default => 'Đã hủy', }; } private function renderCustomerInfo(Invoice $invoice, array $config): string { $fields = $config['fields'] ?? ['name', 'email']; $customer = $invoice->customer; if (!$customer) return ''; $rows = ''; foreach ($fields as $field) { [$label, $value] = match ($field) { 'name' => ['Tên khách hàng', htmlspecialchars($customer->fullname ?? '—')], 'email' => ['Email', htmlspecialchars($customer->email ?? '—')], 'address' => ['Địa chỉ', htmlspecialchars($customer->address ?? '—')], default => [$field, '—'], }; $rows .= "{$label}" . "{$value}\n"; } return <<

Thông tin khách hàng

{$rows}
HTML; } private function renderOrderItems(Invoice $invoice): string { $order = $invoice->order; if (!$order) return ''; $order->loadMissing('orderItems.product'); $items = $order->orderItems; if ($items->isEmpty()) return ''; $cycleMap = [ 'monthly' => 'Hàng tháng', 'quarterly' => 'Hàng quý', 'semi_annually' => '6 tháng', 'annually' => 'Hàng năm', 'biennially' => '2 năm', ]; $rows = ''; foreach ($items as $item) { $name = htmlspecialchars($item->product?->name ?? '—'); $server = htmlspecialchars($item->server_name ?? '—'); $cycle = htmlspecialchars($cycleMap[$item->billing_cycle] ?? ($item->billing_cycle ?? '—')); $price = number_format((float) $item->price) . ' VNĐ'; $rows .= "" . "{$name}" . "{$server}" . "{$cycle}" . "{$price}" . "\n"; } return << {$rows}
Sản phẩm Server Chu kỳ Giá
HTML; } private function renderAttachments(Invoice $invoice): string { $invoice->loadMissing('attachments'); if ($invoice->attachments->isEmpty()) return ''; $items = ''; foreach ($invoice->attachments as $file) { $name = htmlspecialchars($file->filename); $url = htmlspecialchars($file->url ?? '#'); $items .= "
  • {$name}
  • \n"; } return <<

    File đính kèm

    HTML; } private function renderCustomText(array $config): string { $content = $config['content'] ?? ''; if (!$content) return ''; // If plain text (no HTML tags), wrap in

    $html = strip_tags($content) === $content ? '

    ' . htmlspecialchars($content) . '

    ' : $content; return "
    {$html}
    \n"; } private function renderFooterBlock(string $companyName): string { $company = htmlspecialchars($companyName ?: config('app.name', 'Công ty')); $year = date('Y'); return "

    Trân trọng,
    {$company}

    \n"; } // ─── Layout wrapper ─────────────────────────────────────────────────────── private function wrapLayout(string $title, string $color, string $body, string $companyName): string { $company = htmlspecialchars($companyName ?: config('app.name', 'Công ty')); $year = date('Y'); return << {$title}

    {$title}

    {$body}
    © {$year} {$company}. Đây là email tự động, vui lòng không trả lời.
    HTML; } // ─── Sample data for preview ────────────────────────────────────────────── private function sampleVmData(): array { return [ 'company_name' => 'Công ty Demo', 'company_logo' => '', 'client_name' => 'Nguyễn Văn A', 'product_name' => 'VPS Starter', 'product_billing' => 'Hàng tháng', 'product_price' => '420.000', 'service_host' => '203.113.x.x', 'service_username' => 'root', 'service_port' => '22', 'service_password' => 'P@ssw0rd!2026', 'os_name' => 'Ubuntu 22.04 LTS', 'service_due_exp' => date('d/m/Y', strtotime('+1 month')), ]; } private function sampleInvoice(): Invoice { $invoice = new Invoice([ 'invoice_number' => 'INV-20260521-00001', 'sub_total' => 420000, 'tax' => 0, 'total' => 420000, 'status' => 2, 'payment_method' => 'credit', ]); $invoice->created_at = now(); $invoice->due_date = now()->addDays(7)->toDateString(); return $invoice; } }