Skip to main content

Event Checkout & Payment API

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

Base URL: https://api.nexgate.com/api/v1

Short Description: The Event 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 handle the complete ticket purchase workflow with escrow protection. The system includes sophisticated validation for purchase limits, duplicate identity checking, and automatic ticket release on session expiry.

Hints:

  • Ticket Hold System: Tickets automatically held for 15 minutes during checkout
  • Multi-Attendee Support: Buy tickets for self + unlimited other attendees (friends/family)
  • Identity Tracking: Prevents limit bypass using email/phone across attendees
  • Wallet Payment: Integrated wallet system 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/max per order and max per user with identity checks
  • Free Tickets: Support for free events (price = 0.00)
  • Payment Retry: Up to 5 retry attempts for failed payments
  • Automatic Status: Sessions auto-expire, tickets auto-release
  • Booking Creation: Automatic booking order and QR code generation after payment

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:30:45",
  "data": {
    // Actual response data goes here
  }
}

Error Response Structure

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

HTTP Method Badge Standards

  • GET - GET - Green (Read operations)
  • POST - POST - Blue (Create/Action operations)

EventCheckoutResponse Structure

This is the comprehensive response structure returned by checkout endpoints:

{
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "PENDING_PAYMENT",
  "customerId": "660e8400-e29b-41d4-a716-446655440001",
  "customerUserName": "johndoe",
  "eventId": "770e8400-e29b-41d4-a716-446655440002",
  "eventTitle": "East African Tech Summit 2025",
  "ticketDetails": {
    "ticketTypeId": "880e8400-e29b-41d4-a716-446655440003",
    "ticketTypeName": "Early Bird VIP",
    "unitPrice": 150.00,
    "ticketsForBuyer": 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,
    "totalQuantity": 5,
    "subtotal": 750.00
  },
  "pricing": {
    "subtotal": 750.00,
    "total": 750.00
  },
  "paymentIntent": {
    "provider": "WALLET",
    "clientSecret": null,
    "paymentMethods": ["WALLET"],
    "status": "PENDING"
  },
  "ticketsHeld": true,
  "ticketHoldExpiresAt": "2025-12-11T10:45:45",
  "expiresAt": "2025-12-11T10:45:45",
  "createdAt": "2025-12-11T10:30:45",
  "updatedAt": "2025-12-11T10:30:45",
  "completedAt": null,
  "createdBookingOrderId": null,
  "isExpired": false,
  "canRetryPayment": false
}

Response Field Descriptions

Field Type Description
sessionId string (UUID) Unique checkout session identifier
status enum Session status: PENDING_PAYMENT, PAYMENT_COMPLETED, COMPLETED, PAYMENT_FAILED, EXPIRED, CANCELLED
customerId string (UUID) Buyer's account ID
customerUserName string Buyer's username
eventId string (UUID) Event being purchased
eventTitle string Event title for display
ticketDetails object Complete ticket purchase details
ticketDetails.ticketTypeId string (UUID) Ticket type being purchased
ticketDetails.ticketTypeName string Ticket type name
ticketDetails.unitPrice decimal Price per ticket
ticketDetails.ticketsForBuyer integer Tickets for the buyer themselves
ticketDetails.otherAttendees array List of other attendees (friends/family)
ticketDetails.sendTicketsToAttendees boolean Whether to email tickets to other attendees
ticketDetails.totalQuantity integer Total tickets in order (buyer + others)
ticketDetails.subtotal decimal Calculated: unitPrice × totalQuantity
pricing object Pricing breakdown
pricing.subtotal decimal Subtotal before any fees
pricing.total decimal Final total amount
paymentIntent object Payment configuration
paymentIntent.provider string Payment provider (WALLET)
paymentIntent.paymentMethods array Available payment methods
paymentIntent.status string Payment intent status
ticketsHeld boolean Whether tickets are currently held
ticketHoldExpiresAt string When held tickets will be released (LocalDateTime)
expiresAt string When checkout session expires (LocalDateTime)
createdAt string Session creation timestamp (LocalDateTime)
updatedAt string Last update timestamp (LocalDateTime)
completedAt string Completion timestamp (null if not completed)
createdBookingOrderId string (UUID) Booking order ID (set after successful payment)
isExpired boolean Calculated: whether session has expired
canRetryPayment boolean Calculated: whether payment can be retried

Endpoints

1. Create Checkout Session

Purpose: Initialize checkout session with ticket holds for event purchase

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

Access Level: 🔒 Protected (Requires Bearer Token Authentication)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token (format: Bearer <token>)
Content-Type string Yes Must be application/json

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 Body Parameters:

Basic Parameters

Parameter Type Required Description Validation
eventId string (UUID) Yes Event to purchase tickets for Must be PUBLISHED and not past
ticketTypeId string (UUID) Yes Ticket type to purchase Must be ACTIVE and on sale
ticketsForMe integer Yes Tickets for buyer Min: 0 (can buy only for others)
paymentMethodId string (UUID) No Payment method Defaults to wallet

Multi-Attendee Parameters

Parameter Type Required Description Validation
otherAttendees array No List of other attendees Can be empty/null
sendTicketsToAttendees boolean No Email tickets to attendees Default: true

Other Attendee Object Parameters

Parameter Type Required Description Validation
name string Yes Attendee full name Min: 2, Max: 100 characters
email string Yes Attendee email Valid email format
phone string Yes Attendee phone Tanzania format: +255[67]xxxxxxxx
quantity integer Yes Tickets for this attendee Min: 1

Success 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 Validations:

  • Event 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_REQUEST: 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:

Parameter Type Required Description
sessionId string (UUID) Yes Checkout 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
  • Calculates isExpired and canRetryPayment dynamically

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:

Parameter Type Required Description
sessionId string (UUID) Yes Checkout session ID

Success Response: Returns PaymentResponse structure

Success Response Message: "Payment processed successfully"

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Payment processed successfully",
  "action_time": "2025-12-11T10:35:00",
  "data": {
    "paymentId": "990e8400-e29b-41d4-a716-446655440010",
    "status": "COMPLETED",
    "amount": 750.00,
    "currency": "TZS",
    "escrowId": "aa0e8400-e29b-41d4-a716-446655440011",
    "escrowNumber": "ESC-20251211-0001",
    "transactionReference": "TXN-20251211-0001"
  }
}

Behavior:

  1. Session Validation: Checks session is PENDING_PAYMENT and not expired
  2. Wallet Deduction: Deducts amount from buyer's wallet
  3. Escrow Creation: Creates escrow account to hold funds until event completion
  4. Status Update: Updates session to PAYMENT_COMPLETED
  5. Event Trigger: Publishes PaymentCompletedEvent
  6. Booking 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

For Free Tickets (price = 0.00):

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

Payment Retry:

  • Allowed if status = PAYMENT_FAILED
  • Maximum 5 retry attempts
  • Session must not be expired
  • Tracks all attempts in paymentAttempts array

Standard Error Types:

  • 400 BAD_REQUEST: 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 is not pending payment: COMPLETED",
  "action_time": "2025-12-11T10:35:00",
  "data": "Session is not pending payment: COMPLETED"
}

Session Expired (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Checkout session has expired",
  "action_time": "2025-12-11T10:50:00",
  "data": "Checkout session has expired"
}

4. Cancel Checkout Session

Purpose: Cancel checkout session and release held tickets

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

Access Level: 🔒 Protected (Session Owner Only)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
sessionId string (UUID) Yes Checkout session ID

Success Response Message: "Checkout session cancelled successfully"

Success Response JSON Sample:

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

Behavior:

  1. Validation: Checks session is not COMPLETED or PAYMENT_COMPLETED
  2. Ticket Release: Releases held tickets (decrements quantitySold)
  3. Status Update: Changes status to CANCELLED
  4. Hold Flag: Sets ticketsHeld = false

Cannot Cancel:

  • COMPLETED sessions (booking created)
  • PAYMENT_COMPLETED sessions (payment already processed)

Standard Error Types:

  • 400 BAD_REQUEST: Cannot cancel completed sessions
  • 401 UNAUTHORIZED: Authentication issues
  • 404 NOT_FOUND: Session not found

Error Response Examples:

Cannot Cancel Completed (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot cancel completed session",
  "action_time": "2025-12-11T10:40:00",
  "data": "Cannot cancel completed session"
}

Cannot Cancel After Payment (400):

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Cannot cancel - payment completed",
  "action_time": "2025-12-11T10:40:00",
  "data": "Cannot cancel - payment completed"
}

Checkout Session Status Lifecycle

Status Flow Diagram

PENDING_PAYMENT → PAYMENT_COMPLETED → COMPLETED
       ↓                    ↓
   PAYMENT_FAILED      CANCELLED
       ↓
    EXPIRED

Status Descriptions

Status Description Can Process Payment? Can Cancel? Tickets Held?
PENDING_PAYMENT Awaiting payment Yes Yes Yes
PAYMENT_COMPLETED Payment successful No No Yes
COMPLETED Booking created No No No (transferred to booking)
PAYMENT_FAILED Payment failed Yes (retry) Yes Yes
EXPIRED Session timeout No No No (auto-released)
CANCELLED User cancelled No N/A No (released)

Automatic Transitions

Auto-Expiry (15 minutes):

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

Auto-Completion:

  • 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 incrementing quantitySold
    • 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 to all buyers
  • 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 phone
    • All other attendees' emails and phones
  2. Check 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), exceeding the limit.

Identity Masking

Sensitive data masked in errors:

  • Email: john@example.comj***@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
  • ✅ Ticket must belong to specified event
  • ✅ Ticket status must be ACTIVE
  • ✅ Ticket must be currently on sale (within sales period)

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: LocalDateTime (2025-12-11T10:30:45)
  • IDs: UUID format
  • Money: Decimal with 2 decimals (750.00)
  • Currency: TZS (Tanzanian Shilling)
  • Phone: +255[67]xxxxxxxx (Tanzania format)

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