feat(service): huỷ dịch vụ, SSL, đổi mật khẩu hosting, lịch sử thanh toán theo hình thức

- Lịch sử thanh toán hoá đơn trả thêm `method` (tự động gia hạn / số dư / admin / chuyển khoản / hoàn tiền), bỏ đuôi `(method)` khỏi ghi chú
- `DnsServer::panelUrl()` = https://<domain>/ dùng cho email cấp hosting và nút Plesk của khách
- Order item trả thêm `uuid`/`type` của dịch vụ để FE dẫn tới trang quản lý
- Yêu cầu huỷ dịch vụ, chứng chỉ SSL, lịch sử gia hạn, đổi mật khẩu hosting cho khách

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-28 16:40:24 +07:00
co-authored by Claude Sonnet 5.5
parent a6b641015e
commit b44a88bb52
72 changed files with 1701 additions and 231 deletions
+42 -3
View File
@@ -2,7 +2,9 @@
namespace App\Support\Billing;
use App\Models\ActivityLog;
use App\Models\Invoice;
use App\Models\Service;
use App\Models\Transaction;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
@@ -43,11 +45,12 @@ class InvoicePaymentHistory
->get();
$invoiceByNumber = $invoices->keyBy('invoice_number');
$renewalCausers = self::renewalCausers($invoices);
foreach ($transactions as $transaction) {
$invoice = self::matchInvoice($transaction, $invoiceByNumber, $invoiceByOrderNum);
if ($invoice) {
$history[$invoice->id][] = self::present($transaction);
$history[$invoice->id][] = self::present($transaction, $renewalCausers[$invoice->invoice_number] ?? null);
}
}
@@ -65,7 +68,42 @@ class InvoicePaymentHistory
return $invoiceByNumber->get($number);
}
private static function present(Transaction $transaction): array
private static function renewalCausers(Collection $invoices): array
{
$numbers = $invoices->where('invoice_type', 2)->pluck('invoice_number')->all();
if ($numbers === []) {
return [];
}
return ActivityLog::where('subject_type', Service::class)
->where('event', 'renewed')
->whereIn('new_values->invoice_number', $numbers)
->get(['causer_type', 'new_values'])
->mapWithKeys(fn (ActivityLog $log) => [$log->new_values['invoice_number'] => $log->causer_type])
->all();
}
private static function method(Transaction $transaction, bool $isRefund, ?string $renewalCauser): string
{
if ($isRefund) {
return 'refund';
}
if (in_array($transaction->type, ['transfer', 'balance'], true)) {
return $transaction->type;
}
if (preg_match('/\(([a-z_]+)\)$/', (string) $transaction->note, $match) !== 1) {
return 'other';
}
return match (true) {
$match[1] === 'balance' && $renewalCauser === 'system' => 'auto_renew',
default => $match[1],
};
}
private static function present(Transaction $transaction, ?string $renewalCauser): array
{
$isRefund = Str::endsWith((string) $transaction->trans, '-REFUND');
@@ -75,7 +113,8 @@ class InvoicePaymentHistory
'kind' => $isRefund ? 'refund' : 'payment',
'amount' => abs((int) $transaction->amount),
'type' => $transaction->type,
'note' => $transaction->note,
'method' => self::method($transaction, $isRefund, $renewalCauser),
'note' => preg_replace('/ \([a-z_]+\)$/', '', (string) $transaction->note) ?: null,
'recorded_by' => $transaction->user?->fullname ?: $transaction->user?->username,
'created_at' => $transaction->created_at,
];