- Lịch sử thanh toán hoá đơn trả thêm `method` (tự động gia hạn / số dư / admin / chuyển khoản / hoàn tiền), bỏ đuôi `(method)` khỏi ghi chú - `DnsServer::panelUrl()` = https://<domain>/ dùng cho email cấp hosting và nút Plesk của khách - Order item trả thêm `uuid`/`type` của dịch vụ để FE dẫn tới trang quản lý - Yêu cầu huỷ dịch vụ, chứng chỉ SSL, lịch sử gia hạn, đổi mật khẩu hosting cho khách Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
92 lines
3.9 KiB
PHP
92 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Admin\Service;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Notification;
|
|
use App\Models\Service;
|
|
use App\Models\ServiceCancellationRequest;
|
|
use App\Support\ServiceCancellation;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ServiceCancellationRequestController extends Controller
|
|
{
|
|
public function index(int $id): JsonResponse
|
|
{
|
|
$requests = ServiceCancellationRequest::where('service_id', $id)
|
|
->latest('id')
|
|
->limit(20)
|
|
->get(['id', 'reason_code', 'reason', 'status', 'admin_note', 'resolved_at', 'resolved_by', 'created_at']);
|
|
|
|
return response()->json(['error' => false, 'data' => $requests]);
|
|
}
|
|
|
|
public function resolve(Request $request, int $id, int $requestId): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'action' => 'required|in:approve,reject',
|
|
'note' => 'required_if:action,reject|nullable|string|max:1000',
|
|
]);
|
|
$approve = $data['action'] === 'approve';
|
|
|
|
$result = DB::transaction(function () use ($id, $requestId, $data, $approve, $request) {
|
|
$cancellation = ServiceCancellationRequest::where('service_id', $id)->lockForUpdate()->findOrFail($requestId);
|
|
if ($cancellation->status !== ServiceCancellationRequest::STATUS_PENDING) {
|
|
return null;
|
|
}
|
|
|
|
$admin = $request->user();
|
|
$cancellation->update([
|
|
'status' => $approve ? ServiceCancellationRequest::STATUS_APPROVED : ServiceCancellationRequest::STATUS_REJECTED,
|
|
'admin_note' => filled($data['note'] ?? null) ? trim($data['note']) : null,
|
|
'resolved_at' => now(),
|
|
'resolved_by' => (string) ($admin?->fullname ?: $admin?->username ?: 'admin'),
|
|
]);
|
|
|
|
$outcome = $approve ? ServiceCancellation::approve(Service::with('orderItem')->findOrFail($id)) : null;
|
|
|
|
return [$cancellation, $outcome];
|
|
});
|
|
|
|
[$result, $outcome] = $result ?? [null, null];
|
|
|
|
if ($result === null) {
|
|
return response()->json(['error' => true, 'message' => 'Yêu cầu này đã được xử lý.'], 422);
|
|
}
|
|
|
|
$this->notifyCustomer($result, $approve, $outcome);
|
|
|
|
$message = match (true) {
|
|
! $approve => 'Đã từ chối yêu cầu hủy.',
|
|
$outcome === ServiceCancellation::SCHEDULED => 'Đã duyệt: dịch vụ dùng tới hết hạn rồi tự hủy.',
|
|
default => 'Đã duyệt và hủy dịch vụ.',
|
|
};
|
|
|
|
return response()->json(['error' => false, 'message' => $message, 'data' => $result]);
|
|
}
|
|
|
|
private function notifyCustomer(ServiceCancellationRequest $cancellation, bool $approve, ?string $outcome): void
|
|
{
|
|
$cancellation->loadMissing(['customer', 'service']);
|
|
if (! $cancellation->customer) {
|
|
return;
|
|
}
|
|
|
|
$name = $cancellation->service->server_name ?: 'dịch vụ của bạn';
|
|
$expiresAt = $cancellation->service->expires_at?->format('d/m/Y');
|
|
$approvedBody = $outcome === ServiceCancellation::SCHEDULED
|
|
? "Dịch vụ {$name} sẽ không gia hạn nữa và vẫn sử dụng được đến hết {$expiresAt}."
|
|
: "Dịch vụ {$name} đã được hủy theo yêu cầu của bạn.";
|
|
$reason = $cancellation->admin_note ? " Ghi chú: {$cancellation->admin_note}" : '';
|
|
|
|
Notification::notifyCustomer($cancellation->customer, [
|
|
'type' => 'service_cancellation_resolved',
|
|
'title' => $approve ? 'Yêu cầu hủy dịch vụ đã được duyệt' : 'Yêu cầu hủy dịch vụ bị từ chối',
|
|
'body' => ($approve ? $approvedBody : "Yêu cầu hủy dịch vụ {$name} chưa được chấp nhận.") . $reason,
|
|
'link' => '/dich-vu',
|
|
]);
|
|
}
|
|
}
|