Skip to main content
The subscription system enables flexible, time-based billing for customers with support for multi-phase pricing, recurring and one-time charges, prorated billing, and automatic invoice generation.

Subscription model

Subscriptions are built on a hierarchical model:
Subscription
├── Subscription Phase (time-based period)
│   ├── Subscription Phase Item (product/pricing)
│   │   ├── Price reference
│   │   ├── Quantity setting
│   │   └── Price override (optional)
│   └── Subscription Phase Item
│       └── ...
└── Subscription Phase
    └── ...

Subscriptions

The top-level container represents a customer’s ongoing relationship with your service. Each subscription:
  • Belongs to a specific customer and account
  • Must have at least one subscription phase
  • Derives its start and end dates from its phases
  • Tracks billing cycles through phase transitions

Subscription phases

Time-bounded periods within a subscription that define different billing terms:
  • Start date — when this phase begins
  • End date — when this phase ends (optional for the final phase)
  • Multiple items — each phase can contain multiple subscription items
  • Validation — phases cannot overlap and must be sequential

Subscription phase items

Individual billable items within each phase:
  • Price reference — links to a specific product price
  • Quantity — how many units of this item (default: 1)
  • Price override — optional custom pricing that overrides the base price
  • Billing type — recurring or one-time charges
  • Validation — each phase must have at least one item, and all recurring items must share the same interval

Key features

Datetime precision

The subscription system uses full datetime fields (not just dates) for phase boundaries, enabling:
  • Hourly billing — cloud computing, parking, co-working spaces
  • Daily subscriptions — equipment rentals, temporary access
  • Precise transitions — exact moment when trials end and billing begins
  • No ambiguity — clear timezone-aware datetime boundaries

Automatic validation

Built-in validations prevent billing errors:
  • No overlaps — phases cannot overlap in time
  • No gaps — phases must connect seamlessly (end_date = next start_date)
  • Sequential integrity — phases automatically validated for proper ordering
  • Interval consistency — all recurring items in a phase must have matching intervals
  • Price constraints — overrides must be non-negative ($0 allowed for free trials)

Billing cycle anchors

Billing cycle anchors control when recurring charges occur. See Billing cycles for details.

Creating a subscription

Minimum requirements

To create a subscription, you need:
  1. Account — the business account that owns the subscription
  2. Customer — the customer receiving the subscription
  3. At least one phase with a start date
  4. At least one phase item linking to a price

Basic subscription

# Simple monthly subscription
subscription = Subscription.new(
  account: account,
  customer: customer,
  billing_cycle_anchor: Time.zone.parse("2024-02-01"), # Optional
  proration_behavior: 'create_prorations' # Optional, defaults to 'create_prorations'
)

# Add a single phase
phase = subscription.subscription_phases.build(
  start_date: Time.current,
  end_date: nil # nil means ongoing/no end date
)

# Add items to the phase
phase.subscription_phase_items.build(
  price: monthly_price,
  quantity: 1
)

subscription.save!

Multi-phase subscription

# Create a subscription with trial, promotional, and regular pricing phases
subscription = Subscription.new(
  account: account,
  customer: customer
)

# Phase 1: 14-day free trial
trial_start = Time.current
trial_end_date = trial_start + 14.days
trial_phase = subscription.subscription_phases.build(
  start_date: trial_start,
  end_date: trial_end_date
)
trial_phase.subscription_phase_items.build(
  price: monthly_plan_price,
  quantity: 1,
  overridden_price_amount: 0.00  # Free trial
)

# Phase 2: 3-month promotional pricing (starts when trial ends)
promo_end_date = trial_end_date + 3.months
promo_phase = subscription.subscription_phases.build(
  start_date: trial_end_date,
  end_date: promo_end_date
)
promo_phase.subscription_phase_items.build(
  price: monthly_plan_price,
  quantity: 1,
  overridden_price_amount: 19.99  # Discounted from regular $49.99
)

# Phase 3: Regular pricing (ongoing, starts when promo ends)
regular_phase = subscription.subscription_phases.build(
  start_date: promo_end_date,
  end_date: nil  # Ongoing
)
regular_phase.subscription_phase_items.build(
  price: monthly_plan_price,
  quantity: 1
  # No override - uses the price's standard amount
)

subscription.save!

Validation rules

Subscription level

  • Must have at least one subscription phase
  • Cannot exist without phases at any time

Phase level

  • Must have at least one subscription phase item
  • Start date required on all phases
  • End date required if subsequent phases exist
  • Phases cannot overlap
  • Phases must be sequential without gaps — the end_date of one phase must exactly match the start_date of the next
  • Supports hourly, daily, weekly, monthly, and yearly precision

Phase item level

  • Must reference a valid price
  • Quantity must be a positive integer (minimum 1)
  • Price overrides must be non-negative (can be $0.00 for free trials)
  • All recurring items in a phase must have the same interval type and count

Sequential phases

When creating multi-phase subscriptions, phases must connect seamlessly:
# Correct: phases connect exactly
phase_1 = subscription.subscription_phases.build(
  start_date: Time.zone.parse("2024-01-01 00:00:00"),
  end_date: Time.zone.parse("2024-01-31 23:59:59")
)

phase_2 = subscription.subscription_phases.build(
  start_date: Time.zone.parse("2024-01-31 23:59:59"),
  end_date: Time.zone.parse("2024-02-29 23:59:59")
)
Gaps between phases will fail validation. The end_date of one phase must exactly match the start_date of the next phase, down to the second.

Hourly subscription example

# Co-working space with hourly billing
subscription = Subscription.new(account: account, customer: customer)

# Morning session: 9 AM - 1 PM
morning_phase = subscription.subscription_phases.build(
  start_date: Time.zone.parse("2024-01-15 09:00:00"),
  end_date: Time.zone.parse("2024-01-15 13:00:00")
)
morning_phase.subscription_phase_items.build(
  price: hourly_desk_price,  # $15/hour
  quantity: 4
)

# Afternoon session: 1 PM - 5 PM (connects seamlessly)
afternoon_phase = subscription.subscription_phases.build(
  start_date: Time.zone.parse("2024-01-15 13:00:00"),
  end_date: Time.zone.parse("2024-01-15 17:00:00")
)
afternoon_phase.subscription_phase_items.build(
  price: hourly_desk_price,
  quantity: 4
)

subscription.save!

Interval consistency

All recurring items within a single subscription phase must have consistent billing intervals. This prevents ambiguity in invoice generation and billing calculations.

Valid configurations

# All recurring items have the same interval (monthly, count 1)
phase.subscription_phase_items.build(price: monthly_plan_price, quantity: 1)      # month/1
phase.subscription_phase_items.build(price: monthly_addon_price, quantity: 2)     # month/1
phase.subscription_phase_items.build(price: one_time_setup_fee, quantity: 1)      # non-recurring (allowed)

# All recurring items have quarterly billing (monthly, count 3)
phase.subscription_phase_items.build(price: quarterly_base_price, quantity: 1)    # month/3
phase.subscription_phase_items.build(price: quarterly_support_price, quantity: 1) # month/3

# Different phases can have different intervals
phase_1.subscription_phase_items.build(price: monthly_price, quantity: 1)   # Phase 1: monthly
phase_2.subscription_phase_items.build(price: yearly_price, quantity: 1)    # Phase 2: yearly

Invalid configurations

# Mixed interval types (monthly vs daily) -- will fail validation
phase.subscription_phase_items.build(price: monthly_price, quantity: 1)   # month/1
phase.subscription_phase_items.build(price: daily_price, quantity: 1)     # day/1

# Same interval type but different counts -- will fail validation
phase.subscription_phase_items.build(price: monthly_price, quantity: 1)     # month/1
phase.subscription_phase_items.build(price: quarterly_price, quantity: 1)   # month/3
Non-recurring items are exempt from interval consistency rules. One-time charges can coexist with any recurring interval type within the same phase.

Database structure

subscriptions
├── customer_id (required)
├── account_id (required)
├── billing_cycle_anchor (datetime, optional)
├── proration_behavior (string, default: "create_prorations")
└── sandbox_id (optional)

subscription_phases
├── subscription_id (required)
├── start_date (datetime, required)
├── end_date (datetime, optional)
└── sandbox_id (optional)

subscription_phase_items
├── subscription_phase_id (required)
├── price_id (required)
├── quantity (integer, default: 1, must be >= 1)
├── overridden_price_amount (decimal, optional, must be >= 0)
└── sandbox_id (optional)
start_date and end_date are datetime fields (not date fields), enabling precise time-based billing for hourly and daily subscriptions.

Key methods

MethodDescription
subscription.active_phaseReturns the current phase based on Time.current
subscription.total_amountCalculates the total recurring cost of the active phase
subscription.next_billing_dateDetermines when the next invoice should be generated
phase_item.effective_priceReturns overridden_price_amount or falls back to price.amount
subscription.start_dateReturns the start_date of the first phase
subscription.end_dateReturns the end_date of the active phase

Pricing flexibility

Price overrides

Phase items can override the base price of a product:
  • Use case: promotional pricing, volume discounts, custom contracts, free trials
  • Validation: must be non-negative (>= 0), can be $0.00 for free trials
  • Fallback: uses the product’s base price if no override is set (nil)

Quantity support

Each phase item supports configurable quantities:
  • Use case: per-seat licensing, usage-based billing, bulk purchases
  • Calculation: quantity x (overridden_price_amount || price.amount)

Billing intervals

Phase items inherit billing behavior from their associated prices:
  • Recurring: monthly, weekly, yearly billing cycles
  • One-time: charges that appear only once per phase

Phase transitions

Subscriptions automatically transition between phases based on date ranges:
Phase 1: Trial Period (Day 1-30)
├── Basic Plan -- $0/month (overridden from $29/month)
└── Setup Fee -- $10 (one-time)

Phase 2: Discounted Period (Day 31-365)
├── Basic Plan -- $19/month (overridden from $29/month)
└── Premium Feature Add-on -- $5/month

Phase 3: Regular Pricing (Day 366+)
├── Basic Plan -- $29/month (standard pricing)
└── Premium Feature Add-on -- $5/month
At any given time, the subscription has one active phase — the phase whose date range includes the current time. The active phase determines current billing amounts and drives invoice generation.

Real-world scenarios

Phase 1 (Days 1-30): Free trial
├── Core Platform Access -- $0/month (overridden from $99)
├── Users Included -- 2 seats
└── Support Level -- Basic (email only)

Phase 2 (Days 31-120): Post-trial with onboarding
├── Core Platform Access -- $99/month
├── Users Included -- 5 seats
├── Onboarding Package -- $500 (one-time)
├── Dedicated Success Manager -- $0/month (included for first 3 months)
└── Support Level -- Premium

Phase 3 (Days 121-365): Growth phase
├── Core Platform Access -- $99/month
├── Additional User Seats -- $20/month x 5
├── API Access -- $49/month
└── Support Level -- Premium

Phase 4 (Day 366+): Enterprise
├── Core Platform Access -- $79/month (volume discount)
├── Unlimited Users -- $500/month (flat rate)
├── API Access -- $49/month
├── Custom Integrations -- $200/month
└── Support Level -- Enterprise (24/7)
Phase 1: Fall Semester (Aug 15 - Dec 15)
├── Platform Access -- $49/month
├── Course Materials -- $100 (one-time)
├── Tutoring Hours -- 5 hours/month included
└── Exam Proctoring -- $25 per exam (one-time)

Phase 2: Winter Break (Dec 16 - Jan 14)
├── Platform Access -- $9.99/month (reduced rate)
└── Study Materials Access -- Included

Phase 3: Spring Semester (Jan 15 - May 15)
├── Platform Access -- $49/month
├── Course Materials -- $100 (one-time)
├── Tutoring Hours -- 5 hours/month included
└── Exam Proctoring -- $25 per exam

Phase 4: Summer Session (May 16 - Aug 14)
├── Platform Access -- $29/month (summer rate)
├── Course Materials -- $50 (reduced)
└── Tutoring Hours -- 2 hours/month included
Phase 1 (Months 1-3): Explorer
├── Gym Access -- $79/month
├── Classes Included -- 4/month
└── Personal Training Session -- $80 (one-time welcome session)

Phase 2 (Months 4-6): Dedicated
├── Gym Access -- $69/month (reward for staying)
├── Classes Included -- 6/month
└── Quarterly Fitness Assessment -- $0 (included)

Phase 3 (Months 7-12): Champion
├── Gym Access -- $59/month
├── Classes Included -- 8/month
├── Guest Passes -- 2/month
└── Quarterly Fitness Assessment -- $0 (included)

Phase 4 (Month 13+): Elite
├── Gym Access -- $49/month (best rate)
├── Unlimited Classes -- Included
├── Guest Passes -- 4/month
├── Personal Training Session -- 1/quarter included
└── Premium Locker -- Included
Phase 1: Discovery and planning (Month 1)
├── Discovery Workshop -- $5,000 (one-time)
├── Stakeholder Interviews -- $2,000 (one-time)
├── Strategic Planning -- $150/hour x 40 hours
└── Project Management -- $1,500/month

Phase 2: Implementation (Months 2-4)
├── Development Work -- $175/hour x 160 hours/month
├── Weekly Progress Meetings -- Included
├── Project Management -- $2,500/month
└── Cloud Infrastructure -- $500/month

Phase 3: Launch and stabilization (Month 5)
├── Launch Support -- $10,000 (one-time)
├── On-call Support -- $200/hour (as needed)
├── Performance Optimization -- $5,000 (one-time)
└── Training Sessions -- $1,000 per session x 4

Phase 4: Maintenance and support (Month 6+)
├── Monthly Retainer -- $5,000/month (includes 20 hours)
├── Additional Hours -- $150/hour (beyond retainer)
├── Quarterly Reviews -- Included
└── Priority Support SLA -- Included
Phase 1 (Months 1-3): New subscriber special
├── Monthly Box -- $9.99/month (overridden from $39.99)
├── Shipping -- FREE (overridden from $5.99)
├── Welcome Gift -- $20 value (one-time)
└── Exclusive Member Forum -- Included

Phase 2 (Months 4-12): Regular pricing with loyalty perks
├── Monthly Box -- $39.99/month
├── Shipping -- $5.99/month
├── Quarterly Bonus Item -- $15 value (every 3 months)
└── Member Forum -- Included

Phase 3 (Month 13+): VIP status
├── Monthly Box -- $39.99/month
├── Shipping -- FREE (reward for loyalty)
├── Quarterly Bonus Item -- $25 value
├── Early Access -- New products 48hrs early
└── VIP Concierge Service -- Included
Phase 1: Startup plan (0-10 employees)
├── Platform License -- $299/month
├── Users Included -- Up to 10
├── Data Storage -- 100GB included
├── API Calls -- 10,000/month included
└── Support -- Business hours email

Phase 2: Growth plan (11-50 employees)
├── Platform License -- $999/month
├── Users Included -- Up to 50
├── Data Storage -- 500GB included
├── API Calls -- 100,000/month included
├── Custom Integrations -- 2 included
└── Support -- Priority phone and email

Phase 3: Scale plan (51-200 employees)
├── Platform License -- $2,999/month
├── Users Included -- Up to 200
├── Data Storage -- 2TB included
├── API Calls -- 1,000,000/month included
├── Custom Integrations -- 5 included
├── Advanced Analytics -- Included
└── Support -- 24/7 with 1-hour SLA

Phase 4: Enterprise (201+ employees)
├── Platform License -- Custom negotiated base
├── Users -- Unlimited
├── Data Storage -- Unlimited
├── API Calls -- Unlimited
├── Custom Integrations -- Unlimited
├── Professional Services -- 40 hours/month included
└── Support -- Dedicated team with 15-minute SLA

Best practices

Always use Time.zone.parse() or Time.current for phase boundaries, not Date.parse() or Date.today. Datetime precision prevents ambiguity in phase transitions.
  1. Sequential phases — ensure the end_date of one phase exactly matches the start_date of the next
  2. Clear transition points — define specific datetimes for phase transitions, considering time zones
  3. Consistent intervals — all recurring items within a phase must have the same interval type and count
  4. Value progression — each phase should provide clear value changes to justify pricing changes
  5. Test your phases — validate that phases connect properly and intervals are consistent before deploying to production
  6. Grace periods — consider buffer time between phases for major pricing increases (but ensure no gaps in datetime terms)
  7. Communication — notify customers before phase transitions that affect pricing
  8. Price override validation — price overrides can be $0.00 (free trials) but not negative
  9. Last phase open-ended — the final phase can have end_date: nil for ongoing subscriptions