diff --git a/bootstrap/app.php b/bootstrap/app.php index d930734..30f8f40 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -13,6 +13,11 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + // Public event registration is posted from ladl.link (cross-site); session CSRF cannot bind there. + $middleware->validateCsrfTokens(except: [ + 'q/*/register', + ]); + $middleware->redirectGuestsTo(fn (Request $request) => route('sso.connect', [ 'redirect' => $request->fullUrl(), ])); diff --git a/resources/views/public/qr/landing.blade.php b/resources/views/public/qr/landing.blade.php index d64857d..5d34a11 100644 --- a/resources/views/public/qr/landing.blade.php +++ b/resources/views/public/qr/landing.blade.php @@ -656,6 +656,7 @@ if (!this.tier) { this.errorMsg = this.mode === 'contributions' ? 'Select a category.' : 'Select a ticket type.'; return; } if (this.mode === 'contributions' && !(parseFloat(this.amount) > 0)) { this.errorMsg = 'Enter a contribution amount.'; return; } if (!this.name.trim()) { this.errorMsg = 'Enter your full name.'; return; } + if (!this.email.trim() || !this.email.includes('@')) { this.errorMsg = 'Enter a valid email address.'; return; } if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; } this.errorMsg = ''; this.loading = true; try { @@ -664,11 +665,22 @@ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': @js($evCsrf) }, body: JSON.stringify({ tier: this.tier, amount: this.mode === 'contributions' ? this.amount : null, attendee_name: this.name, attendee_email: this.email, attendee_phone: this.phone, badge_fields: this.fields }), }); - const data = await res.json(); + const data = await res.json().catch(() => ({})); + if (!res.ok) { + this.errorMsg = data.error || data.message || 'Registration failed. Please try again.'; + this.loading = false; + return; + } if (data.error) { this.errorMsg = data.error; this.loading = false; return; } - if (!data.paid) { window.location.href = data.success_url; return; } - if (window.innerWidth < 768) { this.checkoutUrl = data.checkout_url; this.showSheet = true; this.loading = false; } - else { window.location.href = data.checkout_url; } + if (data.paid) { + if (!data.checkout_url) { this.errorMsg = 'Could not start checkout. Please try again.'; this.loading = false; return; } + if (window.innerWidth < 768) { this.checkoutUrl = data.checkout_url; this.showSheet = true; this.loading = false; } + else { window.location.href = data.checkout_url; } + return; + } + if (data.success_url) { window.location.href = data.success_url; return; } + this.errorMsg = 'Registration failed. Please try again.'; + this.loading = false; } catch(e) { this.errorMsg = 'Network error. Please try again.'; this.loading = false; } } }" class="min-h-screen bg-slate-50 pb-12"> @@ -887,6 +899,8 @@
Your details
+ @foreach($evFields as $field) diff --git a/tests/Feature/EventRegistrationTest.php b/tests/Feature/EventRegistrationTest.php new file mode 100644 index 0000000..55e39b6 --- /dev/null +++ b/tests/Feature/EventRegistrationTest.php @@ -0,0 +1,74 @@ + Http::response(['balance_minor' => 50000]), + ]); + } + + public function test_free_registration_returns_success_url_without_session_cookie(): void + { + config(['link.public_domain' => 'ladl.link']); + + $owner = User::create([ + 'public_id' => 'usr_reg_free', + 'name' => 'Event Owner', + 'email' => 'owner-reg-free@example.com', + ]); + + QrCode::create([ + 'user_id' => $owner->id, + 'short_code' => 'webinar-demo', + 'type' => QrCode::TYPE_EVENT, + 'label' => 'Product webinar', + 'payload' => [ + 'content' => [ + 'name' => 'Product webinar', + 'mode' => 'free', + 'format' => 'virtual', + 'registration_open' => true, + 'tiers' => [['name' => 'Registration', 'price' => 0, 'capacity' => 0]], + 'virtual_sessions' => [ + ['join_url' => 'https://meet.ladill.com/r/demo-room'], + ], + ], + 'style' => [], + ], + 'is_active' => true, + ]); + + $response = $this->postJson('/q/webinar-demo/register', [ + 'tier' => 'Registration', + 'attendee_name' => 'Jane Doe', + 'attendee_email' => 'jane@example.com', + 'attendee_phone' => '+233200000000', + ], [ + 'X-Ladill-Internal' => '1', + ]); + + $response->assertOk() + ->assertJsonPath('paid', false) + ->assertJsonStructure(['success_url', 'badge_code']); + + $this->assertStringStartsWith( + LadillLink::path('webinar-demo', 'registered/'), + (string) $response->json('success_url') + ); + } +}