feat: nhắc gia hạn QR theo dịch vụ, failover server hosting, CCCD admin, log cấp phát dịch vụ
- Mail campaign: mẫu nhắc gia hạn thanh toán QR, chọn dịch vụ + chu kỳ từng dòng (renewal_services) - Hosting: nhiều server Plesk cho một gói (service_plan_servers), chuyển server khi server chết - Admin xem/sửa giấy tờ định danh khách, tuỳ chọn gia hạn cho khách, nhật ký cấp phát dịch vụ - Đánh dấu mẫu email dịch vụ là non-marketing Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
047c23beff
commit
55a834a2f8
@@ -7,6 +7,8 @@ namespace App\Jobs\Hosting;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\ServicePlan;
|
||||
use App\Services\Hosting\HostingProvisionFailure;
|
||||
use App\Services\Hosting\HostingServerSelector;
|
||||
use App\Services\Plesk\PleskHostingService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@@ -14,6 +16,8 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
use App\Support\ServiceProvisionLog;
|
||||
|
||||
/**
|
||||
* Auto-provisions a Plesk hosting account when a hosting order is paid,
|
||||
@@ -26,97 +30,89 @@ class ProvisionHostingAccountJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/** Plesk subscription creation can take a couple of minutes */
|
||||
public int $timeout = 300;
|
||||
|
||||
/** No auto-retry — hosting provisioning errors need manual review */
|
||||
public int $tries = 1;
|
||||
|
||||
public function __construct(public OrderItem $item)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(PleskHostingService $plesk): void
|
||||
public function handle(PleskHostingService $plesk, HostingServerSelector $servers, HostingProvisionFailure $failure): void
|
||||
{
|
||||
try {
|
||||
$error = $this->provision($plesk, $servers, $failure);
|
||||
} catch (Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
|
||||
if ($error !== null) {
|
||||
$failure->handle($this->item, $error);
|
||||
}
|
||||
}
|
||||
|
||||
private function provision(PleskHostingService $plesk, HostingServerSelector $servers, HostingProvisionFailure $failure): ?string
|
||||
{
|
||||
$service = $this->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;
|
||||
return 'Không tìm thấy dịch vụ hoặc tên miền của đơn hàng.';
|
||||
}
|
||||
|
||||
$servicePlanId = $this->item->product?->service_plan_id;
|
||||
$servicePlan = $servicePlanId
|
||||
? ServicePlan::with(['plesk', 'dnsServer'])->find($servicePlanId)
|
||||
: null;
|
||||
|
||||
$servicePlan = $servicePlanId ? ServicePlan::with('plesk')->find($servicePlanId) : null;
|
||||
$hostingPlan = $servicePlan?->plesk?->plesk_plan_name;
|
||||
$server = $servicePlan?->dnsServer;
|
||||
|
||||
Log::channel('Custom')->info('ProvisionHostingAccountJob resolved service plan', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'product_name' => $this->item->product?->name,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
'plesk_plan_name' => $hostingPlan,
|
||||
'dns_manager_server_id' => $server?->id,
|
||||
'dns_manager_server_ip' => $server?->ip_address,
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'product_name' => $this->item->product?->name,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
'plesk_plan_name' => $hostingPlan,
|
||||
]);
|
||||
|
||||
if (! $hostingPlan) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: no service_plan_plesk mapping for this product', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
]);
|
||||
|
||||
return;
|
||||
return 'Sản phẩm chưa gắn gói Plesk (service_plan_plesk).';
|
||||
}
|
||||
|
||||
if (! $server) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: service plan has no dns_manager_server_id configured', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
]);
|
||||
ServiceProvisionLog::record($service, 'provision_started', 'Bắt đầu khởi tạo hosting', ['domain' => $domainName, 'plesk_plan' => $hostingPlan]);
|
||||
|
||||
return;
|
||||
}
|
||||
$invoiceId = Invoice::where('order_id', $this->item->order_id)->where('invoice_type', 1)->orderBy('id')->value('id');
|
||||
$triedServerIds = [];
|
||||
$lastError = null;
|
||||
|
||||
if (! $server->servesHosting()) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: configured server does not serve hosting', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
while ($server = $servers->pick($servicePlan, $triedServerIds)) {
|
||||
$triedServerIds[] = $server->id;
|
||||
|
||||
$result = $plesk->provision([
|
||||
'customer_id' => $this->item->order->customer_id,
|
||||
'service_id' => $service->id,
|
||||
'dns_manager_server_id' => $server->id,
|
||||
'server_type' => $server->type,
|
||||
'domain_name' => $domainName,
|
||||
'hosting_plan' => $hostingPlan,
|
||||
'invoice_id' => $invoiceId,
|
||||
]);
|
||||
|
||||
return;
|
||||
if (! $result['error']) {
|
||||
$service->update(['status' => 'active']);
|
||||
ServiceProvisionLog::record($service, 'provision_completed', 'Khởi tạo hoàn tất — dịch vụ đã hoạt động', ['server_id' => $server->id]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$lastError = $result['message'] ?? 'Plesk trả về lỗi không rõ.';
|
||||
$servers->forget($server);
|
||||
|
||||
if ($servers->isHealthy($server)) {
|
||||
return $lastError;
|
||||
}
|
||||
|
||||
$failure->serverFailedOver($this->item, $server, $lastError);
|
||||
}
|
||||
|
||||
$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' => $hostingPlan,
|
||||
'invoice_id' => Invoice::where('order_id', $this->item->order_id)->where('invoice_type', 1)->orderBy('id')->value('id'),
|
||||
]);
|
||||
|
||||
if ($result['error']) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'hosting_plan' => $hostingPlan,
|
||||
'message' => $result['message'] ?? 'Unknown error',
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$service->update(['status' => 'active']);
|
||||
return $lastError === null
|
||||
? 'Gói hosting không có server Plesk nào đang hoạt động.'
|
||||
: "Tất cả server Plesk của gói đều lỗi. Lỗi cuối: {$lastError}";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user