Promotion Module Feature List
Promotion admin/customer features, eligibility model, state behavior, and cache/rate-limit contracts.
Promotion Module - Feature List
1. Feature Overview
Promotion module manages coupon lifecycle and customer coupon operations for physical-order commerce flows.
Eligibility is intentionally constrained to:
cartTypeEligibility = physicalpaymentMethodEligibility = online
2. Route Ownership and Surfaces
| Surface | Route prefix | Owner |
|---|---|---|
| Admin API | /api/admin/promotions | PromotionAdminController |
| Customer API | /api/promotions | PromotionCustomerController |
| Mobile-composed customer API | /api/mobile/promotions | MobileModule composition |
3. Admin Feature Matrix
| Capability | Endpoint | AuthZ |
|---|---|---|
| List promotions | GET /api/admin/promotions | Promotions_READ |
| Promotion detail | GET /api/admin/promotions/:id | Promotions_READ |
| Create promotion | POST /api/admin/promotions | Promotions_CREATE |
| Update promotion | PATCH /api/admin/promotions/:id | Promotions_UPDATE |
| Delete promotion | DELETE /api/admin/promotions/:id | Promotions_DELETE |
| Disable promotion | POST /api/admin/promotions/:id/disable | Promotions_UPDATE |
4. Customer/Mobile Feature Matrix
| Capability | Endpoint | Runtime mode |
|---|---|---|
| Apply coupon to active cart | POST /api/promotions/apply (without productId) | validate + mutate cart |
| Validate coupon for product | POST /api/promotions/apply (with productId) | validate only |
| Remove coupon from cart | POST /api/promotions/remove | mutate cart |
| List active promotions | GET /api/promotions | read |
| List customer usage history | GET /api/promotions/my-usage | read |
Mobile equivalents are available under /api/mobile/promotions/....
5. Key Business Rules
- coupon input is normalized (
trim + uppercase) before lookup - promotions enforce date-window validity and discount constraints
- scope targeting uses typed nullable FKs (
product_id,category_id,tag_id) with exactly one target per scope row - usage rows require at least one redemption anchor (
cart_idororder_id) - unresolved invalid legacy usage rows are rejected (no silent drift)
6. State Models
6.1 Promotion lifecycle state
6.2 Coupon operation state
6.3 Apply coupon paths
POST /promotions/apply has two valid paths:
- cart path (
productIdomitted): coupon is validated and cart totals are updated - product validation path (
productIdprovided): returns validation payload and does not mutate cart
7. Caching Strategy
Keyspaces:
promotions:admin:list:promotions:admin:detail:promotions:customer:list:promotions:code:
Behavior:
- deterministic key construction via
CacheKeyUtil.build(...) - prefix invalidation after admin mutations (
invalidatePattern(prefix*)) - customer reads reuse cached code + active-list payloads
8. Rate Limiting
Promotion rate-limit envs:
PROMOTION_ADMIN_RATE_LIMITPROMOTION_ADMIN_RATE_WINDOW_SECONDSPROMOTION_CUSTOMER_OPERATION_RATE_LIMITPROMOTION_CUSTOMER_RATE_WINDOW_SECONDS
Runtime behavior:
- Redis sorted-set sliding windows per action + actor key
- UUIDv7 request markers in rate-limit windows
- threshold breach throws
RateLimitExceededException(RATE_LIMIT_EXCEEDED)
9. Queue/Worker Features
Promotion module does not own BullMQ queues or background workers.
Operational notes:
- Coupon validation and application happen synchronously in request flow.
- There is no promotion-specific async job contract in
packages/jobsfor this module.
10. Error UX Mapping
| Scenario | API behavior | Recommended UI |
|---|---|---|
| Promotion not found | 404 PROMOTION_NOT_FOUND | Show "Coupon not found" toast |
| Code already exists | 409 PROMOTION_CODE_EXISTS | Show "Code already exists" error |
| Not applicable to cart | 400 PROMOTION_NOT_APPLICABLE | Show "Coupon not valid for this cart" |
| Invalid date range | 400 PROMOTION_INVALID_DATE_RANGE | Show "Invalid coupon dates" |
| Invalid discount | 400 PROMOTION_DISCOUNT_INVALID | Show discount validation error |
| Invalid scope | 400 PROMOTION_SCOPE_INVALID | Show "Invalid coupon targets" |
| Usage conflict | 400 PROMOTION_USAGE_CONFLICT | Show "Cannot modify used coupon" |
| Rate limit exceeded | 429 RATE_LIMIT_EXCEEDED | Show "Please wait" with backoff |
11. Data Flow
12. Release/QA Checklist
- Admin CRUD/disable routes enforce
Promotions_*permissions. -
/promotions/applysupports both cart-apply and product-validate modes. - Scope target invariant (
product/category/tag) allows exactly one target per row. - Usage target invariant (
cart_idororder_id) is enforced. - Promotion code uniqueness conflicts return
PROMOTION_CODE_EXISTS. - Admin write paths invalidate admin/detail/code cache prefixes.
- Customer/admin operation limits enforce configured window thresholds.
13. Integration Flows
13.1 Apply Coupon Flow
A customer applies a coupon code to their cart or validates it for a product.
- Customer calls
POST /api/promotions/apply(orPOST /api/mobile/promotions/apply) with{ code: "SAVE20" }. - System normalizes code:
trim() + uppercase()to "SAVE20". - System looks up promotion by normalized code.
- System validates promotion exists, is active, and current date is within
startDateandendDate. - System validates discount constraints: min purchase amount, max discount cap.
- If
productIdprovided: return validation payload only, do not mutate cart. - If no
productId: system validates cart contents are eligible (cartTypeEligibility = physical,paymentMethodEligibility = online). - System evaluates scope targeting: checks if cart products match any promotion scope (product, category, or tag).
- If applicable: system updates cart's
appliedPromotionId,appliedPromotionCode,appliedDiscount. - System invalidates cart cache
cart:user:{userId}and promotion code cache. - Customer fetches updated cart to see discounted total.
13.2 Admin Create Promotion Flow
An admin creates a new promotion coupon with targeting and constraints.
- Admin calls
POST /api/admin/promotionswith promotion details. - System validates: code uniqueness (returns 409 if duplicate), date range, discount value.
- System creates promotion record with status
active(or schedules activation). - For each scope target (product, category, or tag), system inserts
promotion_scoperow with exactly one target. - System caches promotion under
promotions:admin:detail:{id}andpromotions:code:{normalizedCode}. - Admin can later call
POST /api/admin/promotions/:id/disableto deactivate the promotion. - Active promotions appear in customer
GET /api/promotionslist. - Customer usage creates
promotion_usagerows linking tocart_idororder_id.
Time fields in this module are stored as timezone-aware values and should be handled as ISO-8601 instants by API consumers.
See Also
- API Reference: See Promotion - API & Integration Guide Section 7 (Endpoint Reference + Payload Cheatsheet) for complete request/response DTOs.
- Backend Reference: See Promotion - Backend Documentation Section 3 (Data Model) for schema details.