feat(billing): renewals, coupons, VAT numbering, billing cycles and invoice payment tracking
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ea9a53f742
commit
25f23d541e
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service;
|
||||
use App\Services\InvoicePaymentService;
|
||||
use App\Support\MailGlobals;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AutoRenewFailedMail extends TemplateMailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public const SCENARIO = 'auto_renew_failed';
|
||||
|
||||
public function __construct(
|
||||
public readonly Customer $customer,
|
||||
public readonly Service $service,
|
||||
public readonly Invoice $invoice,
|
||||
) {}
|
||||
|
||||
protected function scenario(): string
|
||||
{
|
||||
return self::SCENARIO;
|
||||
}
|
||||
|
||||
protected function variables(): array
|
||||
{
|
||||
$serverName = (string) $this->service->server_name;
|
||||
$frontend = MailGlobals::frontendUrl();
|
||||
|
||||
return [
|
||||
'client_name' => $this->customer->fullname ?? '',
|
||||
'product_name' => $this->service->product?->name ?? 'Dịch vụ',
|
||||
'server_name_suffix' => $serverName !== '' ? ' (' . e($serverName) . ')' : '',
|
||||
'expires_at' => $this->service->expires_at?->format('d/m/Y') ?? 'N/A',
|
||||
'invoice_number' => $this->invoice->invoice_number,
|
||||
'outstanding' => number_format(InvoicePaymentService::outstanding($this->invoice), 0, ',', '.'),
|
||||
'balance_formatted' => number_format((int) $this->customer->total, 0, ',', '.'),
|
||||
'topup_url' => $frontend . '/nap-tien',
|
||||
'invoice_url' => $frontend . $this->invoice->clientPath(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace App\Mail\Customer;
|
||||
|
||||
use App\Mail\TemplateMailable;
|
||||
use App\Services\InvoicePdfService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailables\Attachment;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DomainOrderReceivedNotification extends TemplateMailable implements ShouldQueue
|
||||
@@ -15,6 +17,15 @@ class DomainOrderReceivedNotification extends TemplateMailable implements Should
|
||||
protected array $data,
|
||||
) {}
|
||||
|
||||
public function attachments(): array
|
||||
{
|
||||
$invoice = app(InvoicePdfService::class)->attachment($this->data['invoice_id'] ?? null);
|
||||
|
||||
return $invoice
|
||||
? [Attachment::fromData(fn () => $invoice['pdf'], $invoice['file_name'])->withMime('application/pdf')]
|
||||
: [];
|
||||
}
|
||||
|
||||
protected function scenario(): string
|
||||
{
|
||||
return 'domain_order_received';
|
||||
|
||||
@@ -6,17 +6,26 @@ use App\Mail\TemplateMailable;
|
||||
use App\Support\EmailTemplateShell;
|
||||
use App\Support\MailGlobals;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailables\Attachment;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class HostingAccountCreated extends TemplateMailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public array $data) {}
|
||||
public function __construct(
|
||||
public array $data,
|
||||
public readonly ?string $invoicePdf = null,
|
||||
public readonly ?string $invoicePdfFileName = null,
|
||||
) {}
|
||||
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
if ($this->invoicePdf === null || $this->invoicePdfFileName === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [Attachment::fromData(fn () => $this->invoicePdf, $this->invoicePdfFileName)->withMime('application/pdf')];
|
||||
}
|
||||
|
||||
protected function scenario(): string
|
||||
@@ -47,6 +56,8 @@ class HostingAccountCreated extends TemplateMailable
|
||||
'full_name' => $data['customer_name'] ?? 'Quý khách',
|
||||
'domain_name' => $data['domain_name'] ?? '',
|
||||
'hosting_plan' => $data['hosting_plan'] ?? '',
|
||||
'server_name' => $data['server_name'] ?? '',
|
||||
'recipient_label' => $this->recipientLabel($data),
|
||||
'server_host_row' => $row('Máy chủ / FTP Host', $data['server_host'] ?? null),
|
||||
'ftp_username_row' => $row('Tài khoản FTP', $data['ftp_username'] ?? null),
|
||||
'ftp_password_row' => $row('Mật khẩu FTP', $data['ftp_password'] ?? null),
|
||||
@@ -56,4 +67,16 @@ class HostingAccountCreated extends TemplateMailable
|
||||
'plesk_block' => $pleskBlock,
|
||||
];
|
||||
}
|
||||
|
||||
private function recipientLabel(array $data): string
|
||||
{
|
||||
foreach (['customer_name', 'customer_company'] as $key) {
|
||||
$value = trim((string) ($data[$key] ?? ''));
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Công ty';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Mail;
|
||||
use App\Enums\Invoice\InvoiceStatus;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\InvoicePaymentService;
|
||||
use App\Support\InvoiceLookup;
|
||||
use App\Support\MailGlobals;
|
||||
use App\Support\EmailTemplateShell;
|
||||
use Carbon\Carbon;
|
||||
@@ -91,6 +92,8 @@ class InvoiceNotificationMail extends TemplateMailable
|
||||
? 'File PDF hóa đơn gia hạn đã được đính kèm trong email này. Vui lòng hoàn tất thanh toán trước hạn để dịch vụ không bị gián đoạn.'
|
||||
: 'File PDF hóa đơn đã được đính kèm trong email này để Quý khách tiện lưu trữ và đối chiếu.',
|
||||
'pay_button' => $this->payButton(),
|
||||
'lookup_row' => InvoiceLookup::emailRow($this->invoice),
|
||||
'lookup_note' => InvoiceLookup::emailNote($this->invoice),
|
||||
'attachments_block' => '',
|
||||
'note_block' => $this->noteBlock(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServiceRenewedMail extends TemplateMailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $scenarioKey,
|
||||
private readonly array $data,
|
||||
) {}
|
||||
|
||||
protected function scenario(): string
|
||||
{
|
||||
return $this->scenarioKey;
|
||||
}
|
||||
|
||||
protected function variables(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user