core_merchant 0.5.1 → 0.6.2

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: 465238229ff4e985efc17153d4d8efc1cca941745ded4a1e5dbe3a189499e6d0
4
- data.tar.gz: 07ee307e66cbf913b89abbb41ec1eaa92a5ed5e981a577148cba3e8be0c02e95
3
+ metadata.gz: fceb2084b15e74e2aebe360df8cade628ffd930afd2ef689a6779c3f59f5f70a
4
+ data.tar.gz: 96368a736ce980a109bfd9d863b56cbec0c814111edd4839874738d13cc6c9e3
5
5
  SHA512:
6
- metadata.gz: 6e85d2853136062219964072612ec3d82a8fa2296b3cf71f3400e1f68b01ff19c9ce7da09ef0acdcc9b681aecb487ae5680e9342df5428f7281d12569cad860c
7
- data.tar.gz: 851db004b49a0ac7e0c737c6a53741b5d8747acfa052dedba8e78ac959ff532b02ff556a5a8a6a59cfe65b5aa0aec7cdb92d7500161904b37afdbd3ca1457607
6
+ metadata.gz: 643e829909744d948af7a5270ebdd0f3be2dfa7f3d540da9f0fd728fc9d052731719d6ec6f93cfc73d7357f25c3bbd50a8fd32046b0c2a354058fc611d34e8ac
7
+ data.tar.gz: a2ecdd77b9628e2dee8b67b422f0d4fa2f1a62214c28bc9bb1ae72338986272b03bfce1a193969a3fbbc6e440e45ac041c8dd2c637de1fa0d7f796244f6b6daf
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- core_merchant (0.5.0)
4
+ core_merchant (0.6.0)
5
5
  activesupport (~> 7.0)
6
6
  rails (~> 7.0)
7
7
 
data/README.md CHANGED
@@ -31,8 +31,10 @@ $ gem install core_merchant
31
31
  ### Initialization
32
32
  Run the generator to create the initializer file and the migrations:
33
33
  ```
34
- $ rails generate core_merchant:install
34
+ $ rails generate core_merchant:install --customer_class=User
35
35
  ```
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
+
36
38
  This will create the following files:
37
39
  - `config/initializers/core_merchant.rb` - Configuration file
38
40
  - `db/migrate/xxxxxx_create_core_merchant_subscription_plans.rb` - Migration for subscription plans
@@ -45,19 +47,10 @@ $ rails db:migrate
45
47
  ### Configuration
46
48
  The initializer file `config/initializers/core_merchant.rb` contains the following configuration options:
47
49
  ```ruby
48
- config.customer_class
49
- ```
50
-
51
- ### The cusomer class
52
- The customer class is 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. To do this, you need to set the `customer_class` configuration option in the initializer file:
53
- ```ruby
54
- # config/initializers/core_merchant.rb
55
- CoreMerchant.configure do |config|
56
- config.customer_class = 'User'
57
- end
50
+ config.customer_class = 'User'
58
51
  ```
59
52
 
60
- You need to then include the `CoreMerchant::Customer` module in the customer class:
53
+ You need to include the `CoreMerchant::Customer` module in the customer class:
61
54
  ```ruby
62
55
  # app/models/user.rb
63
56
  class User < ApplicationRecord
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module CoreMerchant
6
+ module Concerns
7
+ class InvalidTransitionError < CoreMerchant::Error; end
8
+
9
+ # Adds state machine logic to a subscription.
10
+ # This module defines the possible states and transitions for a subscription.
11
+ # Possible transitions:
12
+ # - `pending` -> `active`, `trial`
13
+ # - `trial` -> `active`, `pending_cancellation`, `canceled`
14
+ # - `active` -> `pending_cancellation`, `canceled`, `expired`
15
+ # - `pending_cancellation` -> `canceled`, `expired`
16
+ # - `canceled` -> `pending`, `active`
17
+ # - `expired` -> `pending`, `active`
18
+ module SubscriptionStateMachine
19
+ extend ActiveSupport::Concern
20
+
21
+ # List of possible transitions in the form of { to_state: [from_states] }
22
+ POSSIBLE_TRANSITIONS = {
23
+ pending: %i[canceled expired],
24
+ trial: %i[pending],
25
+ active: %i[pending trial canceled expired],
26
+ pending_cancellation: %i[active trial],
27
+ canceled: %i[active pending_cancellation trial],
28
+ expired: %i[active pending_cancellation canceled trial],
29
+ past_due: [],
30
+ paused: [],
31
+ pending_change: []
32
+ }.freeze
33
+
34
+ included do
35
+ POSSIBLE_TRANSITIONS.each do |to_state, from_states|
36
+ define_method("can_transition_to_#{to_state}?") do
37
+ from_states.include?(status.to_sym)
38
+ end
39
+
40
+ define_method("transition_to_#{to_state}") do
41
+ return false unless send("can_transition_to_#{to_state}?")
42
+
43
+ update(status: to_state)
44
+ end
45
+
46
+ define_method("transition_to_#{to_state}!") do
47
+ raise InvalidTransitionError unless send("transition_to_#{to_state}")
48
+
49
+ update!(status: to_state)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core_merchant/concerns/subscription_state_machine"
4
+
5
+ module CoreMerchant
6
+ # Represents a subscription in CoreMerchant.
7
+ # This class manages the lifecycle of a customer's subscription to a specific plan.
8
+ #
9
+ # **Subscriptions can transition through various statuses**:
10
+ # - `pending`: Subscription created but not yet started
11
+ # - `trial`: In a trial period
12
+ # - `active`: Currently active and paid
13
+ # - `past_due`: Payment failed but in grace period, not yet implemented
14
+ # - `pending_cancellation`: Will be canceled at period end
15
+ # - `canceled`: Canceled by user or due to payment failure
16
+ # - `expired`: Subscription period ended
17
+ # - `paused`: Temporarily halted, not yet implemented
18
+ # - `pending_change`: Plan change scheduled for next renewal, not yet implemented
19
+ #
20
+ # **Key features**:
21
+ # - Supports immediate and end-of-period cancellations
22
+ # - Allows plan changes, effective immediately or at next renewal
23
+ # - Handles subscription pausing and resuming
24
+ # - Manages trial periods
25
+ # - Supports variable pricing for renewals
26
+ #
27
+ # **Attributes**:
28
+ # - `customer`: Polymorphic association to the customer
29
+ # - `subscription_plan`: The current plan for this subscription
30
+ # - `status`: Current status of the subscription (see enum definition)
31
+ # - `start_date`: When the subscription started
32
+ # - `end_date`: When the subscription ended (or will end)
33
+ # - `trial_end_date`: End date of the trial period (if applicable)
34
+ # - `canceled_at`: When the subscription was canceled
35
+ # - `current_period_start`: Start of the current billing period
36
+ # - `current_period_end`: End of the current billing period
37
+ # - `pause_start_date`: When the subscription was paused
38
+ # - `pause_end_date`: When the paused subscription will resume
39
+ # - `current_period_price_cents`: Price for the current period
40
+ # - `next_renewal_price_cents`: Price for the next renewal (if different from plan)
41
+ # - `cancellation_reason`: Reason for cancellation (if applicable)
42
+ #
43
+ # **Usage**:
44
+ # ```ruby
45
+ # subscription = CoreMerchant::Subscription.create(customer: user, subscription_plan: plan, status: :active)
46
+ # subscription.cancel(reason: "Too expensive", at_period_end: true)
47
+ # subscription.change_plan(new_plan, at_period_end: false)
48
+ # subscription.pause(until_date: 1.month.from_now)
49
+ # subscription.resume
50
+ # subscription.renew(price_cents: 1999)
51
+ # ```
52
+ class Subscription < ActiveRecord::Base
53
+ include CoreMerchant::Concerns::SubscriptionStateMachine
54
+
55
+ self.table_name = "core_merchant_subscriptions"
56
+
57
+ belongs_to :customer, polymorphic: true
58
+ belongs_to :subscription_plan
59
+
60
+ enum status: {
61
+ pending: 0,
62
+ trial: 1,
63
+ active: 2,
64
+ past_due: 3, # Logic not yet implemented
65
+ pending_cancellation: 4,
66
+ canceled: 5,
67
+ expired: 6,
68
+ paused: 7, # Logic not yet implemented
69
+ pending_change: 8 # Logic not yet implemented
70
+ }
71
+
72
+ validates :customer, :subscription_plan, :status, :start_date, presence: true
73
+ validates :status, inclusion: { in: statuses.keys }
74
+ validate :end_date_after_start_date, if: :end_date
75
+ validate :canceled_at_with_reason, if: :canceled_at
76
+
77
+ def start
78
+ new_period_start = start_date
79
+ new_period_end = new_period_start + subscription_plan.duration_in_date
80
+
81
+ transition_to_active!
82
+ update!(
83
+ current_period_start: new_period_start,
84
+ current_period_end: new_period_end
85
+ )
86
+ end
87
+
88
+ def cancel(reason:, at_period_end:)
89
+ if at_period_end
90
+ transition_to_pending_cancellation!
91
+ else
92
+ transition_to_canceled!
93
+ end
94
+ update!(
95
+ canceled_at: at_period_end ? current_period_end : Time.current,
96
+ cancellation_reason: reason
97
+ )
98
+ end
99
+
100
+ private
101
+
102
+ def set_period_dates
103
+ self.start_date ||= Time.current
104
+ self.current_period_start ||= start_date
105
+ self.current_period_end ||= start_date + subscription_plan.duration_in_date
106
+ end
107
+
108
+ def end_date_after_start_date
109
+ errors.add(:end_date, "must be after the `start date") if end_date <= start_date
110
+ end
111
+
112
+ def canceled_at_with_reason
113
+ errors.add(:cancellation_reason, "must be present when `canceled_at` is set") if cancellation_reason.blank?
114
+ end
115
+ end
116
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CoreMerchant
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.2"
5
5
  end
@@ -27,14 +27,19 @@ module CoreMerchant
27
27
  end
28
28
 
29
29
  def create_migration_file
30
- migration_template "create_core_merchant_subscription_plans.erb",
30
+ migration_template "migrate/create_core_merchant_subscription_plans.erb",
31
31
  "db/migrate/create_core_merchant_subscription_plans.rb"
32
+
33
+ migration_template "migrate/create_core_merchant_subscriptions.erb",
34
+ "db/migrate/create_core_merchant_subscriptions.rb"
32
35
  end
33
36
 
34
37
  def show_post_install
35
38
  say "CoreMerchant has been successfully installed.", :green
36
- say "Customer class: #{@customer_class}"
37
- say "Please run `rails db:migrate` to create the subscription plans table."
39
+ say <<~MESSAGE
40
+ Customer class: #{@customer_class}. Please update this model to include the CoreMerchant::CustomerBehavior module.
41
+ MESSAGE
42
+ say "Please run `rails db:migrate` to create the subscription and subscription plan tables.", :yellow
38
43
  end
39
44
 
40
45
  def self.banner
@@ -0,0 +1,26 @@
1
+ # Created by: rails generate core_merchant:install
2
+ class CreateCoreMerchantSubscriptions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
3
+ def change
4
+ create_table :core_merchant_subscriptions do |t|
5
+ t.references :customer, polymorphic: true, null: false
6
+ t.references :subscription_plan, null: false
7
+ t.integer :status, null: false, default: 0
8
+ t.datetime :canceled_at
9
+ t.string :cancellation_reason
10
+
11
+ t.datetime :start_date, null: false
12
+ t.datetime :trial_end_date
13
+ t.datetime :end_date
14
+ t.datetime :current_period_start_date
15
+ t.datetime :current_period_end_date
16
+ t.datetime :pause_start_date
17
+ t.datetime :pause_end_date
18
+
19
+ t.integer :current_period_price_cents
20
+
21
+ t.timestamps
22
+
23
+ t.index :status
24
+ end
25
+ end
26
+ 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.5.1
4
+ version: 0.6.2
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-20 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -130,13 +130,16 @@ files:
130
130
  - Rakefile
131
131
  - core_merchant.gemspec
132
132
  - lib/core_merchant.rb
133
+ - lib/core_merchant/concerns/subscription_state_machine.rb
133
134
  - lib/core_merchant/customer_behavior.rb
135
+ - lib/core_merchant/subscription.rb
134
136
  - lib/core_merchant/subscription_plan.rb
135
137
  - lib/core_merchant/version.rb
136
138
  - lib/generators/core_merchant/install_generator.rb
137
139
  - lib/generators/core_merchant/templates/core_merchant.en.yml
138
140
  - lib/generators/core_merchant/templates/core_merchant.erb
139
- - lib/generators/core_merchant/templates/create_core_merchant_subscription_plans.erb
141
+ - lib/generators/core_merchant/templates/migrate/create_core_merchant_subscription_plans.erb
142
+ - lib/generators/core_merchant/templates/migrate/create_core_merchant_subscriptions.erb
140
143
  - sig/core_merchant.rbs
141
144
  homepage: https://github.com/theseyithan/core_merchant
142
145
  licenses: