core_merchant 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bd7b45750d9491ddf78c12b41e73a1d79d8bc6fa5cdff69df22f536b1765e42
4
- data.tar.gz: 056e3021e60c7c5edb4a301dae4a29b6184ad3889b55c81e226dccfcfcb00020
3
+ metadata.gz: ff6bdd381d58ecdb8153a14ceff12482d22b55b70a738de1e46df9480f3ab472
4
+ data.tar.gz: a87c17013865cdbe94f70daf969624f494135955568c0662e86cfe41463a6d88
5
5
  SHA512:
6
- metadata.gz: f916fe4fb53d28a9fbb61c8d7306825cb96d82ceeb559a6aeea1373c83e5401c785c0400dd3b41f0d5f9a4c321f0cc91c85eddead5808d546c7fb09d6b671aa7
7
- data.tar.gz: 218935fdbb73a113262735e5b4a0290d7942121b167932ad00a5ead4c1a75ef69d8f18dfffca5fea14aafbbb8bf79f6cb940042f15f0989a59ce1e6c14a58208
6
+ metadata.gz: 044f0541c54678616eff01372b20b54e4a2bfd3f3d087ba276dc749ce06bc550f6bb047ba90bad084d1edb04f1600774cf5dd681fb56b4c91ef0c8530dbcfa38
7
+ data.tar.gz: f471e4c215f2d9f36944621e9bb067f9fcde0a6f67d72ee9b6e1cc6a3a2aeb507dd93cdc110bdaf9058be19c7cb700062932609ce9f10437bb5c813c75f0ed82
data/README.md CHANGED
@@ -10,11 +10,33 @@ CoreMerchant is a library for customer, product, and subscription management in
10
10
  - [X] Add initializer generator
11
11
  - [X] Add SubscriptionPlan model
12
12
  - [X] Add Subscription model
13
- - [ ] Implement subscription manager and callbacks
13
+ - [X] Implement subscription manager and callbacks
14
14
  - [ ] Add sidekiq jobs for subscription management
15
15
  - [ ] Add Invoice model
16
16
  - [ ] Add billing and invoicing service
17
17
 
18
+ ## Table of contents
19
+ - [CoreMerchant](#coremerchant)
20
+ - [To-dos until 1.0.0 release](#to-dos-until-100-release)
21
+ - [Table of contents](#table-of-contents)
22
+ - [Installation](#installation)
23
+ - [Usage](#usage)
24
+ - [Initialization](#initialization)
25
+ - [Configuration](#configuration)
26
+ - [1. Customer class](#1-customer-class)
27
+ - [2. Subscription listener class](#2-subscription-listener-class)
28
+ - [Subscription management](#subscription-management)
29
+ - [Creating a subscription plan](#creating-a-subscription-plan)
30
+ - [Creating a subscription](#creating-a-subscription)
31
+ - [Cancelling a subscription](#cancelling-a-subscription)
32
+ - [Handling subscription events](#handling-subscription-events)
33
+ - [Models](#models)
34
+ - [SubscriptionManager](#subscriptionmanager)
35
+ - [SubscriptionPlan](#subscriptionplan)
36
+ - [Subscription](#subscription)
37
+ - [Contributing](#contributing)
38
+
39
+
18
40
  ## Installation
19
41
  Add this line to your application's Gemfile:
20
42
  ```
@@ -31,14 +53,14 @@ $ gem install core_merchant
31
53
  ### Initialization
32
54
  Run the generator to create the initializer file and the migrations:
33
55
  ```
34
- $ rails generate core_merchant:install --customer_class=User
56
+ $ rails generate core_merchant:install
35
57
  ```
36
- `--customer_class` option is required and should be the name of the model that represents the customer in your application. For example, if you already have a `User` model that represents the users of your application, you can use it as the customer class in CoreMerchant.
37
58
 
38
59
  This will create the following files:
39
60
  - `config/initializers/core_merchant.rb` - Configuration file
61
+ - `config/locales/core_merchant.en.yml` - English translations for core merchant models
40
62
  - `db/migrate/xxxxxx_create_core_merchant_subscription_plans.rb` - Migration for subscription plans
41
-
63
+ - `db/migrate/xxxxxx_create_core_merchant_subscriptions.rb` - Migration for subscriptions
42
64
  You can then run the migrations:
43
65
  ```
44
66
  $ rails db:migrate
@@ -46,11 +68,13 @@ $ rails db:migrate
46
68
 
47
69
  ### Configuration
48
70
  The initializer file `config/initializers/core_merchant.rb` contains the following configuration options:
71
+
72
+ #### 1. Customer class
49
73
  ```ruby
50
74
  config.customer_class = 'User'
51
75
  ```
52
76
 
53
- You need to include the `CoreMerchant::Customer` module in the customer class:
77
+ This is the class that includes the `CoreMerchant::Customer` module. It should be the model class that represents your customers. For example, if you have a `User` model that represents your customers, you can include the `CoreMerchant::Customer` module in the `User` class:
54
78
  ```ruby
55
79
  # app/models/user.rb
56
80
  class User < ApplicationRecord
@@ -60,9 +84,127 @@ class User < ApplicationRecord
60
84
  end
61
85
  ```
62
86
 
87
+ #### 2. Subscription listener class
88
+ ```ruby
89
+ config.subscription_listener_class = 'MySubscriptionListener'
90
+ ```
91
+ This is the class that will receive subscription events. It should include the `CoreMerchant::SubscriptionListener` module and implement event handlers for the subscription events. Some examples:
92
+ ```ruby
93
+ class MySubscriptionListener
94
+ include CoreMerchant::SubscriptionListener
95
+
96
+ def on_test_event_received
97
+ puts 'Test event received, hooray!'
98
+ end
99
+ end
100
+ ```
101
+
102
+ More about subscription events in the [Handling subscription events](#handling-subscription-events) section.
103
+
104
+ ### Subscription management
105
+
106
+ #### Creating a subscription plan
107
+ You can create a subscription plan using the `SubscriptionPlan` model:
108
+ ```ruby
109
+ CoreMerchant::SubscriptionPlan.create(name_key: 'basic', price_cents: 10_00, duration: '1m')
110
+ ```
111
+
112
+ #### Creating a subscription
113
+ You can create a subscription for a customer using the `Subscription` model:
114
+ ```ruby
115
+ customer = User.find(1)
116
+ plan = CoreMerchant::SubscriptionPlan.find_by(name_key: 'basic')
117
+ subscription = CoreMerchant::Subscription.create(customer: customer, plan: plan)
118
+ ```
119
+
120
+ Note that the subscription will not be active until you start it:
121
+ ```ruby
122
+ subscription.start
123
+ ```
124
+
125
+ #### Cancelling a subscription
126
+ You can cancel a subscription by calling the `cancel` method. You can also specify a reason for the cancellation and whether the cancellation should take effect immediately or at the end of the current billing period:
127
+ ```ruby
128
+ subscription.cancel(reason: 'Customer request', at_period_end: false)
129
+ ```
130
+
131
+ #### Handling subscription events
132
+ You can handle subscription events by implementing event handlers in the subscription listener class. For example, you can send an email to the customer when a subscription is created:
133
+ ```ruby
134
+ class MySubscriptionListener
135
+ include CoreMerchant::SubscriptionListener
136
+
137
+
138
+ def on_subscription_created(subscription)
139
+ FakeEmailService.send_email(subscription.customer.email, "We're happy to have you on board!")
140
+
141
+ # You can also start the subscription automatically
142
+ subscription.start
143
+ end
144
+
145
+ def on_subscription_started(subscription)
146
+ FakeEmailService.send_email(subscription.customer.email, "Your subscription has started!")
147
+ end
148
+
149
+ def on_subscription_due_for_renewal(subscription)
150
+ success = FakePaymentService.charge(subscription.customer, subscription.plan.price_cents)
151
+
152
+ if success
153
+ CoreMerchant.subscription_manager.payment_successful_for_renewal(subscription)
154
+ else
155
+ FakeEmailService.send_email(subscription.customer.email, "Payment failed, please update your payment method.")
156
+ CoreMerchant.subscription_manager.payment_failed_for_renewal(subscription)
157
+ end
158
+ end
159
+
160
+ def on_subscription_renewed(subscription)
161
+ FakeEmailService.send_email(subscription.customer.email, "Your subscription has been renewed until #{subscription.current_period_end_date}")
162
+ end
163
+ end
164
+ ```
165
+
166
+ Available subscription events:
167
+ - `on_subscription_created(subscription)`
168
+ - `on_subscription_destroyed(subscription)`
169
+ - `on_subscription_started(subscription)`
170
+ - `on_subscription_canceled(subscription, reason:, at_period_end:)`
171
+ - `on_subscription_due_for_renewal(subscription)`
172
+ - `on_subscription_renewed(subscription)`
173
+ - `on_subscription_renewal_payment_processing(subscription)`
174
+ - `on_subscription_grace_period_started(subscription, days_remaining:)`
175
+
63
176
  ## Models
177
+ ### SubscriptionManager
178
+ Access from `CoreMerchant.subscription_manager`. The `SubscriptionManager` class is responsible for managing subscriptions. It is responsible for notifying listeners when subscription events occur and checking for and handling renewals.
179
+
180
+ **Attributes**:
181
+ - `listeners` - An array of listeners that will be notified when subscription events occur.
182
+
183
+ **Methods**:
184
+ - `check_subscriptions` - Checks all subscriptions for renewals
185
+ - `add_listener(listener)` - Adds a listener to the list of listeners
186
+ - `no_payment_needed_for_renewal(subscription)` - Handles the case where no payment is needed for a renewal.
187
+ Call when a subscription is renewed without payment.
188
+ - `processing_payment_for_renewal(subscription)` - Handles the case where payment is being processed for a renewal.
189
+ Call when payment is being processed for a renewal.
190
+ - `payment_successful_for_renewal(subscription)` - Handles the case where payment was successful for a renewal.
191
+ Call when payment was successful for a renewal.
192
+ - `payment_failed_for_renewal(subscription)` - Handles the case where payment failed for a renewal.
193
+ Call when payment failed for a renewal.
194
+
195
+ **Usage**:
196
+ ```ruby
197
+ manager = CoreMerchant.subscription_manager
198
+ manager.check_subscriptions
199
+ # ... somewhere else in the code ...
200
+ manager.payment_successful_for_renewal(subscription1)
201
+ manager.payment_failed_for_renewal(subscription2)
202
+ ```
203
+
64
204
  ### SubscriptionPlan
65
- The `SubscriptionPlan` model represents a subscription plan in your application. It has the following attributes:
205
+ The `SubscriptionPlan` model represents a subscription plan in your application. Subscription plans are used to define the pricing and features of a subscription. All prices are in cents.
206
+
207
+ **Attributes**:
66
208
  - `name_key`: A unique key for the subscription plan.
67
209
  This key is used to identify the plan in the application,
68
210
  as well as the translation key for the plan name through `core_merchant.subscription_plans`.
@@ -73,6 +215,62 @@ The `SubscriptionPlan` model represents a subscription plan in your application.
73
215
  - `introductory_price_cents`: The introductory price of the subscription plan in cents.
74
216
  - `introductory_duration`: The duration of the introductory price of the subscription plan.
75
217
 
218
+ **Usage**:
219
+ ``` ruby
220
+ plan = CoreMerchant::SubscriptionPlan.new(name_key: "basic_monthly", price_cents: 7_99)
221
+ plan.save
222
+ ```
223
+ ### Subscription
224
+ Represents a subscription in CoreMerchant.
225
+ This class manages the lifecycle of a customer's subscription to a specific plan.
226
+
227
+ **Subscriptions can transition through various statuses**:
228
+ - `pending`: Subscription created but not yet started
229
+ - `trial`: In a trial period
230
+ - `active`: Currently active and paid
231
+ - `past_due`: Payment failed but in grace period
232
+ - `pending_cancellation`: Will be canceled at period end
233
+ - `processing_renewal`: Renewal in progress
234
+ - `processing_payment`: Payment processing
235
+ - `canceled`: Canceled by user or due to payment failure
236
+ - `expired`: Subscription period ended
237
+ - `paused`: Temporarily halted, not yet implemented
238
+ - `pending_change`: Plan change scheduled for next renewal, not yet implemented
239
+
240
+ **Key features**:
241
+ - Supports immediate and end-of-period cancellations
242
+ - Allows plan changes, effective immediately or at next renewal
243
+ - Handles subscription pausing and resuming
244
+ - Manages trial periods
245
+ - Supports variable pricing for renewals
246
+
247
+ **Attributes**:
248
+ - `customer`: Polymorphic association to the customer
249
+ - `subscription_plan`: The current plan for this subscription
250
+ - `status`: Current status of the subscription (see enum definition)
251
+ - `start_date`: When the subscription started
252
+ - `end_date`: When the subscription ended (or will end)
253
+ - `trial_end_date`: End date of the trial period (if applicable)
254
+ - `canceled_at`: When the subscription was canceled
255
+ - `current_period_start`: Start of the current billing period
256
+ - `current_period_end`: End of the current billing period
257
+ - `pause_start_date`: When the subscription was paused
258
+ - `pause_end_date`: When the paused subscription will resume
259
+ - `current_period_price_cents`: Price for the current period
260
+ - `next_renewal_price_cents`: Price for the next renewal (if different from plan)
261
+ - `cancellation_reason`: Reason for cancellation (if applicable)
262
+
263
+ **Methods**:
264
+ - `start` - Starts the subscription
265
+ - `cancel(reason:, at_period_end:)` - Cancels the subscription, optionally at the end of the current period
266
+
267
+ **Usage**:
268
+ ```ruby
269
+ subscription = CoreMerchant::Subscription.create(customer: user, subscription_plan: plan, status: :active)
270
+ subscription.start
271
+ subscription.cancel(reason: "Too expensive", at_period_end: true)
272
+ ```
273
+
76
274
  > [!NOTE]
77
275
  > Other models and features are being developed and will be added in future releases.
78
276
 
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CoreMerchant
4
+ module Concerns
5
+ # Includes logic for notifying listeners of subscription events.
6
+ module SubscriptionManagerNotifications
7
+ extend ActiveSupport::Concern
8
+
9
+ included do # rubocop:disable Metrics/BlockLength
10
+ def notify(subscription, event, **options) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
11
+ case event
12
+ when :created
13
+ notify_subscription_created(subscription)
14
+ when :destroyed
15
+ notify_subscription_destroyed(subscription)
16
+ when :started
17
+ notify_subscription_started(subscription)
18
+ when :canceled
19
+ notify_subscription_canceled(subscription, options[:reason], options[:immediate])
20
+ when :due_for_renewal
21
+ notify_subscription_due_for_renewal(subscription)
22
+ when :renewed
23
+ notify_subscription_renewed(subscription)
24
+ when :renewal_payment_processing
25
+ notify_subscription_renewal_payment_processing(subscription)
26
+ when :grace_period_started
27
+ notify_subscription_grace_period_started(subscription, options[:days_remaining])
28
+ when :expired
29
+ notify_subscription_expired(subscription)
30
+ end
31
+ end
32
+
33
+ def notify_test_event
34
+ send_notification_to_listeners(nil, :on_test_event_received)
35
+ end
36
+
37
+ private
38
+
39
+ def notify_subscription_created(subscription)
40
+ send_notification_to_listeners(subscription, :on_subscription_created)
41
+ end
42
+
43
+ def notify_subscription_destroyed(subscription)
44
+ send_notification_to_listeners(subscription, :on_subscription_destroyed)
45
+ end
46
+
47
+ def notify_subscription_started(subscription)
48
+ send_notification_to_listeners(subscription, :on_subscription_started)
49
+ end
50
+
51
+ def notify_subscription_canceled(subscription, reason, immediate)
52
+ send_notification_to_listeners(subscription, :on_subscription_canceled, reason: reason, immediate: immediate)
53
+ end
54
+
55
+ def notify_subscription_due_for_renewal(subscription)
56
+ send_notification_to_listeners(subscription, :on_subscription_due_for_renewal)
57
+ end
58
+
59
+ def notify_subscription_renewed(subscription)
60
+ send_notification_to_listeners(subscription, :on_subscription_renewed)
61
+ end
62
+
63
+ def notify_subscription_renewal_payment_processing(subscription)
64
+ send_notification_to_listeners(subscription, :on_subscription_renewal_payment_processing)
65
+ end
66
+
67
+ def notify_subscription_expired(subscription)
68
+ send_notification_to_listeners(subscription, :on_subscription_expired)
69
+ end
70
+
71
+ def notify_subscription_grace_period_started(subscription, days_remaining)
72
+ send_notification_to_listeners(
73
+ subscription, :on_subscription_grace_period_started,
74
+ days_remaining: days_remaining
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -46,13 +46,10 @@ module CoreMerchant
46
46
  # **Usage**:
47
47
  # ```ruby
48
48
  # subscription = CoreMerchant::Subscription.create(customer: user, subscription_plan: plan, status: :active)
49
+ # subscription.start
49
50
  # subscription.cancel(reason: "Too expensive", at_period_end: true)
50
- # subscription.change_plan(new_plan, at_period_end: false)
51
- # subscription.pause(until_date: 1.month.from_now)
52
- # subscription.resume
53
- # subscription.renew(price_cents: 1999)
54
51
  # ```
55
- class Subscription < ActiveRecord::Base
52
+ class Subscription < ActiveRecord::Base # rubocop:disable Metrics/ClassLength
56
53
  include CoreMerchant::Concerns::SubscriptionStateMachine
57
54
  include CoreMerchant::Concerns::SubscriptionNotifications
58
55
 
@@ -80,6 +77,12 @@ module CoreMerchant
80
77
  validate :end_date_after_start_date, if: :end_date
81
78
  validate :canceled_at_with_reason, if: :canceled_at
82
79
 
80
+ scope :due_for_renewal,
81
+ lambda {
82
+ where(status: %i[active trial past_due processing_renewal processing_payment])
83
+ .where("current_period_end <= ?", Time.current)
84
+ }
85
+
83
86
  # Starts the subscription.
84
87
  # Sets the current period start and end dates based on the plan's duration.
85
88
  def start
@@ -89,8 +92,8 @@ module CoreMerchant
89
92
  transaction do
90
93
  transition_to_active!
91
94
  update!(
92
- current_period_start: new_period_start,
93
- current_period_end: new_period_end
95
+ current_period_start: new_period_start.to_date,
96
+ current_period_end: new_period_end.to_date
94
97
  )
95
98
  end
96
99
 
@@ -119,6 +122,18 @@ module CoreMerchant
119
122
  notify_subscription_manager(:canceled, reason: reason, immediate: !at_period_end)
120
123
  end
121
124
 
125
+ # Starts a new period for the subscription.
126
+ # This is called by SubscriptionManager when a subscription renewal is successful.
127
+ def start_new_period
128
+ new_period_start = current_period_end
129
+ new_period_end = new_period_start + subscription_plan.duration_in_date
130
+
131
+ update!(
132
+ current_period_start: new_period_start.to_date,
133
+ current_period_end: new_period_end.to_date
134
+ )
135
+ end
136
+
122
137
  # Returns the days remaining in the current period.
123
138
  # Use to show the user how many days are left before the next renewal or
124
139
  # to refund pro-rated amounts for early cancellations.
@@ -151,8 +166,23 @@ module CoreMerchant
151
166
  end
152
167
 
153
168
  def due_for_renewal?
154
- (active? || trial? || past_due? || processing_renewal? || processing_payment?) &&
155
- current_period_end <= Time.current
169
+ renewable? && current_period_end <= Time.current
170
+ end
171
+
172
+ def expired_or_canceled?
173
+ expired? || canceled?
174
+ end
175
+
176
+ def processing?
177
+ processing_renewal? || processing_payment?
178
+ end
179
+
180
+ def ongoing?
181
+ active? || trial? || past_due? || processing_renewal? || processing_payment? || pending_cancellation?
182
+ end
183
+
184
+ def renewable?
185
+ active? || trial? || past_due? || processing?
156
186
  end
157
187
 
158
188
  private
@@ -1,14 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "core_merchant/concerns/subscription_manager_renewals"
3
+ require "core_merchant/concerns/subscription_manager_notifications"
4
4
 
5
5
  module CoreMerchant
6
6
  # Manages subscriptions in CoreMerchant.
7
- # This class is responsible for notifying listeners when subscription events occur.
8
- # Attributes:
7
+ # This class is responsible for notifying listeners when subscription events
8
+ # occur and checking for and handling renewals.
9
+ # **Attributes**:
9
10
  # - `listeners` - An array of listeners that will be notified when subscription events occur.
11
+ #
12
+ # **Methods**:
13
+ # - `check_subscriptions` - Checks all subscriptions for renewals
14
+ # - `add_listener(listener)` - Adds a listener to the list of listeners
15
+ # - `no_payment_needed_for_renewal(subscription)` - Handles the case where no payment is needed for a renewal.
16
+ # Call when a subscription is renewed without payment.
17
+ # - `processing_payment_for_renewal(subscription)` - Handles the case where payment is being processed for a renewal.
18
+ # Call when payment is being processed for a renewal.
19
+ # - `payment_successful_for_renewal(subscription)` - Handles the case where payment was successful for a renewal.
20
+ # Call when payment was successful for a renewal.
21
+ # - `payment_failed_for_renewal(subscription)` - Handles the case where payment failed for a renewal.
22
+ # Call when payment failed for a renewal.
23
+ #
24
+ # **Usage**:
25
+ # ```ruby
26
+ # manager = CoreMerchant.subscription_manager
27
+ # manager.check_subscriptions
28
+ #
29
+ # # ... somewhere else in the code ...
30
+ # manager.payment_successful_for_renewal(subscription1)
31
+ # manager.payment_failed_for_renewal(subscription2)
32
+ # ```
33
+ #
10
34
  class SubscriptionManager
11
- include Concerns::SubscriptionManagerRenewals
35
+ include Concerns::SubscriptionManagerNotifications
12
36
 
13
37
  attr_reader :listeners
14
38
 
@@ -18,82 +42,69 @@ module CoreMerchant
18
42
 
19
43
  def check_subscriptions
20
44
  check_renewals
45
+ check_cancellations
21
46
  end
22
47
 
23
48
  def add_listener(listener)
24
49
  @listeners << listener
25
50
  end
26
51
 
27
- def notify(subscription, event, **options) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
28
- case event
29
- when :created
30
- notify_subscription_created(subscription)
31
- when :destroyed
32
- notify_subscription_destroyed(subscription)
33
- when :started
34
- notify_subscription_started(subscription)
35
- when :canceled
36
- notify_subscription_canceled(subscription, options[:reason], options[:immediate])
37
- when :due_for_renewal
38
- notify_subscription_due_for_renewal(subscription)
39
- when :renewed
40
- notify_subscription_renewed(subscription)
41
- when :renewal_payment_processing
42
- notify_subscription_renewal_payment_processing(subscription)
43
- when :grace_period_started
44
- notify_subscription_grace_period_started(subscription, options[:days_remaining])
45
- when :expired
46
- notify_subscription_expired(subscription)
52
+ def check_renewals
53
+ Subscription.find_each do |subscription|
54
+ process_for_renewal(subscription) if subscription.due_for_renewal?
47
55
  end
48
56
  end
49
57
 
50
- def notify_test_event
51
- send_notification_to_listeners(nil, :on_test_event_received)
52
- end
53
-
54
- private
58
+ def process_for_renewal(subscription)
59
+ return unless subscription.transition_to_processing_renewal
55
60
 
56
- def notify_subscription_created(subscription)
57
- send_notification_to_listeners(subscription, :on_subscription_created)
61
+ notify(subscription, :due_for_renewal)
58
62
  end
59
63
 
60
- def notify_subscription_destroyed(subscription)
61
- send_notification_to_listeners(subscription, :on_subscription_destroyed)
64
+ def no_payment_needed_for_renewal(subscription)
65
+ renew_subscription(subscription)
62
66
  end
63
67
 
64
- def notify_subscription_started(subscription)
65
- send_notification_to_listeners(subscription, :on_subscription_started)
66
- end
68
+ def processing_payment_for_renewal(subscription)
69
+ return unless subscription.transition_to_processing_payment
67
70
 
68
- def notify_subscription_canceled(subscription, reason, immediate)
69
- send_notification_to_listeners(subscription, :on_subscription_canceled, reason: reason, immediate: immediate)
71
+ notify(subscription, :renewal_payment_processing)
70
72
  end
71
73
 
72
- def notify_subscription_due_for_renewal(subscription)
73
- send_notification_to_listeners(subscription, :on_subscription_due_for_renewal)
74
+ def payment_successful_for_renewal(subscription)
75
+ renew_subscription(subscription)
74
76
  end
75
77
 
76
- def notify_subscription_renewed(subscription)
77
- send_notification_to_listeners(subscription, :on_subscription_renewed)
78
+ def payment_failed_for_renewal(subscription)
79
+ is_in_grace_period = subscription.in_grace_period?
80
+ if is_in_grace_period
81
+ subscription.transition_to_past_due
82
+ notify(subscription, :grace_period_started, days_remaining: subscription.days_remaining_in_grace_period)
83
+ else
84
+ subscription.transition_to_expired
85
+ notify(subscription, :expired)
86
+ end
78
87
  end
79
88
 
80
- def notify_subscription_renewal_payment_processing(subscription)
81
- send_notification_to_listeners(subscription, :on_subscription_renewal_payment_processing)
89
+ def check_cancellations
90
+ Subscription.find_each do |subscription|
91
+ process_for_cancellation(subscription) if subscription.pending_cancellation?
92
+ end
82
93
  end
83
94
 
84
- def notify_subscription_expired(subscription)
85
- send_notification_to_listeners(subscription, :on_subscription_expired)
86
- end
95
+ def process_for_cancellation(subscription)
96
+ return unless subscription.transition_to_expired
87
97
 
88
- def notify_subscription_grace_period_started(subscription, days_remaining)
89
- send_notification_to_listeners(
90
- subscription, :on_subscription_grace_period_started,
91
- days_remaining: days_remaining
92
- )
98
+ notify(subscription, :expired)
93
99
  end
94
100
 
95
- def notify_subscription_grace_period_exceeded(subscription)
96
- send_notification_to_listeners(subscription, :on_subscription_grace_period_exceeded)
101
+ private
102
+
103
+ def renew_subscription(subscription)
104
+ return unless subscription.transition_to_active
105
+
106
+ subscription.start_new_period
107
+ notify(subscription, :renewed)
97
108
  end
98
109
 
99
110
  def send_notification_to_listeners(subscription, method_name, **args)
@@ -16,7 +16,7 @@ module CoreMerchant
16
16
  # - `introductory_price_cents`: The introductory price of the subscription plan in cents.
17
17
  # - `introductory_duration`: The duration of the introductory price of the subscription plan.
18
18
  #
19
- # Example:
19
+ # Usage:
20
20
  # ```
21
21
  # plan = CoreMerchant::SubscriptionPlan.new(name_key: "basic_monthly", price_cents: 7_99)
22
22
  # plan.save
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CoreMerchant
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core_merchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seyithan Teymur
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-23 00:00:00.000000000 Z
11
+ date: 2024-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -144,7 +144,7 @@ files:
144
144
  - Rakefile
145
145
  - core_merchant.gemspec
146
146
  - lib/core_merchant.rb
147
- - lib/core_merchant/concerns/subscription_manager_renewals.rb
147
+ - lib/core_merchant/concerns/subscription_manager_notifications.rb
148
148
  - lib/core_merchant/concerns/subscription_notifications.rb
149
149
  - lib/core_merchant/concerns/subscription_state_machine.rb
150
150
  - lib/core_merchant/customer_behavior.rb
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CoreMerchant
4
- module Concerns
5
- # Includes logic for renewal processing, grace period handling, and expiration checking.
6
- module SubscriptionManagerRenewals
7
- extend ActiveSupport::Concern
8
-
9
- included do # rubocop:disable Metrics/BlockLength
10
- def check_renewals
11
- Subscription.find_each do |subscription|
12
- process_for_renewal(subscription) if subscription.due_for_renewal?
13
- end
14
- end
15
-
16
- def process_for_renewal(subscription)
17
- return unless subscription.transition_to_processing_renewal
18
-
19
- notify(subscription, :due_for_renewal)
20
- end
21
-
22
- def no_payment_needed_for_renewal(subscription)
23
- return unless subscription.transition_to_active
24
-
25
- notify(subscription, :renewed)
26
- end
27
-
28
- def processing_payment_for_renewal(subscription)
29
- return unless subscription.transition_to_processing_payment
30
-
31
- notify(subscription, :renewal_payment_processing)
32
- end
33
-
34
- def payment_successful_for_renewal(subscription)
35
- return unless subscription.transition_to_active
36
-
37
- notify(subscription, :renewed)
38
- end
39
-
40
- def payment_failed_for_renewal(subscription)
41
- is_in_grace_period = subscription.in_grace_period?
42
- if is_in_grace_period
43
- subscription.transition_to_past_due
44
- notify(subscription, :grace_period_started, days_remaining: subscription.days_remaining_in_grace_period)
45
- else
46
- subscription.transition_to_expired
47
- notify(subscription, :expired)
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end