Class: CoreMerchant::Subscription

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Concerns::SubscriptionEventAssociation, Concerns::SubscriptionNotifications, Concerns::SubscriptionStateMachine
Defined in:
lib/core_merchant/subscription.rb

Overview

Represents a subscription in CoreMerchant. This class manages the lifecycle of a customer’s subscription to a specific plan.

**Subscriptions can transition through various statuses**:

  • ‘pending`: Subscription created but not yet started

  • ‘trial`: In a trial period

  • ‘active`: Currently active and paid

  • ‘past_due`: Payment failed but in grace period

  • ‘pending_cancellation`: Will be canceled at period end

  • ‘processing_renewal`: Renewal in progress

  • ‘processing_payment`: Payment processing

  • ‘canceled`: Canceled by user or due to payment failure

  • ‘expired`: Subscription period ended

  • ‘paused`: Temporarily halted, not yet implemented

  • ‘pending_change`: Plan change scheduled for next renewal, not yet implemented

**Key features**:

  • Supports immediate and end-of-period cancellations

  • Allows plan changes, effective immediately or at next renewal

  • Handles subscription pausing and resuming

  • Manages trial periods

  • Supports variable pricing for renewals

Attributes:

  • ‘customer`: Polymorphic association to the customer

  • ‘subscription_plan`: The current plan for this subscription

  • ‘status`: Current status of the subscription (see enum definition)

  • ‘start_date`: When the subscription started

  • ‘end_date`: When the subscription ended (or will end)

  • ‘trial_end_date`: End date of the trial period (if applicable)

  • ‘canceled_at`: When the subscription was canceled

  • ‘current_period_start`: Start of the current billing period

  • ‘current_period_end`: End of the current billing period

  • ‘pause_start_date`: When the subscription was paused

  • ‘pause_end_date`: When the paused subscription will resume

  • ‘current_period_price_cents`: Price for the current period

  • ‘next_renewal_price_cents`: Price for the next renewal (if different from plan)

  • ‘cancellation_reason`: Reason for cancellation (if applicable)

Usage:

```ruby
 subscription = CoreMerchant::Subscription.create(customer: user, subscription_plan: plan, status: :active)
 subscription.start
 subscription.cancel(reason: "Too expensive", at_period_end: true)
 ```

Constant Summary

Constants included from Concerns::SubscriptionEventAssociation

Concerns::SubscriptionEventAssociation::EVENT_TYPES

Constants included from Concerns::SubscriptionStateMachine

Concerns::SubscriptionStateMachine::POSSIBLE_TRANSITIONS

Instance Method Summary collapse

Instance Method Details

#cancel(reason:, at_period_end: true) ⇒ Object

Cancels the subscription. Parameters:

  • ‘reason`: Reason for cancellation

  • ‘at_period_end`: If true, the subscription will be canceled at the end of the current period. Otherwise, the subscription will be canceled immediately. Default is `true`.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/core_merchant/subscription.rb', line 112

def cancel(reason:, at_period_end: true)
  transaction do
    if at_period_end
      transition_to_pending_cancellation!
    else
      transition_to_canceled!
    end
    update!(
      canceled_at: at_period_end ? current_period_end : Time.current,
      cancellation_reason: reason
    )
  end

  notify_subscription_manager(:canceled, reason: reason, immediate: !at_period_end)
  cancellation_events.create!(reason: reason, at_period_end: at_period_end)
end

#days_remaining_in_current_periodObject

Returns the days remaining in the current period. Use to show the user how many days are left before the next renewal or to refund pro-rated amounts for early cancellations.



144
145
146
# File 'lib/core_merchant/subscription.rb', line 144

def days_remaining_in_current_period
  (current_period_end.to_date - Time.current.to_date).to_i
end

#days_remaining_in_grace_periodObject



162
163
164
165
166
# File 'lib/core_merchant/subscription.rb', line 162

def days_remaining_in_grace_period
  return 0 unless due_for_renewal?

  (grace_period_end_date.to_date - Time.current.to_date).to_i
end

#due_for_renewal?Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/core_merchant/subscription.rb', line 172

def due_for_renewal?
  renewable? && current_period_end <= Time.current
end

#expired_or_canceled?Boolean

Returns:

  • (Boolean)


176
177
178
# File 'lib/core_merchant/subscription.rb', line 176

def expired_or_canceled?
  expired? || canceled?
end

#grace_periodObject

Returns the number of days as a grace period for past-due subscriptions. By default, this is 3 days.



150
151
152
# File 'lib/core_merchant/subscription.rb', line 150

def grace_period
  3.days
end

#grace_period_end_dateObject



154
155
156
# File 'lib/core_merchant/subscription.rb', line 154

def grace_period_end_date
  current_period_end + grace_period
end

#grace_period_exceeded?Boolean

Returns:

  • (Boolean)


168
169
170
# File 'lib/core_merchant/subscription.rb', line 168

def grace_period_exceeded?
  due_for_renewal? && Time.current > grace_period_end_date
end

#in_grace_period?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/core_merchant/subscription.rb', line 158

def in_grace_period?
  due_for_renewal? && Time.current <= grace_period_end_date
end

#ongoing?Boolean

Returns:

  • (Boolean)


184
185
186
# File 'lib/core_merchant/subscription.rb', line 184

def ongoing?
  active? || trial? || past_due? || processing_renewal? || processing_payment? || pending_cancellation?
end

#processing?Boolean

Returns:

  • (Boolean)


180
181
182
# File 'lib/core_merchant/subscription.rb', line 180

def processing?
  processing_renewal? || processing_payment?
end

#renewable?Boolean

Returns:

  • (Boolean)


188
189
190
# File 'lib/core_merchant/subscription.rb', line 188

def renewable?
  active? || trial? || past_due? || processing?
end

#startObject

Starts the subscription. Sets the current period start and end dates based on the plan’s duration.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/core_merchant/subscription.rb', line 91

def start
  new_period_start = start_date
  new_period_end = new_period_start + subscription_plan.duration_in_date

  transaction do
    transition_to_active!
    update!(
      current_period_start: new_period_start.to_date,
      current_period_end: new_period_end.to_date
    )
  end

  notify_subscription_manager(:started)
end

#start_new_periodObject

Starts a new period for the subscription. This is called by SubscriptionManager when a subscription renewal is successful.



131
132
133
134
135
136
137
138
139
# File 'lib/core_merchant/subscription.rb', line 131

def start_new_period
  new_period_start = current_period_end
  new_period_end = new_period_start + subscription_plan.duration_in_date

  update!(
    current_period_start: new_period_start.to_date,
    current_period_end: new_period_end.to_date
  )
end