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; } /** * OPTIMIZED PAYMENT FLOW * * Thay đổi: TRỪ TIỀN TRƯỚC → TẠO VM SAU * * Lý do: * 1. User đã có credit trong hệ thống (đã nạp qua VNPay) * 2. Nếu tạo VM fail → refund credit dễ dàng * 3. Tránh tình huống: VM tạo ok nhưng chưa trừ tiền * 4. Đảm bảo tính nhất quán: Đã trừ tiền = đã có order valid */ DB::beginTransaction(); try { // STEP 1: Trừ tiền người dùng $user->decrement('credit', (int) $this->order->amount); // STEP 2: 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]); DB::commit(); Log::info("✓ Đã trừ credit và cập nhật order #{$this->order->order_num}"); } catch (\Throwable $e) { DB::rollBack(); Log::error("Lỗi khi trừ credit cho order #{$this->order->order_num}: " . $e->getMessage()); throw $e; } // STEP 3: Tạo VM cho các sản phẩm VPS (sau khi đã trừ tiền) $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; } } // STEP 4: Xử lý kết quả tạo VM if (!$hasVMCreationAttempt) { // Không có VPS nào trong order → OK, chỉ là Hosting/Domain $telegram->SendOrderPaidNotification($invoice); $this->sendSuccessNotification([]); return; } 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); $this->handleVMCreationFailure($user, $invoice, $vmCreationResults, $telegram); 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 if ($allVMsCreated && count($successfulVMs) > 0) { // Gửi thông báo thành công $telegram->SendOrderPaidNotification($invoice); $this->sendSuccessNotification($vmCreationResults); Log::info("✓ Hoàn tất order #{$this->order->order_num} - Tất cả VM đã được tạo"); } else { // Có lỗi khi tạo VM $this->handleVMCreationFailure($user, $invoice, $vmCreationResults, $telegram); } } catch (\Throwable $e) { Log::error("Lỗi nghiêm trọng trong ProcessOrderPaymentJob: " . $e->getMessage(), [ 'order_id' => $this->order->id, 'trace' => $e->getTraceAsString() ]); $telegram->SendErrorMessage( "Lỗi nghiêm trọng khi xử lý order #{$this->order->order_num}: " . $e->getMessage(), 'ProcessOrderPaymentJob - Critical Error' ); } } /** * Xử lý khi tạo VM thất bại */ private function handleVMCreationFailure($user, $invoice, $vmCreationResults, Telegram $telegram): void { $successfulVMs = array_filter($vmCreationResults, fn($vm) => $vm['success']); $failedVMs = array_filter($vmCreationResults, fn($vm) => !$vm['success']); $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), ]; // 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'); // REFUND CREDIT vì không tạo được VM nào try { DB::transaction(function () use ($user, $invoice) { $user->increment('credit', (int) $this->order->amount); $invoice->update(['status' => InvoiceStatus::UNPAID->value]); $this->order->update(['status' => OrderStatus::PENDING->value]); Log::info("✓ Đã hoàn tiền cho order #{$this->order->order_num} do không tạo được VM"); }); } catch (\Throwable $e) { Log::error("Lỗi khi hoàn tiền: " . $e->getMessage()); } } // Thử lại nếu chưa vượt quá số lần thử và có VM fail if ($this->retryCount < self::MAX_RETRIES && count($failedVMs) > 0) { // Kiểm tra nếu lỗi là timeout, tăng delay time $isTimeoutError = $this->isTimeoutError($failedVMs); $delayMinutes = $isTimeoutError ? 10 : 5; // Timeout chờ 10 phút, lỗi khác 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 if (count($failedVMs) > 0) { // Đã 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); } } /** * 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; \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, 'addons_summary' => $this->resolveAddonsSummary(), ]); } catch (\Throwable $e) { Log::error('Không thể gửi email thông báo thành công: ' . $e->getMessage()); } } /** * Resolve addon display rows from all order items' addons_meta. * Returns array of [{name, qty, subtotal}] for email rendering. * * @return array */ private function resolveAddonsSummary(): array { $rows = []; $this->order->load('orderItems'); foreach ($this->order->orderItems as $item) { $meta = is_array($item->addons_meta) ? $item->addons_meta : []; foreach ($meta as $entry) { $addon = \App\Models\Addon::find($entry['addon_id'] ?? null); if (!$addon) { continue; } $qty = (int) ($entry['qty'] ?? 0); $rows[] = [ 'name' => $addon->name, 'qty' => $qty, 'subtotal' => (int) ($entry['subtotal'] ?? $addon->price * $qty), ]; } } return $rows; } /** * 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()); } } }