303 lines
12 KiB
PHP
303 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs\VirtualMachine;
|
|
|
|
use App\Enums\Invoice\InvoiceStatus;
|
|
use App\Enums\Order\OrderStatus;
|
|
use App\Http\Services\Telegram;
|
|
use App\Http\Services\Invoice\InvoiceService;
|
|
use App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob;
|
|
use App\Models\Invoice;
|
|
use App\Models\Order;
|
|
use App\Models\OrderItem;
|
|
|
|
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\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ProcessOrderPaymentJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public Order $order;
|
|
public int $retryCount = 0;
|
|
public const MAX_RETRIES = 3;
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*/
|
|
public function __construct(Order $order, int $retryCount = 0)
|
|
{
|
|
$this->order = $order;
|
|
$this->retryCount = $retryCount;
|
|
}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*/
|
|
public function handle(InvoiceService $invoiceService, Telegram $telegram): void
|
|
{
|
|
try {
|
|
$user = $this->order->customer;
|
|
$invoice = $this->order->invoice;
|
|
|
|
if (!$user || !$invoice) {
|
|
Log::error("Không tìm thấy thông tin user hoặc invoice cho order #{$this->order->order_num}");
|
|
return;
|
|
}
|
|
|
|
// Kiểm tra credit
|
|
if ($user->credit < $this->order->amount) {
|
|
$errorMessage = "Không đủ credit để thanh toán đơn hàng #{$this->order->order_num}";
|
|
Log::error($errorMessage, [
|
|
'user_credit' => $user->credit,
|
|
'order_amount' => $this->order->amount
|
|
]);
|
|
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Insufficient Credit');
|
|
return;
|
|
}
|
|
|
|
// Tạo VM cho các sản phẩm VPS
|
|
$vmCreationResults = [];
|
|
$allVMsCreated = true;
|
|
$hasVMCreationAttempt = false;
|
|
|
|
foreach ($this->order->orderItems as $item) {
|
|
try {
|
|
// Chỉ tạo VM cho sản phẩm VPS (product_type = 4)
|
|
if ($item->product_type == 4) {
|
|
$hasVMCreationAttempt = true;
|
|
|
|
// Tạo VM và lấy kết quả
|
|
$vmCreationResult = $this->createVirtualMachine($item);
|
|
|
|
$vmCreationResults[] = [
|
|
'order_item_id' => $item->id,
|
|
'product_name' => $item->getProductAttribute()?->name ?? 'Unknown',
|
|
'success' => $vmCreationResult
|
|
];
|
|
|
|
if (!$vmCreationResult) {
|
|
$allVMsCreated = false;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::error("Lỗi khi tạo VM cho order item #{$item->id}: " . $e->getMessage());
|
|
$vmCreationResults[] = [
|
|
'order_item_id' => $item->id,
|
|
'product_name' => $item->getProductAttribute()?->name ?? 'Unknown',
|
|
'success' => false,
|
|
'error' => $e->getMessage()
|
|
];
|
|
$allVMsCreated = false;
|
|
}
|
|
}
|
|
|
|
// Kiểm tra xem có thực sự có VM nào được tạo không
|
|
if (!$hasVMCreationAttempt) {
|
|
$errorMessage = "Không có sản phẩm VPS nào được xử lý trong đơn hàng #{$this->order->order_num}";
|
|
Log::error($errorMessage);
|
|
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - No VM Creation Attempt');
|
|
return;
|
|
}
|
|
|
|
// Kiểm tra kết quả tạo VM
|
|
if (empty($vmCreationResults)) {
|
|
$errorMessage = "Không có kết quả tạo VM nào được trả về cho đơn hàng #{$this->order->order_num}";
|
|
Log::error($errorMessage);
|
|
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Empty VM Results');
|
|
return;
|
|
}
|
|
|
|
// Kiểm tra xem có ít nhất một VM thành công không
|
|
$successfulVMs = array_filter($vmCreationResults, fn($vm) => $vm['success']);
|
|
$failedVMs = array_filter($vmCreationResults, fn($vm) => !$vm['success']);
|
|
|
|
// Nếu tất cả VM được tạo thành công, tiến hành thanh toán
|
|
if ($allVMsCreated && count($successfulVMs) > 0) {
|
|
DB::beginTransaction();
|
|
|
|
try {
|
|
// Trừ tiền người dùng
|
|
$user->decrement('credit', (int) $this->order->amount);
|
|
|
|
// Cập nhật trạng thái hóa đơn và đơn hàng
|
|
$invoiceService->markAsPaid($invoice);
|
|
$this->order->update(['status' => OrderStatus::ACTIVE->value]);
|
|
|
|
// Gửi thông báo Telegram
|
|
$telegram->SendOrderPaidNotification($invoice);
|
|
|
|
DB::commit();
|
|
|
|
// Gửi thông báo thành công cho khách hàng
|
|
$this->sendSuccessNotification($vmCreationResults);
|
|
|
|
} catch (\Throwable $e) {
|
|
DB::rollBack();
|
|
throw $e;
|
|
}
|
|
} else {
|
|
// Có lỗi khi tạo VM hoặc không có VM nào thành công
|
|
$errorDetails = [
|
|
'order_id' => $this->order->id,
|
|
'order_num' => $this->order->order_num,
|
|
'vm_results' => $vmCreationResults,
|
|
'total_vms' => count($vmCreationResults),
|
|
'successful_vms' => count($successfulVMs),
|
|
'failed_vms' => count($failedVMs),
|
|
'all_vms_created' => $allVMsCreated
|
|
];
|
|
|
|
// Kiểm tra trường hợp đặc biệt: có VPS nhưng không có VM nào thành công
|
|
if (count($vmCreationResults) > 0 && count($successfulVMs) === 0) {
|
|
Log::error("Có sản phẩm VPS nhưng không có VM nào được tạo thành công", $errorDetails);
|
|
$telegram->SendErrorMessage(array_merge($errorDetails, [
|
|
'error' => 'Tất cả VM đều thất bại - không có VM nào thành công'
|
|
]), 'ProcessOrderPaymentJob - All VMs Failed');
|
|
}
|
|
|
|
// Thử lại nếu chưa vượt quá số lần thử
|
|
if ($this->retryCount < self::MAX_RETRIES) {
|
|
// Kiểm tra nếu lỗi là timeout, tăng delay time
|
|
$isTimeoutError = $this->isTimeoutError($failedVMs);
|
|
$delayMinutes = $isTimeoutError ? 10 : 5; // Timeout thì chờ 10 phút, lỗi khác chờ 5 phút
|
|
|
|
Log::warning("Có lỗi khi tạo VM, thử lại lần " . ($this->retryCount + 1), array_merge($errorDetails, [
|
|
'is_timeout_error' => $isTimeoutError,
|
|
'delay_minutes' => $delayMinutes
|
|
]));
|
|
|
|
// Thử lại với delay tùy theo loại lỗi
|
|
self::dispatch($this->order, $this->retryCount + 1)
|
|
->delay(now()->addMinutes($delayMinutes));
|
|
} else {
|
|
// Đã thử hết số lần, gửi thông báo lỗi
|
|
Log::error("Đã thử tạo VM {$this->retryCount} lần nhưng vẫn thất bại", $errorDetails);
|
|
|
|
$telegram->SendErrorMessage(array_merge($errorDetails, [
|
|
'error' => 'Không thể tạo VM sau nhiều lần thử',
|
|
'retry_count' => $this->retryCount
|
|
]), 'ProcessOrderPaymentJob - VM Creation Failed After Retries');
|
|
|
|
// Gửi thông báo lỗi cho khách hàng
|
|
$this->sendFailureNotification($vmCreationResults);
|
|
}
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
$isTimeoutException = strpos(strtolower($e->getMessage()), 'timeout') !== false ||
|
|
strpos(strtolower($e->getMessage()), 'curl error 28') !== false;
|
|
|
|
Log::error("Lỗi trong ProcessOrderPaymentJob: " . $e->getMessage(), [
|
|
'order_id' => $this->order->id,
|
|
'order_num' => $this->order->order_num,
|
|
'is_timeout' => $isTimeoutException,
|
|
'retry_count' => $this->retryCount,
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
|
|
$telegram->SendErrorMessage([
|
|
'error' => $e->getMessage(),
|
|
'order_id' => $this->order->id,
|
|
'order_num' => $this->order->order_num,
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
'is_timeout' => $isTimeoutException,
|
|
'retry_count' => $this->retryCount
|
|
], 'ProcessOrderPaymentJob - Exception');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tạo VM cho một order item
|
|
*/
|
|
private function createVirtualMachine(OrderItem $item): bool
|
|
{
|
|
try {
|
|
$vCenter = app(\App\Http\Services\vCenter::class);
|
|
$telegram = app(\App\Http\Services\Telegram::class);
|
|
|
|
$vmJob = new CreateVirtualMachineJob($item, $this->retryCount > 0);
|
|
return $vmJob->handle($vCenter, $telegram);
|
|
|
|
} catch (\Throwable $e) {
|
|
Log::error("Lỗi khi tạo VM cho order item #{$item->id}: " . $e->getMessage(), [
|
|
'order_item_id' => $item->id,
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send success notification to customer
|
|
*/
|
|
private function sendSuccessNotification(array $vmResults): void
|
|
{
|
|
try {
|
|
$customer = $this->order->customer;
|
|
|
|
// Gửi email thông báo thành công
|
|
\App\Jobs\Mail\Customer\OrderPaymentSuccessJob::dispatch([
|
|
'customer_email' => $customer->email,
|
|
'customer_name' => trim($customer->firstname . ' ' . $customer->lastname),
|
|
'order_id' => $this->order->id,
|
|
'order_num' => $this->order->order_num,
|
|
'amount' => $this->order->amount,
|
|
'vm_results' => $vmResults
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
Log::error('Không thể gửi email thông báo thành công: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the error is a timeout error
|
|
*/
|
|
private function isTimeoutError(array $failedVMs): bool
|
|
{
|
|
foreach ($failedVMs as $vmResult) {
|
|
if (isset($vmResult['error'])) {
|
|
$errorMessage = strtolower($vmResult['error']);
|
|
|
|
// Kiểm tra các dấu hiệu của timeout error
|
|
if (strpos($errorMessage, 'timeout') !== false ||
|
|
strpos($errorMessage, 'curl error 28') !== false ||
|
|
strpos($errorMessage, 'operation timed out') !== false ||
|
|
strpos($errorMessage, 'connection timeout') !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Send failure notification to customer
|
|
*/
|
|
private function sendFailureNotification(array $vmResults): void
|
|
{
|
|
try {
|
|
$customer = $this->order->customer;
|
|
|
|
// Gửi email thông báo lỗi
|
|
\App\Jobs\Mail\Customer\VMCreationFailedJob::dispatch([
|
|
'customer_email' => $customer->email,
|
|
'customer_name' => trim($customer->firstname . ' ' . $customer->lastname),
|
|
'product_name' => 'Multiple Products',
|
|
'order_id' => $this->order->id,
|
|
'order_num' => $this->order->order_num,
|
|
'error_message' => 'Không thể khởi tạo máy chủ ảo sau nhiều lần thử. Vui lòng liên hệ admin để được hỗ trợ.',
|
|
'vm_results' => $vmResults
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
Log::error('Không thể gửi email thông báo lỗi: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|