Appearance
SystemSubscriptionDiscountManager
Manages admin-granted subscription discounts with lifecycle tracking, separate from reusable promo codes.
Purpose
SystemSubscriptionDiscountManager handles one-off discounts that admins grant to specific subscriptions. Unlike promo codes (which are reusable codes users enter), system discounts are applied directly to a subscription by an admin with a reason, and tracked independently with their own lifecycle.
Each subscription can have at most one active discount at a time. Discounts track how many billing cycles they have been applied to, and can be configured with a maxCycles limit after which they automatically transition to "exhausted" status. Admins can also manually cancel discounts with a reason for audit purposes.
The manager supports bulk lookup for the renewal flow, allowing efficient retrieval of active discounts across multiple subscriptions in a single query.
Error Codes (SystemDiscountError)
| Error | Condition |
|---|---|
INVALID_USER_ID | Empty or missing user ID |
INVALID_SUBSCRIPTION_ID | Empty or missing subscription ID |
INVALID_DISCOUNT_TYPE | Not "percentage" or "dollar_off" |
INVALID_DISCOUNT_VALUE | Non-positive, NaN, infinite, or percentage > 1 |
INVALID_MAX_CYCLES | Less than 1 or not an integer |
INVALID_REASON | Empty or missing reason |
INVALID_GRANTED_BY | Empty or missing admin identifier |
SUBSCRIPTION_ALREADY_HAS_ACTIVE_DISCOUNT | Subscription already has an active discount |
DISCOUNT_NOT_FOUND | Discount ID does not exist |
DISCOUNT_ALREADY_CANCELLED | Attempting to cancel/increment an already cancelled discount |
DISCOUNT_ALREADY_EXHAUSTED | Attempting to cancel/increment an exhausted discount |
CANCEL_RACE_CONDITION | Concurrent cancel: discount was already modified |
INCREMENT_RACE_CONDITION | Concurrent increment: discount was already modified |
State & Storage
In-Memory State
None. This is a stateless manager.
SQLite Tables
admin_subscription_discounts
| Column | Type | Purpose |
|---|---|---|
id | INTEGER PK | Auto-increment ID |
user_id | TEXT | Target user ID |
subscription_id | TEXT | Target subscription ID |
discount_type | TEXT | "percentage" or "dollar_off" |
discount_value | TEXT | Discount amount |
max_cycles | INTEGER NULL | Max billing cycles; NULL for unlimited |
cycles_applied | INTEGER | Number of billing cycles discount has been applied (default 0) |
status | TEXT | "active", "cancelled", or "exhausted" |
reason | TEXT | Admin-provided reason for granting |
granted_by | TEXT | Admin identifier who granted |
granted_at | INTEGER | Unix timestamp of grant |
cancelled_by | TEXT NULL | Admin who cancelled |
cancelled_at | INTEGER NULL | Unix timestamp of cancellation |
cancel_reason | TEXT NULL | Reason for cancellation |
last_applied_at | INTEGER NULL | Timestamp of last cycle increment |
Interactions
Depends On
BaseDurableManager(base class)- SQLite storage via
this.durable.state.storage.sql
Depended By
- PromoCodeRegistry DO -- exposes all methods as RPC endpoints (
grantSystemDiscount,cancelSystemDiscount, etc.) - Admin endpoints -- grant, cancel, list operations
- Subscription renewal flow --
getBulkActiveDiscountsandincrementCyclesAppliedduring billing
Edge Cases & Error Handling
- One active discount per subscription enforced by checking for existing active discount before granting
- Race conditions on cancel and increment handled via
WHERE status = 'active'guard withrowsWrittencheck incrementCyclesAppliedis NOT idempotent -- caller must deduplicate per billing cycle- Percentage discount values must be <= 1.0 (e.g.,
"0.5"for 50% off) - Bulk lookup returns empty map for empty input array (short-circuits)
- Page size capped at 100 to prevent excessive query results
- All timestamps are Unix seconds via
Math.floor(Date.now() / 1000)
See Also
- PromoCodeStorageManager -- storage for reusable promo codes
- PromoCodeValidationManager -- validation for reusable promo codes
- Parent DO:
PromoCodeRegistry