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
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ụ
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 <<
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
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 <<
| Sản phẩm |
Server |
Chu kỳ |
Giá |
{$rows}
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;
}
}