ifnex/04_Laravel/app/Http/Controllers/Api/WalletController.php
Kazem Alghasi 78f5919b69 feat(wallet): complete wallet system with admin adjust, freeze/unfreeze, activity logs
- Add Enums: TransactionType, TransactionStatus, PaymentGateway
- Migrations: wallets, wallet_transactions, wallet_activity_logs
- Models: Wallet, WalletTransaction, WalletActivityLog
- Services: WalletService with complete business logic
- Controllers: WalletController, PaymentController
- Filament Resources: WalletResource, WalletTransactionResource
- Artisan Command: ifnex:token for API token generation
- Sanctum integration with statefulApi and exception handling
- Full API endpoints: balance, transactions, admin-adjust, freeze/unfreeze
2026-08-07 22:51:42 +03:30

290 lines
9.7 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Enums\PaymentGateway;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Wallet;
use App\Models\WalletActivityLog;
use App\Models\WalletTransaction;
use App\Services\WalletService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class WalletController extends Controller
{
public function __construct(protected WalletService $walletService) {}
/**
* مشاهده موجودی کیف پول کاربر
*/
public function balance(Request $request): JsonResponse
{
$user = $request->user();
if (!$user) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$wallet = $user->wallet;
WalletActivityLog::log(
$wallet ?? new Wallet(['user_id' => $user->id]),
'balance_viewed',
'مشاهده موجودی کیف پول',
null,
['balance' => $wallet?->balance ?? 0],
$user
);
return response()->json([
'balance' => $wallet?->balance ?? 0,
'total_deposited' => $wallet?->total_deposited ?? 0,
'total_withdrawn' => $wallet?->total_withdrawn ?? 0,
'is_frozen' => $wallet?->isFrozen() ?? false,
'currency' => 'IRR',
]);
}
/**
* لیست تراکنش‌های کاربر (paginated)
*/
public function transactions(Request $request): JsonResponse
{
$user = $request->user();
if (!$user) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$wallet = $user->wallet;
if (!$wallet) {
return response()->json([
'transactions' => [],
'total' => 0,
'current_page' => 1,
]);
}
$perPage = $request->query('per_page', 15);
$status = $request->query('status'); // pending, completed, failed
$type = $request->query('type'); // deposit, withdrawal
$query = $wallet->transactions()->orderByDesc('created_at');
if ($status) {
$query->where('status', $status);
}
if ($type) {
$query->where('type', $type);
}
$transactions = $query->paginate($perPage);
return response()->json([
'transactions' => $transactions->map(fn ($t) => [
'id' => $t->id,
'amount' => $t->amount,
'formatted_amount' => $t->formatted_amount,
'type' => $t->type->label(),
'status' => $t->status->label(),
'status_code' => $t->status->value,
'gateway' => $t->gateway?->label(),
'reference_id' => $t->gateway_reference_id,
'description' => $t->description,
'created_at' => $t->created_at->toIso8601String(),
'created_at_jalali' => \Morilog\Jalali\Jalalian::fromCarbon($t->created_at)->format('Y/m/d H:i'),
]),
'total' => $transactions->total(),
'current_page' => $transactions->currentPage(),
'last_page' => $transactions->lastPage(),
]);
}
/**
* شارژ/کسر دستی توسط ادمین
*/
public function adminAdjust(Request $request): JsonResponse
{
$user = $request->user();
if (!$user || !$user->isSuperAdmin()) {
return response()->json(['message' => 'Forbidden — Only Super Admin can perform this action'], 403);
}
try {
$validated = $request->validate([
'user_id' => ['required', 'exists:users,id'],
'amount' => ['required', 'numeric', 'not_in:0'], // مثبت = شارژ، منفی = کسر
'description' => ['required', 'string', 'max:1000'],
'admin_notes' => ['nullable', 'string', 'max:2000'],
]);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Validation failed',
'errors' => $e->errors(),
], 422);
}
$targetUser = User::findOrFail($validated['user_id']);
$wallet = $targetUser->wallet ?? Wallet::create([
'user_id' => $targetUser->id,
'balance' => 0,
]);
try {
$amount = abs($validated['amount']);
if ($validated['amount'] > 0) {
// شارژ
$transaction = $this->walletService->manualDeposit(
wallet: $wallet,
amount: $amount,
description: $validated['description'],
admin: $user,
adminNotes: $validated['admin_notes'] ?? null
);
$message = 'شارژ دستی با موفقیت انجام شد';
} else {
// کسر
$transaction = $this->walletService->manualWithdrawal(
wallet: $wallet,
amount: $amount,
description: $validated['description'],
admin: $user,
adminNotes: $validated['admin_notes'] ?? null
);
$message = 'کسر دستی با موفقیت انجام شد';
}
return response()->json([
'message' => $message,
'transaction_id' => $transaction->id,
'target_user_id' => $targetUser->id,
'target_user_name' => $targetUser->name,
'balance_before' => $transaction->balance_before,
'balance_after' => $transaction->balance_after,
'amount' => $transaction->amount,
'description' => $transaction->description,
'admin_notes' => $transaction->admin_notes,
'performed_by' => $user->name,
'created_at' => $transaction->created_at->toIso8601String(),
]);
} catch (\Exception $e) {
return response()->json([
'message' => 'خطا در انجام عملیات: ' . $e->getMessage(),
], 422);
}
}
/**
* مسدود کردن کیف پول (توسط ادمین)
*/
public function freeze(Request $request, $walletId): JsonResponse
{
$user = $request->user();
if (!$user || !$user->isSuperAdmin()) {
return response()->json(['message' => 'Forbidden'], 403);
}
$validated = $request->validate([
'reason' => ['required', 'string', 'max:1000'],
]);
$wallet = Wallet::findOrFail($walletId);
$oldFrozen = $wallet->isFrozen();
$wallet->freeze($validated['reason'], $user);
WalletActivityLog::log(
$wallet,
'wallet_frozen',
'کیف پول مسدود شد توسط ' . $user->name,
['is_frozen' => $oldFrozen],
['is_frozen' => true, 'reason' => $validated['reason']],
$user
);
return response()->json([
'message' => 'کیف پول با موفقیت مسدود شد',
'wallet_id' => $wallet->id,
'reason' => $validated['reason'],
]);
}
/**
* رفع مسدودی کیف پول (توسط ادمین)
*/
public function unfreeze(Request $request, $walletId): JsonResponse
{
$user = $request->user();
if (!$user || !$user->isSuperAdmin()) {
return response()->json(['message' => 'Forbidden'], 403);
}
$wallet = Wallet::findOrFail($walletId);
$oldReason = $wallet->freeze_reason;
$wallet->unfreeze($user);
WalletActivityLog::log(
$wallet,
'wallet_unfrozen',
'کیف پول از مسدودی خارج شد توسط ' . $user->name,
['reason' => $oldReason],
['is_frozen' => false],
$user
);
return response()->json([
'message' => 'کیف پول با موفقیت از مسدودی خارج شد',
'wallet_id' => $wallet->id,
]);
}
/**
* مشاهده لاگ فعالیت‌های کیف پول (توسط ادمین)
*/
public function activityLog(Request $request, $walletId): JsonResponse
{
$user = $request->user();
if (!$user || !$user->isSuperAdmin()) {
return response()->json(['message' => 'Forbidden'], 403);
}
$wallet = Wallet::findOrFail($walletId);
$perPage = $request->query('per_page', 20);
$logs = $wallet->activityLogs()->with('user')->paginate($perPage);
return response()->json([
'wallet_id' => $wallet->id,
'user_name' => $wallet->user->name,
'balance' => $wallet->balance,
'is_frozen' => $wallet->isFrozen(),
'freeze_reason' => $wallet->freeze_reason,
'logs' => $logs->map(fn ($log) => [
'id' => $log->id,
'action' => $log->action,
'description' => $log->description,
'old_values' => $log->old_values,
'new_values' => $log->new_values,
'performed_by' => $log->user?->name ?? 'سیستم',
'ip_address' => $log->ip_address,
'created_at' => $log->created_at->toIso8601String(),
'created_at_jalali' => \Morilog\Jalali\Jalalian::fromCarbon($log->created_at)->format('Y/m/d H:i'),
]),
'total' => $logs->total(),
'current_page' => $logs->currentPage(),
'last_page' => $logs->lastPage(),
]);
}
}