Event Checkout & Payment API
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 |
| 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:
- Validation: Validates event, ticket, quantities, attendee data, wallet balance
- Ticket Hold: Immediately holds tickets (increments quantitySold temporarily)
- Session Creation: Creates checkout session with 15-minute expiry
- Hold Expiry: Sets ticketHoldExpiresAt to current time + 15 minutes
- Payment Intent: Creates payment intent (default: WALLET provider)
- 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 exceeded401 UNAUTHORIZED: Authentication issues404 NOT_FOUND: Event/ticket not found, user not authenticated422 UNPROCESSABLE_ENTITY: Validation errors500 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
isExpiredandcanRetryPaymentdynamically
Standard Error Types:
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:
- Session Validation: Checks session is PENDING_PAYMENT and not expired
- Wallet Deduction: Deducts amount from buyer's wallet
- Escrow Creation: Creates escrow account to hold funds until event completion
- Status Update: Updates session to PAYMENT_COMPLETED
- Event Trigger: Publishes PaymentCompletedEvent
- Booking Creation: Listener automatically creates booking order
- QR Generation: Generates QR codes for all tickets
- Email Sending: Emails tickets to buyer and optionally to other attendees
- 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 session401 UNAUTHORIZED: Authentication issues404 NOT_FOUND: Session not found422 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:
- Validation: Checks session is not COMPLETED or PAYMENT_COMPLETED
- Ticket Release: Releases held tickets (decrements quantitySold)
- Status Update: Changes status to CANCELLED
- 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 sessions401 UNAUTHORIZED: Authentication issues404 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
-
On Checkout Creation:
- Tickets immediately "held" by incrementing
quantitySold - Sets
ticketsHeld = true - Sets
ticketHoldExpiresAt = now + 15 minutes - Other users see reduced availability
- Tickets immediately "held" by incrementing
-
During Hold Period (15 minutes):
- Tickets appear as "sold" to other buyers
- Actual availability = totalQuantity - quantitySold
- Buyer must complete payment within window
-
On Payment Success:
- Hold becomes permanent (tickets truly sold)
- Booking order created with reserved tickets
- QR codes generated
-
On Session Cancel:
- Hold released (quantitySold decremented)
- Tickets return to available pool
- Other buyers can now purchase
-
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:
-
Collect all identities in current order:
- Buyer's email and phone
- All other attendees' emails and phones
-
Check past completed orders:
- Scan buyer's previous orders
- Check if any collected identity appears in:
- Previous buyer tickets
- Previous other attendee tickets
-
Calculate per-identity totals:
- Previous tickets for this identity
- Current tickets for this identity
- Total = previous + current
-
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.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→ UnlimitedmaxQuantityPerUser = 0→ UnlimitedmaxQuantityPerUser > 0→ Enforced
Multi-Attendee Purchase Model
Design Philosophy
Simplified Model: Buyer purchases ALL tickets, can distribute to friends/family
Key Concepts:
- Single Buyer: One person pays for entire order
- Multiple Attendees: Buyer + any number of other people
- Ticket Distribution: Buyer decides ticket allocation
- 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:
- Checkout session created (same validation)
- Tickets held (same as paid)
- Payment processing → No wallet deduction, no escrow
- Booking created immediately
- QR codes generated
- 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:
- Payment: Buyer pays from wallet
- Escrow Created: Money held in escrow account
- Event Happens: Attendees attend event
- Event Ends: System checks event completion
- 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 request201 CREATED: Checkout session created400 Bad Request: Validation errors, limits exceeded, insufficient balance401 Unauthorized: Authentication required/failed404 Not Found: Session/event/ticket not found422 Unprocessable Entity: Validation errors500 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
- ✅ User creates checkout session
- ✅ System validates all rules
- ✅ System holds tickets (15 min)
- ✅ User completes payment
- ✅ System creates escrow (if paid)
- ✅ System creates booking order
- ✅ System generates QR codes
- ✅ System emails tickets
- ✅ 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
No comments to display
No comments to display