Skip to main content

Event Checkout & Payment API

Author: Josh Simon,S. Sakweli, Backend Lead Backend Team
Last Updated: 2025-12-11
02-19 Version: v1.0

Base URL: https://api.nexgate.com/nextgate.co.tz/api/v1

Short Description: The EventNextGate Checkout & Payment API provides comprehensive functionality for purchasing event tickets in the Nexgate platform. This API enables users to create checkout sessions with automatic ticket holds (15 minutes), buy tickets for themselves and other attendees, process payments through wallet integration, and handlemanages the complete ticket purchasepurchasing workflowlifecycle withon escrowthe protection.NextGate Theevent systemplatform. includesIt sophisticatedsupports validationtwo distinct checkout flows: online checkout for purchaseregistered limits, duplicate identity checking,attendees and automaticat-door ticket releasesales onfor sessionevent expiry.organizers and scanner devices. This API should be used by frontend clients, mobile applications, and authorized scanner hardware integrations.

Hints:

  • All monetary values are in TicketTZS Hold(Tanzanian SystemShilling): Ticketsunless automaticallyotherwise heldstated
  • Checkout sessions expire after 15 minutes for 15 minutes duringonline checkout and 1 hour for at-door sales — always check expiresAt before processing payment
  • Multi-AttendeeBearer Support:token Buyauthentication ticketsis required for selfall + unlimited other attendees (friends/family)endpoints
  • IdentityFor Tracking:FREE Preventstickets, limitpayment bypassis usingauto-processed email/phoneimmediately acrossupon attendeessession creation — no separate payment step is needed
  • WalletFor Payment:DONATION Integratedtickets, walletmaximum system1 with balance validation
  • Escrow Protection: Payments held in escrow until event completion
  • Session Expiry: 15-minute checkout window, auto-release tickets on expiry
  • Purchase Limits: Enforces min/maxticket per order and maxcannot perbe userpurchased withfor identityother checksattendees
  • FreeTicket Tickets:holds Supportare forapplied freeimmediately eventson (pricesession =creation; 0.00)cancelling the session releases the hold
  • PaymentScanner Retry:devices Upmust tohave 5the retrySELL_TICKETS attemptspermission for failed payments
  • Automatic Status: Sessions auto-expire, tickets auto-release
  • Booking Creation: Automatic booking orderassigned and QRa codevalid generationdeviceFingerprint afterregistered paymentbefore calling the scanner sale endpoint

Standard Response Format

All API responses follow a consistent structure using our Globe Response Builder pattern:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-12-11T10:09-23T10:30:45",
  "data": {
    // Actual response data goes here
  }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-12-11T10:09-23T10:30:45",
  "data": "Error description"
}

Standard Response Fields

FieldTypeDescription
successbooleanAlways true for successful operations, false for errors
httpStatusstringHTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.)
messagestringHuman-readable message describing the operation result
action_timestringISO 8601 timestamp of when the response was generated
dataobject/stringResponse payload for success, error details for failures

HTTP Method Badge Standards

  • GET - GETGreen: -Safe, Greenread-only (Read operations)operations
  • POST - POSTBlue: -Create Bluenew (Create/Actionresources
  • operations)
  • PUT — Yellow: Update/replace entire resource
  • PATCH — Orange: Partial updates
  • DELETE — Red: Remove resources

EventCheckoutResponseUser StructureJourney Flows

Flow A — Online Checkout (Registered Attendee)

  [ Attendee browses event & selects ticket ]
                      |
                      v
  [ POST /checkout  — Create checkout session ]
                      |
          ............|............
          .                       .
          v                       v
  [ FREE / DONATION ticket ]   [ PAID ticket ]
          |                       |
          v                       |
  [ Auto-processed immediately ]  |
          |                       v
          |           [ GET /checkout/{sessionId} ]
          |           [ — Review session details  ]
          |                       |
          |                       v
          |           [ POST /checkout/{sessionId}/payment ]
          |           [ — Deduct from wallet, create escrow ]
          |                       |
          `----------->-----------'
                      |
                      v
  [ Booking order created asynchronously ]
  [ QR codes generated & sent to attendees ]
                      |
                      v
  [ Session status = COMPLETED ]


  At any point before payment:
  [ POST /checkout/{sessionId}/cancel ]
  [ — Releases ticket hold            ]

Flow B — At-Door Sale via Scanner Device

  [ Customer arrives at event venue ]
                      |
                      v
  [ Scanner device sends sale request ]
  [ POST /checkout/sell-at-door-ticket/scanner ]
                      |
          ............|............
          .                       .
          v                       v
  [ Validate scanner ID ]   [ Validate device fingerprint ]
  [ & permissions       ]   [ & SELL_TICKETS permission   ]
          .                       .
          `..........v............'
                      |
                      v
  [ Validate ticket type belongs to event ]
  [ Check sales channel = AT_DOOR or BOTH ]
                      |
                      v
  [ Cash payment processed (no wallet deduction) ]
  [ Booking order created immediately            ]
                      |
                      v
  [ QR codes returned in response ]
  [ immediateCheckIn = true → ticket marked as checked-in ]

Flow C — At-Door Sale via Organizer

  [ Organizer is authenticated & accesses event ]
                      |
                      v
  [ POST /checkout/{eventId}/organizer ]
  [ — Organizer sells ticket at their counter ]
                      |
                      v
  [ System verifies organizer owns the event ]
                      |
                      v
  [ Validate ticket type & attendee count ]
                      |
                      v
  [ Cash payment recorded (NEUTRAL transaction) ]
  [ Booking order created                       ]
                      |
                      v
  [ QR codes returned in response ]
  [ immediateCheckIn flag respected ]

Endpoints


1. Create Checkout Session

ThisPurpose: isCreates a new online checkout session for a registered attendee purchasing event tickets, holding the comprehensiverequested responsequantity structureand returnedinitializing bythe payment intent.

Endpoint: POST /api/v1/e-events/checkout

endpoints:

Access Level: 🔒 Protected (Requires valid Bearer token — authenticated attendee)

Authentication: Bearer Token

Request Headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token of the authenticated attendee
Content-TypestringYesMust be application/json

Request JSON Sample:

{
  "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
  "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
  "ticketsForMe": 2,
  "donationAmount": null,
  "otherAttendees": [
    {
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "phone": "+255712345678",
      "quantity": 1
    }
  ],
  "sendTicketsToAttendees": true,
  "paymentMethodId": null
}

Request Body Parameters:

ParameterTypeRequiredDescriptionValidation
eventIdUUIDYesThe ID of the event being bookedMust be a valid published event that has not yet started
ticketTypeIdUUIDYesThe ID of the ticket type being purchasedMust belong to the specified event and be active and on sale
ticketsForMeintegerYesNumber of tickets for the buyer themselves. Use 0 if the buyer is not attendingMin: 0
donationAmountdecimalNoDonation amount in TZS. Only applicable for DONATION type ticketsOnly used when ticket pricing type is DONATION
otherAttendeesarrayNoList of other attendees to purchase tickets forEach attendee must have valid name, email, and Tanzanian phone number
otherAttendees[].namestringYes (if array provided)Full name of the attendeeMin: 2, Max: 100 characters
otherAttendees[].emailstringYes (if array provided)Email address of the attendeeValid email format; no duplicate emails in the array
otherAttendees[].phonestringYes (if array provided)Phone number of the attendeeMust match Tanzania format: +255[67]XXXXXXXX
otherAttendees[].quantityintegerYes (if array provided)Number of tickets for this attendeeMin: 1
sendTicketsToAttendeesbooleanNoIf true, QR tickets are sent to each attendee's email. If false, all QR codes are sent to the buyer onlyDefault: true
paymentMethodIdUUIDNoID of a saved payment method. Defaults to wallet if not providedOptional

Business Rules:

  • Total quantity = ticketsForMe + sum of all otherAttendees[].quantity — must be at least 1
  • DONATION tickets: maximum 1 per order, cannot be bought for other attendees, online only
  • AT_DOOR_ONLY tickets cannot be purchased through this endpoint
  • Wallet balance is validated upfront for PAID tickets
  • If the event has a required questionnaire set to BEFORE_CHECKOUT, it must be submitted before calling this endpoint
  • FREE tickets are auto-processed immediately — the response will already show PAYMENT_COMPLETED

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "CREATED",
  "message": "Checkout session created successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": {
    "sessionId": "550e8400-e29b-41d4-a716-446655440000"a1b2c3d4-0000-0000-0000-000000000001",
    "status": "PENDING_PAYMENT",
    "customerId": "660e8400-e29b-41d4-a716-446655440001"f1e2d3c4-0000-0000-0000-000000000010",
    "customerUserName": "johndoe"john_doe",
    "eventId": "770e8400-e29b-41d4-a716-446655440002"b3f1a2c4-1234-5678-abcd-000000000001",
    "eventTitle": "EastKilimanjaro AfricanJazz Tech SummitNight 2025",
    "ticketDetails": {
      "ticketTypeId": "880e8400-e29b-41d4-a716-446655440003"d4e5f6a7-1234-5678-abcd-000000000002",
      "ticketTypeName": "Early Bird VIP",
      "unitPrice": 150.50000.00,
      "ticketsForBuyer": 2,
      "otherAttendees": [
        {
          "name": "Jane Doe",
          "email": "jane@example.jane.doe@example.com",
          "phone": "+255712345678",
        "quantity": 2
      },
      {
        "name": "Bob Smith",
        "email": "bob@example.com",
        "phone": "+255723456789",
          "quantity": 1
        }
      ],
      "sendTicketsToAttendees": true,
      "totalQuantity": 5,3,
      "subtotal": 750.150000.00
    },
    "pricing": {
      "subtotal": 750.150000.00,
      "total": 750.150000.00
    },
    "paymentIntent": {
      "provider": "WALLET",
      "clientSecret": null,
      "paymentMethods": ["WALLET"],
      "status": "PENDING"
    },
    "ticketsHeld": true,
    "ticketHoldExpiresAt": "2025-12-11T10:09-23T10:45:45",
    "expiresAt": "2025-12-11T10:09-23T10:45:45",
    "createdAt": "2025-12-11T10:09-23T10:30:45",
    "updatedAt": "2025-12-11T10:09-23T10:30:45",
    "completedAt": null,
    "createdBookingOrderId": null,
    "isExpired": false,
    "canRetryPayment": false
  }
}

Success Response Field Descriptions

Fields:

Current UsernameofHuman-readableIDofPriceNumber SubtotalFinal TimestampTimestampPopulatedComputed
FieldType Description
sessionIdstring (UUID) Unique identifier for this checkout sessionsession. identifierUse it for all subsequent actions
status enum Sessionsession status:status. PENDING_PAYMENT,Values: PAYMENT_COMPLETED,PENDING_PAYMENT, COMPLETED,PAYMENT_COMPLETED, PAYMENT_FAILED,COMPLETED, EXPIRED,PAYMENT_FAILED, CANCELLED, EXPIRED
customerId string (UUID)Buyer's accountAccount ID of the buyer
customerUserName string Buyer'sof usernamethe buyer
eventId stringID (UUID) Eventthe event being purchasedbooked
eventTitle string Eventevent title for display
ticketDetailsticketDetails.ticketTypeId object Completeof the chosen ticket purchase detailstype
ticketDetails.ticketTypeIdticketTypeName stringName (UUID) Ticketthe ticket type being(e.g., purchasedVIP, Regular)
ticketDetails.ticketTypeNameunitPrice string Ticketper typesingle nameticket in TZS
ticketDetails.unitPriceticketsForBuyer decimal Priceof pertickets ticketallocated to the buyer
ticketDetails.ticketsForBuyerintegerTickets for the buyer themselves
ticketDetails.otherAttendeesarray List of other attendees (friends/family)and their ticket quantities
ticketDetails.sendTicketsToAttendeesboolean Whether QR codes will be emailed to emaileach attendee
ticketDetails.totalQuantityTotal tickets toacross otherbuyer and all attendees
ticketDetails.totalQuantityintegersubtotal Total ticketsprice inbefore orderany discounts (buyer + others)TZS)
ticketDetails.pricing.subtotal decimal Calculated:amount unitPricein × totalQuantityTZS
pricingpricing.total object Pricingpayable breakdownamount in TZS
pricing.subtotaldecimalSubtotal before any fees
pricing.totaldecimalFinal total amount
paymentIntentobjectPayment configuration
paymentIntent.providerstring Payment provider (WALLET)e.g., WALLET)
paymentIntent.paymentMethodsarray Available payment methods for this session
paymentIntent.statusstring Payment intent status (PENDING, COMPLETED)
ticketsHeldboolean Whether the tickets are currently being held in reserve
ticketHoldExpiresAt string Whenwhen heldthe ticketsticket willhold be released (LocalDateTime)expires
expiresAt string Whenwhen checkoutthe entire session expires (LocalDateTime)
createdAtcreatedBookingOrderId string Sessionafter creationpayment timestampis (LocalDateTime)completed — the resulting booking order ID
updatedAtisExpired string Lastflag updateindicating timestampwhether (LocalDateTime)the session has passed its expiry time
completedAtcanRetryPayment stringWhether the session allows another payment attempt (true if status is PAYMENT_FAILED and attempts < 5)

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Please complete the event questionnaire before purchasing tickets",
  "action_time": "2025-09-23T10:30:45",
  "data": "Please complete the event questionnaire before purchasing tickets"
}

Possible Errors:

expired,Event
HTTP StatusScenario
400 BAD_REQUEST CompletionEvent timestamp (null ifis not completed)published, event has already started, ticket not on sale, insufficient wallet balance, AT_DOOR_ONLY ticket purchased online, DONATION rules violated, questionnaire not submitted
createdBookingOrderId401 UNAUTHORIZED stringMissing, (UUID) Bookingor orderinvalid IDBearer (set after successful payment)token
isExpired404 NOT_FOUND boolean Calculated:or whetherticket sessiontype hasnot expiredfound
canRetryPayment422 UNPROCESSABLE_ENTITY booleanValidation failed on request fields (missing eventId, invalid email format, invalid phone format, etc.)
500 INTERNAL_SERVER_ERROR Calculated:Unexpected whetherserver payment can be retriederror

Endpoints

2.

1. CreateGet Checkout Session

Purpose: InitializeRetrieves the current state of an existing checkout session withbelonging ticketto holdsthe forauthenticated event purchaseuser.

Endpoint: POSTGET {base_url}/api/v1/e-events/checkoutcheckout/{sessionId}

Access Level: 🔒 Protected (RequiresAuthenticated Bearerattendee Token Authentication)only the session owner can retrieve it)

Authentication: Bearer Token

Request Headers:

authenticated
Header Type Required Description
Authorization string Yes Bearer token (format:of Bearerthe <token>)
Content-TypestringYesMust be application/jsonattendee

Request JSON Sample (Buying for Self + 2 Friends):

{
  "eventId": "770e8400-e29b-41d4-a716-446655440002",
  "ticketTypeId": "880e8400-e29b-41d4-a716-446655440003",
  "ticketsForMe": 2,
  "otherAttendees": [
    {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "phone": "+255712345678",
      "quantity": 2
    },
    {
      "name": "Bob Smith",
      "email": "bob@example.com",
      "phone": "+255723456789",
      "quantity": 1
    }
  ],
  "sendTicketsToAttendees": true,
  "paymentMethodId": null
}

Request JSON Sample (Buying Only for Friends):

{
  "eventId": "770e8400-e29b-41d4-a716-446655440002",
  "ticketTypeId": "880e8400-e29b-41d4-a716-446655440003",
  "ticketsForMe": 0,
  "otherAttendees": [
    {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "phone": "+255712345678",
      "quantity": 3
    }
  ],
  "sendTicketsToAttendees": true
}

Request BodyPath Parameters:

Basic Parameters

the
Parameter Type Required Description Validation
eventIdsessionId string (UUID)UUID Yes EventThe ID of the checkout session to purchase tickets forretrieve Must be PUBLISHEDa andvalid notUUID past
ticketTypeIdstring (UUID)YesTicket typebelonging to purchase Mustauthenticated be ACTIVE and on sale
ticketsForMeintegerYesTickets for buyerMin: 0 (can buy only for others)
paymentMethodIdstring (UUID)NoPayment methodDefaults to walletuser

Multi-Attendee

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Checkout session retrieved successfully",
  "action_time": "2025-09-23T10:35:00",
  "data": {
    "sessionId": "a1b2c3d4-0000-0000-0000-000000000001",
    "status": "PENDING_PAYMENT",
    "customerId": "f1e2d3c4-0000-0000-0000-000000000010",
    "customerUserName": "john_doe",
    "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
    "eventTitle": "Kilimanjaro Jazz Night 2025",
    "ticketDetails": {
      "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
      "ticketTypeName": "VIP",
      "unitPrice": 50000.00,
      "ticketsForBuyer": 2,
      "otherAttendees": [
        {
          "name": "Jane Doe",
          "email": "jane.doe@example.com",
          "phone": "+255712345678",
          "quantity": 1
        }
      ],
      "sendTicketsToAttendees": true,
      "totalQuantity": 3,
      "subtotal": 150000.00
    },
    "pricing": {
      "subtotal": 150000.00,
      "total": 150000.00
    },
    "paymentIntent": {
      "provider": "WALLET",
      "clientSecret": null,
      "paymentMethods": ["WALLET"],
      "status": "PENDING"
    },
    "paymentAttempts": [],
    "ticketsHeld": true,
    "ticketHoldExpiresAt": "2025-09-23T10:45:45",
    "expiresAt": "2025-09-23T10:45:45",
    "createdAt": "2025-09-23T10:30:45",
    "updatedAt": "2025-09-23T10:30:45",
    "completedAt": null,
    "createdBookingOrderId": null,
    "isExpired": false,
    "canRetryPayment": false
  }
}

Success Response Fields:

FieldDescription
sessionIdUnique session identifier
statusCurrent session status
paymentAttemptsList of all payment attempts made on this session, including failures
paymentAttempts[].attemptNumberSequential attempt number (1-indexed)
paymentAttempts[].paymentMethodPayment method used for this attempt
paymentAttempts[].statusResult of the attempt: SUCCESS or FAILED
paymentAttempts[].errorMessageFailure reason if the attempt failed
paymentAttempts[].attemptedAtTimestamp of the attempt
paymentAttempts[].transactionIdExternal or internal transaction reference
All other fieldsSame as Create Checkout Session response

Possible Errors:

HTTP StatusScenario
401 UNAUTHORIZEDMissing, expired, or invalid Bearer token
404 NOT_FOUNDSession not found or does not belong to the authenticated user
500 INTERNAL_SERVER_ERRORUnexpected server error

3. Process Payment

Purpose: Initiates wallet payment for a pending checkout session, creating an escrow account and triggering asynchronous booking order creation upon success.

Endpoint: POST /api/v1/e-events/checkout/{sessionId}/payment

Access Level: 🔒 Protected (Session owner only)

Authentication: Bearer Token

Request Headers:

HeaderTypeRequiredDescription
AuthorizationstringYesBearer token of the authenticated attendee

Path Parameters:

Parameter Type Required Description Validation
otherAttendeessessionId arrayNoList of other attendeesCan be empty/null
sendTicketsToAttendeesbooleanNoEmail tickets to attendeesDefault: true

Other Attendee Object Parameters

UUID;sessionmustbeinPENDING_PAYMENTand
ParameterTypeRequiredDescriptionValidation
namestringUUID Yes AttendeeThe fullID nameof the checkout session to pay for Min:Must 2,be Max:a 100valid characters
email string Yes Attendeestatus email Validnot email format
phonestringYesAttendee phoneTanzania format: +255[67]xxxxxxxx
quantityintegerYesTickets for this attendeeMin: 1expired

SuccessBusiness Response: Returns complete EventCheckoutResponse structure

Success Response Message: "Checkout session created successfully"

HTTP Status Code: 201 CREATED

Behavior:

  1. Validation: Validates event, ticket, quantities, attendee data, wallet balance
  2. Ticket Hold: Immediately holds tickets (increments quantitySold temporarily)
  3. Session Creation: Creates checkout session with 15-minute expiry
  4. Hold Expiry: Sets ticketHoldExpiresAt to current time + 15 minutes
  5. Payment Intent: Creates payment intent (default: WALLET provider)
  6. Auto-Expiry: Session auto-expires after 15 minutes if payment not completed

Key ValidationsRules:

  • EventSession must be PUBLISHED and not in the past
  • Ticket must be ACTIVE and currently on sale
  • Total quantity must be at least 1 (ticketsForMe + sum of other attendees)
  • Must meet minQuantityPerOrder (if set)
  • Must not exceed maxQuantityPerOrder (if set)
  • Must not exceed maxQuantityPerUser (checked across all attendee identities)
  • Sufficient tickets available
  • Sufficient wallet balance
  • No duplicate attendee emails
  • Valid email/phone formats for all attendees

Standard Error Types:

  • 400 BAD_REQUESTPENDING_PAYMENT: Validation errors, insufficient tickets/balance, purchase limit exceeded
  • 401 UNAUTHORIZED: Authentication issues
  • 404 NOT_FOUND: Event/ticket not found, user not authenticated
  • 422 UNPROCESSABLE_ENTITY: Validation errors
  • 500 INTERNAL_SERVER_ERROR: Server errors

Error Response Examples:

Event Not Available (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Event is not available for booking",
  "action_time": "2025-12-11T10:30:45",
  "data": "Event is not available for booking"
}

Past Event (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot book tickets for past events",
  "action_time": "2025-12-11T10:30:45",
  "data": "Cannot book tickets for past events"
}

Insufficient Tickets (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Only 3 tickets available",
  "action_time": "2025-12-11T10:30:45",
  "data": "Only 3 tickets available"
}

Below Minimum Order (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Minimum 5 tickets per order",
  "action_time": "2025-12-11T10:30:45",
  "data": "Minimum 5 tickets per order"
}

Exceeds Maximum Per Order (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Maximum 10 tickets per order",
  "action_time": "2025-12-11T10:30:45",
  "data": "Maximum 10 tickets per order"
}

Max Per User Exceeded (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Maximum 5 tickets per user for 'Early Bird VIP'. The email/phone 'j***@example.com' has already purchased 3 ticket(s). This order would add 3 more ticket(s), exceeding the limit.",
  "action_time": "2025-12-11T10:30:45",
  "data": "Maximum 5 tickets per user for 'Early Bird VIP'. The email/phone 'j***@example.com' has already purchased 3 ticket(s). This order would add 3 more ticket(s), exceeding the limit."
}

Insufficient Wallet Balance (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Insufficient wallet balance. Required: 750.00 TZS, Available: 500.00 TZS",
  "action_time": "2025-12-11T10:30:45",
  "data": "Insufficient wallet balance. Required: 750.00 TZS, Available: 500.00 TZS"
}

Invalid Phone Format (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Invalid phone format. Must be Tanzania format (+255...)",
  "action_time": "2025-12-11T10:30:45",
  "data": "Invalid phone format. Must be Tanzania format (+255...)"
}

Duplicate Attendee Email (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Duplicate attendee email: jane@example.com",
  "action_time": "2025-12-11T10:30:45",
  "data": "Duplicate attendee email: jane@example.com"
}

2. Get Checkout Session

Purpose: Retrieve checkout session details by ID

Endpoint: GET {base_url}/e-events/checkout/{sessionId}

Access Level: 🔒 Protected (Session Owner Only)

Authentication: Bearer Token

Path Parameters:

ParameterTypeRequiredDescription
sessionIdstring (UUID)YesCheckout session ID

Success Response: Returns complete EventCheckoutResponse structure

Success Response Message: "Checkout session retrieved successfully"

Behavior:

  • Only session owner (customer) can retrieve
  • Returns all session details including ticket holds, pricing, payment status
  • CalculatesSession isExpiredmust andnot be expired
  • Cannot call this on FREE tickets (auto-processed on creation)
  • Wallet must have sufficient balance at time of payment call
  • Maximum 5 payment attempts per session; after that canRetryPayment dynamicallybecomes false
  • On success, escrow is created, session moves to PAYMENT_COMPLETED, and a booking order is created asynchronously

Standard Error Types:

  • 401 UNAUTHORIZED: Authentication issues
  • 404 NOT_FOUND: Session not found or not owned by user

3. Process Payment

Purpose: Process payment for the checkout session using wallet

Endpoint: POST {base_url}/e-events/checkout/{sessionId}/payment

Access Level: 🔒 Protected (Session Owner Only)

Authentication: Bearer Token

Path Parameters:

ParameterTypeRequiredDescription
sessionIdstring (UUID)YesCheckout session ID

Success Response: Returns PaymentResponse structure

Success Response Message: "Payment processed successfully"

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Payment processedcompleted successfully"successfully. Your booking is being processed.",
  "action_time": "2025-12-11T10:35:09-23T10:38:00",
  "data": {
    "paymentId"success": "990e8400-e29b-41d4-a716-446655440010",true,
    "status": "COMPLETED"SUCCESS",
    "amount"message": 750."Payment completed successfully. Your booking is being processed.",
    "checkoutSessionId": "a1b2c3d4-0000-0000-0000-000000000001",
    "escrowId": "e9f8a7b6-0000-0000-0000-000000000020",
    "escrowNumber": "ESC-2025-000001",
    "orderId": null,
    "orderNumber": null,
    "paymentMethod": "WALLET",
    "amountPaid": 150000.00,
    "platformFee": 7500.00,
    "sellerAmount": 142500.00,
    "currency": "TZS",
    "escrowId": "aa0e8400-e29b-41d4-a716-446655440011",
    "escrowNumber": "ESC-20251211-0001",
    "transactionReference": "TXN-20251211-0001"
  }
}

BehaviorSuccess Response Fields:

  1. Session
  2. Validation:Checks notexpired
  3. Wallet
  4. Deduction: Createsescrowaccount UpdatessessiontoPAYMENT_COMPLETEDPaymentCompletedEvent
  5. Booking
  6. Creation: Listener automatically creates booking order
  7. QR Generation: Generates QR codes for all tickets
  8. Email Sending: Emails tickets to buyer and optionally to other attendees
  9. Final Status: Session marked as COMPLETED after booking creation
  10. FieldDescription
    statusPayment result status: SUCCESS, FAILED, or PENDING
    checkoutSessionIdThe checkout session this payment belongs to
    escrowIdID of the escrow account holding the funds
    escrowNumberHuman-readable escrow reference number (format: ESC-YYYY-NNNNNN)
    orderIdBooking order ID — may be null immediately after payment as booking is PENDING_PAYMENTcreated andasynchronously
    orderNumber Human-readable Deductsorder reference — null until order is created
    paymentMethodPayment method used: WALLET
    amountPaidTotal amount deducted from buyer's wallet
  11. Escrowin Creation:TZS
  12. platformFeePlatform fee amount (5% of total) in TZS
    sellerAmountAmount that will be released to hold funds untilthe event completionorganizer
  13. Statusin Update:TZS
  14. currency Currency
  15. Eventcode, Trigger:always PublishesTZS
  16. ForPossible Free Tickets (price = 0.00):

    • No wallet deduction
    • No escrow creation
    • Direct booking creation
    • Still generates QR codes and sends emails

    Payment RetryErrors:

    • Allowed
    • ifstatusPAYMENT_FAILED
    • Maximum
    • 5retryattempts
    • Session must not be expired
    • Tracks all attempts in paymentAttempts array
    • Standard Error Types:

      Invalid session status, expired session
    • 401 UNAUTHORIZED: Authentication issues
    • 404 NOT_FOUND: Session not found
    • 422 UNPROCESSABLE_ENTITY: Payment processing errors
    • Error Response Examples:

      Session Not Pending Payment (400):

      {
        "success": false,
        "httpStatus": "BAD_REQUEST",
        "message": "

      Session

      "Checkoutsessionhas expired", "action_time": "2025-12-11T10:50:00", "data": "Checkout session has expired" }
      HTTP =Status Scenario
      400 BAD_REQUEST: Session is not pendingin payment:PENDING_PAYMENT COMPLETED",status, "action_time": "2025-12-11T10:35:00", "data": "Sessionsession is expired, or attempting payment on a FREE ticket
      401 UNAUTHORIZEDMissing, expired, or invalid Bearer token
      404 NOT_FOUNDSession not pendingfound payment:or COMPLETED"does }not belong to the authenticated user
      500 INTERNAL_SERVER_ERROR Payment Expiredprocessing (400):

      error,
      {insufficient "success":balance false,at "httpStatus":time "BAD_REQUEST",of "message":processing

      4. Cancel Checkout Session

      Purpose: CancelCancels an active checkout session and releasereleases any held tickets back to available inventory.

      Endpoint: POST {base_url}/api/v1/e-events/checkout/{sessionId}/cancel

      Access Level: 🔒 Protected (Session Ownerowner Only)only)

      Authentication: Bearer Token

      Request Headers:

      HeaderTypeRequiredDescription
      AuthorizationstringYesBearer token of the authenticated attendee

      Path Parameters:

      Parameter Type Required Description Validation
      sessionId string (UUID)UUID Yes CheckoutThe ID of the checkout session IDto cancelMust be a valid UUID belonging to the authenticated user

      SuccessBusiness Response MessageRules:

      "Checkout
      • Cannot cancel a session cancelledthat successfully"

        is in COMPLETED status
      • Cannot cancel a session that is in PAYMENT_COMPLETED status (payment already processed)
      • Cancelling releases the ticket hold immediately
      • Cancellation does not trigger a refund — refunds are handled separately through the escrow system

      Success Response JSON Sample:

      {
        "success": true,
        "httpStatus": "OK",
        "message": "Checkout session cancelled successfully",
        "action_time": "2025-12-11T10:09-23T10:40:00",
        "data": null
      }
      

      BehaviorPossible Errors:

      1. HTTP StatusScenario
        400 BAD_REQUESTSession is already completed or payment has been completed
        401 UNAUTHORIZEDMissing, expired, or invalid Bearer token
        404 NOT_FOUNDSession not found or does not belong to the authenticated user
        500 INTERNAL_SERVER_ERRORUnexpected server error

        5. Scanner — Sell Ticket at Door

        ValidationPurpose: ChecksAllows sessionan authorized scanner device to sell tickets at the venue entrance, processing a cash payment and optionally checking in the attendee immediately.

        Endpoint: POST /api/v1/e-events/checkout/sell-at-door-ticket/scanner

        Access Level: 🔒 Protected (Scanner device authentication via scannerId + deviceFingerprint)

        Authentication: Bearer Token (of scanner's linked account) + Scanner credentials in body

        Request Headers:

        HeaderTypeRequiredDescription
        AuthorizationstringYesBearer token linked to the scanner's registered account
        Content-TypestringYesMust be application/json

        Request JSON Sample:

        {
          "scannerId": "SCN-2025-001",
          "deviceFingerprint": "a3f1b2c4d5e6f7890abc1234def56789",
          "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
          "quantity": 2,
          "attendees": [
            {
              "fullName": "John Mbeki",
              "email": "john.mbeki@example.com",
              "phoneNumber": "+255789123456"
            },
            {
              "fullName": "Amina Hassan",
              "email": "amina.hassan@example.com",
              "phoneNumber": "+255754321987"
            }
          ],
          "immediateCheckIn": true
        }
        

        Request Body Parameters:

        held
      2. Hold
      3. ParameterTypeRequiredDescriptionValidation
        scannerIdstringYesUnique identifier of the registered scanner deviceMust match a registered, active scanner with SELL_TICKETS permission
        deviceFingerprintstringYesHardware fingerprint of the scanner deviceMust match the fingerprint registered for this scanner
        ticketTypeIdUUIDYesID of the ticket type to sellMust belong to the event this scanner is assigned to; must not COMPLETEDbe orONLINE_ONLY; PAYMENT_COMPLETEDmust
      4. Ticketbe Release:on Releasessale
      5. quantityintegerYesTotal number of tickets (decrements quantitySold)
      6. Status Update: Changes status to CANCELLED
      7. sell
        Min: Flag:1; Setsmust ticketsHeldequal =the falsenumber of attendees in the attendees array
        attendeesarrayYesList of attendee details, one entry per ticketMin: 1 entry; count must match quantity
        attendees[].fullNamestringNoFull name of the attendeeOptional — if blank, a generated name like ATTENDEE-XXXX is assigned
        attendees[].emailstringNoEmail address of the attendeeValid email format if provided
        attendees[].phoneNumberstringNoPhone number of the attendeeOptional
        immediateCheckInbooleanYesIf true, the ticket is marked as checked-in immediately upon saleRequired

        CannotBusiness CancelRules:

        • COMPLETEDThe sessionsscanner (bookingmust created)be active, not expired, and have the SELL_TICKETS permission
        • PAYMENT_COMPLETEDThe sessionsdeviceFingerprint (paymentmust alreadyexactly processed)match the registered fingerprint for this scanner
        • The number of attendees must equal quantity — a 1-to-1 mapping is enforced
        • Payment method is always CASH — no wallet deduction occurs
        • The ticket type must not be ONLINE_ONLY
        • immediateCheckIn = true automatically marks each generated ticket as checked-in

        StandardSuccess ErrorResponse TypesJSON Sample:

        {
          "success": true,
          "httpStatus": "CREATED",
          "message": "Tickets sold successfully at door",
          "action_time": "2025-09-23T18:00:00",
          "data": {
            "bookingId": "c9d8e7f6-0000-0000-0000-000000000030",
            "bookingReference": "BK-2025-000042",
            "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
            "eventName": "Kilimanjaro Jazz Night 2025",
            "tickets": [
              {
                "ticketInstanceId": "aa11bb22-0000-0000-0000-000000000050",
                "ticketSeries": "VIP-0042-A",
                "ticketTypeName": "VIP",
                "attendeeName": "John Mbeki",
                "attendeeEmail": "john.mbeki@example.com",
                "checkedIn": true,
                "checkInTime": "2025-09-23T18:00:05Z",
                "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
              },
              {
                "ticketInstanceId": "cc33dd44-0000-0000-0000-000000000051",
                "ticketSeries": "VIP-0042-B",
                "ticketTypeName": "VIP",
                "attendeeName": "Amina Hassan",
                "attendeeEmail": "amina.hassan@example.com",
                "checkedIn": true,
                "checkInTime": "2025-09-23T18:00:05Z",
                "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
              }
            ],
            "totalAmount": 100000.00,
            "currency": "TZS",
            "paymentMethod": "CASH",
            "soldBy": "Gate-A Scanner",
            "soldAt": "Main Entrance",
            "saleTime": "2025-09-23T18:00:05Z"
          }
        }
        

        Success Response Fields:

        FieldDescription
        bookingIdUUID of the created booking order
        bookingReferenceHuman-readable booking reference number
        eventIdID of the event
        eventNameName of the event
        ticketsArray of issued ticket instances — one per attendee
        tickets[].ticketInstanceIdUnique ID of this specific ticket instance
        tickets[].ticketSeriesTicket serial number (e.g., VIP-0042-A)
        tickets[].ticketTypeNameThe type of the sold ticket
        tickets[].attendeeNameName of the attendee this ticket is assigned to
        tickets[].attendeeEmailEmail of the attendee
        tickets[].checkedInWhether the attendee has been checked in
        tickets[].checkInTimeTimestamp of check-in if immediateCheckIn was true
        tickets[].qrCodeJWT-encoded QR code string for this ticket
        totalAmountTotal cash amount collected in TZS
        currencyAlways TZS
        paymentMethodAlways CASH for at-door sales
        soldByName of the scanner that processed the sale
        soldAtLocation label of the scanner (e.g., "Main Entrance")
        saleTimeISO 8601 timestamp of when the sale occurred

        Possible Errors:

        HTTP StatusScenario
        400 BAD_REQUESTAttendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale
        401 UNAUTHORIZEDMissing or invalid Bearer token
        403 FORBIDDENScanner does not have SELL_TICKETS permission, device fingerprint mismatch, scanner is inactive or expired
        404 NOT_FOUNDScanner ID not found, ticket type not found
        422 UNPROCESSABLE_ENTITYValidation errors on request fields
        500 INTERNAL_SERVER_ERRORPayment processing or booking creation failure

        6. Organizer — Sell Ticket at Door

        Purpose: Allows the authenticated event organizer to sell tickets directly at their event counter, processing a cash payment and optionally checking in the attendee immediately.

        Endpoint: POST /api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizer

        Access Level: 🔒 Protected (Must be the organizer of the specified event)

        Authentication: Bearer Token

        Request Headers:

        HeaderTypeRequiredDescription
        AuthorizationstringYesBearer token of the authenticated event organizer
        Content-TypestringYesMust be application/json

        Path Parameters:

        ParameterTypeRequiredDescriptionValidation
        eventIdUUIDYesThe ID of the event to sell tickets forMust be an existing, non-deleted event; authenticated user must be the organizer

        Request JSON Sample:

        {
          "ticketTypeId": "d4e5f6a7-1234-5678-abcd-000000000002",
          "quantity": 2,
          "attendees": [
            {
              "fullName": "Peter Salim",
              "email": "peter.salim@example.com",
              "phoneNumber": "+255711223344"
            },
            {
              "fullName": "Grace Mwangi",
              "email": "grace.mwangi@example.com",
              "phoneNumber": null
            }
          ],
          "immediateCheckIn": false,
          "location": "VIP Gate"
        }
        

        Request Body Parameters:

        ParameterTypeRequiredDescriptionValidation
        ticketTypeIdUUIDYesID of the ticket type to sellMust belong to the event in the path; must not be ONLINE_ONLY; must be on sale
        quantityintegerYesTotal number of tickets to sellMin: 1; must equal the number of attendees in the attendees array
        attendeesarrayYesList of attendee details — one entry per ticketMin: 1 entry; count must match quantity
        attendees[].fullNamestringNoFull name of the attendeeOptional — auto-generated if blank
        attendees[].emailstringNoEmail of the attendeeValid email format if provided
        attendees[].phoneNumberstringNoPhone number of the attendeeOptional
        immediateCheckInbooleanYesWhether to mark attendees as checked-in immediatelyRequired
        locationstringNoDescription of the sale point, e.g., "VIP Gate", "Main Counter"Max: 200 characters; defaults to "Organizer Counter" if not provided

        Business Rules:

        • Only the event organizer (the user who created the event) can call this endpoint
        • Number of entries in 400attendees BAD_REQUESTmust equal quantity:
        • Cannot
        • Payment cancelis completedalways sessionsCASH — no wallet or ledger deduction
        • Ticket type must not be ONLINE_ONLY
        • 401immediateCheckIn UNAUTHORIZED= true: Authenticationmarks issues
        • each
        • 404ticket NOT_FOUND:as Sessionchecked-in notat foundtime of sale

        Success Response JSON Sample:

        {
          "success": true,
          "httpStatus": "CREATED",
          "message": "Tickets sold successfully at door",
          "action_time": "2025-09-23T17:30:00",
          "data": {
            "bookingId": "d7e6f5a4-0000-0000-0000-000000000035",
            "bookingReference": "BK-2025-000043",
            "eventId": "b3f1a2c4-1234-5678-abcd-000000000001",
            "eventName": "Kilimanjaro Jazz Night 2025",
            "tickets": [
              {
                "ticketInstanceId": "ee55ff66-0000-0000-0000-000000000060",
                "ticketSeries": "VIP-0043-A",
                "ticketTypeName": "VIP",
                "attendeeName": "Peter Salim",
                "attendeeEmail": "peter.salim@example.com",
                "checkedIn": false,
                "checkInTime": null,
                "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
              },
              {
                "ticketInstanceId": "gg77hh88-0000-0000-0000-000000000061",
                "ticketSeries": "VIP-0043-B",
                "ticketTypeName": "VIP",
                "attendeeName": "Grace Mwangi",
                "attendeeEmail": "grace.mwangi@example.com",
                "checkedIn": false,
                "checkInTime": null,
                "qrCode": "eyJhbGciOiJIUzI1NiJ9..."
              }
            ],
            "totalAmount": 100000.00,
            "currency": "TZS",
            "paymentMethod": "CASH",
            "soldBy": "organizer_username",
            "soldAt": "VIP Gate",
            "saleTime": "2025-09-23T17:30:05Z"
          }
        }
        

        Success Response Fields:

        FieldDescription
        bookingIdUUID of the created booking order
        bookingReferenceHuman-readable booking reference number
        eventIdID of the event
        eventNameName of the event
        ticketsArray of issued ticket instances — one per attendee
        tickets[].ticketInstanceIdUnique ID of this specific ticket instance
        tickets[].ticketSeriesTicket serial number
        tickets[].ticketTypeNameThe type of ticket sold
        tickets[].attendeeNameAssigned attendee name
        tickets[].attendeeEmailAttendee email
        tickets[].checkedInWhether immediately checked in
        tickets[].checkInTimeCheck-in timestamp, null if not checked in
        tickets[].qrCodeJWT-encoded QR code string for this ticket
        totalAmountTotal cash amount in TZS
        currencyAlways TZS
        paymentMethodAlways CASH
        soldByUsername of the organizer who made the sale
        soldAtLocation label provided in the request
        saleTimeISO 8601 timestamp of the sale

        Possible Errors:

        HTTP StatusScenario
        400 BAD_REQUESTAttendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale
        401 UNAUTHORIZEDMissing or invalid Bearer token
        403 FORBIDDENAuthenticated user is not the organizer of the specified event
        404 NOT_FOUNDEvent not found, ticket type not found
        422 UNPROCESSABLE_ENTITYValidation errors on request fields
        500 INTERNAL_SERVER_ERRORPayment processing or booking creation failure

        Standard Error Response Examples:

        CannotBad CancelRequest Completed— General (400):

        {
          "success": false,
          "httpStatus": "BAD_REQUEST",
          "message": "CannotTicket cancelis completednot session"currently on sale",
          "action_time": "2025-12-11T10:40:00"09-23T10:30:45",
          "data": "CannotTicket cancelis completednot session"currently on sale"
        }
        

        CannotUnauthorized Cancel AfterToken PaymentIssues (400)401):

        {
          "success": false,
          "httpStatus": "BAD_REQUEST"UNAUTHORIZED",
          "message": "CannotToken cancelhas - payment completed"expired",
          "action_time": "2025-12-11T10:40:00"09-23T10:30:45",
          "data": "CannotToken cancelhas -expired"
        payment}
        completed"

        Forbidden — Access Denied (403):

        {
          "success": false,
          "httpStatus": "FORBIDDEN",
          "message": "Only the event organizer can sell tickets at door",
          "action_time": "2025-09-23T10:30:45",
          "data": "Only the event organizer can sell tickets at door"
        }
        

        Not Found (404):

        {
          "success": false,
          "httpStatus": "NOT_FOUND",
          "message": "Event not found",
          "action_time": "2025-09-23T10:30:45",
          "data": "Event not found"
        }
        

        Validation Error (422):

        {
          "success": false,
          "httpStatus": "UNPROCESSABLE_ENTITY",
          "message": "Validation failed",
          "action_time": "2025-09-23T10:30:45",
          "data": {
            "ticketTypeId": "must not be null",
            "quantity": "must be greater than or equal to 1",
            "immediateCheckIn": "must not be null"
          }
        }
        

        CheckoutStandard Error Types Reference

        Application-Level Exceptions (400–499)

        • 400 BAD_REQUEST: General invalid request, business rule violations, or item already exists
        • 401 UNAUTHORIZED: Authentication issues (missing, invalid, expired, or malformed token)
        • 403 FORBIDDEN: Access denied, scanner permission issues, organizer mismatch
        • 404 NOT_FOUND: Event, ticket type, session, or scanner not found
        • 422 UNPROCESSABLE_ENTITY: Bean validation errors with per-field details
        • 429 TOO_MANY_REQUESTS: Rate limit exceeded

        Server-Level Exceptions (500+)

        • 500 INTERNAL_SERVER_ERROR: Unexpected server errors, payment orchestration failures

        Quick Reference

        Endpoint Summary

        #MethodPathDescription
        1POST/api/v1/e-events/checkoutCreate online checkout session
        2GET/api/v1/e-events/checkout/{sessionId}Get checkout session details
        3POST/api/v1/e-events/checkout/{sessionId}/paymentProcess wallet payment
        4POST/api/v1/e-events/checkout/{sessionId}/cancelCancel checkout session
        5POST/api/v1/e-events/checkout/sell-at-door-ticket/scannerScanner at-door sale
        6POST/api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizerOrganizer at-door sale

        Session Status Lifecycle

        Status Flow Diagram

        PENDING_PAYMENT → PAYMENT_COMPLETED → COMPLETED
               ↓                    ↓
           PAYMENT_FAILED      CANCELLED
               ↓
            EXPIRED
        

        Status DescriptionsReference

        paymentinitiated,awaiting retrymayfullycreatedand Sessiontimed
        Status DescriptionCan Process Payment?Can Cancel?Tickets Held?Meaning
        PENDING_PAYMENT AwaitingSession created, awaiting paymentYesYesYes
        PAYMENT_COMPLETEDPAYMENT_PROCESSING PaymentExternal successful No No Yesconfirmation
        COMPLETEDPAYMENT_COMPLETED BookingPayment succeeded, booking being createdNoNoNo (transferred to booking)
        PAYMENT_FAILED Payment attempt failedYes (retry) Yes Yesbe possible)
        EXPIREDCOMPLETED SessionBooking timeout No No No (auto-released)confirmed
        CANCELLED UserSession cancelled by user
        EXPIRED No N/A Noout (released)before payment

        AutomaticTicket TransitionsPricing Type Behaviour

        minutes):

        Pricing TypePayment RequiredAt-Door AllowedNotes
        FREENoDepends on sales channelAuto-Expiryprocessed on session creation
        PAIDYes (15Wallet) YesEscrow created on payment
        DONATIONOptional amountNo (Online only)Max 1 ticket per order; no other attendees

        Sales Channel Rules

        Sales ChannelOnline CheckoutAt-Door (Scanner)At-Door (Organizer)
        ONLINE_ONLY✅ Allowed❌ Blocked❌ Blocked
        AT_DOOR_ONLY❌ Blocked✅ Allowed✅ Allowed
        BOTH✅ Allowed✅ Allowed✅ Allowed

        Authentication Reference

        • SystemBearer job checks for expired PENDING_PAYMENT sessions
        • Changes status to EXPIRED
        • Releases held tickets automatically
        • Runs periodically (e.g., every minute)

        Auto-CompletionToken:

        • After PAYMENT_COMPLETED, listener creates booking
        • Updates status to COMPLETED
        • Sets createdBookingOrderId

        Ticket Hold System

        How Ticket Holds Work

        1. On Checkout Creation:

          • Tickets immediately "held" by incrementingInclude quantitySoldAuthorization: Bearer <token>
          • Sets ticketsHeld = true
          • Sets ticketHoldExpiresAt = now + 15 minutes
          • Other users see reduced availability
        2. During Hold Period (15 minutes):

          • Tickets appear as "sold" to other buyers
          • Actual availability = totalQuantity - quantitySold
          • Buyer must complete payment within window
        3. On Payment Success:

          • Hold becomes permanent (tickets truly sold)
          • Booking order created with reserved tickets
          • QR codes generated
        4. On Session Cancel:

          • Hold released (quantitySold decremented)
          • Tickets return to available pool
          • Other buyers can now purchase
        5. On Session Expiry (auto):

          • Hold auto-released by background job
          • Tickets return to available pool
          • Status changed to EXPIRED

        Why Ticket Holds?

        Prevents Overselling:

        • Without holds: Multiple people could checkout same tickets
        • With holds: Tickets reserved during checkout process

        Fair Access:

        • 15-minute window gives reasonable time to complete payment
        • Not too long (doesn't block others)
        • Not too short (enough time to pay)

        Inventory Accuracy:

        • Real-time availability shown toin all buyers
        • request
        • No race conditions during high-demand sales

        Max Quantity Per User - Identity Tracking

        The Problem

        Users could bypass maxQuantityPerUser by:

        • Buying X tickets for themselves
        • Then buying X more tickets "for friends" using different emails
        • Total: 2X tickets for same person (limit bypass)

        The Solution

        System tracks purchases across all identities (email + phone):

        Purchase Check Algorithm:

        1. Collect all identities in current order:

          • Buyer's email and phoneheaders
          • All otherendpoints attendees'require emails and phones
          authentication
        2. For

          Checkscanner past completed orders:

          • Scan buyer's previous orders
          • Check if any collected identity appears in:
            • Previous buyer tickets
            • Previous other attendee tickets
        3. Calculate per-identity totals:

          • Previous tickets for this identity
          • Current tickets for this identity
          • Total = previous + current
        4. Validate against limit:

          • If any identity exceeds maxQuantityPerUser → BLOCK
          • Error shows masked identity and ticket counts

        Example Scenario:

        Ticket Limit: 5 per user for "VIP Pass"

        User's Past Orders:

        • Order 1: Bought 3 VIP tickets for self (john@example.com)

        Current Order Attempt:

        {
          "ticketsForMe": 2,
          "otherAttendees": [
            {
              "email": "john@example.com",  // ❌ Same as buyer's email
              "quantity": 1
            }
          ]
        }
        

        Result: BLOCKED

        • Identity "john@example.com" already has 3 tickets
        • Current order adds 2 (ticketsForMe) + 1 (other attendee) = 3 more
        • Total: 3 + 3 = 6 tickets
        • Exceeds limit of 5

        Error Message:

        Maximum 5 tickets per user for 'VIP Pass'. 
        The email/phone 'j***@example.com' has already purchased 3 ticket(s). 
        This order would add 3 more ticket(s), exceedingendpoints, the limit.Bearer 

        Identity Masking

        Sensitive data masked in errors:

        • Email: john@example.com → j***@example.com
        • Phone: +255712345678 → +255***5678

        Edge Cases Handled

        Multiple Identities:

        • If buyer uses email A and phone B
        • System checks BOTH separately
        • Prevents using phone when email limit reached

        Cross-Order Detection:

        • Finds same email/phone across:
          • Different buyer orders
          • Other attendee entries
          • Mixed buyer/attendee combinations

        Flexible Limits:

        • maxQuantityPerUser = null → Unlimited
        • maxQuantityPerUser = 0 → Unlimited
        • maxQuantityPerUser > 0 → Enforced

        Multi-Attendee Purchase Model

        Design Philosophy

        Simplified Model: Buyer purchases ALL tickets, can distribute to friends/family

        Key Concepts:

        1. Single Buyer: One person pays for entire order
        2. Multiple Attendees: Buyer + any number of other people
        3. Ticket Distribution: Buyer decides ticket allocation
        4. Email Options: Can email QR codes to each attendee

        Purchase Scenarios

        Scenario 1: Only for Myself

        {
          "ticketsForMe": 5,
          "otherAttendees": null
        }
        
        • Total: 5 tickets
        • All tickets for buyer
        • All QR codes sent to buyer only

        Scenario 2: For Myself + Friends

        {
          "ticketsForMe": 2,
          "otherAttendees": [
            {
              "name": "Jane Doe",
              "email": "jane@example.com",
              "phone": "+255712345678",
              "quantity": 2
            },
            {
              "name": "Bob Smith",
              "email": "bob@example.com",
              "phone": "+255723456789",
              "quantity": 1
            }
          ],
          "sendTicketsToAttendees": true
        }
        
        • Total: 5 tickets (2 + 2 + 1)
        • Buyer gets 2 tickets
        • Jane gets 2 tickets (QR codes emailed)
        • Bob gets 1 ticket (QR code emailed)

        Scenario 3: Only for Friends (Gift)

        {
          "ticketsForMe": 0,
          "otherAttendees": [
            {
              "name": "Jane Doe",
              "email": "jane@example.com",
              "phone": "+255712345678",
              "quantity": 3
            }
          ],
          "sendTicketsToAttendees": true
        }
        
        • Total: 3 tickets
        • Buyer not attending (0 tickets)
        • Jane gets all 3 tickets
        • Useful for gifts

        Ticket Distribution Options

        sendTicketsToAttendees = true (Default):

        • Each attendee receives their QR codes via email
        • Buyer also gets confirmation email
        • Attendees can present QR codes independently

        sendTicketsToAttendees = false:

        • ALL QR codes sent only to buyer
        • Buyer must share codes with attendees manually
        • Useful for group leaders managing entry

        Benefits of This Model

        Simplicity:

        • One payment for whole group
        • No complex ticket transfers
        • Clear purchase ownership

        Flexibility:

        • Buy any combination (self + others)
        • Can buy entirely for others (gifts)
        • Easy group bookings

        Payment Clarity:

        • Single payer responsible
        • No split payment complications
        • Clear refund recipient

        Limit Enforcement:

        • maxQuantityPerUser tracked across all attendees
        • Prevents limit bypass via "buying for friends"
        • Fair access for all buyers

        Pricing Calculation

        Current Model (Simplified)

        Subtotal = unitPrice × totalQuantity
        Total = Subtotal
        

        No fees currently, but structure supports future additions:

        • Service fees
        • Processing fees
        • Taxes
        • Discounts

        Free Tickets (price = 0.00)

        {
          "pricing": {
            "subtotal": 0.00,
            "total": 0.00
          }
        }
        

        Free Ticket Flow:

        1. Checkout session created (same validation)
        2. Tickets held (same as paid)
        3. Payment processing → No wallet deduction, no escrow
        4. Booking created immediately
        5. QR codes generated
        6. Emails sent

        Why hold free tickets?

        • Prevents overselling (capacity still limited)
        • Fair distribution (first-come-first-served)
        • Same user experience (just skip payment step)

        Escrow Protection System

        What is Escrow?

        For Paid Tickets:

        1. Payment: Buyer pays from wallet
        2. Escrow Created: Money held in escrow account
        3. Event Happens: Attendees attend event
        4. Event Ends: System checks event completion
        5. Organizer Paid: Money released to organizer

        Why Escrow?

        • Buyer Protection: Get refund if event cancelled
        • Organizer Protection: Guaranteed payment after event
        • Platform Trust: Money safely held by platform
        • Fraud Prevention: Cannot withdraw until event complete

        Escrow Lifecycle

        Created on Payment:

        {
          "escrowId": "aa0e8400-e29b-41d4-a716-446655440011",
          "escrowNumber": "ESC-20251211-0001",
          "totalAmount": 750.00,
          "currency": "TZS",
          "status": "ACTIVE",
          "releasesAt": "2025-12-18T00:00:00+03:00"
        }
        

        Release Conditions:

        • Event marked as COMPLETED
        • Event receives positive feedback (if feedback system enabled)
        • Automatic release after event end + grace period

        Refund Triggers:

        • Event cancelled by organizer
        • Event marked as fraudulent
        • Buyer requests refund (before event)

        Validation Rules Summary

        Event Validation

        • ✅ Event must exist and not be deleted
        • ✅ Event must be PUBLISHED
        • ✅ Event start date must be in future

        Ticket Validation

        • ✅ Ticket must exist and not be deleted
        • ✅ Tickettoken must belong to specifiedthe event
        • account
        • linked Ticketto statusthe mustscanner be ACTIVE
        • ✅ Ticket must be currently on sale (within sales period)device

        Quantity Validation

        • ✅ Total quantity (ticketsForMe + others) must be ≥ 1
        • ✅ Must meet minQuantityPerOrder (if set)
        • ✅ Must not exceed maxQuantityPerOrder (if set)
        • ✅ Must not exceed available tickets
        • ✅ Must not exceed maxQuantityPerUser (per identity check)

        Attendee Validation

        • ✅ Each attendee must have name (2-100 chars)
        • ✅ Each attendee must have valid email format
        • ✅ Each attendee must have valid Tanzania phone (+255[67]xxxxxxxx)
        • ✅ Each attendee quantity must be ≥ 1
        • ✅ No duplicate attendee emails within same order

        Wallet Validation

        • ✅ Buyer wallet must exist and be active
        • ✅ Wallet balance must be ≥ total amount
        • ✅ Wallet must support payment currency (TZS)

        Phone Number Format

        Tanzania Phone Format

        Pattern: +255[67]xxxxxxxx

        Valid Examples:

        • +255712345678 (Vodacom)
        • +255623456789 (Airtel - wait, starts with 6)
        • +255754567890 (Tigo)

        Invalid Examples:

        • 0712345678 (missing +255)
        • 255712345678 (missing +)
        • +255812345678 (wrong prefix - should be 7 or 6)
        • +2557123456 (too short)
        • +25571234567890 (too long)

        Regex: ^\\+255[67]\\d{8}$

        Validation:

        • Must start with +255
        • Followed by 7 or 6 (mobile prefixes)
        • Followed by exactly 8 digits
        • Total: 13 characters

        Quick Reference Guide

        Common HTTP Status Codes

        • 200 OK: Successful request
        • 201 CREATED: Checkout session created
        • 400 Bad Request: Validation errors, limits exceeded, insufficient balance
        • 401 Unauthorized: Authentication required/failed
        • 404 Not Found: Session/event/ticket not found
        • 422 Unprocessable Entity: Validation errors
        • 500 Internal Server Error: Server error

        Session Timing

        • Creation: Instant
        • Expiry: 15 minutes from creation
        • Ticket Hold: 15 minutes from creation
        • Auto-Release: Triggered by background job (runs every minute)

        Purchase Limits Hierarchy

        Total Quantity ≥ minQuantityPerOrder (if set)
        Total Quantity ≤ maxQuantityPerOrder (if set)
        Per Identity ≤ maxQuantityPerUser (if set)
        Total Quantity ≤ Available Tickets
        

        Data Format Standards

        • Dates: LocalDateTimeISO 8601 format (2025-12-11T10:09-23T10:30:45)
        • IDs: UUID format
        • Money: Decimal with 2 decimals (750.00)45)
        • Currency: TZS (Tanzanian Shilling) — decimal values with 2 decimal places
        • UUIDs: Standard UUID v4 format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
        • Phone Numbers: Tanzania format only — +255[67]xxxxxxxx (Tanzania format)XXXXXXXX

        Checkout Flow Checklist

        1. ✅ User creates checkout session
        2. ✅ System validates all rules
        3. ✅ System holds tickets (15 min)
        4. ✅ User completes payment
        5. ✅ System creates escrow (if paid)
        6. ✅ System creates booking order
        7. ✅ System generates QR codes
        8. ✅ System emails tickets
        9. ✅ Session marked COMPLETED

        Best Practices

        For Users:

        • Complete payment within 15 minutes
        • Verify attendee details before submitting
        • Use correct phone formats
        • Check wallet balance before checkout

        For Developers:

        • Always validate session status before payment
        • Handle expiry gracefully
        • Implement retry logic for failed payments
        • Show countdown timer (15 min remaining)
        • Auto-refresh availability
        • Clear error messages for limit violations

        Error Handling Tips

        • 400 Insufficient Balance: Show "Top Up Wallet" option
        • 400 Max Per User: Explain limit clearly, show previous purchases
        • 400 Session Expired: Offer "Create New Session" button
        • 400 Tickets Sold Out: Show "Notify When Available" option
        • 422 Validation: Highlight specific field errors

        Common Mistakes to Avoid

        ❌ Not checking session expiry before payment
        ❌ Forgetting to validate phone format
        ❌ Allowing duplicate attendee emails
        ❌ Not handling ticket hold releases
        ❌ Ignoring maxQuantityPerUser checks
        ❌ Not displaying countdown timer
        ❌ Attempting payment on expired sessions