- Created stubs for JavaScript and SASS assets. - Added stubs for channel, command, component, controller, event, factory, job, listener, mail, middleware, migration, model, notification, observer, policy, request, resource, route provider, seeder, and validation rule. - Implemented scaffolding for configuration, service provider, and Vite integration. - Included example views for module structure. - Introduced a module loader for Vite to handle asset paths dynamically.
236 lines
9.7 KiB
PHP
236 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Admin\Transaction;
|
|
|
|
use App\Enums\TransactionStatus;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Mail\Customer\ManualDepositStatus;
|
|
use App\Models\Notification;
|
|
use App\Models\Log;
|
|
use App\Models\Transaction;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log as FacadesLog;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class AdminTransactionController extends Controller
|
|
{
|
|
public function pendingCount(): JsonResponse
|
|
{
|
|
$count = Transaction::where('type', 'manual')
|
|
->where('status', TransactionStatus::PENDING->value)
|
|
->whereNotNull('proof_image')
|
|
->count();
|
|
|
|
return response()->json(['error' => false, 'data' => ['count' => $count]]);
|
|
}
|
|
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$query = Transaction::with('customer')
|
|
->where('type', 'manual')
|
|
->orderByDesc('created_at');
|
|
|
|
if ($request->filled('status')) {
|
|
$query->where('status', $request->integer('status'));
|
|
}
|
|
|
|
if ($request->filled('filter')) {
|
|
$search = $request->string('filter')->trim();
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('public_code', 'like', "%{$search}%")
|
|
->orWhereHas('customer', fn($c) => $c
|
|
->where('fullname', 'like', "%{$search}%")
|
|
->orWhere('email', 'like', "%{$search}%"));
|
|
});
|
|
}
|
|
|
|
$perPage = $request->integer('per_page', 15);
|
|
$paginated = $query->paginate($perPage);
|
|
|
|
$items = $paginated->map(fn(Transaction $t) => [
|
|
'id' => $t->id,
|
|
'uuid' => $t->uuid,
|
|
'public_code' => $t->public_code,
|
|
'trans' => $t->trans,
|
|
'amount' => $t->amount,
|
|
'type' => $t->type,
|
|
'status' => $t->status,
|
|
'status_label' => TransactionStatus::from((int) $t->status)->label(),
|
|
'proof_image' => $t->proof_image ? Storage::disk('public')->url($t->proof_image) : null,
|
|
'created_at' => $t->created_at?->toISOString(),
|
|
'customer' => $t->customer ? [
|
|
'uuid' => $t->customer->uuid,
|
|
'fullname' => $t->customer->fullname,
|
|
'email' => $t->customer->email,
|
|
'phone' => $t->customer->phone,
|
|
'total' => $t->customer->total,
|
|
'total_pending' => $t->customer->total_pending,
|
|
] : null,
|
|
]);
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'data' => [
|
|
'items' => $items,
|
|
'total' => $paginated->total(),
|
|
'per_page' => $paginated->perPage(),
|
|
'current_page' => $paginated->currentPage(),
|
|
'last_page' => $paginated->lastPage(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function approve(string $uuid): JsonResponse
|
|
{
|
|
$transaction = Transaction::with('customer')
|
|
->where('uuid', $uuid)
|
|
->where('type', 'manual')
|
|
->where('status', TransactionStatus::PENDING->value)
|
|
->first();
|
|
|
|
if (!$transaction) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => 'Giao dịch không tồn tại hoặc không thể duyệt',
|
|
], 404);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$transaction->update(['status' => TransactionStatus::COMPLETED->value]);
|
|
|
|
$customer = $transaction->customer;
|
|
$customer->total += $transaction->amount;
|
|
$customer->credit += (int) floor($transaction->amount / 100);
|
|
$customer->total_pending = max(0, $customer->total_pending - $transaction->amount);
|
|
$customer->save();
|
|
|
|
Log::create([
|
|
'agent' => 'admin',
|
|
'client_ip' => request()->ip(),
|
|
'description' => 'Admin duyệt nạp tiền thủ công: ' . number_format($transaction->amount) . ' VNĐ (+' . floor($transaction->amount / 100) . ' credit)',
|
|
'customer_id' => $customer->id,
|
|
]);
|
|
|
|
Notification::notifyCustomer($customer, [
|
|
'type' => 'deposit_success',
|
|
'title' => 'Nạp tiền thành công',
|
|
'body' => 'Giao dịch ' . $transaction->public_code . ' đã được duyệt.',
|
|
'link' => '/lich-su-giao-dich/' . $transaction->id,
|
|
'data' => [
|
|
'transaction_uuid' => $transaction->uuid,
|
|
'amount' => $transaction->amount,
|
|
'public_code' => $transaction->public_code,
|
|
],
|
|
]);
|
|
|
|
DB::commit();
|
|
|
|
try {
|
|
Mail::to($customer->email)->send(new ManualDepositStatus([
|
|
'customer_id' => $customer->id,
|
|
'customer_email' => $customer->email,
|
|
'customer_name' => $customer->fullname,
|
|
'transaction_code' => $transaction->public_code,
|
|
'amount' => $transaction->amount,
|
|
'status_label' => 'Đã duyệt',
|
|
'status_color' => '#15803d',
|
|
'status_bg' => '#f0fdf4',
|
|
'subject' => 'Yêu cầu nạp tiền đã được duyệt - ' . $transaction->public_code,
|
|
'status_message' => 'Yêu cầu nạp tiền của bạn đã được duyệt. Số tiền đã được cộng vào tài khoản và có thể sử dụng ngay.',
|
|
'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
|
|
'processed_date' => now()->format('d/m/Y H:i:s'),
|
|
]));
|
|
} catch (\Throwable $e) {
|
|
FacadesLog::error('Không thể gửi email duyệt nạp tiền thủ công: ' . $e->getMessage(), [
|
|
'transaction_code' => $transaction->public_code,
|
|
'customer_email' => $customer->email,
|
|
]);
|
|
}
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
FacadesLog::error('AdminTransactionController::approve error: ' . $e->getMessage());
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => 'Có lỗi xảy ra, vui lòng thử lại',
|
|
], 500);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'message' => 'Đã duyệt giao dịch thành công',
|
|
]);
|
|
}
|
|
|
|
public function reject(string $uuid): JsonResponse
|
|
{
|
|
$transaction = Transaction::with('customer')
|
|
->where('uuid', $uuid)
|
|
->where('type', 'manual')
|
|
->where('status', TransactionStatus::PENDING->value)
|
|
->first();
|
|
|
|
if (!$transaction) {
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => 'Giao dịch không tồn tại hoặc không thể từ chối',
|
|
], 404);
|
|
}
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$transaction->update(['status' => TransactionStatus::FAILED->value]);
|
|
|
|
$customer = $transaction->customer;
|
|
$customer->total_pending = max(0, $customer->total_pending - $transaction->amount);
|
|
$customer->save();
|
|
|
|
Log::create([
|
|
'agent' => 'admin',
|
|
'client_ip' => request()->ip(),
|
|
'description' => 'Admin từ chối nạp tiền thủ công: ' . number_format($transaction->amount) . ' VNĐ',
|
|
'customer_id' => $customer->id,
|
|
]);
|
|
|
|
DB::commit();
|
|
|
|
try {
|
|
Mail::to($customer->email)->send(new ManualDepositStatus([
|
|
'customer_id' => $customer->id,
|
|
'customer_email' => $customer->email,
|
|
'customer_name' => $customer->fullname,
|
|
'transaction_code' => $transaction->public_code,
|
|
'amount' => $transaction->amount,
|
|
'status_label' => 'Không phù hợp',
|
|
'status_color' => '#b91c1c',
|
|
'status_bg' => '#fef2f2',
|
|
'subject' => 'Yêu cầu nạp tiền không phù hợp - ' . $transaction->public_code,
|
|
'status_message' => 'Yêu cầu nạp tiền của bạn chưa phù hợp để được duyệt. Vui lòng kiểm tra lại chứng từ hoặc liên hệ bộ phận hỗ trợ để được hướng dẫn thêm.',
|
|
'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
|
|
'processed_date' => now()->format('d/m/Y H:i:s'),
|
|
]));
|
|
} catch (\Throwable $e) {
|
|
FacadesLog::error('Không thể gửi email từ chối nạp tiền thủ công: ' . $e->getMessage(), [
|
|
'transaction_code' => $transaction->public_code,
|
|
'customer_email' => $customer->email,
|
|
]);
|
|
}
|
|
} catch (\Exception $e) {
|
|
DB::rollBack();
|
|
FacadesLog::error('AdminTransactionController::reject error: ' . $e->getMessage());
|
|
return response()->json([
|
|
'error' => true,
|
|
'message' => 'Có lỗi xảy ra, vui lòng thử lại',
|
|
], 500);
|
|
}
|
|
|
|
return response()->json([
|
|
'error' => false,
|
|
'message' => 'Đã từ chối giao dịch',
|
|
]);
|
|
}
|
|
}
|