diff --git a/app/Http/Controllers/Shop/PayPalController.php b/app/Http/Controllers/Shop/PayPalController.php index 47d46ed..b8f439d 100755 --- a/app/Http/Controllers/Shop/PayPalController.php +++ b/app/Http/Controllers/Shop/PayPalController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Shop; use App\Http\Controllers\Controller; use App\Http\Requests\AccountTopupFormRequest; +use App\Models\Shop\WebsitePaypalTransaction; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; @@ -68,6 +69,7 @@ class PayPalController extends Controller $request->user()->transactions()->create([ 'transaction_id' => $response['id'], 'amount' => 0, + 'expected_amount' => $amount, ]); return redirect()->away($links['href']); @@ -79,7 +81,7 @@ class PayPalController extends Controller ); } - public function successful(Request $request): Response + public function successful(Request $request): Response|RedirectResponse { $request->validate([ 'token' => 'required', @@ -92,8 +94,12 @@ class PayPalController extends Controller return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]); } + if ($transaction->status === self::STATUS_COMPLETED) { + return to_route('shop.index')->withErrors(['message' => __('Transaction already processed')]); + } + $response = $this->getProvider()->capturePaymentOrder($request['token']); - $paymentDetails = $response['purchase_units'][0]['payments']['captures'][0]; + $paymentDetails = $response['purchase_units'][0]['payments']['captures'][0] ?? null; if (! isset($response['status'], $paymentDetails)) { Log::error('Invalid response from PayPal', ['response' => $response]); @@ -112,11 +118,28 @@ class PayPalController extends Controller return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please check your paypal account to make sure nothing was deducted and try again')]); } - $paymentDetails = $response['purchase_units'][0]['payments']['captures'][0]; + $capturedAmount = (float) $paymentDetails['amount']['value']; + $expectedAmount = (float) ($transaction->expected_amount ?? 0); + + if ($expectedAmount > 0 && abs($capturedAmount - $expectedAmount) > 0.01) { + Log::warning('PayPal amount mismatch', [ + 'transaction_id' => $transaction->transaction_id, + 'expected' => $expectedAmount, + 'captured' => $capturedAmount, + ]); + + $transaction->update([ + 'status' => 'AMOUNT_MISMATCH', + 'description' => 'Captured amount does not match expected amount', + 'amount' => $capturedAmount, + ]); + + return to_route('shop.index')->withErrors(['message' => __('Transaction amount mismatch detected')]); + } $transaction->update([ 'status' => $paymentDetails['status'], - 'amount' => $paymentDetails['amount']['value'], + 'amount' => $capturedAmount, 'currency' => $paymentDetails['amount']['currency_code'], ]); @@ -126,12 +149,12 @@ class PayPalController extends Controller ); } - $user->increment('website_balance', $paymentDetails['amount']['value']); + $user->increment('website_balance', $capturedAmount); return to_route('shop.index')->with('success', __('Transaction successful')); } - public function cancelled(Request $request): Response + public function cancelled(Request $request): RedirectResponse { $request->validate([ 'token' => 'required', diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 65d2a73..f2f31f5 100755 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -15,7 +15,7 @@ class TrustProxies extends Middleware * @var array|string|null */ #[\Override] - protected $proxies; + protected $proxies = '127.0.0.1,::1'; /** * The headers that should be used to detect proxies. diff --git a/app/Models/User.php b/app/Models/User.php index 5f1164c..19e7c16 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -127,8 +127,6 @@ class User extends Authenticatable implements FilamentUser, HasName #[\Override] protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'team_id', 'avatar_background', 'home_background', 'pincode', 'secret_key', 'extra_rank', 'is_hidden', 'background_id', 'background_stand_id', 'background_overlay_id', 'radio_points', 'pixels', 'points', 'online', 'gender', 'rank', 'mail_verified', 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at']; - protected $guarded = []; - #[\Override] protected $hidden = ['id', 'password', 'remember_token']; diff --git a/config/cors.php b/config/cors.php index f4b074d..8f401c4 100755 --- a/config/cors.php +++ b/config/cors.php @@ -19,7 +19,7 @@ return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], - 'allowed_methods' => ['*'], + 'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''), 'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', ''))), fn ($v) => $v !== ''), diff --git a/config/database.php b/config/database.php index 6513e9d..cf388b0 100755 --- a/config/database.php +++ b/config/database.php @@ -53,13 +53,12 @@ return [ 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => '', 'prefix_indexes' => true, - 'strict' => false, + 'strict' => true, 'engine' => 'InnoDB', 'sticky' => true, 'options' => extension_loaded('pdo_mysql') ? array_filter([ Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), Mysql::ATTR_INIT_COMMAND => 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,"ONLY_FULL_GROUP_BY",""))', - Mysql::ATTR_EMULATE_PREPARES => true, ]) : [], ], diff --git a/config/hashing.php b/config/hashing.php index 28b61d1..33e6178 100755 --- a/config/hashing.php +++ b/config/hashing.php @@ -17,7 +17,7 @@ return [ | */ - 'driver' => 'bcrypt', + 'driver' => 'argon2id', /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index 5fb15f8..ef36920 100755 --- a/config/session.php +++ b/config/session.php @@ -46,7 +46,7 @@ return [ | */ - 'encrypt' => false, + 'encrypt' => env('SESSION_ENCRYPT', true), /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2026_05_19_173520_add_expected_amount_to_paypal_transactions.php b/database/migrations/2026_05_19_173520_add_expected_amount_to_paypal_transactions.php new file mode 100644 index 0000000..e411a6d --- /dev/null +++ b/database/migrations/2026_05_19_173520_add_expected_amount_to_paypal_transactions.php @@ -0,0 +1,22 @@ +decimal('expected_amount', 10, 2)->nullable()->after('amount'); + }); + } + + public function down(): void + { + Schema::table('website_paypal_transactions', function (Blueprint $table) { + $table->dropColumn('expected_amount'); + }); + } +};