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