E_commerce-nexgate-service(7)

Cart Management

Author: Josh S. Sakweli, Backend Lead Team Last Updated: 2025-09-23 Version: v1.0

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:


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:

Error Responses:

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:

Error Responses:

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:

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:

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)

Shops Management Service

Shop Management

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-05-19
Version: v2.0

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:


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
email 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:


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}$
email 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:


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"
  }
}

Endpoint: GET api/v1/e-commerce/shops/featured

Access Level: ๐ŸŒ Public

Returns up to 20 randomly selected featured shops (ShopSummaryListResponse list).


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

Shared Path Parameter:

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
Shops Management Service

Shop Review

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-05-19
Version: v2.0

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:


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:


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:


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:


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:


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:


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
Shops Management Service

Shop Subscription

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-06-04
Version: v1.0

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:


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


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:

Unauthorized (401):

{
  "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:

Unauthorized (401):

{
  "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:

Unauthorized (401):

{
  "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

Products Management Service

Product Management

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-05-19
Version: v2.1

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:

Error Responses:


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:


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:

Error Responses:


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:

Error Responses:


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:


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:


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:


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:


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:

Error Responses:


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:


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:


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:


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:


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:


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:


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:


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.


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:

  1. Call POST /presign-upload โ†’ receive uploadUrl and objectKey
  2. PUT {uploadUrl} with binary file body (do not call the API for this step)
  3. Call POST /confirm with objectKey to 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:

  1. Call POST /presign-upload โ†’ receive uploadUrl and objectKey
  2. PUT {uploadUrl} with binary file body (client-to-MinIO directly, not through the API)
  3. Call POST /confirm with objectKey and previewType to link the file to the product

Error Responses:


17b. Confirm Preview Upload

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:


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:


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

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
Products Management Service

Wishlist Management

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-06-04
Version: v1.0

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:


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


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
Products Management Service

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.

AFTER_PAYMENT โ€” order and access created only after the final payment clears.

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

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-05-23
Version: v2.0

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:


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:


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"
}

Unauthorized (401):

{
  "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.

REGULAR_CART

Checkout from existing shopping cart. Items are fetched automatically from the user's active cart.

GROUP_PURCHASE

Group buying checkout where multiple users purchase the same product at a discounted group price.

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.

{
  "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.

CASH

Pay in cash on delivery or at the point of event check-in.

FREE

Zero-amount checkout (free products or free event tickets).

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:

  1. Status โ†’ EXPIRED
  2. Held inventory released
  3. Session cannot be updated, paid, or cancelled
  4. User must create a new checkout session

Payment Attempts Tracking

Maximum 5 attempts per session. Each attempt records:

After 5 failed attempts, session status moves to EXPIRED and inventory is released.


Error Handling Best Practices

Frontend Checklist

Before calling POST /checkout-sessions:

On 422 Insufficient Balance response:

During active session (PENDING_PAYMENT):

On payment failure:


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

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-10-02
Version: v1.0

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:


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


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:

  1. User creates checkout session with sessionType: GROUP_PURCHASE
  2. User processes payment (WALLET only)
  3. Payment completes successfully
  4. System automatically creates new group instance
  5. User becomes first participant (initiator)
  6. Group gets unique code (e.g., GP-A3X7K9)
  7. Group status set to OPEN
  8. 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:

  1. User finds available group (via product page or group code)
  2. User creates checkout session with sessionType: GROUP_PURCHASE
  3. User includes groupInstanceId in checkout metadata
  4. User processes payment (WALLET only)
  5. Payment completes successfully
  6. System adds user to existing group
  7. Group's seatsOccupied increases
  8. Group's totalParticipants increases (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:

  1. Group status changes to COMPLETED
  2. completedAt timestamp recorded
  3. Orders created for all participants
  4. Inventory permanently deducted
  5. No new participants allowed

4. Group Expiration

When a group reaches expiration time without filling:

Automatic Actions:

  1. Group status changes to FAILED
  2. All participants refunded
  3. Inventory holds released
  4. Participant status changes to REFUNDED

5. Transferring Between Groups

Users can transfer seats between compatible groups:

Compatibility Requirements:

Transfer Types:

Partial Transfer:

Full Transfer:


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:

Use Cases:


Transfer History Tracking

Each transfer is recorded in participant history:

Tracked Information:

Transfer Scenarios:

Scenario 1: Partial Transfer

Scenario 2: Full Transfer

Scenario 3: Multiple Transfers


Group Expiration and Cleanup

Automatic Expiration

Scheduled Job runs periodically to:

  1. Find groups with status=OPEN and expiresAt < now
  2. Change status to FAILED
  3. Initiate refunds for all participants
  4. Update participant status to REFUNDED
  5. Release inventory holds

Soft Deletion

Groups are soft-deleted when:

  1. All participants transfer out (empty group)
  2. Admin manually deletes group

Soft Delete Actions:


Business Rules

Maximum Seats Per Customer

If product has maxPerCustomer limit:

Example:

Group Price Lock

Group price is snapshot at creation:

Inventory Management

During Group Lifecycle:

Transfer Impact:


Integration with Checkout Sessions

Creating New Group

Checkout Session Requirements:

After Payment Success:

GroupPurchaseInstanceEntity group = groupPurchaseService.createGroupInstance(checkoutSession);

Joining Existing Group

Checkout Session Requirements:

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

Group Code Format

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

  1. User A creates group (2 seats)
  2. User B joins group (3 seats)
  3. User C joins group (5 seats)
  4. Group auto-completes
  5. Orders created for all participants

Scenario 2: Transfer Between Groups

  1. User A in Group 1 (3 seats)
  2. User A transfers 2 seats to Group 2
  3. User A remains in Group 1 (1 seat)
  4. User A now in Group 2 (2 seats)

Scenario 3: Group Expiration

  1. Create group with 1-minute expiration
  2. Wait for expiration
  3. Scheduled job processes
  4. Status โ†’ FAILED
  5. Participants refunded

Scenario 4: Hybrid Approach - Multiple Purchases

  1. User A creates group (2 seats)
  2. User A joins same group again (3 seats)
  3. User A total: 5 seats
  4. 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

Installment Purchase - Customer Endpoints

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-10-18
Version: v1.0

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:


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:


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:

Unauthorized - Token Issues (401):

{
  "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

Authentication

Agreement Status Flow

  1. PENDING_FIRST_PAYMENT: Down payment made, waiting for grace period
  2. ACTIVE: Currently paying installments
  3. COMPLETED: Fully paid off
  4. DEFAULTED: Missed 2+ payments, in collections
  5. CANCELLED: User cancelled before first payment

Payment Status Types

Business Rules

Common Error Scenarios

  1. Insufficient Wallet Balance: Top up wallet before payment
  2. Payment Not Due: Wait until due date or make early payoff
  3. Agreement Defaulted: Contact support for resolution
  4. Max Retries Exceeded: Contact support
  5. Ownership Validation Failed: Can only access own agreements

Notes for Developers

Idempotency

Rate Limiting

Webhooks

Testing

Support


Installment Purchase

Installment Purchase - Public & Plan Endpoints

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-10-18
Version: v1.0

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:


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:


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)

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:


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)

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

Payment Frequency Types

Fulfillment Timing Options

Down Payment Rules

APR (Annual Percentage Rate)

Financial Calculations

Amortization Formula

Monthly Payment = P ร— [r(1+r)^n] / [(1+r)^n - 1]

Where:

Period Rate Calculation

Payment Breakdown

Each payment consists of:

  1. Interest Portion: Remaining balance ร— period rate
  2. Principal Portion: Payment amount - interest portion
  3. Remaining Balance: Previous balance - principal portion

Schedule Generation

Comparison Information

Shows cost difference between:

Common Use Cases

1. Display Plans on Product Page

GET /api/v1/installments/products/{productId}/plans

2. Calculate Custom Preview

POST /api/v1/installments/calculate-preview
{
  "planId": "...",
  "productPrice": 2000000.00,
  "quantity": 1,
  "downPaymentPercent": 25
}

3. Pre-Checkout Validation

POST /api/v1/installments/calculate-preview

Integration Examples

Frontend Flow

  1. Product page loads โ†’ Call GET /products/{id}/plans
  2. Display plan options with previews
  3. User selects plan and adjusts down payment
  4. Call POST /calculate-preview on down payment change
  5. Show detailed breakdown and schedule
  6. User proceeds to checkout with selected configuration

Mobile App Flow

  1. Fetch plans when user taps "Installment Options"
  2. Show plan cards with key metrics
  3. Tapped plan shows full preview
  4. Slider for down payment percentage
  5. Real-time preview updates
  6. "Continue to Checkout" with configuration

Error Handling Best Practices

Client-Side Validation

Server-Side Errors

Performance Considerations

Testing Scenarios

Happy Path

  1. Get plans for valid product with installment enabled
  2. Calculate preview with valid parameters
  3. Verify calculations match expected values

Edge Cases

  1. Product with no active plans โ†’ empty array
  2. Product with installment disabled โ†’ empty array
  3. Down payment at minimum boundary
  4. Down payment at maximum boundary (50%)
  5. Inactive plan in preview request โ†’ error

Error Cases

  1. Invalid product ID โ†’ 404
  2. Invalid plan ID โ†’ 404
  3. Down payment too low โ†’ 400
  4. Down payment too high โ†’ 400
  5. Missing required fields โ†’ 422
  6. Invalid data types โ†’ 422

Data Format Standards

Business Rules Summary

  1. One Item Per Agreement: Installment limited to single product
  2. Active Plans Only: Only active plans returned in public endpoints
  3. Down Payment Range: 10-50% (plan minimum to platform maximum)
  4. APR Limits: 0-36% (platform enforced)
  5. Grace Period: 0-60 days (plan-specific)
  6. Payment Frequency: All standard frequencies supported
  7. Fulfillment Options: IMMEDIATE or AFTER_PAYMENT
  8. Currency: TZS only (Tanzania market)
  9. Rounding: All calculations rounded to 2 decimal places
  10. Early Payoff: 75% discount on remaining interest (not shown in preview)

Notes for Developers

Calculation Accuracy

Caching Strategy

Mobile Optimization

Security Considerations

Support & Resources

Documentation Checklist

Before using this API, ensure you understand:


Installment Purchase

Installment Plan Management - Admin/Shop Endpoints

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-10-18
Version: v1.0

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:


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:


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:


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:


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

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:


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:


Common HTTP Status Codes

Payment Frequency Options

Fulfillment Timing Options

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

Inactive Plan

Deleted Plan

Business Rules Summary

Common Workflows

Creating a New Installment Plan

  1. Ensure product exists and you own the shop
  2. POST /installment-plans with plan configuration
  3. Verify plan appears in GET /installment-plans
  4. Optionally set as featured via PATCH /set-featured
  5. Enable installments on product if not already enabled

Managing Multiple Plans

  1. 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)
  2. Set most balanced plan as featured
  3. Order by display priority (displayOrder field)
  4. Monitor customer preferences and adjust

Temporarily Disabling Plans

  1. PATCH /deactivate for specific plan
  2. OR PATCH /disable-installments for all plans
  3. Existing agreements continue normally
  4. Reactivate when ready with PATCH /activate or /enable-installments

Updating Plan Terms

  1. Analyze current agreement performance
  2. PUT /installment-plans/{planId} with new terms
  3. New terms apply only to future agreements
  4. Consider creating new plan for major changes

Deleting Unused Plans

  1. Check if plan has any agreements (attempt delete will fail if so)
  2. DELETE /installment-plans/{planId}
  3. Plan permanently removed
  4. If has agreements, use deactivate instead

Client-Side Validation

Server-Side Error Handling

Testing Scenarios

Happy Path

  1. Create plan with valid parameters
  2. Retrieve plan and verify all fields
  3. Update plan configuration
  4. Set as featured plan
  5. Activate/deactivate plan
  6. Enable/disable product installments

Edge Cases

  1. Create multiple plans with same parameters
  2. Set featured when no featured plan exists
  3. Set featured when another plan is featured
  4. Update plan to use custom frequency
  5. Deactivate only active plan
  6. Enable installments with no active plans

Error Cases

  1. Invalid shop/product IDs โ†’ 404
  2. Non-owner attempting management โ†’ 403
  3. APR outside 0-36% range โ†’ 422
  4. Down payment outside 10-50% โ†’ 422
  5. Delete plan with agreements โ†’ 400
  6. Missing required fields โ†’ 422
  7. Invalid payment frequency โ†’ 422
  8. Custom days without CUSTOM_DAYS โ†’ 422

Performance Considerations

Security Considerations

Monitoring and Analytics

Track the following metrics:

Best Practices for Shop Owners

Plan Design Strategy

  1. 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
  2. Use clear naming: "8-Week Quick Pay", "12-Month Standard"
  3. Set appropriate featured plan: Balance of affordability and profit
  4. Adjust based on data: Monitor which plans customers choose

APR Configuration

Down Payment Strategy

Grace Period Guidelines

Common Mistakes to Avoid

  1. Too many plans (confuses customers) - stick to 2-4
  2. Too high APR (customers avoid) - keep competitive
  3. Too low down payment (higher default risk) - minimum 15-20%
  4. Deleting active plans - deactivate instead
  5. Not setting featured plan - customers want guidance
  6. Inconsistent plan naming - use clear conventions
  7. Not updating based on performance - review quarterly
  8. Enabling without active plans - create plans first

Support & Resources

Version 1.0 (2025-10-18)

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

Author: Josh S. Sakweli, Backend Lead Team Last Updated: 2026-06-10 Version: v1.0

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:


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:


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:


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:


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:


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:


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:


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:


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:


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:


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:


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:


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

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-06-04
Version: v1.0

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:


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

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      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:


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:

Note: The database pre-sorts by soldQuantity DESC as 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:


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:


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:


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:


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:


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):

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:

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:


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