Add feature tests for domain order management
- 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.
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user