Low Level Design: Plan Management Service

What Is a Plan Management Service?

A Plan Management Service owns the catalog of subscription plans and the logic for upgrading, downgrading, and switching between them. It is a supporting service to the subscription lifecycle: while the subscription service tracks which plan a customer is on, the plan management service defines what plans exist, what they cost, what features they unlock, and how mid-cycle plan changes are prorated.

Data Model

-- Plan catalog
CREATE TABLE plans (
    plan_id        INT PRIMARY KEY AUTO_INCREMENT,
    code           VARCHAR(50) NOT NULL UNIQUE,   -- e.g. 'pro_monthly'
    display_name   VARCHAR(100) NOT NULL,
    price_cents    INT NOT NULL,
    currency       CHAR(3) NOT NULL DEFAULT 'USD',
    interval       ENUM('monthly','annual') NOT NULL,
    trial_days     INT NOT NULL DEFAULT 0,
    is_public      BOOLEAN NOT NULL DEFAULT TRUE,
    created_at     TIMESTAMP NOT NULL DEFAULT NOW()
);

-- Feature entitlements per plan
CREATE TABLE plan_features (
    plan_id        INT NOT NULL REFERENCES plans(plan_id),
    feature_key    VARCHAR(100) NOT NULL,
    feature_value  VARCHAR(255) NOT NULL,   -- e.g. 'seats=10', 'storage_gb=100'
    PRIMARY KEY (plan_id, feature_key)
);

-- Change history (proration ledger)
CREATE TABLE plan_changes (
    change_id         BIGINT PRIMARY KEY AUTO_INCREMENT,
    subscription_id   BIGINT NOT NULL,
    old_plan_id       INT NOT NULL,
    new_plan_id       INT NOT NULL,
    effective_at      TIMESTAMP NOT NULL,
    proration_cents   INT NOT NULL DEFAULT 0,   -- positive = credit, negative = charge
    reason            ENUM('upgrade','downgrade','admin') NOT NULL,
    created_at        TIMESTAMP NOT NULL DEFAULT NOW()
);

Core Algorithm: Proration

When a customer switches plans mid-cycle, the service must calculate a prorated credit or charge. The standard algorithm:

  1. Compute days remaining in the current period: days_left = (period_end - NOW()) / 86400.
  2. Compute total days in the period: period_days = (period_end - period_start) / 86400.
  3. Credit for unused old plan: credit = old_price_cents * (days_left / period_days).
  4. Charge for new plan remainder: charge = new_price_cents * (days_left / period_days).
  5. Net proration: charge - credit. Positive = bill now. Negative = apply as account credit.

All arithmetic is done in integer cents with explicit rounding rules (round half-up) documented and tested. Never use floating-point for money.

Plan Change Workflow

  • Immediate upgrade: apply new plan now, charge proration immediately.
  • Downgrade at period end: set pending_plan_id on subscription; apply at renewal to avoid prorating a lower price mid-cycle.
  • Admin override: support zero-proration plan moves for support cases, recorded with reason='admin' and an actor user ID.

Failure Handling

Plan changes that include a charge must wrap the proration payment and the subscription update in a distributed saga: charge first, then update the subscription. If the payment fails, the plan change is rolled back and the customer remains on their current plan. Store plan_changes rows with a status column (pending, applied, failed) and process them transactionally.

Scalability Considerations

  • The plan catalog is small and read-heavy. Cache the full catalog in application memory, refreshed every 5 minutes. A single cache miss is acceptable; stampede protection is not needed at this scale.
  • Feature entitlement lookups (what can this user do?) should be served from the cache, not a per-request DB join.
  • Plan changes are infrequent relative to reads. A write to plan_changes plus a subscription update is the hot write path; a single writer per subscription (using optimistic locking on a version column) prevents concurrent change conflicts.

Summary

Plan Management is a catalog plus a proration calculator plus a change ledger. The catalog should be cached aggressively. Proration must be integer arithmetic with explicit rounding. Plan changes that involve money must use a saga pattern to stay consistent across the payment gateway and the subscription database. Separating immediate upgrades from deferred downgrades keeps billing surprises to a minimum.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What is plan management in the context of system design?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Plan management is the subsystem responsible for defining, storing, and enforcing the rules of subscription tiers — including pricing, feature entitlements, usage limits, and trial policies. It acts as the source of truth that the billing engine, access control layer, and customer portal all query to determine what a subscriber is allowed to do.”
}
},
{
“@type”: “Question”,
“name”: “How do you handle plan upgrades and downgrades without losing billing accuracy?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Use proration: when a subscriber changes plans mid-cycle, calculate the unused credit on the old plan and the prorated charge for the new plan based on the exact timestamp of the change. Store a line-item ledger per billing period so every charge can be audited. Apply upgrades immediately and schedule downgrades to take effect at the end of the current billing period to avoid service disruption.”
}
},
{
“@type”: “Question”,
“name”: “How should feature entitlements be enforced at runtime for different plans?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Maintain an entitlement service that maps (account_id, feature_key) -> allowed/limit. On each API request, the application checks the entitlement service (backed by a low-latency cache like Redis) rather than querying the plan database directly. When a plan changes, publish an event that invalidates and refreshes the affected account’s entitlement cache to keep enforcement consistent without stale data.”
}
},
{
“@type”: “Question”,
“name”: “What database schema considerations matter most for plan management?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Store plans as versioned, immutable records: never mutate an existing plan row that active subscriptions reference. Use a plan_versions table linked from the subscriptions table so historical billing can always reconstruct exactly what features and price applied at any past point in time. A separate plan_features junction table lets you add or remove entitlements per plan without schema changes.”
}
}
]
}

See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems

See also: Shopify Interview Guide

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

Scroll to Top