Return Meet attendees to join flow after event registration.
Deploy Ladill Events / deploy (push) Successful in 47s

Add badge verification API, preserve meet_return through registration and payment, and link confirmation back to Meet with badge auto-login.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-03 14:36:42 +00:00
co-authored by Cursor
parent 9eab538f2e
commit 7cce194567
9 changed files with 197 additions and 8 deletions
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\QrCode;
use App\Models\User;
use App\Services\Meet\EventMeetRoomLinkService;
use App\Services\Meet\EventRegistrationVerifyService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -46,4 +47,24 @@ class ServiceMeetController extends Controller
return response()->json(['event' => $result]);
}
public function verifyRegistration(Request $request, QrCode $event): JsonResponse
{
$validated = $request->validate([
'owner_ref' => ['required', 'string', 'max:64'],
'badge_code' => ['required', 'string', 'max:16'],
]);
$owner = User::query()->where('public_id', $validated['owner_ref'])->firstOrFail();
abort_unless($event->user_id === $owner->id, 403);
abort_unless($event->type === QrCode::TYPE_EVENT, 404);
$result = app(EventRegistrationVerifyService::class)->verifyBadge($event, $validated['badge_code']);
if (! $result) {
return response()->json(['message' => 'Badge not found.'], 404);
}
return response()->json(['registration' => $result]);
}
}
@@ -33,6 +33,7 @@ class EventRegistrationController extends Controller
'attendee_email' => ['nullable', 'email', 'max:200'],
'attendee_phone' => ['required', 'string', 'max:30'],
'badge_fields' => ['nullable', 'array'],
'meet_return' => ['nullable', 'string', 'max:2048'],
]);
try {
@@ -45,7 +46,7 @@ class EventRegistrationController extends Controller
'paid' => $result['paid'],
'checkout_url' => $result['checkout_url'],
'badge_code' => $result['registration']->badge_code,
'success_url' => LadillLink::path($qrCode->short_code, 'registered/'.$result['registration']->reference),
'success_url' => $this->confirmedUrl($qrCode, $result['registration'], $request->input('meet_return')),
]);
}
@@ -60,22 +61,43 @@ class EventRegistrationController extends Controller
try {
$registration = $this->eventService->complete($reference);
return redirect()->away(LadillLink::path($shortCode, 'registered/'.$registration->reference));
return redirect()->away($this->confirmedUrl($registration->qrCode, $registration));
} catch (\Throwable $e) {
return redirect()->away(LadillLink::url($shortCode))->with('order_error', 'Payment could not be verified. Reference: ' . $reference);
}
}
public function confirmed(string $shortCode, string $ref): View
public function confirmed(Request $request, string $shortCode, string $ref): View
{
$registration = QrEventRegistration::query()
->where('reference', $ref)
->whereHas('qrCode', fn ($q) => $q->where('short_code', $shortCode))
->firstOrFail();
$meetReturn = (string) $request->query('meet_return', '');
if ($meetReturn === '') {
$meetReturn = (string) (($registration->metadata ?? [])['meet_return'] ?? '');
}
return view('public.qr.event-confirmed', [
'registration' => $registration,
'qrCode' => $registration->qrCode,
'meetReturn' => $meetReturn,
]);
}
private function confirmedUrl(QrCode $qrCode, QrEventRegistration $registration, ?string $meetReturn = null): string
{
$url = LadillLink::path($qrCode->short_code, 'registered/'.$registration->reference);
$meetReturn = trim((string) ($meetReturn ?? ''));
if ($meetReturn === '') {
$meetReturn = trim((string) (($registration->metadata ?? [])['meet_return'] ?? ''));
}
if ($meetReturn !== '') {
$url .= '?meet_return='.urlencode($meetReturn);
}
return $url;
}
}
@@ -44,7 +44,10 @@ class QrScanController extends Controller
}
if ($qrCode->usesLandingPage()) {
return view('public.qr.landing', ['qrCode' => $qrCode]);
return view('public.qr.landing', [
'qrCode' => $qrCode,
'meetReturn' => trim((string) $request->query('meet_return', '')),
]);
}
abort(404);
@@ -98,6 +98,9 @@ class EventRegistrationService
}
$amountMinor = (int) round($priceGhs * 100);
$meetReturn = trim((string) ($data['meet_return'] ?? ''));
$metadata = $meetReturn !== '' ? ['meet_return' => $meetReturn] : null;
$registration = QrEventRegistration::create([
'qr_code_id' => $qrCode->id,
'user_id' => $qrCode->user_id,
@@ -111,6 +114,7 @@ class EventRegistrationService
'attendee_phone' => trim((string) ($data['attendee_phone'] ?? '')) ?: null,
'badge_fields' => $badgeFields ?: null,
'status' => $amountMinor > 0 ? QrEventRegistration::STATUS_PENDING : QrEventRegistration::STATUS_CONFIRMED,
'metadata' => $metadata,
]);
if ($amountMinor === 0) {
@@ -0,0 +1,37 @@
<?php
namespace App\Services\Meet;
use App\Models\QrCode;
use App\Models\QrEventRegistration;
class EventRegistrationVerifyService
{
/** @return array<string, mixed>|null */
public function verifyBadge(QrCode $event, string $badgeCode): ?array
{
abort_unless($event->type === QrCode::TYPE_EVENT, 404);
$normalized = strtoupper(trim($badgeCode));
if ($normalized === '') {
return null;
}
$registration = QrEventRegistration::query()
->where('qr_code_id', $event->id)
->where('badge_code', $normalized)
->where('status', QrEventRegistration::STATUS_CONFIRMED)
->first();
if (! $registration) {
return null;
}
return [
'attendee_name' => $registration->attendee_name,
'attendee_email' => $registration->attendee_email,
'badge_code' => $registration->badge_code,
'tier_name' => $registration->tier_name,
];
}
}