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.