From b03ee9fc2dadc9cca982a78940148b1c39e183ee Mon Sep 17 00:00:00 2001 From: baodev0 Date: Fri, 14 Aug 2026 08:45:52 +0700 Subject: [PATCH] update --- .claude/settings.local.json | 4 +- .../Hosting/ProvisionHostingAccountJob.php | 84 +++++ .../EmailNotificationTemplateRenderer.php | 293 ++-------------- app/Services/InvoicePaymentService.php | 15 +- app/Services/Plesk/PleskClientService.php | 43 ++- public/images/emails/admin.png | Bin 0 -> 1774 bytes public/images/emails/buy-date.png | Bin 0 -> 2877 bytes public/images/emails/due-date.png | Bin 0 -> 1886 bytes public/images/emails/internet.png | Bin 0 -> 4221 bytes public/images/emails/password.png | Bin 0 -> 4396 bytes .../hosting-account-created.blade.php | 332 +++++++++--------- .../customer/verify-account-welcome.blade.php | 235 ++++++------- .../views/emails/customer/welcome.blade.php | 243 ++++++------- .../emails/layouts/notification.blade.php | 117 ++++++ .../emails/partials/attachments.blade.php | 15 + .../emails/partials/custom-text.blade.php | 9 + .../emails/partials/customer-info.blade.php | 23 ++ .../views/emails/partials/greeting.blade.php | 1 + .../emails/partials/invoice-info.blade.php | 33 ++ .../emails/partials/order-items.blade.php | 30 ++ .../emails/partials/product-info.blade.php | 32 ++ .../views/emails/partials/signature.blade.php | 1 + .../emails/partials/vm-credentials.blade.php | 38 ++ 23 files changed, 844 insertions(+), 704 deletions(-) create mode 100644 app/Jobs/Hosting/ProvisionHostingAccountJob.php create mode 100644 public/images/emails/admin.png create mode 100644 public/images/emails/buy-date.png create mode 100644 public/images/emails/due-date.png create mode 100644 public/images/emails/internet.png create mode 100644 public/images/emails/password.png create mode 100644 resources/views/emails/layouts/notification.blade.php create mode 100644 resources/views/emails/partials/attachments.blade.php create mode 100644 resources/views/emails/partials/custom-text.blade.php create mode 100644 resources/views/emails/partials/customer-info.blade.php create mode 100644 resources/views/emails/partials/greeting.blade.php create mode 100644 resources/views/emails/partials/invoice-info.blade.php create mode 100644 resources/views/emails/partials/order-items.blade.php create mode 100644 resources/views/emails/partials/product-info.blade.php create mode 100644 resources/views/emails/partials/signature.blade.php create mode 100644 resources/views/emails/partials/vm-credentials.blade.php diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1e770b2..fe4f96e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -45,7 +45,9 @@ "Bash(pnpm exec *)", "Bash(echo \"exit: $?\")", "Bash(node -e \"console.log\\(require.resolve\\('ag-grid-react/package.json'\\)\\)\")", - "Bash(awk 'NR==1,NR==900' routes/api.php)" + "Bash(awk 'NR==1,NR==900' routes/api.php)", + "Bash(sed -i '' -e 's/#c93131/#F16723/g' -e 's/#FFF6F6/#FFF1E6/g' -e 's/#ea4544/#F16723/g' -e 's/#EA4544/#F16723/g' -e 's/#e0cccc/#e6e6e6/g' portal/index.html)", + "Bash(sed -i '' -e 's/#c93131/#F16723/g' -e 's/#FFF6F6/#FFF1E6/g' -e 's/#ea4544/#F16723/g' -e 's/#EA4544/#F16723/g' -e 's/#e0cccc/#e6e6e6/g' index.html)" ] } } diff --git a/app/Jobs/Hosting/ProvisionHostingAccountJob.php b/app/Jobs/Hosting/ProvisionHostingAccountJob.php new file mode 100644 index 0000000..9686441 --- /dev/null +++ b/app/Jobs/Hosting/ProvisionHostingAccountJob.php @@ -0,0 +1,84 @@ +item->service()->first(); + $domainName = $this->item->server_name; + + if (! $service || ! $domainName) { + Log::channel('Custom')->warning('ProvisionHostingAccountJob skipped: missing service or domain', [ + 'order_item_id' => $this->item->id, + ]); + + return; + } + + $server = DnsServer::query() + ->where('status', 'activated') + ->whereIn('type', [ServerType::HOSTING->value, ServerType::BOTH->value]) + ->first(); + + if (! $server) { + Log::channel('Custom')->error('ProvisionHostingAccountJob failed: no active hosting server available', [ + 'order_item_id' => $this->item->id, + ]); + + return; + } + + $result = $plesk->provision([ + 'customer_id' => $this->item->order->customer_id, + 'service_id' => $service->id, + 'dns_manager_server_id' => $server->id, + 'domain_name' => $domainName, + 'hosting_plan' => $this->item->product?->name ?? 'Default', + ]); + + if ($result['error']) { + Log::channel('Custom')->error('ProvisionHostingAccountJob failed', [ + 'order_item_id' => $this->item->id, + 'message' => $result['message'] ?? 'Unknown error', + ]); + + return; + } + + $service->update(['status' => 'active']); + } +} diff --git a/app/Services/EmailNotificationTemplateRenderer.php b/app/Services/EmailNotificationTemplateRenderer.php index 398a62c..380d8da 100644 --- a/app/Services/EmailNotificationTemplateRenderer.php +++ b/app/Services/EmailNotificationTemplateRenderer.php @@ -4,9 +4,14 @@ namespace App\Services; use App\Models\EmailNotificationTemplate; use App\Models\Invoice; +use Illuminate\Support\Facades\View; class EmailNotificationTemplateRenderer { + private const BANNERS = [ + 'vm_credentials' => 'https://res.cloudinary.com/izpast9i/image/upload/v1786503590/email_banner_cloud_vps.png', + ]; + public function render(EmailNotificationTemplate $tpl, Invoice $invoice, string $companyName = ''): string { $blocks = collect($tpl->blocks) @@ -19,10 +24,9 @@ class EmailNotificationTemplateRenderer $bodyHtml .= $this->renderBlock($block, $invoice, $companyName); } - $color = htmlspecialchars($tpl->header_color ?? '#1a56db'); - $title = htmlspecialchars($tpl->header_title ?? 'Thông báo hóa đơn'); + $title = $tpl->header_title ?? 'Thông báo hóa đơn'; - return $this->wrapLayout($title, $color, $bodyHtml, $companyName); + return $this->wrapLayout($title, $bodyHtml, $companyName, self::BANNERS[$tpl->type] ?? null); } /** @@ -42,22 +46,21 @@ class EmailNotificationTemplateRenderer 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'] ?? []) : '', + 'greeting' => $this->renderView('emails.partials.greeting', ['name' => $emailData['client_name'] ?? 'Quý khách']), + 'vm_credentials' => $this->renderView('emails.partials.vm-credentials', ['fields' => $block['config']['fields'] ?? null, 'data' => $emailData]), + 'product_info' => $this->renderView('emails.partials.product-info', ['fields' => $block['config']['fields'] ?? null, 'data' => $emailData]), + 'invoice_info' => $invoice ? $this->renderView('emails.partials.invoice-info', ['fields' => $block['config']['fields'] ?? null, 'invoice' => $invoice]) : '', 'order_items' => $invoice ? $this->renderOrderItems($invoice) : '', 'attachments' => $invoice ? $this->renderAttachments($invoice) : '', - 'custom_text' => $this->renderCustomText($block['config'] ?? []), - 'footer' => $this->renderFooterBlock($companyName), + 'custom_text' => $this->renderView('emails.partials.custom-text', ['content' => $block['config']['content'] ?? '']), + 'footer' => $this->renderView('emails.partials.signature', ['companyName' => $companyName]), default => '', }; } - $color = htmlspecialchars($tpl->header_color ?? '#1a56db'); - $title = htmlspecialchars($tpl->header_title ?? 'Thông tin truy cập VPS'); + $title = $tpl->header_title ?? 'Thông tin truy cập VPS'; - return $this->wrapLayout($title, $color, $bodyHtml, $companyName); + return $this->wrapLayout($title, $bodyHtml, $companyName, self::BANNERS[$tpl->type] ?? null); } public function renderPreview(EmailNotificationTemplate $tpl): string @@ -75,277 +78,49 @@ class EmailNotificationTemplateRenderer 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 => '', + 'greeting' => $this->renderView('emails.partials.greeting', ['name' => $invoice->customer?->fullname ?? 'Quý khách']), + 'invoice_info' => $this->renderView('emails.partials.invoice-info', ['fields' => $block['config']['fields'] ?? null, 'invoice' => $invoice]), + 'customer_info' => $this->renderView('emails.partials.customer-info', ['fields' => $block['config']['fields'] ?? null, 'customer' => $invoice->customer]), + 'order_items' => $this->renderOrderItems($invoice), + 'attachments' => $this->renderAttachments($invoice), + 'custom_text' => $this->renderView('emails.partials.custom-text', ['content' => $block['config']['content'] ?? '']), + 'footer' => $this->renderView('emails.partials.signature', ['companyName' => $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ẩmServerChu kỳGiá
- -HTML; + return $this->renderView('emails.partials.order-items', ['items' => $order->orderItems]); } 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; + return $this->renderView('emails.partials.attachments', ['attachments' => $invoice->attachments]); } - private function renderCustomText(array $config): string + private function renderView(string $view, array $data): 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"; + return View::make($view, $data)->render(); } // ─── Layout wrapper ─────────────────────────────────────────────────────── - private function wrapLayout(string $title, string $color, string $body, string $companyName): string + private function wrapLayout(string $title, string $body, string $companyName, ?string $bannerUrl = null): 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; + return View::make('emails.layouts.notification', [ + 'title' => $title, + 'body' => $body, + 'companyName' => $companyName ?: config('app.name', 'Thế Giới Số'), + 'bannerUrl' => $bannerUrl, + ])->render(); } // ─── Sample data for preview ────────────────────────────────────────────── diff --git a/app/Services/InvoicePaymentService.php b/app/Services/InvoicePaymentService.php index 2247c5b..8795c0d 100644 --- a/app/Services/InvoicePaymentService.php +++ b/app/Services/InvoicePaymentService.php @@ -5,6 +5,7 @@ namespace App\Services; use App\Enums\Invoice\InvoiceStatus; use App\Enums\Order\OrderStatus; use App\Enums\TransactionStatus; +use App\Jobs\Hosting\ProvisionHostingAccountJob; use App\Jobs\VirtualMachine\Action\VMJobs; use App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob; use App\Models\Addon; @@ -135,6 +136,10 @@ class InvoicePaymentService } foreach ($order->orderItems as $item) { + $isHostingProvision = ! $item->os_id + && $item->server_name + && $item->product?->product_type === 'hosting'; + $service = Service::firstOrCreate( ['order_item_id' => $item->id], [ @@ -142,7 +147,7 @@ class InvoicePaymentService 'product_id' => $item->product_id, 'invoice_id' => $invoice->id, 'server_name' => $item->server_name, - 'status' => $item->os_id ? 'pending' : 'active', + 'status' => ($item->os_id || $isHostingProvision) ? 'pending' : 'active', 'auto_renew' => false, 'started_at' => now(), 'expires_at' => now()->addMonths($item->months ?: $this->cycleToMonths($item->billing_cycle)), @@ -151,8 +156,12 @@ class InvoicePaymentService $this->attachServiceAddons($service, $item); - if ($service->wasRecentlyCreated && $item->product_id && $item->os_id) { - dispatch(new CreateVirtualMachineJob($item)); + if ($service->wasRecentlyCreated && $item->product_id) { + if ($item->os_id) { + dispatch(new CreateVirtualMachineJob($item)); + } elseif ($isHostingProvision) { + dispatch(new ProvisionHostingAccountJob($item)); + } } } } diff --git a/app/Services/Plesk/PleskClientService.php b/app/Services/Plesk/PleskClientService.php index 9157b6c..87bb181 100644 --- a/app/Services/Plesk/PleskClientService.php +++ b/app/Services/Plesk/PleskClientService.php @@ -34,14 +34,27 @@ class PleskClientService ->first(); if ($existing) { - return [ - 'error' => false, - 'data' => [ - 'id' => $existing->plesk_client_id, - 'login' => $existing->login, - 'password' => Crypt::decryptString($existing->password), - ], - ]; + if ($this->clientExists($server, (int) $existing->plesk_client_id)) { + return [ + 'error' => false, + 'data' => [ + 'id' => $existing->plesk_client_id, + 'login' => $existing->login, + 'password' => Crypt::decryptString($existing->password), + ], + ]; + } + + // Cached client was deleted directly on Plesk (out-of-band) — the id is + // dead, so any call reusing it (e.g. owner_client on domain creation) + // fails with "Owner does not exist". Drop the stale row and re-provision. + Log::warning('Cached Plesk client no longer exists on server — re-creating', [ + 'plesk_client_id' => $existing->plesk_client_id, + 'customer_id' => $customer->id, + 'server_id' => $server->id, + ]); + + $existing->delete(); } $login = $this->generateUsername($customer); @@ -130,6 +143,20 @@ class PleskClientService } } + private function clientExists(DnsServer $server, int $pleskClientId): bool + { + try { + $response = $this->plesk->client($server, 15) + ->get($this->plesk->url($server, 'clients', "/{$pleskClientId}")); + + return $response->successful(); + } catch (RequestException $e) { + Log::error('Plesk client existence check failed', ['error' => $e->getMessage(), 'plesk_client_id' => $pleskClientId]); + + return false; + } + } + private function findByLogin(DnsServer $server, string $login): ?array { try { diff --git a/public/images/emails/admin.png b/public/images/emails/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..51a35b20b8c402a9b28b66d774f403825e6604fb GIT binary patch literal 1774 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 Y4>)`Fn1AxfU;qFB07*qoM6N<$f|EFw-T(jq literal 0 HcmV?d00001 diff --git a/public/images/emails/internet.png b/public/images/emails/internet.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a208ae598c29ca5812b3be8ddedf6006299ea9 GIT binary patch literal 4221 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 T5WzCI00000NkvXXu0mjfF^&xV literal 0 HcmV?d00001 diff --git a/public/images/emails/password.png b/public/images/emails/password.png new file mode 100644 index 0000000000000000000000000000000000000000..4d1d37b7fc3342c319a31f39af067227c72be898 GIT binary patch literal 4396 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 z z@%1>j78k}YxW3ZVzr-zFVJ#ImmeA!`I$N4v&f3xNnfbgv0$D2^4Rrv8tgheg)hS?@ zg_Bue6T%Ox$0zO`Y9W=o1ccwCdp$#d6{}RVo>FavurF3oRLd|7wLUxs_ zJVxHS++Rc_w-1uQF{n=2P56e;H&LAjs37xdn%XyXMuL+S?&0HOdc613_x71qSdGd9 zKKzeLZ1B3leJa2=e$qvD$GcCW%mz6f;ZRYoM4T(_+E7Lc;V31J8^tZ|HijF@&Qtbv z2c0GWqT_c-5Dk7BrPXE4ZT?vNF^wEuW*eyoyln7VRhem+`3X+m91kF(8PJ5UZgP7m z94OZUTt>@*n_z9@#eKY2G?2aM!x_bToJd$P<7FI^P#d{&N4sDjp+R zB$bYaHUN_y<?<$s8rH?(3cFVy1bo0tzH&EPo9WniJ#ezZ4ojVLhf@1DT$EG+_Qi*f(OkC8 zxt-e_Ner?3Cx0v^V)&Yr}qYH*Jekm}#O)&2iYWv6OEHB;< zXd{LxJOo-R%InotJ(mI4TSC`@eg)`JgW0vM$@H+sF9C+70H4&ns&36~b!trb$ijPj z%c^mx(y zKlxhMzm=I69-GHmfxA~c;fp2i9Ok@BhGCxqsyrvcNeIW|#ryyQvE)nOMU~5)y9N0a z@GiNX-`SskdKxJVTLA_K}5_yI^Br3=w6LAp@s mM0BeIs{^sbgQXa&)cAj;N*q;6^QJ!l0000 - + - -
    - +
    - - - -
    -
    🎉
    -

    - Hosting của bạn đã sẵn sàng -

    -
    - Gói hosting cho tên miền {{ $domainName }} đã được khởi tạo thành công -
    -
    - - - - - - -
    -
     
    -
    Xin chào {{ $fullName }},
    -
     
    -
    - Dịch vụ hosting của bạn tại {{ $appName }} đã được khởi tạo thành công. Dưới đây là thông tin truy cập: -
    -
     
    - - - - - - -
    -
    Tên miền: {{ $domainName }}
    -
    Gói hosting: {{ $hostingPlan }}
    - @if($serverHost) -
    Máy chủ / FTP Host: {{ $serverHost }}
    - @endif - @if($ftpUsername) -
    Tài khoản FTP: {{ $ftpUsername }}
    - @endif - @if($ftpPassword) -
    Mật khẩu FTP: {{ $ftpPassword }}
    - @endif -
    Trạng thái: Đang hoạt động ✓
    -
    -
     
    - @if($ftpPassword) -
    - ⚠️ Vì lý do bảo mật, mật khẩu FTP chỉ được gửi một lần duy nhất qua email và không được lưu trữ trên hệ thống. Vui lòng lưu lại ở nơi an toàn. -
    -
     
    - @endif - @if($pleskLogin) -
    Tài khoản đăng nhập Plesk Panel (để tự quản lý domain, DNS, database, mail...):
    -
     
    - - - - - - -
    -
    Địa chỉ Panel: {{ $pleskPanelUrl }}
    -
    Tên đăng nhập: {{ $pleskLogin }}
    - @if($pleskPassword) -
    Mật khẩu: {{ $pleskPassword }}
    - @endif -
    -
     
    - @endif -
    - Bạn có thể đăng nhập vào cổng quản lý để theo dõi và quản lý dịch vụ của mình. -
    -
     
    - -
     
    -
    - Nếu bạn không yêu cầu dịch vụ này, vui lòng liên hệ ngay với chúng tôi qua {{ $supportEmail }} hoặc {{ $supportPhone }}. -
    -
     
    -
    - Email này được gửi tự động. Vui lòng không trả lời email này. -
    -
     
    -
    +
    + + Thư gửi từ Công Ty Cổ Phần Giải Pháp Công Nghệ Thế Giới Số +
    - + + - -
    +

    + Hosting của bạn đã sẵn sàng +

    + +

    + Hosting của bạn đã sẵn sàng +

    + +

    + Kính chào {{ $fullName }}, +

    + +

    + Gói hosting cho tên miền {{ $domainName }} tại {{ $appName }} đã được khởi tạo thành công. Dưới đây là thông tin truy cập: +

    + + - + + - - + + + + @if($serverHost) - + + - - + @endif + @if($ftpUsername) + + + + @endif + @if($ftpPassword) - + + + @endif + + + + + +
    - - - - - - -
     
    -
    Tên miền:{{ $domainName }}
     
    Gói hosting:{{ $hostingPlan }}
    - - - - - - - - - - - - - - -
      - Premium HOSTING -   - Cloud SERVER -   - EMAIL DOANH NGHIỆP -   - Thiết Kế WEBSITE -  
    -
    Máy chủ / FTP Host:{{ $serverHost }}
     
    Tài khoản FTP:{{ $ftpUsername }}
    - - - - - - -
     
    +
    Mật khẩu FTP:{{ $ftpPassword }}
    Trạng thái:Đang hoạt động ✓
    + + @if($ftpPassword) +

    + Lưu ý: Vì lý do bảo mật, mật khẩu FTP chỉ được gửi một lần duy nhất qua email và không được lưu trữ trên hệ thống. Vui lòng lưu lại ở nơi an toàn. +

    + @endif + + @if($pleskLogin) +

    TÀI KHOẢN ĐĂNG NHẬP PLESK PANEL

    + + + + + + + + + + + + @if($pleskPassword) + + + + + @endif + +
    Địa chỉ Panel:{{ $pleskPanelUrl }}
    Tên đăng nhập:{{ $pleskLogin }}
    Mật khẩu:{{ $pleskPassword }}
    + @endif + +

    + Bạn có thể đăng nhập vào cổng quản lý để theo dõi và quản lý dịch vụ của mình. +

    + + + + +
    + Đăng nhập ngay
    + +

    + Nếu bạn không yêu cầu dịch vụ này, vui lòng liên hệ ngay với chúng tôi qua {{ $supportEmail }} hoặc {{ $supportPhone }}. +

    + +

    + Email này được gửi tự động. Vui lòng không trả lời email này. +

    + +

    ---
    + Hotline: {{ $supportPhone }}
    + Email: {{ $supportEmail }} +

    - + +