core_merchant 0.3.0 → 0.6.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: 57cd9272d6c0056fdfd69e31fa31d88ebcee7e0f707d45201564ff464a1ad1d8
4
- data.tar.gz: 8715a63c453c11936e17b98d9712073a1f083f9f35170e6723d03f7789eef67f
3
+ metadata.gz: 4f973a1881b08969b6c5ba44de3ea5c3a047f3e1b28d8fc76e856a3c61ad4101
4
+ data.tar.gz: 1927e4b2202dcf7c0f25c41edc4bcdcac4abc89bf3f90d12f167afb392700c84
5
5
  SHA512:
6
- metadata.gz: cd166c331713fe259316708d16aee09ed7144a54302a52bf5b3aae298e58fa9bee37cc6371f54b3e6bf622be5fcf76ba5b2449e1fafd39e0ce177a5a1be44851
7
- data.tar.gz: a9760cb3d38f36b196bf0f3c22abee20e357bd1cd176c1385e36d00febf5ddbf07135a03a961454d65743063d6cb528f296836a19e042d062a5f557c8892652b
6
+ metadata.gz: 98c6446f4aaa3337e281099ab45bba98fdeae286c8332a753b943c0bbb086e37c026a72d870fe557c92d817eb8c9928d49a148b7c5bfac472d44bae5a09bda1c
7
+ data.tar.gz: e18ff914eaf377f8606c6b4766364678088cd1035384bd580d0ccad3b8bd6b920e513d11d63e9678c47d5e7bf35af8f34a2655377761e0eb5c71a9e758fe72e3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- core_merchant (0.2.2)
4
+ core_merchant (0.5.1)
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
+ # - `next_subscription_plan`: The plan to change to at next renewal (if any)
31
+ # - `status`: Current status of the subscription (see enum definition)
32
+ # - `start_date`: When the subscription started
33
+ # - `end_date`: When the subscription ended (or will end)
34
+ # - `trial_end_date`: End date of the trial period (if applicable)
35
+ # - `canceled_at`: When the subscription was canceled
36
+ # - `current_period_start`: Start of the current billing period
37
+ # - `current_period_end`: End of the current billing period
38
+ # - `pause_start_date`: When the subscription was paused
39
+ # - `pause_end_date`: When the paused subscription will resume
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.3.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -10,8 +10,12 @@ module CoreMerchant
10
10
 
11
11
  source_root File.expand_path("templates", __dir__)
12
12
 
13
+ class_option :customer_class, type: :string, required: true,
14
+ desc: "Name of your existing customer class, e.g. User"
15
+
13
16
  def copy_initializer
14
- template "core_merchant.rb", "config/initializers/core_merchant.rb"
17
+ @customer_class = options[:customer_class].classify
18
+ template "core_merchant.erb", "config/initializers/core_merchant.rb"
15
19
  end
16
20
 
17
21
  def copy_locales
@@ -26,6 +30,25 @@ module CoreMerchant
26
30
  migration_template "create_core_merchant_subscription_plans.erb",
27
31
  "db/migrate/create_core_merchant_subscription_plans.rb"
28
32
  end
33
+
34
+ def show_post_install
35
+ say "CoreMerchant has been successfully installed.", :green
36
+ say <<~MESSAGE
37
+ Customer class: #{@customer_class}. Please update this model to include the CoreMerchant::CustomerBehavior module.
38
+ MESSAGE
39
+ say "Please run `rails db:migrate` to create the subscription plans table."
40
+ end
41
+
42
+ def self.banner
43
+ "rails generate core_merchant:install --customer_class=User"
44
+ end
45
+
46
+ def self.description
47
+ <<~DESC
48
+ Installs CoreMerchant into your application with the specified customer class.
49
+ This could be User, Customer, or any other existing model in your application that represents a customer."
50
+ DESC
51
+ end
29
52
  end
30
53
  end
31
54
  end
@@ -0,0 +1,5 @@
1
+ # Created by: rails generate core_merchant:install --customer_class=<%= @customer_class %>
2
+
3
+ CoreMerchant.configure do |config|
4
+ config.customer_class = "<%= @customer_class %>"
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.3.0
4
+ version: 0.6.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-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,12 +130,14 @@ 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
- - lib/generators/core_merchant/templates/core_merchant.rb
140
+ - lib/generators/core_merchant/templates/core_merchant.erb
139
141
  - lib/generators/core_merchant/templates/create_core_merchant_subscription_plans.erb
140
142
  - sig/core_merchant.rbs
141
143
  homepage: https://github.com/theseyithan/core_merchant
@@ -1,4 +0,0 @@
1
- # Created by: rails generate core_merchant:install
2
- CoreMerchant.configure do |config|
3
- # config.customer_class = "User" # Uncomment and set to your class name that can represent a customer
4
- end