diff --git a/bootstrap/app.php b/bootstrap/app.php index 7620363..08ada05 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -13,9 +13,11 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // Public event registration POSTs land on ladl.link first; CSRF tokens from Events HTML do not match here. + // Public QR action POSTs land on ladl.link first; CSRF tokens from satellite HTML (Events/Mini) + // do not match Link's session — same failure mode as Laravel 419 Page Expired. $middleware->validateCsrfTokens(except: [ '*/register', + '*/pay', ]); $middleware->redirectGuestsTo(fn (Request $request) => route('sso.connect', [ diff --git a/tests/Feature/LinkRedirectTest.php b/tests/Feature/LinkRedirectTest.php index fe06871..2f30e90 100644 --- a/tests/Feature/LinkRedirectTest.php +++ b/tests/Feature/LinkRedirectTest.php @@ -233,4 +233,46 @@ class LinkRedirectTest extends TestCase ->assertOk() ->assertJsonPath('badge_code', 'BADGE123'); } + + public function test_mini_pay_post_does_not_require_link_app_csrf_token(): void + { + $user = $this->user(); + ShortLink::create([ + 'user_id' => $user->id, + 'slug' => 'shop-pay', + 'source_app' => 'mini', + 'source_kind' => 'qr', + 'is_managed_here' => false, + 'destination_url' => 'https://ladl.link/shop-pay', + 'is_active' => true, + ]); + + Http::fake([ + 'https://ladill.com/q/shop-pay/pay' => Http::response(json_encode([ + 'checkout_url' => 'https://ladill.com/pay/momo/REF123', + 'provider' => 'mtn_momo', + ]), 200, ['Content-Type' => 'application/json']), + ]); + + $this->postJson('https://ladl.link/shop-pay/pay', [ + 'amount' => 12.5, + 'customer_phone' => '0241234567', + ]) + ->assertOk() + ->assertJsonPath('provider', 'mtn_momo') + ->assertJsonPath('checkout_url', 'https://ladill.com/pay/momo/REF123'); + + Http::assertSent(function ($request) { + if ($request->url() !== 'https://ladill.com/q/shop-pay/pay') { + return false; + } + + $payload = json_decode($request->body(), true); + + return is_array($payload) + && (float) ($payload['amount'] ?? 0) === 12.5 + && ($payload['customer_phone'] ?? '') === '0241234567' + && $request->hasHeader('X-Ladill-Internal', '1'); + }); + } }