- Implement AdminDomainOrderTest to cover admin functionalities for domain orders including listing, confirming registration, KYC updates, activation, rejection, and refunds. - Implement ClientDomainOrderTest to cover client functionalities for placing domain orders, including validation for balance, duplicate orders, and domain normalization. - Ensure proper handling of domain pricing and service statuses throughout the tests.
292 lines
11 KiB
PHP
292 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Api\Admin\Domain;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Api\Admin\Domain\ConfirmRegistrationRequest;
|
|
use App\Http\Requests\Api\Admin\Domain\RejectAndRefundRequest;
|
|
use App\Http\Requests\Api\Admin\Domain\UpdateKycUrlRequest;
|
|
use App\Mail\Customer\DomainOrderStatusMail;
|
|
use App\Models\Notification;
|
|
use App\Models\Order;
|
|
use App\Models\Service;
|
|
use App\Models\Setting;
|
|
use App\Models\Template;
|
|
use App\Repositories\Api\Domain\DomainOrderRepository;
|
|
use App\Services\Api\Domain\DomainOrderService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class AdminDomainOrderController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly DomainOrderRepository $repo,
|
|
private readonly DomainOrderService $service,
|
|
) {}
|
|
|
|
public function pendingCount(): JsonResponse
|
|
{
|
|
$count = $this->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);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// 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, $templateName] = $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 {
|
|
$setting = Setting::where('key', 'setting')->first();
|
|
$settingValue = $setting ? json_decode((string) $setting->value, true) : [];
|
|
|
|
$template = Template::where('name', $templateName)->first();
|
|
if (!$template) {
|
|
return;
|
|
}
|
|
|
|
Mail::to($customer->email)->send(new DomainOrderStatusMail($template, [
|
|
'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,
|
|
'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(),
|
|
];
|
|
}
|
|
}
|