where('code', 'main') ->first(); if (! $gateway) { return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'Payment gateway settings were not found.', ], 404) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } if (! (bool) ($gateway->is_paymongo_enabled ?? false)) { return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'PayMongo is disabled.', ], 503) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } $storedSecretKey = trim((string) ($gateway->paymongo_secret_key ?? '')); if ($storedSecretKey === '') { return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'PayMongo secret key is not configured.', ], 500) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } /* * Your database stores the key using Laravel encryption. This also * permits a plain-text key as a fallback for older installations. */ try { $secretKey = Crypt::decryptString($storedSecretKey); } catch (\Throwable $exception) { $secretKey = $storedSecretKey; } $secretKey = trim((string) $secretKey); if (! str_starts_with($secretKey, 'sk_')) { return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'The configured PayMongo secret key is invalid.', ], 500) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } /* * PayMongo requires each requested field as a separate `fields` * query parameter. Writing the URL directly preserves duplicates. */ $walletListUrl = 'https://api.paymongo.com/v2/wallets' .'?status=activated' .'&fields=balance' .'&fields=account' .'&fields=limits'; $response = Http::withBasicAuth($secretKey, '') ->acceptJson() ->connectTimeout(8) ->timeout(20) ->retry(2, 500, throw: false) ->get($walletListUrl); if (! $response->successful()) { $paymongoMessage = data_get( $response->json(), 'errors.0.detail', data_get( $response->json(), 'errors.0.code', 'PayMongo returned HTTP '.$response->status().'.' ) ); return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => (string) $paymongoMessage, 'paymongo_status' => $response->status(), ], 502) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } $json = $response->json(); $data = $json['data'] ?? null; $wallet = null; /* * Supports both a single wallet object and a wallet collection. */ if (is_array($data) && isset($data['id'])) { $wallet = $data; } elseif (is_array($data)) { foreach ($data as $item) { if (! is_array($item)) { continue; } $status = strtolower((string) ( data_get($item, 'status') ?? data_get($item, 'attributes.status') ?? '' )); if ($status === 'activated') { $wallet = $item; break; } if ($wallet === null) { $wallet = $item; } } } if (! is_array($wallet)) { return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'No PayMongo wallet was returned.', ], 404) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } /* * Supports PayMongo's older attributes.available_balance response and * the newer balance.available wallet response. */ $balanceInCentavos = data_get($wallet, 'balance.available') ?? data_get($wallet, 'attributes.balance.available') ?? data_get($wallet, 'attributes.available_balance') ?? data_get($wallet, 'attributes.balance.available_balance') ?? data_get($wallet, 'attributes.balances.available_balance.amount') ?? data_get($wallet, 'balances.available_balance.amount'); $pendingInCentavos = data_get($wallet, 'balance.pending') ?? data_get($wallet, 'attributes.balance.pending') ?? data_get($wallet, 'attributes.pending_balance') ?? data_get($wallet, 'attributes.balances.pending_balance.amount') ?? data_get($wallet, 'balances.pending_balance.amount') ?? 0; if (! is_numeric($balanceInCentavos)) { return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'Wallet found, but PayMongo did not return the requested balance field.', 'wallet_id' => (string) ($wallet['id'] ?? ''), 'returned_fields' => array_keys($wallet), 'hint' => 'Verify that Wallet API access is enabled for this PayMongo account.', ], 502) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } $availableCentavos = (int) $balanceInCentavos; $pendingCentavos = is_numeric($pendingInCentavos) ? (int) $pendingInCentavos : 0; $availableBalance = round($availableCentavos / 100, 2); $pendingBalance = round($pendingCentavos / 100, 2); $currency = (string) ( data_get($wallet, 'account.currency') ?? data_get($wallet, 'attributes.account.currency') ?? data_get($wallet, 'attributes.balances.available_balance.currency') ?? data_get($wallet, 'balances.available_balance.currency') ?? 'PHP' ); $accountNumber = (string) ( data_get($wallet, 'account.account_number') ?? data_get($wallet, 'attributes.account.account_number') ?? data_get($wallet, 'attributes.account_number') ?? '' ); $accountName = (string) ( data_get($wallet, 'account.account_name') ?? data_get($wallet, 'attributes.account.account_name') ?? '' ); $walletStatus = (string) ( data_get($wallet, 'status') ?? data_get($wallet, 'attributes.status') ?? 'activated' ); $livemode = (bool) ( data_get($wallet, 'livemode') ?? data_get($wallet, 'attributes.livemode') ?? str_starts_with($secretKey, 'sk_live_') ); return response() ->json([ 'ok' => true, 'provider' => 'paymongo', 'currency' => $currency, 'balance' => $availableBalance, 'available_balance' => $availableBalance, 'pending_balance' => $pendingBalance, 'available_balance_in_centavos' => $availableCentavos, 'pending_balance_in_centavos' => $pendingCentavos, 'formatted_balance' => '₱'.number_format($availableBalance, 2), 'formatted_available_balance' => '₱'.number_format($availableBalance, 2), 'formatted_pending_balance' => '₱'.number_format($pendingBalance, 2), 'wallet' => [ 'id' => (string) ($wallet['id'] ?? ''), 'status' => $walletStatus, 'livemode' => $livemode, 'account_name' => $accountName, 'account_number' => $accountNumber, ], 'retrieved_at' => now()->toIso8601String(), ]) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->header('Pragma', 'no-cache'); } catch (\Throwable $exception) { report($exception); return response() ->json([ 'ok' => false, 'provider' => 'paymongo', 'message' => 'Unable to retrieve the PayMongo balance.', 'error' => config('app.debug') ? $exception->getMessage() : null, ], 502) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); } }) ->middleware('throttle:30,1') ->name('api.paymongo.balance'); Route::get('/', function () { return redirect()->route('login'); }); Route::middleware(['auth'])->get( '/declarator/sync/events/{eventSlug}/stream', [SabongSyncController::class, 'stream'] )->name('declarator.sync.stream'); Route::middleware(['auth'])->group(function () { Route::get('/wallet/paymongo/reconcile', [PaymongoPaymentController::class, 'reconcile']) ->name('wallet.paymongo.reconcile'); Route::get('/wallet/paymongo/{walletTransaction}', [PaymongoPaymentController::class, 'show']) ->name('wallet.paymongo.show'); Route::get('/wallet/paymongo/{walletTransaction}/status', [PaymongoPaymentController::class, 'status']) ->name('wallet.paymongo.status'); Route::get('/wallet/paymongo/{walletTransaction}/success', [PaymongoPaymentController::class, 'success']) ->name('wallet.paymongo.success'); }); Route::get('/wallet-requests/pending-count', function () { $user = auth()->user(); $role = strtolower((string) ($user?->role ?? '')); if (! in_array($role, ['admin', 'loader'], true)) { abort(403); } $query = WalletTransaction::query() ->where('status', 'pending'); if ($role === 'loader') { $query->whereIn('type', ['cashin', 'cashout']); } else { $query->whereIn('type', ['cashin', 'cashout', 'loader_transfer']); } return response() ->json([ 'count' => (int) $query->count(), ]) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); })->name('wallet-requests.pending-count'); Route::middleware('guest')->group(function () { Route::get('/login', [BettingAuthController::class, 'showLogin']) ->name('login'); Route::post('/login', [BettingAuthController::class, 'login']) ->name('login.submit'); Route::get('/register', [BettingAuthController::class, 'showRegister']) ->name('register'); Route::post('/register', [BettingAuthController::class, 'register']) ->name('register.submit'); Route::get('/forgot-password', [BettingAuthController::class, 'showForgotPassword']) ->name('password.request'); Route::post('/forgot-password', [BettingAuthController::class, 'submitForgotPassword']) ->name('password.request.submit'); Route::get('/reset-password', [BettingAuthController::class, 'showResetPassword']) ->name('password.reset.form'); Route::post('/reset-password', [BettingAuthController::class, 'resetPassword']) ->name('password.reset.submit'); }); Route::post('/logout', [BettingAuthController::class, 'logout']) ->middleware('auth') ->name('logout'); Route::middleware(['auth', 'user.activity'])->group(function () { Route::get('/dashboard', [BettingDashboardController::class, 'redirect']) ->name('dashboard.redirect'); /* |-------------------------------------------------------------------------- | User Wallet Requests |-------------------------------------------------------------------------- */ Route::get('/wallet/requests', [WalletRequestController::class, 'index']) ->name('wallet.requests.index'); Route::post('/wallet/requests/cashin', [WalletRequestController::class, 'cashin']) ->name('wallet.requests.cashin'); Route::post('/wallet/requests/cashout', [WalletRequestController::class, 'cashout']) ->name('wallet.requests.cashout'); Route::post('/wallet/requests/loader-transfer', [WalletRequestController::class, 'loaderTransfer']) ->name('wallet.requests.loader-transfer'); /* |-------------------------------------------------------------------------- | Admin |-------------------------------------------------------------------------- */ Route::get('/admin/dashboard', [AdminDashboardController::class, 'index']) ->name('admin.dashboard'); Route::get('/admin/users', [AdminUserController::class, 'index']) ->middleware('role:admin') ->name('admin.users.index'); Route::patch('/admin/users/{user}/toggle-status', [AdminUserController::class, 'toggleStatus']) ->middleware('role:admin') ->name('admin.users.toggle-status'); Route::patch('/admin/users/{user}/change-password', [AdminUserController::class, 'changePassword']) ->middleware('role:admin') ->name('admin.users.change-password'); Route::get('/admin/users/{user}/upline', [AdminUserController::class, 'upline']) ->middleware('role:admin') ->name('admin.users.upline'); Route::get('/admin/users/{user}/bet-summary', [AdminUserController::class, 'betSummary']) ->middleware('role:admin') ->name('admin.users.bet-summary'); Route::get('/admin/users/{user}/agent-summary', [AdminUserController::class, 'agentSummary']) ->middleware('role:admin') ->name('admin.users.agent-summary'); Route::get('/admin/users/{user}/agent-downlines', [AdminUserController::class, 'agentDownlines']) ->middleware('role:admin') ->name('admin.users.agent-downlines'); Route::get('/admin/users/{user}/agent-players', [AdminUserController::class, 'agentPlayers']) ->middleware('role:admin') ->name('admin.users.agent-players'); Route::get('/admin/users/{user}/load-wallet', [AdminUserController::class, 'loadWallet']) ->middleware('role:admin') ->name('admin.users.load-wallet'); Route::post('/admin/users/{user}/load-wallet', [AdminUserController::class, 'storeLoadWallet']) ->middleware('role:admin') ->name('admin.users.load-wallet.store'); Route::get('/admin/users/{user}/remove-load', [AdminUserController::class, 'removeLoad']) ->middleware('role:admin') ->name('admin.users.remove-load'); Route::post('/admin/users/{user}/remove-load', [AdminUserController::class, 'storeRemoveLoad']) ->middleware('role:admin') ->name('admin.users.remove-load.store'); Route::get('/admin/users/{user}/withdraw-load', [AdminUserController::class, 'withdrawLoad']) ->middleware('role:admin') ->name('admin.users.withdraw-load'); Route::post('/admin/users/{user}/withdraw-load', [AdminUserController::class, 'storeWithdrawLoad']) ->middleware('role:admin') ->name('admin.users.withdraw-load.store'); Route::get('/admin/users/{user}/withdraw-commission', [AdminUserController::class, 'withdrawCommission']) ->middleware('role:admin') ->name('admin.users.withdraw-commission'); Route::post('/admin/users/{user}/withdraw-commission', [AdminUserController::class, 'storeWithdrawCommission']) ->middleware('role:admin') ->name('admin.users.withdraw-commission.store'); Route::get('/admin/users/{user}/set-commission', [AdminUserController::class, 'setCommission']) ->middleware('role:admin') ->name('admin.users.set-commission'); Route::patch('/admin/users/{user}/set-commission', [AdminUserController::class, 'updateCommission']) ->middleware('role:admin') ->name('admin.users.set-commission.update'); /* |-------------------------------------------------------------------------- | Admin Portal |-------------------------------------------------------------------------- */ Route::prefix('portal') ->middleware('role:admin') ->name('admin.') ->group(function () { Route::get('/wallet-requests', [AdminWalletRequestController::class, 'index']) ->name('wallet-requests.index'); Route::patch('/wallet-requests/{walletTransaction}/approve', [AdminWalletRequestController::class, 'approve']) ->name('wallet-requests.approve'); Route::patch('/wallet-requests/{walletTransaction}/reject', [AdminWalletRequestController::class, 'reject']) ->name('wallet-requests.reject'); Route::get('/wallet-history', [WalletHistoryController::class, 'index']) ->name('wallet-history.index'); Route::get('/commissions', [CommissionController::class, 'index']) ->name('commissions.index'); Route::get('/payment-methods', [PaymentMethodController::class, 'index']) ->name('payment-methods.index'); Route::patch('/payment-methods/gateway', [PaymentMethodController::class, 'updateGateway']) ->name('payment-methods.gateway.update'); Route::post('/payment-methods/manual', [PaymentMethodController::class, 'storeManual']) ->name('payment-methods.manual.store'); Route::patch('/payment-methods/manual/{paymentMethod}', [PaymentMethodController::class, 'updateManual']) ->name('payment-methods.manual.update'); Route::patch('/payment-methods/manual/{paymentMethod}/toggle', [PaymentMethodController::class, 'toggleManual']) ->name('payment-methods.manual.toggle'); Route::delete('/payment-methods/manual/{paymentMethod}', [PaymentMethodController::class, 'destroyManual']) ->name('payment-methods.manual.destroy'); Route::get('/sabong-events', [SabongEventController::class, 'index']) ->name('sabong.index'); Route::post('/sabong-events', [SabongEventController::class, 'store']) ->name('sabong.store'); Route::get('/sabong-events/{event}', [SabongEventController::class, 'show']) ->name('sabong.show'); Route::put('/sabong-events/{event}', [SabongEventController::class, 'update']) ->name('sabong.update'); Route::delete('/sabong-events/{event}', [SabongEventController::class, 'destroy']) ->name('sabong.destroy'); Route::patch('/sabong-events/{event}/visibility', [SabongEventController::class, 'toggleVisibility']) ->name('sabong.visibility'); Route::post('/sabong-events/{event}/sessions', [SabongEventController::class, 'storeSession']) ->name('sabong.sessions.store'); Route::post('/sabong-events/{event}/fights', [SabongEventController::class, 'storeFight']) ->name('sabong.fights.store'); Route::patch('/sabong-events/{event}/fights/{fight}/activate', [SabongEventController::class, 'activateFight']) ->name('sabong.fights.activate'); Route::patch('/sabong-events/{event}/fights/{fight}/close', [SabongEventController::class, 'closeFight']) ->name('sabong.fights.close'); Route::patch('/sabong-events/{event}/fights/{fight}/result', [SabongEventController::class, 'postResult']) ->name('sabong.fights.result'); Route::get('/settings', [AdminSettingsController::class, 'index']) ->name('settings.index'); Route::post('/settings/profile', [AdminSettingsController::class, 'updateProfile']) ->name('settings.profile'); Route::post('/settings/security', [AdminSettingsController::class, 'updateSecurity']) ->name('settings.security'); Route::post('/settings/assets', [AdminSettingsController::class, 'updateAssets']) ->name('settings.assets'); }); /* |-------------------------------------------------------------------------- | Loader Portal |-------------------------------------------------------------------------- */ Route::prefix('loader') ->middleware('role:loader') ->name('loader.') ->group(function () { Route::get('/', [LoaderPortalController::class, 'dashboard']) ->name('dashboard'); Route::get('/dashboard', function () { return redirect()->route('loader.dashboard'); })->name('dashboard.redirect'); Route::get('/wallet-requests', [LoaderPortalController::class, 'walletRequests']) ->name('wallet-requests.index'); Route::patch('/wallet-requests/{walletTransaction}/approve', [LoaderPortalController::class, 'approveWalletRequest']) ->name('wallet-requests.approve'); Route::patch('/wallet-requests/{walletTransaction}/reject', [LoaderPortalController::class, 'rejectWalletRequest']) ->name('wallet-requests.reject'); Route::get('/transactions', [LoaderPortalController::class, 'transactions']) ->name('transactions.index'); Route::get('/users', [LoaderPortalController::class, 'users']) ->name('users.index'); Route::get('/users/{user}/bet-summary', [LoaderPortalController::class, 'playerBetSummary']) ->name('users.bet-summary'); Route::get('/users/{user}/agent-summary', [LoaderPortalController::class, 'agentCommissionSummary']) ->name('users.agent-summary'); Route::post('/loader/users/{user}/wallet', [LoaderPortalController::class, 'wallet']) ->name('users.wallet'); Route::get('/settings', [LoaderSettingsController::class, 'index']) ->name('settings.index'); Route::post('/settings/profile', [LoaderSettingsController::class, 'updateProfile']) ->name('settings.profile'); Route::post('/settings/security', [LoaderSettingsController::class, 'updateSecurity']) ->name('settings.security'); }); /* |-------------------------------------------------------------------------- | Player Arena |-------------------------------------------------------------------------- */ Route::prefix('player') ->middleware('role:player') ->name('player.') ->group(function () { Route::get('/', function () { return redirect()->route('player.sabong.index'); })->name('home'); Route::get('/dashboard', function () { return redirect()->route('player.sabong.index'); })->name('dashboard'); Route::get('/sabong', [PlayerSabongController::class, 'index']) ->name('sabong.index'); Route::get('/sabong/{event}', [PlayerSabongController::class, 'show']) ->name('sabong.show'); Route::get('/sabong/{event}/stream', [PlayerSabongController::class, 'stream']) ->name('sabong.stream'); Route::get('/sabong/{event}/state', [PlayerSabongController::class, 'state']) ->name('sabong.state'); Route::post('/sabong/{event}/fights/{fight}/bet', [PlayerSabongController::class, 'storeBet']) ->name('sabong.bet.store'); Route::post('/sabong/{event}/presence', [PlayerSabongController::class, 'presence']) ->name('sabong.presence'); Route::get('/transactions', [PlayerTransactionController::class, 'index']) ->name('transactions.index'); Route::get('/settings', [PlayerSettingsController::class, 'edit']) ->name('account'); Route::patch('/settings/password', [PlayerSettingsController::class, 'updatePassword']) ->name('account.password.update'); Route::get('/bets', [PlayerBetController::class, 'index']) ->name('bets.index'); Route::post( '/bets/{bet}/security-demo', [PlayerSabongController::class, 'securityDemo'] )->name('bets.security-demo'); Route::patch('/sabong/{event}/fights/{fight}/bets/{bet}/c-side',[PlayerSabongController::class, 'cBSide'])->name('sabong.bets.change-side'); Route::get('/wallet/balance', function () { $user = auth()->user()?->fresh(); $balance = (float) ($user?->wallet_balance ?? 0); return response() ->json([ 'balance' => $balance, 'wallet_balance' => $balance, 'formatted_balance' => '₱'.number_format($balance, 2), ]) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); })->name('wallet.balance'); }); /* |-------------------------------------------------------------------------- | Other Dashboards |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | Declarator Dashboards |-------------------------------------------------------------------------- */ Route::middleware(['auth'])->prefix('declarator')->name('declarator.')->group(function () { Route::get('/dashboard', [DeclaratorSabongController::class, 'dashboard']) ->name('dashboard'); Route::get('/sabong/{event}/state', [DeclaratorSabongController::class, 'state']) ->name('sabong.state'); Route::get('/sabong/{event}/stream', [DeclaratorSabongController::class, 'stream']) ->name('sabong.stream'); Route::patch('/sabong/{event}/ghost', [DeclaratorSabongController::class, 'toggleGhost']) ->name('sabong.ghost.toggle'); Route::patch('/sabong/{event}/fights/{fight}/open', [DeclaratorSabongController::class, 'openFight']) ->name('sabong.fights.open'); Route::patch('/sabong/{event}/fights/{fight}/last-call', [DeclaratorSabongController::class, 'lastCallFight']) ->name('sabong.fights.last-call'); Route::patch('/sabong/{event}/fights/{fight}/close', [DeclaratorSabongController::class, 'closeFight']) ->name('sabong.fights.close'); Route::patch('/sabong/{event}/fights/{fight}/result', [DeclaratorSabongController::class, 'postResult']) ->name('sabong.fights.result'); Route::post('/sabong/{event}/fights/{fight}/manual-bet', [DeclaratorSabongController::class, 'manualBet']) ->name('sabong.fights.manual-bet'); Route::post('/sabong/{event}/fights/next', [DeclaratorSabongController::class, 'nextFight']) ->name('sabong.fights.next'); Route::patch('/sabong/events/{event}/fights/{fight}/number', [DeclaratorSabongController::class, 'updateFightNumber']) ->name('sabong.fights.number'); }); Route::prefix('agent') ->middleware('role:agent') ->name('agent.') ->group(function () { Route::get('/dashboard', [AgentPortalController::class, 'dashboard']) ->name('dashboard'); Route::get('/wallet/balance', [AgentPortalController::class, 'walletBalance']) ->name('wallet.balance'); Route::post('/load-target', [AgentPortalController::class, 'loadTarget']) ->name('load-target'); Route::post('/commission/convert', [AgentPortalController::class, 'convertCommission']) ->name('commission.convert'); Route::get('/players', [AgentPortalController::class, 'players']) ->name('players.index'); Route::post('/players/{player}/transfer-load', [AgentPortalController::class, 'transferLoad']) ->name('players.transfer-load'); Route::post('/players/{player}/withdraw-load', [AgentPortalController::class, 'withdrawLoad']) ->name('players.withdraw-load'); Route::get('/settings', [AgentPortalController::class, 'settings']) ->name('settings.index'); Route::patch('/settings/password', [AgentPortalController::class, 'updatePassword']) ->name('settings.password.update'); Route::post('/withdraw-target', [AgentPortalController::class, 'withdrawTarget']) ->name('withdraw-target'); Route::get('/users', [AgentUserController::class, 'index'])->name('users.index'); Route::post('/users/{user}/transfer-load', [AgentUserController::class, 'transferLoad']) ->name('users.transfer-load'); Route::post('/users/{user}/withdraw-load', [AgentUserController::class, 'withdrawLoad']) ->name('users.withdraw-load'); Route::get('/users/{user}/bets', [AgentUserController::class, 'bets']) ->name('users.bets'); Route::get('/users/{user}/commissions', [AgentUserController::class, 'commissions']) ->name('users.commissions'); Route::patch('/users/{user}/commission', [AgentUserController::class, 'updateCommission']) ->name('users.update-commission'); Route::get('/commissions', [AgentCommissionController::class, 'index']) ->name('commissions.index'); Route::get('/transactions', [AgentTransactionController::class, 'index'])->name('transactions.index'); Route::post('/commissions/convert', [AgentCommissionController::class, 'convert'])->name('commissions.convert'); Route::get('/wallet/balance', function () { $user = auth()->user()?->fresh(); abort_unless($user && $user->role === 'agent', 403); return response()->json([ 'wallet_balance' => (float) $user->wallet_balance, 'balance' => (float) $user->wallet_balance, 'commission_balance' => (float) $user->commission_balance, 'formatted_balance' => '₱'.number_format((float) $user->wallet_balance, 2), 'formatted_commission_balance' => '₱'.number_format((float) $user->commission_balance, 2), ]) ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); })->name('wallet.balance'); }); });