repo->countPending(); return response()->json(['error' => false, 'data' => ['count' => $count]]); } public function index(Request $request): JsonResponse { $orders = $this->repo->getDomainOrders( $request->only(['status', 'customer', 'domain', 'from', 'to', 'per_page']) ); $items = collect($orders->items())->map(fn ($order) => $this->formatOrder($order)); return response()->json([ 'error' => false, 'data' => [ 'items' => $items, 'total' => $orders->total(), 'per_page' => $orders->perPage(), 'current_page' => $orders->currentPage(), 'last_page' => $orders->lastPage(), ], ]); } public function show(int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); if (!$order) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } return response()->json([ 'error' => false, 'data' => $this->formatOrder($order), ]); } public function confirmRegistration(ConfirmRegistrationRequest $request, int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); if (!$order) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } try { $service = $this->service->confirmRegistration($order, $request->validated()); return response()->json([ 'error' => false, 'message' => 'Xác nhận đăng ký tên miền thành công.', 'data' => $this->formatService($service), ]); } catch (\InvalidArgumentException $e) { return response()->json(['error' => true, 'message' => $e->getMessage()], 422); } catch (\Throwable $e) { Log::error('AdminDomainOrderController@confirmRegistration', ['error' => $e->getMessage(), 'id' => $id]); return response()->json(['error' => true, 'message' => 'Xác nhận thất bại. Vui lòng thử lại.'], 500); } } public function updateKyc(UpdateKycUrlRequest $request, int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); if (!$order) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } try { $service = $this->service->updateKycUrl($order, $request->validated()['kyc_url']); return response()->json([ 'error' => false, 'message' => 'Cập nhật link KYC thành công.', 'data' => $this->formatService($service), ]); } catch (\InvalidArgumentException $e) { return response()->json(['error' => true, 'message' => $e->getMessage()], 422); } catch (\Throwable $e) { Log::error('AdminDomainOrderController@updateKyc', ['error' => $e->getMessage(), 'id' => $id]); return response()->json(['error' => true, 'message' => 'Cập nhật KYC thất bại. Vui lòng thử lại.'], 500); } } public function activate(int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); if (!$order) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } try { $service = $this->service->activate($order); $this->notifyCustomerDomainStatus($order, 'approved'); return response()->json([ 'error' => false, 'message' => 'Kích hoạt dịch vụ tên miền thành công.', 'data' => $this->formatService($service), ]); } catch (\InvalidArgumentException $e) { return response()->json(['error' => true, 'message' => $e->getMessage()], 422); } catch (\Throwable $e) { Log::error('AdminDomainOrderController@activate', ['error' => $e->getMessage(), 'id' => $id]); return response()->json(['error' => true, 'message' => 'Kích hoạt thất bại. Vui lòng thử lại.'], 500); } } public function rejectAndRefund(RejectAndRefundRequest $request, int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); if (!$order) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } try { $service = $this->service->rejectAndRefund($order, $request->validated()['reason']); $this->notifyCustomerDomainStatus($order, 'rejected', $request->validated()['reason']); return response()->json([ 'error' => false, 'message' => 'Từ chối và hoàn tiền thành công.', 'data' => $this->formatService($service), ]); } catch (\InvalidArgumentException $e) { return response()->json(['error' => true, 'message' => $e->getMessage()], 422); } catch (\Throwable $e) { Log::error('AdminDomainOrderController@rejectAndRefund', ['error' => $e->getMessage(), 'id' => $id]); return response()->json(['error' => true, 'message' => 'Hoàn tiền thất bại. Vui lòng thử lại.'], 500); } } public function getRegistrant(int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); $service = $order?->orderItems->first()?->service; if (!$order || !$service) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } return response()->json([ 'error' => false, 'data' => $this->registrantService->get($service, $order->customer), ]); } public function updateRegistrant(UpdateRegistrantInfoRequest $request, int $id): JsonResponse { $order = $this->repo->findDomainOrder($id); $service = $order?->orderItems->first()?->service; if (!$order || !$service) { return response()->json([ 'error' => true, 'message' => 'Không tìm thấy đơn hàng domain.', ], 404); } $data = $request->validated(); $service = $this->registrantService->update($service, $data['registrant_type'], $data['registrant_info']); return response()->json([ 'error' => false, 'message' => 'Cập nhật thông tin đăng ký thành công.', 'data' => $this->formatService($service), ]); } public function downloadRegistrantDocx(int $id): Response { $order = $this->repo->findDomainOrder($id); $item = $order?->orderItems->first(); $service = $item?->service; abort_if(!$order || !$service, 404, 'Không tìm thấy đơn hàng domain.'); $registrant = $this->registrantService->get($service, $order->customer); $docx = $this->registrantService->renderDocx($item->domain_name, $registrant['type'], $registrant['info']); $filename = 'phieu-dang-ky-' . $item->domain_name . '.docx'; return response($docx, 200, [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ]); } // ========================================================================= // Private helpers // ========================================================================= private function notifyCustomerDomainStatus(Order $order, string $status, string $reason = ''): void { $customer = $order->customer; $domainName = $order->orderItems->first()?->domain_name ?? ''; $orderNum = $order->order_num; if (!$customer) { return; } [$title, $body, $scenario] = $status === 'approved' ? ["Tên miền {$domainName} đã được duyệt", "Tên miền {$domainName} (#{$orderNum}) đã được kích hoạt thành công.", 'domain_order_approved'] : ["Tên miền {$domainName} bị từ chối", "Tên miền {$domainName} (#{$orderNum}) đã bị từ chối. Lý do: {$reason}. Số dư đã được hoàn lại.", 'domain_order_rejected']; Notification::notifyCustomer($customer, [ 'type' => 'domain_order_' . $status, 'title' => $title, 'body' => $body, 'link' => '/don-hang', ]); try { if (!\App\Models\Template::where('scenario', $scenario)->where('status', 1)->exists()) { return; } $setting = \App\Models\Setting::where('key', 'setting')->first(); $settingValue = $setting ? json_decode((string) $setting->value, true) : []; app(EmailTemplateService::class)->send($scenario, $customer->email, [ 'company_name' => $settingValue['company'] ?? config('app.name'), 'company_logo' => $settingValue['logo'] ?? '', 'domain_name' => $domainName, 'client_name' => $customer->fullname, 'order_num' => $orderNum, 'reason' => $reason, ]); } catch (\Throwable $e) { Log::error('AdminDomainOrderController: không thể gửi email khách hàng: ' . $e->getMessage()); } } // ========================================================================= // Private formatters // ========================================================================= private function formatOrder(Order $order): array { $item = $order->orderItems->first(); $service = $item?->service; return [ 'id' => $order->id, 'order_num' => $order->order_num, 'status' => $order->status, 'total' => $order->total, 'payment_method' => $order->payment_method, 'created_at' => $order->created_at?->toIso8601String(), 'customer' => $order->customer ? [ 'id' => $order->customer->id, 'uuid' => $order->customer->uuid, 'fullname' => $order->customer->fullname, 'email' => $order->customer->email, ] : null, 'domain_name' => $item?->domain_name, 'months' => $item?->months, 'invoice' => $order->invoice ? [ 'id' => $order->invoice->id, 'invoice_number' => $order->invoice->invoice_number, 'status' => $order->invoice->status, 'total' => $order->invoice->total, 'paid_at' => $order->invoice->paid_at?->toIso8601String(), ] : null, 'service' => $service ? $this->formatService($service) : null, ]; } private function formatService(Service $service): array { return [ 'id' => $service->id, 'uuid' => $service->uuid, 'status' => $service->status, 'server_name' => $service->server_name, 'registrar_reference' => $service->registrar_reference, 'kyc_url' => $service->kyc_url, 'rejection_reason' => $service->rejection_reason, 'registrant_type' => $service->registrant_type, 'registrant_info' => $service->registrant_info, 'auto_renew' => $service->auto_renew, 'started_at' => $service->started_at?->toIso8601String(), 'expires_at' => $service->expires_at?->toIso8601String(), 'refunded_at' => $service->refunded_at?->toIso8601String(), 'cancelled_at' => $service->cancelled_at?->toIso8601String(), 'created_at' => $service->created_at?->toIso8601String(), ]; } }