# Event Booking Orders API

**Author**: Josh S. Sakweli, Backend Lead Team
**Last Updated**: 2026-05-23
**Version**: v1.0

**Base URL**: `https://api.nexgate.com/api/v1`

**Short Description**: The Event Booking Orders API manages confirmed ticket purchases for events on the Nexgate platform. It allows customers to view their bookings with JWT-signed QR codes, track multi-day check-in history, download ticket PDFs, and enables event organizers to monitor all orders for their events with filtering and revenue stats.

**Hints**:
- Bookings are created automatically after successful checkout payment — never created directly via this API
- QR codes are RSA-signed JWTs containing full ticket and event data; they cannot be forged
- Event details (title, venue, organizer) are snapshotted at booking time and never change on a booking record
- Ticket series are auto-generated (e.g., `VIP-0001`, `GENER-0042`) using a per-ticket-type counter
- Multi-day events store a full `checkIns` array per ticket — one record per day attended
- Customers see their own bookings only; organizers see bookings for their events; admins see all
- The PDF endpoint returns a binary file, not a JSON response

---

## Standard Response Format

All JSON endpoints follow a consistent structure using the Globe Response Builder pattern:

### Success Response Structure
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": {}
}
```

### Error Response Structure
```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Booking not found",
  "action_time": "2026-05-23T10:30:45",
  "data": "Booking not found"
}
```

### Standard Response Fields
| Field         | Type          | Description                                                         |
|---------------|---------------|---------------------------------------------------------------------|
| `success`     | boolean       | `true` for successful operations, `false` for errors               |
| `httpStatus`  | string        | HTTP status name (OK, NOT_FOUND, FORBIDDEN, etc.)                  |
| `message`     | string        | Human-readable description of the result                           |
| `action_time` | string        | ISO 8601 timestamp of when the response was generated              |
| `data`        | object/string | Response payload on success; error message string on failure       |

---

## HTTP Method Badge Standards

- **GET** - <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> - Green (Safe, read-only operations)
- **POST** - <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> - Blue (Create new resources)
- **PUT** - <span style="background-color: #ffc107; color: black; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PUT</span> - Yellow (Update/replace entire resource)
- **PATCH** - <span style="background-color: #fd7e14; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> - Orange (Partial updates)
- **DELETE** - <span style="background-color: #dc3545; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">DELETE</span> - Red (Remove resources)

---

## Endpoints

## 1. Get Booking by ID

**Purpose**: Retrieve complete booking details including all tickets, check-in history, and event snapshot for a specific booking.

**Endpoint**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/e-events/booking-orders/{bookingId}`

**Access Level**: 🔒 Protected (Booking Owner, Event Organizer, or Admin)

**Authentication**: Bearer Token

**Request Headers**:
| Header          | Type   | Required | Description                               |
|-----------------|--------|----------|-------------------------------------------|
| `Authorization` | string | Yes      | Bearer token (format: `Bearer <token>`)   |

**Path Parameters**:
| Parameter   | Type         | Required | Description            | Validation       |
|-------------|--------------|----------|------------------------|------------------|
| `bookingId` | string (UUID)| Yes      | Unique booking order ID | Must be valid UUID |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Booking retrieved successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "bookingId": "550e8400-e29b-41d4-a716-446655440000",
    "bookingReference": "EVT-A3F4B21C",
    "status": "CONFIRMED",
    "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "event": {
      "eventId": "770e8400-e29b-41d4-a716-446655440002",
      "title": "East African Tech Summit 2025",
      "startDateTime": "2025-12-15T09:00:00",
      "endDateTime": "2025-12-17T18:00:00",
      "timezone": "Africa/Nairobi",
      "location": "KICC Nairobi, Harambee Avenue, Nairobi",
      "format": "HYBRID",
      "hasApplicantForm": true,
      "virtualDetails": {
        "meetingLink": "https://zoom.us/j/123456789",
        "meetingId": "123 456 789",
        "passcode": "summit2025"
      }
    },
    "organizer": {
      "name": "TechEvents Kenya",
      "email": "organizer@techevents.ke",
      "phone": "+254712345678"
    },
    "customer": {
      "customerId": "660e8400-e29b-41d4-a716-446655440001",
      "name": "johndoe",
      "email": "john@example.com"
    },
    "tickets": [
      {
        "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440010",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "ticketTypeName": "VIP Pass",
        "ticketSeries": "VIP-0001",
        "ticketNumber": "VIP-0001",
        "price": 150.00,
        "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "attendanceMode": "IN_PERSON",
        "attendee": {
          "name": "John Doe",
          "email": "john@example.com",
          "phone": "+255712345678"
        },
        "buyer": {
          "name": "John Doe",
          "email": "john@example.com",
          "buyerType": "SYSTEM_USER"
        },
        "checkIns": [
          {
            "checkInTime": "2025-12-15T09:15:00+03:00",
            "checkInLocation": "Main Gate",
            "checkedInBy": "Scanner Operator 1",
            "dayName": "Day 1 - Opening Day",
            "scannerId": "SCANNER-001",
            "checkInMethod": "QR_SCAN"
          },
          {
            "checkInTime": "2025-12-16T08:45:00+03:00",
            "checkInLocation": "VIP Entrance",
            "checkedInBy": "Scanner Operator 2",
            "dayName": "Day 2 - Conference Day",
            "scannerId": "SCANNER-003",
            "checkInMethod": "QR_SCAN"
          }
        ],
        "hasBeenCheckedIn": true,
        "lastCheckedInAt": "2025-12-16T08:45:00+03:00",
        "lastCheckedInBy": "Scanner Operator 2",
        "lastCheckInLocation": "VIP Entrance",
        "lastCheckInDayName": "Day 2 - Conference Day",
        "status": "USED",
        "validFrom": "2025-12-15T09:00:00+03:00",
        "validUntil": "2025-12-17T18:00:00+03:00"
      },
      {
        "ticketInstanceId": "880e8400-e29b-41d4-a716-446655440011",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "ticketTypeName": "General Admission",
        "ticketSeries": "GENER-0042",
        "ticketNumber": "GENER-0042",
        "price": 50.00,
        "qrCode": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "attendanceMode": "IN_PERSON",
        "attendee": {
          "name": "Jane Smith",
          "email": "jane@example.com",
          "phone": "+255723456789"
        },
        "buyer": {
          "name": "John Doe",
          "email": "john@example.com",
          "buyerType": "SYSTEM_USER"
        },
        "checkIns": [],
        "hasBeenCheckedIn": false,
        "status": "ACTIVE",
        "validFrom": "2025-12-15T09:00:00+03:00",
        "validUntil": "2025-12-17T18:00:00+03:00"
      }
    ],
    "totalTickets": 2,
    "checkedInTicketsCount": 1,
    "subtotal": 200.00,
    "total": 200.00,
    "bookedAt": "2025-12-11T10:30:45",
    "cancelledAt": null
  }
}
```

**Success Response Fields**:
| Field | Description |
|---|---|
| `bookingId` | Unique booking UUID |
| `bookingReference` | Short readable code (e.g., `EVT-A3F4B21C`) |
| `status` | Booking status: `CONFIRMED`, `CANCELLED` |
| `formResponseId` | UUID of the buyer's applicant form response — `null` if the event had no form |
| `event.eventId` | Event UUID |
| `event.title` | Event title at time of booking (immutable snapshot) |
| `event.startDateTime` | Event start — LocalDateTime (no timezone) |
| `event.endDateTime` | Event end — LocalDateTime (no timezone) |
| `event.timezone` | IANA timezone string (e.g., `Africa/Nairobi`) |
| `event.location` | Full venue address snapshotted at booking |
| `event.format` | `IN_PERSON`, `ONLINE`, or `HYBRID` |
| `event.hasApplicantForm` | `true` if the event had an applicant form at time of booking |
| `event.virtualDetails` | Virtual meeting info — only present for `ONLINE`/`HYBRID` events |
| `event.virtualDetails.meetingLink` | Full meeting URL |
| `event.virtualDetails.meetingId` | Meeting ID (optional) |
| `event.virtualDetails.passcode` | Meeting passcode (optional) |
| `organizer.name` | Organizer name snapshotted at booking |
| `organizer.email` | Organizer email |
| `organizer.phone` | Organizer phone |
| `customer.customerId` | Customer account UUID |
| `customer.name` | Customer username |
| `customer.email` | Customer email |
| `tickets` | Array of all booked ticket instances |
| `tickets[].ticketInstanceId` | Unique ticket instance UUID |
| `tickets[].formResponseId` | UUID of the applicant form response linked to this ticket — same as order-level `formResponseId` |
| `tickets[].ticketTypeName` | Ticket type name (e.g., `VIP Pass`) |
| `tickets[].ticketSeries` | Auto-generated series (e.g., `VIP-0001`) |
| `tickets[].ticketNumber` | Same as ticketSeries — used for PDF labeling |
| `tickets[].price` | Price paid for this ticket |
| `tickets[].qrCode` | RSA-signed JWT — used for gate check-in scanning |
| `tickets[].attendanceMode` | `IN_PERSON` or `ONLINE` (relevant for hybrid events) |
| `tickets[].attendee` | Person this ticket is for |
| `tickets[].attendee.name` | Attendee full name |
| `tickets[].attendee.email` | Attendee email |
| `tickets[].attendee.phone` | Attendee phone |
| `tickets[].buyer` | Person who purchased this ticket |
| `tickets[].buyer.name` | Buyer name |
| `tickets[].buyer.email` | Buyer email (null for AT_DOOR purchases) |
| `tickets[].buyer.buyerType` | `SYSTEM_USER` (online) or `AT_DOOR` (sold at gate) |
| `tickets[].checkIns` | Full check-in history — one record per day attended |
| `tickets[].checkIns[].checkInTime` | ZonedDateTime of check-in |
| `tickets[].checkIns[].checkInLocation` | Where checked in (e.g., `Main Gate`, `VIP Entrance`) |
| `tickets[].checkIns[].checkedInBy` | Staff/scanner operator name |
| `tickets[].checkIns[].dayName` | Event day label (e.g., `Day 1 - Opening Day`) |
| `tickets[].checkIns[].scannerId` | Scanner device ID |
| `tickets[].checkIns[].checkInMethod` | `QR_SCAN` (default), `MANUAL`, or `NFC` |
| `tickets[].hasBeenCheckedIn` | `true` if ticket has at least one check-in |
| `tickets[].lastCheckedInAt` | Most recent check-in time (ZonedDateTime, null if none) |
| `tickets[].lastCheckedInBy` | Who performed the most recent check-in (null if none) |
| `tickets[].lastCheckInLocation` | Location of most recent check-in (null if none) |
| `tickets[].lastCheckInDayName` | Day label of most recent check-in (null if none) |
| `tickets[].status` | `ACTIVE`, `USED`, or `CANCELLED` |
| `tickets[].validFrom` | Ticket validity start — ZonedDateTime |
| `tickets[].validUntil` | Ticket validity end — ZonedDateTime |
| `totalTickets` | Total ticket count in this booking |
| `checkedInTicketsCount` | Count of tickets with at least one check-in |
| `subtotal` | Subtotal before any deductions |
| `total` | Total amount paid |
| `bookedAt` | When the booking was created — LocalDateTime |
| `cancelledAt` | When cancelled — LocalDateTime (null if still active) |

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "Booking not found: 550e8400-e29b-41d4-a716-446655440000",
  "action_time": "2026-05-23T10:30:45",
  "data": "Booking not found: 550e8400-e29b-41d4-a716-446655440000"
}
```

**Standard Error Types**:
- `401 UNAUTHORIZED`: Missing or expired Bearer token
- `403 FORBIDDEN`: Authenticated user is not the booking owner, event organizer, or admin
- `404 NOT_FOUND`: Booking with given ID does not exist

---

## 2. Get My Bookings

**Purpose**: Retrieve a summary list of all bookings for the authenticated user, sorted newest first.

**Endpoint**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/e-events/booking-orders/my-bookings`

**Access Level**: 🔒 Protected (Authenticated Users)

**Authentication**: Bearer Token

**Request Headers**:
| Header          | Type   | Required | Description                             |
|-----------------|--------|----------|-----------------------------------------|
| `Authorization` | string | Yes      | Bearer token (format: `Bearer <token>`) |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Bookings retrieved successfully",
  "action_time": "2026-05-23T10:30:45",
  "data": [
    {
      "bookingId": "550e8400-e29b-41d4-a716-446655440000",
      "bookingReference": "EVT-A3F4B21C",
      "status": "CONFIRMED",
      "eventTitle": "East African Tech Summit 2025",
      "eventStartDateTime": "2025-12-15T09:00:00",
      "eventLocation": "KICC Nairobi, Harambee Avenue, Nairobi",
      "totalTickets": 2,
      "checkedInTickets": 1,
      "total": 200.00,
      "bookedAt": "2025-12-11T10:30:45",
      "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
    {
      "bookingId": "550e8400-e29b-41d4-a716-446655440001",
      "bookingReference": "EVT-B5D2E12F",
      "status": "CONFIRMED",
      "eventTitle": "Dar es Salaam Food Festival",
      "eventStartDateTime": "2025-12-20T11:00:00",
      "eventLocation": "Mlimani City, Sam Nujoma Road, Dar es Salaam",
      "totalTickets": 4,
      "checkedInTickets": 0,
      "total": 150.00,
      "bookedAt": "2025-12-10T14:20:30",
      "formResponseId": null
    }
  ]
}
```

**Success Response Fields**:
| Field | Description |
|---|---|
| `bookingId` | Unique booking UUID |
| `bookingReference` | Short readable code (e.g., `EVT-A3F4B21C`) |
| `status` | `CONFIRMED` or `CANCELLED` |
| `eventTitle` | Event title snapshotted at booking time |
| `eventStartDateTime` | Event start date/time — LocalDateTime (no timezone) |
| `eventLocation` | Venue address snapshotted at booking time |
| `totalTickets` | Total number of tickets in this booking |
| `checkedInTickets` | Number of tickets with at least one check-in |
| `total` | Total amount paid |
| `bookedAt` | When booking was created — LocalDateTime |
| `formResponseId` | UUID of the applicant form response — `null` if the event had no form |

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "NOT_FOUND",
  "message": "User not found",
  "action_time": "2026-05-23T10:30:45",
  "data": "User not found"
}
```

**Standard Error Types**:
- `401 UNAUTHORIZED`: Missing or expired Bearer token
- `404 NOT_FOUND`: Authenticated user account not found

---

## 3. Download Ticket PDF

**Purpose**: Download or preview the PDF ticket for a specific ticket instance belonging to the authenticated user.

**Endpoint**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/e-events/booking-orders/tickets/{ticketInstanceId}/pdf`

**Access Level**: 🔒 Protected (Ticket Owner)

**Authentication**: Bearer Token

**Request Headers**:
| Header          | Type   | Required | Description                             |
|-----------------|--------|----------|-----------------------------------------|
| `Authorization` | string | Yes      | Bearer token (format: `Bearer <token>`) |

**Path Parameters**:
| Parameter          | Type          | Required | Description                        | Validation         |
|--------------------|---------------|----------|------------------------------------|--------------------|
| `ticketInstanceId` | string (UUID) | Yes      | Unique ID of the ticket to download | Must be valid UUID |

**Query Parameters**:
| Parameter | Type   | Required | Description                                                                    | Default    |
|-----------|--------|----------|--------------------------------------------------------------------------------|------------|
| `mode`    | string | No       | `download` prompts a save dialog; `inline` opens the PDF in the browser tab   | `download` |

**Success Response**: Binary PDF file (not JSON)

**Success Response Headers**:
| Header                | Value                                                                   |
|-----------------------|-------------------------------------------------------------------------|
| `Content-Type`        | `application/pdf`                                                       |
| `Content-Disposition` | `attachment; filename="ticket-{series}.pdf"` or `inline; ...` by mode  |

**PDF Content Includes**:
- Event name, date, and venue
- Attendee name
- Ticket series and type
- QR code (RSA-signed JWT) for gate scanning
- Ticket validity period

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "You don't have permission to access this ticket",
  "action_time": "2026-05-23T10:30:45",
  "data": "You don't have permission to access this ticket"
}
```

**Standard Error Types**:
- `401 UNAUTHORIZED`: Missing or expired Bearer token
- `403 FORBIDDEN`: Ticket does not belong to the authenticated user
- `404 NOT_FOUND`: Ticket with given ID does not exist
- `500 INTERNAL_SERVER_ERROR`: PDF generation failed

---

## 4. Get Event Orders (Organizer)

**Purpose**: Retrieve a paginated list of all booking orders for a specific event with filtering by status, buyer type, and ticket type, plus aggregated revenue and order stats.

**Endpoint**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `{base_url}/e-events/booking-orders/event/{eventId}`

**Access Level**: 🔒 Protected (Event Organizer or Admin)

**Authentication**: Bearer Token

**Request Headers**:
| Header          | Type   | Required | Description                             |
|-----------------|--------|----------|-----------------------------------------|
| `Authorization` | string | Yes      | Bearer token (format: `Bearer <token>`) |

**Path Parameters**:
| Parameter | Type          | Required | Description       | Validation         |
|-----------|---------------|----------|-------------------|--------------------|
| `eventId` | string (UUID) | Yes      | Target event UUID | Must be valid UUID |

**Query Parameters**:
| Parameter      | Type          | Required | Description                                        | Validation                              | Default |
|----------------|---------------|----------|----------------------------------------------------|-----------------------------------------|---------|
| `status`       | string (enum) | No       | Filter by booking status                          | `CONFIRMED`, `CANCELLED`                | —       |
| `buyerType`    | string (enum) | No       | Filter by how the ticket was purchased            | `SYSTEM_USER`, `AT_DOOR`                | —       |
| `ticketTypeId` | string (UUID) | No       | Filter orders that include a specific ticket type | Must be valid UUID                      | —       |
| `search`       | string        | No       | Search by buyer name, email, or booking reference | —                                       | —       |
| `page`         | integer       | No       | Zero-based page index                             | Min: 0                                  | `0`     |
| `size`         | integer       | No       | Number of orders per page                         | Min: 1                                  | `20`    |

**Success Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Event orders retrieved",
  "action_time": "2026-05-23T10:30:45",
  "data": {
    "eventId": "770e8400-e29b-41d4-a716-446655440002",
    "eventTitle": "East African Tech Summit 2025",
    "appliedFilters": {
      "status": "CONFIRMED",
      "buyerType": null,
      "ticketTypeId": null,
      "ticketTypeName": null,
      "search": null
    },
    "stats": {
      "totalOrders": 142,
      "totalTicketsSold": 318,
      "totalRevenue": 47500.00,
      "confirmedOrders": 139,
      "cancelledOrders": 3,
      "refundedOrders": 0,
      "onlineOrders": 128,
      "atDoorOrders": 14
    },
    "orders": [
      {
        "bookingId": "550e8400-e29b-41d4-a716-446655440000",
        "bookingReference": "EVT-A3F4B21C",
        "status": "CONFIRMED",
        "bookedAt": "2025-12-11T10:30:45",
        "formResponseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "buyer": {
          "buyerName": "John Doe",
          "buyerEmail": "john@example.com",
          "buyerType": "SYSTEM_USER"
        },
        "totalTickets": 2,
        "checkedInCount": 1,
        "total": 200.00,
        "ticketTypes": [
          { "ticketTypeName": "VIP Pass", "quantity": 1 },
          { "ticketTypeName": "General Admission", "quantity": 1 }
        ]
      },
      {
        "bookingId": "550e8400-e29b-41d4-a716-446655440099",
        "bookingReference": "EVT-C7E1F34A",
        "status": "CONFIRMED",
        "bookedAt": "2025-12-10T18:15:00",
        "formResponseId": null,
        "buyer": {
          "buyerName": "Grace Njeri",
          "buyerEmail": null,
          "buyerType": "AT_DOOR"
        },
        "totalTickets": 1,
        "checkedInCount": 1,
        "total": 50.00,
        "ticketTypes": [
          { "ticketTypeName": "General Admission", "quantity": 1 }
        ]
      }
    ],
    "pagination": {
      "currentPage": 0,
      "pageSize": 20,
      "totalPages": 8,
      "totalElements": 142,
      "hasNext": true,
      "hasPrevious": false
    }
  }
}
```

**Success Response Fields**:
| Field                          | Description                                                                 |
|--------------------------------|-----------------------------------------------------------------------------|
| `eventId`                      | UUID of the queried event                                                   |
| `eventTitle`                   | Event title                                                                 |
| `appliedFilters`               | Echo of the active filter values (null = not filtered)                     |
| `appliedFilters.status`        | Active booking status filter                                                |
| `appliedFilters.buyerType`     | Active buyer type filter                                                    |
| `appliedFilters.ticketTypeId`  | Active ticket type UUID filter                                              |
| `appliedFilters.ticketTypeName`| Resolved name of the filtered ticket type                                  |
| `appliedFilters.search`        | Active search string                                                        |
| `stats`                        | Aggregated stats across ALL orders for the event (not just current page)   |
| `stats.totalOrders`            | Total booking count                                                         |
| `stats.totalTicketsSold`       | Total individual tickets across all bookings                                |
| `stats.totalRevenue`           | Sum of all order totals                                                     |
| `stats.confirmedOrders`        | Orders with status `CONFIRMED`                                              |
| `stats.cancelledOrders`        | Orders with status `CANCELLED`                                              |
| `stats.refundedOrders`         | Orders with status `REFUNDED`                                               |
| `stats.onlineOrders`           | Orders placed online (`SYSTEM_USER` buyer type)                            |
| `stats.atDoorOrders`           | Orders sold at the gate (`AT_DOOR` buyer type)                             |
| `orders`                       | Paginated list of order rows                                                |
| `orders[].bookingId`           | Booking UUID                                                                |
| `orders[].bookingReference`    | Short readable code (e.g., `EVT-A3F4B21C`)                                 |
| `orders[].status`              | Booking status string                                                       |
| `orders[].bookedAt`            | When booking was created — LocalDateTime                                    |
| `orders[].formResponseId`      | UUID of the buyer's applicant form response — `null` if no form or not yet submitted |
| `orders[].buyer.buyerName`     | Buyer's name                                                                |
| `orders[].buyer.buyerEmail`    | Buyer's email (null for AT_DOOR)                                            |
| `orders[].buyer.buyerType`     | `SYSTEM_USER` or `AT_DOOR`                                                 |
| `orders[].totalTickets`        | Number of tickets in this order                                             |
| `orders[].checkedInCount`      | Number of tickets checked in at least once                                  |
| `orders[].total`               | Order total amount                                                          |
| `orders[].ticketTypes`         | Breakdown of ticket types in this order                                     |
| `orders[].ticketTypes[].ticketTypeName` | Name of the ticket type                                          |
| `orders[].ticketTypes[].quantity`       | How many of this type are in the order                           |
| `pagination.currentPage`       | Zero-based current page index                                               |
| `pagination.pageSize`          | Number of items per page                                                    |
| `pagination.totalPages`        | Total number of pages                                                       |
| `pagination.totalElements`     | Total matching orders                                                       |
| `pagination.hasNext`           | `true` if a next page exists                                                |
| `pagination.hasPrevious`       | `true` if a previous page exists                                            |

**Error Response JSON Sample**:
```json
{
  "success": false,
  "httpStatus": "FORBIDDEN",
  "message": "Access denied: you are not the organizer of this event",
  "action_time": "2026-05-23T10:30:45",
  "data": "Access denied: you are not the organizer of this event"
}
```

**Standard Error Types**:
- `401 UNAUTHORIZED`: Missing or expired Bearer token
- `403 FORBIDDEN`: Authenticated user is not the event organizer or an admin
- `404 NOT_FOUND`: Event with given ID does not exist

---

## Ticket Series Generation

**Format**: `{TICKET_CODE}-{COUNTER}`

The ticket code is the first 5 characters of the ticket type name (uppercased, spaces removed). The counter is a zero-padded incrementing integer per ticket type, managed with pessimistic locking to prevent duplicates.

**Examples**:
- `VIP Pass` → `VIP-0001`, `VIP-0002`, `VIP-0003`
- `General Admission` → `GENER-0001`, `GENER-0042`
- `Early Bird` → `EARLY-0001`

The counter never resets — ticket series are globally unique per type forever.

---

## JWT-Signed QR Codes

Each ticket's `qrCode` field is a full RSA-signed JWT (2048-bit key pair generated when the event is published). The JWT payload contains:

- `ticketInstanceId`, `ticketTypeId`, `ticketTypeName`, `ticketSeries`
- `eventId`, `eventName`, `eventStartDateTime`
- `attendeeName`, `attendeeEmail`, `attendeePhone`, `attendanceMode`
- `bookingReference`
- `eventSchedules` — array of day objects for multi-day events:
```json
[
  {
    "dayName": "Day 1 - Opening Day",
    "startDateTime": "2025-12-15T09:00:00+03:00",
    "endDateTime": "2025-12-15T18:00:00+03:00",
    "description": "Conference Opening & Keynotes"
  }
]
```
- `validFrom`, `validUntil`

**Why JWT?** Scanners can verify the ticket offline using the public key without a database lookup. Any tampering breaks the signature. The embedded `eventSchedules` lets scanners enforce per-day check-in rules without an internet connection.

---

## Multi-Day Check-Ins

For multi-day events, each ticket can be checked in once per day. The `checkIns` array stores the full history. The `hasBeenCheckedIn`, `lastCheckedInAt`, `lastCheckedInBy`, `lastCheckInLocation`, and `lastCheckInDayName` convenience fields reflect the most recent entry.

- `dayName` in a check-in record must match one of the `eventSchedules` day names embedded in the JWT
- A ticket cannot be checked in twice on the same day
- Ticket status stays `ACTIVE` across days and transitions to `USED` after all expected check-ins

---

## Event Snapshots

Event details (title, venue, times, organizer contacts) are captured into the booking record at the moment of purchase and never change. This means:

- PDF tickets always show what the customer actually bought
- Bookings remain accurate even if the organizer later renames or reschedules the event
- The `event` and `organizer` blocks in `BookingOrderResponse` always reflect booking-time state

---

## Booking Creation Flow

Bookings are created automatically by `EventPaymentCompletedListener` after a successful payment — never through a direct API call. The flow:

1. Fetch checkout session, event, and ticket type from the database
2. Generate a unique booking reference (`EVT-{8-char-UUID}`)
3. Create ticket instances (one per quantity unit, tagged with attendee and buyer info)
4. Sign a JWT QR code for each ticket using the event's RSA private key
5. Snapshot current event and organizer details into the booking record
6. Save the booking and mark the checkout session as `COMPLETED`
7. Publish a `BookingCreatedEvent` to trigger email notifications to buyer, attendees, and organizer

The entire flow runs in a single transaction — no partial bookings are created.

---

## Access Control Rules

| Actor                         | Endpoint                        | Allowed? |
|-------------------------------|---------------------------------|----------|
| Booking owner (customer)      | `GET /booking-orders/{id}`      | Yes      |
| Event organizer               | `GET /booking-orders/{id}`      | Yes      |
| Admin (SUPER_ADMIN/STAFF_ADMIN)| `GET /booking-orders/{id}`     | Yes      |
| Other authenticated user      | `GET /booking-orders/{id}`      | No — 403 |
| Any authenticated user        | `GET /my-bookings`              | Yes (own bookings only) |
| Ticket owner                  | `GET /tickets/{id}/pdf`         | Yes      |
| Other authenticated user      | `GET /tickets/{id}/pdf`         | No — 403 |
| Event organizer               | `GET /booking-orders/event/{id}`| Yes      |
| Admin                         | `GET /booking-orders/event/{id}`| Yes      |
| Non-organizer user            | `GET /booking-orders/event/{id}`| No — 403 |

---

## Booking and Ticket Status

**Booking Status**:
| Status      | Description                       |
|-------------|-----------------------------------|
| `CONFIRMED` | Active booking, tickets are valid |
| `CANCELLED` | Booking cancelled (placeholder — cancellation logic not yet implemented) |

**Ticket Instance Status**:
| Status      | Description                                                         |
|-------------|---------------------------------------------------------------------|
| `ACTIVE`    | Ticket ready for use; can be scanned                               |
| `USED`      | Ticket has been checked in; still scannable for additional days     |
| `CANCELLED` | Ticket cancelled; cannot be used for entry                         |

---

## Date/Time Formats

| Type            | Format                        | Example                        | Used For                                    |
|-----------------|-------------------------------|--------------------------------|---------------------------------------------|
| `LocalDateTime` | `YYYY-MM-DDTHH:mm:ss`         | `2025-12-15T09:00:00`          | `bookedAt`, `cancelledAt`, event snapshots  |
| `ZonedDateTime` | `YYYY-MM-DDTHH:mm:ss±HH:mm`   | `2025-12-15T09:00:00+03:00`    | `validFrom`, `validUntil`, `checkInTime`    |

`ZonedDateTime` fields carry the event's local timezone offset and display correctly regardless of the viewer's locale.

---

## Endpoint Summary

| # | Endpoint | Access |
|---|---|---|
| 1 | `GET /e-events/booking-orders/{bookingId}` | Owner / Organizer / Admin |
| 2 | `GET /e-events/booking-orders/my-bookings` | Any authenticated user |
| 3 | `GET /e-events/booking-orders/tickets/{ticketInstanceId}/pdf` | Ticket owner |
| 4 | `GET /e-events/booking-orders/event/{eventId}` | Event organizer / Admin |