ifnex/04_Laravel/app/Http/Controllers/Api/PaymentController.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

213 lines
8.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Api;
use App\Enums\PaymentGateway;
use App\Enums\TransactionStatus;
use App\Enums\TransactionType;
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 App\Services\ZarinpalService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class PaymentController extends Controller
{
public function __construct(
protected WalletService $walletService,
protected ZarinpalService $zarinpalService
) {}
/**
* ارسال کاربر به درگاه پرداخت
*/
public function redirectToGateway(Request $request): JsonResponse
{
$user = $request->user();
if (!$user) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$validated = $request->validate([
'amount' => ['required', 'numeric', 'min:10000'], // حداقل ۱۰ هزار ریال
'description' => ['nullable', 'string', 'max:500'],
'frontend_callback' => ['nullable', 'url', 'max:500'], // URL فرانت‌اند برای redirect بعد از پرداخت
]);
$wallet = $user->wallet ?? Wallet::create([
'user_id' => $user->id,
'balance' => 0,
]);
// بررسی مسدود نبودن کیف پول
if ($wallet->isFrozen()) {
return response()->json([
'message' => 'کیف پول شما مسدود است. لطفاً با پشتیبانی تماس بگیرید.',
], 403);
}
try {
// ایجاد تراکنش pending
$transaction = $this->walletService->requestDeposit(
wallet: $wallet,
amount: $validated['amount'],
description: $validated['description'] ?? 'شارژ آنلاین کیف پول',
user: $user,
gateway: PaymentGateway::ZARINPAL
);
// دریافت لینک پرداخت از زرین‌پال
$paymentData = $this->zarinpalService->requestPayment(
amount: $validated['amount'],
description: $validated['description'] ?? 'شارژ کیف پول IFNEX',
mobile: $user->phone,
email: $user->email
);
// ذخیره authority و frontend_callback در metadata
$metadata = $transaction->metadata ?? [];
$metadata['zarinpal_authority'] = $paymentData['authority'];
$metadata['frontend_callback'] = $validated['frontend_callback'] ?? null;
$transaction->update(['metadata' => $metadata]);
return response()->json([
'success' => true,
'message' => 'در حال انتقال به درگاه پرداخت...',
'payment_url' => $paymentData['payment_url'],
'transaction_id' => $transaction->id,
'authority' => $paymentData['authority'],
'amount' => $paymentData['amount'],
]);
} catch (\Exception $e) {
Log::error('Payment gateway error', [
'user_id' => $user->id,
'error' => $e->getMessage(),
]);
return response()->json([
'success' => false,
'message' => 'خطا در ارتباط با درگاه پرداخت: ' . $e->getMessage(),
], 500);
}
}
/**
* Callback از درگاه پرداخت (بعد از بازگشت کاربر)
*/
public function callback(Request $request): RedirectResponse
{
$authority = $request->input('Authority');
$status = $request->input('Status'); // OK یا NOK
// پیدا کردن تراکنش بر اساس authority
$transaction = WalletTransaction::whereJsonContains('metadata->zarinpal_authority', $authority)->first();
if (!$transaction) {
Log::warning('Payment callback: transaction not found', ['authority' => $authority]);
return redirect(config('ifnex.zarinpal.frontend_failure_url', '/payment/failed'));
}
$frontendCallback = $transaction->metadata['frontend_callback'] ?? config('ifnex.zarinpal.frontend_failure_url', '/payment/failed');
// کاربر پرداخت را لغو کرده
if ($status !== 'OK') {
$this->walletService->failTransaction($transaction, 'کاربر پرداخت را لغو کرد یا پرداخت ناموفق بود.');
$failureUrl = $frontendCallback . '?' . http_build_query([
'status' => 'failed',
'transaction_id' => $transaction->id,
'message' => 'پرداخت ناموفق بود',
]);
return redirect($failureUrl);
}
try {
// بررسی صحت پرداخت
$verification = $this->zarinpalService->verifyPayment(
authority: $authority,
expectedAmount: $transaction->amount
);
if (!$verification['success']) {
$this->walletService->failTransaction(
$transaction,
'تأیید درگاه ناموفق بود: ' . ($verification['error_message'] ?? 'نامشخص')
);
$failureUrl = $frontendCallback . '?' . http_build_query([
'status' => 'failed',
'transaction_id' => $transaction->id,
'message' => 'تأیید پرداخت ناموفق بود',
]);
return redirect($failureUrl);
}
// تأیید تراکنش و افزایش موجودی
$this->walletService->completeDeposit(
transaction: $transaction,
gatewayReferenceId: $verification['ref_id'] ?? $authority
);
$successUrl = config('ifnex.zarinpal.frontend_success_url', '/payment/success') . '?' . http_build_query([
'status' => 'success',
'transaction_id' => $transaction->id,
'ref_id' => $verification['ref_id'] ?? '',
'amount' => $transaction->amount,
]);
return redirect($successUrl);
} catch (\Exception $e) {
Log::error('Payment callback error', [
'transaction_id' => $transaction->id,
'error' => $e->getMessage(),
]);
$this->walletService->failTransaction($transaction, 'خطای سیستمی: ' . $e->getMessage());
$failureUrl = $frontendCallback . '?' . http_build_query([
'status' => 'failed',
'transaction_id' => $transaction->id,
'message' => 'خطای سیستمی در پردازش پرداخت',
]);
return redirect($failureUrl);
}
}
/**
* استعلام وضعیت یک پرداخت
*/
public function checkStatus(Request $request, $transactionId): JsonResponse
{
$user = $request->user();
$transaction = WalletTransaction::where('id', $transactionId)
->whereHas('wallet', fn ($q) => $q->where('user_id', $user->id))
->first();
if (!$transaction) {
return response()->json(['message' => 'تراکنش یافت نشد'], 404);
}
return response()->json([
'transaction_id' => $transaction->id,
'status' => $transaction->status->label(),
'status_code' => $transaction->status->value,
'amount' => $transaction->amount,
'description' => $transaction->description,
'reference_id' => $transaction->gateway_reference_id,
'created_at' => $transaction->created_at->toIso8601String(),
]);
}
}