Ticketing system — back-office and customer-facing platform

Collaborated on a complete ticketing back-office under the supervision of a colleague, with full autonomy in implementation and testing: product, ticket, customer, order, gift card management, sales/attendance statistics, and archiving.

The gift card system in particular — purchase flow, delivery logic (postal vs. electronic), shipping cost deduplication across an order, and recipient/sender data handling — was primarily my own work.
The underlying data model is tightly interdependent by necessity — cancelling a single order, for instance, cascades into ticket status changes, visit-record cleanup, and (for subscriptions) physical card deactivation, all of which had to stay consistent with each other. On the customer-facing side, I built the page templates (purchase flow, customer account area, confirmation pages), while the underlying logic — payment gateway integration, PDF ticket generation with barcodes, automated email delivery — was handled by my colleague.

  • Ticketing System
  • Gift card
  • PHP

Code Snippet

PHP
<?php


/**
 * Amusement park ticketing system — gift card line item processing
 * (part of the order-creation flow, cart checkout).
 * Anonymized/condensed for portfolio purposes.
 *
 * Handles one gift-card line from the cart: creates one GiftCard record
 * per unit (quantity > 1 means multiple distinct cards, each with its
 * own code), resolves sender/recipient details with sensible defaults,
 * and applies shipping cost deduplication for physical cards sent to
 * the same address within the same order.
 *
 * Architectural Note & Self-Review (Portfolio context):
 * Integrated within the project's procedural order-processing pipeline.
 * Identified refactoring targets for future iterations:
 * - Encapsulate state: move logic into a dedicated `GiftCardProcessor` service to eliminate outer-scope side effects.
 */

if (is_string($key) && str_starts_with($key, 'giftCard')) {

    if (empty($giftCardsInCart[$key]) || !is_array($giftCardsInCart[$key])) {
        throw new GiftCardException('Invalid cart: missing gift card data.');
    }

    $giftCardInput = $giftCardsInCart[$key];

    $quantity = max(1, (int)($line['qty'] ?? $giftCardInput['qty'] ?? 1));

    $unitAmount = (float)($giftCardInput['price'] ?? 0);
    $unitAmountCents = euroToCents($unitAmount);

    if ($unitAmountCents <= 0) {
        throw new GiftCardException('Invalid gift card amount.');
    }

    $isPhysicalCard = ($giftCardInput['cardType'] ?? null) === GIFT_CARDS_TYPES['PHYSICAL'];
    $isSentToBuyer = ($giftCardInput['deliveryMethod'] ?? null) === DELIVERY_METHODS_GIFT_CARDS['SEND_TO_BUYER'];
    $isSentByEmail = $isSentToBuyer && ($giftCardInput['cardType'] ?? null) === GIFT_CARDS_TYPES['ELECTRONIC'];
    $isSentByPost = $isSentToBuyer && $isPhysicalCard;

    $codeGenerator = new Code();
    $codeGenerator->for = 'giftCard';

    for ($i = 0; $i < $quantity; $i++) {

        $codeGenerator->setNew($orderDate, ['type' => 'S', 'cat' => 'K']);

        $giftCardData = [
            'orderId' => $order->getId(),
            'code' => $codeGenerator->new,
            'amount' => $unitAmount,
            'expirationDate' => date('Y-m-d', strtotime('+2 years')),
            'status' => 0,
            'fromName' => $giftCardInput['giverName'] ?? ($client->getFirstName() . ' ' . $client->getLastName()),
        ];

        if (!empty($giftCardInput['beneficiaryName'])) {
            $giftCardData['toName'] = $giftCardInput['beneficiaryName'];
        }
        if (!empty($giftCardInput['message'])) {
            $giftCardData['customMessage'] = $giftCardInput['message'];
        }

        $giftCardData['recipientLastName'] = $giftCardInput['name'] ?? ($isSentToBuyer ? $client->getLastName() : null);
        $giftCardData['recipientFirstName'] = $giftCardInput['firstname'] ?? ($isSentToBuyer ? $client->getFirstName() : null);
        $giftCardData['recipientEmail'] = $giftCardInput['email'] ?? ($isSentByEmail ? $client->getEmail() : null);
        $giftCardData['recipientPostcode'] = $giftCardInput['postcode'] ?? ($isSentByPost ? $client->getPostcode() : null);
        $giftCardData['recipientAddress'] = $giftCardInput['address'] ?? ($isSentByPost ? $client->getAddress() : null);
        $giftCardData['recipientCity'] = $giftCardInput['city'] ?? ($isSentByPost ? $client->getCity() : null);
        $giftCardData['recipientCountry'] = $giftCardInput['country'] ?? ($isSentByPost ? $client->getCountry() : null);

        $giftCardData = array_filter($giftCardData, fn($value) => $value !== null);

        if ($isPhysicalCard) {
            $giftCardData['requiresShipping'] = true;

            $destinationKey = $isSentToBuyer
                ? DELIVERY_METHODS_GIFT_CARDS['SEND_TO_BUYER']
                : implode('|', [
                    (string)($giftCardInput['address'] ?? ''),
                    (string)($giftCardInput['postcode'] ?? ''),
                    (string)($giftCardInput['city'] ?? ''),
                    (string)($giftCardInput['country'] ?? ''),
                ]);

            $alreadyBilled = in_array($destinationKey, $shippingAddressesBilled, true);
            $giftCardData['shippingCost'] = $alreadyBilled ? 0 : SHIPPING_COST_GIFT_CARD;

            if (!$alreadyBilled) {
                $shippingAddressesBilled[] = $destinationKey;
            }
        }

        if (!$giftCard->create($giftCardData)) {
            throw new GiftCardException('Error creating the gift card.');
        }
    }

    $lineTotalCents = $unitAmountCents * $quantity;
    $orderTotalCents += $lineTotalCents;
    $orderTotalQty += $quantity;

    $orderLineItems[] = [
        'name' => (string)($giftCardInput['title'] ?? 'Gift card'),
        'qty' => $quantity,
        'amount' => (string)$lineTotalCents,
    ];

    continue;
}