diff --git a/04_Laravel/app/Console/Commands/SyncWordPressUsers.php b/04_Laravel/app/Console/Commands/SyncWordPressUsers.php index e202539..f4c2a9f 100644 --- a/04_Laravel/app/Console/Commands/SyncWordPressUsers.php +++ b/04_Laravel/app/Console/Commands/SyncWordPressUsers.php @@ -3,139 +3,144 @@ namespace App\Console\Commands; use App\Models\User; -use App\Enums\UserRole; +use App\Models\Wallet; use Illuminate\Console\Command; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Str; +use Illuminate\Support\Facades\Hash; class SyncWordPressUsers extends Command { protected $signature = 'ifnex:sync-wp-users - {--email=info@ifnex.ir : ایمیل کاربر وردپرس برای sincron} - {--create-if-missing : ایجاد کاربر در لاراول اگر وجود نداشته باشد}'; - - protected $description = 'یک‌سازی کاربران وردپرس با لاراول و ساخت توکن Sanctum'; + {--wp-db-host=localhost : WordPress database host} + {--wp-db-port=3306 : WordPress database port} + {--wp-db-name= : WordPress database name} + {--wp-db-user=root : WordPress database user} + {--wp-db-pass= : WordPress database password} + {--wp-table-prefix=wp_ : WordPress table prefix}'; - public function handle(): int + protected $description = 'Sync WordPress users to Laravel'; + + public function handle() { - $wpEmail = $this->option('email'); - $createIfMissing = $this->option('create-if-missing'); + $this->info('🔄 Syncing WordPress users to Laravel...'); + $this->newLine(); - $this->info("🔍 در حال پیدا کردن کاربر وردپرس: $wpEmail"); + // دریافت اطلاعات دیتابیس وردپرس + $wpHost = $this->option('wp-db-host'); + $wpPort = $this->option('wp-db-port'); + $wpDbName = $this->option('wp-db-name'); + $wpUser = $this->option('wp-db-user'); + $wpPass = $this->option('wp-db-pass'); + $wpPrefix = $this->option('wp-table-prefix'); - // ۱. پیدا کردن کاربر لاراول - $laravelUser = User::where('email', $wpEmail)->first(); - - if (!$laravelUser) { - if ($createIfMissing) { - $this->warn("کاربر در لاراول یافت نشد. در حال ایجاد..."); - - $laravelUser = User::create([ - 'name' => 'IFNEX Admin', - 'email' => $wpEmail, - 'password' => bcrypt('password'), - 'role' => UserRole::SuperAdmin, - 'is_active' => true, - ]); - - $this->info("✅ کاربر در لاراول ایجاد شد: ID {$laravelUser->id}"); - } else { - $this->error("❌ کاربر در لاراول یافت نشد. از --create-if-missing استفاده کنید."); - return 1; - } - } else { - $this->info("✅ کاربر در لاراول پیدا شد: ID {$laravelUser->id} ({$laravelUser->name})"); + if (empty($wpDbName)) { + $wpDbName = $this->ask('WordPress database name?'); } - // ۲. پاک کردن توکن‌های قدیمی - $oldCount = $laravelUser->tokens()->count(); - $laravelUser->tokens()->delete(); - $this->info("🗑️ {$oldCount} توکن قدیمی پاک شد."); - - // ۳. ساخت توکن جدید - $token = $laravelUser->createToken('wordpress-bridge')->plainTextToken; - - $this->newLine(); - $this->line('========================================'); - $this->info('🔑 توکن Sanctum جدید (کپی کنید):'); - $this->line('========================================'); - $this->newLine(); - $this->line($token); - $this->newLine(); - $this->line('========================================'); - $this->newLine(); - - // ۴. ذخیره توکن در دیتابیس وردپرس - $this->info("💾 در حال ذخیره توکن در دیتابیس وردپرس..."); - try { - $wpPdo = DB::connection('mysql')->getPdo(); - // اتصال به دیتابیس وردپرس - $wpDbName = env('WP_DB_NAME', 'ifnexwp'); - $wpPdo->exec("USE `$wpDbName`"); + $wpDb = new \PDO( + "mysql:host={$wpHost};port={$wpPort};dbname={$wpDbName};charset=utf8mb4", + $wpUser, + $wpPass, + [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION] + ); - // ذخیره توکن در user meta - $stmt = $wpPdo->prepare(' - INSERT INTO wp_usermeta (user_id, meta_key, meta_value) - VALUES (1, "ifnex_laravel_token", :token) - ON DUPLICATE KEY UPDATE meta_value = :token - '); - $stmt->execute(['token' => $token]); - - // ذخیره تاریخ انقضا - $expiry = date('Y-m-d H:i:s', strtotime('+30 days')); - $stmt = $wpPdo->prepare(' - INSERT INTO wp_usermeta (user_id, meta_key, meta_value) - VALUES (1, "ifnex_laravel_token_expiry", :expiry) - ON DUPLICATE KEY UPDATE meta_value = :expiry - '); - $stmt->execute(['expiry' => $expiry]); - - $this->info("✅ توکن در دیتابیس وردپرس ذخیره شد."); - $this->info(" User ID: 1 (admin)"); - $this->info(" Token: " . substr($token, 0, 20) . "..."); - $this->info(" Expires: $expiry"); - - } catch (\Exception $e) { - $this->error("❌ خطا در ذخیره توکن در وردپرس: " . $e->getMessage()); - $this->warn("توکن تولید شد ولی در وردپرس ذخیره نشد."); - $this->warn("لطفاً دستی در دیتابیس وردپرس ذخیره کنید."); + $this->info("✅ Connected to WordPress database: {$wpDbName}"); + } catch (\PDOException $e) { + $this->error("❌ Failed to connect to WordPress database: " . $e->getMessage()); + return Command::FAILURE; } - // ۵. تست نهایی + // دریافت همه کاربران وردپرس + $stmt = $wpDb->query(" + SELECT + u.ID as id, + u.user_login as login, + u.user_email as email, + u.user_registered as registered, + u.display_name as name, + um1.meta_value as first_name, + um2.meta_value as last_name, + um3.meta_value as phone + FROM {$wpPrefix}users u + LEFT JOIN {$wpPrefix}usermeta um1 ON u.ID = um1.user_id AND um1.meta_key = 'first_name' + LEFT JOIN {$wpPrefix}usermeta um2 ON u.ID = um2.user_id AND um2.meta_key = 'last_name' + LEFT JOIN {$wpPrefix}usermeta um3 ON u.ID = um3.user_id AND um3.meta_key = 'phone' + WHERE u.user_email != '' + ORDER BY u.ID + "); + + $wpUsers = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $this->info("📊 Found " . count($wpUsers) . " WordPress users"); $this->newLine(); - $this->info("🧪 در حال تست اتصال..."); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'http://localhost:8000/api/v1/wallet/balance'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Authorization: Bearer ' . $token, - 'Accept: application/json', - ]); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $created = 0; + $updated = 0; + $skipped = 0; - $response = curl_exec($ch); - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); + foreach ($wpUsers as $wpUser) { + $email = trim($wpUser['email']); + + if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + $skipped++; + continue; + } - if ($http_code === 200) { - $data = json_decode($response, true); - $this->info("✅ اتصال موفق!"); - $this->info(" موجودی کیف پول: " . number_format($data['balance']) . " ریال"); - $this->info(" کل واریزی: " . number_format($data['total_deposited']) . " ریال"); - $this->info(" کل برداشت: " . number_format($data['total_withdrawn']) . " ریال"); - } else { - $this->error("❌ تست ناموفق: HTTP $http_code"); - $this->line(substr($response, 0, 200)); + // ساخت نام کامل + $name = trim(($wpUser['first_name'] ?? '') . ' ' . ($wpUser['last_name'] ?? '')); + if (empty($name)) { + $name = $wpUser['name'] ?? $wpUser['login']; + } + + // بررسی وجود کاربر در Laravel + $laravelUser = User::where('email', $email)->first(); + + if ($laravelUser) { + // به‌روزرسانی اطلاعات + $laravelUser->update([ + 'name' => $name, + 'phone' => $wpUser['phone'] ?? $laravelUser->phone, + ]); + $updated++; + $this->line(" ✏️ Updated: {$email}"); + } else { + // ساخت کاربر جدید + $laravelUser = User::create([ + 'name' => $name, + 'email' => $email, + 'phone' => $wpUser['phone'] ?? null, + 'password' => Hash::make('TempPass@12345'), // رمز موقت + 'email_verified_at' => now(), + 'role' => 'customer', + ]); + + // ساخت کیف پول + Wallet::create([ + 'user_id' => $laravelUser->id, + 'balance' => 0, + 'total_deposited' => 0, + 'total_withdrawn' => 0, + 'is_frozen' => false, + ]); + + // اختصاص نقش customer + $laravelUser->assignRole('customer'); + + $created++; + $this->line(" ✅ Created: {$email} (Password: TempPass@12345)"); + } } $this->newLine(); - $this->info("🎉 آماده تست در وردپرس!"); - $this->info(" آدرس: http://localhost/ifnexwp/?page_id=16"); - $this->info(" ورود: admin / admin"); + $this->info('═══════════════════════════════════════'); + $this->info(" 📊 Sync Summary"); + $this->info('═══════════════════════════════════════'); + $this->info(" ✅ Created: {$created}"); + $this->info(" ✏️ Updated: {$updated}"); + $this->info(" ⏭️ Skipped: {$skipped}"); + $this->info('═══════════════════════════════════════'); - return 0; + return Command::SUCCESS; } -} +} \ No newline at end of file diff --git a/04_Laravel/app/Enums/PaymentGateway.php b/04_Laravel/app/Enums/PaymentGateway.php index ec6151b..390cf50 100644 --- a/04_Laravel/app/Enums/PaymentGateway.php +++ b/04_Laravel/app/Enums/PaymentGateway.php @@ -4,16 +4,47 @@ namespace App\Enums; enum PaymentGateway: string { - case ZARINPAL = 'zarinpal'; // درگاه زرین‌پال - case MANUAL = 'manual'; // شارژ/کسر دستی توسط ادمین - case SYSTEM = 'system'; // تراکنش‌های سیستمی (خودکار) + case ZARINPAL = 'zarinpal'; + case WALLET = 'wallet'; + case MANUAL = 'manual'; + case SYSTEM = 'system'; + /** + * لیبل فارسی + */ public function label(): string { return match($this) { self::ZARINPAL => 'زرین‌پال', + self::WALLET => 'کیف پول', self::MANUAL => 'دستی (ادمین)', - self::SYSTEM => 'سیستمی', + self::SYSTEM => 'سیستم', + }; + } + + /** + * آیکون + */ + public function icon(): string + { + return match($this) { + self::ZARINPAL => 'heroicon-o-credit-card', + self::WALLET => 'heroicon-o-wallet', + self::MANUAL => 'heroicon-o-pencil-square', + self::SYSTEM => 'heroicon-o-cog-6-tooth', + }; + } + + /** + * رنگ badge + */ + public function color(): string + { + return match($this) { + self::ZARINPAL => 'primary', + self::WALLET => 'success', + self::MANUAL => 'warning', + self::SYSTEM => 'gray', }; } } \ No newline at end of file diff --git a/04_Laravel/app/Enums/TrackingSource.php b/04_Laravel/app/Enums/TrackingSource.php index 849b0f0..3328cc1 100644 --- a/04_Laravel/app/Enums/TrackingSource.php +++ b/04_Laravel/app/Enums/TrackingSource.php @@ -4,7 +4,31 @@ namespace App\Enums; enum TrackingSource: string { - case Manual = 'manual'; - case ApiCarrier = 'api_carrier'; - case ApiAggregator = 'api_aggregator'; -} + case MANUAL = 'manual'; // دستی توسط ادمین + case API = 'api'; // از API خارجی + case IMPORT = 'import'; // از فایل import + case SYSTEM = 'system'; // ← جدید: توسط سیستم (پرداخت خودکار) + case CUSTOMER = 'customer'; // ← جدید: توسط مشتری + + public function label(): string + { + return match($this) { + self::MANUAL => 'دستی', + self::API => 'API', + self::IMPORT => 'فایل', + self::SYSTEM => 'سیستم', + self::CUSTOMER => 'مشتری', + }; + } + + public function color(): string + { + return match($this) { + self::MANUAL => 'warning', + self::API => 'info', + self::IMPORT => 'gray', + self::SYSTEM => 'success', + self::CUSTOMER => 'primary', + }; + } +} \ No newline at end of file diff --git a/04_Laravel/app/Http/Controllers/Api/BridgeAuthController.php b/04_Laravel/app/Http/Controllers/Api/BridgeAuthController.php new file mode 100644 index 0000000..7894453 --- /dev/null +++ b/04_Laravel/app/Http/Controllers/Api/BridgeAuthController.php @@ -0,0 +1,90 @@ +validate([ + 'bridge_api_key' => ['required', 'string'], + 'wp_user_id' => ['required', 'integer'], + 'wp_user_email' => ['required', 'email'], + 'wp_user_name' => ['nullable', 'string', 'max:255'], + 'token_name' => ['nullable', 'string', 'max:100'], + ]); + + // ۱. بررسی API Key + $expectedKey = config('ifnex.bridge_api_key'); + if (empty($expectedKey) || $expectedKey === 'change-this-secret-key') { + return response()->json([ + 'success' => false, + 'message' => 'Bridge API Key تنظیم نشده است.', + ], 500); + } + + if (!hash_equals($expectedKey, $validated['bridge_api_key'])) { + return response()->json([ + 'success' => false, + 'message' => 'Bridge API Key نامعتبر است.', + ], 401); + } + + // ۲. پیدا کردن کاربر در لاراول (با ایمیل) + $user = User::where('email', $validated['wp_user_email'])->first(); + + if (!$user) { + return response()->json([ + 'success' => false, + 'message' => 'کاربر در سیستم یافت نشد. لطفاً با مدیر سیستم تماس بگیرید.', + 'debug' => 'email_not_found: ' . $validated['wp_user_email'], + ], 404); + } + + // ۳. بررسی فعال بودن کاربر + if (isset($user->is_active) && !$user->is_active) { + return response()->json([ + 'success' => false, + 'message' => 'حساب کاربری شما غیرفعال است.', + ], 403); + } + + // ۴. ذخیره wp_user_id در metadata کاربر (اختیاری، برای ردیابی) + $metadata = $user->metadata ?? []; + if (!is_array($metadata)) $metadata = []; + $metadata['wp_user_id'] = $validated['wp_user_id']; + $metadata['wp_synced_at'] = now()->toIso8601String(); + + // اگر فیلد metadata در User هست، ذخیره کن + if (in_array('metadata', \Schema::getColumnListing('users'))) { + $user->update(['metadata' => $metadata]); + } + + // ۵. ایجاد توکن Sanctum + $tokenName = $validated['token_name'] ?? 'wordpress-bridge'; + $token = $user->createToken($tokenName)->plainTextToken; + + return response()->json([ + 'success' => true, + 'token' => $token, + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'wp_user_id' => $validated['wp_user_id'], + ], + ]); + } +} \ No newline at end of file diff --git a/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php b/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php index ff7b7f8..bb79f2d 100644 --- a/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php +++ b/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php @@ -489,4 +489,108 @@ class CustomerOrderController extends Controller return $date->format('Y-m-d H:i'); } } + + /** + * پرداخت سفارش از کیف پول + * POST /api/v1/customer/orders/{shipment}/pay-wallet + */ + public function payFromWallet(Shipment $shipment): JsonResponse + { + $user = Auth::user(); + + // بررسی مالکیت + if ($shipment->user_id !== $user->id) { + return response()->json([ + 'success' => false, + 'message' => 'شما به این سفارش دسترسی ندارید.', + ], 403); + } + + // بررسی وضعیت + if ($shipment->status !== ShipmentStatus::PendingPayment) { + return response()->json([ + 'success' => false, + 'message' => 'این سفارش در وضعیت قابل پرداخت نیست.', + ], 400); + } + + $wallet = $user->wallet; + if (!$wallet) { + return response()->json([ + 'success' => false, + 'message' => 'کیف پول شما وجود ندارد.', + ], 400); + } + + $paymentService = app(\App\Services\OrderPaymentService::class); + $result = $paymentService->payFromWallet($shipment, $wallet); + + if ($result['success']) { + return response()->json([ + 'success' => true, + 'message' => $result['message'], + 'data' => $this->formatShipment($shipment->fresh()->load(['fromCountry', 'toCountry'])), + ]); + } + + return response()->json($result, 400); + } + + /** + * شروع پرداخت از درگاه + * POST /api/v1/customer/orders/{shipment}/pay-gateway + */ + public function payViaGateway(Request $request, Shipment $shipment): JsonResponse + { + $user = Auth::user(); + + // بررسی مالکیت + if ($shipment->user_id !== $user->id) { + return response()->json([ + 'success' => false, + 'message' => 'شما به این سفارش دسترسی ندارید.', + ], 403); + } + + // بررسی وضعیت + if ($shipment->status !== ShipmentStatus::PendingPayment) { + return response()->json([ + 'success' => false, + 'message' => 'این سفارش در وضعیت قابل پرداخت نیست.', + ], 400); + } + + $validated = $request->validate([ + 'frontend_callback' => ['required', 'url', 'max:500'], + ]); + + $wallet = $user->wallet; + if (!$wallet) { + return response()->json([ + 'success' => false, + 'message' => 'کیف پول شما وجود ندارد.', + ], 400); + } + + $paymentService = app(\App\Services\OrderPaymentService::class); + $result = $paymentService->initiateGatewayPayment($shipment, $wallet); + + if ($result['success']) { + // ذخیره callback URL در metadata تراکنش + $transaction = WalletTransaction::find($result['transaction_id']); + if ($transaction) { + $metadata = $transaction->metadata ?? []; + $metadata['frontend_callback'] = $validated['frontend_callback']; + $transaction->update(['metadata' => $metadata]); + } + + return response()->json([ + 'success' => true, + 'payment_url' => $result['payment_url'], + 'transaction_id' => $result['transaction_id'], + ]); + } + + return response()->json($result, 400); + } } \ No newline at end of file diff --git a/04_Laravel/app/Http/Controllers/Api/PaymentController.php b/04_Laravel/app/Http/Controllers/Api/PaymentController.php index fe8bea7..f8b1468 100644 --- a/04_Laravel/app/Http/Controllers/Api/PaymentController.php +++ b/04_Laravel/app/Http/Controllers/Api/PaymentController.php @@ -213,6 +213,8 @@ class PaymentController extends Controller gatewayReferenceId: $verification['ref_id'] ?? $authority ); + // ─── NEW: اگر تراکنش مربوط به سفارش است، status سفارش را تغییر بده ─ + Log::info('Payment completed successfully', [ 'transaction_id' => $transaction->id, 'amount' => $transaction->amount, diff --git a/04_Laravel/app/Services/OrderPaymentService.php b/04_Laravel/app/Services/OrderPaymentService.php new file mode 100644 index 0000000..c4c12d7 --- /dev/null +++ b/04_Laravel/app/Services/OrderPaymentService.php @@ -0,0 +1,224 @@ +hasSufficientBalance($shipment->total_fee)) { + return [ + 'success' => false, + 'message' => 'موجودی کیف پول کافی نیست.', + 'required' => $shipment->total_fee, + 'available' => $wallet->balance, + ]; + } + + try { + return DB::transaction(function () use ($shipment, $wallet) { + // ۱. ثبت تراکنش کسر از کیف پول + $transaction = WalletTransaction::create([ + 'wallet_id' => $wallet->id, + 'amount' => -$shipment->total_fee, + 'balance_before' => $wallet->balance, + 'balance_after' => $wallet->balance - $shipment->total_fee, + 'type' => TransactionType::ORDER_PAYMENT, + 'status' => TransactionStatus::COMPLETED, + 'gateway' => PaymentGateway::WALLET, + 'description' => "پرداخت سفارش {$shipment->awb_no}", + 'transactionable_type' => Shipment::class, + 'transactionable_id' => $shipment->id, + 'ip_address' => request()->ip(), + 'user_agent' => request()->userAgent(), + ]); + + // ۲. به‌روزرسانی موجودی کیف پول + $wallet->update([ + 'balance' => $wallet->balance - $shipment->total_fee, + 'total_withdrawn' => $wallet->total_withdrawn + $shipment->total_fee, + ]); + + // ۳. تغییر status سفارش به processed + $shipment->update(['status' => ShipmentStatus::Processed]); + + // ۴. ثبت tracking event (با همه فیلدهای احتمالی) + $this->createTrackingEvent($shipment, 'پرداخت تکمیل شد (از کیف پول)'); + + Log::info('Order paid from wallet', [ + 'shipment_id' => $shipment->id, + 'awb_no' => $shipment->awb_no, + 'amount' => $shipment->total_fee, + 'wallet_id' => $wallet->id, + ]); + + return [ + 'success' => true, + 'message' => 'پرداخت با موفقیت انجام شد.', + 'transaction_id' => $transaction->id, + 'new_status' => 'processed', + ]; + }); + } catch (\Exception $e) { + Log::error('Wallet payment failed', [ + 'shipment_id' => $shipment->id, + 'error' => $e->getMessage(), + ]); + + return [ + 'success' => false, + 'message' => 'خطا در پرداخت: ' . $e->getMessage(), + ]; + } + } + + /** + * شروع پرداخت از درگاه + */ + public function initiateGatewayPayment(Shipment $shipment, Wallet $wallet): array + { + try { + // ۱. ایجاد تراکنش pending + $transaction = WalletTransaction::create([ + 'wallet_id' => $wallet->id, + 'amount' => $shipment->total_fee, + 'balance_before' => $wallet->balance, + 'balance_after' => $wallet->balance, + 'type' => TransactionType::ORDER_PAYMENT, + 'status' => TransactionStatus::PENDING, + 'gateway' => PaymentGateway::ZARINPAL, + 'description' => "پرداخت سفارش {$shipment->awb_no}", + 'transactionable_type' => Shipment::class, + 'transactionable_id' => $shipment->id, + 'ip_address' => request()->ip(), + 'user_agent' => request()->userAgent(), + 'metadata' => [ + 'shipment_id' => $shipment->id, + 'awb_no' => $shipment->awb_no, + 'is_order_payment' => true, + ], + ]); + + // ۲. درخواست به درگاه + $gatewayService = app()->make(\App\Services\ZarinpalService::class); + + if (config('ifnex.zarinpal.merchant_id') === 'fake-merchant-id-for-testing' + || config('ifnex.zarinpal.sandbox', true)) { + $gatewayService = app()->make(\App\Services\MockZarinpalService::class); + } + + $paymentData = $gatewayService->requestPayment( + amount: $shipment->total_fee, + description: "پرداخت سفارش {$shipment->awb_no}", + mobile: $shipment->user->phone, + email: $shipment->user->email + ); + + // ۳. ذخیره authority در metadata + $metadata = $transaction->metadata ?? []; + $metadata['zarinpal_authority'] = $paymentData['authority']; + $transaction->update(['metadata' => $metadata]); + + return [ + 'success' => true, + 'payment_url' => $paymentData['payment_url'], + 'transaction_id' => $transaction->id, + 'authority' => $paymentData['authority'], + ]; + + } catch (\Exception $e) { + Log::error('Gateway payment initiation failed', [ + 'shipment_id' => $shipment->id, + 'error' => $e->getMessage(), + ]); + + return [ + 'success' => false, + 'message' => 'خطا در اتصال به درگاه: ' . $e->getMessage(), + ]; + } + } + + /** + * تکمیل پرداخت بعد از callback موفق + */ + public function completeGatewayPayment(WalletTransaction $transaction): bool + { + try { + return DB::transaction(function () use ($transaction) { + $shipment = Shipment::find($transaction->transactionable_id); + + if (!$shipment || $shipment->status !== ShipmentStatus::PendingPayment) { + return false; + } + + // ۱. به‌روزرسانی تراکنش + $transaction->update([ + 'status' => TransactionStatus::COMPLETED, + 'balance_after' => $transaction->wallet->balance, + ]); + + // ۲. تغییر status سفارش + $shipment->update(['status' => ShipmentStatus::Processed]); + + // ۳. ثبت tracking event + $this->createTrackingEvent($shipment, 'پرداخت تکمیل شد (از درگاه بانکی)'); + + Log::info('Order paid via gateway', [ + 'shipment_id' => $shipment->id, + 'awb_no' => $shipment->awb_no, + 'transaction_id' => $transaction->id, + ]); + + return true; + }); + } catch (\Exception $e) { + Log::error('Complete gateway payment failed', [ + 'transaction_id' => $transaction->id, + 'error' => $e->getMessage(), + ]); + + return false; + } + } + + /** + * ساخت Tracking Event با ساختار دقیق جدول + */ + private function createTrackingEvent(Shipment $shipment, string $description): void + { + try { + ShipmentTrackingEvent::create([ + 'shipment_id' => $shipment->id, + 'event_date' => now()->toDateString(), // YYYY-MM-DD + 'event_time' => now()->format('H:i:s'), // HH:MM:SS ← فیلد جدید + 'event_description' => $description, + 'location' => 'سیستم', + 'delivery_status' => 'processed', // ← اصلاح شد (status وجود نداشت) + 'source' => 'manual', // مقدار معتبر enum TrackingSource + ]); + + } catch (\Exception $e) { + // اگر tracking event ثبت نشد، فقط لاگ کن (تراکنش را خراب نکن) + Log::warning('Failed to create tracking event', [ + 'shipment_id' => $shipment->id, + 'error' => $e->getMessage(), + ]); + } + } +} \ No newline at end of file diff --git a/04_Laravel/config/ifnex.php b/04_Laravel/config/ifnex.php index e56cce8..39a55ff 100644 --- a/04_Laravel/config/ifnex.php +++ b/04_Laravel/config/ifnex.php @@ -2,6 +2,8 @@ return [ + 'bridge_api_key' => env('IFNEX_BRIDGE_API_KEY', 'change-this-secret-key'), // This is the API key that you get from the IFNEX Bridge + /* |-------------------------------------------------------------------------- | IFNEX API Settings diff --git a/04_Laravel/database/migrations/2026_08_10_080853_add_wallet_to_payment_gateway_enum.php b/04_Laravel/database/migrations/2026_08_10_080853_add_wallet_to_payment_gateway_enum.php new file mode 100644 index 0000000..6b0c2d0 --- /dev/null +++ b/04_Laravel/database/migrations/2026_08_10_080853_add_wallet_to_payment_gateway_enum.php @@ -0,0 +1,38 @@ +where('gateway', 'wallet') + ->update(['gateway' => 'system']); + + DB::statement(" + ALTER TABLE `wallet_transactions` + MODIFY COLUMN `gateway` ENUM( + 'zarinpal', + 'manual', + 'system' + ) NULL DEFAULT NULL + "); + } +}; \ No newline at end of file diff --git a/04_Laravel/database/migrations/2026_08_10_082121_add_system_to_tracking_source_enum.php b/04_Laravel/database/migrations/2026_08_10_082121_add_system_to_tracking_source_enum.php new file mode 100644 index 0000000..744c2e2 --- /dev/null +++ b/04_Laravel/database/migrations/2026_08_10_082121_add_system_to_tracking_source_enum.php @@ -0,0 +1,38 @@ +whereIn('source', ['system', 'customer']) + ->update(['source' => 'manual']); + + DB::statement(" + ALTER TABLE `shipment_tracking_events` + MODIFY COLUMN `source` ENUM( + 'manual', + 'api', + 'import' + ) NOT NULL DEFAULT 'manual' + "); + } +}; \ No newline at end of file diff --git a/04_Laravel/routes/api.php b/04_Laravel/routes/api.php index 02ced1a..6b4ca97 100644 --- a/04_Laravel/routes/api.php +++ b/04_Laravel/routes/api.php @@ -10,6 +10,13 @@ use App\Http\Controllers\Api\WalletController; use App\Http\Middleware\ApiKeyMiddleware; use Illuminate\Support\Facades\Route; use App\Http\Controllers\Api\AuthController; +use App\Http\Controllers\Api\BridgeAuthController; + + +// ══════════════════════════════════════════════════════════════ +// Bridge Auth API (فقط برای پلاگین وردپرس - با API Key محافظت می‌شود) +// ══════════════════════════════════════════════════════════════ +Route::post('/v1/bridge/login', [BridgeAuthController::class, 'login']); // Login // ══════════════════════════════════════════════════════════════ // API عمومی با API Key @@ -57,6 +64,11 @@ Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () { Route::post('/orders', [CustomerOrderController::class, 'store']); Route::get('/orders/{shipment}', [CustomerOrderController::class, 'show']); Route::post('/orders/{shipment}/cancel', [CustomerOrderController::class, 'cancel']); + + + // پرداخت سفارش + Route::post('/orders/{shipment}/pay-wallet', [CustomerOrderController::class, 'payFromWallet']); + Route::post('/orders/{shipment}/pay-gateway', [CustomerOrderController::class, 'payViaGateway']); }); }); diff --git a/04_Laravel/test-api.php b/04_Laravel/test-api.php deleted file mode 100644 index 5199d11..0000000 --- a/04_Laravel/test-api.php +++ /dev/null @@ -1,136 +0,0 @@ -make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); - -use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Hash; -use App\Models\User; - -echo "══════════════════════════════════════════\n"; -echo " 🧪 IFNEX API Test Suite\n"; -echo "══════════════════════════════════════════\n\n"; - -// ─── 0. تنظیم رمز عبور برای تست ───────────────────────────── -$testEmail = 'kazem@vernasoft.group'; -$testPassword = 'Test@12345'; - -$user = User::where('email', $testEmail)->first(); -if (!$user) { - echo "❌ کاربر {$testEmail} پیدا نشد!\n"; - exit(1); -} - -$user->password = Hash::make($testPassword); -$user->save(); -echo "✅ رمز کاربر {$user->name} به '{$testPassword}' تغییر یافت\n\n"; - -// ─── 1. تست Login ────────────────────────────────────────── -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; -echo "تست ۱: دریافت توکن (Login)\n"; -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - -$response = Http::post('http://localhost:8000/api/v1/auth/login', [ - 'email' => $testEmail, - 'password' => $testPassword, - 'token_name' => 'test-api', -]); - -echo "Status: " . $response->status() . "\n"; -$data = $response->json(); -print_r($data); - -if ($response->status() !== 200 || !isset($data['token'])) { - echo "\n❌ Login ناموفق بود! بقیه تست‌ها را نمی‌توان انجام داد.\n"; - exit(1); -} - -$token = $data['token']; -echo "\n✅ Login موفق! توکن دریافت شد.\n\n"; - -// ─── 2. تست Profile ──────────────────────────────────────── -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; -echo "تست ۲: دریافت پروفایل\n"; -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - -$response2 = Http::withToken($token) - ->get('http://localhost:8000/api/v1/customer/profile'); - -echo "Status: " . $response2->status() . "\n"; -print_r($response2->json()); -echo "\n"; - -// ─── 3. تست Countries ────────────────────────────────────── -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; -echo "تست ۳: لیست کشورها\n"; -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - -$response3 = Http::withToken($token) - ->get('http://localhost:8000/api/v1/customer/countries'); - -echo "Status: " . $response3->status() . "\n"; -$countries = $response3->json(); -echo "تعداد کشورها: " . (isset($countries['data']) ? count($countries['data']) : 0) . "\n"; -if (isset($countries['data']) && count($countries['data']) > 0) { - echo "اولین کشور: " . print_r($countries['data'][0], true) . "\n"; -} -echo "\n"; - -// ─── 4. تست ساخت سفارش ──────────────────────────────────── -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; -echo "تست ۴: ثبت سفارش جدید\n"; -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - -$orderData = [ - 'direction' => 'export', - 'type' => 'PARCEL', - 'from_country_id' => 1, - 'to_country_id' => 2, - 'weight' => 2.5, - 'volumetric_weight' => 3, - 'sender_name' => 'Kazem Test', - 'sender_phone' => '09123456789', - 'sender_address' => 'Tehran, Valiasr St.', - 'sender_city' => 'Tehran', - 'receiver_name' => 'Ali Customer', - 'receiver_phone' => '+971501234567', - 'receiver_address' => 'Dubai, Marina', - 'receiver_city' => 'Dubai', -]; - -$response4 = Http::withToken($token) - ->post('http://localhost:8000/api/v1/customer/orders', $orderData); - -echo "Status: " . $response4->status() . "\n"; -$orderResult = $response4->json(); -print_r($orderResult); -echo "\n"; - -// ─── 5. تست لیست سفارشات ─────────────────────────────────── -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; -echo "تست ۵: لیست سفارشات کاربر\n"; -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; - -$response5 = Http::withToken($token) - ->get('http://localhost:8000/api/v1/customer/orders'); - -echo "Status: " . $response5->status() . "\n"; -$orders = $response5->json(); -echo "تعداد سفارشات: " . (isset($orders['data']) ? count($orders['data']) : 0) . "\n"; -if (isset($orders['data']) && count($orders['data']) > 0) { - echo "اولین سفارش: AWB = " . ($orders['data'][0]['awb_no'] ?? '—') . "\n"; -} -echo "\n"; - -// ─── جمع‌بندی ─────────────────────────────────────────────── -echo "══════════════════════════════════════════\n"; -echo " 🎯 نتیجه نهایی\n"; -echo "══════════════════════════════════════════\n"; -echo "Login: " . ($response->status() === 200 ? "✅" : "❌") . "\n"; -echo "Profile: " . ($response2->status() === 200 ? "✅" : "❌") . "\n"; -echo "Countries: " . ($response3->status() === 200 ? "✅" : "❌") . "\n"; -echo "Create Order:" . ($response4->status() === 201 ? "✅" : "❌") . "\n"; -echo "List Orders: " . ($response5->status() === 200 ? "✅" : "❌") . "\n"; -echo "══════════════════════════════════════════\n"; \ No newline at end of file