- 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>
119 lines
4.3 KiB
PHP
119 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
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;
|
|
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,
|
|
* mirroring CreateVirtualMachineJob's role for VPS orders. Reuses the same
|
|
* PleskHostingService::provision() the admin "create hosting account" form
|
|
* calls, so behavior (customer creation, subscription, notification emails)
|
|
* stays identical between manual and automatic provisioning.
|
|
*/
|
|
class ProvisionHostingAccountJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $timeout = 300;
|
|
|
|
public int $tries = 1;
|
|
|
|
public function __construct(public OrderItem $item)
|
|
{
|
|
}
|
|
|
|
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) {
|
|
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')->find($servicePlanId) : null;
|
|
$hostingPlan = $servicePlan?->plesk?->plesk_plan_name;
|
|
|
|
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,
|
|
]);
|
|
|
|
if (! $hostingPlan) {
|
|
return 'Sản phẩm chưa gắn gói Plesk (service_plan_plesk).';
|
|
}
|
|
|
|
ServiceProvisionLog::record($service, 'provision_started', 'Bắt đầu khởi tạo hosting', ['domain' => $domainName, 'plesk_plan' => $hostingPlan]);
|
|
|
|
$invoiceId = Invoice::where('order_id', $this->item->order_id)->where('invoice_type', 1)->orderBy('id')->value('id');
|
|
$triedServerIds = [];
|
|
$lastError = null;
|
|
|
|
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,
|
|
'domain_name' => $domainName,
|
|
'hosting_plan' => $hostingPlan,
|
|
'invoice_id' => $invoiceId,
|
|
]);
|
|
|
|
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);
|
|
}
|
|
|
|
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}";
|
|
}
|
|
}
|