AbstractQrCodeApplicationService.php
7 months ago
QrCodeApplicationService.php
3 months ago
StarterQrCodeApplicationService.php
7 months ago
QrCodeApplicationService.php
238 lines
| 1 | <?php |
| 2 | |
| 3 | namespace AmeliaBooking\Application\Services\QrCode; |
| 4 | |
| 5 | use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException; |
| 6 | use AmeliaBooking\Domain\Entity\Booking\Event\CustomerBookingEventTicket; |
| 7 | use AmeliaBooking\Domain\Services\DateTime\DateTimeService; |
| 8 | use AmeliaBooking\Infrastructure\Common\Exceptions\NotFoundException; |
| 9 | use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException; |
| 10 | use AmeliaBooking\Infrastructure\Repository\Booking\Event\EventTicketRepository; |
| 11 | use AmeliaBooking\Infrastructure\Repository\Location\LocationRepository; |
| 12 | use AmeliaBooking\Infrastructure\Services\QrCode\QrCodeInfrastructureService; |
| 13 | use Interop\Container\Exception\ContainerException; |
| 14 | |
| 15 | /** |
| 16 | * Class QrCodeApplicationService |
| 17 | * |
| 18 | * @package AmeliaBooking\Application\Services\QrCode |
| 19 | */ |
| 20 | class QrCodeApplicationService extends AbstractQrCodeApplicationService |
| 21 | { |
| 22 | /** |
| 23 | * @param array $eventData |
| 24 | * @param array $booking |
| 25 | * @param string $ticketCode |
| 26 | * |
| 27 | * @return array |
| 28 | * |
| 29 | * @throws NotFoundException |
| 30 | * @throws QueryExecutionException |
| 31 | * @throws ContainerException |
| 32 | * @throws InvalidArgumentException |
| 33 | */ |
| 34 | public function createQrCodeEventTickets($eventData, $booking, $ticketCode = ''): array |
| 35 | { |
| 36 | /** @var QrCodeInfrastructureService $qrService */ |
| 37 | $qrService = $this->container->get('infrastructure.qrcode.service'); |
| 38 | |
| 39 | $locale = is_string($booking['info']) ? json_decode($booking['info'], true)['locale'] : ''; |
| 40 | $qrCodeItems = []; |
| 41 | $qrJson = $booking['qrCodes']; |
| 42 | $qrArr = is_string($qrJson) ? json_decode($qrJson, true) : (is_array($qrJson) ? $qrJson : []); |
| 43 | |
| 44 | if (is_array($qrArr)) { |
| 45 | foreach ($qrArr as $qr) { |
| 46 | if (!$ticketCode || hash_equals($qr['ticketManualCode'], $ticketCode)) { |
| 47 | $qrData = $qr; |
| 48 | if (!empty($qr['qrCodeData'])) { |
| 49 | $eventTranslations = $eventData['translations'] ? json_decode($eventData['translations'], true) : null; |
| 50 | $qrData['bookingId'] = $booking['id']; |
| 51 | $qrData['eventName'] = !empty($eventTranslations['name'][$locale]) |
| 52 | ? $eventTranslations['name'][$locale] |
| 53 | : $eventData['name']; |
| 54 | $qrData['eventStartDateTime'] = $eventData['periods'][0]['periodStart']; |
| 55 | |
| 56 | if ( |
| 57 | $qrData['type'] === 'ticket' && |
| 58 | array_key_exists('eventTicketId', $qrData) && |
| 59 | isset($booking['ticketsData']) |
| 60 | ) { |
| 61 | foreach ($eventData['customTickets'] as $ticket) { |
| 62 | if ($ticket['id'] === $qrData['eventTicketId']) { |
| 63 | $ticketTranslations = $ticket['translations'] ? json_decode($ticket['translations'], true) : null; |
| 64 | $ticketName = !empty($ticketTranslations[$locale]) |
| 65 | ? $ticketTranslations[$locale] |
| 66 | : $ticket['name']; |
| 67 | $qrData['eventTicketName'] = $ticketName; |
| 68 | break; |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | if (isset($eventData['customLocation']) && $eventData['customLocation']) { |
| 74 | $qrData['eventLocation'] = $eventData['customLocation']; |
| 75 | } |
| 76 | if (isset($eventData['location'])) { |
| 77 | $qrData['eventLocation'] = $eventData['location']; |
| 78 | } |
| 79 | if (isset($eventData['locationId'])) { |
| 80 | /** @var LocationRepository $locationRepository */ |
| 81 | $locationRepository = $this->container->get('domain.locations.repository'); |
| 82 | $location = $locationRepository->getById($eventData['locationId']); |
| 83 | if ($location) { |
| 84 | $locationTranslations = $location->getTranslations() ? json_decode($location->getTranslations()->getValue(), true) : null; |
| 85 | $locTranslation = $locationTranslations['name'][$locale] ?? $location->getName()->getValue(); |
| 86 | $qrData['eventLocation'] = $location->getAddress()->getValue() ?: $locTranslation; |
| 87 | } |
| 88 | } |
| 89 | if ($ticketCode) { |
| 90 | return $qrService->generateQrCode($qrData); |
| 91 | } |
| 92 | $qrCodeItems[] = $qrService->generateQrCode($qrData); |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | return $qrCodeItems; |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Create QR code data array for a booking and event |
| 103 | * |
| 104 | * @param $event |
| 105 | * @param $booking |
| 106 | * @return array |
| 107 | * @throws InvalidArgumentException |
| 108 | * @throws ContainerException |
| 109 | * @throws NotFoundException |
| 110 | * @throws QueryExecutionException |
| 111 | */ |
| 112 | public function createQrCodeEventData($event, $booking): array |
| 113 | { |
| 114 | $qrCodes = []; |
| 115 | $qrNumberData = $this->getNumberOfQrCodes($booking, $event); |
| 116 | |
| 117 | if ($qrNumberData['number'] > 0 && $event->getId()) { |
| 118 | // Common timestamp for generation moment (UTC ISO8601) |
| 119 | $generatedAt = DateTimeService::getNowDateTimeObjectInUtc()->format('Y-m-d H:i:s'); |
| 120 | |
| 121 | $bookingIdVal = $booking->getId()->getValue(); |
| 122 | $eventIdVal = $event->getId()->getValue(); |
| 123 | $customerIdVal = $booking->getCustomerId() ? $booking->getCustomerId()->getValue() : ''; |
| 124 | |
| 125 | // Booking-level |
| 126 | if ($qrNumberData['number'] > 1) { |
| 127 | $bookingManualCode = $this->generateManualCode([ |
| 128 | 'bookingId' => $bookingIdVal, |
| 129 | 'eventId' => $eventIdVal, |
| 130 | 'customerId' => $customerIdVal, |
| 131 | 'generatedAt' => $generatedAt, |
| 132 | ]); |
| 133 | |
| 134 | $bookingQrData = 'type: booking | bookingId:' . $bookingIdVal . ' | ticketManualCode:' . $bookingManualCode; |
| 135 | |
| 136 | $qrCodes[] = [ |
| 137 | 'type' => 'booking', |
| 138 | 'eventName' => $event->getName() ? $event->getName()->getValue() : '', |
| 139 | 'ticketManualCode' => $bookingManualCode, |
| 140 | 'qrCodeData' => $bookingQrData, |
| 141 | 'generatedAt' => $generatedAt, |
| 142 | 'dates' => [], |
| 143 | ]; |
| 144 | } |
| 145 | |
| 146 | // Person / ticket level |
| 147 | for ($i = 1; $i <= $qrNumberData['number']; $i++) { |
| 148 | $ticketIdForPerson = $qrNumberData['ticketIds'][$i - 1] ?? null; |
| 149 | $codePayload = [ |
| 150 | 'bookingId' => $bookingIdVal, |
| 151 | 'eventId' => $eventIdVal, |
| 152 | 'customerId' => $customerIdVal, |
| 153 | 'ticketIndex' => $i, |
| 154 | 'generatedAt' => $generatedAt, |
| 155 | ]; |
| 156 | |
| 157 | if ($ticketIdForPerson) { |
| 158 | $codePayload['ticketId'] = $ticketIdForPerson; |
| 159 | } |
| 160 | |
| 161 | $manualTicketCode = $this->generateManualCode($codePayload); |
| 162 | |
| 163 | $ticketQrData = 'type: ticket | bookingId:' . $bookingIdVal . ' ticketManualCode:' . $manualTicketCode; |
| 164 | |
| 165 | $entry = [ |
| 166 | 'type' => 'ticket', |
| 167 | 'eventName' => $event->getName() ? $event->getName()->getValue() : '', |
| 168 | 'ticketManualCode' => $manualTicketCode, |
| 169 | 'qrCodeData' => $ticketQrData, |
| 170 | 'generatedAt' => $generatedAt, |
| 171 | 'dates' => [] |
| 172 | ]; |
| 173 | |
| 174 | if ($ticketIdForPerson) { |
| 175 | /** @var EventTicketRepository $eventTicketRepository */ |
| 176 | $eventTicketRepository = $this->container->get('domain.booking.event.ticket.repository'); |
| 177 | $ticket = $eventTicketRepository->getById($ticketIdForPerson); |
| 178 | $entry['eventTicketId'] = $ticketIdForPerson; |
| 179 | if ($ticket && $ticket->getName()) { |
| 180 | $entry['eventTicketName'] = $ticket->getName()->getValue(); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | $qrCodes[] = $entry; |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | return $qrCodes; |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Determine number of QR codes to generate for a booking, and build a sequence of ticket ids if applicable |
| 193 | * |
| 194 | * @param $booking |
| 195 | * @param $event |
| 196 | * @return array ['number' => int, 'ticketIds' => array|null] |
| 197 | */ |
| 198 | private function getNumberOfQrCodes($booking, $event): array |
| 199 | { |
| 200 | $personsForQr = 0; |
| 201 | $ticketIdSequence = []; |
| 202 | if ($booking->getTicketsBooking() && $event->getCustomPricing()->getValue()) { |
| 203 | /** @var CustomerBookingEventTicket $ticketBooking */ |
| 204 | foreach ($booking->getTicketsBooking()->getItems() as $ticketBooking) { |
| 205 | $ticketPersons = ($ticketBooking->getPersons() ? $ticketBooking->getPersons()->getValue() : 0); |
| 206 | $personsForQr += $ticketPersons; |
| 207 | // Build a flat sequence of ticket ids, one per person, to map each QR to a ticket |
| 208 | if ($ticketPersons && $ticketBooking->getEventTicketId()) { |
| 209 | for ($ti = 0; $ti < $ticketPersons; $ti++) { |
| 210 | $ticketIdSequence[] = $ticketBooking->getEventTicketId()->getValue(); |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | } else { |
| 215 | $personsForQr = $booking->getPersons() ? $booking->getPersons()->getValue() : 0; |
| 216 | } |
| 217 | |
| 218 | $qrCodesNumberData['number'] = $personsForQr; |
| 219 | $qrCodesNumberData['ticketIds'] = $ticketIdSequence ?: null; |
| 220 | |
| 221 | return $qrCodesNumberData; |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Generate a short manual code from data array |
| 226 | * |
| 227 | * @param array $data |
| 228 | * @return string |
| 229 | */ |
| 230 | private function generateManualCode($data): string |
| 231 | { |
| 232 | $raw = hash('sha256', json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true); |
| 233 | $b64 = rtrim(strtr(base64_encode($raw), '+/', 'AZ'), '='); |
| 234 | |
| 235 | return substr($b64, 0, 10); |
| 236 | } |
| 237 | } |
| 238 |