confirmedRegistrations($event); $emails = $regs->filter(fn ($r) => (bool) $r->attendee_email)->count(); $phones = $regs->filter(fn ($r) => (bool) $r->attendee_phone)->count(); $smsSegments = $phones * $this->estimateSmsSegments($event, $mode); $estimated = round(($emails * self::EMAIL_PRICE_GHS) + ($smsSegments * self::SMS_PRICE_GHS), 2); $ownerRef = (string) $event->user->public_id; $integrationsError = $this->integrationsError($event, $mode, $emails, $phones); return [ 'recipients' => $regs->count(), 'emails' => $emails, 'sms' => $phones, 'estimated_ghs' => $estimated, 'affordable' => $integrationsError === null, 'integrations_error' => $integrationsError, ]; } /** * @return array{emailed: int, texted: int, skipped: int} */ public function send(QrCode $event, string $mode): array { $preview = $this->preview($event, $mode); abort_if($preview['integrations_error'] !== null, 422, $preview['integrations_error']); $programme = $this->programmeFor($event); $eventName = $event->content()['name'] ?? $event->label; $programmeUrl = $programme?->publicUrl(); $joinUrl = $this->primaryJoinUrl($event); $ownerRef = (string) $event->user->public_id; $organizerEmail = $event->user->email; $organizerName = $event->user->name; $emailed = 0; $texted = 0; $skipped = 0; foreach ($this->confirmedRegistrations($event) as $reg) { $sent = false; if ($reg->attendee_email && in_array($mode, [self::MODE_PROGRAMME, self::MODE_BOTH], true) && $programmeUrl) { if ($mode === self::MODE_BOTH) { $sent = $this->email->sendJoinAndProgramme($ownerRef, $reg->attendee_email, $eventName, $joinUrl, $programmeUrl, $reg->attendee_name, $organizerEmail, $organizerName); } else { $sent = $this->email->sendProgrammeShare($ownerRef, $reg->attendee_email, $eventName, $programmeUrl, $reg->attendee_name, $organizerEmail, $organizerName); } if ($sent) { $emailed++; } } elseif ($reg->attendee_email && $mode === self::MODE_JOIN && $joinUrl !== '') { $sent = $this->email->sendJoinLink($ownerRef, $reg->attendee_email, $eventName, $joinUrl, $reg->attendee_name, $organizerEmail, $organizerName); if ($sent) { $emailed++; } } if ($reg->attendee_phone) { $message = $this->smsBody($reg, $eventName, $mode, $programmeUrl, $joinUrl); if ($message !== '' && $this->sms->send($ownerRef, $reg->attendee_phone, $message)) { $texted++; $sent = true; } } if (! $sent) { $skipped++; } } return compact('emailed', 'texted', 'skipped'); } /** @return Collection */ private function confirmedRegistrations(QrCode $event): Collection { return $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->get(); } private function programmeFor(QrCode $event): ?QrCode { $programmeId = (int) ($event->content()['programme_qr_id'] ?? 0); if ($programmeId <= 0) { return null; } return QrCode::where('id', $programmeId) ->where('user_id', $event->user_id) ->where('type', QrCode::TYPE_ITINERARY) ->first(); } public function primaryJoinUrl(QrCode $event): string { foreach ((array) ($event->content()['virtual_sessions'] ?? []) as $session) { if (! is_array($session)) { continue; } $url = trim((string) ($session['join_url'] ?? '')); if ($url !== '') { return $url; } } return ''; } private function smsBody(QrEventRegistration $reg, string $eventName, string $mode, ?string $programmeUrl, string $joinUrl): string { $first = explode(' ', $reg->attendee_name ?? 'there')[0]; return match ($mode) { self::MODE_PROGRAMME => $programmeUrl ? sprintf('Hi %s, the programme for %s: %s', $first, $eventName, $programmeUrl) : '', self::MODE_JOIN => $joinUrl !== '' ? sprintf('Hi %s, join %s here: %s', $first, $eventName, $joinUrl) : '', self::MODE_BOTH => ($programmeUrl && $joinUrl !== '') ? sprintf('Hi %s, %s — programme: %s | join: %s', $first, $eventName, $programmeUrl, $joinUrl) : ($joinUrl !== '' ? sprintf('Hi %s, join %s: %s', $first, $eventName, $joinUrl) : ''), default => '', }; } private function estimateSmsSegments(QrCode $event, string $mode): int { $sample = $this->smsBody( new QrEventRegistration(['attendee_name' => 'Guest']), $event->content()['name'] ?? $event->label, $mode, $this->programmeFor($event)?->publicUrl(), $this->primaryJoinUrl($event), ); return max(1, (int) ceil(strlen($sample) / self::SMS_SEGMENT_CHARS)); } private function integrationsError(QrCode $event, string $mode, int $emails, int $phones): ?string { $ownerRef = (string) $event->user->public_id; $credential = $this->credentials->forOwner($ownerRef); $programmeUrl = $this->programmeFor($event)?->publicUrl(); $joinUrl = $this->primaryJoinUrl($event); $needsEmail = $emails > 0 && ( (in_array($mode, [self::MODE_PROGRAMME, self::MODE_BOTH], true) && $programmeUrl) || ($mode === self::MODE_JOIN && $joinUrl !== '') ); $needsSms = $phones > 0 && $this->smsBody( new QrEventRegistration(['attendee_name' => 'Guest']), $event->content()['name'] ?? $event->label, $mode, $programmeUrl, $joinUrl, ) !== ''; $emailReady = $this->platformEmail->isConfigured() || $credential->hasValidBird(); $smsReady = $this->platformSms->isConfigured() || $credential->hasValidSms(); if ($needsEmail && ! $emailReady) { return 'Email is not available yet. Bind a mailbox under Account → Messaging once platform keys are configured, or connect Bird under Advanced keys.'; } if ($needsSms && ! $smsReady) { return 'SMS is not available yet. Ensure platform SMS is configured on this Events instance, or connect a product SMS key under Advanced keys.'; } return null; } }