E-Commerce
The platform includes a full e-commerce system with products, cart, checkout, orders, coupons, inventory management, and Stripe payment processing. All e-commerce data is managed from the Admin section.
Products
Products are managed from Admin > Products. Each product has a type, pricing, optional variants, inventory tracking, and category assignments.
Product Types
Every product has a type that determines its behavior at checkout:
| Type | Shipping Address | Shipping Cost | Tax |
|---|
physical | Required | Yes | Yes |
digital | Not required | No | Yes |
service | Not required | No | Yes |
Pricing
- Base price - The default price for simple (non-variant) products.
- Compare-at price - Optional original price displayed alongside the sale price.
- Zero state - How $0 products display:
hidden (no price shown), zero (shows $0.00), or free (shows "Free"). Free products skip the payment step at checkout.
Variants
Products support up to 3 variant types (e.g., Size, Color, Material). The system automatically generates all possible combinations.
Variant Types
Each variant type defines:
- Name - The variant category (e.g., "Size").
- Layout - How options display: dropdown, buttons, or color swatches.
- Options - The available choices (e.g., "Small", "Medium", "Large"), each with an optional sub-SKU, hex color, swatch image, and size chart measurements.
Variant Combinations
Auto-generated from the variant types. Each combination can have:
- Price - An absolute price that overrides the base price entirely.
- Price modifier - An adjustment added to the base price (e.g., +$5.00 or -$2.50).
- Active toggle - Disable specific combinations (e.g., "XL in Red" out of stock).
- Per-variant stock - Each combination tracks its own inventory independently.
- SKU - Auto-generated from the base SKU + option sub-SKUs (e.g., SHIRT-LG-RED).
Inventory
Stock tracking is configurable per product:
| Mode | Behavior |
|---|
track | Enforces stock limits. Items can't be purchased when stock reaches zero. |
unlimited | No stock limits. Skips all inventory checks. |
backorder | Allows overselling. Customers can purchase even when stock is negative. |
Additional inventory settings:
- Stock display - What customers see:
exact (shows numbers), approximate ("In stock", "Last one left!"), or none (hidden). - Low stock alert - A threshold that triggers an admin email notification when stock drops below it.
- Cart reservations - When a customer adds an item to their cart, the quantity is reserved for 30 minutes to prevent overselling.
Purchase Limits
Up to 8 limit rules per product, each defining a restriction on purchasing behavior. Useful for limited-edition drops, preventing abuse, or compliance requirements.
Each rule has:
- Threshold - A max or min value (e.g., "max 2").
- Scope - Per-user (across all their orders) or per-order (within a single checkout).
- Period - Time window: forever, day, week, month, quarter, or year. Can be rolling (last 30 days) or calendar-based (this month).
Examples: "Max 2 per user per month", "Max 5 per order", "Min $10 per order."
Product Flags
- Available - Override availability regardless of inventory mode.
- Subscription - Enable Stripe subscription checkout. Requires a variant with a billing interval (month/year).
- Direct link only - Hide from listings and search results. Only accessible via direct URL.
Categories, Tags & Images
- Categories - Assign to one or more categories. The first category determines the product URL.
- Tags - Free-form labels for additional filtering.
- Images - A primary image and additional gallery images. Variant options can have their own images.
Status Workflow
- Draft - Being prepared. Not visible to customers, cannot be purchased.
- Live - Published and available for purchase.
- Archived - Hidden from the storefront. Existing orders referencing it are unaffected.
Orders
Orders are created when customers complete checkout and are managed from Admin > Orders. Each order is an immutable snapshot of the purchase - prices, customer info, and product data are frozen at the time of creation.
Order Contents
Every order includes:
- Order number - A human-readable identifier for customer communication.
- Line items - Each item records the product, variant, SKU, quantity, unit price, and a full product snapshot.
- Customer snapshot - Name, email, and phone captured at checkout time.
- Addresses - Shipping and billing addresses (billing can be same as shipping).
Order Financials
| Field | Description |
|---|
| Subtotal | Sum of all line item totals before adjustments. |
| Discount | Coupon discount applied (if any). |
| Account balance | Amount paid from the customer's account balance (if applicable). |
| Tax | Tax calculated based on the shipping address and configured tax provider. |
| Shipping | Shipping cost (only for orders with physical products). |
| Total | Final amount: subtotal - discount - account balance + tax + shipping. |
All financial totals are calculated server-side at order creation to prevent tampering. Prices, customer info, and product data are frozen at the time of purchase.
Payment
Payment is processed through Stripe. Each order tracks:
- Payment status - pending, paid, failed, or refunded.
- Payment method - Card type, last 4 digits, and brand (Visa, Mastercard, etc.).
- Stripe IDs - PaymentIntent ID, Customer ID, and Subscription ID (for recurring orders).
- Failure reason - If payment fails, the error message is captured for admin review.
Free orders ($0 total) skip payment entirely and are marked as completed immediately.
Order Status Lifecycle
| Status | Description |
|---|
pending | Order created, awaiting payment confirmation. |
confirmed | Payment successful. Ready to fulfill. |
shipped | Order shipped. Tracking info available. |
delivered | Order delivered to customer. |
cancelled | Order cancelled. Inventory released, payment refunded if applicable. |
refunded | Payment refunded via Stripe. |
Tracking
For shipped orders, admins can add:
- Tracking number - The carrier tracking code.
- Carrier - UPS, FedEx, USPS, DHL, Amazon, or Other.
- Shipped date - When the order was shipped.
- Estimated delivery - Expected delivery date.
Customers can view their order status and tracking information from their dashboard.
Admin Notes
A rich-text notes field is available for internal admin communication. Notes are not visible to customers.
High-Concurrency Mode
For flash sales or high-traffic scenarios, the platform supports an order queue that processes orders asynchronously. This prevents timeouts and overselling during traffic spikes (100+ orders/second). The queue is configurable in Site Config:
auto - Activates automatically when traffic exceeds 10 orders/2 minutes or 20 orders/5 minutes.on - Always queue orders (useful before an anticipated sale).off - Process orders synchronously (normal operation).
When queued, customers see a "processing" status and the order confirms within 5-30 seconds.
Coupons
Coupons are managed from Admin > Coupons. They support percentage discounts, fixed-amount discounts, and guest site access grants.
Coupon Types
| Type | Discount | Access | Use Case |
|---|
discount_percentage | % of subtotal | No | 20% off sale |
discount_fixed | Fixed amount | No | $10 off coupon |
guest_access | None | Yes | Invite-only site access code |
guest_access_discount | % of subtotal | Yes | VIP access + discount |
Discount Rules
- Value - The discount amount. For percentage types, this is the percent (e.g., 20 = 20%). For fixed types, this is the dollar amount.
- Min order amount - Minimum cart subtotal required to apply the coupon. The coupon is rejected if the subtotal is below this threshold.
- Max discount amount - Caps the discount for percentage coupons. For example, "20% off, max $50 discount." Ignored for fixed-amount coupons.
- The discount can never exceed the cart subtotal (orders can't go negative).
Usage Controls
- Usage limit - Maximum number of times the coupon can be used across all customers. Leave empty for unlimited uses.
- Usage count - Tracks how many times the coupon has been redeemed (read-only).
- Exhausted - Automatically set when usage reaches the limit. The coupon stops working.
Scheduling
- Starts at - Optional activation date. The coupon is invalid before this time.
- Expires at - Optional expiration date. The coupon is invalid after this time.
- If neither is set, the coupon is valid immediately and indefinitely (until manually deactivated or exhausted).
Guest Access Coupons
When the site is password-protected (via Site Config > Security), guest access coupons allow specific people to bypass the gate. The coupon code doubles as the access code. Combine with a discount to offer VIP pricing to invited guests.
Applying Coupons
Customers enter a coupon code during checkout. The system validates the code in real time and displays the discount before the order is placed.
Only one coupon can be applied per order.
Checkout Flow
The checkout process adapts dynamically based on what's in the cart.
Standard Flow (4 Steps)
- Cart Review - Review items, adjust quantities, apply coupons.
- Details - Customer info, shipping address (for physical products), and billing address.
- Payment - Stripe payment form, account balance application, and real-time tax calculation.
- Review & Place - Final summary and order placement.
Condensed Flow
When condensed checkout is enabled in Site Config, steps are dynamically reduced based on cart contents:
- Digital/service + free - Skips to Review only (1 step).
- Physical + free - Details + Review (2 steps, needs shipping address).
- Digital/service + paid - Payment + Review (2 steps).
- Physical + paid - Details + Payment + Review (3 steps).
Cart Behavior
- 30-minute expiration - Carts expire after 30 minutes of inactivity. Each action (add, remove, update, apply coupon) resets the timer.
- Auto-validation - When a cart is loaded, the system checks for price changes, stock issues, and unavailable items. Customers are notified of any changes.
- Subscription rules - Subscription and one-time products can't be mixed in the same cart. Only one subscription product is allowed per cart, with quantity locked to 1.
- Cart reactivation - Logged-in users can recover abandoned carts. Out-of-stock items are automatically removed.
Order approval workflow (MOAS)
For organizations that need procurement sign-off before an order is settled, the platform supports role-based approval routing. Currently the approval flow gates expense orders only (Pay-by-Invoice). Card orders charge immediately regardless of role topology — card-with-approval is a v2 follow-up.
- Set up approval relationships on roles — open a role (e.g.
manager), open the MOAS object, and toggle "Can approve orders" = ON. The "Approver for" field appears below — add buyer role slugs (e.g. ['junior-buyer']). Now every expense order placed by a junior buyer auto-routes to managers. - Notification target — if the approver role has an
email set (shared inbox), the approval-request email goes there. Otherwise it broadcasts to all individual users holding the role. Buyers can never approve their own orders, even if they hold a matching role. - Approver acts at /dashboard/approvals — the queue lists all pending-approval orders the current user can decide. Approve or reject with optional notes. First-action-wins: the first approver settles the order; remaining notifications become "already decided" stale messages.
- Super fallback — if no custom role's "Approver for" matches the buyer's roles, the order is routed to supers as a fallback (never silently bypasses MOAS). The approvals page surfaces a yellow warning so admins know to wire up a real approver role for that buyer.
- What happens after approval — expense orders: invoice email goes out to the billing contact (with terms). Order status flips from
pending_approval to confirmed — Net-30 semantics: the order is green-lit for fulfillment now and AP settles the invoice independently within the stated terms. The invoice stays in "Awaiting payment" until someone marks it paid (see Settling an invoice below). - Rejection — any rejection is terminal. Status flips to
rejected, buyer is notified with the approver's notes, no charges made. Decisions are immutable; if the rejection was a mistake, the buyer re-submits a new order. - Single-tier in v1 — one round of approval. Multi-tier escalation ("Manager THEN VP") is a v2 follow-up. For now, if you need two approvers, list both as "Approver for" on a single tier (any-1-of any approver settles).
Pay by Invoice (expense orders)
For enterprise buyers, the checkout supports an alternate payment method that bypasses Stripe entirely. The buyer picks "Pay by Invoice" at the Payment step; the order is created with payment.method = 'invoice' and an HTML invoice email is sent to the billing contact (with optional cc to a central AP inbox). Payment is settled out-of-band; an admin marks the invoice paid in DataManager.
- Enable at the site level — Site Config > Checkout > "Allow expense orders". Off by default.
- Grant per-role — assign a custom role with MOAS > Can expense orders = ON to the buyers who can use this payment method. Built-in visitor/user roles don't get it by default. SSO group claims can map directly to roles, so this scales cleanly for enterprise tenants.
- Invoice terms — configure default terms (e.g. "Net 30", "Due upon receipt") in Site Config > Checkout > Invoice payment terms. Free-text, printed on the invoice email.
- Optional AP cc — Site Config > Checkout > Invoice CC email. Every outgoing invoice also goes to this address (handy for a shared AP inbox).
- Settling an invoice — anyone with order-update permission (not just SUPER) can mark the invoice paid. From the Orders table, click the green dollar-sign icon on the row to mark it paid as of today. For retroactive dates, payment method (check / cash / wire / ACH), or notes, open the order in DataManager and edit the payment details directly. The same action is also exposed as
POST /api/ecommerce/orders/:id/mark-paid for external systems. We deliberately don't auto-flip from a webhook — Stripe doesn't see checks or wires, so a human or upstream system has to record the settlement.
Expense orders skip Stripe entirely — no PaymentIntent, no receipt_email routed by Stripe. Your own confirmation email still goes to the buyer; the invoice email is the AP-facing artifact.
B2B fields: separate billing/shipping contacts + Company / PO
Enterprise B2B orders routinely involve three different people: a buyer (places the order), a recipient (gets the package), and an AP contact (pays / gets the receipt). The checkout supports all three:
- Order Contact - The buyer. Always required. Where order confirmation emails go and the default for shipping/billing unless overridden.
- Ship To override - Unchecking "Ship to me at this address" reveals fields for a different recipient name + phone. Used on the shipping label.
- Bill To override - Unchecking "Bill to me at the contact info above" (only available when the billing address differs from shipping) reveals fields for a separate billing contact (name, email, phone). Stripe's machine receipt is routed to this email when overridden; our system order-confirmation email still goes to the order contact.
- Company Name - Optional B2B reference. Surfaced in the order, on the receipt, and in Stripe metadata. Off by default — enable in Site Config > Checkout.
- PO Number - Optional reference string for AP reconciliation. Stored on the order and printed on receipts; it does not change the payment flow (billing info / card collection still happens normally). Off by default — enable in Site Config > Checkout.
Admins can search and filter orders by Company Name and PO Number from the Orders table in Admin > Data > Orders.
Shipping & Tax
Shipping and tax are configured in Site Config and calculated automatically at checkout. See the Site Config docs for detailed configuration options.
Shipping
- Only applies to physical products. Digital and service products have zero shipping cost.
- Flat-rate pricing with separate domestic and international rates.
- Optional free shipping threshold (e.g., free shipping over $75).
- Country restrictions configurable per domestic and international lists.
Tax
- Two built-in providers:
stripe-tax (default) and static (approximate state averages, demo / fallback only). - Calculated based on the shipping address (or billing address for digital products).
- Optional fallback to static rates if the configured provider fails.
- Business type setting (physical goods, digital services, or mixed) affects tax categorization.
- Other providers (Avalara, TaxJar, etc.) are NOT built into the template. Wire them up via the recipe in
docs/integrations/AVALARA.md.