core_merchant 0.5.1 → 0.6.2

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: 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: