Event Checkout & Payment API
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
TicketTZSHold(TanzanianSystemShilling):Ticketsunlessautomaticallyotherwiseheldstated - Checkout sessions expire after 15 minutes for
15 minutes duringonline checkout and 1 hour for at-door sales — always checkexpiresAtbefore processing payment Multi-AttendeeBearerSupport:tokenBuyauthenticationticketsis required forselfall+ unlimited other attendees (friends/family)endpointsIdentityForTracking:FREEPreventstickets,limitpaymentbypassisusingauto-processedemail/phoneimmediatelyacrossuponattendeessession creation — no separate payment step is neededWalletForPayment:DONATIONIntegratedtickets,walletmaximumsystem1with balance validationEscrow Protection: Payments held in escrow until event completionSession Expiry: 15-minute checkout window, auto-release tickets on expiryPurchase Limits: Enforces min/maxticket per order andmaxcannotperbeuserpurchasedwithforidentityotherchecksattendeesFreeTicketTickets:holdsSupportareforappliedfreeimmediatelyeventson(pricesession=creation;0.00)cancelling the session releases the holdPaymentScannerRetry:devicesUpmusttohave5theretrySELL_TICKETSattemptspermissionfor failed paymentsAutomatic Status: Sessions auto-expire, tickets auto-releaseBooking Creation: Automatic booking orderassigned andQRacodevalidgenerationdeviceFingerprintafterregisteredpaymentbefore 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
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true for successful operations, false for errors |
httpStatus |
string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
message |
string | Human-readable message describing the operation result |
action_time |
string | ISO 8601 timestamp of when the response was generated |
data |
object/string | Response payload for success, error details for failures |
HTTP Method Badge Standards
- GET
-—GETGreen:-Safe,Greenread-only(Read operations)operations - POST
-—POSTBlue:-CreateBluenew(Create/Actionresources - 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
Access Level: 🔒 Protected (Requires valid Bearer token — authenticated attendee)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token of the authenticated attendee |
Content-Type |
string | Yes | Must 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:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
eventId |
UUID | Yes | The ID of the event being booked | Must be a valid published event that has not yet started |
ticketTypeId |
UUID | Yes | The ID of the ticket type being purchased | Must belong to the specified event and be active and on sale |
ticketsForMe |
integer | Yes | Number of tickets for the buyer themselves. Use 0 if the buyer is not attending |
Min: 0 |
donationAmount |
decimal | No | Donation amount in TZS. Only applicable for DONATION type tickets |
Only used when ticket pricing type is DONATION |
otherAttendees |
array | No | List of other attendees to purchase tickets for | Each attendee must have valid name, email, and Tanzanian phone number |
otherAttendees[].name |
string | Yes (if array provided) | Full name of the attendee | Min: 2, Max: 100 characters |
otherAttendees[].email |
string | Yes (if array provided) | Email address of the attendee | Valid email format; no duplicate emails in the array |
otherAttendees[].phone |
string | Yes (if array provided) | Phone number of the attendee | Must match Tanzania format: +255[67]XXXXXXXX |
otherAttendees[].quantity |
integer | Yes (if array provided) | Number of tickets for this attendee | Min: 1 |
sendTicketsToAttendees |
boolean | No | If true, QR tickets are sent to each attendee's email. If false, all QR codes are sent to the buyer only |
Default: true |
paymentMethodId |
UUID | No | ID of a saved payment method. Defaults to wallet if not provided | Optional |
Business Rules:
- Total quantity =
ticketsForMe+ sum of allotherAttendees[].quantity— must be at least 1 DONATIONtickets: maximum 1 per order, cannot be bought for other attendees, online onlyAT_DOOR_ONLYtickets cannot be purchased through this endpoint- Wallet balance is validated upfront for
PAIDtickets - If the event has a required questionnaire set to
BEFORE_CHECKOUT, it must be submitted before calling this endpoint FREEtickets are auto-processed immediately — the response will already showPAYMENT_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 DescriptionsFields:
| Field | Description | |
|---|---|---|
sessionId | Unique identifier for this checkout |
|
status |
PENDING_PAYMENT, PAYMENT_COMPLETED, COMPLETED, PAYMENT_FAILED, CANCELLED, EXPIRED |
|
customerId |
||
customerUserName |
||
eventId |
||
eventTitle |
||
ticketDetails.ticketTypeId |
||
ticketDetails. |
||
ticketDetails. |
||
ticketDetails. |
||
ticketDetails.otherAttendees | List of other attendees |
|
ticketDetails.sendTicketsToAttendees | Whether QR codes will be emailed to |
|
ticketDetails.totalQuantity |
Total tickets |
|
ticketDetails. | Total |
|
pricing.subtotal |
||
pricing.total |
||
paymentIntent.provider | Payment provider (WALLET) |
|
paymentIntent.paymentMethods | Available payment methods for this session | |
paymentIntent.status | Payment intent status (PENDING, COMPLETED) |
|
ticketsHeld | Whether the tickets are currently being held in reserve | |
ticketHoldExpiresAt |
||
expiresAt |
||
createdBookingOrderId |
||
isExpired |
||
canRetryPayment |
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:
| HTTP Status | Scenario | |
|---|---|---|
400 BAD_REQUEST |
||
401 UNAUTHORIZED |
||
404 NOT_FOUND |
||
422 UNPROCESSABLE_ENTITY |
||
500 INTERNAL_SERVER_ERROR |
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:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token | authenticated
attendee |
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
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
sessionId |
Yes | Must be | ||
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:
| Field | Description |
|---|---|
sessionId |
Unique session identifier |
status |
Current session status |
paymentAttempts |
List of all payment attempts made on this session, including failures |
paymentAttempts[].attemptNumber |
Sequential attempt number (1-indexed) |
paymentAttempts[].paymentMethod |
Payment method used for this attempt |
paymentAttempts[].status |
Result of the attempt: SUCCESS or FAILED |
paymentAttempts[].errorMessage |
Failure reason if the attempt failed |
paymentAttempts[].attemptedAt |
Timestamp of the attempt |
paymentAttempts[].transactionId |
External or internal transaction reference |
| All other fields | Same as Create Checkout Session response |
Possible Errors:
| HTTP Status | Scenario |
|---|---|
401 UNAUTHORIZED |
Missing, expired, or invalid Bearer token |
404 NOT_FOUND |
Session not found or does not belong to the authenticated user |
500 INTERNAL_SERVER_ERROR |
Unexpected 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:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token of the authenticated attendee |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
sessionId |
||||
Other Attendee Object Parameters
| Yes | ||||
SuccessBusiness 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 balanceTicket Hold: Immediately holds tickets (increments quantitySold temporarily)Session Creation: Creates checkout session with 15-minute expiryHold Expiry: Sets ticketHoldExpiresAt to current time + 15 minutesPayment Intent: Creates payment intent (default: WALLET provider)Auto-Expiry: Session auto-expires after 15 minutes if payment not completed
Key ValidationsRules:
EventSession must bePUBLISHED and notinthe pastTicket must be ACTIVE and currently on saleTotal 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 availableSufficient wallet balanceNo duplicate attendee emailsValid email/phone formats for all attendees
Standard Error Types:
400 BAD_REQUESTPENDING_PAYMENT: 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:
Success Response: Returns complete EventCheckoutResponse structure
Success Response Message: "Checkout session retrieved successfully"
Behavior:
Only session owner (customer) can retrieveReturns all session details including ticket holds, pricing, paymentstatusCalculatesSessionmustisExpiredandnot 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
canRetryPaymentdynamicallybecomesfalse - On success, escrow is created, session moves to
PAYMENT_COMPLETED, and a booking order is created asynchronously
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:
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:
SessionValidation:ChecksField Description statusPayment result status: SUCCESS,FAILED, orPENDINGcheckoutSessionIdThe 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 nullimmediately after payment as booking isPENDING_PAYMENTcreatedandasynchronouslynotexpiredWalletorderNumberDeduction:Human-readable Deductsorder reference —nulluntil order is createdpaymentMethodPayment method used: WALLETamountPaidTotal amount deducted from buyer's wallet EscrowinCreation:TZSCreatesescrowaccountplatformFeePlatform fee amount (5% of total) in TZS sellerAmountAmount that will be released to hold funds untilthe eventcompletionorganizerStatusinUpdate:TZSUpdatessessiontocurrencyPAYMENT_COMPLETEDCurrency Eventcode,Trigger:alwaysPublishesTZSPaymentCompletedEventBookingCreation: Listener automatically creates booking orderQR Generation: Generates QR codes for all ticketsEmail Sending: Emails tickets to buyer and optionally to other attendeesFinal Status: Session marked as COMPLETED after booking creationNo wallet deductionNo escrow creationDirect booking creationStill generates QR codes and sends emailsAllowedifstatusHTTP =StatusPAYMENT_FAILEDScenario Maximum5retryattemptsSession must not be expiredTracks all attempts in paymentAttempts arrayStandard 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 pendinginpayment:PENDING_PAYMENTCOMPLETED",status,"action_time": "2025-12-11T10:35:00", "data": "Sessionsession is expired, or attempting payment on a FREE ticket401 UNAUTHORIZEDMissing, expired, or invalid Bearer token 404 NOT_FOUNDSession not pendingfoundpayment:orCOMPLETED"does}not belong to the authenticated user500 INTERNAL_SERVER_ERRORSessionPayment Expiredprocessing(400):error,{insufficient"success":balancefalse,at"httpStatus":time"BAD_REQUEST",of"message":processing"Checkoutsessionhas expired", "action_time": "2025-12-11T10:50:00", "data": "Checkout session has expired" }- Cannot cancel a session
cancelledthatsuccessfully"is inCOMPLETEDstatus - Cannot cancel a session that is in
PAYMENT_COMPLETEDstatus (payment already processed) - Cancelling releases the ticket hold immediately
- Cancellation does not trigger a refund — refunds are handled separately through the escrow system
-
HTTP Status Scenario 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 TicketbeRelease:onReleasessaleStatus Update: Changes statustoCANCELLEDsellHoldMin: Flag:1;SetsmustticketsHeldequal=thefalsenumber of attendees in theattendeesarrayCOMPLETEDThesessionsscanner(bookingmustcreated)be active, not expired, and have theSELL_TICKETSpermissionPAYMENT_COMPLETEDThesessionsdeviceFingerprint(paymentmustalreadyexactlyprocessed)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 = trueautomatically marks each generated ticket as checked-in- Only the event organizer (the user who created the event) can call this endpoint
- Number of entries in
400attendeesBAD_REQUESTmust equalquantity: - Payment
canceliscompletedalwayssessionsCASH— no wallet or ledger deduction - Ticket type must not be
ONLINE_ONLY 401immediateCheckInUNAUTHORIZED= true:Authenticationmarksissueseach 404ticketNOT_FOUND:asSessionchecked-innotatfoundtime of sale400 BAD_REQUEST: General invalid request, business rule violations, or item already exists401 UNAUTHORIZED: Authentication issues (missing, invalid, expired, or malformed token)403 FORBIDDEN: Access denied, scanner permission issues, organizer mismatch404 NOT_FOUND: Event, ticket type, session, or scanner not found422 UNPROCESSABLE_ENTITY: Bean validation errors with per-field details429 TOO_MANY_REQUESTS: Rate limit exceeded500 INTERNAL_SERVER_ERROR: Unexpected server errors, payment orchestration failuresSystemBearerjob checks for expired PENDING_PAYMENT sessionsChanges status to EXPIREDReleases held tickets automaticallyRuns periodically (e.g., every minute)After PAYMENT_COMPLETED, listener creates bookingUpdates status to COMPLETEDSets createdBookingOrderIdOn Checkout Creation:Tickets immediately "held" by incrementingIncludequantitySoldAuthorization: Bearer <token>SetsticketsHeld = trueSetsticketHoldExpiresAt = now + 15 minutesOther users see reduced availability
During Hold Period(15 minutes):Tickets appear as "sold" to other buyersActual availability = totalQuantity - quantitySoldBuyer must complete payment within window
On Payment Success:Hold becomes permanent (tickets truly sold)Booking order created with reserved ticketsQR codes generated
On Session Cancel:Hold released (quantitySold decremented)Tickets return to available poolOther buyers can now purchase
On Session Expiry(auto):Hold auto-released by background jobTickets return to available poolStatus changed to EXPIRED
Without holds: Multiple people could checkout same ticketsWith holds: Tickets reserved during checkout process15-minute window gives reasonable time to complete paymentNot too long (doesn't block others)Not too short (enough time to pay)Real-time availability shown toin allbuyersrequest No race conditions during high-demand salesBuying X tickets for themselvesThen buying X more tickets "for friends" using different emailsTotal: 2X tickets for same person (limit bypass)Collect all identities in current order:Buyer's email and phoneheaders- All
otherendpointsattendees'requireemails and phones
authentication- For
Checkscannerpast completed orders:Scan buyer's previous ordersCheck if any collected identity appears in:Previous buyer ticketsPrevious other attendee tickets
Calculate per-identity totals:Previous tickets for this identityCurrent tickets for this identityTotal = previous + current
Validate against limit:If any identity exceeds maxQuantityPerUser → BLOCKError shows masked identity and ticket counts
Order 1: Bought 3 VIP tickets for self (john@example.com)Identity "john@example.com" already has 3 ticketsCurrent order adds 2 (ticketsForMe) + 1 (other attendee) = 3 moreTotal: 3 + 3 = 6 ticketsExceeds limit of 5Email:john@example.com→j***@example.comPhone:+255712345678→+255***5678If buyer uses email A and phone BSystem checks BOTH separatelyPrevents using phone when email limit reachedFinds same email/phone across:Different buyer ordersOther attendee entriesMixed buyer/attendee combinations
maxQuantityPerUser = null→ UnlimitedmaxQuantityPerUser = 0→ UnlimitedmaxQuantityPerUser > 0→ EnforcedSingle Buyer: One person pays for entire orderMultiple Attendees: Buyer + any number of other peopleTicket Distribution: Buyer decides ticket allocationEmail Options: Can email QR codes to each attendeeTotal: 5 ticketsAll tickets for buyerAll QR codes sent to buyer onlyTotal: 5 tickets (2 + 2 + 1)Buyer gets 2 ticketsJane gets 2 tickets (QR codes emailed)Bob gets 1 ticket (QR code emailed)Total: 3 ticketsBuyer not attending (0 tickets)Jane gets all 3 ticketsUseful for giftsEach attendee receives their QR codes via emailBuyer also gets confirmation emailAttendees can present QR codes independentlyALL QR codes sent only to buyerBuyer must share codes with attendees manuallyUseful for group leaders managing entryOne payment for whole groupNo complex ticket transfersClear purchase ownershipBuy any combination (self + others)Can buy entirely for others (gifts)Easy group bookingsSingle payer responsibleNo split payment complicationsClear refund recipientmaxQuantityPerUser tracked across all attendeesPrevents limit bypass via "buying for friends"Fair access for all buyersService feesProcessing feesTaxesDiscountsCheckout session created (same validation)Tickets held (same as paid)Payment processing → No wallet deduction, no escrowBooking created immediatelyQR codes generatedEmails sentPrevents overselling (capacity still limited)Fair distribution (first-come-first-served)Same user experience (just skip payment step)Payment: Buyer pays from walletEscrow Created: Money held in escrow accountEvent Happens: Attendees attend eventEvent Ends: System checks event completionOrganizer Paid: Money released to organizerBuyer Protection: Get refund if event cancelledOrganizer Protection: Guaranteed payment after eventPlatform Trust: Money safely held by platformFraud Prevention: Cannot withdraw until event completeEvent marked as COMPLETEDEvent receives positive feedback (if feedback system enabled)Automatic release after event end + grace periodEvent cancelled by organizerEvent marked as fraudulentBuyer requests refund (before event)✅ Event must exist and not be deleted✅ Event must be PUBLISHED✅ Event start date must be in future✅ Ticket must exist and not be deleted✅ Tickettoken must belong tospecifiedtheeventaccount ✅linkedTickettostatusthemustscannerbe ACTIVE✅ Ticket must be currently on sale (within sales period)device✅ 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)✅ 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✅ Buyer wallet must exist and be active✅ Wallet balance must be ≥ total amount✅ Wallet must support payment currency (TZS)+255712345678(Vodacom)+255623456789(Airtel - wait, starts with 6)+255754567890(Tigo)0712345678(missing +255)255712345678(missing +)+255812345678(wrong prefix - should be 7 or 6)+2557123456(too short)+25571234567890(too long)Must start with +255Followed by 7 or 6 (mobile prefixes)Followed by exactly 8 digitsTotal: 13 characters200 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 errorCreation: InstantExpiry: 15 minutes from creationTicket Hold: 15 minutes from creationAuto-Release: Triggered by background job (runs every minute)- Dates:
LocalDateTimeISO 8601 format (2025-12-11T10:09-23T10:30:45) IDs: UUID formatMoney: 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 ✅ 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 COMPLETEDComplete payment within 15 minutesVerify attendee details before submittingUse correct phone formatsCheck wallet balance before checkoutAlways validate session status before paymentHandle expiry gracefullyImplement retry logic for failed paymentsShow countdown timer (15 min remaining)Auto-refresh availabilityClear error messages for limit violations400 Insufficient Balance: Show "Top Up Wallet" option400 Max Per User: Explain limit clearly, show previous purchases400 Session Expired: Offer "Create New Session" button400 Tickets Sold Out: Show "Notify When Available" option422 Validation: Highlight specific field errors
ForPossible Free Tickets (price = 0.00):
Payment RetryErrors:
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:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token of the authenticated attendee |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
sessionId |
Yes | Must be a valid UUID belonging to the authenticated user |
SuccessBusiness Response MessageRules:
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:
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:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token linked to the scanner's registered account |
Content-Type |
string | Yes | Must 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:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
scannerId |
string | Yes | Unique identifier of the registered scanner device | Must match a registered, active scanner with SELL_TICKETS permission |
deviceFingerprint |
string | Yes | Hardware fingerprint of the scanner device | Must match the fingerprint registered for this scanner |
ticketTypeId |
UUID | Yes | ID of the ticket type to sell | Must belong to the event this scanner is assigned to; must not ONLINE_ONLY; |
quantity |
integer | Yes | Total number of tickets |
|
attendees |
array | Yes | List of attendee details, one entry per ticket | Min: 1 entry; count must match quantity |
attendees[].fullName |
string | No | Full name of the attendee | Optional — if blank, a generated name like ATTENDEE-XXXX is assigned |
attendees[].email |
string | No | Email address of the attendee | Valid email format if provided |
attendees[].phoneNumber |
string | No | Phone number of the attendee | Optional |
immediateCheckIn |
boolean | Yes | If true, the ticket is marked as checked-in immediately upon sale |
Required |
CannotBusiness CancelRules:
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:
| Field | Description |
|---|---|
bookingId |
UUID of the created booking order |
bookingReference |
Human-readable booking reference number |
eventId |
ID of the event |
eventName |
Name of the event |
tickets |
Array of issued ticket instances — one per attendee |
tickets[].ticketInstanceId |
Unique ID of this specific ticket instance |
tickets[].ticketSeries |
Ticket serial number (e.g., VIP-0042-A) |
tickets[].ticketTypeName |
The type of the sold ticket |
tickets[].attendeeName |
Name of the attendee this ticket is assigned to |
tickets[].attendeeEmail |
Email of the attendee |
tickets[].checkedIn |
Whether the attendee has been checked in |
tickets[].checkInTime |
Timestamp of check-in if immediateCheckIn was true |
tickets[].qrCode |
JWT-encoded QR code string for this ticket |
totalAmount |
Total cash amount collected in TZS |
currency |
Always TZS |
paymentMethod |
Always CASH for at-door sales |
soldBy |
Name of the scanner that processed the sale |
soldAt |
Location label of the scanner (e.g., "Main Entrance") |
saleTime |
ISO 8601 timestamp of when the sale occurred |
Possible Errors:
| HTTP Status | Scenario |
|---|---|
400 BAD_REQUEST |
Attendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale |
401 UNAUTHORIZED |
Missing or invalid Bearer token |
403 FORBIDDEN |
Scanner does not have SELL_TICKETS permission, device fingerprint mismatch, scanner is inactive or expired |
404 NOT_FOUND |
Scanner ID not found, ticket type not found |
422 UNPROCESSABLE_ENTITY |
Validation errors on request fields |
500 INTERNAL_SERVER_ERROR |
Payment 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:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer token of the authenticated event organizer |
Content-Type |
string | Yes | Must be application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
eventId |
UUID | Yes | The ID of the event to sell tickets for | Must 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:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
ticketTypeId |
UUID | Yes | ID of the ticket type to sell | Must belong to the event in the path; must not be ONLINE_ONLY; must be on sale |
quantity |
integer | Yes | Total number of tickets to sell | Min: 1; must equal the number of attendees in the attendees array |
attendees |
array | Yes | List of attendee details — one entry per ticket | Min: 1 entry; count must match quantity |
attendees[].fullName |
string | No | Full name of the attendee | Optional — auto-generated if blank |
attendees[].email |
string | No | Email of the attendee | Valid email format if provided |
attendees[].phoneNumber |
string | No | Phone number of the attendee | Optional |
immediateCheckIn |
boolean | Yes | Whether to mark attendees as checked-in immediately | Required |
location |
string | No | Description of the sale point, e.g., "VIP Gate", "Main Counter" | Max: 200 characters; defaults to "Organizer Counter" if not provided |
Business Rules:
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:
| Field | Description |
|---|---|
bookingId |
UUID of the created booking order |
bookingReference |
Human-readable booking reference number |
eventId |
ID of the event |
eventName |
Name of the event |
tickets |
Array of issued ticket instances — one per attendee |
tickets[].ticketInstanceId |
Unique ID of this specific ticket instance |
tickets[].ticketSeries |
Ticket serial number |
tickets[].ticketTypeName |
The type of ticket sold |
tickets[].attendeeName |
Assigned attendee name |
tickets[].attendeeEmail |
Attendee email |
tickets[].checkedIn |
Whether immediately checked in |
tickets[].checkInTime |
Check-in timestamp, null if not checked in |
tickets[].qrCode |
JWT-encoded QR code string for this ticket |
totalAmount |
Total cash amount in TZS |
currency |
Always TZS |
paymentMethod |
Always CASH |
soldBy |
Username of the organizer who made the sale |
soldAt |
Location label provided in the request |
saleTime |
ISO 8601 timestamp of the sale |
Possible Errors:
| HTTP Status | Scenario |
|---|---|
400 BAD_REQUEST |
Attendee count does not match quantity, ticket is ONLINE_ONLY, ticket not on sale |
401 UNAUTHORIZED |
Missing or invalid Bearer token |
403 FORBIDDEN |
Authenticated user is not the organizer of the specified event |
404 NOT_FOUND |
Event not found, ticket type not found |
422 UNPROCESSABLE_ENTITY |
Validation errors on request fields |
500 INTERNAL_SERVER_ERROR |
Payment 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"
}
{
"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)
Server-Level Exceptions (500+)
Quick Reference
Endpoint Summary
| # | Method | Path | Description |
|---|---|---|---|
| 1 | POST | /api/v1/e-events/checkout |
Create online checkout session |
| 2 | GET | /api/v1/e-events/checkout/{sessionId} |
Get checkout session details |
| 3 | POST | /api/v1/e-events/checkout/{sessionId}/payment |
Process wallet payment |
| 4 | POST | /api/v1/e-events/checkout/{sessionId}/cancel |
Cancel checkout session |
| 5 | POST | /api/v1/e-events/checkout/sell-at-door-ticket/scanner |
Scanner at-door sale |
| 6 | POST | /api/v1/e-events/checkout/sell-at-door-ticket/{eventId}/organizer |
Organizer at-door sale |
Session Status Lifecycle
Status Flow Diagram
PENDING_PAYMENT → PAYMENT_COMPLETED → COMPLETED
↓ ↓
PAYMENT_FAILED CANCELLED
↓
EXPIRED
Status DescriptionsReference
PENDING_PAYMENT → PAYMENT_COMPLETED → COMPLETED
↓ ↓
PAYMENT_FAILED CANCELLED
↓
EXPIRED
| Status | ||||
|---|---|---|---|---|
PENDING_PAYMENT |
||||
PAYMENT_PROCESSING |
||||
PAYMENT_COMPLETED |
||||
PAYMENT_FAILED |
Payment attempt failed | |||
COMPLETED |
||||
CANCELLED |
||||
EXPIRED |
AutomaticTicket TransitionsPricing Type Behaviour
Pricing Type
Payment Required
At-Door Allowed
Notes
FREENo
Depends on sales channel
Auto-
Expiryprocessed on session creation
PAIDYes (
15Wallet)minutes):Yes
Escrow created on payment
DONATIONOptional amount
No (Online only)
Max 1 ticket per order; no other attendees
Sales Channel Rules
Sales Channel
Online Checkout
At-Door (Scanner)
At-Door (Organizer)
ONLINE_ONLY✅ Allowed
❌ Blocked
❌ Blocked
AT_DOOR_ONLY❌ Blocked
✅ Allowed
✅ Allowed
BOTH✅ Allowed
✅ Allowed
✅ Allowed
Authentication Reference
Auto-CompletionToken:
Ticket Hold System
How Ticket Holds Work
Why Ticket Holds?
Prevents Overselling:
Fair Access:
Inventory Accuracy:
Max Quantity Per User - Identity Tracking
The Problem
Users could bypass maxQuantityPerUser by:
The Solution
System tracks purchases across all identities (email + phone):
Purchase Check Algorithm:
Example Scenario:
Ticket Limit: 5 per user for "VIP Pass"
User's Past Orders:
Current Order Attempt:
{
"ticketsForMe": 2,
"otherAttendees": [
{
"email": "john@example.com", // ❌ Same as buyer's email
"quantity": 1
}
]
}
Result: BLOCKED
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:
Edge Cases Handled
Multiple Identities:
Cross-Order Detection:
Flexible Limits:
Multi-Attendee Purchase Model
Design Philosophy
Simplified Model: Buyer purchases ALL tickets, can distribute to friends/family
Key Concepts:
Purchase Scenarios
Scenario 1: Only for Myself
{
"ticketsForMe": 5,
"otherAttendees": null
}
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
}
Scenario 3: Only for Friends (Gift)
{
"ticketsForMe": 0,
"otherAttendees": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+255712345678",
"quantity": 3
}
],
"sendTicketsToAttendees": true
}
Ticket Distribution Options
sendTicketsToAttendees = true (Default):
sendTicketsToAttendees = false:
Benefits of This Model
Simplicity:
Flexibility:
Payment Clarity:
Limit Enforcement:
Pricing Calculation
Current Model (Simplified)
Subtotal = unitPrice × totalQuantity
Total = Subtotal
No fees currently, but structure supports future additions:
Free Tickets (price = 0.00)
{
"pricing": {
"subtotal": 0.00,
"total": 0.00
}
}
Free Ticket Flow:
Why hold free tickets?
Escrow Protection System
What is Escrow?
For Paid Tickets:
Why Escrow?
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:
Refund Triggers:
Validation Rules Summary
Event Validation
Ticket Validation
Quantity Validation
Attendee Validation
Wallet Validation
Phone Number Format
Tanzania Phone Format
Pattern: +255[67]xxxxxxxx
Valid Examples:
Invalid Examples:
Regex: ^\\+255[67]\\d{8}$
Validation:
Quick Reference Guide
Common HTTP Status Codes
Session Timing
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
Checkout Flow Checklist
Best Practices
For Users:
For Developers:
Error Handling Tips
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