E_commerce-nexgate-service(7)
- Cart Management
- Shops Management Service
- Products Management Service
- Checkout Session
- Group Purchase
- Installment Purchase
- Installment Purchase - Customer Endpoints
- Installment Purchase - Public & Plan Endpoints
- Installment Plan Management - Admin/Shop Endpoints
- Order Management
- Marketplace
Cart Management
Short Description: The Cart Management API provides shopping cart functionality for the NextGate e-commerce platform. It supports adding products to cart, updating quantities, removing items, and real-time stock validation.
Base URL: api/v1/e-commerce/cart
Hints:
- All cart operations require user authentication via Bearer token
- Each user has one persistent cart that maintains state across sessions
- Real-time stock validation prevents overselling
- Adding an existing product updates its quantity (additive)
- Cart persistence survives user logout/login cycles
Endpoints
1. Add Product to Cart
Purpose: Adds a product to the user's cart or updates quantity if already exists
Endpoint: POST {base}/add
Access Level: ๐ Protected (Requires authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | application/json |
Request JSON Sample:
{
"productId": "456e7890-e89b-12d3-a456-426614174001",
"quantity": 2
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| productId | UUID | Yes | ID of the product to add | Not null, product must exist and be active |
| quantity | integer | Yes | Quantity to add to cart | Min: 1 |
Response JSON Sample (New Item):
{
"success": true,
"message": "Product added to cart successfully",
"data": null
}
Response JSON Sample (Updated Existing):
{
"success": true,
"message": "Product quantity updated in cart successfully",
"data": null
}
Business Logic:
- Creates cart automatically if one doesn't exist
- If product already in cart, adds to existing quantity
- Validates total quantity doesn't exceed available stock
- Only allows active products to be added
Error Responses:
400 Bad Request: Invalid productId or quantity validation error401 Unauthorized: Authentication required404 Not Found: Product not found or not active422 Unprocessable Entity: Insufficient stock available
Error Examples:
{
"success": false,
"message": "Insufficient stock for 'iPhone 15 Pro'. Only 3 units available",
"data": null
}
{
"success": false,
"message": "Cannot add more items. Total quantity (8) would exceed available stock (5) for 'MacBook Pro'",
"data": null
}
2. Get Shopping Cart
Purpose: Retrieves the complete shopping cart with all items and pricing
Endpoint: GET {base}
Access Level: ๐ Protected (Requires authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Response JSON Sample:
{
"success": true,
"message": "Shopping cart retrieved successfully",
"data": {
"user": {
"userId": "111e2222-e89b-12d3-a456-426614174003",
"userName": "john.doe",
"name": "John Doe"
},
"cartSummary": {
"totalItems": 2,
"totalQuantity": 3,
"subtotal": 2198.00,
"totalDiscount": 0.00,
"totalAmount": 2198.00
},
"cartItems": [
{
"itemId": "cart001-e89b-12d3-a456-426614174004",
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max 512GB",
"productSlug": "iphone-15-pro-max-512gb",
"productImage": "https://example.com/images/iphone15-pro-max.jpg",
"productType": "PHYSICAL",
"unitPrice": 1199.00,
"quantity": 2,
"itemSubtotal": 2398.00,
"totalPrice": 2398.00,
"shop": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "TechStore Pro",
"shopSlug": "techstore-pro",
"logoUrl": "https://example.com/logos/techstore-pro.png"
},
"availability": {
"inStock": true,
"availableQuantity": 25,
"maxPerCustomer": 5
},
"addedAt": "2025-09-23T10:30:00Z"
},
{
"itemId": "cart002-e89b-12d3-a456-426614174005",
"productId": "567e8901-e89b-12d3-a456-426614174002",
"productName": "MacBook Air M3",
"productSlug": "macbook-air-m3",
"productImage": "https://example.com/images/macbook-air-m3.jpg",
"productType": "PHYSICAL",
"unitPrice": 999.00,
"quantity": 1,
"itemSubtotal": 999.00,
"totalPrice": 999.00,
"shop": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "TechStore Pro",
"shopSlug": "techstore-pro",
"logoUrl": "https://example.com/logos/techstore-pro.png"
},
"availability": {
"inStock": true,
"availableQuantity": 8,
"maxPerCustomer": null
},
"addedAt": "2025-09-23T11:15:00Z"
}
],
"updatedAt": "2025-09-23T14:20:00Z"
}
}
Response Structure:
User Summary
| Field | Description |
|---|---|
| userId | Unique identifier of the cart owner |
| userName | User's login username |
| name | User's full name (firstName + lastName) |
Cart Summary
| Field | Description |
|---|---|
| totalItems | Number of distinct products in cart |
| totalQuantity | Total quantity of all items combined |
| subtotal | Total price before discounts |
| totalDiscount | Total discount amount applied |
| totalAmount | Final amount after discounts |
Cart Item Details
| Field | Description |
|---|---|
| itemId | Unique identifier for the cart item |
| productId | Product identifier |
| productName | Current product name |
| productSlug | URL-friendly product identifier |
| productImage | Primary product image URL |
| productType | PHYSICAL or DIGITAL |
| unitPrice | Current product price per unit |
| quantity | Quantity of this product in cart |
| itemSubtotal | unitPrice ร quantity |
| totalPrice | itemSubtotal after discounts |
| shop | Shop information (id, name, slug, logo) |
| availability | Real-time stock information |
| addedAt | When item was first added to cart |
Product Availability
| Field | Description |
|---|---|
| inStock | Whether the product is currently in stock |
| availableQuantity | Current available inventory |
| maxPerCustomer | Maximum quantity a single customer can purchase (null if no limit) |
Error Responses:
3. Update Cart Item Quantity
Purpose: Updates the quantity of a specific item in the cart
Endpoint: PUT {base}/items/{itemId}
Access Level: ๐ Protected (Requires authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | application/json |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| itemId | UUID | Yes | ID of the cart item to update |
Request JSON Sample:
{
"quantity": 3
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| quantity | integer | Yes | New quantity for the cart item | Min: 1 |
Response JSON Sample:
{
"success": true,
"message": "Product quantity updated successfully",
"data": null
}
Business Logic:
- Updates quantity to the exact specified value (not additive)
- Validates new quantity doesn't exceed current stock
- Only allows updating items in the authenticated user's own cart
Error Responses:
400 Bad Request: Invalid quantity value401 Unauthorized: Authentication required404 Not Found: Cart item not found in user's cart422 Unprocessable Entity: Insufficient stock for requested quantity
Error Example:
{
"success": false,
"message": "Insufficient stock for 'iPhone 15 Pro'. Only 2 units available",
"data": null
}
4. Remove Cart Item
Purpose: Removes a specific item completely from the cart
Endpoint: DELETE {base}/items/{itemId}
Access Level: ๐ Protected (Requires authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| itemId | UUID | Yes | ID of the cart item to remove |
Response JSON Sample:
{
"success": true,
"message": "Product removed from cart successfully",
"data": null
}
Business Logic:
- Completely removes the cart item and all its quantity
- Only allows removing items from the authenticated user's own cart
Error Responses:
5. Clear Shopping Cart
Purpose: Removes all items from the user's cart
Endpoint: DELETE {base}/clear
Access Level: ๐ Protected (Requires authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Response JSON Sample:
{
"success": true,
"message": "Shopping cart cleared successfully",
"data": null
}
Business Logic:
- Removes all cart items for the authenticated user
- Cart entity remains but becomes empty
Error Responses:
Shops Management Service
The Shops Management Service provides comprehensive shop management capabilities for the NextGate social commerce platform. This service handles shop categories, shop creation and management, rating systems, and review functionality with role-based access controls and business logic enforcement.
Hints:
- Shop Approval: All shops are auto-approved upon creation (isApproved: true)
- Business Rules: Shop owners cannot rate/review their own shops
- One Per User: Each user can submit only one rating and one review per shop
- Admin Categories: Shop categories require SUPER_ADMIN role for management
- Soft Deletion: Shops use soft delete (isDeleted flag) to maintain data integrity
- Slug Generation: Shop slugs are auto-generated from names with uniqueness validation
- Rating Scale: Ratings use 1-5 star system with summary statistics
- Review Moderation: Reviews support status management (ACTIVE, HIDDEN, FLAGGED, UNDER_REVIEW)
Shop Management
Short Description: The Shop Management API provides endpoints for creating, managing, and retrieving shop information on the NextGate platform. Covers shop registration, updates, approvals, WABA (WhatsApp Business) integration, AI chatbot toggling, and conversation history.
Hints:
- All shop endpoints use the prefix
api/v1/e-commerce/shops - Shop creation requires authentication; public users can only read
- Pagination uses 1-based page numbering
- Shop approval operations require
ROLE_SUPER_ADMINorROLE_STAFF_ADMIN - Featured shops are randomized on each request
- Rating and review data is automatically included in shop responses (read-only โ managed by separate review service)
- WABA = WhatsApp Business Account integration for the shop's AI-powered chatbot
Standard Response Format
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2026-05-19T10:30:45",
"data": { }
}
Error responses follow the same envelope with "success": false and "data" set to the error message string.
Endpoints
1. Create Shop
Endpoint: POST api/v1/e-commerce/shops
Access Level: ๐ Protected
Request Body:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopName | string | Yes | Name of the shop | Min: 2, Max: 100 chars |
| shopDescription | string | Yes | Detailed description | Max: 1000 chars |
| phoneNumber | string | Yes | Contact phone | Pattern: ^\+?[0-9]{10,15}$ |
| city | string | Yes | City | Min: 2, Max: 50 chars |
| region | string | Yes | Region/state | Min: 2, Max: 50 chars |
| logoUrl | string | No | Shop logo URL | Valid URL, max 1000 chars |
| bannerUrl | string | No | Shop banner URL | Valid URL, max 1000 chars |
| shopImages | array | No | Array of shop image URLs | Valid URLs, max 1000 chars each |
| string | No | Contact email | Valid email, max 100 chars | |
| countryCode | string | No | Country code | Max: 3 chars, Default: "TZ" |
| streetAddress | string | No | Street address | Max: 255 chars |
| landmark | string | No | Landmark / location notes | Max: 300 chars |
| latitude | decimal | No | GPS latitude | Range: -90.0 to 90.0 |
| longitude | decimal | No | GPS longitude | Range: -180.0 to 180.0 |
Request JSON Sample:
{
"shopName": "Mama Lucy's Restaurant",
"shopDescription": "Authentic Tanzanian cuisine in the heart of Dar es Salaam",
"phoneNumber": "+255123456789",
"city": "Dar es Salaam",
"region": "Dar es Salaam",
"logoUrl": "https://example.com/logo.jpg",
"bannerUrl": "https://example.com/banner.jpg",
"shopImages": ["https://example.com/shop1.jpg"],
"email": "info@mamalucy.co.tz",
"countryCode": "TZ",
"streetAddress": "Msimbazi Street, Block 45",
"landmark": "Near the main bus stop",
"latitude": -6.7924,
"longitude": 39.2083
}
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Shop created successfully",
"action_time": "2026-05-19T10:30:45",
"data": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "Mama Lucy's Restaurant",
"shopSlug": "mama-lucys-restaurant",
"shopDescription": "Authentic Tanzanian cuisine...",
"logoUrl": "https://example.com/logo.jpg",
"bannerUrl": "https://example.com/banner.jpg",
"shopImages": ["https://example.com/shop1.jpg"],
"ownerId": "456e7890-e89b-12d3-a456-426614174001",
"ownerName": "Lucy Mwalimu",
"status": "PENDING",
"phoneNumber": "+255123456789",
"email": "info@mamalucy.co.tz",
"streetAddress": "Msimbazi Street, Block 45",
"city": "Dar es Salaam",
"region": "Dar es Salaam",
"countryCode": "TZ",
"latitude": -6.7924,
"longitude": 39.2083,
"landmark": "Near the main bus stop",
"isVerified": false,
"verificationBadge": null,
"trustScore": 0.00,
"isApproved": true,
"createdAt": "2026-05-19T10:30:45",
"updatedAt": "2026-05-19T10:30:45",
"approvedAt": null,
"averageRating": null,
"totalRatings": 0,
"totalActiveReviews": 0,
"reviews": []
}
}
Error Responses:
400: Shop name already exists401: Authentication required422: Validation errors
2. Get All Shops
Endpoint: GET api/v1/e-commerce/shops/all
Access Level: ๐ Public
Returns a list of all shops with summary info and top 5 reviews per shop.
3. Get All Shops (Paginated)
Endpoint: GET api/v1/e-commerce/shops/all-paged
Access Level: ๐ Public
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-based) |
| size | integer | 10 | Items per page (max: 100) |
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Shops retrieved successfully",
"action_time": "2026-05-19T10:30:45",
"data": {
"shops": [ { "...ShopSummary fields..." } ],
"currentPage": 1,
"pageSize": 10,
"totalElements": 150,
"totalPages": 15,
"hasNext": true,
"hasPrevious": false,
"isFirst": true,
"isLast": false
}
}
4. Update Shop
Endpoint: PUT api/v1/e-commerce/shops/{shopId}
Access Level: ๐ Protected (Shop Owner only)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| shopId | UUID | ID of the shop |
All request body fields are optional โ only provided fields are updated:
| Parameter | Type | Validation |
|---|---|---|
| shopName | string | Min: 2, Max: 100 chars |
| shopDescription | string | Max: 1000 chars |
| logoUrl | string | Valid URL |
| bannerUrl | string | Valid URL |
| shopImages | array | Valid URLs |
| phoneNumber | string | ^\+?[0-9]{10,15}$ |
| string | Valid email | |
| streetAddress | string | Max: 255 chars |
| city | string | Min: 2, Max: 50 chars |
| region | string | Min: 2, Max: 50 chars |
| countryCode | string | Max: 3 chars |
| latitude | decimal | -90.0 to 90.0 |
| longitude | decimal | -180.0 to 180.0 |
| landmark | string | Max: 300 chars |
Response: Full ShopResponse (same shape as Create response)
Error Responses:
400: Not the shop owner, or shop is deleted401: Authentication required404: Shop not found
5. Get Shop by ID (Summary)
Endpoint: GET api/v1/e-commerce/shops/{shopId}
Access Level: ๐ Public
Returns ShopSummaryListResponse โ public-facing fields including top 5 reviews and rating.
6. Get Shop by ID (Detailed)
Endpoint: GET api/v1/e-commerce/shops/{shopId}/detailed
Access Level: ๐ Protected (Shop Owner or Admin)
Returns full ShopResponse including all reviews and management fields.
7. Get My Shops
Endpoint: GET api/v1/e-commerce/shops/my-shops
Access Level: ๐ Protected
Returns all shops owned by the authenticated user (ShopSummaryListResponse list).
8. Get My Shops (Paginated)
Endpoint: GET api/v1/e-commerce/shops/my-shops-paged
Access Level: ๐ Protected
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number (1-based) |
| size | 10 | Items per page (max: 100) |
9. Approve / Reject Shop
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/approve-shop
Access Level: ๐ Protected (ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN)
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| approve | boolean | Yes | true to approve, false to reject |
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Shop approval status changed successfully",
"action_time": "2026-05-19T10:30:45",
"data": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "Mama Lucy's Restaurant",
"isApproved": true,
"approvedAt": "2026-05-19T10:30:45"
}
}
10. Get Featured Shops
Endpoint: GET api/v1/e-commerce/shops/featured
Access Level: ๐ Public
Returns up to 20 randomly selected featured shops (ShopSummaryListResponse list).
11. Get Featured Shops (Paginated)
Endpoint: GET api/v1/e-commerce/shops/featured-paged
Access Level: ๐ Public
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number |
| size | 10 | Items per page (max: 100) |
12. Search Shops
Endpoint: GET api/v1/e-commerce/shops/search
Access Level: ๐ Public
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| q | string | No | โ | Search query |
| page | integer | No | 1 | Page number |
| size | integer | No | 10 | Items per page |
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Shops retrieved successfully",
"action_time": "2026-05-19T10:30:45",
"data": {
"content": [ { "...ShopSearchResponse fields..." } ],
"totalElements": 8,
"totalPages": 1,
"currentPage": 1,
"pageSize": 10,
"hasNext": false,
"hasPrevious": false
}
}
13. Get Shop Summary Stats
Endpoint: GET api/v1/e-commerce/shops/{shopId}/summary-stats
Access Level: ๐ Public
Returns aggregated review and rating statistics for a shop, including per-user activity.
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Shop summary stats retrieved successfully",
"data": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "Mama Lucy's Restaurant",
"averageRating": 4.5,
"totalRatings": 25,
"ratingDistribution": { "1": 1, "2": 2, "3": 5, "4": 7, "5": 10 },
"totalReviews": 15,
"activeReviews": 12,
"hiddenReviews": 2,
"flaggedReviews": 1,
"userActivities": [
{
"userId": "user-123",
"userName": "John Doe",
"feedbackId": "rev-123",
"reviewText": "Amazing food and service!",
"reviewStatus": "ACTIVE",
"ratingValue": 5,
"date": "2026-05-19T14:30:00",
"hasReview": true,
"hasRating": true
}
]
}
}
14. WABA (WhatsApp Business) Integration
WABA allows shops to receive and respond to WhatsApp customer messages via an AI-powered chatbot. The flow is: shop registers a WABA โ admin approves with Meta credentials โ shop toggles AI on/off and monitors conversation history.
Base path for all WABA endpoints: api/v1/e-commerce/shops/{shopId}/waba
| Parameter | Type | Description |
|---|---|---|
| shopId | UUID | ID of the shop |
14a. Register WABA
Purpose: Shop owner submits a WhatsApp number and display name to begin WABA registration.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/waba/register
Access Level: ๐ Protected (Shop Owner)
Request Body:
| Parameter | Type | Required | Validation |
|---|---|---|---|
| phoneNumber | string | Yes | Max: 20 chars |
| displayName | string | Yes | Max: 100 chars |
Request JSON Sample:
{
"phoneNumber": "+255712345678",
"displayName": "Mama Lucy's Restaurant"
}
14b. Approve WABA
Purpose: Admin approves a pending WABA registration by supplying Meta WABA credentials.
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/waba/approve
Access Level: ๐ Protected (ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN)
Request Body:
| Parameter | Type | Required | Validation |
|---|---|---|---|
| wabaId | string | Yes | Max: 64 chars (Meta WABA ID) |
| phoneNumberId | string | Yes | Max: 64 chars (Meta Phone Number ID) |
| phoneNumber | string | Yes | Max: 20 chars |
14c. Resubmit WABA
Purpose: Shop owner resubmits a rejected or pending WABA with corrected info.
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/waba/resubmit
Access Level: ๐ Protected (Shop Owner)
Request Body:
| Parameter | Type | Validation |
|---|---|---|
| phoneNumber | string | Max: 20 chars |
| displayName | string | Max: 100 chars |
14d. Admin Update WABA
Purpose: Admin updates Meta credentials on an existing WABA account.
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/waba/admin-update
Access Level: ๐ Protected (ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN)
Same request body as Resubmit WABA.
14e. Update WABA Status
Purpose: Admin changes the status of a WABA account (e.g., suspend or reactivate).
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/waba/status
Access Level: ๐ Protected (ROLE_SUPER_ADMIN or ROLE_STAFF_ADMIN)
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | ShopWabaStatus | Yes | PENDING, ACTIVE, SUSPENDED, REJECTED |
14f. Toggle AI Chatbot
Purpose: Shop owner enables or disables the AI chatbot for their WABA.
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/waba/toggle-ai
Access Level: ๐ Protected (Shop Owner)
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| enabled | boolean | Yes | true to enable, false to disable |
Response JSON Sample:
{
"success": true,
"message": "AI enabled successfully",
"data": {
"shopId": "...",
"aiEnabled": true,
"updatedAt": "2026-05-19T11:00:00"
}
}
14g. Get WABA Conversations
Purpose: Retrieves paginated WhatsApp conversation sessions for the shop.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/waba/conversations
Access Level: ๐ Protected (Shop Owner or Admin)
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number |
| size | 10 | Items per page |
Response JSON Sample:
{
"success": true,
"message": "Conversations retrieved",
"data": {
"content": [ { "...WabaSessionResponse fields..." } ],
"currentPage": 1,
"pageSize": 10,
"totalElements": 42,
"totalPages": 5,
"hasNext": true,
"hasPrevious": false
}
}
14h. Get Session Messages
Purpose: Retrieves paginated messages within a specific conversation session.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/waba/conversations/{sessionId}/messages
Access Level: ๐ Protected (Shop Owner or Admin)
Additional Path Parameter:
| Parameter | Type | Description |
|---|---|---|
| sessionId | UUID | ID of the conversation session |
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number |
| size | 20 | Items per page |
14i. Get Session Messages by Date Range
Purpose: Retrieves messages in a session filtered by date range.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/waba/conversations/{sessionId}/messages/by-date
Access Level: ๐ Protected (Shop Owner or Admin)
Additional Path Parameter:
| Parameter | Type | Description |
|---|---|---|
| sessionId | UUID | ID of the conversation session |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| from | LocalDateTime | Yes | Start datetime (ISO 8601) |
| to | LocalDateTime | Yes | End datetime (ISO 8601) |
| page | integer | No | Default: 1 |
| size | integer | No | Default: 20 |
Quick Reference
Enums
ShopStatus: PENDING, ACTIVE, SUSPENDED, CLOSED, UNDER_REVIEW
ShopType: PHYSICAL, ONLINE, HYBRID
VerificationBadge: BRONZE, SILVER, GOLD, PREMIUM
ShopWabaStatus: PENDING, ACTIVE, SUSPENDED, REJECTED
Error Response Codes
| Code | Meaning |
|---|---|
400 |
Business logic violation or item already exists |
401 |
Authentication required or token invalid/expired |
403 |
Insufficient permissions |
404 |
Resource not found |
422 |
Field-level validation errors |
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"data": {
"shopName": "Shop name must be between 2 and 100 characters",
"phoneNumber": "Phone number must be between 10-15 digits and may start with +"
}
}
Access Control Summary
| Role | Capabilities |
|---|---|
| Public | Read shops, search, featured, summary stats |
| Authenticated user | All public + create shop, view own shops |
| Shop Owner | All authenticated + update own shop, WABA management, view conversations |
ROLE_SUPER_ADMIN / ROLE_STAFF_ADMIN |
All + approve/reject shops, approve/update/status WABA |
WABA Registration Flow
1. POST /{shopId}/waba/register โ shop owner submits phone + display name
2. PATCH /{shopId}/waba/approve โ admin supplies Meta wabaId + phoneNumberId
3. PATCH /{shopId}/waba/toggle-ai โ shop owner enables AI chatbot
4. GET /{shopId}/waba/conversations โ shop owner monitors customer chats
Shop Review
Short Description: The Shop Review API allows users to write, manage, and retrieve reviews (text + star rating) for shops. Supports create, update, delete, listing with pagination, and summary statistics.
Hints:
- All endpoints use the prefix
api/v1/e-commerce/shops/reviews/{shopId} - All operations require Bearer token authentication
- One review per user per shop โ use PUT to update an existing one
- Shop owners cannot review their own shops
reviewTextandratingValueare both optional fields, but at least one should be providedratingValuemust be 1โ5 if providedreviewTextmust be 10โ1000 characters if providedisMyReviewflag is only populated when the authenticated user is included in the response builder
Standard Response Format
{
"success": true,
"httpStatus": "OK",
"message": "...",
"action_time": "2026-05-19T10:30:45",
"data": { }
}
Endpoints
1. Create Review (Feedback)
Endpoint: POST api/v1/e-commerce/shops/reviews/{shopId}
Access Level: ๐ Protected
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| shopId | UUID | ID of the shop to review |
Request Body:
| Parameter | Type | Required | Validation |
|---|---|---|---|
| reviewText | string | No | Min: 10, Max: 1000 chars |
| ratingValue | integer | No | Min: 1, Max: 5 |
Request JSON Sample:
{
"reviewText": "Amazing food and excellent service! Highly recommend the local dishes.",
"ratingValue": 5
}
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Feedback submitted successfully",
"action_time": "2026-05-19T10:30:45",
"data": {
"reviewId": "123e4567-e89b-12d3-a456-426614174000",
"shopId": "456e7890-e89b-12d3-a456-426614174001",
"shopName": "Mama Lucy's Restaurant",
"userId": "789e0123-e89b-12d3-a456-426614174002",
"userName": "John Doe",
"reviewText": "Amazing food and excellent service! Highly recommend the local dishes.",
"ratingValue": 5,
"status": "ACTIVE",
"createdAt": "2026-05-19T10:30:45",
"updatedAt": "2026-05-19T10:30:45",
"isMyReview": false
}
}
Error Responses:
400: Already reviewed this shop, or shop owner reviewing own shop401: Authentication required404: Shop not found
2. Update Review (Feedback)
Endpoint: PUT api/v1/e-commerce/shops/reviews/{shopId}
Access Level: ๐ Protected (Review Owner only)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| shopId | UUID | ID of the reviewed shop |
Request Body (same as Create โ all fields optional):
| Parameter | Type | Validation |
|---|---|---|
| reviewText | string | Min: 10, Max: 1000 chars |
| ratingValue | integer | Min: 1, Max: 5 |
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Feedback updated successfully",
"action_time": "2026-05-19T11:15:30",
"data": {
"reviewId": "123e4567-e89b-12d3-a456-426614174000",
"shopId": "456e7890-e89b-12d3-a456-426614174001",
"shopName": "Mama Lucy's Restaurant",
"userId": "789e0123-e89b-12d3-a456-426614174002",
"userName": "John Doe",
"reviewText": "Still amazing food. Local dishes are worth it.",
"ratingValue": 4,
"status": "ACTIVE",
"createdAt": "2026-05-19T10:30:45",
"updatedAt": "2026-05-19T11:15:30",
"isMyReview": false
}
}
Error Responses:
401: Authentication required404: Review not found (create one first)
3. Delete Review (Feedback)
Endpoint: DELETE api/v1/e-commerce/shops/reviews/{shopId}
Access Level: ๐ Protected (Review Owner only)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| shopId | UUID | ID of the reviewed shop |
Soft-deletes the review. Deleted reviews no longer appear in listings or counts.
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Feedback deleted successfully",
"action_time": "2026-05-19T10:30:45",
"data": null
}
Error Responses:
401: Authentication required404: Review not found
4. Get Active Reviews for Shop
Endpoint: GET api/v1/e-commerce/shops/reviews/{shopId}
Access Level: ๐ Protected (auth required to populate isMyReview)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| shopId | UUID | ID of the shop |
Returns all ACTIVE reviews. isMyReview is set to true for the authenticated user's own review.
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Reviews retrieved successfully",
"action_time": "2026-05-19T10:30:45",
"data": [
{
"reviewId": "123e4567-e89b-12d3-a456-426614174000",
"shopId": "456e7890-e89b-12d3-a456-426614174001",
"shopName": "Mama Lucy's Restaurant",
"userId": "789e0123-e89b-12d3-a456-426614174002",
"userName": "John Doe",
"reviewText": "Amazing food!",
"ratingValue": 5,
"status": "ACTIVE",
"createdAt": "2026-05-19T10:30:45",
"updatedAt": "2026-05-19T10:30:45",
"isMyReview": true
}
]
}
Error Responses:
401: Authentication required404: Shop not found
5. Get Active Reviews (Paginated)
Endpoint: GET api/v1/e-commerce/shops/reviews/{shopId}/paged
Access Level: ๐ Protected
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
| page | 1 | Page number (1-based) |
| size | 10 | Items per page (max: 100) |
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Reviews retrieved successfully",
"action_time": "2026-05-19T10:30:45",
"data": {
"reviews": [
{
"reviewId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "Mama Lucy's Restaurant",
"userName": "John Doe",
"reviewText": "Amazing food!",
"ratingValue": 5,
"status": "ACTIVE",
"createdAt": "2026-05-19T10:30:45"
}
],
"currentPage": 1,
"pageSize": 10,
"totalElements": 25,
"totalPages": 3,
"hasNext": true,
"hasPrevious": false,
"isFirst": true,
"isLast": false
}
}
Error Responses:
401: Authentication required404: Shop not found
6. Get My Review for Shop
Endpoint: GET api/v1/e-commerce/shops/reviews/{shopId}/my-review
Access Level: ๐ Protected
Returns the authenticated user's review for the shop, or null if they haven't reviewed it yet.
Response JSON Sample (has review):
{
"success": true,
"httpStatus": "OK",
"message": "Your feedback retrieved successfully",
"data": {
"reviewId": "123e4567-e89b-12d3-a456-426614174000",
"shopId": "456e7890-e89b-12d3-a456-426614174001",
"shopName": "Mama Lucy's Restaurant",
"userId": "789e0123-e89b-12d3-a456-426614174002",
"userName": "John Doe",
"reviewText": "Amazing food!",
"ratingValue": 5,
"status": "ACTIVE",
"createdAt": "2026-05-19T10:30:45",
"updatedAt": "2026-05-19T10:30:45",
"isMyReview": false
}
}
Response when no review exists:
{
"success": true,
"httpStatus": "OK",
"message": "Your feedback retrieved successfully",
"data": null
}
7. Get Shop Review Summary
Endpoint: GET api/v1/e-commerce/shops/reviews/{shopId}/summary
Access Level: ๐ Public
Returns aggregated rating and review counts for a shop.
Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Shop feedback summary retrieved successfully",
"data": {
"shopId": "456e7890-e89b-12d3-a456-426614174001",
"shopName": "Mama Lucy's Restaurant",
"averageRating": 4.5,
"totalRatings": 25,
"ratingDistribution": { "1": 1, "2": 2, "3": 5, "4": 7, "5": 10 },
"totalReviews": 15,
"activeReviews": 12,
"hiddenReviews": 2,
"flaggedReviews": 1
}
}
Quick Reference
ReviewStatus Enum
| Value | Description |
|---|---|
ACTIVE |
Visible in public listings |
HIDDEN |
Hidden from public view |
FLAGGED |
Flagged for admin review |
UNDER_REVIEW |
Under admin review |
ReviewResponse Fields
| Field | Type | Description |
|---|---|---|
| reviewId | UUID | Unique review ID |
| shopId | UUID | Shop that was reviewed |
| shopName | string | Shop name |
| userId | UUID | Reviewer's user ID |
| userName | string | Reviewer's display name |
| reviewText | string | Review text content |
| ratingValue | integer | Star rating (1โ5), nullable |
| status | ReviewStatus | Current review status |
| createdAt | LocalDateTime | Creation timestamp |
| updatedAt | LocalDateTime | Last update timestamp |
| isMyReview | boolean | True if this is the current user's review |
Error Codes
| Code | Meaning |
|---|---|
400 |
Business rule violation (duplicate review, owner self-review) |
401 |
Authentication required |
404 |
Shop or review not found |
422 |
Field validation errors |
Shop Subscription
Base URL: api/v1/e-commerce/shops
Short Description: The Shop Subscription API allows users to subscribe and unsubscribe from shops on the Nexgate marketplace. Subscriptions drive personalised product discovery โ subscribed shops receive a ranking boost in the Trending and For You feeds, and a dedicated "from your shops" signal in the relevance formula. Shop owners can view who has subscribed to their shop.
Hints:
- The action is called Subscribe โ not Follow. This is intentional to distinguish from the social module's userโuser Follow feature. Users follow people, they subscribe to shops.
POST /{shopId}/subscribeis a toggle โ calling it once subscribes, calling it again unsubscribes. No separate unsubscribe endpoint is needed.- Subscription status (
isSubscribed) is automatically included inShopResponsefor all shop detail endpoints. Authenticated users see their real status; anonymous users always receivefalse. - Subscriptions directly feed into the Marketplace
FOR_YOUandTRENDINGpersonalisation โ seemarketplace_api_doc.mdfor formula details. - Pages are 1-based (
page=1returns the first page).
Standard Response Format
All API responses follow a consistent structure using the Globe Response Builder pattern:
Success Response Structure
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2026-06-04T10:30:45",
"data": { }
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2026-06-04T10:30:45",
"data": "Error description"
}
Standard Response Fields
| Field | Type | Description |
|---|---|---|
success |
boolean | true for success, false for errors |
httpStatus |
string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
message |
string | Human-readable operation result |
action_time |
string | ISO 8601 timestamp of response generation |
data |
object | Response payload for success, error detail for failures |
HTTP Method Badge Standards
- GET โ GET Green
- POST โ POST Blue
Subscription State Reference
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SUBSCRIPTION STATE FLOW โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ User not subscribed โ
โ โ โ
โ โผ POST /{shopId}/subscribe โ
โ User subscribed โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ โ
โ โ subscriberCount + 1 โ
โ โ subscribed: true โ
โ โ โ
โ โผ POST /{shopId}/subscribe (toggle again) โ
โ User not subscribed โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ subscriberCount - 1 โ
โ subscribed: false โ
โ โ
โ Side effects: โ
โ - Shop's subscriberCount is updated atomically โ
โ - Marketplace FOR_YOU feed recalculates on next request โ
โ - Marketplace TRENDING feed gives +0.25 boost to subscribed shops โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
How Subscriptions Affect the Marketplace
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SUBSCRIPTION โ MARKETPLACE SIGNALS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ TRENDING feed: โ
โ personalizedScore = trendingScore + 0.25 โ
โ (applied to every product from a subscribed shop) โ
โ โ
โ FOR YOU feed: โ
โ relevanceScore = categoryMatch ร 0.40 โ
โ + favShopBoost ร 0.40 โ subscription signal โ
โ + trendingScore ร 0.20 โ
โ favShopBoost = 1.0 if subscribed, 0.0 otherwise โ
โ โ
โ Result: products from subscribed shops surface near the top of โ
โ both feeds while still allowing genuinely trending products to โ
โ compete (soft priority, not guaranteed top position). โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Endpoints
1. Toggle Subscribe / Unsubscribe
Purpose: Subscribes the authenticated user to a shop if they are not already subscribed, or unsubscribes them if they are. Returns the new subscription state and the updated subscriber count.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/subscribe
Access Level: ๐ Protected (authentication required)
Authentication: Bearer Token (required)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
shopId |
UUID | Yes | The shop to subscribe or unsubscribe from | Must be a valid UUID of an active, non-deleted shop |
Success Response JSON Sample โ Subscribe:
{
"success": true,
"httpStatus": "OK",
"message": "Subscribed to shop successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"subscribed": true,
"subscriberCount": 1284
}
}
Success Response JSON Sample โ Unsubscribe (same endpoint, called again):
{
"success": true,
"httpStatus": "OK",
"message": "Unsubscribed from shop successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"subscribed": false,
"subscriberCount": 1283
}
}
Success Response Fields:
| Field | Type | Description |
|---|---|---|
subscribed |
boolean | Current subscription state after the toggle โ true if now subscribed, false if now unsubscribed |
subscriberCount |
long | Updated total number of subscribers for this shop |
Error Response JSON Samples:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token has expired",
"action_time": "2026-06-04T10:30:45",
"data": "Token has expired"
}
Shop Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Shop not found",
"action_time": "2026-06-04T10:30:45",
"data": "Shop not found"
}
Standard Error Types:
2. My Subscriptions
Purpose: Returns a paginated list of all shops the authenticated user is currently subscribed to, ordered by most recently subscribed first. Each entry includes shop details and the current subscriber count.
Endpoint: GET api/v1/e-commerce/shops/my-subscriptions
Access Level: ๐ Protected (authentication required)
Authentication: Bearer Token (required)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 10 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "My subscriptions retrieved successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"content": [
{
"subscriptionId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"shopId": "7cb3a812-1234-4abc-b3fc-9d84f55bce12",
"shopName": "TechStore Tanzania",
"shopSlug": "techstore-tanzania",
"logoUrl": "https://cdn.nexgate.com/shops/techstore-logo.jpg",
"bannerUrl": "https://cdn.nexgate.com/shops/techstore-banner.jpg",
"status": "ACTIVE",
"isVerified": true,
"verificationBadge": "GOLD",
"trustScore": 4.80,
"subscriberCount": 1284,
"subscribedAt": "2026-05-20T14:32:00"
}
],
"currentPage": 1,
"pageSize": 10,
"totalElements": 7,
"totalPages": 1,
"hasNext": false,
"hasPrevious": false
}
}
Success Response Fields:
| Field | Description |
|---|---|
content[].subscriptionId |
Unique identifier of this subscription record |
content[].shopId |
Shop's unique identifier |
content[].shopName |
Shop display name |
content[].shopSlug |
Shop URL slug โ use for navigating to the shop page |
content[].logoUrl |
Shop logo image URL |
content[].bannerUrl |
Shop banner image URL |
content[].status |
Shop status โ ACTIVE, TEMPORARILY_OFFLINE, etc. |
content[].isVerified |
Whether the shop has passed platform verification |
content[].verificationBadge |
Verification badge level โ BRONZE, SILVER, GOLD |
content[].trustScore |
Shop trust rating 0.00โ5.00 |
content[].subscriberCount |
Total subscribers this shop currently has |
content[].subscribedAt |
ISO 8601 timestamp of when the user subscribed |
currentPage |
Current page number (1-based) |
totalElements |
Total shops the user is subscribed to |
hasNext / hasPrevious |
Pagination navigation flags |
Error Response JSON Samples:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token has expired",
"action_time": "2026-06-04T10:30:45",
"data": "Token has expired"
}
Standard Error Types:
3. Shop Subscribers (Owner Only)
Purpose: Returns a paginated list of users who have subscribed to a specific shop, ordered by most recently subscribed first. Only the shop owner can access this endpoint โ calling it for a shop you do not own returns a 403 error.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/subscribers
Access Level: ๐ Protected (shop owner only)
Authentication: Bearer Token (required โ must be the owner of the shop)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
shopId |
UUID | Yes | The shop whose subscriber list to retrieve | Caller must own this shop |
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 10 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Subscribers retrieved successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"content": [
{
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fullName": "Amina Hassan",
"userName": "amina.h",
"avatarUrl": "https://cdn.nexgate.com/avatars/amina-h.jpg",
"subscribedAt": "2026-06-01T09:45:00"
},
{
"userId": "1cb2e847-3f1a-4bcd-a3fc-9e84f22bce99",
"fullName": "John Mwangi",
"userName": "jmwangi",
"avatarUrl": null,
"subscribedAt": "2026-05-29T16:12:00"
}
],
"currentPage": 1,
"pageSize": 10,
"totalElements": 1284,
"totalPages": 129,
"hasNext": true,
"hasPrevious": false
}
}
Success Response Fields:
| Field | Description |
|---|---|
content[].userId |
Subscriber's unique account identifier |
content[].fullName |
Subscriber's first + last name |
content[].userName |
Subscriber's public username |
content[].avatarUrl |
Subscriber's profile picture URL โ null if no avatar set |
content[].subscribedAt |
ISO 8601 timestamp of when this user subscribed |
currentPage |
Current page number (1-based) |
totalElements |
Total subscriber count for this shop |
hasNext / hasPrevious |
Pagination navigation flags |
Error Response JSON Samples:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token has expired",
"action_time": "2026-06-04T10:30:45",
"data": "Token has expired"
}
Forbidden โ Not the shop owner (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "You do not own this shop",
"action_time": "2026-06-04T10:30:45",
"data": "You do not own this shop"
}
Shop Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Shop not found",
"action_time": "2026-06-04T10:30:45",
"data": "Shop not found"
}
Standard Error Types:
isSubscribed & subscriberCount in ShopResponse
Subscription state is automatically embedded in the standard ShopResponse returned by all shop detail endpoints โ no separate call needed.
{
"shopId": "7cb3a812-...",
"shopName": "TechStore Tanzania",
"isVerified": true,
"trustScore": 4.80,
"isSubscribed": true,
"subscriberCount": 1284,
"productCount": 47
}
| Field | Type | Description |
|---|---|---|
isSubscribed |
boolean | true if the requesting authenticated user is subscribed to this shop. Always false for anonymous users |
subscriberCount |
long | Total number of subscribers this shop currently has |
productCount |
long | Number of active (published) products in this shop |
Quick Reference โ All Subscription Endpoints
| # | Endpoint | Method | Auth | Who can call |
|---|---|---|---|---|
| 1 | api/v1/e-commerce/shops/{shopId}/subscribe |
POST | Required | Any authenticated user |
| 2 | api/v1/e-commerce/shops/my-subscriptions |
GET | Required | Any authenticated user |
| 3 | api/v1/e-commerce/shops/{shopId}/subscribers |
GET | Required | Shop owner only |
Products Management Service
Product Management
Short Description: The Product Management API provides comprehensive functionality for managing products within shops on the NextGate platform. It supports group buying, installment payment plans, color variations, specifications, digital product file delivery, product preview media (video, PDF, 3D, image), and comprehensive search/filter capabilities with role-based access control.
Hints:
Endpoints
1. Create Product
Purpose: Creates a new product in a shop, supporting group buying, color variations, specifications, and digital product rules.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/products
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token |
| Content-Type | string | Yes | application/json |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| action | ReqAction | Yes | โ | SAVE_DRAFT or SAVE_PUBLISH |
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| productType | ProductType | Yes | PHYSICAL or DIGITAL |
Required |
| productName | string | Yes | Unique name within shop | Min: 2, Max: 100 chars |
| productDescription | string | Yes | Detailed description | Min: 10, Max: 1000 chars |
| price | decimal | Yes | Selling price | Min: 0.01, max 8 digits + 2 decimal places |
| stockQuantity | integer | Yes | Available stock | Min: 0 |
| categoryId | UUID | Yes | Product category | Must exist and be active |
| comparePrice | decimal | No | Original price for discount display | Must be > price if provided |
| lowStockThreshold | integer | No | Low stock alert threshold | Min: 1, Max: 1000, Default: 5 |
| condition | ProductCondition | No | Product condition | NEW, USED_LIKE_NEW, USED_GOOD, USED_FAIR, REFURBISHED, FOR_PARTS |
| status | ProductStatus | No | Initial status (overridden by action) |
Default: ACTIVE |
| productImages | array | Yes | Product image URLs | Valid URLs, at least 1 required |
| specifications | object | No | Key-value specs | Key max 100 chars, Value max 500 chars |
| colors | array | No | Color variations | See Color object below |
| minOrderQuantity | integer | No | Minimum order qty | Min: 1, Default: 1 |
| maxOrderQuantity | integer | No | Maximum order qty per order | Min: 1, must be โฅ minOrderQuantity |
| groupBuyingEnabled | boolean | No | Enable group buying | Default: false |
| groupMaxSize | integer | No | Maximum group participants | Min: 2, required if groupBuyingEnabled |
| groupPrice | decimal | No | Discounted group price | Must be < price |
| groupTimeLimitHours | integer | No | Group formation time limit | Min: 1, Max: 8760 |
| downloadExpiryDays | integer | No | Download link expiry (DIGITAL only) | Min: 1, Default: 7 |
| maxDownloadsPerBuyer | integer | No | Download attempts per buyer (DIGITAL only) | Min: 1 |
| maxQuantityForDigital | integer | No | Purchase cap per buyer (DIGITAL only) | Min: 1 |
Color Object:
| Field | Type | Required | Validation |
|---|---|---|---|
| name | string | Yes | Max: 50 chars |
| hex | string | Yes | Valid #RRGGBB format |
| images | array | No | Valid URLs |
| priceAdjustment | decimal | No | Min: 0.0, Default: 0 |
Request JSON Sample (PHYSICAL):
{
"productType": "PHYSICAL",
"productName": "iPhone 15 Pro Max 256GB",
"productDescription": "The most advanced iPhone featuring the A17 Pro chip and titanium design.",
"price": 1199.00,
"comparePrice": 1299.00,
"stockQuantity": 25,
"lowStockThreshold": 5,
"categoryId": "123e4567-e89b-12d3-a456-426614174000",
"condition": "NEW",
"productImages": [
"https://example.com/images/iphone15-main.jpg"
],
"specifications": {
"Display": "6.7-inch Super Retina XDR OLED",
"Chip": "A17 Pro"
},
"colors": [
{
"name": "Natural Titanium",
"hex": "#F5F5DC",
"images": ["https://example.com/colors/natural-titanium.jpg"],
"priceAdjustment": 0.00
}
],
"minOrderQuantity": 1,
"maxOrderQuantity": 3,
"groupBuyingEnabled": true,
"groupMaxSize": 50,
"groupPrice": 1099.00,
"groupTimeLimitHours": 72
}
Request JSON Sample (DIGITAL):
{
"productType": "DIGITAL",
"productName": "UI Design Kit Pro",
"productDescription": "A comprehensive Figma component library with 500+ components.",
"price": 49.00,
"stockQuantity": 1000,
"categoryId": "123e4567-e89b-12d3-a456-426614174000",
"productImages": ["https://example.com/images/design-kit-preview.jpg"],
"downloadExpiryDays": 30,
"maxDownloadsPerBuyer": 5,
"maxQuantityForDigital": 1
}
Response JSON Sample:
{
"success": true,
"message": "Product created successfully",
"data": null
}
Business Rules:
- Product name must be unique within the shop
comparePricemust be greater thanpricegroupPricemust be less thanprice- All group buying settings (
groupMaxSize,groupPrice,groupTimeLimitHours) required whengroupBuyingEnabled=true maxOrderQuantitymust be โฅminOrderQuantity- Digital download fields (
downloadExpiryDays,maxDownloadsPerBuyer,maxQuantityForDigital) only apply toDIGITALproducts - After creation, add installment plans via Installment Plan Config and digital files via Digital File Management
Error Responses:
400: Validation errors or business rule violations401: Authentication required403: Insufficient permissions404: Shop or category not found409: Product with same name already exists in shop422: Field-level validation errors
2. Update Product
Purpose: Updates an existing product. Only provided fields are updated.
Endpoint: PUT api/v1/e-commerce/shops/{shopId}/products/{productId}
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the product |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| action | ReqAction | Yes | โ | SAVE_DRAFT or SAVE_PUBLISH |
Request Body Parameters (all optional):
| Parameter | Type | Description | Validation |
|---|---|---|---|
| productName | string | Updated name | Min: 2, Max: 100 chars |
| productDescription | string | Updated description | Min: 10, Max: 1000 chars |
| price | decimal | Updated selling price | Min: 0.01 |
| comparePrice | decimal | Updated compare price | Must be > price |
| stockQuantity | integer | Updated stock | Min: 0 |
| lowStockThreshold | integer | Updated low stock threshold | Min: 1, Max: 1000 |
| condition | ProductCondition | Updated condition | See enum values |
| status | ProductStatus | Updated status | Overridden by action |
| urgencyTag | UrgencyTag | Urgency badge on product | NONE, NEW_ARRIVAL, LIMITED_EDITION, LIMITED_OFFER, FEW_REMAINS |
| categoryId | UUID | Updated category | Must exist and be active |
| productImages | array | Updated image URLs | Valid URLs, replaces existing |
| specifications | object | Updated specifications | Completely replaces existing |
| colors | array | Updated color variations | Completely replaces existing |
| minOrderQuantity | integer | Updated min order qty | Min: 1 |
| maxOrderQuantity | integer | Updated max order qty | Min: 1, must be โฅ min |
| groupBuyingEnabled | boolean | Enable/disable group buying | โ |
| groupMaxSize | integer | Updated group max | Min: 2 |
| groupPrice | decimal | Updated group price | Must be < price |
| groupTimeLimitHours | integer | Updated group time limit | Min: 1, Max: 8760 |
| installmentEnabled | boolean | Enable/disable installment feature toggle | โ |
| maxQuantityForInstallment | integer | Max qty a buyer can purchase on installment | Min: 1 |
| showStockAvailableToPublic | boolean | Show available stock count publicly | โ |
| showSoldCountToPublic | boolean | Show sold count publicly | โ |
| clearPreview | boolean | Set true to remove the product's preview file and type |
โ |
| previewDownloadable | boolean | Allow/disallow viewers from downloading the preview file | โ |
Response JSON Sample:
{
"success": true,
"message": "Product updated successfully and published",
"data": {
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max 512GB",
"productSlug": "iphone-15-pro-max-512gb",
"price": 1399.00,
"status": "ACTIVE",
"updatedAt": "2026-05-19T14:30:00Z"
}
}
Error Responses:
400: Invalid update data or validation errors401: Authentication required403: Insufficient permissions404: Shop or product not found409: Updated product name already exists
3. Publish Product
Purpose: Publishes a draft product making it active and publicly available.
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/publish
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the draft product |
Response JSON Sample:
{
"success": true,
"message": "Product 'iPhone 15 Pro Max' published successfully",
"data": {
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max",
"status": "ACTIVE",
"publishedAt": "2026-05-19T14:45:00Z"
}
}
Publishing Requirements:
- Product name, description, price, stock quantity, category, and at least one image must be present
- Group buying settings complete if enabled
- Installment plans present if installment is enabled
Error Responses:
400: Product already published or missing required publish fields401: Authentication required403: Insufficient permissions404: Shop or product not found
4. Delete Product
Purpose: Deletes a product. Draft products are hard-deleted; published products are soft-deleted.
Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the product |
Response JSON Sample (Soft Delete):
{
"success": true,
"message": "Product 'iPhone 15 Pro Max' has been deleted and will be permanently removed after 30 days",
"data": {
"productName": "iPhone 15 Pro Max",
"productId": "456e7890-e89b-12d3-a456-426614174001",
"previousStatus": "ACTIVE",
"deletedAt": "2026-05-19T15:00:00Z",
"deletionType": "SOFT_DELETE"
}
}
Response JSON Sample (Hard Delete):
{
"success": true,
"message": "Draft product 'iPhone 15 Pro Max' has been permanently deleted",
"data": null
}
Deletion Logic:
- Draft products: Hard delete (permanently removed)
- Published products: Soft delete (status โ ARCHIVED, 30-day recovery window)
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop or product not found
5. Restore Product
Purpose: Restores a soft-deleted product back to draft status.
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/restore
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the soft-deleted product |
Response JSON Sample:
{
"success": true,
"message": "Product 'iPhone 15 Pro Max' has been restored successfully",
"data": {
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max",
"status": "DRAFT",
"restoredAt": "2026-05-19T15:30:00Z",
"note": "Product restored as draft. Publish to make it active again."
}
}
Error Responses:
400: Product is not deleted401: Authentication required403: Insufficient permissions404: Shop or product not found
6. Get Product Detailed (Owner/Admin View)
Purpose: Retrieves comprehensive product details including all management information.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/detailed
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the product |
Response JSON Sample:
{
"success": true,
"message": "Product details retrieved successfully",
"data": {
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max 512GB",
"productSlug": "iphone-15-pro-max-512gb",
"productType": "PHYSICAL",
"productDescription": "The most advanced iPhone ever...",
"productImages": ["https://example.com/images/iphone15-main.jpg"],
"price": 1199.00,
"comparePrice": 1299.00,
"discountAmount": 100.00,
"discountPercentage": 7.69,
"isOnSale": true,
"stockQuantity": 25,
"isInStock": true,
"isLowStock": false,
"sku": "SHP12345678-ELE-APP-512-0001",
"condition": "NEW",
"status": "ACTIVE",
"urgencyTag": "NONE",
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "TechStore Pro",
"categoryId": "789e0123-e89b-12d3-a456-426614174002",
"categoryName": "Smartphones",
"specifications": {
"Display": "6.7-inch Super Retina XDR OLED",
"Chip": "A17 Pro"
},
"colors": [
{
"name": "Natural Titanium",
"hex": "#F5F5DC",
"images": ["https://example.com/colors/natural-titanium.jpg"],
"priceAdjustment": 0.00,
"finalPrice": 1199.00
}
],
"groupBuying": {
"isEnabled": true,
"groupMaxSize": 50,
"groupPrice": 1099.00,
"timeLimitHours": 72
},
"installmentOptions": {
"isEnabled": true,
"plans": []
},
"previewType": "VIDEO",
"previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_trailer.mp4",
"previewDownloadable": false,
"createdAt": "2026-05-19T10:30:00Z",
"updatedAt": "2026-05-19T14:30:00Z"
}
}
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop or product not found
7. Get Shop Products (Management View)
Purpose: Retrieves all products for a shop with summary statistics.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/all
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
Response JSON Sample:
{
"success": true,
"message": "Retrieved 47 products from shop: TechStore Pro",
"data": {
"shop": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "TechStore Pro",
"isVerified": true,
"isMyShop": true
},
"summary": {
"totalProducts": 47,
"activeProducts": 35,
"draftProducts": 8,
"outOfStockProducts": 4,
"lowStockProducts": 6,
"productsWithGroupBuying": 18,
"productsWithInstallments": 25
},
"products": [
{
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max 512GB",
"price": 1199.00,
"stockQuantity": 25,
"status": "ACTIVE",
"isInStock": true,
"hasGroupBuying": true,
"hasInstallments": true,
"createdAt": "2026-05-19T10:30:00Z"
}
],
"totalProducts": 47
}
}
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop not found
8. Get Shop Products Paginated (Management View)
Purpose: Retrieves shop products with pagination for management dashboard.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/all-paged
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| page | integer | No | 1 | Page number (1-indexed) |
| size | integer | No | 10 | Items per page (max: 100) |
Response JSON Sample:
{
"success": true,
"message": "Retrieved 10 products from shop: TechStore Pro (Page 1 of 5)",
"data": {
"contents": { "...same structure as /all..." },
"currentPage": 1,
"pageSize": 10,
"totalElements": 47,
"totalPages": 5,
"hasNext": true,
"hasPrevious": false
}
}
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop not found
9. Get Public Product by ID
Purpose: Retrieves a single active product for public viewing.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}
Access Level: ๐ Public
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Shop must be active and approved |
| productId | UUID | Yes | Product must be active |
Response JSON Sample:
{
"success": true,
"message": "Product retrieved successfully",
"data": {
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max 512GB",
"productSlug": "iphone-15-pro-max-512gb",
"productType": "PHYSICAL",
"productDescription": "The most advanced iPhone ever...",
"price": 1199.00,
"comparePrice": 1299.00,
"discountAmount": 100.00,
"discountPercentage": 7.69,
"isOnSale": true,
"isInStock": true,
"stockQuantity": 25,
"condition": "NEW",
"shopName": "TechStore Pro",
"categoryName": "Smartphones",
"specifications": { "Display": "6.7-inch OLED" },
"colors": [
{
"name": "Natural Titanium",
"hex": "#F5F5DC",
"priceAdjustment": 0.00,
"finalPrice": 1199.00
}
],
"groupBuying": {
"isAvailable": true,
"groupMaxSize": 50,
"groupPrice": 1099.00,
"timeLimitHours": 72
},
"installmentOptions": {
"isAvailable": true,
"plans": [
{
"planId": "...",
"planName": "6-Month Interest-Free",
"paymentFrequency": "MONTHLY",
"numberOfPayments": 6,
"apr": 0.00,
"minDownPaymentPercent": 20
}
]
},
"previewType": "PDF",
"previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_sample.pdf",
"previewDownloadable": true,
"createdAt": "2026-05-19T10:30:00Z"
}
}
Notes:
previewTypeandpreviewUrlarenullwhen no preview has been uploaded- Preview files are publicly accessible without authentication โ the URL is permanent and direct
Error Responses:
404: Shop not found/not approved, or product not found/not active
10. Get Public Shop Products
Purpose: Retrieves all active products from a shop for public browsing.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/public-view/all
Access Level: ๐ Public
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Shop must be active and approved |
Response JSON Sample:
{
"success": true,
"message": "Retrieved 23 products from TechStore Pro",
"data": {
"shop": {
"shopId": "123e4567-e89b-12d3-a456-426614174000",
"shopName": "TechStore Pro",
"isVerified": true
},
"products": [
{
"productId": "456e7890-e89b-12d3-a456-426614174001",
"productName": "iPhone 15 Pro Max",
"price": 1199.00,
"isOnSale": true,
"isInStock": true,
"hasGroupBuying": true,
"hasInstallments": true
}
],
"totalProducts": 23
}
}
Error Responses:
404: Shop not found, not approved, or not active
11. Get Public Shop Products Paginated
Purpose: Retrieves active products from a shop with pagination for public browsing.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/public-view/all-paged
Access Level: ๐ Public
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Shop must be active and approved |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| page | integer | No | 1 | Page number (1-indexed) |
| size | integer | No | 10 | Items per page (max: 50) |
Error Responses:
404: Shop not found, not approved, or not active
12. Search Products
Purpose: Searches products within a shop using multi-word query matching across multiple fields.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/search
Access Level: ๐ Public (Enhanced features for authenticated users)
Authentication: Optional Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| q | string | Yes | โ | Search query (min: 2, max: 100 chars) |
| status | ProductStatus[] | No | ACTIVE | Statuses to search (owners/admins only for non-ACTIVE) |
| page | integer | No | 1 | Page number |
| size | integer | No | 10 | Items per page (max: 50) |
| sortBy | string | No | relevance | relevance, createdAt, updatedAt, productName, price, stockQuantity, brand |
| sortDir | string | No | desc | asc or desc |
Search Behavior:
| Feature | Description |
|---|---|
| Multi-word | Searches for products containing ALL words |
| Partial match | "iph" matches "iPhone" |
| Cross-field | Matches against name, description, brand, tags, specifications |
| Case-insensitive | "APPLE" matches "apple" |
User Access:
| User Type | Searchable Statuses |
|---|---|
| Public / Authenticated | ACTIVE only |
| Shop Owner / Admin | All statuses |
Response JSON Sample:
{
"success": true,
"message": "Found 12 products matching 'iphone'",
"data": {
"contents": {
"shop": { "shopId": "...", "shopName": "TechStore Pro" },
"products": [ { "...product summary fields..." } ],
"totalProducts": 12,
"searchMetadata": {
"searchQuery": "iphone",
"searchedStatuses": ["ACTIVE"],
"userType": "PUBLIC"
}
},
"currentPage": 1,
"pageSize": 10,
"totalElements": 12,
"totalPages": 2,
"hasNext": true,
"hasPrevious": false
}
}
Error Responses:
400: Query too short or too long404: Shop not found or not accessible
13. Advanced Product Filter
Purpose: Filters products using multiple criteria with combined AND/OR logic.
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/advanced-filter
Access Level: ๐ Public (Enhanced features for authenticated users)
Authentication: Optional Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| minPrice | decimal | No | โ | Minimum price |
| maxPrice | decimal | No | โ | Maximum price (must be โฅ minPrice) |
| condition | ProductCondition | No | โ | Condition filter |
| categoryId | UUID | No | โ | Category filter |
| inStock | boolean | No | โ | Filter by availability |
| onSale | boolean | No | โ | Filter by sale status |
| hasGroupBuying | boolean | No | โ | Filter by group buying |
| hasInstallments | boolean | No | โ | Filter by installments |
| hasMultipleColors | boolean | No | โ | Filter by color variations |
| status | ProductStatus[] | No | ACTIVE | Status filter (owners/admins for non-ACTIVE) |
| page | integer | No | 1 | Page number |
| size | integer | No | 10 | Items per page (max: 50) |
| sortBy | string | No | createdAt | createdAt, updatedAt, productName, price, stockQuantity |
| sortDir | string | No | desc | asc or desc |
Filter Logic:
| Filter Type | Logic |
|---|---|
| Price range | AND (minPrice AND maxPrice) |
| Feature flags | AND (all must match) |
| Multiple statuses | OR |
Error Responses:
400: Invalid filter values or price range error404: Shop or category not found
14. Get Public Product by Slug
Purpose: Retrieves a single active product by its slug (same response shape as Get Public Product by ID).
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/find-by-slug/{slug}
Access Level: ๐ Public
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Shop must be active and approved |
| slug | string | Yes | Product slug |
Error Responses:
404: Shop not found/not approved, or product not found/not active
15. Installment Plan Config
Purpose: CRUD for installment plans attached to a product. Plans are created separately after the product, and linked to it by productId.
Base URL: api/v1/e-commerce/products/{shopId}/{productId}/installment-plans
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters (shared):
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the product |
15a. Create Installment Plan
Endpoint: POST api/v1/e-commerce/products/{shopId}/{productId}/installment-plans
Request Body:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| planName | string | Yes | Display name for the plan | Min: 3, Max: 100 chars |
| paymentFrequency | PaymentFrequency | Yes | Payment interval | DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS |
| customFrequencyDays | integer | Conditional | Days between payments | Required when paymentFrequency=CUSTOM_DAYS, min: 1 |
| numberOfPayments | integer | Yes | Total number of payments | Min: 2, Max: 120 |
| apr | decimal | Yes | Annual percentage rate | Min: 0.0, Max: 36.0, 2 decimal places |
| minDownPaymentPercent | integer | Yes | Minimum down payment % | Min: 10, Max: 50 |
| fulfillmentTiming | FulfillmentTiming | Yes | When to ship | IMMEDIATE (ship after down payment), AFTER_PAYMENT (layaway โ ship after final payment) |
| displayOrder | integer | No | Sort order in UI | Min: 0, Default: 0 |
| isFeatured | boolean | No | Highlight as recommended plan | Default: false |
| isActive | boolean | No | Plan is available to buyers | Default: true |
Request JSON Sample:
{
"planName": "6-Month Interest-Free",
"paymentFrequency": "MONTHLY",
"numberOfPayments": 6,
"apr": 0.00,
"minDownPaymentPercent": 20,
"fulfillmentTiming": "IMMEDIATE",
"displayOrder": 1,
"isFeatured": true,
"isActive": true
}
Error Responses:
400: Validation errors404: Shop or product not found
15b. Get All Installment Plans
Endpoint: GET api/v1/e-commerce/products/{shopId}/{productId}/installment-plans
Returns a list of all installment plans for the product.
Error Responses:
404: Shop or product not found
15c. Get Installment Plan by ID
Endpoint: GET api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}
Additional Path Parameter:
| Parameter | Type | Description |
|---|---|---|
| planId | UUID | ID of the installment plan |
15d. Update Installment Plan
Endpoint: PUT api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}
All fields are optional โ only provided fields are updated. Same field structure as Create.
15e. Delete Installment Plan
Endpoint: DELETE api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}
15f. Activate / Deactivate Plan
Activate: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/activate
Deactivate: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/deactivate
Toggles isActive on the plan without changing any other fields.
15g. Set Featured Plan
Endpoint: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/set-featured
Marks the specified plan as the featured (recommended) plan for this product.
16. Digital File Management
Purpose: Manages downloadable files for DIGITAL products. Uses a presign โ upload โ confirm flow to upload files directly to object storage.
Base URL: api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters (shared):
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the digital product |
16a. Presign Upload
Purpose: Generates a presigned PUT URL. The client uploads the file directly to this URL, then calls /confirm.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/presign-upload
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| fileName | string | Yes | Original file name |
| contentType | string | Yes | MIME type (e.g. application/pdf) |
| fileSize | long | Yes | File size in bytes (must be positive) |
| displayOrder | integer | No | Sort order for multiple files |
Request JSON Sample:
{
"fileName": "design-kit-v2.fig",
"contentType": "application/octet-stream",
"fileSize": 52428800,
"displayOrder": 1
}
Response JSON Sample:
{
"success": true,
"message": "Upload URL generated โ upload directly to this URL then call /confirm",
"data": {
"uploadUrl": "https://s3.example.com/bucket/key?X-Amz-Signature=...",
"objectKey": "digital-files/product-456/design-kit-v2.fig",
"expiresAt": "2026-05-19T11:00:00"
}
}
Upload Flow:
- Call
POST /presign-uploadโ receiveuploadUrlandobjectKey PUT {uploadUrl}with binary file body (do not call the API for this step)- Call
POST /confirmwithobjectKeyto register the file
16b. Confirm Upload
Purpose: Registers a file after direct upload to object storage. Must be called after the actual file upload succeeds.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/confirm
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| objectKey | string | Yes | Returned by presign-upload |
| fileName | string | Yes | Original file name |
| contentType | string | Yes | MIME type |
| fileSize | long | Yes | File size in bytes |
| displayOrder | integer | No | Sort order |
Response JSON Sample:
{
"success": true,
"message": "File confirmed and linked to product",
"data": {
"fileId": "aaa1bbbb-e89b-12d3-a456-426614174001",
"productId": "456e7890-e89b-12d3-a456-426614174001",
"fileName": "design-kit-v2.fig",
"contentType": "application/octet-stream",
"fileSize": 52428800,
"fileVersion": 1,
"displayOrder": 1,
"isActive": true,
"uploadedAt": "2026-05-19T10:45:00"
}
}
16c. Get Product Files
Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files
Returns a list of DigitalFileResponse objects for all files linked to the product.
16d. Delete File
Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/{fileId}
Additional Path Parameter:
| Parameter | Type | Description |
|---|---|---|
| fileId | UUID | ID of the file to delete |
16e. Toggle File Active Status
Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/{fileId}/toggle
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| isActive | boolean | Yes | true to activate, false to deactivate |
Deactivated files are hidden from buyers but not deleted.
17. Product Preview Management
Purpose: Manages a single preview file per product โ a publicly accessible teaser shown to buyers before purchase. Distinct from private digital content files. Supports VIDEO, PDF, 3D models, and IMAGE previews.
Base URL: api/v1/e-commerce/shops/{shopId}/products/{productId}/preview
Access Level: ๐ Protected (Requires shop owner or system admin role)
Authentication: Bearer Token
Path Parameters (shared):
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | ID of the shop |
| productId | UUID | Yes | ID of the product (any type โ PHYSICAL or DIGITAL) |
Storage: Uploaded to nextgate-preview-content bucket (public read). The confirmed URL is permanent and requires no authentication to access.
17a. Presign Preview Upload
Purpose: Generates a presigned PUT URL. The client uploads the preview file directly to this URL, then calls /confirm.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/preview/presign-upload
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| fileName | string | Yes | Original file name (used to build the storage key) |
| contentType | string | Yes | MIME type (e.g. video/mp4, application/pdf, image/jpeg, model/gltf-binary) |
| fileSize | long | Yes | File size in bytes (must be positive) |
Request JSON Sample:
{
"fileName": "product-trailer.mp4",
"contentType": "video/mp4",
"fileSize": 52428800
}
Response JSON Sample:
{
"success": true,
"message": "Preview upload URL generated โ upload directly then call /confirm",
"data": {
"uploadUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_product-trailer.mp4?X-Amz-Signature=...",
"objectKey": "preview/shop-id/product-id/uuid_product-trailer.mp4",
"expiresAt": "2026-05-19T11:30:00"
}
}
Upload Flow:
- Call
POST /presign-uploadโ receiveuploadUrlandobjectKey PUT {uploadUrl}with binary file body (client-to-MinIO directly, not through the API)- Call
POST /confirmwithobjectKeyandpreviewTypeto link the file to the product
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop or product not found
17b. Confirm Preview Upload
Purpose: Links the uploaded file to the product. Sets previewType and stores the permanent public URL as previewUrl. If the product already has a preview, the old file is deleted from storage.
Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/preview/confirm
Request Body:
| Parameter | Type | Required | Description |
|---|---|---|---|
| objectKey | string | Yes | Returned by presign-upload |
| previewType | PreviewType | Yes | VIDEO, PDF, THREE_D, or IMAGE |
| previewDownloadable | boolean | No | Whether viewers can download the file. Default: false (view/stream only) |
Request JSON Sample:
{
"objectKey": "preview/shop-id/product-id/uuid_product-trailer.mp4",
"previewType": "VIDEO",
"previewDownloadable": false
}
Response JSON Sample:
{
"success": true,
"message": "Preview confirmed and linked to product",
"data": null
}
After confirm, the product's public response will include:
{
"previewType": "VIDEO",
"previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_product-trailer.mp4",
"previewDownloadable": false
}
Error Responses:
400: Missing objectKey or previewType401: Authentication required403: Insufficient permissions404: Shop or product not found
17c. Remove Preview
Purpose: Deletes the product's preview file from storage and clears previewType and previewUrl on the product.
Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}/preview
Response JSON Sample:
{
"success": true,
"message": "Preview removed from product",
"data": null
}
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop or product not found
Quick Reference
Common HTTP Status Codes
| Code | Meaning |
|---|---|
200 OK |
Successful GET/PUT/PATCH |
201 Created |
Successful POST (resource created) |
400 Bad Request |
Invalid data, validation errors, business rule violations |
401 Unauthorized |
Authentication required or invalid token |
403 Forbidden |
Insufficient permissions |
404 Not Found |
Resource not found or not accessible |
409 Conflict |
Duplicate product name or business constraint violation |
422 Unprocessable Entity |
Field-level validation errors |
500 Internal Server Error |
Server error |
User Access Levels
| User Type | Product Management | Status Access |
|---|---|---|
| Public | View active products only | ACTIVE only |
| Authenticated | View active products only | ACTIVE only |
| Shop Owner | Full CRUD on own shop | All statuses |
| System Admin | Full CRUD on all shops | All statuses |
Product Type Fulfillment Flows
| Type | Flow |
|---|---|
PHYSICAL |
Payment โ PENDING_SHIPMENT โ Seller ships โ Buyer confirms with 6-digit code โ Escrow releases โ COMPLETED |
DIGITAL |
Payment โ COMPLETED immediately โ Escrow released โ DigitalDownloadAccess records created โ Buyer downloads |
Product Status Lifecycle
DRAFT โ ACTIVE โ INACTIVE โ ARCHIVED
โ โ
โโโโโโโโโโ RESTORE โโโโโโโโโโโ
OUT_OF_STOCK โโ ACTIVE (automatic based on inventory)
| Status | Public Visibility | Available Actions |
|---|---|---|
DRAFT |
Hidden | Edit, Publish, Hard Delete |
ACTIVE |
Visible | Edit, Deactivate, Soft Delete |
INACTIVE |
Hidden | Edit, Activate, Soft Delete |
OUT_OF_STOCK |
Visible (out of stock badge) | Restock (auto-activates) |
ARCHIVED |
Hidden | Restore |
Enums Reference
ProductType: PHYSICAL, DIGITAL
PreviewType: VIDEO, PDF, THREE_D, IMAGE โ null means no preview
ProductCondition: NEW, USED_LIKE_NEW, USED_GOOD, USED_FAIR, REFURBISHED, FOR_PARTS
ReqAction: SAVE_DRAFT (โ DRAFT status), SAVE_PUBLISH (โ ACTIVE status)
UrgencyTag: NONE, NEW_ARRIVAL, LIMITED_EDITION, LIMITED_OFFER, FEW_REMAINS
PaymentFrequency: DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS
FulfillmentTiming: IMMEDIATE (ship after down payment), AFTER_PAYMENT (layaway โ ship after final payment)
SKU Format
SHP[8-CHAR-UUID]-[CATEGORY-3]-[BRAND-3]-[ATTRIBUTE-3]-[SEQUENCE-4]
Example: SHP12345678-ELE-APP-512-0001
Data Format Standards
- Dates: ISO 8601 (
2026-05-19T14:30:00Z) - Prices: Decimal with 2 decimal places, stored as BigDecimal
- UUIDs: Standard UUID format
- Pagination: 1-indexed page parameter
- Colors: Hex format
#RRGGBB - Percentages: Decimal format (
20.00= 20%)
Error Response Format
{
"success": false,
"message": "Human-readable error message",
"error": {
"code": "ERROR_CODE",
"details": "Detailed information",
"field": "fieldName (if field-specific)",
"timestamp": "2026-05-19T14:30:00Z"
}
}
Product Creation Flow
1. POST /shops/{shopId}/products?action=SAVE_DRAFT
โ create the product shell
2. POST /products/{shopId}/{productId}/installment-plans
โ add installment plans (if installmentEnabled)
3. POST /shops/{shopId}/products/{productId}/digital-files/presign-upload
PUT {uploadUrl} (direct to storage)
POST /shops/{shopId}/products/{productId}/digital-files/confirm
โ upload private digital files (DIGITAL products only)
4. POST /shops/{shopId}/products/{productId}/preview/presign-upload
PUT {uploadUrl} (direct to storage)
POST /shops/{shopId}/products/{productId}/preview/confirm
โ upload preview teaser (any product type, optional)
โ preview is stored in public bucket; URL is immediately accessible
5. PATCH /shops/{shopId}/products/{productId}/publish
โ publish when ready
Preview vs Digital Files
| Preview | Digital Files | |
|---|---|---|
| Who sees it | Everyone (before purchase) | Buyers only (after payment) |
| Bucket | nextgate-preview-content (public) |
nextgate-digital-content (private) |
| Access | Permanent public URL | Presigned URL, expires per downloadExpiryDays |
| Count | One per product | Multiple files per product |
| Products | PHYSICAL or DIGITAL | DIGITAL only |
| Purpose | Teaser/sample before buying | Actual purchased content |
Wishlist Management
Base URL: {base_url}/api/v1/e-commerce/wishlist
Short Description: The Wishlist API lets authenticated users save products for later, organize them into named groups, transfer items between groups, and move items directly to the cart. Every wishlist operation is strictly private โ users can only read and modify their own wishlist.
Hints:
- All endpoints require a valid JWT Bearer token โ there are no public wishlist endpoints
- When adding a product, pass either
groupId(existing group) orgroupName(creates a new group) โ passing both returns400 - Group names are unique per user โ attempting to create a duplicate name returns
400 - Deleting a group with
?deleteProducts=false(default) moves all items in that group to Ungrouped;?deleteProducts=truepermanently removes those items from the wishlist PATCH /{itemId}/groupwithgroupId: nullmoves an item to Ungrouped without deleting itisInWishlist,wishlistItemId,wishlistGroupId, andwishlistGroupNameare populated on the single product detail response (GET /api/v1/e-commerce/products/{slug}) for authenticated users
Standard Response Format
Success Response Structure
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2025-09-23T10:30:45",
"data": {}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-09-23T10:30:45",
"data": "Error description"
}
Standard Response Fields
| Field | Type | Description |
|---|---|---|
success |
boolean | 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 result |
action_time |
string | ISO 8601 timestamp of when the response was generated |
data |
object/string | Response payload on success, error details on failure |
Standard Error Types
400 BAD_REQUEST: Business rule violation (duplicate product, duplicate group name, ambiguous group fields)401 UNAUTHORIZED: Missing, expired, or invalid JWT token404 NOT_FOUND: Product, wishlist item, or group not found422 UNPROCESSABLE_ENTITY: Validation errors with field-level detail500 INTERNAL_SERVER_ERROR: Unexpected server error
Endpoints
1. Add Product to Wishlist
Purpose: Adds a product to the authenticated user's wishlist. Optionally places it in an existing group (via groupId) or creates a new group on the fly (via groupName). If neither is provided the item lands in Ungrouped.
Endpoint: POST {base_url}/api/v1/e-commerce/wishlist/add
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Request JSON Sample โ add to Ungrouped (no group):
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Request JSON Sample โ add to an existing group:
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"groupId": "f9e8d7c6-b5a4-3210-fedc-ba9876543210"
}
Request JSON Sample โ add and create a new group:
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"groupName": "Birthday Gifts"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
productId |
UUID | Yes | ID of the product to add | Must be a valid, non-deleted product |
groupId |
UUID | No | Add to an existing group | Must belong to the authenticated user. Cannot be combined with groupName |
groupName |
string | No | Create a new group with this name and add the product to it | Must not already exist for this user. Cannot be combined with groupId |
Success Response JSON Sample โ added to Ungrouped:
{
"success": true,
"httpStatus": "OK",
"message": "Product added to wishlist successfully",
"action_time": "2026-06-04T14:22:10",
"data": null
}
Success Response JSON Sample โ added to existing group:
{
"success": true,
"httpStatus": "OK",
"message": "Product added to wishlist in group 'Electronics'",
"action_time": "2026-06-04T14:22:10",
"data": null
}
Success Response JSON Sample โ new group created and product added:
{
"success": true,
"httpStatus": "OK",
"message": "Product added to wishlist in group 'Birthday Gifts'",
"action_time": "2026-06-04T14:22:10",
"data": null
}
Success Response Fields:
| Field | Description |
|---|---|
message |
Confirms the product was added; includes group name when a group is involved |
Error Response JSON Sample:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "'Sony WH-1000XM5' is already in your wishlist",
"action_time": "2026-06-04T14:22:10",
"data": "'Sony WH-1000XM5' is already in your wishlist"
}
2. Get Wishlist (Flat)
Purpose: Returns the authenticated user's full wishlist as a flat list. Each item includes its group ID and group name (or null if Ungrouped). Useful for list views where grouping is handled client-side.
Endpoint: GET {base_url}/api/v1/e-commerce/wishlist
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Wishlist retrieved successfully",
"action_time": "2026-06-04T14:22:10",
"data": {
"user": {
"userId": "uuid",
"userName": "josh_dev",
"name": "Josh Sakweli"
},
"wishlistSummary": {
"totalItems": 3,
"totalValue": 749.97,
"inStockItems": 2,
"outOfStockItems": 1
},
"wishlistItems": [
{
"wishlistId": "uuid",
"productId": "uuid",
"productName": "Sony WH-1000XM5",
"productSlug": "sony-wh-1000xm5",
"productImage": "https://cdn.example.com/img.jpg",
"unitPrice": 349.99,
"isOnSale": false,
"shop": {
"shopId": "uuid",
"shopName": "Tech Haven",
"shopSlug": "tech-haven",
"logoUrl": "https://cdn.example.com/logo.jpg"
},
"availability": {
"inStock": true,
"stockQuantity": 12
},
"groupId": "uuid",
"groupName": "Birthday Gifts",
"addedAt": "2026-06-04T10:00:00"
}
],
"updatedAt": "2026-06-04T10:00:00"
}
}
Success Response Fields:
| Field | Description |
|---|---|
user.userId |
UUID of the authenticated user |
user.userName |
System username |
user.name |
Full name |
wishlistSummary.totalItems |
Total number of items in the wishlist |
wishlistSummary.totalValue |
Sum of unit prices of all items |
wishlistSummary.inStockItems |
Count of items currently in stock |
wishlistSummary.outOfStockItems |
Count of items out of stock |
wishlistItems[].wishlistId |
UUID of the wishlist entry (used for remove/transfer) |
wishlistItems[].productId |
UUID of the product |
wishlistItems[].productName |
Product display name |
wishlistItems[].productSlug |
URL-friendly product identifier |
wishlistItems[].productImage |
URL of the primary product image |
wishlistItems[].unitPrice |
Current price of the product |
wishlistItems[].isOnSale |
Whether the product is currently on sale |
wishlistItems[].shop |
Shop that sells the product (id, name, slug, logo) |
wishlistItems[].availability.inStock |
Whether the product is in stock |
wishlistItems[].availability.stockQuantity |
Current stock count |
wishlistItems[].groupId |
UUID of the group this item belongs to (null if Ungrouped) |
wishlistItems[].groupName |
Name of the group (null if Ungrouped) |
wishlistItems[].addedAt |
Timestamp when the product was added |
updatedAt |
Timestamp of the most recently added item |
3. Get Wishlist (Grouped)
Purpose: Returns the wishlist organized into sections โ one section per named group plus a separate Ungrouped section. Useful for grouped display views.
Endpoint: GET {base_url}/api/v1/e-commerce/wishlist/grouped
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Grouped wishlist retrieved successfully",
"action_time": "2026-06-04T14:22:10",
"data": {
"user": {
"userId": "uuid",
"userName": "josh_dev",
"name": "Josh Sakweli"
},
"wishlistSummary": {
"totalItems": 3,
"totalValue": 749.97,
"inStockItems": 2,
"outOfStockItems": 1
},
"groups": [
{
"groupId": "uuid",
"groupName": "Birthday Gifts",
"itemCount": 2,
"items": [ ]
}
],
"ungrouped": {
"groupId": null,
"groupName": "Ungrouped",
"itemCount": 1,
"items": [ ]
},
"updatedAt": "2026-06-04T10:00:00"
}
}
Success Response Fields:
| Field | Description |
|---|---|
user |
Same user summary as flat response |
wishlistSummary |
Same summary totals across all items |
groups |
Array of named group sections, ordered by creation date (oldest first) |
groups[].groupId |
UUID of the group |
groups[].groupName |
Name of the group |
groups[].itemCount |
Number of items in this group |
groups[].items |
Array of wishlist item responses (same shape as flat list items) |
ungrouped |
Section for items with no group assigned |
ungrouped.groupId |
Always null |
ungrouped.groupName |
Always "Ungrouped" |
ungrouped.itemCount |
Count of items with no group |
ungrouped.items |
Array of ungrouped wishlist items |
updatedAt |
Timestamp of the most recently added item |
4. Remove Item from Wishlist
Purpose: Permanently removes a specific item from the authenticated user's wishlist.
Endpoint: DELETE {base_url}/api/v1/e-commerce/wishlist/{itemId}
Access Level: ๐ Protected (Requires valid JWT โ owns the item)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
itemId |
UUID | Yes | The wishlistId of the item to remove |
Must belong to the authenticated user |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Product removed from wishlist successfully",
"action_time": "2026-06-04T14:22:10",
"data": null
}
5. Clear Wishlist
Purpose: Permanently removes all items from the authenticated user's wishlist. Groups are not deleted โ only the items inside them.
Endpoint: DELETE {base_url}/api/v1/e-commerce/wishlist/clear
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Wishlist cleared successfully",
"action_time": "2026-06-04T14:22:10",
"data": null
}
6. Move Item to Cart
Purpose: Adds a wishlist item to the user's cart at the specified quantity. The item remains in the wishlist โ it is not automatically removed.
Endpoint: POST {base_url}/api/v1/e-commerce/wishlist/move-to-cart/{itemId}
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
itemId |
UUID | Yes | The wishlistId of the item to move |
Must belong to the authenticated user |
Query Parameters:
| Parameter | Type | Required | Description | Validation | Default |
|---|---|---|---|---|---|
quantity |
integer | No | Quantity to add to cart | Min: 1 | 1 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Product moved to cart successfully",
"action_time": "2026-06-04T14:22:10",
"data": null
}
7. Transfer Item to Group
Purpose: Moves a wishlist item to a different group, or removes it from its current group by setting groupId to null (moves to Ungrouped). The item stays in the wishlist โ only its group assignment changes.
Endpoint: PATCH {base_url}/api/v1/e-commerce/wishlist/{itemId}/group
Access Level: ๐ Protected (Requires valid JWT โ owns the item)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
itemId |
UUID | Yes | The wishlistId of the item to transfer |
Must belong to the authenticated user |
Request JSON Sample โ move to a group:
{
"groupId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Request JSON Sample โ remove from group (move to Ungrouped):
{
"groupId": null
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
groupId |
UUID or null | Yes | Target group UUID, or null to move to Ungrouped |
When a UUID, must be a group that belongs to the authenticated user |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Item moved to group 'Electronics'",
"action_time": "2026-06-04T14:22:10",
"data": null
}
8. Create Group
Purpose: Creates a new named wishlist group for the authenticated user. Group names must be unique per user.
Endpoint: POST {base_url}/api/v1/e-commerce/wishlist/groups
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Request JSON Sample:
{
"name": "Electronics"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
name |
string | Yes | Display name for the group | Must not be blank. Must be unique for the authenticated user |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Wishlist group created successfully",
"action_time": "2026-06-04T14:22:10",
"data": {
"groupId": "uuid",
"name": "Electronics",
"itemCount": 0,
"createdAt": "2026-06-04T14:22:10"
}
}
Success Response Fields:
| Field | Description |
|---|---|
groupId |
UUID of the newly created group |
name |
Name of the group as saved |
itemCount |
Always 0 on creation |
createdAt |
Timestamp of group creation |
Error Response JSON Sample:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "A group named 'Electronics' already exists",
"action_time": "2026-06-04T14:22:10",
"data": "A group named 'Electronics' already exists"
}
9. Get Groups
Purpose: Returns all wishlist groups created by the authenticated user, ordered by creation date (oldest first). Each group includes a live item count.
Endpoint: GET {base_url}/api/v1/e-commerce/wishlist/groups
Access Level: ๐ Protected (Requires valid JWT)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Wishlist groups retrieved successfully",
"action_time": "2026-06-04T14:22:10",
"data": [
{
"groupId": "uuid",
"name": "Birthday Gifts",
"itemCount": 3,
"createdAt": "2026-06-01T09:00:00"
},
{
"groupId": "uuid",
"name": "Electronics",
"itemCount": 1,
"createdAt": "2026-06-03T11:30:00"
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
[].groupId |
UUID of the group |
[].name |
Display name of the group |
[].itemCount |
Current number of wishlist items in this group |
[].createdAt |
Timestamp of group creation |
10. Delete Group
Purpose: Deletes a wishlist group. Controls what happens to the items inside via the deleteProducts query parameter.
Endpoint: DELETE {base_url}/api/v1/e-commerce/wishlist/groups/{groupId}
Access Level: ๐ Protected (Requires valid JWT โ owns the group)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
Authorization |
string | Yes | Bearer <token> |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
groupId |
UUID | Yes | UUID of the group to delete | Must belong to the authenticated user |
Query Parameters:
| Parameter | Type | Required | Description | Validation | Default |
|---|---|---|---|---|---|
deleteProducts |
boolean | No | true โ permanently delete all items in the group from wishlist. false โ move items to Ungrouped, then delete group |
true or false |
false |
Success Response JSON Sample โ items moved to Ungrouped:
{
"success": true,
"httpStatus": "OK",
"message": "Group deleted, products moved to Ungrouped",
"action_time": "2026-06-04T14:22:10",
"data": null
}
Success Response JSON Sample โ items permanently deleted:
{
"success": true,
"httpStatus": "OK",
"message": "Group and all its products deleted from wishlist",
"action_time": "2026-06-04T14:22:10",
"data": null
}
Quick Reference
All Endpoints Summary
| # | Method | Path | Description |
|---|---|---|---|
| 1 | POST | /wishlist/add |
Add product to wishlist |
| 2 | GET | /wishlist |
Get flat wishlist |
| 3 | GET | /wishlist/grouped |
Get grouped wishlist |
| 4 | DELETE | /wishlist/{itemId} |
Remove item from wishlist |
| 5 | DELETE | /wishlist/clear |
Clear entire wishlist |
| 6 | POST | /wishlist/move-to-cart/{itemId} |
Move item to cart |
| 7 | PATCH | /wishlist/{itemId}/group |
Transfer item to group |
| 8 | POST | /wishlist/groups |
Create group |
| 9 | GET | /wishlist/groups |
Get all groups |
| 10 | DELETE | /wishlist/groups/{groupId} |
Delete group |
Group Behavior Rules
| Scenario | Behavior |
|---|---|
| Add with no group | Item lands in Ungrouped |
Add with groupId |
Item added to that existing group |
Add with groupName (new) |
New group created, item added to it |
Add with groupName (exists) |
400 โ duplicate group name |
Add with both groupId and groupName |
400 โ ambiguous request |
Delete group ?deleteProducts=false |
Items move to Ungrouped, group deleted |
Delete group ?deleteProducts=true |
Items permanently removed, group deleted |
Transfer item with groupId: null |
Item moved to Ungrouped |
NextGate Product Ecosystem โ Architecture & Purchase Flows
Overview
NextGate's e-commerce layer handles two fundamentally different kinds of products: physical products (tangible goods that require shipping and delivery confirmation) and digital products (files that a buyer downloads). Both types share the same payment infrastructure, checkout session system, financial rails, and inventory system โ but diverge completely at the fulfillment stage.
1. Product Types
Physical Products
A physical product has real-world stock. The platform tracks inventory, holds it during checkout, ships it through a seller, and releases escrow only after the buyer confirms physical receipt. The entire lifecycle can take days or weeks.
Digital Products
A digital product is a file (or collection of files) that the seller uploads to a private, access-controlled storage bucket. There is no shipping. There is no confirmation code. The moment payment clears, the buyer can download. Escrow releases immediately. The lifecycle is measured in seconds.
A single product can be either physical or digital โ never both. The productType field on the product record is the authoritative signal that drives every downstream decision.
2. Inventory โ Shared by Both Product Types
Key rule: Every product has a
stockQuantity. Digital and physical products are treated identically by the inventory system. The only difference is what happens after payment.
Every product โ physical or digital โ requires a stockQuantity set by the seller at creation time. The seller decides what that number is:
10 โ limited edition digital art, exclusive release
500 โ cohort-based course intake
1,000,000 โ effectively open โ seller still sets a real number
There is no "unlimited" mode and no trackInventory toggle. All inventory mechanics apply to both product types without exception:
- stockQuantity check on add-to-cart
- Inventory hold at checkout session creation
- Hold released on session expiry or cancellation
- stockQuantity decremented on successful payment
- Low stock threshold warnings apply to both
- isInStock() and canOrderQuantity() run identically
This means zero special-casing in the codebase. Every product behaves the same through cart, checkout, and payment. The divergence only begins at fulfillment.
Why sellers set quantity for digital products
The business reasons vary:
Exclusivity / scarcity โ "Only 500 copies ever sold" โ creates urgency and perceived value
License seat control โ Software licensed for exactly N seats
Cohort control โ Course intake capped at N students for direct purchase
Business guardrail โ Seller wants a hard ceiling as a safety net
Even if a seller sets 1,000,000, the system enforces it as a real number. The seller always sees and manages a stock figure.
3. The Only Real Difference Between Physical and Digital
Physical Digital
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
stockQuantity โ โ
Inventory hold at checkout โ โ
Stock decrements on buy โ โ
Low stock warnings โ โ
Requires shipping โ โ
Delivery confirmation code โ โ
Escrow held until delivery โ โ
Order โ PENDING_SHIPMENT โ โ
Escrow released immediatelyโ โ
Order โ COMPLETED โ โ
DigitalDownloadAccess โ โ
Everything above the dividing line is shared. Everything below is where the paths split.
4. How Sellers Prepare Digital Products
Before a digital product can be purchased, the seller must upload its files through a two-step process designed to handle large files without routing them through the API server.
Seller โ Request presigned upload URL
โ Platform generates time-limited URL (MinIO private bucket)
โ Seller uploads file DIRECTLY to MinIO (API server bypassed)
โ Seller confirms upload to API
โ Platform records file metadata and links to product
A product can have multiple files. Each gets its own record. Buyers get access to all files on purchase.
Sellers configure per product:
stockQuantity โ required ยท how many units can ever be sold
lowStockThreshold โ when to trigger low stock warning
maxQuantityForDigital โ max a single buyer can purchase in one order
(1 for personal-use content, higher for licenses/gifts)
Download rules:
expiryDays โ days download links stay active after purchase (default: 7)
downloadCap โ max downloads per buyer (default: unlimited)
clockStart โ expiry from purchase time OR from first download
5. The Cart
The cart is a persistent bag of products. It does not distinguish between physical and digital items โ both coexist freely. Stock availability is checked on add-to-cart for both types.
The physical/digital split only becomes relevant at fulfillment โ not at cart, not at payment.
6. Checkout Session Types
Every purchase flows through a checkout session โ a short-lived record holding purchase intent before payment. All four session types work for both physical and digital products.
| Session Type | Description |
|---|---|
REGULAR_DIRECTLY |
Single-item purchase from the product page |
REGULAR_CART |
Multi-item purchase from a cart |
GROUP_PURCHASE |
Coordinated group buy at a discounted group price |
INSTALLMENT |
Down payment now, remainder paid over time |
Sessions expire (typically 15โ30 minutes). During this window, inventory is held for both physical and digital products โ preventing overselling while the buyer completes payment.
7. Shipping Logic
All items digital? โ Skip shipping entirely. Cost = 0.
Inventory hold still applies.
Any item physical? โ Shipping address + method required. Inventory held.
Mixed cart (physical + digital, same shop)?
โ Engine splits into TWO sessions automatically:
Session 1: physical items โ shipping lifecycle
Session 2: digital items โ download lifecycle
8. Payment Infrastructure (Shared by All Types)
Payment works identically regardless of product type:
Buyer wallet โ debited
Escrow โ credited (money held, not yet with seller)
Ledger entry โ recorded (double-entry, full audit trail)
Tx history โ updated for buyer
For installment purchases, only the down payment moves at session time. Each subsequent payment creates its own ledger entry.
Escrow is the control point. Physical โ held until buyer confirms delivery. Digital โ released immediately at order creation.
9. Post-Payment Fulfillment โ The Core Split
Physical path
Payment completes
โ stockQuantity decremented
โ Inventory hold released
โ Order created [PENDING_SHIPMENT]
โ Seller notified
โ Seller ships โ marks order SHIPPED
โ 6-digit confirmation code generated โ sent to buyer
โ Buyer enters code in app
โ Escrow releases to seller
โ Order [COMPLETED]
Escrow is held the entire shipping period. The code is the handshake โ seller cannot claim money without buyer confirming receipt.
Digital path
Payment completes
โ stockQuantity decremented
โ Inventory hold released
โ Order created [COMPLETED immediately]
โ Escrow released to seller immediately
โ DigitalDownloadAccess records created (one per file)
โ Buyer notified with download link
โ Buyer downloads within expiry window
No shipping. No confirmation code. Delivery = access records created.
10. Group Purchase โ Physical vs Digital
Both product types support group purchase. The financial hold (escrow per participant) and inventory hold are identical in both cases during the waiting period. What differs is post-completion fulfillment.
Physical group purchase
Each participant pays โ Escrow held + Inventory held per participant
Group fills โ COMPLETED
โ Physical orders created for all participants [PENDING_SHIPMENT]
โ stockQuantity decremented for all participants
โ Each participant goes through shipping lifecycle individually
โ Each escrow releases on individual delivery confirmation
Group expires without filling:
โ All escrows refunded
โ All inventory holds released
Digital group purchase
Each participant pays โ Escrow held + Inventory held per participant
Group fills โ COMPLETED
โ Orders created for all participants [COMPLETED immediately]
โ stockQuantity decremented for all participants
โ All escrows released immediately
โ DigitalDownloadAccess records created for every participant
โ All buyers can download immediately
Group expires without filling:
โ All escrows refunded
โ All inventory holds released (identical to physical)
The seller's participant cap on the group instance is separate from
stockQuantity. Both are enforced โ a buyer cannot join if either the group is full or the product stock is exhausted.
11. Installment Purchase โ Physical vs Digital
Both product types support installment. The payment schedule, ledger entries, agreement lifecycle, early payoff, and flexible payment features are identical. What differs is fulfillment timing.
Fulfillment timing options
IMMEDIATE โ order and access created after the down payment. Buyer gets the product now, pays over time.
- Physical: seller ships after down payment, takes risk on future payments.
- Digital: buyer downloads immediately. If buyer defaults, downloaded files cannot be revoked. Seller has no recourse.
AFTER_PAYMENT โ order and access created only after the final payment clears.
- Physical: layaway โ seller holds stock, ships at the end.
- Digital: safest model โ access never opens until fully paid. On default, access records simply never created.
Platform recommendation: AFTER_PAYMENT is the default for digital installment products. Sellers must explicitly opt into IMMEDIATE with acknowledgment that pre-delivery means no recourse on default.
On default โ digital IMMEDIATE
Buyer stops paying โ Agreement DEFAULTED
โ No new access records created for future files
โ Already-downloaded files cannot be revoked โ known limitation, seller accepts this
On default โ digital AFTER_PAYMENT
Buyer stops paying โ Agreement DEFAULTED
โ Access records never created
โ No content ever delivered โ clean outcome
12. The Download System
On every successful digital purchase (any session type, any fulfillment trigger):
Platform creates DigitalDownloadAccess record per file:
- buyer ID + order ID + file ID
- downloadCount (starts at 0)
- maxDownloads (null = unlimited, or seller-set cap)
- accessExpiresAt (now + expiry window)
- firstDownloadAt (set on first use)
On every download request:
Buyer hits authenticated endpoint
โ Check 1: Does buyer own an active access record for this file?
โ Check 2: Has expiry window passed?
โ Check 3: Has download cap been reached?
All pass โ Platform generates presigned GET URL (5-min TTL)
โ Buyer browser downloads DIRECTLY from MinIO private bucket
โ Access record downloadCount incremented
โ API server is NOT in the file transfer path
The 5-minute TTL means a leaked URL is useless within minutes. The raw MinIO object key is never exposed to the buyer.
13. Scenario Walkthrough โ All Combinations
Scenario A โ Direct purchase, physical product
Buyer finds a T-shirt. Clicks "Buy Now". Quantity: 1.
REGULAR_DIRECTLY session created
โ Stock check passes ยท Inventory held
โ Shipping address + method required
โ Buyer pays โ Escrow funded
โ stockQuantity decremented ยท hold released
โ Order created [PENDING_SHIPMENT]
โ Seller ships โ marks SHIPPED
โ 6-digit code sent to buyer
โ Buyer confirms โ Escrow released
โ Order [COMPLETED]
Scenario B โ Direct purchase, digital product
Buyer finds a PDF course. Clicks "Buy Now". Quantity: 1.
REGULAR_DIRECTLY session created
โ Stock check passes ยท Inventory held
โ No shipping fields collected
โ Buyer pays โ Escrow funded and immediately released
โ stockQuantity decremented ยท hold released
โ Order created [COMPLETED]
โ 3 DigitalDownloadAccess records created (one per chapter PDF)
โ Buyer receives download link ยท downloads within 7 days
Scenario C โ Direct purchase, digital product, quantity 3
Buyer wants 3 software licenses to distribute to colleagues.
REGULAR_DIRECTLY session created (quantity: 3)
โ Stock check: stockQuantity >= 3? passes ยท 3 units held
โ No shipping fields collected
โ Buyer pays (unitPrice ร 3) โ Escrow funded and immediately released
โ stockQuantity decremented by 3 ยท hold released
โ Order created [COMPLETED]
โ 6 DigitalDownloadAccess records created (3 sets ร 2 files per product)
โ Each set is independent โ buyer can share with colleagues
Scenario D โ Cart, physical only, multiple shops
Phone case from Shop A + charger from Shop B.
REGULAR_CART session created
โ Inventory held for both items
โ Buyer pays once
โ Order engine groups by shop:
Order 1: Shop A [PENDING_SHIPMENT]
Order 2: Shop B [PENDING_SHIPMENT]
โ Each seller ships independently
โ Each escrow releases on individual buyer confirmation
Scenario E โ Cart, digital only
Video course + design template pack.
REGULAR_CART session created
โ Stock check + inventory hold for both digital products
โ No shipping.
โ Buyer pays once
โ Order 1: video course [COMPLETED] โ 4 access records
โ Order 2: template pack [COMPLETED] โ 2 access records
โ Buyer downloads all 6 files within 7 days
Scenario F โ Cart, mixed physical + digital, same shop
Printed book (physical) + PDF supplement (digital), same shop.
Engine detects mixed cart โ splits automatically:
Session 1: printed book โ shipping lifecycle ยท inventory held
Session 2: PDF โ download lifecycle ยท inventory held
Buyer sees one checkout flow, gets two orders in history:
Order 1 (book): [PENDING_SHIPMENT] โ shipping โ confirmation โ escrow releases
Order 2 (PDF): [COMPLETED] โ download access immediate
Scenario G โ Group purchase, physical product
5 buyers collectively buy a speaker.
Buyer 1 initiates โ GROUP_PURCHASE session โ pays โ group instance created (timer starts)
Buyers 2โ5 join โ each pays โ inventory held + escrow held per participant
Group fills โ COMPLETED
โ Physical orders created for all 5 [PENDING_SHIPMENT]
โ stockQuantity decremented for all 5
โ Each participant ships independently
โ Each escrow releases on individual delivery confirmation
Timer expires before filling:
โ All 5 escrows refunded ยท all inventory holds released
Scenario H โ Group purchase, digital product
30 buyers join a cohort-based online course. Seller stock: 30.
Buyers 1โ30 join โ each pays โ inventory held + escrow held per participant
Group fills (30th seat) โ COMPLETED
โ Orders created for all 30 [COMPLETED immediately]
โ stockQuantity decremented by 30 (now 0 โ sold out)
โ All 30 escrows released immediately
โ DigitalDownloadAccess records created for every participant
โ All 30 buyers can download immediately
Timer expires before filling:
โ All escrows refunded ยท all inventory holds released
Scenario I โ Installment, physical, IMMEDIATE fulfillment
Laptop, 6-month plan, 20% down, seller ships immediately.
INSTALLMENT session created
โ Stock check ยท inventory held
โ Down payment charged (20%)
โ Agreement created (6 scheduled payments)
โ Order created [PENDING_SHIPMENT] โ immediately after down payment
โ stockQuantity decremented ยท hold released
โ Seller ships
โ Monthly payments auto-process via scheduled jobs
Scenario J โ Installment, physical, AFTER_PAYMENT fulfillment
Same laptop, layaway model.
INSTALLMENT session created
โ Stock check ยท inventory held
โ Down payment charged
โ Agreement created
โ NO order created yet ยท inventory held until final payment
โ Monthly payments auto-process
โ Final payment clears โ Agreement COMPLETED
โ Order created [PENDING_SHIPMENT] โ only now
โ stockQuantity decremented ยท hold released
โ Shipping lifecycle begins
Scenario K โ Installment, digital, AFTER_PAYMENT (recommended)
Video course library, 500,000 TZS, 3-month plan.
INSTALLMENT session created
โ Stock check ยท inventory held
โ Down payment charged (30%)
โ Agreement created (3 scheduled payments)
โ NO order, NO download access yet ยท inventory held
โ Monthly payments auto-process
โ Final payment clears โ Agreement COMPLETED
โ stockQuantity decremented ยท hold released
โ Order created [COMPLETED]
โ DigitalDownloadAccess records created โ only now
โ Buyer downloads
If buyer defaults:
โ Agreement DEFAULTED
โ Inventory hold released ยท stockQuantity restored
โ Access records never created ยท no content delivered
Scenario L โ Installment, digital, IMMEDIATE fulfillment
Same course, seller opts into IMMEDIATE.
INSTALLMENT session created
โ Stock check ยท inventory held
โ Down payment charged (30%)
โ Agreement created
โ stockQuantity decremented ยท hold released
โ Order created [COMPLETED] โ immediately
โ DigitalDownloadAccess records created โ immediately
โ Buyer downloads now
Monthly payments continue auto-processing.
If buyer defaults:
โ Agreement DEFAULTED
โ Already-downloaded files CANNOT be revoked โ seller accepted this risk
14. Escrow Behavior Summary
| Scenario | Inventory Held | Escrow Released |
|---|---|---|
| Physical โ direct | Yes, at session creation | On delivery confirmation |
| Digital โ direct | Yes, at session creation | Immediately at order creation |
| Physical โ group | Yes, per participant | Per delivery confirmation |
| Digital โ group | Yes, per participant | Immediately when group completes |
| Group โ expired / failed | Released to stock | Refunded to all participants |
| Physical โ installment IMMEDIATE | Yes, until down pmt | Per installment via direct ledger |
| Physical โ installment AFTER_PAYMENT | Yes, until final pmt | After final payment |
| Digital โ installment IMMEDIATE | Yes, until down pmt | After down payment |
| Digital โ installment AFTER_PAYMENT | Yes, until final pmt | After final payment |
15. MinIO Bucket Architecture
nextgate-public (world-readable)
โ Product images ยท shop logos ยท avatars ยท category images
โ URLs are permanent ยท no expiry
nextgate-digital-content (private ยท no public access)
โ Paid digital product files only
โ Accessible only via presigned URLs (5-min TTL)
โ Generated by API after access verification
โ Raw object key never exposed to buyers
This separation ensures a seller can never accidentally expose a paid file by uploading to the wrong location.
16. Key Invariants
- Every money movement goes through the double-entry ledger.
No direct wallet balance adjustments exist.
- Escrow receives the full payment before any fulfillment action begins.
- Every product has a stockQuantity โ physical and digital alike.
There is no "unlimited" mode. Sellers set a real number.
- Inventory is held for both physical and digital products during the
checkout session window. Released on expiry, cancellation, or payment.
- Orders are never created before:
regular purchase โ payment completes
group purchase โ group fills
installment โ fulfillment trigger (down pmt or final pmt)
- Download links never expose the raw MinIO object key.
Only opaque access identifiers resolve to presigned URLs via authenticated API.
- A checkout session produces orders exactly once.
Idempotency checks prevent duplicates under retry or failure.
- Session expiry is enforced before payment processing.
An expired session cannot be paid.
- For digital installment IMMEDIATE: already-downloaded files cannot be
revoked on default. Sellers must explicitly accept this risk.
- For digital installment AFTER_PAYMENT: default means access records
are never created and stockQuantity is restored. Clean outcome.
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 |
Group Purchase
Base URL: https://apinexgate.glueauth.com/api/v1/
Short Description: The Group Purchase API enables collaborative buying where multiple users join together to purchase products at discounted group prices. Users can create new groups, join existing groups, transfer between groups, and track their participations. The system automatically handles seat management, expiration, and order creation when groups are completed.
Hints:
- Groups automatically expire based on product's
groupTimeLimitHourssetting - Groups complete automatically when all seats are filled
- Users can join the same group multiple times to buy more seats (Hybrid Approach)
- Transfer between groups only allowed for same product, shop, and price
- Empty groups are automatically soft-deleted after all participants transfer out
- Group codes are auto-generated with format: GP-XXXXXX (6 random characters)
- Only WALLET payment method supported for group purchases
- Purchase and transfer history tracked for each participant
- Seats are released when users transfer out of groups
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": {
// Actual response data goes here
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-10-02T10: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 - GET - Green (Safe, read-only operations)
- POST - POST - Blue (Create new resources)
Endpoints
1. Get Available Groups for Product
Purpose: Retrieves all available (open, not expired, not full) group purchase instances for a specific product.
Endpoint: GET {base_url}/group-purchases/product/{productId}/available
Access Level: ๐ Public (No Authentication Required)
Authentication: None
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| productId | string (UUID) | Yes | Unique identifier of the product | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Available groups retrieved successfully",
"action_time": "2025-10-02T14:30:45",
"data": [
{
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopName": "TechWorld Electronics",
"groupPrice": 80000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 4,
"seatsRemaining": 6,
"totalParticipants": 3,
"progressPercentage": 40.00,
"status": "OPEN",
"expiresAt": "2025-10-02T20:30:45",
"isExpired": false,
"isUserMember": false,
"participants": [
{
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"contributionPercentage": 50.00
},
{
"userId": "user2345-6789-01bc-def1-234567890123",
"userName": "jane_smith",
"userProfilePicture": "https://cdn.nextgate.com/profiles/jane.jpg",
"quantity": 1,
"contributionPercentage": 25.00
},
{
"userId": "user3456-7890-12cd-ef12-345678901234",
"userName": "bob_wilson",
"userProfilePicture": null,
"quantity": 1,
"contributionPercentage": 25.00
}
]
},
{
"groupInstanceId": "gp234567-8901-bcde-f123-456789012345",
"groupCode": "GP-B2Y8M5",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopName": "TechWorld Electronics",
"groupPrice": 80000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 7,
"seatsRemaining": 3,
"totalParticipants": 5,
"progressPercentage": 70.00,
"status": "OPEN",
"expiresAt": "2025-10-02T18:45:30",
"isExpired": false,
"isUserMember": true,
"participants": [
{
"userId": "user4567-8901-23de-f234-567890123456",
"userName": "alice_brown",
"userProfilePicture": "https://cdn.nextgate.com/profiles/alice.jpg",
"quantity": 3,
"contributionPercentage": 42.86
}
]
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| groupInstanceId | Unique identifier for the group |
| groupCode | Human-readable group code (e.g., GP-A3X7K9) |
| productName | Name of the product in this group |
| productImage | Product image URL |
| shopName | Shop selling this product |
| groupPrice | Discounted price per unit in TZS |
| savingsPercentage | Percentage saved vs regular price |
| currency | Currency code (TZS) |
| totalSeats | Maximum number of seats in this group |
| seatsOccupied | Number of seats currently filled |
| seatsRemaining | Available seats remaining |
| totalParticipants | Number of unique participants |
| progressPercentage | Group completion percentage (0-100) |
| status | Group status (OPEN, COMPLETED, FAILED, DELETED) |
| expiresAt | When the group expires |
| isExpired | Whether the group has expired |
| isUserMember | Whether authenticated user is in this group |
| participants | Array of participant previews |
| participants[].userId | Participant's user ID |
| participants[].userName | Participant's username |
| participants[].userProfilePicture | Participant's profile picture URL |
| participants[].quantity | Number of seats this participant holds |
| participants[].contributionPercentage | Percentage of total seats this participant holds |
Error Response Examples:
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"
}
2. Get Group by ID
Purpose: Retrieves detailed information about a specific group purchase instance including all participants and their histories.
Endpoint: GET {base_url}/group-purchases/{groupId}
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 |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| groupId | string (UUID) | Yes | Unique identifier of the group | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Group retrieved successfully",
"action_time": "2025-10-02T14:35:45",
"data": {
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productId": "prod1234-5678-90ab-cdef-123456789012",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopId": "shop1234-5678-90ab-cdef-123456789012",
"shopName": "TechWorld Electronics",
"shopLogo": "https://cdn.nextgate.com/shops/techworld-logo.jpg",
"regularPrice": 150000.00,
"groupPrice": 80000.00,
"savingsAmount": 70000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 4,
"seatsRemaining": 6,
"totalParticipants": 3,
"progressPercentage": 40.00,
"status": "OPEN",
"isExpired": false,
"isFull": false,
"initiatorId": "user1234-5678-90ab-cdef-123456789012",
"initiatorName": "john_doe",
"durationHours": 24,
"createdAt": "2025-10-01T20:30:45",
"expiresAt": "2025-10-02T20:30:45",
"completedAt": null,
"maxPerCustomer": 5,
"isUserMember": true,
"myParticipantId": "part1234-5678-90ab-cdef-123456789012",
"myQuantity": 2,
"participants": [
{
"participantId": "part1234-5678-90ab-cdef-123456789012",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"totalPaid": 160000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-01T20:30:45",
"contributionPercentage": 50.00,
"purchaseCount": 1,
"hasTransferred": false,
"purchaseHistory": [
{
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"quantity": 2,
"amountPaid": 160000.00,
"purchasedAt": "2025-10-01T20:30:45",
"transactionId": "txn_1234567890abcdef"
}
],
"transferHistory": []
},
{
"participantId": "part2345-6789-01bc-def1-234567890123",
"userId": "user2345-6789-01bc-def1-234567890123",
"userName": "jane_smith",
"userProfilePicture": "https://cdn.nextgate.com/profiles/jane.jpg",
"quantity": 1,
"totalPaid": 80000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-01T21:15:20",
"contributionPercentage": 25.00,
"purchaseCount": 1,
"hasTransferred": false
},
{
"participantId": "part3456-7890-12cd-ef12-345678901234",
"userId": "user3456-7890-12cd-ef12-345678901234",
"userName": "bob_wilson",
"userProfilePicture": null,
"quantity": 1,
"totalPaid": 80000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-02T08:45:10",
"contributionPercentage": 25.00,
"purchaseCount": 1,
"hasTransferred": false
}
]
}
}
Success Response Fields:
| Field | Description |
|---|---|
| groupInstanceId | Unique identifier for the group |
| groupCode | Human-readable group code |
| productId | Product unique identifier |
| productName | Product name |
| productImage | Product image URL |
| shopId | Shop identifier |
| shopName | Shop name |
| shopLogo | Shop logo URL |
| regularPrice | Original product price in TZS |
| groupPrice | Discounted group price in TZS |
| savingsAmount | Amount saved per unit (regularPrice - groupPrice) |
| savingsPercentage | Percentage saved |
| currency | Currency code (TZS) |
| totalSeats | Maximum seats in group |
| seatsOccupied | Filled seats |
| seatsRemaining | Available seats |
| totalParticipants | Unique participants count |
| progressPercentage | Completion percentage |
| status | OPEN, COMPLETED, FAILED, or DELETED |
| isExpired | Whether group expired |
| isFull | Whether all seats filled |
| initiatorId | User who created the group |
| initiatorName | Initiator's username |
| durationHours | Group duration in hours |
| createdAt | Group creation timestamp |
| expiresAt | Expiration timestamp |
| completedAt | Completion timestamp (null if not completed) |
| maxPerCustomer | Maximum seats per customer |
| isUserMember | Whether authenticated user is member |
| myParticipantId | User's participant ID (if member) |
| myQuantity | User's seat quantity (if member) |
| participants | Array of detailed participant information |
| participants[].participantId | Participant unique identifier |
| participants[].userId | User ID |
| participants[].userName | Username |
| participants[].userProfilePicture | Profile picture URL |
| participants[].quantity | Number of seats held |
| participants[].totalPaid | Total amount paid in TZS |
| participants[].status | ACTIVE, TRANSFERRED_OUT, or REFUNDED |
| participants[].joinedAt | Join timestamp |
| participants[].contributionPercentage | Percentage of total seats |
| participants[].purchaseCount | Number of purchases made |
| participants[].hasTransferred | Whether participated in transfers |
| participants[].purchaseHistory | Purchase records (only shown to participant owner) |
| participants[].transferHistory | Transfer records (only shown to participant owner) |
Error Response Examples:
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Group not found with ID: gp123456-7890-abcd-ef12-345678901234",
"action_time": "2025-10-02T14:35:45",
"data": "Group not found with ID: gp123456-7890-abcd-ef12-345678901234"
}
3. Get Group by Code
Purpose: Retrieves group information using the human-readable group code instead of UUID.
Endpoint: GET {base_url}/group-purchases/code/{groupCode}
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 |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| groupCode | string | Yes | Group code (e.g., GP-A3X7K9) | Format: GP-XXXXXX (6 alphanumeric characters) |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Group retrieved successfully",
"action_time": "2025-10-02T14:40:45",
"data": {
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productId": "prod1234-5678-90ab-cdef-123456789012",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopId": "shop1234-5678-90ab-cdef-123456789012",
"shopName": "TechWorld Electronics",
"shopLogo": "https://cdn.nextgate.com/shops/techworld-logo.jpg",
"regularPrice": 150000.00,
"groupPrice": 80000.00,
"savingsAmount": 70000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 4,
"seatsRemaining": 6,
"totalParticipants": 3,
"progressPercentage": 40.00,
"status": "OPEN",
"isExpired": false,
"isFull": false,
"initiatorId": "user1234-5678-90ab-cdef-123456789012",
"initiatorName": "john_doe",
"durationHours": 24,
"createdAt": "2025-10-01T20:30:45",
"expiresAt": "2025-10-02T20:30:45",
"completedAt": null,
"maxPerCustomer": 5,
"isUserMember": false,
"myParticipantId": null,
"myQuantity": null,
"participants": []
}
}
Success Response Fields:
| Field | Description |
|---|---|
| All fields | Same as "Get Group by ID" response |
Error Response Examples:
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Group not found with code: GP-INVALID",
"action_time": "2025-10-02T14:40:45",
"data": "Group not found with code: GP-INVALID"
}
4. Get My Groups
Purpose: Retrieves all groups that the authenticated user is a member of, optionally filtered by status.
Endpoint: GET {base_url}/group-purchases/my-groups
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 |
Query Parameters:
| Parameter | Type | Required | Description | Validation | Default |
|---|---|---|---|---|---|
| status | string | No | Filter by group status | enum: OPEN, COMPLETED, FAILED, DELETED | null (all statuses) |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "My groups retrieved successfully",
"action_time": "2025-10-02T14:45:45",
"data": [
{
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopName": "TechWorld Electronics",
"groupPrice": 80000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 8,
"seatsRemaining": 2,
"totalParticipants": 5,
"progressPercentage": 80.00,
"status": "OPEN",
"expiresAt": "2025-10-02T20:30:45",
"isExpired": false,
"isUserMember": true,
"participants": [
{
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"contributionPercentage": 25.00
}
]
},
{
"groupInstanceId": "gp234567-8901-bcde-f123-456789012345",
"groupCode": "GP-B2Y8M5",
"productName": "Smart Watch Series 5",
"productImage": "https://cdn.nextgate.com/products/watch-005.jpg",
"shopName": "Gadget Hub",
"groupPrice": 250000.00,
"savingsPercentage": 28.57,
"currency": "TZS",
"totalSeats": 15,
"seatsOccupied": 15,
"seatsRemaining": 0,
"totalParticipants": 8,
"progressPercentage": 100.00,
"status": "COMPLETED",
"expiresAt": "2025-10-01T18:20:30",
"isExpired": false,
"isUserMember": true,
"participants": []
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| All fields | Same as "Get Available Groups for Product" response |
5. Get My Participations
Purpose: Retrieves all active participations of the authenticated user across all groups with detailed participant information.
Endpoint: GET {base_url}/group-purchases/my-participations
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 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "My participations retrieved successfully",
"action_time": "2025-10-02T14:50:45",
"data": [
{
"participantId": "part1234-5678-90ab-cdef-123456789012",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"totalPaid": 160000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-01T20:30:45",
"purchaseCount": 1,
"hasTransferred": false,
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"purchaseHistory": [
{
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"quantity": 2,
"amountPaid": 160000.00,
"purchasedAt": "2025-10-01T20:30:45",
"transactionId": "txn_1234567890abcdef"
}
],
"transferHistory": []
},
{
"participantId": "part2345-6789-01bc-def1-234567890123",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 3,
"totalPaid": 750000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-02T10:15:20",
"purchaseCount": 2,
"hasTransferred": true,
"checkoutSessionId": "checkout2-3456-7890-1bcd-ef2345678901",
"purchaseHistory": [
{
"checkoutSessionId": "checkout2-3456-7890-1bcd-ef2345678901",
"quantity": 1,
"amountPaid": 250000.00,
"purchasedAt": "2025-10-02T10:15:20",
"transactionId": "txn_2345678901bcdef0"
},
{
"checkoutSessionId": "checkout3-4567-8901-2cde-f34567890123",
"quantity": 2,
"amountPaid": 500000.00,
"purchasedAt": "2025-10-02T12:30:15",
"transactionId": "txn_3456789012cdef01"
}
],
"transferHistory": [
{
"fromGroupId": "gp345678-9012-cdef-1234-567890123456",
"fromGroupCode": null,
"toGroupId": "gp234567-8901-bcde-f123-456789012345",
"toGroupCode": null,
"transferredAt": "2025-10-02T11:45:30",
"reason": "Transferred 1 seats from group GP-C3Z9N7"
}
]
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| participantId | Participant unique identifier |
| userId | User ID of the participant |
| userName | Username |
| userProfilePicture | Profile picture URL |
| quantity | Total seats held |
| totalPaid | Total amount paid in TZS |
| status | ACTIVE, TRANSFERRED_OUT, or REFUNDED |
| joinedAt | When user joined this group |
| purchaseCount | Number of purchases made in this group |
| hasTransferred | Whether user has transfer history |
| checkoutSessionId | Original checkout session ID |
| purchaseHistory | Array of all purchases in this group |
| purchaseHistory[].checkoutSessionId | Checkout session for this purchase |
| purchaseHistory[].quantity | Seats purchased |
| purchaseHistory[].amountPaid | Amount paid for this purchase |
| purchaseHistory[].purchasedAt | Purchase timestamp |
| purchaseHistory[].transactionId | Payment transaction ID |
| transferHistory | Array of all transfers involving this participation |
| transferHistory[].fromGroupId | Source group ID |
| transferHistory[].toGroupId | Target group ID |
| transferHistory[].transferredAt | Transfer timestamp |
| transferHistory[].reason | Transfer reason/description |
6. Transfer Seats Between Groups
Purpose: Transfers seats from one group to another. Allows users to move their purchases between compatible groups (same product, shop, and price).
Endpoint: POST {base_url}/group-purchases/transfer
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:
{
"sourceGroupId": "gp123456-7890-abcd-ef12-345678901234",
"targetGroupId": "gp234567-8901-bcde-f123-456789012345",
"quantity": 2
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| sourceGroupId | string (UUID) | Yes | Group to transfer from | Valid UUID, user must be active member |
| targetGroupId | string (UUID) | Yes | Group to transfer to | Valid UUID, must be different from source |
| quantity | integer | Yes | Number of seats to transfer | Min: 1, cannot exceed user's quantity in source group |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Seats transferred successfully",
"action_time": "2025-10-02T15:00:45",
"data": {
"participantId": "part2345-6789-01bc-def1-234567890123",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 3,
"totalPaid": 0.00,
"status": "ACTIVE",
"joinedAt": "2025-10-02T15:00:45",
"purchaseCount": 0,
"hasTransferred": true,
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"purchaseHistory": [],
"transferHistory": [
{
"fromGroupId": "gp123456-7890-abcd-ef12-345678901234",
"fromGroupCode": null,
"toGroupId": "gp234567-8901-bcde-f123-456789012345",
"toGroupCode": null,
"transferredAt": "2025-10-02T15:00:45",
"reason": "Transferred 2 seats from group GP-A3X7K9"
}
]
}
}
Success Response Fields:
| Field | Description |
|---|---|
| participantId | Updated participant ID in target group |
| userId | User ID |
| userName | Username |
| userProfilePicture | Profile picture URL |
| quantity | New total quantity in target group |
| totalPaid | Total paid (0 for transfers) |
| status | Participant status (ACTIVE) |
| joinedAt | Join timestamp (current time if new to target) |
| purchaseCount | Purchase count (0 for pure transfers) |
| hasTransferred | Always true for transferred participants |
| checkoutSessionId | Original checkout session ID |
| purchaseHistory | Purchase history (shown only to owner) |
| transferHistory | Transfer history including this transfer |
Error Response Examples:
Bad Request - Same Source and Target (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Source and target groups must be different",
"action_time": "2025-10-02T15:00:45",
"data": "Source and target groups must be different"
}
Bad Request - Insufficient Seats (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Not enough seats to transfer. You have: 1, requested: 2",
"action_time": "2025-10-02T15:00:45",
"data": "Not enough seats to transfer. You have: 1, requested: 2"
}
Bad Request - Target Group Full (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Not enough seats available. Requested: 2, Available: 1",
"action_time": "2025-10-02T15:00:45",
"data": "Not enough seats available. Requested: 2, Available: 1"
}
Bad Request - Product Mismatch (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot transfer between groups with different products",
"action_time": "2025-10-02T15:00:45",
"data": "Cannot transfer between groups with different products"
}
Bad Request - Price Mismatch (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot transfer. Price mismatch: 80000.00 vs 75000.00",
"action_time": "2025-10-02T15:00:45",
"data": "Cannot transfer. Price mismatch: 80000.00 vs 75000.00"
}
Not Found - Not a Participant (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "You are not a participant in the source group",
"action_time": "2025-10-02T15:00:45",
"data": "You are not a participant in the source group"
}
7. Update Group Name
Purpose: Allow the group initiator to update the group name. Group names must be unique among active (OPEN) groups.
Endpoint: PATCH {base_url}/api/v1/groups/{groupId}/name
Access Level: ๐ Protected (Requires Authentication - Group Initiator Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| groupId | UUID | Yes | Unique identifier of the group | Valid UUID format |
Request Body:
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
| groupName | string | Yes | New name for the group | 3-100 characters, unique among active groups |
Request JSON Sample:
{
"groupName": "iPhone 15 Pro - Dar es Salaam Deal"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Group name updated successfully",
"action_time": "2025-12-30T10:00:00",
"data": {
"groupInstanceId": "550e8400-e29b-41d4-a716-446655440000",
"groupCode": "GP-ABC123",
"groupName": "iPhone 15 Pro - Dar es Salaam Deal",
"productName": "iPhone 15 Pro",
"productImage": "https://storage.example.com/products/iphone15.jpg",
"regularPrice": 2500000.00,
"groupPrice": 2200000.00,
"totalSeats": 5,
"seatsOccupied": 3,
"seatsRemaining": 2,
"totalParticipants": 3,
"status": "OPEN",
"createdAt": "2025-12-30T08:00:00",
"expiresAt": "2025-12-31T08:00:00",
"updatedAt": "2025-12-30T10:00:00"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| groupInstanceId | Unique identifier of the group |
| groupCode | Auto-generated group code (immutable) |
| groupName | Updated group name |
| productName | Name of the product |
| productImage | Product image URL |
| regularPrice | Regular product price |
| groupPrice | Discounted group price |
| totalSeats | Total seats available in the group |
| seatsOccupied | Number of seats currently occupied |
| seatsRemaining | Number of seats still available |
| totalParticipants | Number of participants in the group |
| status | Group status (OPEN) |
| createdAt | Group creation timestamp |
| expiresAt | Group expiration timestamp |
| updatedAt | Last update timestamp |
Error Response JSON Samples:
Group not found:
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Group not found",
"action_time": "2025-12-30T10:00:00",
"data": null
}
Not the initiator:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Only the group initiator can change the group name",
"action_time": "2025-12-30T10:00:00",
"data": null
}
Group not active:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot rename group with status: COMPLETED",
"action_time": "2025-12-30T10:00:00",
"data": null
}
Group expired:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot rename expired group",
"action_time": "2025-12-30T10:00:00",
"data": null
}
Invalid name length:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group name must be between 3 and 100 characters",
"action_time": "2025-12-30T10:00:00",
"data": null
}
Name already taken:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group name already taken: iPhone 15 Pro - Dar es Salaam Deal",
"action_time": "2025-12-30T10:00:00",
"data": null
}
Standard Error Types:
| Status | Message | Cause |
|---|---|---|
400 BAD_REQUEST |
Only the group initiator can change the group name | User is not the group initiator |
400 BAD_REQUEST |
Cannot rename group with status: {status} | Group is not OPEN |
400 BAD_REQUEST |
Cannot rename deleted group | Group has been deleted |
400 BAD_REQUEST |
Cannot rename expired group | Group has expired |
400 BAD_REQUEST |
Group name must be between 3 and 100 characters | Invalid name length |
400 BAD_REQUEST |
Group name already taken: {name} | Name exists on another active group |
401 UNAUTHORIZED |
User not authenticated | Missing or invalid token |
404 NOT_FOUND |
Group not found | Invalid group ID |
Business Rules:
| Rule | Description |
|---|---|
| Initiator only | Only the user who created the group can rename it |
| Active groups only | Group must have status OPEN |
| Not expired | Group must not be past its expiration time |
| Not deleted | Group must not be soft-deleted |
| Unique name | Name must be unique among all active (OPEN) groups |
| Name length | Must be between 3 and 100 characters |
| Trimmed | Leading/trailing whitespace is automatically removed |
Default Group Name: When a group is created, the name is auto-generated as:
{groupCode}-{productName}
Example: GP-ABC123-iPhone 15 Pro
Usage Example:
curl -X PATCH \
'https://api.nexgate.com/api/v1/groups/550e8400-e29b-41d4-a716-446655440000/name' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...' \
-H 'Content-Type: application/json' \
-d '{
"groupName": "iPhone 15 Pro - Dar es Salaam Deal"
}'
Group Purchase Workflow
1. Creating a New Group
When a user makes a GROUP_PURCHASE checkout and no groupInstanceId is provided in metadata:
Flow:
- User creates checkout session with
sessionType: GROUP_PURCHASE - User processes payment (WALLET only)
- Payment completes successfully
- System automatically creates new group instance
- User becomes first participant (initiator)
- Group gets unique code (e.g., GP-A3X7K9)
- Group status set to OPEN
- Expiration set based on product's
groupTimeLimitHours
2. Joining an Existing Group
When a user makes a GROUP_PURCHASE checkout with groupInstanceId in metadata:
Flow:
- User finds available group (via product page or group code)
- User creates checkout session with
sessionType: GROUP_PURCHASE - User includes
groupInstanceIdin checkout metadata - User processes payment (WALLET only)
- Payment completes successfully
- System adds user to existing group
- Group's
seatsOccupiedincreases - Group's
totalParticipantsincreases (if new member)
Note: User can join same group multiple times to buy more seats (Hybrid Approach)
3. Group Completion
When a group fills all seats:
Automatic Actions:
- Group status changes to COMPLETED
completedAttimestamp recorded- Orders created for all participants
- Inventory permanently deducted
- No new participants allowed
4. Group Expiration
When a group reaches expiration time without filling:
Automatic Actions:
- Group status changes to FAILED
- All participants refunded
- Inventory holds released
- Participant status changes to REFUNDED
5. Transferring Between Groups
Users can transfer seats between compatible groups:
Compatibility Requirements:
- Same product
- Same shop
- Same group price
- Target group must be OPEN
- Target group not expired
- Target group has available seats
Transfer Types:
Partial Transfer:
- Transfer some seats, keep some in source
- User remains ACTIVE in both groups
- Source group seats reduced
- Target group seats increased
Full Transfer:
- Transfer all seats from source
- Source participant status โ TRANSFERRED_OUT
- Source group participants count decreased
- If source group becomes empty โ soft deleted
Group Status Definitions
| Status | Description | Can Join? | Can Transfer From? | Can Transfer To? |
|---|---|---|---|---|
| OPEN | Active, accepting participants | Yes | Yes | Yes |
| COMPLETED | All seats filled, orders created | No | No | No |
| FAILED | Expired without filling, refunds issued | No | No | No |
| DELETED | Soft deleted (empty or admin action) | No | No | No |
Participant Status Definitions
| Status | Description | Still in Group? | Can Buy More? | Can Transfer? |
|---|---|---|---|---|
| ACTIVE | Currently participating in group | Yes | Yes | Yes |
| TRANSFERRED_OUT | Left this group via transfer | No | No | No |
| REFUNDED | Group failed, money refunded | No | No | No |
Purchase History Tracking
Each participant maintains detailed purchase history:
Tracked Information:
- Checkout session ID
- Quantity purchased
- Amount paid
- Purchase timestamp
- Transaction ID
Use Cases:
- User buys 2 seats initially
- User joins same group again, buys 3 more seats
- Purchase history shows 2 separate purchases
- Total quantity: 5 seats
- Total paid: sum of both purchases
Transfer History Tracking
Each transfer is recorded in participant history:
Tracked Information:
- Source group ID and code
- Target group ID and code
- Transfer timestamp
- Transfer reason/description
Transfer Scenarios:
Scenario 1: Partial Transfer
- User has 5 seats in Group A
- Transfers 2 seats to Group B
- Group A participant: 3 seats, ACTIVE status
- Group B participant: 2 seats, ACTIVE status, transfer history added
Scenario 2: Full Transfer
- User has 3 seats in Group A
- Transfers all 3 seats to Group B
- Group A participant: 0 seats, TRANSFERRED_OUT status
- Group B participant: 3 seats, ACTIVE status, transfer history added
Scenario 3: Multiple Transfers
- User transfers from Group A to Group B
- Later transfers from Group B to Group C
- Full transfer history maintained in each participation
Group Expiration and Cleanup
Automatic Expiration
Scheduled Job runs periodically to:
- Find groups with status=OPEN and expiresAt < now
- Change status to FAILED
- Initiate refunds for all participants
- Update participant status to REFUNDED
- Release inventory holds
Soft Deletion
Groups are soft-deleted when:
- All participants transfer out (empty group)
- Admin manually deletes group
Soft Delete Actions:
isDeletedset to truestatuschanged to DELETEDdeletedAttimestamp recordeddeletedByuser ID recordeddeleteReasonstored- Group still queryable but excluded from active lists
Business Rules
Maximum Seats Per Customer
If product has maxPerCustomer limit:
- Single purchase cannot exceed limit
- Multiple purchases in same group respect limit
- Transfer to group validates combined quantity
Example:
- Product has maxPerCustomer = 5
- User has 3 seats in Group A
- Tries to transfer to Group B where they have 3 seats
- Transfer rejected (3 + 3 = 6 > 5)
Group Price Lock
Group price is snapshot at creation:
- Price stored in group instance
- Transfers validate price match
- Product price changes don't affect existing groups
Inventory Management
During Group Lifecycle:
- Seats held in inventory when purchased
- Holds maintained until group completes or fails
- Completed: inventory permanently deducted
- Failed: inventory holds released
Transfer Impact:
- No inventory change during transfer
- Total inventory hold remains same
- Just moves between groups
Integration with Checkout Sessions
Creating New Group
Checkout Session Requirements:
sessionType: GROUP_PURCHASE- Exactly 1 item
- WALLET payment only
- No
groupInstanceIdin metadata - Status: PAYMENT_COMPLETED
After Payment Success:
GroupPurchaseInstanceEntity group = groupPurchaseService.createGroupInstance(checkoutSession);
Joining Existing Group
Checkout Session Requirements:
sessionType: GROUP_PURCHASE- Exactly 1 item
- WALLET payment only
groupInstanceIdin metadata- Status: PAYMENT_COMPLETED
After Payment Success:
UUID groupId = (UUID) checkoutSession.getMetadata().get("groupInstanceId");
GroupPurchaseInstanceEntity group = groupPurchaseService.joinGroup(groupId, checkoutSession);
Error Handling Best Practices
Common Error Scenarios
Product Not Available for Group Buying:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group buying is not enabled for this product"
}
Action: Check product has groupBuyingEnabled: true
Group Expired:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group has expired at: 2025-10-02T20:30:45"
}
Action: Find another available group or create new group
Group Full:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group is full. Seats occupied: 10/10"
}
Action: Find another available group or create new group
Quantity Exceeds Group Size:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Quantity (8) exceeds group max size (5)"
}
Action: Reduce quantity or create multiple purchases
Transfer Between Incompatible Groups:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot transfer between groups from different shops"
}
Action: Only transfer between compatible groups
Quick Reference Guide
Endpoint Summary
| Endpoint | Method | Purpose |
|---|---|---|
/group-purchases/product/{productId}/available |
GET | Get available groups for product |
/group-purchases/{groupId} |
GET | Get group details by ID |
/group-purchases/code/{groupCode} |
GET | Get group details by code |
/group-purchases/my-groups |
GET | Get user's groups |
/group-purchases/my-participations |
GET | Get user's participations |
/group-purchases/transfer |
POST | Transfer seats between groups |
Common HTTP Status Codes
200 OK: Successful operation400 Bad Request: Validation error or business rule violation401 Unauthorized: Authentication required404 Not Found: Group, product, or participation not found
Group Code Format
- Pattern:
GP-XXXXXX - Length: 9 characters (GP- + 6 alphanumeric)
- Example:
GP-A3X7K9,GP-B2Y8M5 - Auto-generated at group creation
Participant Contribution Calculation
contributionPercentage = (participantQuantity / totalSeatsOccupied) ร 100
Progress Calculation
progressPercentage = (seatsOccupied / totalSeats) ร 100
Savings Calculation
savingsAmount = regularPrice - groupPrice
savingsPercentage = (savingsAmount / regularPrice) ร 100
Testing
Test Scenarios
Scenario 1: Create and Complete Group
- User A creates group (2 seats)
- User B joins group (3 seats)
- User C joins group (5 seats)
- Group auto-completes
- Orders created for all participants
Scenario 2: Transfer Between Groups
- User A in Group 1 (3 seats)
- User A transfers 2 seats to Group 2
- User A remains in Group 1 (1 seat)
- User A now in Group 2 (2 seats)
Scenario 3: Group Expiration
- Create group with 1-minute expiration
- Wait for expiration
- Scheduled job processes
- Status โ FAILED
- Participants refunded
Scenario 4: Hybrid Approach - Multiple Purchases
- User A creates group (2 seats)
- User A joins same group again (3 seats)
- User A total: 5 seats
- Purchase history shows 2 records
ยฉ 2025 NexGate. All rights reserved.
Installment Purchase
Flexible payment plans with 7+ frequencies, custom intervals, and 2-4 plans per product. Features transparent amortization schedules, 75% early payoff interest discount, and two fulfillment options (immediate or after payment). Includes configurable 0-60 day grace periods, automated payment processing via JobRunr, up to 5 retry attempts for failed payments, real-time tracking of payment history and agreement status, and full admin control for plan management.
Installment Purchase - Customer Endpoints
Base URL: https://api.nextgate.com/api/v1/installments
Short Description: This API provides endpoints for customers to manage their installment purchase agreements. Customers can view their agreements, track payment schedules, make manual payments, retry failed payments, calculate and process early payoffs, and cancel agreements. All endpoints require authentication and operate on the principle that customers can only access their own agreement data.
Hints:
- All customer endpoints require authentication via Bearer token
- Customers can only access their own agreements (ownership validation applied)
- Payment processing uses wallet-based transactions
- Early payoff provides 75% discount on remaining interest
- Agreements can only be cancelled before the first payment is completed
- Failed payments can be retried up to 5 times
- Grace periods apply before first payment due date
- All amounts are in TZS (Tanzanian Shillings)
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-18T10:30:45",
"data": {
// Actual response data goes here
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-10-18T10: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
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)
- DELETE - DELETE - Red (Remove resources)
Endpoints
1. Get My Agreements
Purpose: Retrieve all installment agreements for the authenticated customer, optionally filtered by status
Endpoint: GET {base_url}/my-agreements
Access Level: ๐ Protected (Requires Authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Query Parameters:
| Parameter | Type | Required | Description | Validation | Default |
|---|---|---|---|---|---|
| status | string | No | Filter by agreement status | enum: PENDING_FIRST_PAYMENT, ACTIVE, COMPLETED, DEFAULTED, CANCELLED | null (all statuses) |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Agreements retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": [
{
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agreementNumber": "INST-2025-12345",
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"productImage": "https://cdn.example.com/products/samsung-s24.jpg",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"totalAmount": 2500000.00,
"amountPaid": 500000.00,
"amountRemaining": 2000000.00,
"currency": "TZS",
"paymentsCompleted": 1,
"paymentsRemaining": 11,
"totalPayments": 12,
"progressPercentage": 20.0,
"nextPaymentDate": "2025-11-18T00:00:00",
"nextPaymentAmount": 166666.67,
"agreementStatus": "ACTIVE",
"agreementStatusDisplay": "ACTIVE",
"createdAt": "2025-10-18T09:00:00",
"completedAt": null,
"canMakeEarlyPayment": true,
"canCancel": false
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| agreementId | Unique identifier for the agreement |
| agreementNumber | Human-readable agreement number (format: INST-YYYY-NNNNN) |
| productId | ID of the product being purchased |
| productName | Name of the product |
| productImage | URL to product image |
| shopId | ID of the shop selling the product |
| shopName | Name of the shop |
| totalAmount | Total amount to be paid including interest |
| amountPaid | Amount paid so far |
| amountRemaining | Amount still owed |
| currency | Currency code (TZS) |
| paymentsCompleted | Number of payments made |
| paymentsRemaining | Number of payments left |
| totalPayments | Total number of scheduled payments |
| progressPercentage | Percentage of payments completed |
| nextPaymentDate | Date when next payment is due |
| nextPaymentAmount | Amount of next payment |
| agreementStatus | Current status of the agreement |
| agreementStatusDisplay | Human-readable status |
| createdAt | When agreement was created |
| completedAt | When agreement was completed (null if not completed) |
| canMakeEarlyPayment | Whether early payoff is allowed |
| canCancel | Whether agreement can be cancelled |
Error Response JSON Sample:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token has expired",
"action_time": "2025-10-18T10:30:45",
"data": "Token has expired"
}
Standard Error Types:
Application-Level Exceptions (400-499)
Error Response Examples:
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Token has expired",
"action_time": "2025-10-18T10:30:45",
"data": "Token has expired"
}
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "User not found",
"action_time": "2025-10-18T10:30:45",
"data": "User not found"
}
2. Get My Active Agreements
Purpose: Retrieve only active installment agreements for the authenticated customer
Endpoint: GET {base_url}/my-agreements/active
Access Level: ๐ Protected (Requires Authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Active agreements retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": [
{
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agreementNumber": "INST-2025-12345",
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"productImage": "https://cdn.example.com/products/samsung-s24.jpg",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"totalAmount": 2500000.00,
"amountPaid": 500000.00,
"amountRemaining": 2000000.00,
"currency": "TZS",
"paymentsCompleted": 1,
"paymentsRemaining": 11,
"totalPayments": 12,
"progressPercentage": 20.0,
"nextPaymentDate": "2025-11-18T00:00:00",
"nextPaymentAmount": 166666.67,
"agreementStatus": "ACTIVE",
"agreementStatusDisplay": "ACTIVE",
"createdAt": "2025-10-18T09:00:00",
"completedAt": null,
"canMakeEarlyPayment": true,
"canCancel": false
}
]
}
Success Response Fields: Same as Get My Agreements endpoint
Error Response Examples: Same error types as Get My Agreements endpoint
3. Get Agreement By ID
Purpose: Retrieve detailed information about a specific installment agreement by its ID
Endpoint: GET {base_url}/agreements/{agreementId}
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | Unique identifier of the agreement | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Agreement details retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agreementNumber": "INST-2025-12345",
"customerId": "9b2e4d56-7c8a-4f9b-a3d1-5e6f7a8b9c0d",
"customerName": "John Doe",
"customerEmail": "john.doe@example.com",
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"productImage": "https://cdn.example.com/products/samsung-s24.jpg",
"productPrice": 2000000.00,
"quantity": 1,
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"selectedPlanId": "4b5c6d7e-8f9a-4b1c-9d2e-3f4a5b6c7d8e",
"planName": "12 Month Standard Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 12,
"duration": "12 months",
"apr": 15.00,
"gracePeriodDays": 30,
"downPaymentAmount": 400000.00,
"financedAmount": 1600000.00,
"monthlyPaymentAmount": 166666.67,
"totalInterestAmount": 400000.00,
"totalAmount": 2400000.00,
"currency": "TZS",
"paymentsCompleted": 1,
"paymentsRemaining": 11,
"amountPaid": 566666.67,
"amountRemaining": 1833333.33,
"progressPercentage": 8.33,
"nextPaymentDate": "2025-11-18T00:00:00",
"nextPaymentAmount": 166666.67,
"agreementStatus": "ACTIVE",
"defaultCount": 0,
"createdAt": "2025-10-18T09:00:00",
"firstPaymentDate": "2025-11-18T00:00:00",
"lastPaymentDate": "2026-10-18T00:00:00",
"completedAt": null,
"fulfillmentTiming": "IMMEDIATE",
"shippedAt": "2025-10-18T14:00:00",
"deliveredAt": null,
"orderId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"shippingAddress": {
"fullName": "John Doe",
"phoneNumber": "+255712345678",
"street": "123 Main Street",
"city": "Dar es Salaam",
"state": "Dar es Salaam",
"postalCode": "12345",
"country": "Tanzania"
},
"billingAddress": {
"fullName": "John Doe",
"phoneNumber": "+255712345678",
"street": "123 Main Street",
"city": "Dar es Salaam",
"state": "Dar es Salaam",
"postalCode": "12345",
"country": "Tanzania"
},
"payments": [
{
"paymentId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"paymentNumber": 1,
"scheduledAmount": 166666.67,
"paidAmount": 166666.67,
"principalPortion": 146666.67,
"interestPortion": 20000.00,
"remainingBalance": 1453333.33,
"lateFee": null,
"currency": "TZS",
"paymentStatus": "COMPLETED",
"paymentStatusDisplay": "COMPLETED",
"dueDate": "2025-11-18T00:00:00",
"paidAt": "2025-11-18T10:30:00",
"attemptedAt": "2025-11-18T10:29:45",
"paymentMethod": "WALLET",
"transactionId": "TXN-2025-67890",
"failureReason": null,
"retryCount": 0,
"daysUntilDue": null,
"daysOverdue": null,
"canPay": false,
"canRetry": false
}
],
"canMakeEarlyPayment": true,
"canCancel": false,
"canUpdatePaymentMethod": false
}
}
Success Response Fields:
| Field | Description |
|---|---|
| agreementId | Unique identifier for the agreement |
| agreementNumber | Human-readable agreement number |
| customerId | ID of the customer |
| customerName | Customer's full name |
| customerEmail | Customer's email address |
| productId | ID of the product |
| productName | Name of the product |
| productImage | URL to product image |
| productPrice | Original product price |
| quantity | Quantity purchased |
| shopId | ID of the shop |
| shopName | Name of the shop |
| selectedPlanId | ID of the selected installment plan |
| planName | Name of the installment plan |
| paymentFrequency | Payment frequency enum value |
| paymentFrequencyDisplay | Human-readable payment frequency |
| customFrequencyDays | Custom frequency in days (if applicable) |
| numberOfPayments | Total number of payments |
| duration | Human-readable duration |
| apr | Annual Percentage Rate |
| gracePeriodDays | Grace period before first payment |
| downPaymentAmount | Down payment amount |
| financedAmount | Amount being financed |
| monthlyPaymentAmount | Amount per payment |
| totalInterestAmount | Total interest to be paid |
| totalAmount | Grand total (product + interest) |
| currency | Currency code |
| paymentsCompleted | Number of completed payments |
| paymentsRemaining | Number of remaining payments |
| amountPaid | Total amount paid so far |
| amountRemaining | Total amount remaining |
| progressPercentage | Percentage of completion |
| nextPaymentDate | Next payment due date |
| nextPaymentAmount | Next payment amount |
| agreementStatus | Current agreement status |
| defaultCount | Number of missed payments |
| createdAt | Agreement creation timestamp |
| firstPaymentDate | First payment due date |
| lastPaymentDate | Last payment due date |
| completedAt | Completion timestamp (null if not completed) |
| fulfillmentTiming | When product is shipped (IMMEDIATE or AFTER_PAYMENT) |
| shippedAt | Shipment timestamp |
| deliveredAt | Delivery timestamp |
| orderId | Associated order ID |
| shippingAddress | Shipping address object |
| billingAddress | Billing address object |
| payments | Array of payment objects with full details |
| canMakeEarlyPayment | Whether early payoff is allowed |
| canCancel | Whether agreement can be cancelled |
| canUpdatePaymentMethod | Whether payment method can be updated |
Error Response Examples:
Bad Request (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "You do not have access to this agreement",
"action_time": "2025-10-18T10:30:45",
"data": "You do not have access to this agreement"
}
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Agreement not found with ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6",
"action_time": "2025-10-18T10:30:45",
"data": "Agreement not found with ID: 3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
4. Get Agreement By Number
Purpose: Retrieve detailed information about a specific installment agreement by its agreement number
Endpoint: GET {base_url}/agreements/number/{agreementNumber}
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementNumber | string | Yes | Agreement number (format: INST-YYYY-NNNNN) | Pattern: INST-\d{4}-\d{5} |
Success Response JSON Sample: Same as Get Agreement By ID endpoint
Success Response Fields: Same as Get Agreement By ID endpoint
Error Response Examples: Same as Get Agreement By ID endpoint, with agreement number in error messages instead of ID
5. Get Agreement Payments
Purpose: Retrieve all payment records for a specific installment agreement
Endpoint: GET {base_url}/agreements/{agreementId}/payments
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | Unique identifier of the agreement | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Payment history retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": [
{
"paymentId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"paymentNumber": 1,
"scheduledAmount": 166666.67,
"paidAmount": 166666.67,
"principalPortion": 146666.67,
"interestPortion": 20000.00,
"remainingBalance": 1453333.33,
"lateFee": null,
"currency": "TZS",
"paymentStatus": "COMPLETED",
"paymentStatusDisplay": "COMPLETED",
"dueDate": "2025-11-18T00:00:00",
"paidAt": "2025-11-18T10:30:00",
"attemptedAt": "2025-11-18T10:29:45",
"paymentMethod": "WALLET",
"transactionId": "TXN-2025-67890",
"failureReason": null,
"retryCount": 0,
"daysUntilDue": null,
"daysOverdue": null,
"canPay": false,
"canRetry": false
},
{
"paymentId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"paymentNumber": 2,
"scheduledAmount": 166666.67,
"paidAmount": null,
"principalPortion": 148533.33,
"interestPortion": 18133.34,
"remainingBalance": 1304800.00,
"lateFee": null,
"currency": "TZS",
"paymentStatus": "SCHEDULED",
"paymentStatusDisplay": "SCHEDULED",
"dueDate": "2025-12-18T00:00:00",
"paidAt": null,
"attemptedAt": null,
"paymentMethod": null,
"transactionId": null,
"failureReason": null,
"retryCount": 0,
"daysUntilDue": 61,
"daysOverdue": null,
"canPay": false,
"canRetry": false
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| paymentId | Unique identifier for the payment |
| paymentNumber | Sequential payment number (1, 2, 3...) |
| scheduledAmount | Amount scheduled to be paid |
| paidAmount | Amount actually paid (null if not paid) |
| principalPortion | Amount going toward principal |
| interestPortion | Amount going toward interest |
| remainingBalance | Balance after this payment |
| lateFee | Late fee if applicable |
| currency | Currency code |
| paymentStatus | Current payment status |
| paymentStatusDisplay | Human-readable status |
| dueDate | When payment is due |
| paidAt | When payment was made (null if not paid) |
| attemptedAt | Last payment attempt timestamp |
| paymentMethod | Payment method used |
| transactionId | Transaction reference ID |
| status | Payment status after processing |
| processedAt | When payment was processed |
| message | Success message |
| agreementUpdate | Updated agreement information |
| agreementUpdate.paymentsCompleted | Updated number of completed payments |
| agreementUpdate.paymentsRemaining | Updated number of remaining payments |
| agreementUpdate.amountPaid | Updated total amount paid |
| agreementUpdate.amountRemaining | Updated remaining amount |
| agreementUpdate.nextPaymentDate | Next payment due date |
| agreementUpdate.nextPaymentAmount | Next payment amount |
| agreementUpdate.agreementStatus | Updated agreement status |
| agreementUpdate.isCompleted | Whether agreement is now completed |
Error Response Examples:
Bad Request - Insufficient Balance (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Insufficient wallet balance. Required: 166666.67 TZS, Available: 50000.00 TZS. Please top up your wallet before the next payment attempt.",
"action_time": "2025-10-18T10:30:45",
"data": "Insufficient wallet balance. Required: 166666.67 TZS, Available: 50000.00 TZS. Please top up your wallet before the next payment attempt."
}
Bad Request - Payment Already Completed (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Payment is already completed",
"action_time": "2025-10-18T10:30:45",
"data": "Payment is already completed"
}
Bad Request - Payment Not Due (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Payment is not due yet. Due date: 2025-12-18T00:00:00",
"action_time": "2025-10-18T10:30:45",
"data": "Payment is not due yet. Due date: 2025-12-18T00:00:00"
}
6. Get Upcoming Payments
Purpose: Retrieve all upcoming payments across all active agreements for the authenticated customer
Endpoint: GET {base_url}/upcoming-payments
Access Level: ๐ Protected (Requires Authentication)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Upcoming payments retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": [
{
"paymentId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"paymentNumber": 2,
"scheduledAmount": 166666.67,
"paidAmount": null,
"principalPortion": 148533.33,
"interestPortion": 18133.34,
"remainingBalance": 1304800.00,
"lateFee": null,
"currency": "TZS",
"paymentStatus": "SCHEDULED",
"paymentStatusDisplay": "SCHEDULED",
"dueDate": "2025-12-18T00:00:00",
"paidAt": null,
"attemptedAt": null,
"paymentMethod": null,
"transactionId": null,
"failureReason": null,
"retryCount": 0,
"daysUntilDue": 61,
"daysOverdue": null,
"canPay": false,
"canRetry": false
}
]
}
Success Response Fields: Same as Get Agreement Payments endpoint
Error Response Examples: Standard authentication errors only
7. Make Manual Payment
Purpose: Process a manual payment for a specific scheduled installment
Endpoint: POST {base_url}/agreements/{agreementId}/payments/{paymentId}/pay
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | Unique identifier of the agreement | Valid UUID format |
| paymentId | UUID | Yes | Unique identifier of the payment | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Payment processed successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"paymentId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agreementNumber": "INST-2025-12345",
"amount": 166666.67,
"currency": "TZS",
"paymentMethod": "WALLET",
"transactionId": "TXN-2025-67891",
"status": "COMPLETED",
"processedAt": "2025-10-18T10:30:45",
"message": "Payment processed successfully",
"agreementUpdate": {
"paymentsCompleted": 2,
"paymentsRemaining": 10,
"amountPaid": 733333.34,
"amountRemaining": 1666666.66,
"nextPaymentDate": "2026-01-18T00:00:00",
"nextPaymentAmount": 166666.67,
"agreementStatus": "ACTIVE",
"isCompleted": false
}
}
}
Success Response Fields:
| Field | Description |
|---|---|
| paymentId | ID of the processed payment |
| agreementId | ID of the agreement |
| agreementNumber | Agreement number |
| amount | Amount paid |
| currency | Currency code |
| paymentMethod | Payment method used |
| transaction |
8. Retry Failed Payment
Purpose: Retry a failed payment that has not exceeded maximum retry attempts
Endpoint: POST {base_url}/payments/{paymentId}/retry
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| paymentId | UUID | Yes | Unique identifier of the payment to retry | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Payment retry processed successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"paymentId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agreementNumber": "INST-2025-12345",
"amount": 166666.67,
"currency": "TZS",
"paymentMethod": "WALLET",
"transactionId": "TXN-2025-67892",
"status": "COMPLETED",
"processedAt": "2025-10-18T10:30:45",
"message": "Payment retry successful",
"agreementUpdate": {
"paymentsCompleted": 2,
"paymentsRemaining": 10,
"amountPaid": 733333.34,
"amountRemaining": 1666666.66,
"nextPaymentDate": "2026-01-18T00:00:00",
"nextPaymentAmount": 166666.67,
"agreementStatus": "ACTIVE",
"isCompleted": false
}
}
}
Success Response Fields: Same as Make Manual Payment endpoint
Error Response Examples:
Bad Request - Cannot Retry (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Payment cannot be retried",
"action_time": "2025-10-18T10:30:45",
"data": "Payment cannot be retried"
}
Bad Request - Max Retries Exceeded (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Maximum retry attempts (5) exceeded",
"action_time": "2025-10-18T10:30:45",
"data": "Maximum retry attempts (5) exceeded"
}
9. Preview Flexible Payment
Purpose: Calculate and preview how a flexible payment amount will be distributed across installment payments before processing
Endpoint: POST {base_url}/installments/agreements/{agreementId}/early-flexible-payment/preview
Access Level: ๐ Protected (Requires Authentication)
Authentication: Bearer Token (JWT)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | Must be application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | The ID of the installment agreement | Must be a valid UUID, agreement must belong to authenticated user |
Request JSON Sample:
{
"amount": 500000.00
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| amount | decimal | Yes | Amount the customer wants to pay | Must be > 0, cannot exceed remaining balance |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Flexible payment preview calculated successfully",
"action_time": "2025-11-06T10:30:45",
"data": {
"requestedAmount": 500000.00,
"minimumRequired": 150000.00,
"maximumAllowed": 1200000.00,
"isValid": true,
"validationMessage": null,
"impactedPayments": [
{
"paymentNumber": 3,
"dueDate": "2025-12-01T00:00:00",
"scheduledAmount": 200000.00,
"currentPaid": 50000.00,
"willApply": 150000.00,
"willRemain": 0.00,
"resultStatus": "Will be COMPLETED"
},
{
"paymentNumber": 4,
"dueDate": "2026-01-01T00:00:00",
"scheduledAmount": 200000.00,
"currentPaid": 0.00,
"willApply": 200000.00,
"willRemain": 0.00,
"resultStatus": "Will be COMPLETED"
},
{
"paymentNumber": 5,
"dueDate": "2026-02-01T00:00:00",
"scheduledAmount": 200000.00,
"currentPaid": 0.00,
"willApply": 150000.00,
"willRemain": 50000.00,
"resultStatus": "Will be PARTIALLY_PAID"
}
],
"paymentsWillComplete": 2,
"paymentsWillBePartial": 1,
"remainingAfter": 700000.00
}
}
Success Response Fields:
| Field | Description |
|---|---|
| requestedAmount | The amount customer wants to pay |
| minimumRequired | Minimum payment required (next incomplete payment amount) |
| maximumAllowed | Maximum payment allowed (total remaining balance) |
| isValid | Whether the requested amount is valid |
| validationMessage | Error message if amount is invalid, null if valid |
| impactedPayments | Array of payments that will be affected by this payment |
| impactedPayments[].paymentNumber | Sequential payment number |
| impactedPayments[].dueDate | When this payment is due |
| impactedPayments[].scheduledAmount | Original scheduled amount for this payment |
| impactedPayments[].currentPaid | Amount already paid towards this payment |
| impactedPayments[].willApply | How much of the flexible payment will apply here |
| impactedPayments[].willRemain | How much will remain unpaid after applying |
| impactedPayments[].resultStatus | Final status: "Will be COMPLETED" or "Will be PARTIALLY_PAID" |
| paymentsWillComplete | Number of payments that will be fully completed |
| paymentsWillBePartial | Number of payments that will be partially paid |
| remainingAfter | Total remaining balance after this payment |
Error Response Examples:
Bad Request - Amount Too Low (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Minimum payment required: 150000.00 TZS",
"action_time": "2025-11-06T10:30:45",
"data": "Minimum payment required: 150000.00 TZS"
}
Bad Request - Amount Too High (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Payment amount exceeds remaining balance. Use early payoff endpoint if paying off completely.",
"action_time": "2025-11-06T10:30:45",
"data": "Payment amount exceeds remaining balance. Use early payoff endpoint if paying off completely."
}
Forbidden - Not Agreement Owner (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "You do not have access to this agreement",
"action_time": "2025-11-06T10:30:45",
"data": "You do not have access to this agreement"
}
Not Found - Agreement Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Agreement not found",
"action_time": "2025-11-06T10:30:45",
"data": "Agreement not found"
}
Validation Error - Invalid Amount (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-11-06T10:30:45",
"data": {
"amount": "must be greater than 0"
}
}
10. Process Flexible Payment
Purpose: Process a flexible payment that can pay multiple installments or partially pay upcoming installments
Endpoint: POST {base_url}/installments/agreements/{agreementId}/early-flexible-payment
Access Level: ๐ Protected (Requires Authentication, Sufficient Wallet Balance)
Authentication: Bearer Token (JWT)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | Must be application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | The ID of the installment agreement | Must be a valid UUID, agreement must belong to authenticated user |
Request JSON Sample:
{
"amount": 500000.00,
"note": "Paying 3 months ahead"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| amount | decimal | Yes | Amount to pay | Must be > 0, between minimumRequired and maximumAllowed |
| note | string | No | Optional note about the payment | Max 500 characters |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Successfully paid 2 installments and partially paid 1 more",
"action_time": "2025-11-06T10:30:45",
"data": {
"agreementId": "a8b3c4d5-e6f7-8901-2345-6789abcdef01",
"agreementNumber": "INST-2025-12345",
"totalAmountPaid": 500000.00,
"currency": "TZS",
"transactionId": "TXN-2025-67890",
"processedAt": "2025-11-06T10:30:45",
"paymentsAffected": [
{
"paymentId": "p1a2b3c4-d5e6-f789-0123-456789abcdef",
"paymentNumber": 3,
"dueDate": "2025-12-01T00:00:00",
"scheduledAmount": 200000.00,
"amountApplied": 150000.00,
"previouslyPaid": 50000.00,
"newPaidAmount": 200000.00,
"remaining": 0.00,
"status": "COMPLETED",
"wasCompleted": true
},
{
"paymentId": "p2a3b4c5-d6e7-f890-1234-56789abcdef0",
"paymentNumber": 4,
"dueDate": "2026-01-01T00:00:00",
"scheduledAmount": 200000.00,
"amountApplied": 200000.00,
"previouslyPaid": 0.00,
"newPaidAmount": 200000.00,
"remaining": 0.00,
"status": "COMPLETED",
"wasCompleted": true
},
{
"paymentId": "p3a4b5c6-d7e8-f901-2345-6789abcdef01",
"paymentNumber": 5,
"dueDate": "2026-02-01T00:00:00",
"scheduledAmount": 200000.00,
"amountApplied": 150000.00,
"previouslyPaid": 0.00,
"newPaidAmount": 150000.00,
"remaining": 50000.00,
"status": "PARTIALLY_PAID",
"wasCompleted": false
}
],
"agreementUpdate": {
"paymentsCompleted": 4,
"paymentsPartial": 1,
"paymentsRemaining": 8,
"amountPaid": 1300000.00,
"amountRemaining": 700000.00,
"nextPaymentDate": "2026-02-01T00:00:00",
"nextPaymentAmount": 50000.00,
"agreementStatus": "ACTIVE",
"isCompleted": false
},
"message": "Successfully paid 2 installments and partially paid 1 more"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| agreementId | ID of the installment agreement |
| agreementNumber | Human-readable agreement number |
| totalAmountPaid | Total amount paid in this transaction |
| currency | Currency code (TZS) |
| transactionId | Unique transaction identifier from ledger system |
| processedAt | Timestamp when payment was processed |
| paymentsAffected | Array of payments that were affected |
| paymentsAffected[].paymentId | ID of the affected payment |
| paymentsAffected[].paymentNumber | Sequential payment number |
| paymentsAffected[].dueDate | When this payment is due |
| paymentsAffected[].scheduledAmount | Original scheduled amount |
| paymentsAffected[].amountApplied | How much of flexible payment was applied here |
| paymentsAffected[].previouslyPaid | Amount that was previously paid |
| paymentsAffected[].newPaidAmount | Total amount now paid for this payment |
| paymentsAffected[].remaining | Amount still remaining for this payment |
| paymentsAffected[].status | New payment status (COMPLETED or PARTIALLY_PAID) |
| paymentsAffected[].wasCompleted | Whether this payment became fully completed |
| agreementUpdate | Updated agreement summary |
| agreementUpdate.paymentsCompleted | Total number of completed payments |
| agreementUpdate.paymentsPartial | Number of partially paid payments |
| agreementUpdate.paymentsRemaining | Number of payments still remaining |
| agreementUpdate.amountPaid | Total amount paid on agreement |
| agreementUpdate.amountRemaining | Total amount still remaining |
| agreementUpdate.nextPaymentDate | When next payment is due |
| agreementUpdate.nextPaymentAmount | Amount of next payment (remaining amount) |
| agreementUpdate.agreementStatus | Current agreement status |
| agreementUpdate.isCompleted | Whether agreement is fully completed |
| message | Human-readable success message |
Error Response Examples:
Bad Request - Insufficient Balance (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Insufficient wallet balance. Required: 500000.00 TZS, Available: 300000.00 TZS",
"action_time": "2025-11-06T10:30:45",
"data": "Insufficient wallet balance. Required: 500000.00 TZS, Available: 300000.00 TZS"
}
Bad Request - Agreement Not Active (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot make payment on inactive agreement. Status: COMPLETED",
"action_time": "2025-11-06T10:30:45",
"data": "Cannot make payment on inactive agreement. Status: COMPLETED"
}
Bad Request - Wallet Not Active (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Wallet is not active",
"action_time": "2025-11-06T10:30:45",
"data": "Wallet is not active"
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-11-06T10:30:45",
"data": {
"amount": "must be greater than 0",
"note": "size must be between 0 and 500"
}
}
11. Calculate Early Payoff
Purpose: Calculate the amount required to pay off the entire agreement early with interest discount
Endpoint: GET {base_url}/agreements/{agreementId}/early-payoff
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | Unique identifier of the agreement | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Early payoff calculation completed",
"action_time": "2025-10-18T10:30:45",
"data": {
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentsCompleted": 2,
"paymentsRemaining": 10,
"amountPaid": 733333.34,
"remainingPrincipal": 1320000.00,
"unaccruedInterest": 346666.66,
"interestRebate": 260000.00,
"payoffWithRebate": 1406666.66,
"payoffWithoutRebate": 1666666.66,
"savingsVsScheduled": 260000.00,
"rebatePolicy": "75% discount on remaining interest for early payoff",
"calculatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| agreementId | ID of the agreement |
| paymentsCompleted | Number of payments already made |
| paymentsRemaining | Number of payments left |
| amountPaid | Total amount paid so far |
| remainingPrincipal | Principal amount remaining |
| unaccruedInterest | Interest not yet charged |
| interestRebate | Interest discount amount (75% of remaining interest) |
| payoffWithRebate | Early payoff amount with discount applied |
| payoffWithoutRebate | Full remaining amount if paid on schedule |
| savingsVsScheduled | Amount saved by paying off early |
| rebatePolicy | Description of rebate policy |
| calculatedAt | When calculation was performed |
Error Response Examples:
Bad Request - Not Eligible (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Early payoff not available for this agreement",
"action_time": "2025-10-18T10:30:45",
"data": "Early payoff not available for this agreement"
}
Bad Request - Agreement Not Active (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Agreement is not active. Status: COMPLETED",
"action_time": "2025-10-18T10:30:45",
"data": "Agreement is not active. Status: COMPLETED"
}
12. Process Early Payoff
Purpose: Process full early payment of the agreement with interest discount applied
Endpoint: POST {base_url}/agreements/{agreementId}/early-payoff
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | Unique identifier of the agreement | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Early payoff processed successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"paymentId": null,
"agreementId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agreementNumber": "INST-2025-12345",
"amount": 1406666.66,
"currency": "TZS",
"paymentMethod": "WALLET",
"transactionId": null,
"status": "COMPLETED",
"processedAt": "2025-10-18T10:30:45",
"message": "Early payoff processed successfully",
"agreementUpdate": {
"paymentsCompleted": 12,
"paymentsRemaining": 0,
"amountPaid": 2400000.00,
"amountRemaining": 0.00,
"nextPaymentDate": null,
"nextPaymentAmount": null,
"agreementStatus": "COMPLETED",
"isCompleted": true
}
}
}
Success Response Fields:
| Field | Description |
|---|---|
| paymentId | Payment ID (null for early payoff) |
| agreementId | ID of the agreement |
| agreementNumber | Agreement number |
| amount | Total early payoff amount paid |
| currency | Currency code |
| paymentMethod | Payment method used |
| transactionId | Transaction reference (null for early payoff) |
| status | Payment status (COMPLETED) |
| processedAt | When payoff was processed |
| message | Success message |
| agreementUpdate | Updated agreement information |
| agreementUpdate.paymentsCompleted | Updated to total number of payments |
| agreementUpdate.paymentsRemaining | Set to 0 |
| agreementUpdate.amountPaid | Updated to total amount |
| agreementUpdate.amountRemaining | Set to 0 |
| agreementUpdate.nextPaymentDate | Set to null |
| agreementUpdate.nextPaymentAmount | Set to null |
| agreementUpdate.agreementStatus | Set to COMPLETED |
| agreementUpdate.isCompleted | Set to true |
Error Response Examples:
Bad Request - Insufficient Balance (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Insufficient wallet balance for early payoff. Required: 1406666.66 TZS, Available: 500000.00 TZS",
"action_time": "2025-10-18T10:30:45",
"data": "Insufficient wallet balance for early payoff. Required: 1406666.66 TZS, Available: 500000.00 TZS"
}
Bad Request - Not Eligible (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Agreement is not active. Status: DEFAULTED",
"action_time": "2025-10-18T10:30:45",
"data": "Agreement is not active. Status: DEFAULTED"
}
13. Cancel Agreement
Purpose: Cancel an installment agreement before any payments have been completed
Endpoint: POST {base_url}/agreements/{agreementId}/cancel
Access Level: ๐ Protected (Requires Authentication, Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| agreementId | UUID | Yes | Unique identifier of the agreement | Valid UUID format |
Request JSON Sample:
{
"reason": "Changed my mind about the purchase. Found a better deal elsewhere."
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| reason | string | Yes | Reason for cancellation | Min: 1, Max: 500 characters |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Agreement cancelled successfully",
"action_time": "2025-10-18T10:30:45",
"data": null
}
Success Response Fields: No data returned on successful cancellation
Error Response Examples:
Bad Request - Cannot Cancel (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Agreement cannot be cancelled. Only agreements with no completed payments can be cancelled.",
"action_time": "2025-10-18T10:30:45",
"data": "Agreement cannot be cancelled. Only agreements with no completed payments can be cancelled."
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-10-18T10:30:45",
"data": {
"reason": "Cancellation reason is required"
}
}
Quick Reference Guide
HTTP Method Badge Code Templates
GET Badge:
<span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span>
POST Badge:
<span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span>
Common HTTP Status Codes
200 OK: Successful GET/POST request400 Bad Request: Invalid request data or business logic violation401 Unauthorized: Authentication required/failed403 Forbidden: Insufficient permissions404 Not Found: Resource not found422 Unprocessable Entity: Validation errors500 Internal Server Error: Server error
Authentication
- Bearer Token: Include
Authorization: Bearer your_tokenin headers - All customer endpoints require valid authentication
- Token must belong to the customer who owns the agreement
Agreement Status Flow
- PENDING_FIRST_PAYMENT: Down payment made, waiting for grace period
- ACTIVE: Currently paying installments
- COMPLETED: Fully paid off
- DEFAULTED: Missed 2+ payments, in collections
- CANCELLED: User cancelled before first payment
Payment Status Types
- SCHEDULED: Not due yet
- PENDING: Due today, awaiting payment
- PROCESSING: Payment in progress
- COMPLETED: Successfully paid
- FAILED: Payment attempt failed
- LATE: Past due date
- SKIPPED: Missed completely
- WAIVED: Forgiven (special cases)
Business Rules
- Early Payoff Discount: 75% off remaining interest
- Maximum Retries: 5 attempts per payment
- Default Threshold: 2 missed payments triggers DEFAULTED status
- Cancellation Window: Only before first payment completed
- Payment Method: Wallet payments only
- Currency: All amounts in TZS (Tanzanian Shillings)
Common Error Scenarios
- Insufficient Wallet Balance: Top up wallet before payment
- Payment Not Due: Wait until due date or make early payoff
- Agreement Defaulted: Contact support for resolution
- Max Retries Exceeded: Contact support
- Ownership Validation Failed: Can only access own agreements
Notes for Developers
Idempotency
- Payment processing endpoints are idempotent
- Duplicate payment requests will return existing payment status
- Use transaction IDs to track payment processing
Rate Limiting
- Standard rate limits apply: 100 requests per hour per user
- Payment processing has additional throttling for security
Webhooks
- Payment success/failure events trigger notifications
- Agreement completion triggers order creation (for AFTER_PAYMENT fulfillment)
- Integration with notification service for email/SMS alerts
Testing
- Use sandbox environment for testing:
https://sandbox-api.nextgate.com - Test user accounts available with pre-loaded wallets
- Mock payment processing for integration testing
Support
- For API issues: api-support@nextgate.com
- For business logic questions: product@nextgate.com
- Emergency contact: +255-XXX-XXX-XXXId | Transaction reference ID | | failureReason | Reason if payment failed | | retryCount | Number of retry attempts | | daysUntilDue | Days until payment is due (negative if overdue) | | daysOverdue | Days payment is overdue (null if not overdue) | | canPay | Whether payment can be made now | | canRetry | Whether payment can be retried |
Installment Purchase - Public & Plan Endpoints
Base URL: https://api.nextgate.com/api/v1/installments
Short Description: This API provides public endpoints for browsing installment plans and calculating installment previews before checkout. These endpoints do not require authentication and are designed to help customers explore installment options, understand payment breakdowns, and make informed purchase decisions. The endpoints return detailed financial calculations including amortization schedules, interest breakdowns, and payment timelines.
Hints:
- Public endpoints do not require authentication
- Plans are filtered to show only active plans to customers
- Preview calculations use amortization formula for accurate payment schedules
- Early payoff discount (75% off remaining interest) is standard across all plans
- Down payment range: minimum set by plan (typically 10-20%), maximum 50% (platform limit)
- All financial calculations rounded to 2 decimal places
- Grace periods determine when first payment is due
- Fulfillment timing (IMMEDIATE vs AFTER_PAYMENT) affects when product ships
- APR (Annual Percentage Rate) converted to period rate based on payment frequency
- All amounts are in TZS (Tanzanian Shillings)
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-18T10:30:45",
"data": {
// Actual response data goes here
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-10-18T10: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
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)
Endpoints
1. Get Available Plans for Product
Purpose: Retrieve all active installment plans available for a specific product
Endpoint: GET {base_url}/products/{productId}/plans
Access Level: ๐ Public (No Authentication Required)
Authentication: None
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Available installment plans retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": [
{
"planId": "4b5c6d7e-8f9a-4b1c-9d2e-3f4a5b6c7d8e",
"planName": "Quick Payment Plan",
"paymentFrequency": "WEEKLY",
"paymentFrequencyDisplay": "Weekly",
"customFrequencyDays": null,
"numberOfPayments": 8,
"duration": "8 weeks",
"apr": 10.00,
"minDownPaymentPercent": 20,
"gracePeriodDays": 7,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": false,
"displayOrder": 1,
"preview": {
"productPrice": 2000000.00,
"minDownPaymentAmount": 400000.00,
"maxDownPaymentAmount": 1000000.00,
"financedAmountExample": 1600000.00,
"paymentAmountExample": 210000.00,
"totalInterestExample": 80000.00,
"totalCostExample": 2080000.00,
"firstPaymentDateExample": "2025-10-25T00:00:00",
"lastPaymentDateExample": "2025-12-13T00:00:00"
}
},
{
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "Standard Monthly Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 12,
"duration": "12 months",
"apr": 15.00,
"minDownPaymentPercent": 15,
"gracePeriodDays": 30,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": true,
"displayOrder": 2,
"preview": {
"productPrice": 2000000.00,
"minDownPaymentAmount": 300000.00,
"maxDownPaymentAmount": 1000000.00,
"financedAmountExample": 1700000.00,
"paymentAmountExample": 156250.00,
"totalInterestExample": 175000.00,
"totalCostExample": 2175000.00,
"firstPaymentDateExample": "2025-11-17T00:00:00",
"lastPaymentDateExample": "2026-10-17T00:00:00"
}
},
{
"planId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"planName": "Budget Friendly Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 24,
"duration": "24 months",
"apr": 18.00,
"minDownPaymentPercent": 10,
"gracePeriodDays": 30,
"fulfillmentTiming": "AFTER_PAYMENT",
"isActive": true,
"isFeatured": false,
"displayOrder": 3,
"preview": {
"productPrice": 2000000.00,
"minDownPaymentAmount": 200000.00,
"maxDownPaymentAmount": 1000000.00,
"financedAmountExample": 1800000.00,
"paymentAmountExample": 87500.00,
"totalInterestExample": 300000.00,
"totalCostExample": 2300000.00,
"firstPaymentDateExample": "2025-11-17T00:00:00",
"lastPaymentDateExample": "2027-10-17T00:00:00"
}
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| planId | Unique identifier for the plan |
| planName | Display name of the plan |
| paymentFrequency | Payment frequency enum (DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS) |
| paymentFrequencyDisplay | Human-readable payment frequency |
| customFrequencyDays | Custom frequency in days (only for CUSTOM_DAYS type) |
| numberOfPayments | Total number of payments in the plan |
| duration | Human-readable duration (e.g., "12 months", "8 weeks") |
| apr | Annual Percentage Rate |
| minDownPaymentPercent | Minimum down payment percentage required by this plan |
| gracePeriodDays | Days before first payment is due |
| fulfillmentTiming | When product ships (IMMEDIATE = after down payment, AFTER_PAYMENT = after final payment) |
| isActive | Whether plan is currently active |
| isFeatured | Whether plan is featured/recommended (shows "Most Popular" badge) |
| displayOrder | Order for display on product page |
| preview | Preview calculations object |
| preview.productPrice | Product price used for calculations |
| preview.minDownPaymentAmount | Minimum down payment amount |
| preview.maxDownPaymentAmount | Maximum down payment amount (platform limit: 50%) |
| preview.financedAmountExample | Amount being financed (at minimum down payment) |
| preview.paymentAmountExample | Each installment amount (at minimum down payment) |
| preview.totalInterestExample | Total interest to be paid (at minimum down payment) |
| preview.totalCostExample | Grand total cost including interest (at minimum down payment) |
| preview.firstPaymentDateExample | When first payment would be due |
| preview.lastPaymentDateExample | When last payment would be due |
Error Response JSON Sample:
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found with ID: 7c9e6679-7425-40de-944b-e07fc1f90ae7",
"action_time": "2025-10-18T10:30:45",
"data": "Product not found with ID: 7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
Standard Error Types:
Application-Level Exceptions (400-499)
404 NOT_FOUND: Product not found or product does not have installment enabled
Error Response Examples:
Not Found - Product (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found with ID: 7c9e6679-7425-40de-944b-e07fc1f90ae7",
"action_time": "2025-10-18T10:30:45",
"data": "Product not found with ID: 7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
Special Cases:
- If product has installment disabled, returns empty array
- If product has no active plans, returns empty array
- Plans are automatically sorted by displayOrder
2. Calculate Installment Preview
Purpose: Calculate detailed payment breakdown and schedule for a specific plan with customer's chosen down payment percentage
Endpoint: POST {base_url}/calculate-preview
Access Level: ๐ Public (No Authentication Required)
Authentication: None
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Content-Type | string | Yes | application/json |
Request JSON Sample:
{
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"productPrice": 2000000.00,
"quantity": 1,
"downPaymentPercent": 20
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| planId | UUID | Yes | ID of the installment plan to calculate | Valid UUID format, plan must exist and be active |
| productPrice | decimal | Yes | Price of the product | Min: 0.01, Max: 999999999.99 |
| quantity | integer | Yes | Quantity of product | Min: 1, Max: 1 (installment limited to 1 item) |
| downPaymentPercent | integer | Yes | Down payment percentage customer chooses | Min: 10, Max: 50, must be >= plan's minDownPaymentPercent |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment preview calculated successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "Standard Monthly Plan",
"planDescription": "Pay in 12 monthly installments at 15.0% APR",
"paymentFrequency": "Monthly",
"numberOfPayments": 12,
"durationDisplay": "12 months",
"apr": 15.00,
"gracePeriodDays": 30,
"productPrice": 2000000.00,
"quantity": 1,
"totalProductCost": 2000000.00,
"downPaymentPercent": 20,
"downPaymentAmount": 400000.00,
"minDownPaymentPercent": 15,
"maxDownPaymentPercent": 50,
"minDownPaymentAmount": 300000.00,
"maxDownPaymentAmount": 1000000.00,
"financedAmount": 1600000.00,
"monthlyPaymentAmount": 146666.67,
"totalInterestAmount": 160000.00,
"totalAmount": 2160000.00,
"currency": "TZS",
"firstPaymentDate": "2025-11-17T00:00:00",
"lastPaymentDate": "2026-10-17T00:00:00",
"schedule": [
{
"paymentNumber": 1,
"dueDate": "2025-11-17T00:00:00",
"amount": 146666.67,
"principalPortion": 126666.67,
"interestPortion": 20000.00,
"remainingBalance": 1473333.33,
"description": "Month 1 payment"
},
{
"paymentNumber": 2,
"dueDate": "2025-12-17T00:00:00",
"amount": 146666.67,
"principalPortion": 128250.00,
"interestPortion": 18416.67,
"remainingBalance": 1345083.33,
"description": "Month 2 payment"
},
{
"paymentNumber": 3,
"dueDate": "2026-01-17T00:00:00",
"amount": 146666.67,
"principalPortion": 129853.13,
"interestPortion": 16813.54,
"remainingBalance": 1215230.20,
"description": "Month 3 payment"
},
{
"paymentNumber": 12,
"dueDate": "2026-10-17T00:00:00",
"amount": 146666.67,
"principalPortion": 144833.33,
"interestPortion": 1833.34,
"remainingBalance": 0.00,
"description": "Final payment"
}
],
"comparison": {
"payingUpfront": 2000000.00,
"payingWithInstallment": 2160000.00,
"additionalCost": 160000.00,
"additionalCostPercent": 8.00
},
"fulfillmentTiming": "IMMEDIATE",
"fulfillmentDescription": "Product ships immediately after down payment"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| planId | ID of the plan used |
| planName | Name of the plan |
| planDescription | Description of the plan |
| paymentFrequency | Human-readable payment frequency |
| numberOfPayments | Total number of payments |
| durationDisplay | Human-readable duration |
| apr | Annual Percentage Rate |
| gracePeriodDays | Grace period before first payment |
| productPrice | Per-unit product price |
| quantity | Quantity purchased |
| totalProductCost | Total product cost (price ร quantity) |
| downPaymentPercent | Chosen down payment percentage |
| downPaymentAmount | Calculated down payment amount |
| minDownPaymentPercent | Minimum allowed down payment % |
| maxDownPaymentPercent | Maximum allowed down payment % (50%) |
| minDownPaymentAmount | Minimum down payment amount |
| maxDownPaymentAmount | Maximum down payment amount |
| financedAmount | Amount being financed (after down payment) |
| monthlyPaymentAmount | Each installment payment amount |
| totalInterestAmount | Total interest to be paid |
| totalAmount | Grand total (product + interest) |
| currency | Currency code (TZS) |
| firstPaymentDate | When first payment is due |
| lastPaymentDate | When last payment is due |
| schedule | Array of payment schedule items |
| schedule[].paymentNumber | Payment number (1, 2, 3...) |
| schedule[].dueDate | When payment is due |
| schedule[].amount | Payment amount |
| schedule[].principalPortion | Amount going to principal |
| schedule[].interestPortion | Amount going to interest |
| schedule[].remainingBalance | Balance after this payment |
| schedule[].description | Payment description |
| comparison | Comparison information object |
| comparison.payingUpfront | Cost if paying full price now |
| comparison.payingWithInstallment | Total cost with installment |
| comparison.additionalCost | Extra cost (interest) |
| comparison.additionalCostPercent | Interest as % of product price |
| fulfillmentTiming | When product ships |
| fulfillmentDescription | Description of fulfillment timing |
Error Response JSON Sample:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Down payment must be at least 15% for this plan",
"action_time": "2025-10-18T10:30:45",
"data": "Down payment must be at least 15% for this plan"
}
Standard Error Types:
Application-Level Exceptions (400-499)
400 BAD_REQUEST: Invalid down payment percentage, inactive plan, or business rule violation404 NOT_FOUND: Plan not found422 UNPROCESSABLE_ENTITY: Validation errors on request parameters
Error Response Examples:
Bad Request - Down Payment Too Low (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Down payment must be at least 15% for this plan",
"action_time": "2025-10-18T10:30:45",
"data": "Down payment must be at least 15% for this plan"
}
Bad Request - Down Payment Too High (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Down payment cannot exceed 50%",
"action_time": "2025-10-18T10:30:45",
"data": "Down payment cannot exceed 50%"
}
Bad Request - Plan Not Active (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "This installment plan is not currently available",
"action_time": "2025-10-18T10:30:45",
"data": "This installment plan is not currently available"
}
Not Found - Plan (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Installment plan not found with ID: 5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"action_time": "2025-10-18T10:30:45",
"data": "Installment plan not found with ID: 5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f"
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "Plan ID is required",
"productPrice": "Product price must be greater than 0",
"downPaymentPercent": "must be between 10 and 50"
}
}
Quick Reference Guide
HTTP Method Badge Code Templates
GET Badge:
<span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span>
POST Badge:
<span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span>
Common HTTP Status Codes
200 OK: Successful GET/POST request400 Bad Request: Invalid request data or business logic violation404 Not Found: Resource not found422 Unprocessable Entity: Validation errors500 Internal Server Error: Server error
Payment Frequency Types
- DAILY: Payment every day
- WEEKLY: Payment every week (7 days)
- BI_WEEKLY: Payment every 2 weeks (14 days)
- SEMI_MONTHLY: Payment twice per month (1st and 15th)
- MONTHLY: Payment every month (30 days)
- QUARTERLY: Payment every 3 months (90 days)
- CUSTOM_DAYS: Payment at custom interval (specified in customFrequencyDays)
Fulfillment Timing Options
- IMMEDIATE: Product ships immediately after down payment is made. Customer gets product while paying installments.
- AFTER_PAYMENT: Product ships only after final payment is completed (layaway model). Inventory is held during payment period.
Down Payment Rules
- Minimum: Set by individual plan (typically 10-20%)
- Maximum: 50% (platform-wide limit)
- Validation: Customer's choice must be within min-max range
- Purpose: Reduces financed amount and total interest paid
APR (Annual Percentage Rate)
- Expressed as percentage (e.g., 15.00 = 15%)
- Range: 0% to 36% (platform limits)
- Converted to period rate based on payment frequency
- Used in amortization calculation for each payment
Financial Calculations
Amortization Formula
Monthly Payment = P ร [r(1+r)^n] / [(1+r)^n - 1]
Where:
- P = Principal (financed amount)
- r = Period rate (APR / periods per year)
- n = Number of payments
Period Rate Calculation
- Daily: APR / 365
- Weekly: APR / 52
- Bi-weekly: APR / 26
- Semi-monthly: APR / 24
- Monthly: APR / 12
- Quarterly: APR / 4
- Custom: APR / (365 / customDays)
Payment Breakdown
Each payment consists of:
- Interest Portion: Remaining balance ร period rate
- Principal Portion: Payment amount - interest portion
- Remaining Balance: Previous balance - principal portion
Schedule Generation
- First payment due after grace period
- Subsequent payments calculated based on frequency
- Last payment adjusted for rounding differences
- All dates in ISO 8601 format
Comparison Information
Shows cost difference between:
- Paying upfront: Original product price
- Paying with installment: Product price + total interest
- Additional cost: Total interest amount
- Additional cost %: Interest as percentage of product price
Common Use Cases
1. Display Plans on Product Page
GET /api/v1/installments/products/{productId}/plans
- Shows all active plans for product
- Displays preview calculations at minimum down payment
- Highlights featured plans
- Sorted by displayOrder
2. Calculate Custom Preview
POST /api/v1/installments/calculate-preview
{
"planId": "...",
"productPrice": 2000000.00,
"quantity": 1,
"downPaymentPercent": 25
}
- Customer adjusts down payment slider
- Real-time calculation of payments
- Shows full payment schedule
- Displays savings comparison
3. Pre-Checkout Validation
POST /api/v1/installments/calculate-preview
- Validates customer's choices before checkout
- Ensures plan is still active
- Confirms down payment within range
- Generates schedule for agreement creation
Integration Examples
Frontend Flow
- Product page loads โ Call GET /products/{id}/plans
- Display plan options with previews
- User selects plan and adjusts down payment
- Call POST /calculate-preview on down payment change
- Show detailed breakdown and schedule
- User proceeds to checkout with selected configuration
Mobile App Flow
- Fetch plans when user taps "Installment Options"
- Show plan cards with key metrics
- Tapped plan shows full preview
- Slider for down payment percentage
- Real-time preview updates
- "Continue to Checkout" with configuration
Error Handling Best Practices
Client-Side Validation
- Validate down payment range before API call
- Check quantity = 1 for installment
- Ensure product price > 0
- Validate UUID formats
Server-Side Errors
- 400: Show user-friendly message, allow retry
- 404: Product/plan not found, redirect or show alternatives
- 422: Display field-specific validation errors
- 500: Generic error message, log for investigation
Performance Considerations
- Cache plan data (TTL: 1 hour)
- Debounce preview calculations (300ms)
- Lazy load full schedules
- Optimize for mobile networks
Testing Scenarios
Happy Path
- Get plans for valid product with installment enabled
- Calculate preview with valid parameters
- Verify calculations match expected values
Edge Cases
- Product with no active plans โ empty array
- Product with installment disabled โ empty array
- Down payment at minimum boundary
- Down payment at maximum boundary (50%)
- Inactive plan in preview request โ error
Error Cases
- Invalid product ID โ 404
- Invalid plan ID โ 404
- Down payment too low โ 400
- Down payment too high โ 400
- Missing required fields โ 422
- Invalid data types โ 422
Data Format Standards
- Dates: ISO 8601 format (2025-10-18T14:30:00Z)
- Currency: TZS (Tanzanian Shillings), no currency symbol in API
- Decimals: 2 decimal places for all monetary values
- Percentages: Whole numbers (15 = 15%, not 0.15)
- UUIDs: Standard UUID v4 format with hyphens
Business Rules Summary
- One Item Per Agreement: Installment limited to single product
- Active Plans Only: Only active plans returned in public endpoints
- Down Payment Range: 10-50% (plan minimum to platform maximum)
- APR Limits: 0-36% (platform enforced)
- Grace Period: 0-60 days (plan-specific)
- Payment Frequency: All standard frequencies supported
- Fulfillment Options: IMMEDIATE or AFTER_PAYMENT
- Currency: TZS only (Tanzania market)
- Rounding: All calculations rounded to 2 decimal places
- Early Payoff: 75% discount on remaining interest (not shown in preview)
Notes for Developers
Calculation Accuracy
- Use decimal/numeric types for currency calculations
- Avoid floating-point arithmetic
- Round to 2 decimals at final step only
- Last payment absorbs rounding differences
Caching Strategy
- Cache plan data per product (1 hour TTL)
- Invalidate cache when plans updated
- Preview calculations should not be cached
- Consider CDN for plan endpoints
Mobile Optimization
- Minimize payload size (exclude schedule if not needed)
- Compress responses (gzip/brotli)
- Use pagination for long schedules
- Implement request debouncing
Security Considerations
- No authentication required for public endpoints
- Rate limiting: 1000 requests/hour per IP
- Input validation on all parameters
- SQL injection prevention (parameterized queries)
- XSS prevention (sanitize all inputs)
Support & Resources
- API Documentation: https://docs.nextgate.com/api/installments
- Developer Portal: https://developers.nextgate.com
- Support Email: api-support@nextgate.com
- Status Page: https://status.nextgate.com
- Changelog: https://docs.nextgate.com/changelog
Documentation Checklist
Before using this API, ensure you understand:
- Public Access: No authentication required
- Plan Filtering: Only active plans returned
- Down Payment Rules: Min (plan-specific) to Max (50%)
- APR Calculation: Converted to period rate automatically
- Amortization: Principal + interest breakdown per payment
- Schedule Generation: All payment dates calculated
- Fulfillment Options: IMMEDIATE vs AFTER_PAYMENT
- Currency: TZS only
- Rounding: 2 decimal places
- Error Handling: Proper validation and error messages
- Rate Limiting: 1000 requests/hour per IP
- Caching: Recommended for plan data
- Testing: Sandbox environment available
Installment Plan Management - Admin/Shop Endpoints
Base URL: https://api.nextgate.com/api/v1/products/{shopId}/{productId}/installment-plans
Short Description: This API provides comprehensive endpoints for shop owners and administrators to create, manage, and configure installment plans for their products. Shop owners can define flexible payment terms including payment frequencies, interest rates, down payment requirements, grace periods, and fulfillment options. The API supports full CRUD operations, plan activation/deactivation, featured plan selection, and product-level installment enablement. All endpoints require authentication and validate shop ownership.
Hints:
- All endpoints require authentication and shop ownership validation
- Shop owners can only manage plans for their own products
- Multiple plans can be created per product for different customer segments
- Only one plan per product can be featured at a time
- Plans can be deactivated without deletion to preserve historical data
- Active plans are automatically available to customers on product pages
- Inactive plans are hidden from customers but preserved for existing agreements
- Payment frequencies support daily, weekly, bi-weekly, semi-monthly, monthly, quarterly, and custom intervals
- APR (Annual Percentage Rate) must be between 0% and 36%
- Down payment requirements: 10-50% range
- Grace periods: 0-60 days before first payment
- Fulfillment options: IMMEDIATE (ship after down payment) or AFTER_PAYMENT (ship after completion)
- Display order determines plan sorting on product pages
- Featured plans get "Most Popular" or "Recommended" badge
- Enabling/disabling installments at product level affects all plans
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-18T10:30:45",
"data": {
// Actual response data goes here
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-10-18T10: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
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)
- PUT - PUT - Yellow (Update/replace entire resource)
- PATCH - PATCH - Orange (Partial updates)
- DELETE - DELETE - Red (Remove resources)
Endpoints
1. Create Installment Plan
Purpose: Create a new installment plan for a specific product
Endpoint: POST {base_url}
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
| planId | UUID | Yes | Unique identifier of the plan to deactivate | Valid UUID format, must belong to specified product |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plan deactivated successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"planName": "Budget Friendly Plan",
"isActive": false,
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| planId | ID of the deactivated plan |
| planName | Name of the plan |
| isActive | Active status (false) |
| updatedAt | Update timestamp |
Error Response Examples: Same as Activate Installment Plan endpoint
Important Notes:
- Deactivated plans are hidden from customers on product pages
- Existing agreements continue to use deactivated plans
- Plan can be reactivated at any time
- Deactivation is preferred over deletion for plans with existing agreements
2. Get Product Installment Plans
Purpose: Retrieve all installment plans for a specific product (including inactive plans for admin view)
Endpoint: GET {base_url}
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plans retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": [
{
"planId": "4b5c6d7e-8f9a-4b1c-9d2e-3f4a5b6c7d8e",
"planName": "Quick Payment Plan",
"paymentFrequency": "WEEKLY",
"paymentFrequencyDisplay": "Weekly",
"customFrequencyDays": null,
"numberOfPayments": 8,
"calculatedDurationDays": 56,
"calculatedDurationDisplay": "8 weeks",
"apr": 10.00,
"minDownPaymentPercent": 20,
"gracePeriodDays": 7,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": false,
"displayOrder": 1,
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"createdAt": "2025-10-15T09:00:00",
"updatedAt": "2025-10-15T09:00:00"
},
{
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "12 Month Standard Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 12,
"calculatedDurationDays": 360,
"calculatedDurationDisplay": "12 months",
"apr": 15.00,
"minDownPaymentPercent": 20,
"gracePeriodDays": 30,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": true,
"displayOrder": 2,
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"createdAt": "2025-10-16T10:30:00",
"updatedAt": "2025-10-16T10:30:00"
},
{
"planId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"planName": "Budget Friendly Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 24,
"calculatedDurationDays": 720,
"calculatedDurationDisplay": "24 months",
"apr": 18.00,
"minDownPaymentPercent": 10,
"gracePeriodDays": 30,
"fulfillmentTiming": "AFTER_PAYMENT",
"isActive": false,
"isFeatured": false,
"displayOrder": 3,
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"createdAt": "2025-10-17T14:00:00",
"updatedAt": "2025-10-18T08:00:00"
}
]
}
Success Response Fields: Same as Create Installment Plan endpoint, returned as array
Error Response Examples:
Forbidden (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "You do not have permission to view this shop's products",
"action_time": "2025-10-18T10:30:45",
"data": "You do not have permission to view this shop's products"
}
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found",
"action_time": "2025-10-18T10:30:45",
"data": "Product not found"
}
3. Get Installment Plan By ID
Purpose: Retrieve detailed information about a specific installment plan
Endpoint: GET {base_url}/{planId}
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
| planId | UUID | Yes | Unique identifier of the plan | Valid UUID format, must belong to specified product |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plan retrieved successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "12 Month Standard Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 12,
"calculatedDurationDays": 360,
"calculatedDurationDisplay": "12 months",
"apr": 15.00,
"minDownPaymentPercent": 20,
"gracePeriodDays": 30,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": true,
"displayOrder": 2,
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"createdAt": "2025-10-16T10:30:00",
"updatedAt": "2025-10-16T10:30:00",
"metadata": {}
}
}
Success Response Fields: Same as Create Installment Plan endpoint
Error Response Examples: Same as Get Product Installment Plans endpoint, plus:
Not Found - Plan (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Installment plan not found",
"action_time": "2025-10-18T10:30:45",
"data": "Installment plan not found"
}
4. Update Installment Plan
Purpose: Update an existing installment plan's configuration
Endpoint: PUT {base_url}/{planId}
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| Content-Type | string | Yes | application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
| planId | UUID | Yes | Unique identifier of the plan to update | Valid UUID format, must belong to specified product |
Request JSON Sample:
{
"planName": "12 Month Premium Plan",
"paymentFrequency": "MONTHLY",
"customFrequencyDays": null,
"numberOfPayments": 12,
"apr": 12.00,
"minDownPaymentPercent": 25,
"gracePeriodDays": 45,
"fulfillmentTiming": "IMMEDIATE",
"displayOrder": 1
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| planName | string | No | Updated display name | Min: 3, Max: 100 characters |
| paymentFrequency | string | No | Updated payment frequency | enum: DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS |
| customFrequencyDays | integer | Conditional | Updated custom frequency | Required if paymentFrequency is CUSTOM_DAYS, Min: 1, Max: 365 |
| numberOfPayments | integer | No | Updated number of payments | Min: 2, Max: 120 |
| apr | decimal | No | Updated APR | Min: 0.00, Max: 36.00 |
| minDownPaymentPercent | integer | No | Updated minimum down payment | Min: 10, Max: 50 |
| gracePeriodDays | integer | No | Updated grace period | Min: 0, Max: 60 |
| fulfillmentTiming | string | No | Updated fulfillment option | enum: IMMEDIATE, AFTER_PAYMENT |
| displayOrder | integer | No | Updated display order | Min: 0 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plan updated successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "12 Month Premium Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 12,
"calculatedDurationDays": 360,
"calculatedDurationDisplay": "12 months",
"apr": 12.00,
"minDownPaymentPercent": 25,
"gracePeriodDays": 45,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": true,
"displayOrder": 1,
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"createdAt": "2025-10-16T10:30:00",
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields: Same as Create Installment Plan endpoint
Error Response Examples: Same as Create Installment Plan endpoint
Important Notes:
- Updating a plan only affects new agreements created after the update
- Existing agreements continue using the original terms
- Cannot update isActive or isFeatured via this endpoint (use dedicated endpoints)
5. Delete Installment Plan
Purpose: Permanently delete an installment plan (only if no active agreements exist)
Endpoint: DELETE {base_url}/{planId}
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
| planId | UUID | Yes | Unique identifier of the plan to delete | Valid UUID format, must belong to specified product |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plan deleted successfully",
"action_time": "2025-10-18T10:30:45",
"data": null
}
Success Response Fields: No data returned on successful deletion
Error Response Examples:
Bad Request - Has Active Agreements (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot delete plan with active agreements. Deactivate the plan instead.",
"action_time": "2025-10-18T10:30:45",
"data": "Cannot delete plan with active agreements. Deactivate the plan instead."
}
Forbidden (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "You do not have permission to delete this plan",
"action_time": "2025-10-18T10:30:45",
"data": "You do not have permission to delete this plan"
}
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Installment plan not found",
"action_time": "2025-10-18T10:30:45",
"data": "Installment plan not found"
}
6. Activate Installment Plan
Purpose: Activate an installment plan to make it available to customers
Endpoint: PATCH {base_url}/{planId}/activate
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
| planId | UUID | Yes | Unique identifier of the plan to activate | Valid UUID format, must belong to specified product |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plan activated successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "6d7e8f9a-0b1c-4d2e-9f3a-4b5c6d7e8f9a",
"planName": "Budget Friendly Plan",
"isActive": true,
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| planId | ID of the activated plan |
| planName | Name of the plan |
| isActive | Active status (true) |
| updatedAt | Update timestamp |
Error Response Examples: Same as Delete Installment Plan endpoint
7. Deactivate Installment Plan
Purpose: Deactivate an installment plan to hide it from customers (preserves data for existing agreements)
Endpoint: PATCH {base_url}/{planId}/deactivate
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token |
8. Set Featured Plan
Purpose: Mark a plan as featured (recommended/most popular) - only one plan per product can be featured
Endpoint: PATCH {base_url}/{planId}/set-featured
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
| planId | UUID | Yes | Unique identifier of the plan to feature | Valid UUID format, must belong to specified product |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Featured plan set successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "12 Month Premium Plan",
"isFeatured": true,
"previousFeaturedPlanId": "4b5c6d7e-8f9a-4b1c-9d2e-3f4a5b6c7d8e",
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| planId | ID of the newly featured plan |
| planName | Name of the plan |
| isFeatured | Featured status (true) |
| previousFeaturedPlanId | ID of previously featured plan (null if none) |
| updatedAt | Update timestamp |
Error Response Examples: Same as Activate Installment Plan endpoint
Important Notes:
- Setting a plan as featured automatically un-features any previously featured plan
- Only active plans should be featured
- Featured plans typically display "Most Popular" or "Recommended" badge
- Featured plans often appear first in plan listings
9. Enable Installments for Product
Purpose: Enable installment payment option for a product (makes all active plans available)
Endpoint: PATCH {base_url}/enable-installments
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installments enabled successfully for product",
"action_time": "2025-10-18T10:30:45",
"data": {
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"installmentAvailable": true,
"activePlansCount": 2,
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| productId | ID of the product |
| productName | Name of the product |
| installmentAvailable | Installment availability status (true) |
| activePlansCount | Number of active plans for this product |
| updatedAt | Update timestamp |
Error Response Examples:
Bad Request - No Plans (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot enable installments: No installment plans created for this product",
"action_time": "2025-10-18T10:30:45",
"data": "Cannot enable installments: No installment plans created for this product"
}
Forbidden (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "You do not have permission to modify this product",
"action_time": "2025-10-18T10:30:45",
"data": "You do not have permission to modify this product"
}
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found",
"action_time": "2025-10-18T10:30:45",
"data": "Product not found"
}
10. Disable Installments for Product
Purpose: Disable installment payment option for a product (hides all plans from customers)
Endpoint: PATCH {base_url}/disable-installments
Access Level: ๐ Protected (Requires Authentication, Shop Owner Only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installments disabled successfully for product",
"action_time": "2025-10-18T10:30:45",
"data": {
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"installmentAvailable": false,
"activePlansCount": 2,
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields: Same as Enable Installments endpoint
Error Response Examples: Same as Enable Installments endpoint
Important Notes:
- Disabling installments hides the option from product page
- Existing agreements continue to function normally
- Plans are preserved and can be reactivated
- Customers with active agreements can still make payments
Common HTTP Status Codes
200 OK: Successful request400 Bad Request: Invalid request data or business logic violation401 Unauthorized: Authentication required/failed403 Forbidden: Insufficient permissions or not shop owner404 Not Found: Resource not found422 Unprocessable Entity: Validation errors500 Internal Server Error: Server error
Payment Frequency Options
- DAILY: Payment every day (1 day)
- WEEKLY: Payment every week (7 days)
- BI_WEEKLY: Payment every 2 weeks (14 days)
- SEMI_MONTHLY: Payment twice per month (~15 days)
- MONTHLY: Payment every month (~30 days)
- QUARTERLY: Payment every 3 months (~90 days)
- CUSTOM_DAYS: Custom interval (1-365 days)
Fulfillment Timing Options
- IMMEDIATE: Product ships after down payment, customer receives product while paying installments
- AFTER_PAYMENT: Product ships after final payment (layaway model), inventory held during payment period
Plan Configuration Constraints
| Parameter | Minimum | Maximum | Notes |
|---|---|---|---|
| APR | 0% | 36% | Platform-enforced limit |
| Down Payment | 10% | 50% | Minimum by plan, maximum platform-wide |
| Number of Payments | 2 | 120 | Typical range: 4-24 payments |
| Grace Period | 0 days | 60 days | Days before first payment |
| Custom Frequency | 1 day | 365 days | Only for CUSTOM_DAYS type |
| Plan Name | 3 chars | 100 chars | Must be unique per product |
Plan States and Lifecycle
Active Plan
- Visible to customers on product page
- Can be selected during checkout
- Creates new agreements
- Appears in public API responses
Inactive Plan
Featured Plan
- Displays "Most Popular" or "Recommended" badge
- Only one plan per product can be featured
- Typically listed first in plan options
- Used for promoting preferred payment terms
Deleted Plan
- Permanently removed from system
- Only possible if no agreements exist
- Cannot be recovered
- Use deactivation instead for preservation
Business Rules Summary
- One Featured Plan: Only one plan per product can be featured at a time
- Shop Ownership: Only shop owners can manage their product plans
- Active Plans Only: Customers only see active plans for products with installments enabled
- No Deletion with Agreements: Plans with existing agreements cannot be deleted, only deactivated
- Update Impact: Plan updates only affect new agreements, not existing ones
- Product-Level Control: Disabling installments at product level hides all plans
- Duration Calculation: System automatically calculates duration based on frequency and payment count
- Validation: All parameters validated against platform constraints
- Plan Minimum: At least one active plan required to enable installments on product
- Historical Preservation: Deactivated plans preserved for agreement history
Common Workflows
Creating a New Installment Plan
- Ensure product exists and you own the shop
- POST /installment-plans with plan configuration
- Verify plan appears in GET /installment-plans
- Optionally set as featured via PATCH /set-featured
- Enable installments on product if not already enabled
Managing Multiple Plans
- Create multiple plans for different customer segments
- Quick payment plan (8 weeks, 10% APR)
- Standard plan (12 months, 15% APR)
- Budget plan (24 months, 18% APR)
- Set most balanced plan as featured
- Order by display priority (displayOrder field)
- Monitor customer preferences and adjust
Temporarily Disabling Plans
- PATCH /deactivate for specific plan
- OR PATCH /disable-installments for all plans
- Existing agreements continue normally
- Reactivate when ready with PATCH /activate or /enable-installments
Updating Plan Terms
- Analyze current agreement performance
- PUT /installment-plans/{planId} with new terms
- New terms apply only to future agreements
- Consider creating new plan for major changes
Deleting Unused Plans
- Check if plan has any agreements (attempt delete will fail if so)
- DELETE /installment-plans/{planId}
- Plan permanently removed
- If has agreements, use deactivate instead
Client-Side Validation
- Validate shop/product ownership before API call
- Check APR within 0-36% range
- Verify down payment within 10-50% range
- Validate payment count between 2-120
- Ensure customFrequencyDays present for CUSTOM_DAYS
- Check plan name length (3-100 characters)
Server-Side Error Handling
- 400: Display user-friendly message, allow retry
- 403: Show "Insufficient permissions" message
- 404: Redirect to product list or show error
- 422: Display field-specific validation errors
- 500: Generic error message, log for investigation
Testing Scenarios
Happy Path
- Create plan with valid parameters
- Retrieve plan and verify all fields
- Update plan configuration
- Set as featured plan
- Activate/deactivate plan
- Enable/disable product installments
Edge Cases
- Create multiple plans with same parameters
- Set featured when no featured plan exists
- Set featured when another plan is featured
- Update plan to use custom frequency
- Deactivate only active plan
- Enable installments with no active plans
Error Cases
- Invalid shop/product IDs โ 404
- Non-owner attempting management โ 403
- APR outside 0-36% range โ 422
- Down payment outside 10-50% โ 422
- Delete plan with agreements โ 400
- Missing required fields โ 422
- Invalid payment frequency โ 422
- Custom days without CUSTOM_DAYS โ 422
Performance Considerations
- Cache plan data at product level (TTL: 30 minutes)
- Invalidate cache on plan create/update/delete
- Batch operations when managing multiple plans
- Use pagination for products with many plans
- Index on productId for fast retrieval
Security Considerations
- Always validate shop ownership
- Verify product belongs to shop
- Rate limiting: 100 requests/hour per user
- Input validation on all parameters
- SQL injection prevention (parameterized queries)
- XSS prevention (sanitize all inputs)
- Audit log all plan changes
Monitoring and Analytics
Track the following metrics:
- Plans created per shop
- Plan activation/deactivation frequency
- Featured plan changes
- Plans by payment frequency
- Average APR across plans
- Popular plan configurations
- Plans with most agreements
Best Practices for Shop Owners
Plan Design Strategy
- Offer 2-3 plans for different budgets
- Quick plan: Short term, low interest
- Standard plan: Medium term, moderate interest
- Budget plan: Long term, higher interest
- Use clear naming: "8-Week Quick Pay", "12-Month Standard"
- Set appropriate featured plan: Balance of affordability and profit
- Adjust based on data: Monitor which plans customers choose
APR Configuration
Down Payment Strategy
- High-value items: Higher down payment (25-40%)
- Impulse purchases: Lower down payment (10-20%)
- Luxury items: Flexible range (15-50%)
- Clearance items: Fixed minimum (10-15%)
Grace Period Guidelines
- Weekly payments: 7-14 days grace
- Monthly payments: 30-45 days grace
- Seasonal products: Align with customer cash flow
- New customers: Shorter grace (7-15 days)
Common Mistakes to Avoid
- Too many plans (confuses customers) - stick to 2-4
- Too high APR (customers avoid) - keep competitive
- Too low down payment (higher default risk) - minimum 15-20%
- Deleting active plans - deactivate instead
- Not setting featured plan - customers want guidance
- Inconsistent plan naming - use clear conventions
- Not updating based on performance - review quarterly
- Enabling without active plans - create plans first
Support & Resources
- API Documentation: https://docs.nextgate.com/api/installment-plans
- Shop Owner Guide: https://help.nextgate.com/shop/installments
- Developer Portal: https://developers.nextgate.com
- Support Email: shop-support@nextgate.com
- Status Page: https://status.nextgate.com
- Video Tutorials: https://academy.nextgate.com/installments
Version 1.0 (2025-10-18)
- Initial release
- Full CRUD operations for installment plans
- Plan activation/deactivation
- Featured plan management
- Product-level installment control
- Shop ownership validation
- Comprehensive error handling
- Audit logging support for authentication | | Content-Type | string | Yes | application/json |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop | Valid UUID format, must be owned by authenticated user |
| productId | UUID | Yes | Unique identifier of the product | Valid UUID format, must belong to specified shop |
Request JSON Sample:
{
"planName": "12 Month Standard Plan",
"paymentFrequency": "MONTHLY",
"customFrequencyDays": null,
"numberOfPayments": 12,
"apr": 15.00,
"minDownPaymentPercent": 20,
"gracePeriodDays": 30,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": false,
"displayOrder": 1
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| planName | string | Yes | Display name for the plan | Min: 3, Max: 100 characters |
| paymentFrequency | string | Yes | Payment frequency type | enum: DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS |
| customFrequencyDays | integer | Conditional | Custom frequency in days | Required if paymentFrequency is CUSTOM_DAYS, Min: 1, Max: 365 |
| numberOfPayments | integer | Yes | Total number of payments | Min: 2, Max: 120 |
| apr | decimal | Yes | Annual Percentage Rate | Min: 0.00, Max: 36.00 |
| minDownPaymentPercent | integer | Yes | Minimum down payment percentage | Min: 10, Max: 50 |
| gracePeriodDays | integer | Yes | Days before first payment due | Min: 0, Max: 60 |
| fulfillmentTiming | string | Yes | When to ship product | enum: IMMEDIATE, AFTER_PAYMENT |
| isActive | boolean | No | Whether plan is active | Default: true |
| isFeatured | boolean | No | Whether plan is featured | Default: false |
| displayOrder | integer | No | Display order on product page | Min: 0, Default: 0 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Installment plan created successfully",
"action_time": "2025-10-18T10:30:45",
"data": {
"planId": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
"planName": "12 Month Standard Plan",
"paymentFrequency": "MONTHLY",
"paymentFrequencyDisplay": "Monthly",
"customFrequencyDays": null,
"numberOfPayments": 12,
"calculatedDurationDays": 360,
"calculatedDurationDisplay": "12 months",
"apr": 15.00,
"minDownPaymentPercent": 20,
"gracePeriodDays": 30,
"fulfillmentTiming": "IMMEDIATE",
"isActive": true,
"isFeatured": false,
"displayOrder": 1,
"productId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"productName": "Samsung Galaxy S24 Ultra",
"shopId": "8d3a7b12-9c4e-4f8a-b5d2-3e6f7a8b9c0d",
"shopName": "Tech World Store",
"createdAt": "2025-10-18T10:30:45",
"updatedAt": "2025-10-18T10:30:45"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| planId | Unique identifier for the created plan |
| planName | Name of the plan |
| paymentFrequency | Payment frequency enum value |
| paymentFrequencyDisplay | Human-readable frequency |
| customFrequencyDays | Custom days (if CUSTOM_DAYS type) |
| numberOfPayments | Total number of payments |
| calculatedDurationDays | Auto-calculated total duration in days |
| calculatedDurationDisplay | Human-readable duration |
| apr | Annual Percentage Rate |
| minDownPaymentPercent | Minimum down payment percentage |
| gracePeriodDays | Grace period in days |
| fulfillmentTiming | Fulfillment option |
| isActive | Active status |
| isFeatured | Featured status |
| displayOrder | Display order |
| productId | Associated product ID |
| productName | Associated product name |
| shopId | Associated shop ID |
| shopName | Associated shop name |
| createdAt | Creation timestamp |
| updatedAt | Last update timestamp |
Error Response Examples:
Bad Request - Invalid APR (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "APR must be between 0% and 36%",
"action_time": "2025-10-18T10:30:45",
"data": "APR must be between 0% and 36%"
}
Bad Request - Invalid Payment Count (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Number of payments must be between 2 and 120",
"action_time": "2025-10-18T10:30:45",
"data": "Number of payments must be between 2 and 120"
}
Validation Error (422):
{
"success": false,
"httpStatus": "UNPROCESSABLE_ENTITY",
"message": "Validation failed",
"action_time": "2025-10-18T10:30:45",
"data": {
"planName": "Plan name is required",
"apr": "must be between 0 and 36",
"minDownPaymentPercent": "must be between 10 and 50"
}
}
Forbidden - Not Shop Owner (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "You do not have permission to manage this shop's products",
"action_time": "2025-10-18T10:30:45",
"data": "You do not have permission to manage this shop's products"
}
Not Found - Product (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found",
"action_time": "2025-10-18T10:30:45",
"data": "Product not found"
}
Order Management
Base URL: api/v1/e-commerce/orders
Short Description: The Order Management API handles the complete order lifecycle for the NextGate e-commerce platform. It supports multiple purchase types, order tracking, shipping management, delivery confirmation with 6-digit codes, escrow integration, and digital product downloads.
Hints:
- All endpoints require Bearer token authentication
- Order sources: DIRECT_PURCHASE, CART_PURCHASE, DIGITAL_PURCHASE, INSTALLMENT, GROUP_PURCHASE
- Delivery confirmation uses a 6-digit code (SHA-256 hashed with salt, expires in 30 days, max 5 attempts)
- Digital orders have
deliveryStatus: NOT_APPLICABLE - Confirm-delivery response is returned directly (not wrapped in the standard response envelope)
- Every order detail response includes a
timelinearray โ ordered list of status steps with timestamps. Steps not yet reached havetimestamp: nullandisCompleted: false
Endpoints
1. Get Order by ID
Purpose: Retrieve detailed information about a specific order.
Endpoint: GET {base}/{orderId}
Access Level: ๐ Protected (Buyer or Seller only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderId | UUID | Yes | Unique identifier of the order |
Response JSON Sample:
{
"success": true,
"message": "Order retrieved successfully",
"data": {
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"orderNumber": "ORD-2025-12345",
"buyer": {
"accountId": "123e4567-e89b-12d3-a456-426614174000",
"userName": "johndoe",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe"
},
"seller": {
"shopId": "789e0123-e45b-67d8-a901-234567890abc",
"shopName": "TechStore",
"shopLogo": "https://cdn.example.com/shops/techstore.png",
"shopSlug": "techstore"
},
"productOrderStatus": "SHIPPED",
"deliveryStatus": "IN_TRANSIT",
"productOrderSource": "DIRECT_PURCHASE",
"items": [
{
"orderItemId": "111e2222-e33b-44d5-a666-777788889999",
"productId": "abc12345-def6-7890-ghij-klmnopqrstuv",
"productName": "Wireless Headphones",
"productSlug": "wireless-headphones",
"productImage": "https://cdn.example.com/products/headphones.jpg",
"productType": "PHYSICAL",
"fileIds": null,
"quantity": 2,
"unitPrice": 85000.00,
"subtotal": 170000.00,
"tax": 0.00,
"total": 170000.00
}
],
"subtotal": 170000.00,
"shippingFee": 5000.00,
"tax": 0.00,
"totalAmount": 175000.00,
"platformFee": 8750.00,
"sellerAmount": 166250.00,
"currency": "TZS",
"paymentMethod": "MPESA",
"amountPaid": 175000.00,
"amountRemaining": 0.00,
"deliveryAddress": "123 Main St, Dar es Salaam, Tanzania",
"trackingNumber": "TRACK-550E8400",
"carrier": "NextGate Shipping",
"isDeliveryConfirmed": false,
"deliveryConfirmedAt": null,
"orderedAt": "2025-10-20T14:30:00",
"shippedAt": "2025-10-21T09:15:00",
"deliveredAt": null,
"cancelledAt": null,
"cancellationReason": null,
"timeline": [
{
"status": "ORDER_PLACED",
"label": "Order Placed",
"timestamp": "2025-10-20T14:30:00",
"isCompleted": true,
"note": null
},
{
"status": "SHIPPED",
"label": "Shipped",
"timestamp": "2025-10-21T09:15:00",
"isCompleted": true,
"note": "NextGate Shipping ยท TRACK-550E8400"
},
{
"status": "DELIVERED",
"label": "Delivered",
"timestamp": null,
"isCompleted": false,
"note": null
},
{
"status": "COMPLETED",
"label": "Order Completed",
"timestamp": null,
"isCompleted": false,
"note": null
}
]
}
}
Response Fields:
| Field | Description |
|---|---|
| orderId | Unique identifier of the order |
| orderNumber | Human-readable order number |
| buyer | Buyer account info (accountId, userName, email, firstName, lastName) |
| seller | Shop info (shopId, shopName, shopLogo, shopSlug) |
| productOrderStatus | PENDING_PAYMENT, PENDING_SHIPMENT, SHIPPED, DELIVERED, COMPLETED, CANCELLED, REFUNDED |
| deliveryStatus | PENDING, SHIPPED, IN_TRANSIT, DELIVERED, CONFIRMED, NOT_APPLICABLE |
| productOrderSource | DIRECT_PURCHASE, CART_PURCHASE, DIGITAL_PURCHASE, INSTALLMENT, GROUP_PURCHASE |
| items | Array of order items โ see item fields below |
| items[].productType | PHYSICAL or DIGITAL โ frontend uses this to show tracking UI vs download UI |
| items[].fileIds | List of file UUIDs for the item โ populated only when productType is DIGITAL, null for physical. Use these with endpoint 14/15 to download files |
| subtotal | Sum of all item totals before shipping and tax |
| shippingFee | Shipping cost |
| tax | Tax amount |
| totalAmount | Final amount (subtotal + shipping + tax) |
| platformFee | Platform commission |
| sellerAmount | Amount seller receives after platform fee |
| currency | Currency code (TZS) |
| paymentMethod | Payment method used |
| amountPaid | Amount already paid |
| amountRemaining | Remaining balance (installment orders) |
| deliveryAddress | Shipping address |
| trackingNumber | Shipping tracking number (null until shipped) |
| carrier | Shipping carrier (null until shipped) |
| isDeliveryConfirmed | Whether buyer confirmed delivery |
| deliveryConfirmedAt | Timestamp of delivery confirmation (null if not confirmed) |
| orderedAt | Order creation timestamp |
| shippedAt | Shipping timestamp (null until shipped) |
| deliveredAt | Delivery timestamp (null until delivered) |
| cancelledAt | Cancellation timestamp (null if not cancelled) |
| cancellationReason | Reason for cancellation (null if not cancelled) |
| timeline | Ordered list of status steps โ see Timeline Fields below |
| timeline[].status | Step identifier: ORDER_PLACED, SHIPPED, DELIVERED, COMPLETED, FILES_AVAILABLE (digital), CANCELLED, DISPUTED, REFUNDED |
| timeline[].label | Human-readable step label |
| timeline[].timestamp | When this step occurred (null if not yet reached) |
| timeline[].isCompleted | true if this step has been reached |
| timeline[].note | Optional context โ shipping carrier + tracking on SHIPPED, cancellation reason on CANCELLED, "Confirmed by buyer" or "Auto-confirmed" on COMPLETED, null otherwise |
Error Responses:
400 Bad Request: Access denied โ user is not buyer or seller of this order401 Unauthorized: Authentication required404 Not Found: Order not found
2. Get Order by Order Number
Purpose: Retrieve order details using the human-readable order number.
Endpoint: GET {base}/number/{orderNumber}
Access Level: ๐ Protected (Buyer or Seller only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderNumber | string | Yes | Human-readable order number (e.g. ORD-2025-12345) |
Response: Same structure as Get Order by ID.
Error Responses:
400 Bad Request: Access denied โ user is not buyer or seller of this order401 Unauthorized: Authentication required404 Not Found: Order not found
3. Get My Orders
Purpose: Retrieve all orders for the authenticated customer.
Endpoint: GET {base}/my-orders
Access Level: ๐ Protected (Customer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Response JSON Sample:
{
"success": true,
"message": "Orders retrieved successfully",
"data": [ ...array of order objects (same structure as endpoint 1)... ]
}
Error Responses:
4. Get My Orders by Status
Purpose: Retrieve customer orders filtered by order status.
Endpoint: GET {base}/my-orders/status/{status}
Access Level: ๐ Protected (Customer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | enum | Yes | PENDING_PAYMENT, PENDING_SHIPMENT, SHIPPED, DELIVERED, COMPLETED, CANCELLED, REFUNDED |
Response: Array of order objects (same structure as endpoint 1).
Error Responses:
400 Bad Request: Invalid status value401 Unauthorized: Authentication required404 Not Found: User account not found
5. Get My Orders (Paginated)
Purpose: Retrieve customer orders with pagination.
Endpoint: GET {base}/my-orders/paged
Access Level: ๐ Protected (Customer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| page | integer | No | 1 | Page number (1-based) |
| size | integer | No | 10 | Number of items per page |
Response JSON Sample:
{
"success": true,
"message": "Orders retrieved successfully",
"data": {
"orders": [ "...order objects (same structure as endpoint 1)..." ],
"currentPage": 1,
"pageSize": 10,
"totalElements": 25,
"totalPages": 3,
"hasNext": true,
"hasPrevious": false,
"isFirst": true,
"isLast": false
}
}
Error Responses:
6. Get My Orders by Status (Paginated)
Purpose: Retrieve customer orders filtered by status with pagination.
Endpoint: GET {base}/my-orders/status/{status}/paged
Access Level: ๐ Protected (Customer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | enum | Yes | PENDING_PAYMENT, PENDING_SHIPMENT, SHIPPED, DELIVERED, COMPLETED, CANCELLED, REFUNDED |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| page | integer | No | 1 | Page number (1-based) |
| size | integer | No | 10 | Number of items per page |
Response: Same paginated structure as endpoint 5.
Error Responses:
400 Bad Request: Invalid status value401 Unauthorized: Authentication required404 Not Found: User account not found
7. Get Shop Orders
Purpose: Retrieve all orders for a specific shop.
Endpoint: GET {base}/shop/{shopId}/orders
Access Level: ๐ Protected (Shop Owner only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop |
Response: Array of order objects (same structure as endpoint 1).
Error Responses:
400 Bad Request: User is not the owner of this shop401 Unauthorized: Authentication required404 Not Found: Shop not found
8. Get Shop Orders by Status
Purpose: Retrieve shop orders filtered by order status.
Endpoint: GET {base}/shop/{shopId}/orders/status/{status}
Access Level: ๐ Protected (Shop Owner only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop |
| status | enum | Yes | PENDING_PAYMENT, PENDING_SHIPMENT, SHIPPED, DELIVERED, COMPLETED, CANCELLED, REFUNDED |
Response: Array of order objects (same structure as endpoint 1).
Error Responses:
400 Bad Request: Invalid status value or user is not shop owner401 Unauthorized: Authentication required404 Not Found: Shop not found
9. Get Shop Orders (Paginated)
Purpose: Retrieve shop orders with pagination.
Endpoint: GET {base}/shop/{shopId}/orders/paged
Access Level: ๐ Protected (Shop Owner only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| page | integer | No | 1 | Page number (1-based) |
| size | integer | No | 10 | Number of items per page |
Response: Same paginated structure as endpoint 5.
Error Responses:
400 Bad Request: User is not the owner of this shop401 Unauthorized: Authentication required404 Not Found: Shop not found
10. Get Shop Orders by Status (Paginated)
Purpose: Retrieve shop orders filtered by status with pagination.
Endpoint: GET {base}/shop/{shopId}/orders/status/{status}/paged
Access Level: ๐ Protected (Shop Owner only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| shopId | UUID | Yes | Unique identifier of the shop |
| status | enum | Yes | PENDING_PAYMENT, PENDING_SHIPMENT, SHIPPED, DELIVERED, COMPLETED, CANCELLED, REFUNDED |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| page | integer | No | 1 | Page number (1-based) |
| size | integer | No | 10 | Number of items per page |
Response: Same paginated structure as endpoint 5.
Error Responses:
400 Bad Request: Invalid status value or user is not shop owner401 Unauthorized: Authentication required404 Not Found: Shop not found
11. Mark Order as Shipped
Purpose: Seller marks an order as shipped. Generates a delivery confirmation code and sends it to the buyer.
Endpoint: POST {base}/{orderId}/ship
Access Level: ๐ Protected (Shop Owner/Seller only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderId | UUID | Yes | Unique identifier of the order |
Response JSON Sample:
{
"success": true,
"message": "Order marked as shipped",
"data": {
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"orderNumber": "ORD-2025-12345",
"shippedAt": "2025-10-25T10:30:45",
"message": "Order marked as shipped. Confirmation code sent to customer.",
"confirmationCodeSent": true,
"codeExpiresAt": "2025-11-24T10:30:45",
"maxVerificationAttempts": 5
}
}
Response Fields:
| Field | Description |
|---|---|
| orderId | UUID of the shipped order |
| orderNumber | Human-readable order number |
| shippedAt | Timestamp when order was marked as shipped |
| message | Confirmation message |
| confirmationCodeSent | Whether confirmation code was sent to customer |
| codeExpiresAt | When the confirmation code expires (30 days from generation) |
| maxVerificationAttempts | Maximum number of code verification attempts allowed |
Error Responses:
400 Bad Request: Order is a digital order (does not require shipping), order status is not PENDING_SHIPMENT, or user is not the seller401 Unauthorized: Authentication required404 Not Found: Order not found
12. Confirm Delivery
Purpose: Customer confirms order delivery using the 6-digit confirmation code. Releases escrow to seller.
Endpoint: POST {base}/{orderId}/confirm-delivery
Access Level: ๐ Protected (Buyer/Customer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
| User-Agent | string | No | Device info for verification tracking |
| X-Forwarded-For | string | No | Client IP address (if behind proxy) |
| X-Real-IP | string | No | Real client IP address |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderId | UUID | Yes | Unique identifier of the order |
Request JSON Sample:
{
"confirmationCode": "123456"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| confirmationCode | string | Yes | 6-digit delivery confirmation code | Exactly 6 digits (0-9) |
Response JSON Sample:
{
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"orderNumber": "ORD-2025-12345",
"deliveredAt": "2025-10-25T10:30:45",
"confirmedAt": "2025-10-25T10:30:45",
"escrowReleased": true,
"sellerAmount": 166250.00,
"currency": "TZS",
"message": "Delivery confirmed successfully. Order completed!"
}
Note: This response is returned directly without the standard success envelope.
Response Fields:
| Field | Description |
|---|---|
| orderId | UUID of the confirmed order |
| orderNumber | Human-readable order number |
| deliveredAt | Timestamp when order was marked as delivered |
| confirmedAt | Timestamp when delivery was confirmed |
| escrowReleased | Whether escrow funds were released to seller |
| sellerAmount | Amount released to seller after platform fee |
| currency | Currency code |
| message | Confirmation message |
Error Responses:
400 Bad Request: Order is a digital order (completed automatically, no confirmation needed), invalid confirmation code, order not SHIPPED, user is not buyer, max attempts exceeded, code expired, or escrow already released401 Unauthorized: Authentication required404 Not Found: Order not found or no active confirmation code422 Unprocessable Entity: Confirmation code format invalid
13. Regenerate Confirmation Code
Purpose: Customer requests a new delivery confirmation code if the previous one was lost or expired.
Endpoint: POST {base}/{orderId}/regenerate-code
Access Level: ๐ Protected (Buyer/Customer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderId | UUID | Yes | Unique identifier of the order |
Response JSON Sample:
{
"success": true,
"message": "Confirmation code regenerated successfully",
"data": {
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"orderNumber": "ORD-2025-12345",
"codeSent": true,
"destination": "email",
"codeExpiresAt": "2025-11-24T10:30:45",
"maxAttempts": 5,
"message": "New confirmation code sent to your email"
}
}
Response Fields:
| Field | Description |
|---|---|
| orderId | UUID of the order |
| orderNumber | Human-readable order number |
| codeSent | Whether new code was successfully sent |
| destination | Where the code was sent (email) |
| codeExpiresAt | When the new code expires (30 days from generation) |
| maxAttempts | Maximum number of verification attempts allowed |
| message | Confirmation message |
Error Responses:
400 Bad Request: Order is a digital order (does not use delivery confirmation codes), order status is not SHIPPED, user is not the buyer, or delivery already confirmed401 Unauthorized: Authentication required404 Not Found: Order not found
14. Get Digital Download URL
Purpose: Generates a presigned download URL for a digital file linked to an order. The URL expires in 5 minutes.
Endpoint: GET {base}/{orderId}/downloads/{fileId}
Access Level: ๐ Protected (Buyer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderId | UUID | Yes | Unique identifier of the order |
| fileId | UUID | Yes | Unique identifier of the digital file |
Response JSON Sample:
{
"success": true,
"message": "Download URL generated โ link expires in 5 minutes",
"data": {
"fileId": "abc12345-def6-7890-ghij-klmnopqrstuv",
"fileName": "course-material.pdf",
"downloadUrl": "https://storage.example.com/files/...",
"expiresAt": "2025-10-25T10:35:45",
"downloadsRemaining": 3,
"downloadCount": 2
}
}
Response Fields:
| Field | Description |
|---|---|
| fileId | Unique identifier of the digital file |
| fileName | Name of the file |
| downloadUrl | Presigned URL for downloading the file (expires in 5 minutes) |
| expiresAt | Timestamp when the download URL expires |
| downloadsRemaining | Number of downloads remaining for this buyer |
| downloadCount | Number of times this file has been downloaded |
Error Responses:
15. List Order Downloads
Purpose: Returns all digital files the buyer has access to for a given order, including fileId needed to generate download URLs. Call this first before endpoint 14.
Endpoint: GET {base}/{orderId}/downloads
Access Level: ๐ Protected (Buyer only)
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authentication |
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderId | UUID | Yes | Unique identifier of the order |
Response JSON Sample:
{
"success": true,
"message": "2 file(s) available for download",
"data": [
{
"fileId": "f1a2b3c4-def5-6789-ghij-klmnopqrstuv",
"fileName": "spring-boot-course.zip",
"contentType": "application/zip",
"fileSize": 524288000,
"downloadCount": 1,
"downloadsRemaining": 4,
"accessExpiresAt": "2026-06-18T10:00:00",
"canDownload": true
},
{
"fileId": "a9b8c7d6-e5f4-3210-hijk-lmnopqrstuvw",
"fileName": "bonus-resources.pdf",
"contentType": "application/pdf",
"fileSize": 2048000,
"downloadCount": 0,
"downloadsRemaining": 4,
"accessExpiresAt": "2026-06-18T10:00:00",
"canDownload": true
}
]
}
Response Fields:
| Field | Description |
|---|---|
| fileId | Use this in endpoint 14 to get the actual download URL |
| fileName | Display name of the file |
| contentType | MIME type of the file |
| fileSize | File size in bytes |
| downloadCount | How many times this buyer has downloaded this file |
| downloadsRemaining | Downloads left before cap is hit (null = unlimited) |
| accessExpiresAt | When this buyer's access to this file expires |
| canDownload | false if access is revoked, expired, or download cap reached |
Error Responses:
Order Creation โ How Orders Are Generated
Orders are never created manually. They are generated automatically when a checkout session moves to PAYMENT_COMPLETED. The system reads the session, applies grouping rules, and creates one or more orders depending on the cart contents and purchase type.
Order Grouping Rules
The grouping key is (shop + product type). Two items end up in the same order only if they share both the same shop and the same product type.
| Scenario | Result |
|---|---|
| Same shop, same type (both PHYSICAL) | 1 order |
| Same shop, same type (both DIGITAL) | 1 order |
| Same shop, different types (PHYSICAL + DIGITAL) | 2 separate orders |
| Different shops, same type | 1 order per shop |
| Different shops, different types | 1 order per shop per type |
Why split by type? Digital orders complete immediately (no shipping, escrow released on creation). Physical orders wait for seller shipment then buyer confirmation. Mixing them in one order would make status tracking and escrow management impossible.
Scenario 1 โ Direct Purchase (Buy Now)
Buyer clicks Buy Now on a single product. Always produces exactly one order.
Physical product:
Buyer โ Buy Now โ Payment
โ 1 order created (source: DIRECT_PURCHASE)
โ status: PENDING_SHIPMENT
โ escrow held until buyer confirms delivery
โ seller ships โ buyer confirms with 6-digit code โ escrow released โ COMPLETED
Digital product:
Buyer โ Buy Now โ Payment
โ 1 order created (source: DIGITAL_PURCHASE)
โ status: COMPLETED immediately
โ escrow released immediately
โ DigitalDownloadAccess records created for all active files
โ buyer can download right away
Scenario 2 โ Cart Purchase
Buyer checks out a cart with multiple items. The system groups by (shop, product type) and creates one order per group.
Example cart:
| Item | Shop | Type |
|---|---|---|
| Wireless Headphones | TechStore | PHYSICAL |
| Spring Boot Course (PDF) | TechStore | DIGITAL |
| Running Shoes | SportShop | PHYSICAL |
Result: 3 orders created
Order #1 โ TechStore | PHYSICAL
source: CART_PURCHASE
status: PENDING_SHIPMENT
shipping: split proportionally if multi-shop
Order #2 โ TechStore | DIGITAL
source: DIGITAL_PURCHASE
status: COMPLETED immediately
shipping: TZS 0
โ DigitalDownloadAccess created for Spring Boot Course files
โ buyer can download immediately
Order #3 โ SportShop | PHYSICAL
source: CART_PURCHASE
status: PENDING_SHIPMENT
shipping: split proportionally
Shipping split rule: If the cart has items from multiple shops, the total shipping cost is divided equally across the number of distinct shops. Each physical order gets its share.
Scenario 3 โ Installment Purchase (IMMEDIATE fulfillment)
Buyer pays in installments but gets the product after the first payment.
Physical product:
First payment โ order created (source: INSTALLMENT)
โ status: PENDING_SHIPMENT
โ seller ships after first payment
โ buyer confirms delivery โ escrow released proportionally as payments come in
Remaining payments โ collected without creating new orders
Digital product:
First payment โ order created (source: INSTALLMENT โ detected as DIGITAL_PURCHASE)
โ status: COMPLETED immediately
โ DigitalDownloadAccess created
โ buyer can download after first payment
Remaining payments โ collected, no new order needed
Scenario 4 โ Installment Purchase (AFTER_PAYMENT fulfillment)
Buyer pays all installments first, gets the product only after full payment.
Physical product:
First payment โ no order created yet, agreement tracked only
...
Final payment โ order created (source: INSTALLMENT)
โ status: PENDING_SHIPMENT
โ seller ships โ buyer confirms โ COMPLETED
Digital product:
First payment โ no order created yet
...
Final payment โ order created (source: INSTALLMENT โ detected as DIGITAL_PURCHASE)
โ status: COMPLETED immediately
โ DigitalDownloadAccess created
โ buyer can download only after all installments are paid
Scenario 5 โ Group Purchase
Multiple buyers join a group for a discounted price. When the group reaches its participant goal, an order is created for every participant simultaneously.
Physical product:
Group goal reached โ
For each participant:
โ 1 order created (source: GROUP_PURCHASE)
โ status: PENDING_SHIPMENT
โ seller ships to each buyer individually
โ each buyer confirms delivery independently
Digital product:
Group goal reached โ
For each participant:
โ 1 order created (source: GROUP_PURCHASE โ detected as DIGITAL_PURCHASE)
โ status: COMPLETED immediately
โ DigitalDownloadAccess created per participant
โ all buyers can download simultaneously
Group metadata stored on each order: groupInstanceId, groupPrice, regularPrice, savings.
Digital Download Flow (after any purchase)
Once an order with source DIGITAL_PURCHASE is created, the fulfillment service creates a DigitalDownloadAccess record per file per buyer. These records enforce:
| Rule | Configured by |
|---|---|
| Access expiry | product.downloadExpiryDays (default: 365 days) |
| Max downloads | product.maxDownloadsPerBuyer (null = unlimited) |
| Per-download URL TTL | 5 minutes (hardcoded) |
Frontend download flow:
Step 1 โ List available files for an order:
GET api/v1/e-commerce/orders/{orderId}/downloads
Response:
[
{
"fileId": "f1a2b3c4-...",
"fileName": "spring-boot-course.zip",
"contentType": "application/zip",
"fileSize": 524288000,
"downloadCount": 0,
"downloadsRemaining": 5,
"accessExpiresAt": "2026-06-18T10:00:00",
"canDownload": true
}
]
Step 2 โ Get a short-lived download link per file:
GET api/v1/e-commerce/orders/{orderId}/downloads/{fileId}
Response:
{
"fileId": "f1a2b3c4-...",
"fileName": "spring-boot-course.zip",
"downloadUrl": "https://storage.../...?X-Amz-Expires=300&...",
"expiresAt": "2026-05-19T11:05:00",
"downloadsRemaining": 4,
"downloadCount": 1
}
Step 3 โ Buyer hits downloadUrl directly. The URL points to MinIO and expires in 5 minutes. Each call to Step 2 increments downloadCount.
Order Status Reference
| Status | Applies to | Meaning |
|---|---|---|
PENDING_SHIPMENT |
Physical | Order paid, waiting for seller to ship |
SHIPPED |
Physical | Seller marked as shipped, waiting for buyer confirmation |
COMPLETED |
Both | Physical: buyer confirmed delivery. Digital: set immediately on creation |
CANCELLED |
Both | Order cancelled |
REFUNDED |
Both | Payment refunded |
| Delivery Status | Applies to | Meaning |
|---|---|---|
PENDING |
Physical | Not yet shipped |
IN_TRANSIT |
Physical | Seller marked as shipped |
CONFIRMED |
Physical | Buyer confirmed receipt |
NOT_APPLICABLE |
Digital | No physical delivery involved |
Timeline Reference
The timeline field is embedded in every order detail response. It is a sequential list of steps representing the order's lifecycle. Steps not yet reached have timestamp: null and isCompleted: false โ the frontend renders these as pending/greyed-out.
Physical order steps (DIRECT_PURCHASE, CART_PURCHASE, INSTALLMENT, GROUP_PURCHASE):
ORDER_PLACED โ SHIPPED โ DELIVERED โ COMPLETED
Digital order steps (DIGITAL_PURCHASE):
ORDER_PLACED โ FILES_AVAILABLE โ COMPLETED
Terminal branches (replace remaining steps when reached):
CANCELLED โ appears after ORDER_PLACED if cancelled before shipping
DISPUTED โ appears after SHIPPED/FILES_AVAILABLE if buyer raises a dispute
REFUNDED โ appears after DISPUTED if resolved in buyer's favour
Step notes:
| Step | Note value |
|---|---|
SHIPPED |
"<Carrier> ยท <TrackingNumber>" if tracking info is set, otherwise null |
CANCELLED |
Cancellation reason if provided, otherwise null |
COMPLETED |
"Confirmed by buyer" or "Auto-confirmed" depending on how it was confirmed |
| All others | null |
Marketplace
Base URL: api/v1/e-commerce/marketplace
Short Description: The Marketplace API is the primary product discovery layer of the Nexgate e-commerce platform. It exposes personalised product feeds, trending rankings, hot deals, live group purchases, and a powerful advanced filter with keyword search โ all driven by a scoring formula built on real purchase, view, and cart signals.
Hints:
- All endpoints work for anonymous users (no token required). Authenticated users automatically receive personalised results where applicable โ no extra parameter needed.
TRENDINGandFOR_YOUfeeds re-rank within each page using the full scoring formula. Other sort modes (NEWEST,PRICE_ASC, etc.) are sorted at the database level and return exact ordering.hasMultipleColors,maxGroupSeatsLeft, andminGroupDiscountPercentfilters are applied after the database query, so thetotalElementscount in the response reflects the pre-filtered DB count โ not the post-filtered count.viewCountincrements each timeGET /shops/{shopId}/products/{productId}is called.cartAddCountincrements when a product is added to a cart for the first time (not on quantity updates).- Pages are 1-based (
page=1returns the first page).
Standard Response Format
All API responses follow a consistent structure using the Globe Response Builder pattern:
Success Response Structure
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"content": [...],
"currentPage": 1,
"pageSize": 20,
"totalElements": 158,
"totalPages": 8,
"hasNext": true,
"hasPrevious": false
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2026-06-04T10:30:45",
"data": "Error description"
}
Standard Response Fields
| Field | Type | Description |
|---|---|---|
success |
boolean | true for success, false for errors |
httpStatus |
string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
message |
string | Human-readable operation result |
action_time |
string | ISO 8601 timestamp of response generation |
data |
object | Paginated payload for success, error detail for failures |
Scoring Formulas
These formulas are the engine behind TRENDING, FOR_YOU, and BEST_DEAL sort modes. Understanding them helps predict how products are ranked.
Log Normalization
All count-based signals (soldQuantity, viewCount, cartAddCount) are log-normalized before applying weights. This prevents one product with 100,000 sales from dominating everything else.
normalize(value) = min(1.0, log(1 + value) / log(1 + 10,000))
Examples:
0 sales โ 0.000
100 sales โ 0.501
1,000 sales โ 0.750
10,000 sales โ 1.000 โ reference ceiling
50,000 sales โ 1.000 โ capped at 1.0
Formula 1 โ Trending Score (global, same for everyone)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TRENDING SCORE FORMULA โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ trendingScore = โ
โ normalize(soldQuantity) ร 0.30 โ real money paid โ
โ + normalize(viewCount) ร 0.25 โ real browsing intent โ
โ + groupHeat ร 0.20 โ social urgency โ
โ + normalize(cartAddCount) ร 0.15 โ purchase intent โ
โ + discountStrength ร 0.07 โ deal attractiveness โ
โ + recencyBonus ร 0.03 โ freshness tiebreaker โ
โ โ
โ groupHeat = seatsOccupied / totalSeats (hottest live group) โ
โ discountStrength= (comparePrice - price) / comparePrice โ
โ recencyBonus = 1.0 if โค 7 days old โ
โ 0.5 if โค 30 days old โ
โ 0.0 otherwise โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Formula 2 โ Personalized Trending Score (authenticated users)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PERSONALIZED TRENDING SCORE FORMULA โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ personalizedScore = โ
โ trendingScore โ
โ + 0.25 (if product's shop is in user's subscriptions) โ
โ + 0.00 (otherwise) โ
โ โ
โ This is Option A โ soft priority. Subscribed-shop products float โ
โ near the top but a genuinely viral product still beats them. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Formula 3 โ Relevance Score (For You feed)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ RELEVANCE SCORE FORMULA โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ relevanceScore = โ
โ categoryMatch ร 0.40 โ product's category is in cart โ
โ + favShopBoost ร 0.40 โ shop is in user's subscriptions โ
โ + trendingScore ร 0.20 โ global popularity tiebreaker โ
โ โ
โ categoryMatch = 1.0 if product's category matches any category โ
โ currently in the user's cart, else 0.0 โ
โ favShopBoost = 1.0 if shop is subscribed, else 0.0 โ
โ โ
โ Fallback: if user has no cart items and no subscriptions, โ
โ the endpoint falls back to the Personalized Trending feed. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Advanced Filter โ UI Reference
The advanced filter groups its parameters into logical sections. Below is a reference layout showing how a frontend filter panel would be structured:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MARKETPLACE FILTERS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ SEARCH โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ?q= keyword search across name & description... โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ฐ PRICE RANGE โ
โ Min [ minPrice _________ ] โ
โ Max [ maxPrice _________ ] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ท๏ธ PRODUCT โ
โ Category [ categoryId โผ ] โ
โ Condition โ NEW โ USED โ REFURBISHED โ
โ Type โ Physical โ Digital โ
โ Urgency โ NONE โ LIMITED_TIME โ LOW_STOCK โ
โ โ FLASH_SALE โ
โ [ ] hasMultipleColors โ show colour variants only โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ฆ AVAILABILITY โ
โ [ ] inStock โ in-stock only โ
โ Min stock [ minStockQuantity ] โ bulk buyers โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ค GROUP DEALS โ
โ [ ] hasGroupBuying โ supports group buying โ
โ [ ] hasActiveGroup โ live OPEN group right now โ
โ Max seats left [ maxGroupSeatsLeft ] โ urgency โ
โ Min group disc. [ minGroupDiscountPercent ]% โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ณ PAYMENT โ
โ [ ] onSale โ currently discounted โ
โ [ ] hasInstallments โ instalment plans available โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ช SHOP TRUST โ
โ [ ] shopVerified โ verified shops only โ
โ Min trust score [ minTrustScore ] / 5.00 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ POPULARITY โ
โ Min sold count [ minSoldCount ] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ๏ธ SORT BY โ
โ โ TRENDING โ formula-ranked โ
โ โ FOR_YOU โ personalised relevance โ
โ โ NEWEST โ createdAt DESC โ
โ โ PRICE_ASC โ cheapest first โ
โ โ PRICE_DESC โ most expensive first โ
โ โ MOST_SOLD โ soldQuantity DESC โ
โ โ BEST_DEAL โ highest discount % first โ
โ โ MOST_VIEWED โ viewCount DESC โ
โ โ MOST_CARTED โ cartAddCount DESC โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Endpoints
1. Main Discovery Feed
Purpose: Returns the primary marketplace product feed with optional filters and all sort strategies. Works for both anonymous and authenticated users โ authenticated users receive personalised scoring automatically.
Endpoint: GET api/v1/e-commerce/marketplace/feed
Access Level: ๐ Public (personalised when authenticated)
Authentication: Bearer Token (optional โ improves results when provided)
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
sortBy |
enum | No | Sort strategy. Values: TRENDING, FOR_YOU, NEWEST, PRICE_ASC, PRICE_DESC, MOST_SOLD, BEST_DEAL, MOST_VIEWED, MOST_CARTED |
TRENDING |
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 20 |
minPrice |
decimal | No | Minimum product price | โ |
maxPrice |
decimal | No | Maximum product price | โ |
categoryId |
UUID | No | Filter by product category | โ |
condition |
enum | No | NEW, USED, REFURBISHED |
โ |
productType |
enum | No | PHYSICAL, DIGITAL |
โ |
inStock |
boolean | No | true = in-stock products only |
โ |
onSale |
boolean | No | true = discounted products only |
โ |
hasActiveGroup |
boolean | No | true = products with a live OPEN group right now |
โ |
shopVerified |
boolean | No | true = verified shops only |
โ |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Feed retrieved successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"content": [
{
"productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"productName": "Samsung Galaxy S24",
"productSlug": "samsung-galaxy-s24-techstore",
"primaryImage": "https://cdn.nexgate.com/products/galaxy-s24.jpg",
"productType": "PHYSICAL",
"price": 850000.00,
"comparePrice": 1050000.00,
"discountPercentage": 19.05,
"stockQuantity": 42,
"soldQuantity": 318,
"viewCount": 2741,
"cartAddCount": 195,
"urgencyTag": "LOW_STOCK",
"condition": "NEW",
"inStock": true,
"onSale": true,
"hasInstallments": true,
"shopId": "7cb3a812-1234-4abc-b3fc-9d84f55bce12",
"shopName": "TechStore Tanzania",
"shopSlug": "techstore-tanzania",
"shopLogoUrl": "https://cdn.nexgate.com/shops/techstore-logo.jpg",
"shopVerified": true,
"shopTrustScore": 4.80,
"categoryId": "a1b2c3d4-1234-5678-abcd-ef0123456789",
"categoryName": "Smartphones",
"hasActiveGroup": true,
"activeGroupHeat": 0.78,
"activeGroupPrice": 720000.00,
"activeGroupSeatsLeft": 4,
"activeGroupExpiresAt": "2026-06-04T18:00:00",
"createdAt": "2026-05-28T09:15:00"
}
],
"currentPage": 1,
"pageSize": 20,
"totalElements": 1284,
"totalPages": 65,
"hasNext": true,
"hasPrevious": false
}
}
Success Response Fields:
| Field | Description |
|---|---|
content |
Array of product cards for this page |
content[].productId |
Unique product identifier |
content[].primaryImage |
URL of the first product image |
content[].price |
Current selling price |
content[].comparePrice |
Original price (present only if product is on sale) |
content[].discountPercentage |
Regular sale discount % โ null if no comparePrice |
content[].effectiveDiscountPercentage |
Best available deal across ALL discount types โ max(salePct, activeGroupPct) ร 100. null if product has no discount of any kind. This is what the Hot Deals feed sorts by |
content[].soldQuantity |
Total units sold โ visible only if shop has enabled this |
content[].viewCount |
Cumulative public views since tracking began |
content[].cartAddCount |
Cumulative times added to any cart |
content[].urgencyTag |
NONE, LIMITED_TIME, LOW_STOCK, FLASH_SALE |
content[].hasActiveGroup |
true if there is a live OPEN group purchase right now |
content[].activeGroupHeat |
Group fill ratio โ 0.0 (empty) to 1.0 (full). null if no active group |
content[].activeGroupPrice |
Discounted price available inside the active group |
content[].activeGroupSeatsLeft |
Remaining seats in the active group |
content[].activeGroupExpiresAt |
When the active group purchase expires |
content[].shopTrustScore |
Shop trust rating from 0.00 to 5.00 |
currentPage |
Current page number (1-based) |
totalElements |
Total matching products across all pages |
hasNext / hasPrevious |
Pagination navigation flags |
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
2. Trending Products
Purpose: Returns products ranked by the trending score formula. Anonymous users receive the global trending score. Authenticated users receive a personalised ranking โ products from subscribed shops receive a +0.25 score boost so they float near the top.
Endpoint: GET api/v1/e-commerce/marketplace/trending
Access Level: ๐ Public (personalised when authenticated)
Authentication: Bearer Token (optional)
Scoring applied:
Anonymous โ trendingScore
Authenticated โ trendingScore + 0.25 (if product's shop is subscribed)
Note: The database pre-sorts by
soldQuantity DESCas an approximation, then the full formula re-ranks within each page. This means the absolute order across pages may differ slightly from a pure formula sort โ which is intentional (pagination stability).
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 20 |
categoryId |
UUID | No | Filter by category | โ |
minPrice |
decimal | No | Minimum price | โ |
maxPrice |
decimal | No | Maximum price | โ |
inStock |
boolean | No | In-stock only | โ |
onSale |
boolean | No | On sale only | โ |
shopVerified |
boolean | No | Verified shops only | โ |
Success Response: Same structure as the Main Feed endpoint.
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
3. For You โ Personalised Recommendations
Purpose: Returns products ranked by relevance to the authenticated user based on their cart categories and shop subscriptions. Falls back to the trending feed for unauthenticated users or users with no cart items and no subscriptions.
Endpoint: GET api/v1/e-commerce/marketplace/for-you
Access Level: ๐ Public (best results when authenticated)
Authentication: Bearer Token (optional โ falls back to trending if absent)
Scoring applied:
relevanceScore =
categoryMatch ร 0.40 (product's category is in user's cart)
+ favShopBoost ร 0.40 (shop is in user's subscriptions)
+ trendingScore ร 0.20 (global popularity tiebreaker)
Pool strategy: fetches 3ร the requested page size (max 150), scores in Java,
then slices the requested page from the ranked result.
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 20 |
categoryId |
UUID | No | Narrow recommendations to a specific category | โ |
inStock |
boolean | No | In-stock products only | โ |
shopVerified |
boolean | No | Verified shops only | โ |
Success Response: Same structure as the Main Feed endpoint.
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
4. Hot Deals
Purpose: Returns products with any form of discount โ regular sale price or an active group purchase discount โ ranked by the best available saving. A product with a 35% group discount ranks higher than one with a 15% regular sale, even if it has no comparePrice set.
Endpoint: GET api/v1/e-commerce/marketplace/hot-deals
Access Level: ๐ Public
Authentication: None required
Scoring applied:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EFFECTIVE DISCOUNT FORMULA โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ salePct = (comparePrice - price) / comparePrice โ
โ 0.0 if no comparePrice or comparePrice โค price โ
โ โ
โ activeGroupPct = (regularPrice - groupPrice) / regularPrice โ
โ 0.0 if no live OPEN group exists for the product โ
โ โ
โ effectiveDiscountPct = max(salePct, activeGroupPct) โ
โ โ
โ Products included: onSale = true OR hasActiveGroup = true โ
โ Sorted by: effectiveDiscountPct DESC (within each page) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Example:
Product A โ regular sale 15% off, no group โ effectivePct = 15%
Product B โ no sale, group discount 35% off โ effectivePct = 35% โ ranks first
Product C โ sale 20% off + group 40% off โ effectivePct = 40% โ ranks first
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 20 |
categoryId |
UUID | No | Filter by category | โ |
minPrice |
decimal | No | Minimum price | โ |
maxPrice |
decimal | No | Maximum price | โ |
shopVerified |
boolean | No | Verified shops only | โ |
inStock |
boolean | No | In-stock only | โ |
Success Response: Same structure as the Main Feed endpoint. effectiveDiscountPercentage is always present and non-null in this feed.
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
5. New Arrivals
Purpose: Returns recently published products, newest first. Ideal for users who want to discover what just dropped.
Endpoint: GET api/v1/e-commerce/marketplace/new-arrivals
Access Level: ๐ Public
Authentication: None required
Sorting: createdAt DESC โ exact database-level sort, no formula applied.
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 20 |
categoryId |
UUID | No | Filter by category | โ |
productType |
enum | No | PHYSICAL or DIGITAL |
โ |
shopVerified |
boolean | No | Verified shops only | โ |
Success Response: Same structure as the Main Feed endpoint.
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
6. Live Group Purchases
Purpose: Returns products that currently have an active OPEN group purchase, sorted by group heat (most seats filled = most urgent = shown first). Useful for showing users time-sensitive social buying opportunities.
Endpoint: GET api/v1/e-commerce/marketplace/live-groups
Access Level: ๐ Public
Authentication: None required
Sorting applied:
groupHeat = seatsOccupied / totalSeats
Products sorted by groupHeat DESC โ a group at 90% capacity appears
before one at 30%, creating urgency awareness for the user.
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
page |
integer | No | Page number (1-based) | 1 |
size |
integer | No | Items per page | 20 |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Live group purchases retrieved successfully",
"action_time": "2026-06-04T10:30:45",
"data": {
"content": [
{
"productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"productName": "Samsung Galaxy S24",
"price": 850000.00,
"hasActiveGroup": true,
"activeGroupHeat": 0.92,
"activeGroupPrice": 720000.00,
"activeGroupSeatsLeft": 2,
"activeGroupExpiresAt": "2026-06-04T18:00:00"
}
],
"currentPage": 1,
"pageSize": 20,
"totalElements": 37,
"totalPages": 2,
"hasNext": true,
"hasPrevious": false
}
}
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
7. Advanced Filter
Purpose: The most powerful endpoint in the marketplace. Combines every available filter with an optional keyword search (?q=) so users can express highly specific queries like: "show me NEW Samsung smartphones under 1M from verified shops with an active group deal saving at least 20%, sorted by trending."
Endpoint: GET api/v1/e-commerce/marketplace/advanced-filter
Access Level: ๐ Public (personalised scoring when authenticated)
Authentication: Bearer Token (optional โ enables FOR_YOU and personalised TRENDING)
Scoring applied per sortBy:
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ sortBy โ How it works โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ TRENDING โ Full formula re-rank in Java after DB fetch โ
โ FOR_YOU โ Relevance formula re-rank in Java after DB fetch โ
โ NEWEST โ createdAt DESC โ exact DB sort โ
โ PRICE_ASC โ price ASC โ exact DB sort โ
โ PRICE_DESC โ price DESC โ exact DB sort โ
โ MOST_SOLD โ soldQuantity DESC โ exact DB sort โ
โ BEST_DEAL โ discount % DESC โ re-ranked in Java after DB fetch โ
โ MOST_VIEWED โ viewCount DESC โ exact DB sort โ
โ MOST_CARTED โ cartAddCount DESC โ exact DB sort โ
โโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Post-fetch Java filters (applied after DB query โ totalElements reflects pre-filter count):
hasMultipleColorsโ checkscolors.size() > 1in memorymaxGroupSeatsLeftโ checks active group seats remaining in memoryminGroupDiscountPercentโ calculates group discount % in memory
Query Parameters:
| Group | Parameter | Type | Required | Description | Default |
|---|---|---|---|---|---|
| Sort & Page | sortBy |
enum | No | Sort strategy (see table above) | TRENDING |
page |
integer | No | Page number (1-based) | 1 |
|
size |
integer | No | Items per page | 20 |
|
| Search | q |
string | No | Keyword โ searches product name and description (case-insensitive LIKE) | โ |
| Price | minPrice |
decimal | No | Minimum price | โ |
maxPrice |
decimal | No | Maximum price | โ | |
| Product | categoryId |
UUID | No | Filter by product category | โ |
condition |
enum | No | NEW, USED, REFURBISHED |
โ | |
productType |
enum | No | PHYSICAL, DIGITAL |
โ | |
urgencyTag |
enum | No | NONE, LIMITED_TIME, LOW_STOCK, FLASH_SALE |
โ | |
hasMultipleColors |
boolean | No | true = colour variant products only (post-fetch) |
โ | |
| Availability | inStock |
boolean | No | true = in-stock only |
โ |
minStockQuantity |
integer | No | Minimum stock units (bulk buyers) | โ | |
| Deals | onSale |
boolean | No | true = discounted only |
โ |
hasGroupBuying |
boolean | No | true = group buying enabled on product |
โ | |
hasActiveGroup |
boolean | No | true = live OPEN group right now |
โ | |
maxGroupSeatsLeft |
integer | No | Max seats remaining in active group (post-fetch) | โ | |
minGroupDiscountPercent |
integer | No | Min group discount % e.g. 20 (post-fetch) |
โ | |
hasInstallments |
boolean | No | true = instalment plans available |
โ | |
| Shop Trust | shopVerified |
boolean | No | true = verified shops only |
โ |
minTrustScore |
decimal | No | Min shop trust score e.g. 4.00 (0.00โ5.00) |
โ | |
| Popularity | minSoldCount |
integer | No | Min total units sold | โ |
Example Requests:
Find trending Samsung phones on sale from verified shops:
GET /marketplace/advanced-filter
?q=samsung
&categoryId=a1b2c3d4-...
&onSale=true
&shopVerified=true
&sortBy=TRENDING
Find live group deals saving at least 25% with fewer than 5 seats left:
GET /marketplace/advanced-filter
?hasActiveGroup=true
&minGroupDiscountPercent=25
&maxGroupSeatsLeft=5
&sortBy=BEST_DEAL
Find digital products under 50,000 newly published, for anonymous browsing:
GET /marketplace/advanced-filter
?productType=DIGITAL
&maxPrice=50000
&sortBy=NEWEST
Success Response: Same structure as the Main Feed endpoint.
Standard Error Types:
500 INTERNAL_SERVER_ERROR: Unexpected server failure
MarketplaceProductResponse โ Full Field Reference
| Field | Type | Nullable | Description |
|---|---|---|---|
productId |
UUID | No | Unique product identifier |
productName |
string | No | Product display name |
productSlug |
string | No | URL-safe unique slug |
primaryImage |
string | Yes | URL of first product image |
productType |
enum | No | PHYSICAL or DIGITAL |
price |
decimal | No | Current selling price |
comparePrice |
decimal | Yes | Original price โ present only when product is on sale |
discountPercentage |
decimal | Yes | Regular sale discount % โ (comparePrice - price) / comparePrice ร 100. null if not on sale |
effectiveDiscountPercentage |
decimal | Yes | Best available deal: max(salePct, activeGroupPct) ร 100. Covers both regular sale AND live group discounts. This is the field the Hot Deals feed sorts by. null if no discount of any kind |
stockQuantity |
integer | No | Available stock units |
soldQuantity |
integer | No | Total units sold |
viewCount |
long | No | Cumulative public views |
cartAddCount |
long | No | Cumulative first-time cart adds |
urgencyTag |
enum | No | NONE, LIMITED_TIME, LOW_STOCK, FLASH_SALE |
condition |
enum | No | NEW, USED, REFURBISHED |
inStock |
boolean | No | true if stockQuantity > 0 |
onSale |
boolean | No | true if comparePrice > price |
hasInstallments |
boolean | No | true if instalment plans exist |
shopId |
UUID | No | Owning shop identifier |
shopName |
string | No | Shop display name |
shopSlug |
string | No | Shop URL slug |
shopLogoUrl |
string | Yes | Shop logo image URL |
shopVerified |
boolean | No | Whether shop has passed verification |
shopTrustScore |
decimal | No | Shop trust rating 0.00โ5.00 |
categoryId |
UUID | Yes | Product category identifier |
categoryName |
string | Yes | Product category display name |
hasActiveGroup |
boolean | No | true if a live OPEN group exists |
activeGroupHeat |
decimal | Yes | Fill ratio 0.0โ1.0 of hottest live group |
activeGroupPrice |
decimal | Yes | Discounted group price |
activeGroupSeatsLeft |
integer | Yes | Remaining seats in active group |
activeGroupExpiresAt |
datetime | Yes | Expiry of active group purchase |
createdAt |
datetime | No | When the product was published |
Quick Reference โ All Marketplace Endpoints
| # | Endpoint | Auth | Sort Mode |
|---|---|---|---|
| 1 | GET /marketplace/feed |
Optional | All MarketplaceSortBy values |
| 2 | GET /marketplace/trending |
Optional | Formula-ranked |
| 3 | GET /marketplace/for-you |
Optional | Relevance-ranked |
| 4 | GET /marketplace/hot-deals |
None | effectiveDiscountPercentage DESC (sale + group) |
| 5 | GET /marketplace/new-arrivals |
None | createdAt DESC |
| 6 | GET /marketplace/live-groups |
None | Group heat DESC |
| 7 | GET /marketplace/advanced-filter |
Optional | All MarketplaceSortBy values |