Discount Module — Storefront
Storefront surface for the discount plugin — the coupons a product page can advertise. Coupon apply/remove and the cart's browsable coupon list live on the cart routes.
Storefront surface for the discount plugin — the coupons a product page can advertise. Coupon apply/remove and the cart's browsable coupon list live on the cart routes, not here.
Source:
api-modules/discount/src/controllers/store-discount.controller.ts.The discount module is optional. Removing
DiscountModule.forRoot()from the API'sapp.module.tsremoves this route from OpenAPI along with the admin surface, and collapsesDISCOUNT_PORTso the cart stops evaluating coupons entirely.
Conventions
| Auth | Optional. A customer session narrows the result to coupons that customer can still use. |
x-platform header | WEB or APP; anything else reads as WEB. Platform-scoped coupons are filtered against it. |
| Money | Integer subunits (paise). |
GET /store/discounts/applicable — Coupons advertisable on a product
Returns the showOnCart coupons that could apply to one product, for the PDP's "offers on this item" block.
Query
| Param | Type | Notes |
|---|---|---|
productId | string | Required. |
platform | WEB | APP | BOTH | Optional; overrides the x-platform header. |
Response 200 — an array of coupon cards, the same shape GET /store/cart/coupons/eligible returns, so one component renders both surfaces.
{
"data": [
{
"discountId": "01J9...",
"code": "FESTIVE25",
"name": "Q4 Festive",
"discountType": "FIXED",
"value": 25000,
"freeShipping": false,
"individualUse": false,
"requireCustomerLogin": false,
"platform": "BOTH",
"criteriaScope": "CART_SUBTOTAL",
"minAmount": 150000,
"maxAmount": null,
"minQuantity": null,
"minProductCount": null,
"startsAt": null,
"endsAt": "2026-09-30T00:00:00.000Z"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Product does not exist or is soft-deleted |
What is and is not decided here
A product page has no cart, so this endpoint answers "could this coupon ever apply to this item", not "will it apply right now". It applies everything knowable up front and returns the rest as conditions on the card:
- Applied — lifecycle (active, not archived/deleted, inside
startsAt/endsAt), platform, the include/exclude filters across variant, category, brand, tag, ingredient and vendor, sale-item exclusions (excludeSaleItems/excludeSaleItemsOverPercent), exhaustedtotalUsageLimit, exhaustedusageLimitPerCustomerfor a signed-in customer, andcustomerScope. A coupon that fails any of these is never advertised. - Returned as conditions, not verdicts —
minAmount,maxAmount,minQuantityandminProductCount. These measure against a cart total that does not exist yet, so render them as "on orders above ₹1,500" rather than filtering on them.
A coupon matches if any one of the product's variants passes the filters — a coupon scoped to a single SKU still advertises on that SKU's product page.
Ordering — easiest threshold first (minAmount ascending, ties broken on code), so the first tile is the one the shopper is most likely to qualify for. The order is stable across requests.
requireCustomerLogin coupons are returned to anonymous callers, carrying the flag — the PDP can render them behind a "sign in to use" affordance rather than hiding an offer that would convert.
Admin & cart surfaces
- Creating coupons, the
showOnCarttoggle, and every restriction above: Admin → Discounts. - Applying, removing, browsing against a real cart, and auto-apply: Cart — Storefront.
Customer Module — Storefront
HTTP surface for the customer-side address book. The customer identity itself lives in Better-Auth's user table; this module owns the shopping-related customer data on top of that…
Dynamic Link Module — Storefront
HTTP surface for reading dynamic link groups by slug. Dynamic link groups are CMS-style ordered collections of {image, text, url} cards — used for the storefront home grid, promo…