Shop It Docs
Developer Resourcespromotion

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.

Promotions support three cart type eligibility modes ("digital", "physical", "both") and three payment method eligibility modes ("online", "cod", "any"). Eligibility is enforced at coupon apply time and re-validated at order creation for payment method conflicts.

2. Route Ownership and Surfaces

SurfaceRoute prefixOwner
Admin API/api/admin/promotionsPromotionAdminController
Customer API/api/promotionsPromotionCustomerController
Mobile-composed customer API/api/mobile/promotionsMobileModule composition

3. Admin Feature Matrix

CapabilityEndpointAuthZ
List promotionsGET /api/admin/promotionsPromotions_READ
Promotion detailGET /api/admin/promotions/:idPromotions_READ
Create promotionPOST /api/admin/promotionsPromotions_CREATE
Update promotionPATCH /api/admin/promotions/:idPromotions_UPDATE
Delete promotionDELETE /api/admin/promotions/:idPromotions_DELETE
Disable promotionPOST /api/admin/promotions/:id/disablePromotions_UPDATE

4. Customer/Mobile Feature Matrix

CapabilityEndpointRuntime mode
Apply coupon to active cartPOST /api/promotions/apply (without productId)validate + mutate cart
Validate coupon for productPOST /api/promotions/apply (with productId)validate only
Remove coupon from cartPOST /api/promotions/removemutate cart
List active promotionsGET /api/promotionsread
List customer usage historyGET /api/promotions/my-usageread

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_id or order_id)
  • unresolved invalid legacy usage rows are rejected (no silent drift)
  • Partial discount on mixed carts: When cartTypeEligibility is "digital" or "physical" (not "both"), the discount is computed on eligibleSubtotal — the sum of unitSp × quantity for items whose productKind matches the coupon's cart type. Physical items are excluded from digital-coupon discounts and vice versa.
  • minCartValue uses eligible subtotal: The minimum cart value threshold is checked against eligibleSubtotal, not the full cart.totalSp. A mixed cart with total 1000 but only 400 in eligible items fails a minCartValue = 500 coupon.
  • care_package treated as digital: Products with productKind = "care_package" are treated identically to digital products for all eligibility checks.
  • COD coupon requires physical cart: A coupon with paymentMethodEligibility = "cod" is only valid for carts containing only physical products (not "mixed", "digital", or "care_package" carts). Fails with CART_TYPE_MISMATCH_ERROR at apply time.
  • COD payment re-validation at order creation: If a COD coupon is applied but the order is placed with an online payment (or vice versa), the order creation is rejected with ORDER_COUPON_PAYMENT_METHOD_MISMATCH.
  • "both" applies to all cart kinds: Discount is computed on the full cart.totalSp.

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 (productId omitted): coupon is validated and cart totals are updated
  • product validation path (productId provided): 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_LIMIT
  • PROMOTION_ADMIN_RATE_WINDOW_SECONDS
  • PROMOTION_CUSTOMER_OPERATION_RATE_LIMIT
  • PROMOTION_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/jobs for this module.

10. Error UX Mapping

ScenarioAPI behaviorRecommended UI
Promotion not found404 PROMOTION_NOT_FOUNDShow "Coupon not found" toast
Code already exists409 PROMOTION_CODE_EXISTSShow "Code already exists" error
Not applicable to cart400 PROMOTION_NOT_APPLICABLEShow "Coupon not valid for this cart"
Invalid date range400 PROMOTION_INVALID_DATE_RANGEShow "Invalid coupon dates"
Invalid discount400 PROMOTION_DISCOUNT_INVALIDShow discount validation error
Invalid scope400 PROMOTION_SCOPE_INVALIDShow "Invalid coupon targets"
Usage conflict400 PROMOTION_USAGE_CONFLICTShow "Cannot modify used coupon"
Payment method mismatch at order creation400 ORDER_COUPON_PAYMENT_METHOD_MISMATCHShow "Coupon not valid for selected payment method"
Rate limit exceeded429 RATE_LIMIT_EXCEEDEDShow "Please wait" with backoff

11. Data Flow

12. Release/QA Checklist

  • Admin CRUD/disable routes enforce Promotions_* permissions.
  • /promotions/apply supports both cart-apply and product-validate modes.
  • Scope target invariant (product/category/tag) allows exactly one target per row.
  • Usage target invariant (cart_id or order_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.
  • Physical coupon applied to physical cart → discount applied correctly
  • Digital coupon applied to mixed cart → partial discount (digital items only)
  • Physical coupon applied to digital cart → rejected (CART_TYPE_MISMATCH_ERROR)
  • COD coupon applied to digital or mixed cart → rejected at apply time (CART_TYPE_MISMATCH_ERROR)
  • COD coupon + COD payment method at order creation → passes
  • Online coupon + COD payment at order creation → rejected (ORDER_COUPON_PAYMENT_METHOD_MISMATCH)
  • "both" coupon on any non-empty cart → discount on full cart.totalSp
  • "any" coupon with any payment method → passes
  • minCartValue checked against eligibleSubtotal (not cart.totalSp) for non-"both" coupons

13. Integration Flows

13.1 Apply Coupon Flow

A customer applies a coupon code to their cart or validates it for a product.

  1. Customer calls POST /api/promotions/apply (or POST /api/mobile/promotions/apply) with { code: "SAVE20" }.
  2. System normalizes code: trim() + uppercase() to "SAVE20".
  3. System looks up promotion by normalized code.
  4. System validates promotion exists, is active, and current date is within startDate and endDate.
  5. System validates discount constraints: min purchase amount, max discount cap.
  6. If productId provided: return validation payload only, do not mutate cart.
  7. If no productId: system validates cart contents: isCartTypeEligible(cart, promotion.cartTypeEligibility). If the cart kind does not qualify for the coupon's cart type requirement, return CART_TYPE_MISMATCH_ERROR. Then check payment method: isPaymentMethodEligible(cart.cartKind, promotion.paymentMethodEligibility). If mismatch, return CART_TYPE_MISMATCH_ERROR.
  8. System evaluates scope targeting: checks if cart products match any promotion scope (product, category, or tag).
  9. If applicable: system updates cart's appliedPromotionId, appliedPromotionCode, appliedDiscount.
  10. System invalidates cart cache cart:user:{userId} and promotion code cache.
  11. 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.

  1. Admin calls POST /api/admin/promotions with promotion details.
  2. System validates: code uniqueness (returns 409 if duplicate), date range, discount value.
  3. System creates promotion record with status active (or schedules activation).
  4. For each scope target (product, category, or tag), system inserts promotion_scope row with exactly one target.
  5. System caches promotion under promotions:admin:detail:{id} and promotions:code:{normalizedCode}.
  6. Admin can later call POST /api/admin/promotions/:id/disable to deactivate the promotion.
  7. Active promotions appear in customer GET /api/promotions list.
  8. Customer usage creates promotion_usage rows linking to cart_id or order_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