Add virtual event sessions with Meet sync and platform-billed messaging.
Deploy Ladill Events / deploy (push) Successful in 41s

Events can define hybrid/virtual sessions synced to Meet rooms; SMS and email use wallet-billed platform APIs instead of Termii and Laravel mail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 22:08:09 +00:00
co-authored by Cursor
parent 5a8a462102
commit 06fedcfc55
18 changed files with 735 additions and 30 deletions
+58
View File
@@ -260,6 +260,12 @@ class QrPayloadValidator
$categories = [];
}
$format = in_array($input['format'] ?? 'in_person', ['in_person', 'virtual', 'hybrid'], true)
? ($input['format'] ?? 'in_person')
: 'in_person';
$virtualSessions = $this->normalizeVirtualSessions($input['virtual_sessions'] ?? [], $format);
// Contributions always take payment; ticketing only for paid tiers; free never.
$acceptsPayment = match ($mode) {
'contributions' => true,
@@ -289,11 +295,63 @@ class QrPayloadValidator
'accepts_payment' => $acceptsPayment,
'registration_open' => filter_var($input['registration_open'] ?? true, FILTER_VALIDATE_BOOL),
'programme_qr_id' => ($pid = (int) ($input['programme_qr_id'] ?? 0)) > 0 ? $pid : null,
'format' => $format,
'virtual_sessions' => $virtualSessions,
],
'destination_url' => null,
];
}
/** @param mixed $sessions */
private function normalizeVirtualSessions(mixed $sessions, string $format): array
{
if ($format === 'in_person') {
return [];
}
if (! is_array($sessions)) {
return [];
}
$normalized = [];
foreach ($sessions as $session) {
if (! is_array($session)) {
continue;
}
$title = trim((string) ($session['title'] ?? ''));
if ($title === '') {
continue;
}
$id = trim((string) ($session['id'] ?? ''));
$roomType = in_array($session['meet_room_type'] ?? '', ['town_hall', 'webinar'], true)
? (string) $session['meet_room_type']
: 'town_hall';
$itemRefs = [];
foreach ((array) ($session['programme_item_refs'] ?? []) as $ref) {
$ref = trim((string) $ref);
if ($ref !== '') {
$itemRefs[] = mb_substr($ref, 0, 80);
}
}
$normalized[] = [
'id' => $id !== '' ? mb_substr($id, 0, 64) : null,
'title' => mb_substr($title, 0, 120),
'meet_room_type' => $roomType,
'scheduled_at' => QrDateFormatter::normalize((string) ($session['scheduled_at'] ?? '')),
'duration_minutes' => max(15, min(480, (int) ($session['duration_minutes'] ?? 60))),
'meet_room_uuid' => ($uuid = trim((string) ($session['meet_room_uuid'] ?? ''))) !== '' ? $uuid : null,
'join_url' => ($url = trim((string) ($session['join_url'] ?? ''))) !== '' ? $url : null,
'programme_item_refs' => array_values($itemRefs),
];
}
return array_values($normalized);
}
/** @param array<int, array{price: float}> $tiers */
private function eventHasPaidTier(array $tiers): bool
{