Products Management Service

Product Management

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

Short Description: The Product Management API provides comprehensive functionality for managing products within shops on the NextGate platform. It supports group buying, installment payment plans, color variations, specifications, digital product file delivery, product preview media (video, PDF, 3D, image), and comprehensive search/filter capabilities with role-based access control.

Hints:


Endpoints

1. Create Product

Purpose: Creates a new product in a shop, supporting group buying, color variations, specifications, and digital product rules.

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
action ReqAction Yes SAVE_DRAFT or SAVE_PUBLISH

Request Body Parameters:

Parameter Type Required Description Validation
productType ProductType Yes PHYSICAL or DIGITAL Required
productName string Yes Unique name within shop Min: 2, Max: 100 chars
productDescription string Yes Detailed description Min: 10, Max: 1000 chars
price decimal Yes Selling price Min: 0.01, max 8 digits + 2 decimal places
stockQuantity integer Yes Available stock Min: 0
categoryId UUID Yes Product category Must exist and be active
comparePrice decimal No Original price for discount display Must be > price if provided
lowStockThreshold integer No Low stock alert threshold Min: 1, Max: 1000, Default: 5
condition ProductCondition No Product condition NEW, USED_LIKE_NEW, USED_GOOD, USED_FAIR, REFURBISHED, FOR_PARTS
status ProductStatus No Initial status (overridden by action) Default: ACTIVE
productImages array Yes Product image URLs Valid URLs, at least 1 required
specifications object No Key-value specs Key max 100 chars, Value max 500 chars
colors array No Color variations See Color object below
minOrderQuantity integer No Minimum order qty Min: 1, Default: 1
maxOrderQuantity integer No Maximum order qty per order Min: 1, must be ≥ minOrderQuantity
groupBuyingEnabled boolean No Enable group buying Default: false
groupMaxSize integer No Maximum group participants Min: 2, required if groupBuyingEnabled
groupPrice decimal No Discounted group price Must be < price
groupTimeLimitHours integer No Group formation time limit Min: 1, Max: 8760
downloadExpiryDays integer No Download link expiry (DIGITAL only) Min: 1, Default: 7
maxDownloadsPerBuyer integer No Download attempts per buyer (DIGITAL only) Min: 1
maxQuantityForDigital integer No Purchase cap per buyer (DIGITAL only) Min: 1

Color Object:

Field Type Required Validation
name string Yes Max: 50 chars
hex string Yes Valid #RRGGBB format
images array No Valid URLs
priceAdjustment decimal No Min: 0.0, Default: 0

Request JSON Sample (PHYSICAL):

{
  "productType": "PHYSICAL",
  "productName": "iPhone 15 Pro Max 256GB",
  "productDescription": "The most advanced iPhone featuring the A17 Pro chip and titanium design.",
  "price": 1199.00,
  "comparePrice": 1299.00,
  "stockQuantity": 25,
  "lowStockThreshold": 5,
  "categoryId": "123e4567-e89b-12d3-a456-426614174000",
  "condition": "NEW",
  "productImages": [
    "https://example.com/images/iphone15-main.jpg"
  ],
  "specifications": {
    "Display": "6.7-inch Super Retina XDR OLED",
    "Chip": "A17 Pro"
  },
  "colors": [
    {
      "name": "Natural Titanium",
      "hex": "#F5F5DC",
      "images": ["https://example.com/colors/natural-titanium.jpg"],
      "priceAdjustment": 0.00
    }
  ],
  "minOrderQuantity": 1,
  "maxOrderQuantity": 3,
  "groupBuyingEnabled": true,
  "groupMaxSize": 50,
  "groupPrice": 1099.00,
  "groupTimeLimitHours": 72
}

Request JSON Sample (DIGITAL):

{
  "productType": "DIGITAL",
  "productName": "UI Design Kit Pro",
  "productDescription": "A comprehensive Figma component library with 500+ components.",
  "price": 49.00,
  "stockQuantity": 1000,
  "categoryId": "123e4567-e89b-12d3-a456-426614174000",
  "productImages": ["https://example.com/images/design-kit-preview.jpg"],
  "downloadExpiryDays": 30,
  "maxDownloadsPerBuyer": 5,
  "maxQuantityForDigital": 1
}

Response JSON Sample:

{
  "success": true,
  "message": "Product created successfully",
  "data": null
}

Business Rules:

Error Responses:


2. Update Product

Purpose: Updates an existing product. Only provided fields are updated.

Endpoint: PUT api/v1/e-commerce/shops/{shopId}/products/{productId}

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

Query Parameters:

Parameter Type Required Default Description
action ReqAction Yes SAVE_DRAFT or SAVE_PUBLISH

Request Body Parameters (all optional):

Parameter Type Description Validation
productName string Updated name Min: 2, Max: 100 chars
productDescription string Updated description Min: 10, Max: 1000 chars
price decimal Updated selling price Min: 0.01
comparePrice decimal Updated compare price Must be > price
stockQuantity integer Updated stock Min: 0
lowStockThreshold integer Updated low stock threshold Min: 1, Max: 1000
condition ProductCondition Updated condition See enum values
status ProductStatus Updated status Overridden by action
urgencyTag UrgencyTag Urgency badge on product NONE, NEW_ARRIVAL, LIMITED_EDITION, LIMITED_OFFER, FEW_REMAINS
categoryId UUID Updated category Must exist and be active
productImages array Updated image URLs Valid URLs, replaces existing
specifications object Updated specifications Completely replaces existing
colors array Updated color variations Completely replaces existing
minOrderQuantity integer Updated min order qty Min: 1
maxOrderQuantity integer Updated max order qty Min: 1, must be ≥ min
groupBuyingEnabled boolean Enable/disable group buying
groupMaxSize integer Updated group max Min: 2
groupPrice decimal Updated group price Must be < price
groupTimeLimitHours integer Updated group time limit Min: 1, Max: 8760
installmentEnabled boolean Enable/disable installment feature toggle
maxQuantityForInstallment integer Max qty a buyer can purchase on installment Min: 1
showStockAvailableToPublic boolean Show available stock count publicly
showSoldCountToPublic boolean Show sold count publicly
clearPreview boolean Set true to remove the product's preview file and type
previewDownloadable boolean Allow/disallow viewers from downloading the preview file

Response JSON Sample:

{
  "success": true,
  "message": "Product updated successfully and published",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max 512GB",
    "productSlug": "iphone-15-pro-max-512gb",
    "price": 1399.00,
    "status": "ACTIVE",
    "updatedAt": "2026-05-19T14:30:00Z"
  }
}

Error Responses:


3. Publish Product

Purpose: Publishes a draft product making it active and publicly available.

Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/publish

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the draft product

Response JSON Sample:

{
  "success": true,
  "message": "Product 'iPhone 15 Pro Max' published successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max",
    "status": "ACTIVE",
    "publishedAt": "2026-05-19T14:45:00Z"
  }
}

Publishing Requirements:

Error Responses:


4. Delete Product

Purpose: Deletes a product. Draft products are hard-deleted; published products are soft-deleted.

Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

Response JSON Sample (Soft Delete):

{
  "success": true,
  "message": "Product 'iPhone 15 Pro Max' has been deleted and will be permanently removed after 30 days",
  "data": {
    "productName": "iPhone 15 Pro Max",
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "previousStatus": "ACTIVE",
    "deletedAt": "2026-05-19T15:00:00Z",
    "deletionType": "SOFT_DELETE"
  }
}

Response JSON Sample (Hard Delete):

{
  "success": true,
  "message": "Draft product 'iPhone 15 Pro Max' has been permanently deleted",
  "data": null
}

Deletion Logic:

Error Responses:


5. Restore Product

Purpose: Restores a soft-deleted product back to draft status.

Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/restore

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the soft-deleted product

Response JSON Sample:

{
  "success": true,
  "message": "Product 'iPhone 15 Pro Max' has been restored successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max",
    "status": "DRAFT",
    "restoredAt": "2026-05-19T15:30:00Z",
    "note": "Product restored as draft. Publish to make it active again."
  }
}

Error Responses:


6. Get Product Detailed (Owner/Admin View)

Purpose: Retrieves comprehensive product details including all management information.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/detailed

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

Response JSON Sample:

{
  "success": true,
  "message": "Product details retrieved successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max 512GB",
    "productSlug": "iphone-15-pro-max-512gb",
    "productType": "PHYSICAL",
    "productDescription": "The most advanced iPhone ever...",
    "productImages": ["https://example.com/images/iphone15-main.jpg"],
    "price": 1199.00,
    "comparePrice": 1299.00,
    "discountAmount": 100.00,
    "discountPercentage": 7.69,
    "isOnSale": true,
    "stockQuantity": 25,
    "isInStock": true,
    "isLowStock": false,
    "sku": "SHP12345678-ELE-APP-512-0001",
    "condition": "NEW",
    "status": "ACTIVE",
    "urgencyTag": "NONE",
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "TechStore Pro",
    "categoryId": "789e0123-e89b-12d3-a456-426614174002",
    "categoryName": "Smartphones",
    "specifications": {
      "Display": "6.7-inch Super Retina XDR OLED",
      "Chip": "A17 Pro"
    },
    "colors": [
      {
        "name": "Natural Titanium",
        "hex": "#F5F5DC",
        "images": ["https://example.com/colors/natural-titanium.jpg"],
        "priceAdjustment": 0.00,
        "finalPrice": 1199.00
      }
    ],
    "groupBuying": {
      "isEnabled": true,
      "groupMaxSize": 50,
      "groupPrice": 1099.00,
      "timeLimitHours": 72
    },
    "installmentOptions": {
      "isEnabled": true,
      "plans": []
    },
    "previewType": "VIDEO",
    "previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_trailer.mp4",
    "previewDownloadable": false,
    "createdAt": "2026-05-19T10:30:00Z",
    "updatedAt": "2026-05-19T14:30:00Z"
  }
}

Error Responses:


7. Get Shop Products (Management View)

Purpose: Retrieves all products for a shop with summary statistics.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/all

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Response JSON Sample:

{
  "success": true,
  "message": "Retrieved 47 products from shop: TechStore Pro",
  "data": {
    "shop": {
      "shopId": "123e4567-e89b-12d3-a456-426614174000",
      "shopName": "TechStore Pro",
      "isVerified": true,
      "isMyShop": true
    },
    "summary": {
      "totalProducts": 47,
      "activeProducts": 35,
      "draftProducts": 8,
      "outOfStockProducts": 4,
      "lowStockProducts": 6,
      "productsWithGroupBuying": 18,
      "productsWithInstallments": 25
    },
    "products": [
      {
        "productId": "456e7890-e89b-12d3-a456-426614174001",
        "productName": "iPhone 15 Pro Max 512GB",
        "price": 1199.00,
        "stockQuantity": 25,
        "status": "ACTIVE",
        "isInStock": true,
        "hasGroupBuying": true,
        "hasInstallments": true,
        "createdAt": "2026-05-19T10:30:00Z"
      }
    ],
    "totalProducts": 47
  }
}

Error Responses:


8. Get Shop Products Paginated (Management View)

Purpose: Retrieves shop products with pagination for management dashboard.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/all-paged

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
page integer No 1 Page number (1-indexed)
size integer No 10 Items per page (max: 100)

Response JSON Sample:

{
  "success": true,
  "message": "Retrieved 10 products from shop: TechStore Pro (Page 1 of 5)",
  "data": {
    "contents": { "...same structure as /all..." },
    "currentPage": 1,
    "pageSize": 10,
    "totalElements": 47,
    "totalPages": 5,
    "hasNext": true,
    "hasPrevious": false
  }
}

Error Responses:


9. Get Public Product by ID

Purpose: Retrieves a single active product for public viewing.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved
productId UUID Yes Product must be active

Response JSON Sample:

{
  "success": true,
  "message": "Product retrieved successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max 512GB",
    "productSlug": "iphone-15-pro-max-512gb",
    "productType": "PHYSICAL",
    "productDescription": "The most advanced iPhone ever...",
    "price": 1199.00,
    "comparePrice": 1299.00,
    "discountAmount": 100.00,
    "discountPercentage": 7.69,
    "isOnSale": true,
    "isInStock": true,
    "stockQuantity": 25,
    "condition": "NEW",
    "shopName": "TechStore Pro",
    "categoryName": "Smartphones",
    "specifications": { "Display": "6.7-inch OLED" },
    "colors": [
      {
        "name": "Natural Titanium",
        "hex": "#F5F5DC",
        "priceAdjustment": 0.00,
        "finalPrice": 1199.00
      }
    ],
    "groupBuying": {
      "isAvailable": true,
      "groupMaxSize": 50,
      "groupPrice": 1099.00,
      "timeLimitHours": 72
    },
    "installmentOptions": {
      "isAvailable": true,
      "plans": [
        {
          "planId": "...",
          "planName": "6-Month Interest-Free",
          "paymentFrequency": "MONTHLY",
          "numberOfPayments": 6,
          "apr": 0.00,
          "minDownPaymentPercent": 20
        }
      ]
    },
    "previewType": "PDF",
    "previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_sample.pdf",
    "previewDownloadable": true,
    "createdAt": "2026-05-19T10:30:00Z"
  }
}

Notes:

Error Responses:


10. Get Public Shop Products

Purpose: Retrieves all active products from a shop for public browsing.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/public-view/all

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved

Response JSON Sample:

{
  "success": true,
  "message": "Retrieved 23 products from TechStore Pro",
  "data": {
    "shop": {
      "shopId": "123e4567-e89b-12d3-a456-426614174000",
      "shopName": "TechStore Pro",
      "isVerified": true
    },
    "products": [
      {
        "productId": "456e7890-e89b-12d3-a456-426614174001",
        "productName": "iPhone 15 Pro Max",
        "price": 1199.00,
        "isOnSale": true,
        "isInStock": true,
        "hasGroupBuying": true,
        "hasInstallments": true
      }
    ],
    "totalProducts": 23
  }
}

Error Responses:


11. Get Public Shop Products Paginated

Purpose: Retrieves active products from a shop with pagination for public browsing.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/public-view/all-paged

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved

Query Parameters:

Parameter Type Required Default Description
page integer No 1 Page number (1-indexed)
size integer No 10 Items per page (max: 50)

Error Responses:


12. Search Products

Purpose: Searches products within a shop using multi-word query matching across multiple fields.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/search

Access Level: 🌐 Public (Enhanced features for authenticated users)

Authentication: Optional Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
q string Yes Search query (min: 2, max: 100 chars)
status ProductStatus[] No ACTIVE Statuses to search (owners/admins only for non-ACTIVE)
page integer No 1 Page number
size integer No 10 Items per page (max: 50)
sortBy string No relevance relevance, createdAt, updatedAt, productName, price, stockQuantity, brand
sortDir string No desc asc or desc

Search Behavior:

Feature Description
Multi-word Searches for products containing ALL words
Partial match "iph" matches "iPhone"
Cross-field Matches against name, description, brand, tags, specifications
Case-insensitive "APPLE" matches "apple"

User Access:

User Type Searchable Statuses
Public / Authenticated ACTIVE only
Shop Owner / Admin All statuses

Response JSON Sample:

{
  "success": true,
  "message": "Found 12 products matching 'iphone'",
  "data": {
    "contents": {
      "shop": { "shopId": "...", "shopName": "TechStore Pro" },
      "products": [ { "...product summary fields..." } ],
      "totalProducts": 12,
      "searchMetadata": {
        "searchQuery": "iphone",
        "searchedStatuses": ["ACTIVE"],
        "userType": "PUBLIC"
      }
    },
    "currentPage": 1,
    "pageSize": 10,
    "totalElements": 12,
    "totalPages": 2,
    "hasNext": true,
    "hasPrevious": false
  }
}

Error Responses:


13. Advanced Product Filter

Purpose: Filters products using multiple criteria with combined AND/OR logic.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/advanced-filter

Access Level: 🌐 Public (Enhanced features for authenticated users)

Authentication: Optional Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
minPrice decimal No Minimum price
maxPrice decimal No Maximum price (must be ≥ minPrice)
condition ProductCondition No Condition filter
categoryId UUID No Category filter
inStock boolean No Filter by availability
onSale boolean No Filter by sale status
hasGroupBuying boolean No Filter by group buying
hasInstallments boolean No Filter by installments
hasMultipleColors boolean No Filter by color variations
status ProductStatus[] No ACTIVE Status filter (owners/admins for non-ACTIVE)
page integer No 1 Page number
size integer No 10 Items per page (max: 50)
sortBy string No createdAt createdAt, updatedAt, productName, price, stockQuantity
sortDir string No desc asc or desc

Filter Logic:

Filter Type Logic
Price range AND (minPrice AND maxPrice)
Feature flags AND (all must match)
Multiple statuses OR

Error Responses:


14. Get Public Product by Slug

Purpose: Retrieves a single active product by its slug (same response shape as Get Public Product by ID).

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/find-by-slug/{slug}

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved
slug string Yes Product slug

Error Responses:


15. Installment Plan Config

Purpose: CRUD for installment plans attached to a product. Plans are created separately after the product, and linked to it by productId.

Base URL: api/v1/e-commerce/products/{shopId}/{productId}/installment-plans

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters (shared):

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

15a. Create Installment Plan

Endpoint: POST api/v1/e-commerce/products/{shopId}/{productId}/installment-plans

Request Body:

Parameter Type Required Description Validation
planName string Yes Display name for the plan Min: 3, Max: 100 chars
paymentFrequency PaymentFrequency Yes Payment interval DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS
customFrequencyDays integer Conditional Days between payments Required when paymentFrequency=CUSTOM_DAYS, min: 1
numberOfPayments integer Yes Total number of payments Min: 2, Max: 120
apr decimal Yes Annual percentage rate Min: 0.0, Max: 36.0, 2 decimal places
minDownPaymentPercent integer Yes Minimum down payment % Min: 10, Max: 50
fulfillmentTiming FulfillmentTiming Yes When to ship IMMEDIATE (ship after down payment), AFTER_PAYMENT (layaway — ship after final payment)
displayOrder integer No Sort order in UI Min: 0, Default: 0
isFeatured boolean No Highlight as recommended plan Default: false
isActive boolean No Plan is available to buyers Default: true

Request JSON Sample:

{
  "planName": "6-Month Interest-Free",
  "paymentFrequency": "MONTHLY",
  "numberOfPayments": 6,
  "apr": 0.00,
  "minDownPaymentPercent": 20,
  "fulfillmentTiming": "IMMEDIATE",
  "displayOrder": 1,
  "isFeatured": true,
  "isActive": true
}

Error Responses:


15b. Get All Installment Plans

Endpoint: GET api/v1/e-commerce/products/{shopId}/{productId}/installment-plans

Returns a list of all installment plans for the product.

Error Responses:


15c. Get Installment Plan by ID

Endpoint: GET api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}

Additional Path Parameter:

Parameter Type Description
planId UUID ID of the installment plan

15d. Update Installment Plan

Endpoint: PUT api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}

All fields are optional — only provided fields are updated. Same field structure as Create.


15e. Delete Installment Plan

Endpoint: DELETE api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}


15f. Activate / Deactivate Plan

Activate: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/activate

Deactivate: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/deactivate

Toggles isActive on the plan without changing any other fields.


Endpoint: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/set-featured

Marks the specified plan as the featured (recommended) plan for this product.


16. Digital File Management

Purpose: Manages downloadable files for DIGITAL products. Uses a presign → upload → confirm flow to upload files directly to object storage.

Base URL: api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters (shared):

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the digital product

16a. Presign Upload

Purpose: Generates a presigned PUT URL. The client uploads the file directly to this URL, then calls /confirm.

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/presign-upload

Request Body:

Parameter Type Required Description
fileName string Yes Original file name
contentType string Yes MIME type (e.g. application/pdf)
fileSize long Yes File size in bytes (must be positive)
displayOrder integer No Sort order for multiple files

Request JSON Sample:

{
  "fileName": "design-kit-v2.fig",
  "contentType": "application/octet-stream",
  "fileSize": 52428800,
  "displayOrder": 1
}

Response JSON Sample:

{
  "success": true,
  "message": "Upload URL generated — upload directly to this URL then call /confirm",
  "data": {
    "uploadUrl": "https://s3.example.com/bucket/key?X-Amz-Signature=...",
    "objectKey": "digital-files/product-456/design-kit-v2.fig",
    "expiresAt": "2026-05-19T11:00:00"
  }
}

Upload Flow:

  1. Call POST /presign-upload → receive uploadUrl and objectKey
  2. PUT {uploadUrl} with binary file body (do not call the API for this step)
  3. Call POST /confirm with objectKey to register the file

16b. Confirm Upload

Purpose: Registers a file after direct upload to object storage. Must be called after the actual file upload succeeds.

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/confirm

Request Body:

Parameter Type Required Description
objectKey string Yes Returned by presign-upload
fileName string Yes Original file name
contentType string Yes MIME type
fileSize long Yes File size in bytes
displayOrder integer No Sort order

Response JSON Sample:

{
  "success": true,
  "message": "File confirmed and linked to product",
  "data": {
    "fileId": "aaa1bbbb-e89b-12d3-a456-426614174001",
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "fileName": "design-kit-v2.fig",
    "contentType": "application/octet-stream",
    "fileSize": 52428800,
    "fileVersion": 1,
    "displayOrder": 1,
    "isActive": true,
    "uploadedAt": "2026-05-19T10:45:00"
  }
}

16c. Get Product Files

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files

Returns a list of DigitalFileResponse objects for all files linked to the product.


16d. Delete File

Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/{fileId}

Additional Path Parameter:

Parameter Type Description
fileId UUID ID of the file to delete

16e. Toggle File Active Status

Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/{fileId}/toggle

Query Parameters:

Parameter Type Required Description
isActive boolean Yes true to activate, false to deactivate

Deactivated files are hidden from buyers but not deleted.


17. Product Preview Management

Purpose: Manages a single preview file per product — a publicly accessible teaser shown to buyers before purchase. Distinct from private digital content files. Supports VIDEO, PDF, 3D models, and IMAGE previews.

Base URL: api/v1/e-commerce/shops/{shopId}/products/{productId}/preview

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters (shared):

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product (any type — PHYSICAL or DIGITAL)

Storage: Uploaded to nextgate-preview-content bucket (public read). The confirmed URL is permanent and requires no authentication to access.


17a. Presign Preview Upload

Purpose: Generates a presigned PUT URL. The client uploads the preview file directly to this URL, then calls /confirm.

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/preview/presign-upload

Request Body:

Parameter Type Required Description
fileName string Yes Original file name (used to build the storage key)
contentType string Yes MIME type (e.g. video/mp4, application/pdf, image/jpeg, model/gltf-binary)
fileSize long Yes File size in bytes (must be positive)

Request JSON Sample:

{
  "fileName": "product-trailer.mp4",
  "contentType": "video/mp4",
  "fileSize": 52428800
}

Response JSON Sample:

{
  "success": true,
  "message": "Preview upload URL generated — upload directly then call /confirm",
  "data": {
    "uploadUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_product-trailer.mp4?X-Amz-Signature=...",
    "objectKey": "preview/shop-id/product-id/uuid_product-trailer.mp4",
    "expiresAt": "2026-05-19T11:30:00"
  }
}

Upload Flow:

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

Error Responses:


17b. Confirm Preview Upload

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/preview/confirm

Request Body:

Parameter Type Required Description
objectKey string Yes Returned by presign-upload
previewType PreviewType Yes VIDEO, PDF, THREE_D, or IMAGE
previewDownloadable boolean No Whether viewers can download the file. Default: false (view/stream only)

Request JSON Sample:

{
  "objectKey": "preview/shop-id/product-id/uuid_product-trailer.mp4",
  "previewType": "VIDEO",
  "previewDownloadable": false
}

Response JSON Sample:

{
  "success": true,
  "message": "Preview confirmed and linked to product",
  "data": null
}

After confirm, the product's public response will include:

{
  "previewType": "VIDEO",
  "previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_product-trailer.mp4",
  "previewDownloadable": false
}

Error Responses:


17c. Remove Preview

Purpose: Deletes the product's preview file from storage and clears previewType and previewUrl on the product.

Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}/preview

Response JSON Sample:

{
  "success": true,
  "message": "Preview removed from product",
  "data": null
}

Error Responses:


Quick Reference

Common HTTP Status Codes

Code Meaning
200 OK Successful GET/PUT/PATCH
201 Created Successful POST (resource created)
400 Bad Request Invalid data, validation errors, business rule violations
401 Unauthorized Authentication required or invalid token
403 Forbidden Insufficient permissions
404 Not Found Resource not found or not accessible
409 Conflict Duplicate product name or business constraint violation
422 Unprocessable Entity Field-level validation errors
500 Internal Server Error Server error

User Access Levels

User Type Product Management Status Access
Public View active products only ACTIVE only
Authenticated View active products only ACTIVE only
Shop Owner Full CRUD on own shop All statuses
System Admin Full CRUD on all shops All statuses

Product Type Fulfillment Flows

Type Flow
PHYSICAL Payment → PENDING_SHIPMENT → Seller ships → Buyer confirms with 6-digit code → Escrow releases → COMPLETED
DIGITAL Payment → COMPLETED immediately → Escrow released → DigitalDownloadAccess records created → Buyer downloads

Product Status Lifecycle

DRAFT → ACTIVE → INACTIVE → ARCHIVED
  ↑                            ↓
  └───────── RESTORE ──────────┘

OUT_OF_STOCK ←→ ACTIVE (automatic based on inventory)
Status Public Visibility Available Actions
DRAFT Hidden Edit, Publish, Hard Delete
ACTIVE Visible Edit, Deactivate, Soft Delete
INACTIVE Hidden Edit, Activate, Soft Delete
OUT_OF_STOCK Visible (out of stock badge) Restock (auto-activates)
ARCHIVED Hidden Restore

Enums Reference

ProductType: PHYSICAL, DIGITAL

PreviewType: VIDEO, PDF, THREE_D, IMAGEnull means no preview

ProductCondition: NEW, USED_LIKE_NEW, USED_GOOD, USED_FAIR, REFURBISHED, FOR_PARTS

ReqAction: SAVE_DRAFT (→ DRAFT status), SAVE_PUBLISH (→ ACTIVE status)

UrgencyTag: NONE, NEW_ARRIVAL, LIMITED_EDITION, LIMITED_OFFER, FEW_REMAINS

PaymentFrequency: DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS

FulfillmentTiming: IMMEDIATE (ship after down payment), AFTER_PAYMENT (layaway — ship after final payment)

SKU Format

SHP[8-CHAR-UUID]-[CATEGORY-3]-[BRAND-3]-[ATTRIBUTE-3]-[SEQUENCE-4]

Example: SHP12345678-ELE-APP-512-0001

Data Format Standards

Error Response Format

{
  "success": false,
  "message": "Human-readable error message",
  "error": {
    "code": "ERROR_CODE",
    "details": "Detailed information",
    "field": "fieldName (if field-specific)",
    "timestamp": "2026-05-19T14:30:00Z"
  }
}

Product Creation Flow

1. POST /shops/{shopId}/products?action=SAVE_DRAFT
   — create the product shell

2. POST /products/{shopId}/{productId}/installment-plans
   — add installment plans (if installmentEnabled)

3. POST /shops/{shopId}/products/{productId}/digital-files/presign-upload
   PUT {uploadUrl} (direct to storage)
   POST /shops/{shopId}/products/{productId}/digital-files/confirm
   — upload private digital files (DIGITAL products only)

4. POST /shops/{shopId}/products/{productId}/preview/presign-upload
   PUT {uploadUrl} (direct to storage)
   POST /shops/{shopId}/products/{productId}/preview/confirm
   — upload preview teaser (any product type, optional)
   — preview is stored in public bucket; URL is immediately accessible

5. PATCH /shops/{shopId}/products/{productId}/publish
   — publish when ready

Preview vs Digital Files

Preview Digital Files
Who sees it Everyone (before purchase) Buyers only (after payment)
Bucket nextgate-preview-content (public) nextgate-digital-content (private)
Access Permanent public URL Presigned URL, expires per downloadExpiryDays
Count One per product Multiple files per product
Products PHYSICAL or DIGITAL DIGITAL only
Purpose Teaser/sample before buying Actual purchased content

Wishlist Management

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

Base URL: {base_url}/api/v1/e-commerce/wishlist

Short Description: The Wishlist API lets authenticated users save products for later, organize them into named groups, transfer items between groups, and move items directly to the cart. Every wishlist operation is strictly private — users can only read and modify their own wishlist.

Hints:


Standard Response Format

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-09-23T10:30:45",
  "data": {}
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-09-23T10:30:45",
  "data": "Error description"
}

Standard Response Fields

Field Type Description
success boolean true for successful operations, false for errors
httpStatus string HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.)
message string Human-readable message describing the result
action_time string ISO 8601 timestamp of when the response was generated
data object/string Response payload on success, error details on failure

Standard Error Types


Endpoints

1. Add Product to Wishlist

Purpose: Adds a product to the authenticated user's wishlist. Optionally places it in an existing group (via groupId) or creates a new group on the fly (via groupName). If neither is provided the item lands in Ungrouped.

Endpoint: POST {base_url}/api/v1/e-commerce/wishlist/add

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Request JSON Sample — add to Ungrouped (no group):

{
  "productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Request JSON Sample — add to an existing group:

{
  "productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "groupId": "f9e8d7c6-b5a4-3210-fedc-ba9876543210"
}

Request JSON Sample — add and create a new group:

{
  "productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "groupName": "Birthday Gifts"
}

Request Body Parameters:

Parameter Type Required Description Validation
productId UUID Yes ID of the product to add Must be a valid, non-deleted product
groupId UUID No Add to an existing group Must belong to the authenticated user. Cannot be combined with groupName
groupName string No Create a new group with this name and add the product to it Must not already exist for this user. Cannot be combined with groupId

Success Response JSON Sample — added to Ungrouped:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product added to wishlist successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

Success Response JSON Sample — added to existing group:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product added to wishlist in group 'Electronics'",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

Success Response JSON Sample — new group created and product added:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product added to wishlist in group 'Birthday Gifts'",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

Success Response Fields:

Field Description
message Confirms the product was added; includes group name when a group is involved

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "'Sony WH-1000XM5' is already in your wishlist",
  "action_time": "2026-06-04T14:22:10",
  "data": "'Sony WH-1000XM5' is already in your wishlist"
}

2. Get Wishlist (Flat)

Purpose: Returns the authenticated user's full wishlist as a flat list. Each item includes its group ID and group name (or null if Ungrouped). Useful for list views where grouping is handled client-side.

Endpoint: GET {base_url}/api/v1/e-commerce/wishlist

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Wishlist retrieved successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": {
    "user": {
      "userId": "uuid",
      "userName": "josh_dev",
      "name": "Josh Sakweli"
    },
    "wishlistSummary": {
      "totalItems": 3,
      "totalValue": 749.97,
      "inStockItems": 2,
      "outOfStockItems": 1
    },
    "wishlistItems": [
      {
        "wishlistId": "uuid",
        "productId": "uuid",
        "productName": "Sony WH-1000XM5",
        "productSlug": "sony-wh-1000xm5",
        "productImage": "https://cdn.example.com/img.jpg",
        "unitPrice": 349.99,
        "isOnSale": false,
        "shop": {
          "shopId": "uuid",
          "shopName": "Tech Haven",
          "shopSlug": "tech-haven",
          "logoUrl": "https://cdn.example.com/logo.jpg"
        },
        "availability": {
          "inStock": true,
          "stockQuantity": 12
        },
        "groupId": "uuid",
        "groupName": "Birthday Gifts",
        "addedAt": "2026-06-04T10:00:00"
      }
    ],
    "updatedAt": "2026-06-04T10:00:00"
  }
}

Success Response Fields:

Field Description
user.userId UUID of the authenticated user
user.userName System username
user.name Full name
wishlistSummary.totalItems Total number of items in the wishlist
wishlistSummary.totalValue Sum of unit prices of all items
wishlistSummary.inStockItems Count of items currently in stock
wishlistSummary.outOfStockItems Count of items out of stock
wishlistItems[].wishlistId UUID of the wishlist entry (used for remove/transfer)
wishlistItems[].productId UUID of the product
wishlistItems[].productName Product display name
wishlistItems[].productSlug URL-friendly product identifier
wishlistItems[].productImage URL of the primary product image
wishlistItems[].unitPrice Current price of the product
wishlistItems[].isOnSale Whether the product is currently on sale
wishlistItems[].shop Shop that sells the product (id, name, slug, logo)
wishlistItems[].availability.inStock Whether the product is in stock
wishlistItems[].availability.stockQuantity Current stock count
wishlistItems[].groupId UUID of the group this item belongs to (null if Ungrouped)
wishlistItems[].groupName Name of the group (null if Ungrouped)
wishlistItems[].addedAt Timestamp when the product was added
updatedAt Timestamp of the most recently added item

3. Get Wishlist (Grouped)

Purpose: Returns the wishlist organized into sections — one section per named group plus a separate Ungrouped section. Useful for grouped display views.

Endpoint: GET {base_url}/api/v1/e-commerce/wishlist/grouped

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Grouped wishlist retrieved successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": {
    "user": {
      "userId": "uuid",
      "userName": "josh_dev",
      "name": "Josh Sakweli"
    },
    "wishlistSummary": {
      "totalItems": 3,
      "totalValue": 749.97,
      "inStockItems": 2,
      "outOfStockItems": 1
    },
    "groups": [
      {
        "groupId": "uuid",
        "groupName": "Birthday Gifts",
        "itemCount": 2,
        "items": [ ]
      }
    ],
    "ungrouped": {
      "groupId": null,
      "groupName": "Ungrouped",
      "itemCount": 1,
      "items": [ ]
    },
    "updatedAt": "2026-06-04T10:00:00"
  }
}

Success Response Fields:

Field Description
user Same user summary as flat response
wishlistSummary Same summary totals across all items
groups Array of named group sections, ordered by creation date (oldest first)
groups[].groupId UUID of the group
groups[].groupName Name of the group
groups[].itemCount Number of items in this group
groups[].items Array of wishlist item responses (same shape as flat list items)
ungrouped Section for items with no group assigned
ungrouped.groupId Always null
ungrouped.groupName Always "Ungrouped"
ungrouped.itemCount Count of items with no group
ungrouped.items Array of ungrouped wishlist items
updatedAt Timestamp of the most recently added item

4. Remove Item from Wishlist

Purpose: Permanently removes a specific item from the authenticated user's wishlist.

Endpoint: DELETE {base_url}/api/v1/e-commerce/wishlist/{itemId}

Access Level: 🔒 Protected (Requires valid JWT — owns the item)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
itemId UUID Yes The wishlistId of the item to remove Must belong to the authenticated user

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product removed from wishlist successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

5. Clear Wishlist

Purpose: Permanently removes all items from the authenticated user's wishlist. Groups are not deleted — only the items inside them.

Endpoint: DELETE {base_url}/api/v1/e-commerce/wishlist/clear

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Wishlist cleared successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

6. Move Item to Cart

Purpose: Adds a wishlist item to the user's cart at the specified quantity. The item remains in the wishlist — it is not automatically removed.

Endpoint: POST {base_url}/api/v1/e-commerce/wishlist/move-to-cart/{itemId}

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
itemId UUID Yes The wishlistId of the item to move Must belong to the authenticated user

Query Parameters:

Parameter Type Required Description Validation Default
quantity integer No Quantity to add to cart Min: 1 1

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Product moved to cart successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

7. Transfer Item to Group

Purpose: Moves a wishlist item to a different group, or removes it from its current group by setting groupId to null (moves to Ungrouped). The item stays in the wishlist — only its group assignment changes.

Endpoint: PATCH {base_url}/api/v1/e-commerce/wishlist/{itemId}/group

Access Level: 🔒 Protected (Requires valid JWT — owns the item)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
itemId UUID Yes The wishlistId of the item to transfer Must belong to the authenticated user

Request JSON Sample — move to a group:

{
  "groupId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Request JSON Sample — remove from group (move to Ungrouped):

{
  "groupId": null
}

Request Body Parameters:

Parameter Type Required Description Validation
groupId UUID or null Yes Target group UUID, or null to move to Ungrouped When a UUID, must be a group that belongs to the authenticated user

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Item moved to group 'Electronics'",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

8. Create Group

Purpose: Creates a new named wishlist group for the authenticated user. Group names must be unique per user.

Endpoint: POST {base_url}/api/v1/e-commerce/wishlist/groups

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Request JSON Sample:

{
  "name": "Electronics"
}

Request Body Parameters:

Parameter Type Required Description Validation
name string Yes Display name for the group Must not be blank. Must be unique for the authenticated user

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Wishlist group created successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": {
    "groupId": "uuid",
    "name": "Electronics",
    "itemCount": 0,
    "createdAt": "2026-06-04T14:22:10"
  }
}

Success Response Fields:

Field Description
groupId UUID of the newly created group
name Name of the group as saved
itemCount Always 0 on creation
createdAt Timestamp of group creation

Error Response JSON Sample:

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "A group named 'Electronics' already exists",
  "action_time": "2026-06-04T14:22:10",
  "data": "A group named 'Electronics' already exists"
}

9. Get Groups

Purpose: Returns all wishlist groups created by the authenticated user, ordered by creation date (oldest first). Each group includes a live item count.

Endpoint: GET {base_url}/api/v1/e-commerce/wishlist/groups

Access Level: 🔒 Protected (Requires valid JWT)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Wishlist groups retrieved successfully",
  "action_time": "2026-06-04T14:22:10",
  "data": [
    {
      "groupId": "uuid",
      "name": "Birthday Gifts",
      "itemCount": 3,
      "createdAt": "2026-06-01T09:00:00"
    },
    {
      "groupId": "uuid",
      "name": "Electronics",
      "itemCount": 1,
      "createdAt": "2026-06-03T11:30:00"
    }
  ]
}

Success Response Fields:

Field Description
[].groupId UUID of the group
[].name Display name of the group
[].itemCount Current number of wishlist items in this group
[].createdAt Timestamp of group creation

10. Delete Group

Purpose: Deletes a wishlist group. Controls what happens to the items inside via the deleteProducts query parameter.

Endpoint: DELETE {base_url}/api/v1/e-commerce/wishlist/groups/{groupId}

Access Level: 🔒 Protected (Requires valid JWT — owns the group)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer <token>

Path Parameters:

Parameter Type Required Description Validation
groupId UUID Yes UUID of the group to delete Must belong to the authenticated user

Query Parameters:

Parameter Type Required Description Validation Default
deleteProducts boolean No true → permanently delete all items in the group from wishlist. false → move items to Ungrouped, then delete group true or false false

Success Response JSON Sample — items moved to Ungrouped:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Group deleted, products moved to Ungrouped",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

Success Response JSON Sample — items permanently deleted:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Group and all its products deleted from wishlist",
  "action_time": "2026-06-04T14:22:10",
  "data": null
}

Quick Reference

All Endpoints Summary

# Method Path Description
1 POST /wishlist/add Add product to wishlist
2 GET /wishlist Get flat wishlist
3 GET /wishlist/grouped Get grouped wishlist
4 DELETE /wishlist/{itemId} Remove item from wishlist
5 DELETE /wishlist/clear Clear entire wishlist
6 POST /wishlist/move-to-cart/{itemId} Move item to cart
7 PATCH /wishlist/{itemId}/group Transfer item to group
8 POST /wishlist/groups Create group
9 GET /wishlist/groups Get all groups
10 DELETE /wishlist/groups/{groupId} Delete group

Group Behavior Rules

Scenario Behavior
Add with no group Item lands in Ungrouped
Add with groupId Item added to that existing group
Add with groupName (new) New group created, item added to it
Add with groupName (exists) 400 — duplicate group name
Add with both groupId and groupName 400 — ambiguous request
Delete group ?deleteProducts=false Items move to Ungrouped, group deleted
Delete group ?deleteProducts=true Items permanently removed, group deleted
Transfer item with groupId: null Item moved to Ungrouped

NextGate Product Ecosystem — Architecture & Purchase Flows

Overview

NextGate's e-commerce layer handles two fundamentally different kinds of products: physical products (tangible goods that require shipping and delivery confirmation) and digital products (files that a buyer downloads). Both types share the same payment infrastructure, checkout session system, financial rails, and inventory system — but diverge completely at the fulfillment stage.


1. Product Types

Physical Products

A physical product has real-world stock. The platform tracks inventory, holds it during checkout, ships it through a seller, and releases escrow only after the buyer confirms physical receipt. The entire lifecycle can take days or weeks.

Digital Products

A digital product is a file (or collection of files) that the seller uploads to a private, access-controlled storage bucket. There is no shipping. There is no confirmation code. The moment payment clears, the buyer can download. Escrow releases immediately. The lifecycle is measured in seconds.

A single product can be either physical or digital — never both. The productType field on the product record is the authoritative signal that drives every downstream decision.


2. Inventory — Shared by Both Product Types

Key rule: Every product has a stockQuantity. Digital and physical products are treated identically by the inventory system. The only difference is what happens after payment.

Every product — physical or digital — requires a stockQuantity set by the seller at creation time. The seller decides what that number is:

10        →  limited edition digital art, exclusive release
500       →  cohort-based course intake
1,000,000 →  effectively open — seller still sets a real number

There is no "unlimited" mode and no trackInventory toggle. All inventory mechanics apply to both product types without exception:

- stockQuantity check on add-to-cart
- Inventory hold at checkout session creation
- Hold released on session expiry or cancellation
- stockQuantity decremented on successful payment
- Low stock threshold warnings apply to both
- isInStock() and canOrderQuantity() run identically

This means zero special-casing in the codebase. Every product behaves the same through cart, checkout, and payment. The divergence only begins at fulfillment.

Why sellers set quantity for digital products

The business reasons vary:

Exclusivity / scarcity    →  "Only 500 copies ever sold" — creates urgency and perceived value
License seat control      →  Software licensed for exactly N seats
Cohort control            →  Course intake capped at N students for direct purchase
Business guardrail        →  Seller wants a hard ceiling as a safety net

Even if a seller sets 1,000,000, the system enforces it as a real number. The seller always sees and manages a stock figure.


3. The Only Real Difference Between Physical and Digital

                           Physical        Digital
─────────────────────────────────────────────────────────────
stockQuantity              ✓               ✓
Inventory hold at checkout ✓               ✓
Stock decrements on buy    ✓               ✓
Low stock warnings         ✓               ✓
Requires shipping          ✓               ✗
Delivery confirmation code ✓               ✗
Escrow held until delivery ✓               ✗
Order → PENDING_SHIPMENT   ✓               ✗
Escrow released immediately✗               ✓
Order → COMPLETED          ✗               ✓
DigitalDownloadAccess      ✗               ✓

Everything above the dividing line is shared. Everything below is where the paths split.


4. How Sellers Prepare Digital Products

Before a digital product can be purchased, the seller must upload its files through a two-step process designed to handle large files without routing them through the API server.

Seller → Request presigned upload URL
       → Platform generates time-limited URL (MinIO private bucket)
       → Seller uploads file DIRECTLY to MinIO (API server bypassed)
       → Seller confirms upload to API
       → Platform records file metadata and links to product

A product can have multiple files. Each gets its own record. Buyers get access to all files on purchase.

Sellers configure per product:

stockQuantity      →  required · how many units can ever be sold
lowStockThreshold  →  when to trigger low stock warning
maxQuantityForDigital → max a single buyer can purchase in one order
                        (1 for personal-use content, higher for licenses/gifts)

Download rules:
  expiryDays       →  days download links stay active after purchase (default: 7)
  downloadCap      →  max downloads per buyer (default: unlimited)
  clockStart       →  expiry from purchase time OR from first download

5. The Cart

The cart is a persistent bag of products. It does not distinguish between physical and digital items — both coexist freely. Stock availability is checked on add-to-cart for both types.

The physical/digital split only becomes relevant at fulfillment — not at cart, not at payment.


6. Checkout Session Types

Every purchase flows through a checkout session — a short-lived record holding purchase intent before payment. All four session types work for both physical and digital products.

Session Type Description
REGULAR_DIRECTLY Single-item purchase from the product page
REGULAR_CART Multi-item purchase from a cart
GROUP_PURCHASE Coordinated group buy at a discounted group price
INSTALLMENT Down payment now, remainder paid over time

Sessions expire (typically 15–30 minutes). During this window, inventory is held for both physical and digital products — preventing overselling while the buyer completes payment.


7. Shipping Logic

All items digital?  →  Skip shipping entirely. Cost = 0.
                        Inventory hold still applies.
Any item physical?  →  Shipping address + method required. Inventory held.

Mixed cart (physical + digital, same shop)?
    →  Engine splits into TWO sessions automatically:
           Session 1: physical items  →  shipping lifecycle
           Session 2: digital items   →  download lifecycle

8. Payment Infrastructure (Shared by All Types)

Payment works identically regardless of product type:

Buyer wallet  →  debited
Escrow        →  credited (money held, not yet with seller)
Ledger entry  →  recorded (double-entry, full audit trail)
Tx history    →  updated for buyer

For installment purchases, only the down payment moves at session time. Each subsequent payment creates its own ledger entry.

Escrow is the control point. Physical → held until buyer confirms delivery. Digital → released immediately at order creation.


9. Post-Payment Fulfillment — The Core Split

Physical path

Payment completes
  → stockQuantity decremented
  → Inventory hold released
  → Order created  [PENDING_SHIPMENT]
  → Seller notified
  → Seller ships → marks order SHIPPED
  → 6-digit confirmation code generated → sent to buyer
  → Buyer enters code in app
  → Escrow releases to seller
  → Order  [COMPLETED]

Escrow is held the entire shipping period. The code is the handshake — seller cannot claim money without buyer confirming receipt.


Digital path

Payment completes
  → stockQuantity decremented
  → Inventory hold released
  → Order created  [COMPLETED immediately]
  → Escrow released to seller immediately
  → DigitalDownloadAccess records created (one per file)
  → Buyer notified with download link
  → Buyer downloads within expiry window

No shipping. No confirmation code. Delivery = access records created.


10. Group Purchase — Physical vs Digital

Both product types support group purchase. The financial hold (escrow per participant) and inventory hold are identical in both cases during the waiting period. What differs is post-completion fulfillment.

Physical group purchase

Each participant pays → Escrow held + Inventory held per participant
Group fills → COMPLETED
  → Physical orders created for all participants  [PENDING_SHIPMENT]
  → stockQuantity decremented for all participants
  → Each participant goes through shipping lifecycle individually
  → Each escrow releases on individual delivery confirmation

Group expires without filling:
  → All escrows refunded
  → All inventory holds released

Digital group purchase

Each participant pays → Escrow held + Inventory held per participant
Group fills → COMPLETED
  → Orders created for all participants  [COMPLETED immediately]
  → stockQuantity decremented for all participants
  → All escrows released immediately
  → DigitalDownloadAccess records created for every participant
  → All buyers can download immediately

Group expires without filling:
  → All escrows refunded
  → All inventory holds released  (identical to physical)

The seller's participant cap on the group instance is separate from stockQuantity. Both are enforced — a buyer cannot join if either the group is full or the product stock is exhausted.


11. Installment Purchase — Physical vs Digital

Both product types support installment. The payment schedule, ledger entries, agreement lifecycle, early payoff, and flexible payment features are identical. What differs is fulfillment timing.

Fulfillment timing options

IMMEDIATE — order and access created after the down payment. Buyer gets the product now, pays over time.

AFTER_PAYMENT — order and access created only after the final payment clears.

Platform recommendation: AFTER_PAYMENT is the default for digital installment products. Sellers must explicitly opt into IMMEDIATE with acknowledgment that pre-delivery means no recourse on default.

On default — digital IMMEDIATE

Buyer stops paying → Agreement DEFAULTED
  → No new access records created for future files
  → Already-downloaded files cannot be revoked  ← known limitation, seller accepts this

On default — digital AFTER_PAYMENT

Buyer stops paying → Agreement DEFAULTED
  → Access records never created
  → No content ever delivered  ← clean outcome

12. The Download System

On every successful digital purchase (any session type, any fulfillment trigger):

Platform creates DigitalDownloadAccess record per file:
  - buyer ID + order ID + file ID
  - downloadCount (starts at 0)
  - maxDownloads (null = unlimited, or seller-set cap)
  - accessExpiresAt (now + expiry window)
  - firstDownloadAt (set on first use)

On every download request:

Buyer hits authenticated endpoint
  → Check 1: Does buyer own an active access record for this file?
  → Check 2: Has expiry window passed?
  → Check 3: Has download cap been reached?

All pass → Platform generates presigned GET URL (5-min TTL)
         → Buyer browser downloads DIRECTLY from MinIO private bucket
         → Access record downloadCount incremented
         → API server is NOT in the file transfer path

The 5-minute TTL means a leaked URL is useless within minutes. The raw MinIO object key is never exposed to the buyer.


13. Scenario Walkthrough — All Combinations

Scenario A — Direct purchase, physical product

Buyer finds a T-shirt. Clicks "Buy Now". Quantity: 1.

REGULAR_DIRECTLY session created
  → Stock check passes · Inventory held
  → Shipping address + method required
  → Buyer pays → Escrow funded
  → stockQuantity decremented · hold released
  → Order created  [PENDING_SHIPMENT]
  → Seller ships → marks SHIPPED
  → 6-digit code sent to buyer
  → Buyer confirms → Escrow released
  → Order  [COMPLETED]

Scenario B — Direct purchase, digital product

Buyer finds a PDF course. Clicks "Buy Now". Quantity: 1.

REGULAR_DIRECTLY session created
  → Stock check passes · Inventory held
  → No shipping fields collected
  → Buyer pays → Escrow funded and immediately released
  → stockQuantity decremented · hold released
  → Order created  [COMPLETED]
  → 3 DigitalDownloadAccess records created (one per chapter PDF)
  → Buyer receives download link · downloads within 7 days

Scenario C — Direct purchase, digital product, quantity 3

Buyer wants 3 software licenses to distribute to colleagues.

REGULAR_DIRECTLY session created  (quantity: 3)
  → Stock check: stockQuantity >= 3? passes · 3 units held
  → No shipping fields collected
  → Buyer pays (unitPrice × 3) → Escrow funded and immediately released
  → stockQuantity decremented by 3 · hold released
  → Order created  [COMPLETED]
  → 6 DigitalDownloadAccess records created (3 sets × 2 files per product)
  → Each set is independent — buyer can share with colleagues

Scenario D — Cart, physical only, multiple shops

Phone case from Shop A + charger from Shop B.

REGULAR_CART session created
  → Inventory held for both items
  → Buyer pays once
  → Order engine groups by shop:
       Order 1: Shop A  [PENDING_SHIPMENT]
       Order 2: Shop B  [PENDING_SHIPMENT]
  → Each seller ships independently
  → Each escrow releases on individual buyer confirmation

Scenario E — Cart, digital only

Video course + design template pack.

REGULAR_CART session created
  → Stock check + inventory hold for both digital products
  → No shipping. 
  → Buyer pays once
  → Order 1: video course     [COMPLETED] → 4 access records
  → Order 2: template pack    [COMPLETED] → 2 access records
  → Buyer downloads all 6 files within 7 days

Scenario F — Cart, mixed physical + digital, same shop

Printed book (physical) + PDF supplement (digital), same shop.

Engine detects mixed cart → splits automatically:
  Session 1: printed book  →  shipping lifecycle · inventory held
  Session 2: PDF           →  download lifecycle · inventory held

Buyer sees one checkout flow, gets two orders in history:
  Order 1 (book):  [PENDING_SHIPMENT] → shipping → confirmation → escrow releases
  Order 2 (PDF):   [COMPLETED]        → download access immediate

Scenario G — Group purchase, physical product

5 buyers collectively buy a speaker.

Buyer 1 initiates → GROUP_PURCHASE session → pays → group instance created (timer starts)
Buyers 2–5 join   → each pays → inventory held + escrow held per participant
Group fills → COMPLETED
  → Physical orders created for all 5  [PENDING_SHIPMENT]
  → stockQuantity decremented for all 5
  → Each participant ships independently
  → Each escrow releases on individual delivery confirmation

Timer expires before filling:
  → All 5 escrows refunded · all inventory holds released

Scenario H — Group purchase, digital product

30 buyers join a cohort-based online course. Seller stock: 30.

Buyers 1–30 join → each pays → inventory held + escrow held per participant
Group fills (30th seat) → COMPLETED
  → Orders created for all 30  [COMPLETED immediately]
  → stockQuantity decremented by 30 (now 0 — sold out)
  → All 30 escrows released immediately
  → DigitalDownloadAccess records created for every participant
  → All 30 buyers can download immediately

Timer expires before filling:
  → All escrows refunded · all inventory holds released

Scenario I — Installment, physical, IMMEDIATE fulfillment

Laptop, 6-month plan, 20% down, seller ships immediately.

INSTALLMENT session created
  → Stock check · inventory held
  → Down payment charged (20%)
  → Agreement created (6 scheduled payments)
  → Order created  [PENDING_SHIPMENT]  ← immediately after down payment
  → stockQuantity decremented · hold released
  → Seller ships
  → Monthly payments auto-process via scheduled jobs

Scenario J — Installment, physical, AFTER_PAYMENT fulfillment

Same laptop, layaway model.

INSTALLMENT session created
  → Stock check · inventory held
  → Down payment charged
  → Agreement created
  → NO order created yet · inventory held until final payment
  → Monthly payments auto-process
  → Final payment clears → Agreement COMPLETED
  → Order created  [PENDING_SHIPMENT]  ← only now
  → stockQuantity decremented · hold released
  → Shipping lifecycle begins

Scenario K — Installment, digital, AFTER_PAYMENT (recommended)

Video course library, 500,000 TZS, 3-month plan.

INSTALLMENT session created
  → Stock check · inventory held
  → Down payment charged (30%)
  → Agreement created (3 scheduled payments)
  → NO order, NO download access yet · inventory held
  → Monthly payments auto-process
  → Final payment clears → Agreement COMPLETED
  → stockQuantity decremented · hold released
  → Order created  [COMPLETED]
  → DigitalDownloadAccess records created  ← only now
  → Buyer downloads

If buyer defaults:
  → Agreement DEFAULTED
  → Inventory hold released · stockQuantity restored
  → Access records never created · no content delivered

Scenario L — Installment, digital, IMMEDIATE fulfillment

Same course, seller opts into IMMEDIATE.

INSTALLMENT session created
  → Stock check · inventory held
  → Down payment charged (30%)
  → Agreement created
  → stockQuantity decremented · hold released
  → Order created  [COMPLETED]           ← immediately
  → DigitalDownloadAccess records created ← immediately
  → Buyer downloads now

Monthly payments continue auto-processing.

If buyer defaults:
  → Agreement DEFAULTED
  → Already-downloaded files CANNOT be revoked  ← seller accepted this risk

14. Escrow Behavior Summary

Scenario Inventory Held Escrow Released
Physical — direct Yes, at session creation On delivery confirmation
Digital — direct Yes, at session creation Immediately at order creation
Physical — group Yes, per participant Per delivery confirmation
Digital — group Yes, per participant Immediately when group completes
Group — expired / failed Released to stock Refunded to all participants
Physical — installment IMMEDIATE Yes, until down pmt Per installment via direct ledger
Physical — installment AFTER_PAYMENT Yes, until final pmt After final payment
Digital — installment IMMEDIATE Yes, until down pmt After down payment
Digital — installment AFTER_PAYMENT Yes, until final pmt After final payment

15. MinIO Bucket Architecture

nextgate-public  (world-readable)
  → Product images · shop logos · avatars · category images
  → URLs are permanent · no expiry

nextgate-digital-content  (private · no public access)
  → Paid digital product files only
  → Accessible only via presigned URLs (5-min TTL)
  → Generated by API after access verification
  → Raw object key never exposed to buyers

This separation ensures a seller can never accidentally expose a paid file by uploading to the wrong location.


16. Key Invariants

- Every money movement goes through the double-entry ledger.
  No direct wallet balance adjustments exist.

- Escrow receives the full payment before any fulfillment action begins.

- Every product has a stockQuantity — physical and digital alike.
  There is no "unlimited" mode. Sellers set a real number.

- Inventory is held for both physical and digital products during the
  checkout session window. Released on expiry, cancellation, or payment.

- Orders are never created before:
    regular purchase  → payment completes
    group purchase    → group fills
    installment       → fulfillment trigger (down pmt or final pmt)

- Download links never expose the raw MinIO object key.
  Only opaque access identifiers resolve to presigned URLs via authenticated API.

- A checkout session produces orders exactly once.
  Idempotency checks prevent duplicates under retry or failure.

- Session expiry is enforced before payment processing.
  An expired session cannot be paid.

- For digital installment IMMEDIATE: already-downloaded files cannot be
  revoked on default. Sellers must explicitly accept this risk.

- For digital installment AFTER_PAYMENT: default means access records
  are never created and stockQuantity is restored. Clean outcome.