Promotions - Marketing & Discounts
Domain: Promotions
Location: api/nextango/apps/promotions/
Last Updated: 2026-07-08
Overview
The Promotions app provides two discount mechanisms: customer-entered PromoCodes (standalone or grouped under a PromotionalCampaign) and codeless Promotions evaluated automatically against every cart. Both share the same discount vocabulary (percentage, fixed amount, free shipping, BOGO) and both flow through a shared clamp engine that bounds combined discounts to the cart subtotal. The domain also includes A/B testing (ABTest) and four immutable ledger models backing usage counters and analytics.
Documentation Quick Links
Core Documentation
| File | Description |
|---|---|
| models.md | PromotionalCampaign, PromoCode, ABTest, Promotion, and the ledger models |
| serializers.md | Promotion API serializers and validation |
| views.md | Campaign, promo code, and auto-apply promotion API endpoints |
| services.md | PromotionService, discount calculation, the clamp engine |
| signals.md | Promotion lifecycle signals and Sanity sync |
| examples.md | Usage examples and patterns |
| analytics_service.md | Dashboard analytics: promo performance, campaign lift, auto-promotion performance |
Key Features
Promotional Campaigns
- Campaign management - Create, schedule, and manage marketing campaigns
- Date range control - Start and end dates for campaigns
- Active status - Enable/disable campaigns dynamically
- Target products - Specific products eligible for promotion
- Target stores - Store-specific promotional campaigns
- Campaign metrics - Track usage, revenue impact, conversion rates
Promo Codes
- Discount codes - Alphanumeric promo codes for customers
- Multiple discount types - Percentage, fixed amount, free shipping, BOGO
- Stackable codes - Apply multiple promo codes per transaction
- Usage limits - Per-customer and total usage restrictions
- Date restrictions - Valid date ranges for codes
- Minimum purchase - Minimum order value requirements
Discount Types
- Percentage discount - Percentage off (e.g., 20% off)
- Fixed amount discount - Dollar amount off (e.g., $10 off)
- Free shipping - Waives the transaction's actual shipping cost
- BOGO - Buy X get Y free, calculated per-unit from real cart line items
- BOGO discounted - Buy X get Y at a percentage discount
Targeting & Restrictions
- Product and collection targeting - Scope a discount to specific inventory or collections via
applies_to - Store targeting - Limit to specific store locations via
applicable_stores/target_stores - Date restrictions - Valid date windows for both codes and campaigns
- Usage restrictions - Total and per-customer usage caps via
usage_restrictions - Inventory gate - Auto-apply promotions only fire when the store has sufficient stock
A/B Testing
- Test variants - Compare two variants' conversion rates
- Thread-safe counters - Impressions and conversions increment via atomic
F()expressions - Statistical significance -
Trueonce combined impressions reach 1000
Performance Tracking
- Usage analytics - Promo code performance, campaign summary, campaign lift, auto-promotion performance via
PromotionAnalyticsService - Immutable ledgers - Usage and conversion counters backed by unique-constrained ledger rows
API Endpoints
Every endpoint below is root-level, there is no /promotions/ or /api/ prefix on campaigns, promo-codes, or ab-tests. See Views for the URL grounding.
Campaign Management
| Endpoint | Method | Description |
|---|---|---|
/campaigns/ | GET, POST | List, create campaigns |
/campaigns/{id}/ | GET, PUT, PATCH, DELETE | Individual campaign operations |
/campaigns/analytics/summary/ | GET | Campaign performance summary |
Promo Code Management
| Endpoint | Method | Description |
|---|---|---|
/promo-codes/ | GET, POST | List, create promo codes |
/promo-codes/{code}/ | GET, PUT, PATCH, DELETE | Individual code operations, looked up by code string |
/promo-codes/validate/ | POST | Validate a code for checkout, read-only, does not increment usage |
/promo-codes/analytics/performance/ | GET | Promo code performance summary |
The /promo-codes/validate/ endpoint is public (AllowAny) and does not increment current_usage_count, that increment happens only when the transaction using the code reaches COMPLETED status.
Auto-Apply Promotions
| Endpoint | Method | Description |
|---|---|---|
/promotions/ | GET | List, retrieve active promotions (read-only) |
/promotions/active/ | GET | Active promotions for a store, public, checkout-preview shape |
A/B Testing
| Endpoint | Method | Description |
|---|---|---|
/ab-tests/ | GET, POST | List, create A/B tests |
/ab-tests/{id}/ | GET, PUT, PATCH, DELETE | Individual test operations |
/ab-tests/{id}/record-impression/ | POST | Record a variant impression |
/ab-tests/{id}/record-conversion/ | POST | Record a variant conversion |
Full endpoint documentation: views.md
Architecture Highlights
Two discount mechanisms, one clamp engine
PromoCode (customer-entered) and Promotion (auto-apply, no code) share the same discount_effect and applies_to JSON shapes. Both flow through PromotionService's shared clamp engine, which bounds combined discounts so a code and an auto-apply promotion on the same line item cannot jointly discount that line below zero, and the transaction total cannot exceed the subtotal.
Read more: services.md
Stackable and competing discounts
allow_combination=True promotions stack, applying together against a shared per-unit headroom pool. allow_combination=False promotions compete against each other for a single winning slot, chosen by post-clamp contribution.
Read more: services.md
Service layer architecture
Promotion logic is centralized in PromotionService (checkout validation, discount calculation, the clamp engine) and PromotionAnalyticsService (dashboard aggregates). Neither model classes nor views contain discount calculation logic.
Read more: services.md
Signal-based Sanity sync
Signals sync PromotionalCampaign, PromoCode, ABTest, and Promotion to Sanity CMS in real time on save, with multi-layer loop prevention. No deletion propagates outbound: PromotionalCampaign, PromoCode, and ABTest carry delete handlers that are deliberately disabled (log only), and Promotion has no delete handler at all.
Read more: signals.md
Getting Started
1. Understand the models
Start with models.md to understand PromotionalCampaign, PromoCode, ABTest, Promotion, and the four ledger models that back usage counters and analytics.
2. Review the promotion service
Read services.md for checkout validation, the discount calculators, and the clamp engine that reconciles codes and auto-apply promotions against a cart.
3. Explore the API
Check views.md for the campaign, promo code, auto-apply promotion, and A/B test endpoints, including the correct root-level URL mounts.
4. Study the analytics surface
Review analytics_service.md for promo code performance, campaign summary, campaign lift, and auto-promotion performance.
Related Domains
- Transactions - Discount application to transactions via
recalculate_totals() - Products - Product-specific and collection-specific targeting
- Stores - Store-specific campaigns
- Web Commerce - Storefront checkout promo code flow
- Integrations - Campaign synchronization
Key Architectural Decisions
- Two discount sources, one clamp engine - Codes and auto-apply promotions never jointly over-discount a line
- Stackable vs. competing discounts - Explicit
allow_combinationflag on every discount source - Service layer - Business logic centralized, not in views or models
- Immutable ledgers - Usage and conversion counters are backed by unique-constrained ledger rows, safe to retry from Celery
- A/B testing support - Thread-safe atomic counters via
F()expressions - Store targeting - Localized promotional campaigns and codes
- Usage restrictions - Flexible per-code restriction rules
Common Use Cases
- Percentage sale - Create "20% off" campaign for specific products
- Dollar discount - "$10 off orders over $50" promotion
- BOGO promotion - "Buy one get one free" for select items
- Free shipping - "Free shipping on orders over $25"
- Flash sale - Time-limited promotional campaign
- Auto-apply discount - A codeless promotion that applies automatically at checkout
- A/B testing - Compare 10% vs 15% discount effectiveness
- Campaign analytics - Track promotion lift against a control period
Next Steps
- Start with models.md - Understand the data structure
- Then services.md - Learn discount calculation and the clamp engine
- Review views.md - Explore API endpoints
- Check signals.md - See Sanity sync
- Study serializers.md - Understand validation