Documentation Index
Fetch the complete documentation index at: https://86d.app/docs/llms.txt
Use this file to discover all available pages before exploring further.
The discounts module handles promo code creation, validation, and application. It is standalone: no dependencies on other modules, and no configuration at initialization time. Discount rules and promo codes are managed at runtime through the admin endpoints or the controller API. The checkout module integrates with discounts automatically via structural typing, so promo codes entered in CheckoutSummary flow through without any additional wiring.
Source: modules/discounts · npm: @86d-app/discounts
Installation
npm install @86d-app/discounts
Configuration
The discounts module takes no configuration options. Initialize it with no arguments:
import discounts from "@86d-app/discounts";
import { createModuleClient } from "@86d-app/core";
const client = createModuleClient([discounts()]);
Discount types
type | value field | Calculation |
|---|
percentage | 0 to 100 | subtotal * value / 100 |
fixed_amount | cents | min(value, subtotal). Never exceeds cart total. |
free_shipping | 0 | Sets freeShipping: true in the result; no amount deducted |
Store endpoints
POST /discounts/validate
Validates a promo code and calculates the discount amount without applying it or incrementing any usage counters. Safe to call on every cart update or preview.
Request body:
{
code: string;
subtotal: number; // cart subtotal in cents
productIds?: string[]; // for product-scoped discounts
categoryIds?: string[]; // for category-scoped discounts
}
Response:
{
valid: boolean;
discountAmount: number; // in cents; 0 for free_shipping type
freeShipping: boolean;
error?: string; // present when valid is false
}
Admin endpoints
| Method | Path | Description |
|---|
GET | /admin/discounts | List all discounts (paginated) |
POST | /admin/discounts/create | Create a new discount rule |
GET | /admin/discounts/:id | Get a discount with all its codes |
PUT | /admin/discounts/:id/update | Update a discount rule |
DELETE | /admin/discounts/:id/delete | Delete a discount and all its codes |
POST | /admin/discounts/:id/codes | Add a promo code to a discount |
DELETE | /admin/discounts/codes/:id/delete | Delete a single promo code |
Deleting a discount cascades: all promo codes attached to that discount are removed first, then the discount record is deleted.
Components
Promo code entry field with live validation. Shows an applied state with the discount amount when a valid code is entered, and lets the customer remove the code.
With props:
<DiscountCodeInput
subtotal={cartSubtotal}
compact={true}
onApplied={(result) => console.log(result)}
/>
| Prop | Type | Default | Description |
|---|
subtotal | number | 0 | Cart subtotal in cents, used for minimum amount validation |
productIds | string[] | | Product IDs for product-scoped discount filtering |
categoryIds | string[] | | Category IDs for category-scoped discount filtering |
onApplied | (result: ApplyResult) => void | | Called when a valid code is applied |
onRemoved | () => void | | Called when the applied code is removed |
compact | boolean | false | Compact inline layout |
CartDiscounts
Displays active discount savings on the cart page, summarizing the discount code and amount deducted.
DiscountBanner
Promotional banner that surfaces active discount offers to store visitors.
AutoAppliedSavings
Shows savings that were applied automatically (without a promo code), such as sale prices or tiered discounts.
Types
type DiscountType = "percentage" | "fixed_amount" | "free_shipping";
type DiscountAppliesTo =
| "all"
| "specific_products"
| "specific_categories";
interface Discount {
id: string;
name: string;
description?: string;
type: DiscountType;
value: number; // percentage 0 to 100, cents, or 0 for free_shipping
minimumAmount?: number; // minimum cart subtotal in cents
maximumUses?: number; // null = unlimited
usedCount: number;
isActive: boolean;
startsAt?: Date;
endsAt?: Date;
appliesTo: DiscountAppliesTo;
appliesToIds: string[]; // product or category IDs when scoped
stackable: boolean;
metadata?: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
interface DiscountCode {
id: string;
discountId: string;
code: string; // stored uppercase
usedCount: number;
maximumUses?: number; // null = unlimited
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
interface ApplyResult {
valid: boolean;
discountAmount: number; // in cents; 0 for free_shipping type
freeShipping: boolean;
discount?: Discount;
code?: DiscountCode;
error?: string; // reason when valid is false
}
Notes
Case-insensitive codes. Promo codes are stored and matched as uppercase. SAVE10, save10, and Save10 all resolve to the same code.
validateCode vs. applyCode. Use validateCode for previews and cart-page feedback; it never mutates state. Call applyCode exactly once per order at confirmation time; it increments both the code’s usedCount and the parent discount’s usedCount.
Checkout integration. The checkout module accesses DiscountController through the runtime context via structural typing; no direct import is needed. The CheckoutSummary component handles the promo code form automatically; you do not need to wire up DiscountCodeInput separately on the checkout page.