Checkout Session
Base URL: https://apinexgate.glueauth.com/api/v1/
Short Description: The Checkout Session API manages the complete checkout process for both e-commerce and event transactions. It handles session creation (with upfront wallet balance validation), inventory holds, payment processing via Wallet, Cash, and Free flows, group purchasing, and installment payments. Each session maintains state, inventory holds, and payment attempt tracking with automatic expiration handling.
Hints:
- Wallet balance is validated at session creation time — if insufficient, no session is created and a rich balance response is returned so the frontend can guide the user to top up
- All checkout sessions expire after 15 minutes by default
- Inventory is automatically held during active sessions and released upon expiration or cancellation
- Maximum 5 payment retry attempts allowed per session
- Group purchase sessions require WALLET payment method only
- Sessions in PAYMENT_PROCESSING status cannot be modified
- Installment payment only charges the down payment at checkout; monthly payments are handled by the scheduler
- All monetary values are in TZS (Tanzanian Shillings)
- Use ISO 8601 format for all datetime fields
- Sessions can only be updated when in PENDING_PAYMENT or PAYMENT_FAILED status
Full Checkout Flow Diagram
CLIENT BACKEND SYSTEMS
| | |
| POST /checkout-sessions | |
|------------------------------>| |
| |-- Validate request |
| |-- Calculate pricing |
| | |
| |-- assertSufficientBalance() |
| | | |
| | |-- GET wallet balance -|-> Ledger
| | | |
| | ....................... |
| | . BALANCE INSUFFICIENT . |
| | ....................... |
| 422 + rich balance data | | |
|<------------------------------|<-------' |
| { | |
| walletBalance: 5000, | |
| sessionTotal: 12000, | |
| shortfall: 7000, | |
| recommendedTopUp: 7000 | |
| } | |
| | |
| | ....................... |
| | . BALANCE SUFFICIENT . |
| | ....................... |
| | | |
| |-- Hold inventory ------------>|-> Inventory
| |-- Save session |
| |-- Schedule expiry job ------->|-> JobRunr
| | |
| 201 + session data | |
|<------------------------------| |
| { sessionId, status: | |
| PENDING_PAYMENT, ... } | |
| | |
| ...15 min window... | |
| | |
| POST /{sessionId}/payment | |
|------------------------------>| |
| |-- Validate status |
| |-- Check expiry |
| | |
| | ....................... |
| | . FREE checkout . |
| | ....................... |
| | | |
| |-- Create booking/order |
| |-- Track free transaction |
| 200 SUCCESS | |
|<------------------------------| |
| | |
| | ....................... |
| | . CASH checkout . |
| | ....................... |
| | | |
| |-- Create booking/order |
| |-- Track cash transaction |
| 200 SUCCESS | |
|<------------------------------| |
| | |
| | ....................... |
| | . WALLET checkout . |
| | ....................... |
| | | |
| |-- Set PAYMENT_PROCESSING |
| |-- holdMoney() --------------->|-> Escrow
| | |
| | ....................... |
| | . SUCCESS . |
| | ....................... |
| | | |
| |-- Set PAYMENT_COMPLETED |
| |-- Create order/booking |
| |-- Commit inventory hold ----->|-> Inventory
| |-- Publish payment event ----->|-> Notifications
| 200 SUCCESS | |
|<------------------------------| |
| { orderId, escrowId, | |
| amountPaid, ... } | |
| | |
| | ....................... |
| | . FAILED . |
| | ....................... |
| | | |
| |-- recordFailedAttempt() |
| |-- Release reservation |
| | (events only) |
| 200 FAILED | |
|<------------------------------| |
| { success: false, | |
| canRetry: true/false } | |
| | |
| POST /{sessionId}/retry | |
|------------------------------>| |
| |-- Check status = FAILED |
| |-- Check attempts < 5 |
| |-- Re-validate inventory |
| |-- Check wallet balance |
| |-- Re-hold inventory |
| |-- Reset to PENDING_PAYMENT |
| |-- processPayment() ... |
| | (same wallet flow above) |
Session Type Routing
POST /checkout-sessions
sessionType?
.
├─ REGULAR_DIRECTLY ──> validate 1 item ──> balance check ──> hold inventory ──> session
|
├─ REGULAR_CART ──────> validate cart ───> balance check ──> hold inventory ──> session
|
├─ GROUP_PURCHASE ────> validate group ──> balance check ──> session (no hold, group-level)
|
└─ INSTALLMENT ───────> calculate down payment ──> balance check ──> hold inventory ──> session
POST /e-events/checkout
ticketPricingType?
.
├─ PAID ──> balance check ──> create payment intent ──> reserve ticket ──> session
|
└─ FREE ──> (no balance check) ──> reserve ticket ──> session
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-10-02T10:30:45",
"data": {}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-10-02T10:30:45",
"data": "Error description"
}
Insufficient Balance Error Structure (422)
This is a special structured error returned when wallet balance is insufficient at session creation time. The data field contains rich balance details so the frontend can guide the user to top up.
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Insufficient wallet balance to complete checkout",
"action_time": "2025-10-02T10:30:45",
"data": {
"walletBalance": 5000.00,
"sessionTotal": 12000.00,
"shortfall": 7000.00,
"hasSufficientBalance": false,
"recommendedTopUp": 7000.00,
"pspMinimum": 500.00,
"currency": "TZS"
}
}
| Field | Description |
|---|---|
walletBalance |
Current wallet balance of the user |
sessionTotal |
Total amount required for this checkout |
shortfall |
How much is missing (sessionTotal - walletBalance) |
hasSufficientBalance |
Always false when this error is returned |
recommendedTopUp |
Suggested top-up amount (at least shortfall, rounded up to PSP minimum) |
pspMinimum |
Minimum top-up amount accepted by the payment provider |
currency |
Always TZS |
Standard Response Fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true for successful operations, false for errors |
httpStatus |
string | HTTP status name (OK, CREATED, 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
For better visual clarity, all endpoints use colored badges for HTTP methods with the following standard colors:
- GET - GET - Green (Safe, read-only operations)
- POST - POST - Blue (Create new resources)
- PATCH - PATCH - Orange (Partial updates)
- DELETE - DELETE - Red (Remove resources)
Endpoints
1. Create Checkout Session
Purpose: Creates a new checkout session for processing a purchase. Wallet balance is validated upfront — if insufficient, no session is created and the caller receives rich balance data to guide the user. Supports multiple checkout types: direct product purchase, cart checkout, group purchasing, and installment payments.
Endpoint: POST {base_url}/checkout-sessions
Access Level: 🔒 Protected (Requires Authentication)
Authentication: Bearer Token required in Authorization header
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
| Content-Type | string | Yes | Must be application/json |
Request JSON Sample:
{
"sessionType": "REGULAR_DIRECTLY",
"items": [
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"quantity": 2
}
],
"shippingAddressId": "f1e2d3c4-b5a6-7890-cdef-123456789abc",
"shippingMethodId": "standard-shipping",
"metadata": {
"couponCode": "SAVE20",
"referralCode": "REF123",
"notes": "Please handle with care"
},
"installmentPlanId": null,
"groupInstanceId": null,
"downPaymentPercent": 20
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| sessionType | string | Yes | Type of checkout session | enum: REGULAR_DIRECTLY, REGULAR_CART, GROUP_PURCHASE, INSTALLMENT |
| items | array | Conditional | Array of items to checkout. Required for REGULAR_DIRECTLY and GROUP_PURCHASE | For REGULAR_DIRECTLY: exactly 1 item. For GROUP_PURCHASE: exactly 1 item. Not used for REGULAR_CART |
| items[].productId | string (UUID) | Yes (if items provided) | Product identifier | Valid UUID format |
| items[].quantity | integer | Yes (if items provided) | Quantity to purchase | Min: 1, must not exceed product constraints |
| shippingAddressId | string (UUID) | Yes | User's shipping address identifier | Valid UUID format, must belong to authenticated user |
| shippingMethodId | string | Yes | Selected shipping method identifier | Must be valid shipping method |
| metadata | object | No | Additional metadata for the session | Key-value pairs for coupons, referrals, notes, etc. |
| installmentPlanId | string (UUID) | Conditional | Installment plan identifier (INSTALLMENT type only) | Valid UUID format |
| downPaymentPercent | integer | Conditional | Down payment percentage (INSTALLMENT type only) | Must satisfy plan's min/max constraints |
| groupInstanceId | string (UUID) | No | Group instance to join (GROUP_PURCHASE type only) | Valid UUID format, group must be joinable. Null = create new group |
| groupName | string | Conditional | Name for new group (GROUP_PURCHASE, groupInstanceId null) | Must be unique per product |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "CREATED",
"message": "Checkout session created successfully",
"action_time": "2025-10-02T14:30:45",
"data": {
"sessionId": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"sessionType": "REGULAR_DIRECTLY",
"status": "PENDING_PAYMENT",
"customerId": "u1s2e3r4-i5d6-7890-abcd-ef1234567890",
"customerUserName": "john_doe",
"items": [
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"productName": "Premium Wireless Headphones",
"productSlug": "premium-wireless-headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"quantity": 2,
"unitPrice": 150000.00,
"discountAmount": 20000.00,
"subtotal": 300000.00,
"tax": 0.00,
"total": 280000.00,
"shopId": "s1h2o3p4-i5d6-7890-abcd-ef1234567890",
"shopName": "TechWorld Electronics",
"shopLogo": "https://cdn.nextgate.com/shops/techworld-logo.jpg",
"availableForCheckout": true,
"availableQuantity": 50
}
],
"pricing": {
"subtotal": 300000.00,
"discount": 20000.00,
"shippingCost": 5000.00,
"tax": 0.00,
"total": 285000.00,
"currency": "TZS"
},
"shippingAddress": {
"fullName": "John Doe",
"addressLine1": "123 Main Street",
"addressLine2": "Apartment 4B",
"city": "Dar es Salaam",
"state": "Dar es Salaam Region",
"postalCode": "12345",
"country": "Tanzania",
"phone": "+255123456789"
},
"shippingMethod": {
"id": "standard-shipping",
"name": "Standard Shipping",
"carrier": "DHL",
"cost": 5000.00,
"estimatedDays": "3-5 business days",
"estimatedDelivery": "2025-10-07T14:30:45"
},
"paymentIntent": {
"provider": "WALLET",
"clientSecret": null,
"paymentMethods": ["WALLET"],
"status": "READY"
},
"paymentAttempts": [],
"inventoryHeld": true,
"inventoryHoldExpiresAt": "2025-10-02T14:45:45",
"metadata": {
"couponCode": "SAVE20",
"referralCode": "REF123",
"notes": "Please handle with care"
},
"expiresAt": "2025-10-02T14:45:45",
"createdAt": "2025-10-02T14:30:45",
"updatedAt": "2025-10-02T14:30:45",
"completedAt": null,
"createdOrderId": null,
"cartId": null
}
}
Success Response Fields:
| Field | Description |
|---|---|
| sessionId | Unique identifier for the checkout session |
| sessionType | Type of checkout (REGULAR_DIRECTLY, REGULAR_CART, GROUP_PURCHASE, INSTALLMENT) |
| status | Current status of the session (always PENDING_PAYMENT on creation) |
| customerId | User ID who created the session |
| customerUserName | Username of the customer |
| items | Array of checkout items with product details, pricing, and availability |
| pricing | Summary of all pricing calculations |
| pricing.total | Final amount to be charged (what was validated against wallet balance) |
| pricing.currency | Always TZS |
| shippingAddress | Complete shipping address details |
| shippingMethod | Selected shipping method details |
| paymentIntent | Payment processing details |
| paymentAttempts | Empty array on creation |
| inventoryHeld | Whether inventory is currently held (false for GROUP_PURCHASE) |
| inventoryHoldExpiresAt | When the inventory hold will be released |
| expiresAt | When this checkout session expires (15 minutes from creation) |
| createdOrderId | null until payment succeeds |
| cartId | Cart ID if session was created from cart, null otherwise |
Error Responses:
Insufficient Wallet Balance (422) — includes rich top-up data:
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Insufficient wallet balance to complete checkout",
"action_time": "2025-10-02T14:30:45",
"data": {
"walletBalance": 150000.00,
"sessionTotal": 285000.00,
"shortfall": 135000.00,
"hasSufficientBalance": false,
"recommendedTopUp": 135000.00,
"pspMinimum": 500.00,
"currency": "TZS"
}
}
Bad Request - Invalid Session Type (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "REGULAR_DIRECTLY checkout supports only 1 item. Use REGULAR_CART for multiple items.",
"action_time": "2025-10-02T14:30:45",
"data": "REGULAR_DIRECTLY checkout supports only 1 item. Use REGULAR_CART for multiple items."
}
Bad Request - Insufficient Inventory (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Insufficient stock. Available: 3, Requested: 5",
"action_time": "2025-10-02T14:30:45",
"data": "Insufficient stock. Available: 3, Requested: 5"
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-10-02T14:30:45",
"data": {
"sessionType": "must not be null",
"items[0].quantity": "must be greater than or equal to 1",
"shippingAddressId": "must not be null"
}
}
Not Found - Product Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found",
"action_time": "2025-10-02T14:30:45",
"data": "Product not found"
}
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Authentication token is required",
"action_time": "2025-10-02T14:30:45",
"data": "Authentication token is required"
}
2. Get Checkout Session by ID
Purpose: Retrieves detailed information about a specific checkout session by its ID. Only the session owner can access their session.
Endpoint: GET {base_url}/checkout-sessions/{sessionId}
Access Level: 🔒 Protected (Requires Authentication and Ownership)
Authentication: Bearer Token required in Authorization header
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| sessionId | string (UUID) | Yes | Unique identifier of the checkout session | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Checkout session retrieved successfully",
"action_time": "2025-10-02T14:35:45",
"data": {
"sessionId": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"sessionType": "REGULAR_DIRECTLY",
"status": "PENDING_PAYMENT",
"customerId": "u1s2e3r4-i5d6-7890-abcd-ef1234567890",
"customerUserName": "john_doe",
"items": [],
"pricing": {},
"shippingAddress": {},
"shippingMethod": {},
"paymentIntent": {},
"paymentAttempts": [],
"inventoryHeld": true,
"inventoryHoldExpiresAt": "2025-10-02T14:45:45",
"metadata": { "couponCode": "SAVE20" },
"expiresAt": "2025-10-02T14:45:45",
"createdAt": "2025-10-02T14:30:45",
"updatedAt": "2025-10-02T14:30:45",
"completedAt": null,
"createdOrderId": null,
"cartId": null
}
}
Error Responses:
Not Found - Session Not Found or No Permission (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Checkout session not found or you don't have permission to access it",
"action_time": "2025-10-02T14:35:45",
"data": "Checkout session not found or you don't have permission to access it"
}
3. Get My Checkout Sessions
Purpose: Retrieves all checkout sessions belonging to the authenticated user, ordered by creation date (newest first).
Endpoint: GET {base_url}/checkout-sessions
Access Level: 🔒 Protected (Requires Authentication)
Success Response Fields:
| Field | Description |
|---|---|
| sessionId | Unique identifier for the checkout session |
| sessionType | Type of checkout session |
| status | Current status of the session |
| itemCount | Number of items in the checkout |
| totalAmount | Total amount to be paid in TZS |
| currency | Currency code (TZS) |
| expiresAt | When this session expires |
| createdAt | When this session was created |
| isExpired | Whether the session has expired |
| canRetryPayment | true only if status is PAYMENT_FAILED and not expired and attempts < 5 |
| itemPreviews | Array of preview information for items |
4. Get My Active Checkout Sessions
Purpose: Retrieves only active checkout sessions (PENDING_PAYMENT or PAYMENT_FAILED status) that haven't expired yet, ordered by creation date (newest first).
Endpoint: GET {base_url}/checkout-sessions/active
Access Level: 🔒 Protected (Requires Authentication)
Notes: Response structure is the same as Get My Checkout Sessions but filtered to active sessions only. isExpired is always false in this response.
5. Update Checkout Session
Purpose: Updates an existing checkout session. Can modify shipping address, shipping method, or metadata. Only sessions in PENDING_PAYMENT or PAYMENT_FAILED status can be updated.
Endpoint: PATCH {base_url}/checkout-sessions/{sessionId}
Access Level: 🔒 Protected (Requires Authentication and Ownership)
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string (UUID) | Yes | Unique identifier of the checkout session |
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shippingAddressId | string (UUID) | No | New shipping address identifier |
| shippingMethodId | string | No | New shipping method (triggers pricing recalculation) |
| metadata | object | No | Key-value pairs, merged with existing metadata |
Error Responses:
Cannot Update Completed Session (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot update a completed checkout session",
"action_time": "2025-10-02T14:50:45",
"data": "Cannot update a completed checkout session"
}
6. Cancel Checkout Session
Purpose: Cancels an existing checkout session and releases held inventory. Cannot cancel sessions that are completed or have successful payment.
Endpoint: DELETE {base_url}/checkout-sessions/{sessionId}/cancel
Access Level: 🔒 Protected (Requires Authentication and Ownership)
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Checkout session cancelled successfully",
"action_time": "2025-10-02T14:55:45",
"data": null
}
Error Responses:
Cannot Cancel Completed (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot cancel - payment has been completed. Please contact support.",
"action_time": "2025-10-02T14:55:45",
"data": "Cannot cancel - payment has been completed. Please contact support."
}
Already Cancelled (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Checkout session is already cancelled",
"action_time": "2025-10-02T14:55:45",
"data": "Checkout session is already cancelled"
}
7. Process Payment
Purpose: Initiates payment processing for a checkout session in PENDING_PAYMENT status. Routes to the appropriate payment processor (WALLET, CASH, FREE) based on the session amount and payment method.
Endpoint: POST {base_url}/checkout-sessions/{sessionId}/process-payment
Access Level: 🔒 Protected (Requires Authentication and Ownership)
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string (UUID) | Yes | Unique identifier of the checkout session |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Payment completed successfully. Your order is being processed.",
"action_time": "2025-10-02T15:00:45",
"data": {
"success": true,
"status": "SUCCESS",
"message": "Payment completed successfully. Your order is being processed.",
"checkoutSessionId": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"escrowId": "esc-uuid",
"escrowNumber": "ESC-20251002-001",
"orderId": "ord-uuid",
"paymentMethod": "WALLET",
"amountPaid": 285000.00,
"platformFee": 5700.00,
"sellerAmount": 279300.00,
"currency": "TZS"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| success | Whether the payment was successful |
| status | SUCCESS, PENDING, or FAILED |
| checkoutSessionId | The session that was paid |
| escrowId | Escrow account holding the funds |
| escrowNumber | Human-readable escrow reference |
| orderId | Order or booking ID created after payment |
| paymentMethod | WALLET, CASH, or FREE |
| amountPaid | Total amount charged in TZS |
| platformFee | Platform fee deducted |
| sellerAmount | Amount the seller receives |
| currency | Always TZS |
Error Responses:
Session Not in PENDING_PAYMENT status (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot process payment - session is not pending: PAYMENT_COMPLETED",
"action_time": "2025-10-02T15:00:45",
"data": "Cannot process payment - session is not pending: PAYMENT_COMPLETED"
}
Session Expired (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Checkout session has expired",
"action_time": "2025-10-02T15:00:45",
"data": "Checkout session has expired"
}
8. Retry Payment
Purpose: Retries payment for a session in PAYMENT_FAILED status. Re-validates inventory availability, checks wallet balance, re-holds inventory, and extends session expiration before retrying. Maximum 5 total attempts.
Endpoint: POST {base_url}/checkout-sessions/{sessionId}/retry-payment
Access Level: 🔒 Protected (Requires Authentication and Ownership)
Error Responses:
Invalid Status (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot retry payment - session status: PENDING_PAYMENT. Expected: PAYMENT_FAILED",
"action_time": "2025-10-02T15:10:45",
"data": "Cannot retry payment - session status: PENDING_PAYMENT. Expected: PAYMENT_FAILED"
}
Max Attempts Exceeded (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Maximum payment attempts (5) exceeded. Please create a new checkout session.",
"action_time": "2025-10-02T15:10:45",
"data": "Maximum payment attempts (5) exceeded. Please create a new checkout session."
}
Insufficient Wallet Balance on Retry (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Insufficient wallet balance. Required: 285000 TZS, Available: 150000 TZS. Please top up your wallet.",
"action_time": "2025-10-02T15:10:45",
"data": "Insufficient wallet balance. Required: 285000 TZS, Available: 150000 TZS. Please top up your wallet."
}
Product No Longer Available (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Product 'Premium Wireless Headphones' is no longer available in requested quantity. Please create a new checkout session.",
"action_time": "2025-10-02T15:10:45",
"data": "Product 'Premium Wireless Headphones' is no longer available in requested quantity. Please create a new checkout session."
}
Checkout Session Types
REGULAR_DIRECTLY
Direct product purchase without using a cart. Must include exactly 1 item.
- Best for "Buy Now" buttons
- Balance is checked against
pricing.total(includes shipping) - Inventory is held immediately on session creation
REGULAR_CART
Checkout from existing shopping cart. Items are fetched automatically from the user's active cart.
- Multi-item purchase flow
- Items array not required in request
- Balance is checked against
pricing.total(includes shipping) - Inventory is held for all cart items
GROUP_PURCHASE
Group buying checkout where multiple users purchase the same product at a discounted group price.
- Single item at
product.groupPrice - Balance is checked against
groupPrice × quantity - WALLET payment method only
- Can join existing group (provide
groupInstanceId) or create new group (providegroupName) - No session-level inventory hold — inventory is held at the group level after payment
Create New Group:
{
"sessionType": "GROUP_PURCHASE",
"items": [{ "productId": "prod-uuid", "quantity": 2 }],
"shippingAddressId": "addr-uuid",
"shippingMethodId": "standard",
"groupName": "My Winning Group"
}
Join Existing Group:
{
"sessionType": "GROUP_PURCHASE",
"items": [{ "productId": "prod-uuid", "quantity": 3 }],
"shippingAddressId": "addr-uuid",
"shippingMethodId": "standard",
"groupInstanceId": "group-uuid-to-join"
}
INSTALLMENT
Split payment over multiple months. Only the down payment is charged at checkout; remaining monthly payments are processed automatically by the scheduler.
- Balance is checked against
downPaymentAmountonly (not the full product price) installmentPlanIdanddownPaymentPercentare requiredpricing.totalin the session response reflects the down payment only- Inventory is held immediately on session creation
{
"sessionType": "INSTALLMENT",
"items": [{ "productId": "prod-uuid", "quantity": 1 }],
"shippingAddressId": "addr-uuid",
"shippingMethodId": "standard",
"installmentPlanId": "plan-uuid",
"downPaymentPercent": 20
}
Checkout Session Status Flow
Status Definitions
| Status | Description | Can Update? | Can Cancel? | Can Pay? | Can Retry? |
|---|---|---|---|---|---|
| PENDING_PAYMENT | Session created, awaiting payment | Yes | Yes | Yes | No |
| PAYMENT_PROCESSING | Payment in progress | No | No | No | No |
| PAYMENT_FAILED | Payment failed, can retry | Yes | Yes | No | Yes |
| PAYMENT_COMPLETED | Payment successful | No | No | No | No |
| EXPIRED | Session expired | No | No | No | No |
| CANCELLED | User cancelled | No | No | No | No |
| COMPLETED | Free/cash order completed | No | No | No | No |
Status Transition Flow
[Session Creation]
|
| balance check passes
v
PENDING_PAYMENT
|
|-- user cancels ──────────────────> CANCELLED
| (inventory released)
|
|-- 15 min timeout ────────────────> EXPIRED
| (inventory released)
|
|-- processPayment()
|
|-- amount = 0 ────────────────> COMPLETED (free)
|
|-- CASH ──────────────────────> COMPLETED (cash)
|
|-- WALLET ────────────────────> PAYMENT_PROCESSING
|
|-- success ──> PAYMENT_COMPLETED
| (order/booking created,
| inventory committed)
|
|-- failure ──> PAYMENT_FAILED
(inventory released for events,
max 5 retries)
|
|-- retryPayment() ──> PENDING_PAYMENT
| (inventory re-held)
|
|-- 5 attempts ──────> EXPIRED
Wallet Balance Check Endpoint
For cases where the frontend wants to proactively check balance against an existing session (e.g., before showing the "Pay Now" button), use:
Endpoint: GET {base_url}/wallet/checkout-balance-check?sessionId={id}&domain={PRODUCT|EVENT}
Success Response:
{
"success": true,
"httpStatus": "OK",
"message": "Checkout balance check completed",
"data": {
"walletBalance": 150000.00,
"sessionTotal": 285000.00,
"shortfall": 135000.00,
"hasSufficientBalance": false,
"recommendedTopUp": 135000.00,
"pspMinimum": 500.00,
"currency": "TZS"
}
}
Note: This endpoint always returns 200 — it never throws. Use hasSufficientBalance to determine if the user can pay. This is the "soft check" for an existing session; the "hard check" (which blocks session creation) happens automatically inside POST /checkout-sessions.
Payment Methods Supported
WALLET (Default)
Internal wallet system. Default if no payment method is specified.
- Balance validated at session creation (hard block) and again at payment time (safety net)
- Instant processing via escrow
- Funds held in escrow until order is confirmed/delivered
CASH
Pay in cash on delivery or at the point of event check-in.
- No pre-payment required
- Order/booking created immediately
- Applicable to both product and event checkouts
FREE
Zero-amount checkout (free products or free event tickets).
- Handled automatically when
pricing.total = 0 - No payment method required
- Order/booking created immediately
CREDIT_CARD / MOBILE_MONEY
Status: Planned — not yet implemented.
Inventory Management
Hold Mechanism
| Session Type | Hold Created At | Hold Released At |
|---|---|---|
| REGULAR_DIRECTLY | Session creation | Expiry, cancellation, or payment success (committed) |
| REGULAR_CART | Session creation | Expiry, cancellation, or payment success (committed) |
| GROUP_PURCHASE | After payment, at group level | Group expiry or group failure |
| INSTALLMENT | Session creation | Expiry, cancellation, or payment success (committed) |
On successful payment, holds are committed (stock permanently deducted). On failure/expiry/cancellation, holds are released (stock returned to available).
Session Expiration
Default: 15 minutes from creation.
Extended when: Payment retry is initiated (adds 15 minutes).
On expiry:
- Status → EXPIRED
- Held inventory released
- Session cannot be updated, paid, or cancelled
- User must create a new checkout session
Payment Attempts Tracking
Maximum 5 attempts per session. Each attempt records:
- Attempt number (1–5)
- Payment method used
- Status: SUCCESS, FAILED, or RETRY_INITIATED
- Error message (if failed)
- Timestamp and transaction ID
After 5 failed attempts, session status moves to EXPIRED and inventory is released.
Error Handling Best Practices
Frontend Checklist
Before calling POST /checkout-sessions:
- Ensure the user has a shipping address saved
- No need to pre-check balance — the API returns rich balance data if insufficient
On 422 Insufficient Balance response:
- Read
data.shortfallto show how much the user is short - Read
data.recommendedTopUpto pre-fill a top-up amount - Navigate the user to the wallet top-up screen
- Once topped up, retry
POST /checkout-sessions— do not store the failed session
During active session (PENDING_PAYMENT):
- Show a countdown timer using
expiresAt - On expiry, prompt user to create a new session
On payment failure:
- Show
canRetryPaymentto decide whether to show a retry button - Show remaining attempts (
5 - paymentAttemptCount) - On retry, call
POST /{sessionId}/retry-payment— no need to create a new session
Integration Examples
Example 1: Direct Product Purchase
1. POST /checkout-sessions
→ 422 if balance insufficient (show top-up screen with data.recommendedTopUp)
→ 201 with sessionId if balance OK
2. POST /checkout-sessions/{sessionId}/process-payment
→ 200 with orderId on success
Example 2: Cart Checkout
1. POST /checkout-sessions { sessionType: REGULAR_CART, ... }
→ 422 or 201
2. (optional) PATCH /checkout-sessions/{sessionId} { shippingMethodId: "express" }
3. POST /checkout-sessions/{sessionId}/process-payment
Example 3: Group Purchase
1. POST /checkout-sessions { sessionType: GROUP_PURCHASE, groupName: "...", ... }
→ 422 if balance < groupPrice × qty
→ 201 with sessionId
2. POST /checkout-sessions/{sessionId}/process-payment
(WALLET only)
Example 4: Installment
1. POST /checkout-sessions { sessionType: INSTALLMENT, installmentPlanId: "...", downPaymentPercent: 20 }
→ 422 if balance < downPaymentAmount
→ 201 with sessionId (pricing.total = down payment only)
2. POST /checkout-sessions/{sessionId}/process-payment
→ charges down payment only; monthly payments handled by scheduler
Example 5: Payment Retry
1. GET /checkout-sessions/active
→ find session with status: PAYMENT_FAILED, canRetryPayment: true
2. POST /checkout-sessions/{sessionId}/retry-payment
→ re-validates inventory + balance
→ re-holds inventory
→ processes payment
Rate Limiting
| Endpoint | Limit |
|---|---|
| Create Checkout | 20 req/min per user |
| Get Sessions | 60 req/min per user |
| Process / Retry Payment | 10 req/min per user |
| Update / Cancel | 30 req/min per user |
No comments to display
No comments to display