core_merchant 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff6bdd381d58ecdb8153a14ceff12482d22b55b70a738de1e46df9480f3ab472
4
- data.tar.gz: a87c17013865cdbe94f70daf969624f494135955568c0662e86cfe41463a6d88
3
+ metadata.gz: 698e2da9b3b98b8e6c6275063af7d439c1c2a79384927e952e50419691331ce9
4
+ data.tar.gz: fde06b7ff60d2ac49bc36b5c89200c073a1f498eca046075c7bd0cfb626702de
5
5
  SHA512:
6
- metadata.gz: 044f0541c54678616eff01372b20b54e4a2bfd3f3d087ba276dc749ce06bc550f6bb047ba90bad084d1edb04f1600774cf5dd681fb56b4c91ef0c8530dbcfa38
7
- data.tar.gz: f471e4c215f2d9f36944621e9bb067f9fcde0a6f67d72ee9b6e1cc6a3a2aeb507dd93cdc110bdaf9058be19c7cb700062932609ce9f10437bb5c813c75f0ed82
6
+ metadata.gz: 2cc9e74b4053e9a91435977f1b6d30222c96a69403654062a9c36dcfec6a18e2e4eebecfdc9c889cc3afbac1ac1a34e4f451cd78e6abbe64bf4ca7e795570543
7
+ data.tar.gz: 6a8cfc973fe959bfbb5cf29c0b68e3e7f644e1cee9f5e21fdc415a4d510b26ddf904e85d05c29188a176fec0225c6e763938713dcdb4d8521cf12d0aab4797c5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- core_merchant (0.8.0)
4
+ core_merchant (0.9.0)
5
5
  activesupport (~> 7.0)
6
6
  rails (~> 7.0)
7
7
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ CoreMerchant is a library for customer, product, and subscription management in
11
11
  - [X] Add SubscriptionPlan model
12
12
  - [X] Add Subscription model
13
13
  - [X] Implement subscription manager and callbacks
14
- - [ ] Add sidekiq jobs for subscription management
14
+ - [X] Implement SubscriptionEvent model for logging
15
15
  - [ ] Add Invoice model
16
16
  - [ ] Add billing and invoicing service
17
17
 
@@ -19,28 +19,35 @@ CoreMerchant is a library for customer, product, and subscription management in
19
19
  - [CoreMerchant](#coremerchant)
20
20
  - [To-dos until 1.0.0 release](#to-dos-until-100-release)
21
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)
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
+ - [Subscription History](#subscription-history)
34
+ - [Public API](#public-api)
34
35
  - [SubscriptionManager](#subscriptionmanager)
35
36
  - [SubscriptionPlan](#subscriptionplan)
36
37
  - [Subscription](#subscription)
38
+ - [SubscriptionEvent](#subscriptionevent)
39
+ - [Subclasses](#subclasses)
40
+ - [SubscriptionRenewalEvent](#subscriptionrenewalevent)
41
+ - [SubscriptionStatusChangeEvent](#subscriptionstatuschangeevent)
42
+ - [SubscriptionPlanChangeEvent](#subscriptionplanchangeevent)
43
+ - [SubscriptionCancellationEvent](#subscriptioncancellationevent)
37
44
  - [Contributing](#contributing)
38
45
 
39
46
 
40
- ## Installation
47
+ # Installation
41
48
  Add this line to your application's Gemfile:
42
49
  ```
43
- gem 'core_merchant', '~> 0.1.0'
50
+ gem 'core_merchant', '~> 0.10.0'
44
51
  ```
45
52
  and run `bundle install`.
46
53
 
@@ -49,8 +56,8 @@ Alternatively, you can install the gem manually:
49
56
  $ gem install core_merchant
50
57
  ```
51
58
 
52
- ## Usage
53
- ### Initialization
59
+ # Usage
60
+ ## Initialization
54
61
  Run the generator to create the initializer file and the migrations:
55
62
  ```
56
63
  $ rails generate core_merchant:install
@@ -66,10 +73,10 @@ You can then run the migrations:
66
73
  $ rails db:migrate
67
74
  ```
68
75
 
69
- ### Configuration
76
+ ## Configuration
70
77
  The initializer file `config/initializers/core_merchant.rb` contains the following configuration options:
71
78
 
72
- #### 1. Customer class
79
+ ### 1. Customer class
73
80
  ```ruby
74
81
  config.customer_class = 'User'
75
82
  ```
@@ -84,7 +91,7 @@ class User < ApplicationRecord
84
91
  end
85
92
  ```
86
93
 
87
- #### 2. Subscription listener class
94
+ ### 2. Subscription listener class
88
95
  ```ruby
89
96
  config.subscription_listener_class = 'MySubscriptionListener'
90
97
  ```
@@ -101,15 +108,15 @@ end
101
108
 
102
109
  More about subscription events in the [Handling subscription events](#handling-subscription-events) section.
103
110
 
104
- ### Subscription management
111
+ ## Subscription management
105
112
 
106
- #### Creating a subscription plan
113
+ ### Creating a subscription plan
107
114
  You can create a subscription plan using the `SubscriptionPlan` model:
108
115
  ```ruby
109
116
  CoreMerchant::SubscriptionPlan.create(name_key: 'basic', price_cents: 10_00, duration: '1m')
110
117
  ```
111
118
 
112
- #### Creating a subscription
119
+ ### Creating a subscription
113
120
  You can create a subscription for a customer using the `Subscription` model:
114
121
  ```ruby
115
122
  customer = User.find(1)
@@ -122,13 +129,13 @@ Note that the subscription will not be active until you start it:
122
129
  subscription.start
123
130
  ```
124
131
 
125
- #### Cancelling a subscription
132
+ ### Cancelling a subscription
126
133
  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
134
  ```ruby
128
135
  subscription.cancel(reason: 'Customer request', at_period_end: false)
129
136
  ```
130
137
 
131
- #### Handling subscription events
138
+ ### Handling subscription events
132
139
  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
140
  ```ruby
134
141
  class MySubscriptionListener
@@ -173,7 +180,29 @@ Available subscription events:
173
180
  - `on_subscription_renewal_payment_processing(subscription)`
174
181
  - `on_subscription_grace_period_started(subscription, days_remaining:)`
175
182
 
176
- ## Models
183
+ ### Subscription History
184
+ CoreMerchant now keeps a detailed history of subscription events, including creations, renewals, cancellations, status changes, and plan changes. This provides an audit trail and can be useful for debugging, customer support, and analytics.
185
+
186
+ To access a subscription's history:
187
+ ```ruby
188
+ subscription = CoreMerchant::Subscription.find(42)
189
+
190
+ # Get all events
191
+ subscription.subscription_events
192
+
193
+ # Get specific event types
194
+ latest_renewal = subscription.renewal_events.last
195
+ puts "Last renewed at: #{latest_renewal.created_at}"
196
+ puts "Renewal price: #{latest_renewal.price_cents} cents, renewed until: #{latest_renewal.renewed_until}"
197
+
198
+ latest_status_change = subscription.status_change_events.last
199
+ puts "Status changed from #{latest_status_change.from} to #{latest_status_change.to}"
200
+
201
+ latest_plan_change = subscription.plan_change_events.last
202
+ puts "Plan changed from #{latest_plan_change.from_plan.name} to #{latest_plan_change.to_plan.name}"
203
+ ```
204
+
205
+ ## Public API
177
206
  ### SubscriptionManager
178
207
  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
208
 
@@ -271,6 +300,53 @@ subscription.start
271
300
  subscription.cancel(reason: "Too expensive", at_period_end: true)
272
301
  ```
273
302
 
303
+ ### SubscriptionEvent
304
+ The `SubscriptionEvent` model represents a historical log of events related to a subscription. It provides an audit trail of all significant actions and state changes for a subscription.
305
+
306
+ This class has subclasses for specific event types, such as `SubscriptionRenewalEvent`, `SubscriptionStatusChangeEvent`, and `SubscriptionPlanChangeEvent`. Each subclass has additional fields specific to the event type.
307
+
308
+ **Attributes**:
309
+ - `subscription`: Association to the related Subscription
310
+ - `event_type`: Type of the event (e.g., 'created', 'renewed', 'canceled', 'status_changed', 'plan_changed')
311
+ - `metadata`: JSON field for storing additional event-specific data
312
+
313
+ **Usage**:
314
+ ```ruby
315
+ # Automatically logged when a subscription is created
316
+ subscription = CoreMerchant::Subscription.create(customer: user, subscription_plan: plan)
317
+
318
+ # Logging a custom event
319
+ subscription.log_event('custom_event', key: 'value')
320
+
321
+ # Retrieve the last renewal event
322
+ latest_renewal = subscription.renewal_events.last
323
+ puts "Last renewed at: #{latest_renewal.created_at}"
324
+ puts "Renewal price: #{latest_renewal.price_cents} cents, renewed until: #{latest_renewal.renewed_until}"
325
+
326
+ # Retrieve the last event of any type
327
+ latest_event = subscription.subscription_events.last
328
+ puts "Last event type: #{latest_event.event_type}, metadata: #{latest_event.metadata}"
329
+ ```
330
+
331
+ #### Subclasses
332
+ ##### SubscriptionRenewalEvent
333
+ - `price_cents`: The price of the renewal in cents
334
+ - `renewed_until`: The end date of the renewal
335
+ - `renewed_at`: The date and time of the renewal
336
+
337
+ ##### SubscriptionStatusChangeEvent
338
+ - `from`: The previous status of the subscription
339
+ - `to`: The new status of the subscription
340
+
341
+ ##### SubscriptionPlanChangeEvent
342
+ - `from_plan`: The previous plan of the subscription
343
+ - `to_plan`: The new plan of the subscription
344
+
345
+ ##### SubscriptionCancellationEvent
346
+ - `reason`: The reason for the cancellation
347
+ - `at_period_end`: Whether the cancellation is scheduled for the end of the current period
348
+ - `canceled_at`: The date and time of the cancellation
349
+
274
350
  > [!NOTE]
275
351
  > Other models and features are being developed and will be added in future releases.
276
352
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CoreMerchant
4
+ module Concerns
5
+ # This concern adds a SubscriptionEvent association including creation and retrieval
6
+ module SubscriptionEventAssociation
7
+ extend ActiveSupport::Concern
8
+
9
+ EVENT_TYPES = %i[renewal status_change plan_change cancellation].freeze
10
+
11
+ included do
12
+ has_many :events, class_name: "SubscriptionEvent", dependent: :destroy
13
+
14
+ EVENT_TYPES.each do |event_type|
15
+ scope_name = "#{event_type}_events".to_sym
16
+ class_name = "Subscription#{event_type.to_s.camelize}Event"
17
+
18
+ has_many scope_name, -> { where(event_type: event_type) }, class_name: class_name
19
+
20
+ define_method "create_#{event_type}_event" do |**metadata|
21
+ events.create!(event_type: event_type, metadata: metadata)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "core_merchant/concerns/subscription_state_machine"
4
4
  require "core_merchant/concerns/subscription_notifications"
5
+ require "core_merchant/concerns/subscription_event_association"
6
+ require "core_merchant/subscription_event"
5
7
 
6
8
  module CoreMerchant
7
9
  # Represents a subscription in CoreMerchant.
@@ -52,6 +54,7 @@ module CoreMerchant
52
54
  class Subscription < ActiveRecord::Base # rubocop:disable Metrics/ClassLength
53
55
  include CoreMerchant::Concerns::SubscriptionStateMachine
54
56
  include CoreMerchant::Concerns::SubscriptionNotifications
57
+ include CoreMerchant::Concerns::SubscriptionEventAssociation
55
58
 
56
59
  self.table_name = "core_merchant_subscriptions"
57
60
 
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CoreMerchant
4
+ # The `SubscriptionEvent` model represents a historical log of events related to a subscription.
5
+ # It provides an audit trail of all significant actions and state changes for a subscription.
6
+
7
+ # This class has subclasses for specific event types, such as
8
+ # `SubscriptionRenewalEvent`, `SubscriptionStatusChangeEvent`, and `SubscriptionPlanChangeEvent`.
9
+ # Each subclass has additional fields specific to the event type.
10
+
11
+ # **Attributes**:
12
+ # - `subscription`: Association to the related Subscription
13
+ # - `event_type`: Type of the event (e.g., 'created', 'renewed', 'canceled', 'status_changed', 'plan_changed')
14
+ # - `metadata`: JSON field for storing additional event-specific data
15
+
16
+ # **Usage**:
17
+ # ```ruby
18
+ # # Automatically logged when a subscription is created
19
+ # subscription = CoreMerchant::Subscription.create(customer: user, subscription_plan: plan)
20
+
21
+ # # Logging a custom event
22
+ # subscription.log_event('custom_event', key: 'value')
23
+
24
+ # # Retrieve the last renewal event
25
+ # latest_renewal = subscription.renewal_events.last
26
+ # puts "Last renewed at: #{latest_renewal.created_at}"
27
+ # puts "Renewal price: #{latest_renewal.price_cents} cents, renewed until: #{latest_renewal.renewed_until}"
28
+
29
+ # # Retrieve the last event of any type
30
+ # latest_event = subscription.subscription_events.last
31
+ # puts "Last event type: #{latest_event.event_type}, metadata: #{latest_event.metadata}"
32
+ # ```
33
+ class SubscriptionEvent < ActiveRecord::Base
34
+ self.table_name = "core_merchant_subscription_events"
35
+
36
+ belongs_to :subscription, class_name: "CoreMerchant::Subscription"
37
+
38
+ validates :event_type, presence: true
39
+
40
+ def self.event_type
41
+ name.demodulize.underscore
42
+ end
43
+
44
+ def metadata
45
+ value = self[:metadata]
46
+ value.is_a?(Hash) ? value : JSON.parse(value || "{}")
47
+ end
48
+
49
+ def metadata=(value)
50
+ self[:metadata] = value.is_a?(Hash) ? value.to_json : value
51
+ end
52
+ end
53
+
54
+ # Represents a renewal event for a subscription.
55
+ class SubscriptionRenewalEvent < SubscriptionEvent
56
+ def price_cents
57
+ metadata["price_cents"]
58
+ end
59
+
60
+ def price_cents=(value)
61
+ self.metadata = metadata.merge(price_cents: value)
62
+ end
63
+
64
+ def renewed_from
65
+ metadata["renewed_from"].to_date
66
+ end
67
+
68
+ def renewed_from=(value)
69
+ self.metadata = metadata.merge(renewed_from: value)
70
+ end
71
+
72
+ def renewed_until
73
+ metadata["renewed_until"].to_date
74
+ end
75
+
76
+ def renewed_until=(value)
77
+ self.metadata = metadata.merge(renewed_until: value)
78
+ end
79
+ end
80
+
81
+ # Represents a status change event for a subscription.
82
+ class SubscriptionStatusChangeEvent < SubscriptionEvent
83
+ def from
84
+ metadata["from_status"]
85
+ end
86
+
87
+ def from=(value)
88
+ self.metadata = metadata.merge(from_status: value)
89
+ end
90
+
91
+ def to
92
+ metadata["to_status"]
93
+ end
94
+
95
+ def to=(value)
96
+ self.metadata = metadata.merge(to_status: value)
97
+ end
98
+ end
99
+
100
+ # Represents a plan change event for a subscription.
101
+ class SubscriptionPlanChangeEvent < SubscriptionEvent
102
+ def from_plan
103
+ id = metadata["from_plan_id"]
104
+ CoreMerchant::SubscriptionPlan.find(id) if id
105
+ end
106
+
107
+ def from_plan=(value)
108
+ self.metadata = metadata.merge(from_plan_id: value.id)
109
+ end
110
+
111
+ def to_plan
112
+ id = metadata["to_plan_id"]
113
+ CoreMerchant::SubscriptionPlan.find(id) if id
114
+ end
115
+
116
+ def to_plan=(value)
117
+ self.metadata = metadata.merge(to_plan_id: value.id)
118
+ end
119
+ end
120
+
121
+ # Represents a cancellation event for a subscription.
122
+ class SubscriptionCancellationEvent < SubscriptionEvent
123
+ def canceled_at
124
+ created_at
125
+ end
126
+
127
+ def at_period_end?
128
+ metadata["at_period_end"]
129
+ end
130
+
131
+ def at_period_end=(value)
132
+ self.metadata = metadata.merge(at_period_end: value)
133
+ end
134
+
135
+ def reason
136
+ metadata["reason"]
137
+ end
138
+
139
+ def reason=(value)
140
+ self.metadata = metadata.merge(reason: value)
141
+ end
142
+ end
143
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CoreMerchant
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -28,6 +28,9 @@ module CoreMerchant
28
28
 
29
29
  migration_template "migrate/create_core_merchant_subscriptions.erb",
30
30
  "db/migrate/create_core_merchant_subscriptions.rb"
31
+
32
+ migration_template "migrate/create_core_merchant_subscription_events.erb",
33
+ "db/migrate/create_core_merchant_subscription_events.rb"
31
34
  end
32
35
 
33
36
  def show_post_install
@@ -36,7 +39,7 @@ module CoreMerchant
36
39
  Next steps:
37
40
  1. Set the customer class in the initializer file (config/initializers/core_merchant.rb) to the class you want to use for customers.
38
41
  2. Create a subscription listener class (should include CoreMerchant::SubscriptionListener) in your app and set this class in the initializer file (config/initializers/core_merchant.rb) to the class you want to use for subscription listeners.
39
- 3. Run `rails db:migrate` to create the subscription and subscription plan tables.
42
+ 3. Run `rails db:migrate` to create the subscription, subscription plan, and subscription event tables.
40
43
  MESSAGE
41
44
  say next_steps, :yellow
42
45
  end
@@ -47,7 +50,7 @@ module CoreMerchant
47
50
 
48
51
  def self.description
49
52
  <<~DESC
50
- Installs CoreMerchant into your application. This generator will create an initializer file, migration files for the subscription and subscription plan tables, and a locale file."
53
+ Installs CoreMerchant into your application. This generator will create an initializer file, migration files for the subscription, subscription plan, and subscription event tables, and a locale file.
51
54
  DESC
52
55
  end
53
56
  end
@@ -0,0 +1,14 @@
1
+ # Created by: rails generate core_merchant:install
2
+ class CreateCoreMerchantSubscriptionEvents < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
3
+ def change
4
+ create_table :core_merchant_subscription_events do |t|
5
+ t.references :subscription, null: false, foreign_key: { to_table: :core_merchant_subscriptions }
6
+ t.string :event_type, null: false
7
+ t.jsonb :metadata, default: {}, null: false
8
+
9
+ t.timestamps
10
+
11
+ t.index :event_type
12
+ end
13
+ end
14
+ 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.9.0
4
+ version: 0.10.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-24 00:00:00.000000000 Z
11
+ date: 2024-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -144,11 +144,13 @@ files:
144
144
  - Rakefile
145
145
  - core_merchant.gemspec
146
146
  - lib/core_merchant.rb
147
+ - lib/core_merchant/concerns/subscription_event_association.rb
147
148
  - lib/core_merchant/concerns/subscription_manager_notifications.rb
148
149
  - lib/core_merchant/concerns/subscription_notifications.rb
149
150
  - lib/core_merchant/concerns/subscription_state_machine.rb
150
151
  - lib/core_merchant/customer_behavior.rb
151
152
  - lib/core_merchant/subscription.rb
153
+ - lib/core_merchant/subscription_event.rb
152
154
  - lib/core_merchant/subscription_listener.rb
153
155
  - lib/core_merchant/subscription_manager.rb
154
156
  - lib/core_merchant/subscription_plan.rb
@@ -156,6 +158,7 @@ files:
156
158
  - lib/generators/core_merchant/install_generator.rb
157
159
  - lib/generators/core_merchant/templates/core_merchant.en.yml
158
160
  - lib/generators/core_merchant/templates/core_merchant.rb
161
+ - lib/generators/core_merchant/templates/migrate/create_core_merchant_subscription_events.erb
159
162
  - lib/generators/core_merchant/templates/migrate/create_core_merchant_subscription_plans.erb
160
163
  - lib/generators/core_merchant/templates/migrate/create_core_merchant_subscriptions.erb
161
164
  - sig/core_merchant.rbs