Nviti Commerce Module — Feature Overview

This document describes how the Nviti Commerce module works end-to-end: how vendors set up their shop, how customers interact with it on WhatsApp, and how orders, payments, and notifications flow through the system.


1. What Is the Commerce Module?

Nviti Commerce is a WhatsApp-native shopping platform embedded inside the Nviti conversational AI product. It allows businesses (vendors) to sell products directly through WhatsApp conversations — no website or separate e-commerce store required.

Customers browse a product catalog, add items to a cart, check out, pay, and track orders — all within a WhatsApp chat window using interactive buttons, list messages, and WhatsApp Flows (multi-screen forms).


2. Shop Setup (Vendor Side)

2.1 Enabling Commerce

A vendor enables commerce in Workspace → Shop Settings → General tab by toggling "Enable Order Processing" on. This activates the commerce command interception layer in the WhatsApp webhook pipeline.

2.2 Shop Settings

Shop Settings is a comprehensive configuration panel with 10 tabs:

Tab What It Controls
General Order processing toggle, assigned AI agent, currency, default payment method, store name, bank account details (for Paystack)
Main Menu Header text, body text, labels for Browse/Cart/Checkout/Track Order buttons
Catalogue Category list headers, product list body text, Add to Cart CTA, search placeholder
Cart Empty cart message, View Cart/Checkout/Clear button labels, post-add instructions
Checkout Start Checkout CTA, flow body text, Confirm/Cancel buttons, payment method header/body
Orders Confirmation messages (supports {id} and {total} placeholders), Track/History CTA labels, default status note, empty history message
Flow: Form Labels Field labels for all flow forms — Full Name, Phone, Address, City, State, Delivery Method, Variant, Quantity, etc.
Flow: Catalogue Screen titles and headings for the catalogue shopping flow
Flow: Checkout Screen titles and headings for the standalone checkout flow
Flow: Cart Empty cart / cart cleared screen titles and body messages
Flow: Order History Done screen message

All settings have sensible defaults and can be customized per-vendor. Settings are stored in the company_settings table, falling back to defaults from commerce.php config.

2.3 Bank Account & Paystack Subaccount

In the General tab, vendors enter:

  • Settlement Bank (dropdown populated from Paystack's Nigerian bank list)
  • Account Number (10-digit NUBAN)
  • Account Name (auto-filled after account number is entered)

On save, the system creates or updates a Paystack Subaccount behind the scenes. The subaccount code is stored in Company.metadata and used later to split payments when customers pay by card.

2.4 Products

Products are managed in Workspace → Products (Filament resource). Each product has:

  • Name (translatable), SKU, Slug
  • Price, optional Discount price
  • Stock Quantity or Unlimited Stock toggle
  • Category assignment
  • Weight (for shipping calculations)
  • Image (displayed in WhatsApp Flows)
  • Variants (size, color, etc.) with their own pricing
  • Activation toggle — only activated, in-stock products appear in the WhatsApp shop

2.5 Categories

Categories organize the product catalog. They are managed in Workspace → Categories. Only categories containing active, in-stock products are shown to customers.

2.6 Shipping

Shipping vendors can be configured with name, phone, delivery estimation, and pricing. If no shipping vendors are configured, a flat "Standard Delivery" rate is used (configurable in Shop Settings). Delivery time estimates are location-based (Lagos: 1 day, nearby states: 2 days, elsewhere: 5 days).


3. The WhatsApp Shopping Experience

3.1 How Customers Access the Shop

Customers interact with the vendor's WhatsApp Business number. The commerce system intercepts specific text commands and button taps to provide a shopping experience. There are three interaction modes:

Mode 1: Text Commands

Customers type natural-language shortcuts that are intercepted before the AI processes them:

Command Action
menu, help, browse, shop, products Opens the main shopping menu
cart, view cart, my cart Shows the current cart
checkout Starts the checkout flow
orders, my orders, order history Opens order history
order status, track order Opens order status tracker
ORD-XXXXXX or similar patterns Looks up a specific order

Mode 2: Interactive Buttons & Lists

WhatsApp interactive messages provide tappable buttons and list items. When a customer taps a button, the system routes the callback to the appropriate handler:

Button ID Prefix Action
menu_ Main menu navigation (browse, cart, checkout, orders)
add_ Add product to cart
remove_ Remove item from cart
update_ Update item quantity
view_cart View shopping cart
clear_cart Clear all cart items
cat_ Browse a category
prod_ View product details
page_ Paginate product lists
checkout_ Start/continue checkout
confirm_ Confirm order
cancel_ Cancel checkout
pay_ Select payment method

Mode 3: WhatsApp Flows (Multi-Screen Forms)

WhatsApp Flows are rich, app-like forms rendered inside WhatsApp. The commerce module provisions 5 flow types:

  1. Catalogue Flow — The primary shopping flow with 6 screens:

    • Product List (search, browse, add to cart)
    • Product Detail (image, description, variant, quantity)
    • Checkout Cart (select items to check out)
    • Delivery Details (address, delivery method)
    • Order Summary (items, fees, total)
    • Order Success (confirmation)
  2. Cart Flow — 3 screens for viewing and managing the cart

  3. Checkout Flow — 3 screens for standalone checkout (delivery → payment → confirmation)

  4. Order Status Flow — 2 screens for viewing a single order's details and line items

  5. Order History Flow — 2 screens for browsing/searching all past orders

3.2 The Shopping Journey (Step by Step)

Here's the complete customer journey from browsing to receiving an order:

1. CUSTOMER SENDS "menu"
   └─► Main menu appears with buttons: Shop Now, My Cart, Checkout, Track Order, My Orders

2. CUSTOMER TAPS "Shop Now"
   └─► Catalogue Flow opens with product list
       ├─► Search bar for finding products
       ├─► Product cards with prices
       └─► Cart indicator in footer (shows item count + total)

3. CUSTOMER SELECTS A PRODUCT
   └─► Product Detail screen shows:
       ├─► Product image
       ├─► Name, description, price
       ├─► Variant dropdown (if applicable)
       ├─► Quantity input
       └─► "Add to Cart" button

4. CUSTOMER ADDS TO CART
   └─► Confirmation message with cart summary
   └─► Returns to product list with status message

5. CUSTOMER TAPS CHECKOUT FOOTER
   └─► Checkout Cart screen: select which items to check out

6. CUSTOMER SELECTS ITEMS → PROCEEDS
   └─► Delivery Details screen: name, phone, address, city, state, delivery method

7. CUSTOMER FILLS DELIVERY INFO
   └─► Order Summary screen: items, delivery fee, total, "Complete Order" button

8. CUSTOMER COMPLETES ORDER
   └─► Order is created in the database
   └─► Stock is decremented
   └─► Cart items are cleared
   └─► Order confirmation message sent via WhatsApp

9. IF PAYMENT METHOD IS "CARD":
   └─► Paystack payment link is generated
   └─► Link is shortened
   └─► Customer receives "Pay Now" button (CTA URL)
   └─► Payment is split: vendor receives order total minus Nviti fee

10. CUSTOMER TRACKS ORDER
    └─► Types "order status" or taps "Track My Order"
    └─► Order Status Flow shows current status, items, delivery info
    └─► Status updates (Processing → Shipped → Delivered) are pushed via WhatsApp

4. Cart System

4.1 How the Cart Works

Each WhatsApp visitor has their own cart, identified by their phone number and the vendor's company ID. The cart supports:

  • Add to cart — with quantity and optional variant selection. Max 20 items (configurable).
  • Update quantity — change quantity of existing items; setting qty to 0 removes the item.
  • Remove items — by product ID or cart index.
  • Clear cart — remove all items at once.
  • Variant pricing — cart items can have different prices based on selected variant.
  • Stock validation — checks stock before adding; decrements stock on order creation; restores stock on cancellation.

Cart data is stored in the carts table with fields: visitor_id, product_id, item name, price, qty, total, options (JSON for variant data), source, is_active flag.

4.2 Partial Checkout

The Catalogue Flow supports partial checkout — customers can select specific cart items to check out while leaving others in the cart. This is handled via selected_cart_ids in the checkout data.


5. Checkout Flow

5.1 Checkout State Machine

The checkout process is a state machine with 5 steps:

CART_REVIEW → DELIVERY_INFO → PAYMENT_METHOD → CONFIRMATION → COMPLETE

State is persisted in Visitor.metadata['checkout_state'] as JSON, including:

  • Current step
  • Cart snapshot
  • Accumulated checkout data (name, address, payment method, etc.)
  • Coupon code and discount
  • Selected cart IDs (for partial checkout)

5.2 Totals Calculation

The calculateTotals() method computes:

  • Subtotal — sum of cart item totals
  • Shipping — flat rate from config + weight-based surcharge (100/kg over 10kg)
  • VAT — if applicable
  • Discount — from applied coupon (percentage or flat)
  • Total — subtotal + shipping + VAT - discount

5.3 Coupon Support

Coupons can be applied during checkout:

  • Percentage-based or flat discount
  • Active/inactive toggle
  • Expiry date
  • Usage limit tracking

6. Orders

6.1 Order Creation

When checkout completes, OrderService::createFromCart() runs in a database transaction:

  1. Gets cart items (filtered by selected_cart_ids if partial checkout)
  2. Calculates subtotal, shipping, discount, VAT, total
  3. Creates an Order record with a unique UUID (ORD-XXXXXXXX)
  4. Creates OrdersItem records for each cart item
  5. Decrements product stock (unless unlimited stock)
  6. Deletes purchased cart items
  7. Dispatches OrderCreated event

6.2 Order Identification

Orders have multiple identifiers:

  • UUIDORD-XXXXXXXX format (primary)
  • Visitor Order ID{phone_digits}-{sequence} format (per-visitor sequential)
  • Display Order ID — formatted for customer-facing messages

6.3 Order Status Lifecycle

PENDING → PROCESSING → CONFIRMED → SHIPPED → DELIVERED
                                                    ↓
                                              CANCELLED ← (any pre-shipped status)
                                              REFUNDED
                                              FAILED

Status changes trigger OrderStatusChanged events which send WhatsApp notifications to the customer.

6.4 Order Management (Vendor Side)

Vendors manage orders through the Filament admin panel with:

  • Order list with filtering by status, date, customer
  • Order detail view with items, delivery info, payment status
  • Status update actions
  • Cancel order action (restores stock)

7. Payment Processing

7.1 Supported Payment Methods

Method How It Works
Cash on Delivery No payment link generated. Customer pays on delivery.
Card (Paystack) Payment link generated via Paystack. Customer pays online. Funds split between vendor and Nviti.

7.2 Card Payment Flow (Paystack)

1. Customer selects "Card Payment" during checkout

2. Order is created → OrderCreated event fires

3. SendOrderNotification listener:
   ├─► Calls PaymentService::generateOrderPaymentLink()
   │   ├─► Looks up vendor's Paystack subaccount code
   │   ├─► Calculates Nviti platform fee via NvitiFeeService
   │   ├─► Calls Paystack transaction/initialize API:
   │   │   • amount = order total (in kobo)
   │   │   • subaccount = vendor's subaccount code
   │   │   • transaction_charge = Nviti fee (flat, in kobo)
   │   ├─► Shortens the payment URL
   │   └─► Stores link in Order.metadata
   │
   └─► Sends WhatsApp CTA URL message with "Pay Now" button

4. Customer taps "Pay Now" → Paystack checkout page
   ├─► Customer enters card details
   ├─► Paystack processes payment
   ├─► Funds split: Vendor receives (total - Nviti fee)
   └─► Paystack webhook confirms payment (if configured)

7.3 Nviti Platform Fee

Nviti charges a platform fee on card payments. The fee uses progressive bands:

Order Amount Fee %
₦0 – ₦50,000 1.0%
₦50,001 – ₦100,000 0.85%
₦100,001 – ₦150,000 0.75%
₦150,001 – ₦300,000 0.65%
₦300,001+ 0.5%

All fees are capped at ₦1,500 per order. The fee can be disabled globally via config.

7.4 Paystack Subaccount Management

When a vendor saves bank account details in Shop Settings:

  • A Paystack Subaccount is created (or updated if one exists)
  • The subaccount has percentage_charge = 0 (Nviti uses transaction_charge instead)
  • Subaccount code and ID are stored in Company.metadata
  • Bank list is cached for 24 hours from Paystack's API

8. WhatsApp Notifications

8.1 Order Confirmation

When an order is placed:

  • Card payment: Customer receives an interactive message with a "Pay Now" button linking to Paystack. Falls back to a text message with the payment link if interactive message fails.
  • Cash on delivery: Customer receives a text confirmation with order ID and total.

8.2 Order Status Updates

When an order's status changes, the customer receives a WhatsApp notification:

  • Interactive Flow (preferred): Opens an Order Status Flow with full order details, item list, and drill-down into individual products.
  • Text fallback: Status summary with emoji indicators:
    • 🔄 Processing
    • ✅ Confirmed
    • 🚚 Shipped
    • 📦 Delivered
    • ❌ Cancelled
    • 💰 Refunded

Status notifications include the payment link for card orders that haven't been paid yet.


9. AI Integration

9.1 Structured Output

The commerce module integrates with Nviti's AI layer. When a customer sends a message that the AI determines is a commerce intent, it returns a structured output (CommerceStructuredOutputDTO) containing:

  • The detected intent (browse, search, add to cart, etc.)
  • Extracted data (product ID, quantity, search query)
  • An optional action to execute
  • An optional interactive message payload
  • A confidence score

The StructuredOutputHandler processes this output and either:

  • Executes the action and sends the result
  • Sends an interactive message directly
  • Sends a text response

9.2 Commerce Intents

The AI can detect 16 commerce intents:

Intent Description
BROWSE_CATALOG Browse categories/products
PRODUCT_SEARCH Search for products
PRODUCT_VIEW View product details
CART_ADD Add item to cart
CART_REMOVE Remove item from cart
CART_UPDATE Update cart item quantity
CART_VIEW View cart
CART_CLEAR Clear cart
CHECKOUT_START Start checkout
CHECKOUT_CONTINUE Continue checkout process
ORDER_STATUS Check order status
ORDER_HISTORY View order history
APPLY_COUPON Apply a discount coupon
CATALOGUE_BROWSE Browse the catalogue flow
GENERAL_INQUIRY General commerce question
SUPPORT Customer support request

10. Architecture & Module Structure

app/Modules/Commerce/
├── Builders/
│   └── InteractiveMessageBuilder.php    # WhatsApp message builder (lists, buttons, flows)
├── CommerceActionHandler.php            # Central intent-to-action dispatcher
├── CommerceFacade.php                   # Laravel facade
├── CommerceServiceProvider.php          # Service container bindings
├── Config/
│   └── commerce.php                     # All config: defaults, fees, settings definitions
├── Defaults/
│   └── ShopSettingDefaults.php          # Flattens config into key-value settings
├── DTOs/
│   ├── CartDTO.php                      # Cart data transfer object
│   ├── CartItemDTO.php                  # Cart item DTO
│   ├── CheckoutDataDTO.php              # Checkout form data
│   ├── CommerceStructuredOutputDTO.php  # AI structured output
│   ├── OrderDTO.php                     # Order DTO
│   └── ProductDTO.php                   # Product DTO
├── Enums/
│   ├── CheckoutStep.php                 # CART_REVIEW → COMPLETE
│   ├── CommerceIntent.php               # 16 intent types
│   ├── InteractiveMessageType.php       # LIST, BUTTON, FLOW, etc.
│   ├── OrderStatus.php                  # PENDING → DELIVERED/CANCELLED
│   └── PaymentMethod.php               # CASH, CARD, TRANSFER, WALLET
├── Events/
│   ├── CartAbandoned.php
│   ├── CartCleared.php
│   ├── CartUpdated.php
│   ├── CheckoutStarted.php
│   ├── OrderCreated.php
│   └── OrderStatusChanged.php
├── Exceptions/
│   ├── CartException.php
│   ├── CheckoutException.php
│   └── OrderException.php
├── Handlers/
│   ├── FlowResponseHandler.php          # Handles WhatsApp Flow completions
│   ├── InteractiveMessageHandler.php    # Routes button/text commands
│   └── StructuredOutputHandler.php      # Processes AI commerce intents
├── Jobs/
│   ├── SendCartSummaryWithCheckoutJob.php
│   ├── SendPaymentLinkJob.php
│   └── SyncCommerceFormJob.php
├── Listeners/
│   └── SendOrderNotification.php        # Order event → WhatsApp notification
├── Services/
│   ├── CartService.php                  # Cart CRUD + formatting
│   ├── CatalogService.php               # Products, categories, search
│   ├── CheckoutFlowService.php          # Multi-step checkout state machine
│   ├── CommerceFormService.php          # WhatsApp Flow provisioning
│   ├── NvitiFeeService.php              # Platform fee calculation
│   ├── OrderService.php                 # Order lifecycle management
│   ├── PaymentService.php               # Paystack payment link generation
│   ├── PaystackSubaccountService.php    # Vendor subaccount management
│   ├── ShippingService.php              # Shipping cost calculation
│   └── Contracts/                       # Service interfaces
└── Templates/
    ├── CartFlowTemplate.php             # Cart viewing flow (3 screens)
    ├── CatalogueFlowTemplate.php        # Main shopping flow (6 screens)
    ├── CheckoutFlowTemplate.php         # Standalone checkout flow (3 screens)
    ├── HasSampleProducts.php            # Shared sample data trait
    ├── OrderHistoryFlowTemplate.php     # Order browser flow (2 screens)
    └── OrderStatusFlowTemplate.php      # Single order view flow (2 screens)

11. Data Flow Diagrams

11.1 Message Routing

WhatsApp Message
    │
    ▼
ProcessWhatsappWebhookJob
    │
    ├── Is it a button/list reply?
    │   └── YES → InteractiveMessageHandler → Route by button ID prefix
    │
    ├── Is it a flow completion (nfm_reply)?
    │   └── YES → FlowResponseHandler → Route by flow token prefix
    │
    ├── Is it a recognized text command (menu, cart, etc.)?
    │   └── YES → InteractiveMessageHandler::handleTextCommand() → SHORT-CIRCUIT (no AI)
    │
    └── None of the above?
        └── Pass to AI → StructuredOutputHandler (if commerce intent detected)

11.2 Payment Split Flow

Customer pays ₦100,000 via card
    │
    ▼
Paystack receives ₦100,000
    │
    ├── Vendor subaccount receives ₦99,150 (order total)
    │
    └── Nviti receives ₦850 (0.85% fee, via transaction_charge)

12. Key Technical Notes

  • Currency: Nigerian Naira (NGN) by default. Configurable per vendor.
  • Payment amounts: Paystack API expects amounts in kobo (multiply by 100).
  • URL shortening: Payment links are shortened via yorcreative/laravel-urlshortener before sending to WhatsApp. Falls back to the full URL if shortening fails.
  • Flow provisioning: Commerce WhatsApp Flows are automatically provisioned when a vendor enables order processing. The JSON templates are synced to WhatsApp via the WhatsApp Business API.
  • Multi-tenant: All data is scoped by company_id and tenant_id. Products, orders, carts, and settings are isolated per vendor.
  • Stock management: Stock is decremented atomically during order creation using lockForUpdate. Stock is restored on order cancellation.
  • Checkout timeout: Checkout sessions expire after 30 minutes (configurable).
  • Bank account validation: Account name auto-fill is done via Paystack's account resolution API.
  • Rate limiting: Product lists are paginated (20 items per page in flows, 10 in text-based browsing).

13. Test Coverage

Test File Tests Assertions Coverage
NvitiFeeServiceTest.php 18 45 Fee bands, cap, kobo, breakdown, disabled state
PaystackSubaccountServiceTest.php 20 29 Subaccount CRUD, bank list, caching, error handling
CommerceTextCommandTest.php 34 Text command routing, workflow behavior
InteractiveMessageBuilderTest.php 15 Message building for all types

All commerce tests use Http::fake() to mock external APIs and RefreshDatabase for database isolation.