Skip to main content

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

typevalue fieldCalculation
percentage0 to 100subtotal * value / 100
fixed_amountcentsmin(value, subtotal). Never exceeds cart total.
free_shipping0Sets 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

MethodPathDescription
GET/admin/discountsList all discounts (paginated)
POST/admin/discounts/createCreate a new discount rule
GET/admin/discounts/:idGet a discount with all its codes
PUT/admin/discounts/:id/updateUpdate a discount rule
DELETE/admin/discounts/:id/deleteDelete a discount and all its codes
POST/admin/discounts/:id/codesAdd a promo code to a discount
DELETE/admin/discounts/codes/:id/deleteDelete 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

DiscountCodeInput

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.
<DiscountCodeInput />
With props:
<DiscountCodeInput
  subtotal={cartSubtotal}
  compact={true}
  onApplied={(result) => console.log(result)}
/>
PropTypeDefaultDescription
subtotalnumber0Cart subtotal in cents, used for minimum amount validation
productIdsstring[]Product IDs for product-scoped discount filtering
categoryIdsstring[]Category IDs for category-scoped discount filtering
onApplied(result: ApplyResult) => voidCalled when a valid code is applied
onRemoved() => voidCalled when the applied code is removed
compactbooleanfalseCompact inline layout

CartDiscounts

Displays active discount savings on the cart page, summarizing the discount code and amount deducted.
<CartDiscounts />

DiscountBanner

Promotional banner that surfaces active discount offers to store visitors.
<DiscountBanner />

AutoAppliedSavings

Shows savings that were applied automatically (without a promo code), such as sale prices or tiered discounts.
<AutoAppliedSavings />

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.