pricing_plans 0.4.1 → 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 +4 -4
- data/.rubocop.yml +5 -8
- data/.simplecov +6 -5
- data/Appraisals +5 -1
- data/CHANGELOG.md +30 -0
- data/README.md +66 -6
- data/docs/01-define-pricing-plans.md +8 -3
- data/docs/03-model-helpers.md +79 -12
- data/docs/06-gem-compatibility.md +48 -1
- data/docs/07-repricing.md +98 -0
- data/gemfiles/rails_7.1.gemfile +26 -0
- data/gemfiles/rails_7.2.gemfile +3 -2
- data/gemfiles/rails_8.1.gemfile +2 -1
- data/lib/generators/pricing_plans/grants/grants_generator.rb +43 -0
- data/lib/generators/pricing_plans/grants/templates/create_pricing_plans_feature_grants.rb.erb +32 -0
- data/lib/generators/pricing_plans/install/templates/create_pricing_plans_tables.rb.erb +23 -10
- data/lib/generators/pricing_plans/install/templates/initializer.rb +11 -0
- data/lib/pricing_plans/configuration.rb +21 -0
- data/lib/pricing_plans/engine.rb +5 -0
- data/lib/pricing_plans/legacy_plan_assignment_api.rb +84 -0
- data/lib/pricing_plans/limitable.rb +14 -1
- data/lib/pricing_plans/models/assignment.rb +20 -6
- data/lib/pricing_plans/models/feature_grant.rb +139 -0
- data/lib/pricing_plans/overage_reporter.rb +7 -1
- data/lib/pricing_plans/pay_support.rb +92 -100
- data/lib/pricing_plans/plan.rb +108 -7
- data/lib/pricing_plans/plan_owner.rb +104 -10
- data/lib/pricing_plans/plan_owner_identity.rb +30 -0
- data/lib/pricing_plans/plan_resolution.rb +16 -1
- data/lib/pricing_plans/plan_resolver.rb +81 -45
- data/lib/pricing_plans/version.rb +1 -1
- data/lib/pricing_plans.rb +22 -0
- metadata +10 -3
|
@@ -18,11 +18,11 @@ module PricingPlans
|
|
|
18
18
|
def resolution_for(plan_owner)
|
|
19
19
|
log_debug "[PricingPlans::PlanResolver] resolution_for called for #{plan_owner.class.name}##{plan_owner.respond_to?(:id) ? plan_owner.id : 'N/A'}"
|
|
20
20
|
|
|
21
|
-
assignment =
|
|
22
|
-
subscription = current_subscription_for(plan_owner)
|
|
21
|
+
assignment = pricing_plan_override_for(plan_owner)
|
|
22
|
+
subscription = current_subscription_for(plan_owner, preferred_plan_key: assignment&.plan_key)
|
|
23
23
|
|
|
24
24
|
if assignment
|
|
25
|
-
log_debug "[PricingPlans::PlanResolver] Returning
|
|
25
|
+
log_debug "[PricingPlans::PlanResolver] Returning explicit-override resolution: #{assignment.plan_key}"
|
|
26
26
|
return PlanResolution.new(
|
|
27
27
|
plan: Registry.plan(assignment.plan_key),
|
|
28
28
|
source: :assignment,
|
|
@@ -31,20 +31,8 @@ module PricingPlans
|
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
log_debug "[PricingPlans::PlanResolver] resolution_for subscription processor_plan = #{processor_plan.inspect}"
|
|
37
|
-
|
|
38
|
-
if processor_plan && (plan = plan_from_processor_plan(processor_plan))
|
|
39
|
-
log_debug "[PricingPlans::PlanResolver] Returning subscription-backed resolution: #{plan.key}"
|
|
40
|
-
return PlanResolution.new(
|
|
41
|
-
plan: plan,
|
|
42
|
-
source: :subscription,
|
|
43
|
-
assignment: nil,
|
|
44
|
-
subscription: subscription
|
|
45
|
-
)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
34
|
+
subscription_resolution = subscription_resolution_for(subscription)
|
|
35
|
+
return subscription_resolution if subscription_resolution
|
|
48
36
|
|
|
49
37
|
default = Registry.default_plan
|
|
50
38
|
log_debug "[PricingPlans::PlanResolver] Returning default-backed resolution: #{default ? default.key : 'nil'}"
|
|
@@ -56,12 +44,46 @@ module PricingPlans
|
|
|
56
44
|
)
|
|
57
45
|
end
|
|
58
46
|
|
|
47
|
+
def override_pricing_plan_for!(plan_owner, plan_key, source:)
|
|
48
|
+
Assignment.create_or_update_pricing_plan_override_for!(
|
|
49
|
+
plan_owner,
|
|
50
|
+
plan_key,
|
|
51
|
+
source: source
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def clear_pricing_plan_override_for!(plan_owner)
|
|
56
|
+
Assignment.clear_pricing_plan_override_for!(plan_owner)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
59
|
def assign_plan_manually!(plan_owner, plan_key, source: "manual")
|
|
60
|
-
|
|
60
|
+
LegacyPlanAssignmentApi.create_or_update_override!(
|
|
61
|
+
plan_owner,
|
|
62
|
+
plan_key,
|
|
63
|
+
source: source,
|
|
64
|
+
called_method_name: "PricingPlans::PlanResolver.assign_plan_manually!"
|
|
65
|
+
)
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
def remove_manual_assignment!(plan_owner)
|
|
64
|
-
|
|
69
|
+
LegacyPlanAssignmentApi.clear_override!(
|
|
70
|
+
plan_owner,
|
|
71
|
+
called_method_name: "PricingPlans::PlanResolver.remove_manual_assignment!"
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Resolve a processor price identifier to the configured plan that owns
|
|
76
|
+
# it. Public so entitlement provenance can verify that an underlying
|
|
77
|
+
# subscription belongs to the same plan as a manual override.
|
|
78
|
+
def plan_for_processor_plan(processor_plan)
|
|
79
|
+
return nil if processor_plan.blank?
|
|
80
|
+
|
|
81
|
+
Registry.plans.values.find do |plan|
|
|
82
|
+
stripe_price = plan.stripe_price
|
|
83
|
+
next unless stripe_price
|
|
84
|
+
|
|
85
|
+
stripe_price.is_a?(Hash) ? stripe_price.value?(processor_plan) : stripe_price == processor_plan
|
|
86
|
+
end
|
|
65
87
|
end
|
|
66
88
|
|
|
67
89
|
private
|
|
@@ -71,25 +93,41 @@ module PricingPlans
|
|
|
71
93
|
PaySupport.pay_available?
|
|
72
94
|
end
|
|
73
95
|
|
|
74
|
-
def
|
|
75
|
-
log_debug "[PricingPlans::PlanResolver] Checking for
|
|
96
|
+
def pricing_plan_override_for(plan_owner)
|
|
97
|
+
log_debug "[PricingPlans::PlanResolver] Checking for an explicit pricing plan override..."
|
|
76
98
|
return nil unless plan_owner.respond_to?(:id)
|
|
77
99
|
|
|
78
100
|
assignment = Assignment.find_by(
|
|
79
|
-
|
|
80
|
-
plan_owner_id: plan_owner.id
|
|
101
|
+
PlanOwnerIdentity.conditions_for(plan_owner)
|
|
81
102
|
)
|
|
82
103
|
|
|
83
104
|
if assignment
|
|
84
|
-
log_debug "[PricingPlans::PlanResolver] Found
|
|
105
|
+
log_debug "[PricingPlans::PlanResolver] Found explicit pricing plan override: #{assignment.plan_key}"
|
|
85
106
|
else
|
|
86
|
-
log_debug "[PricingPlans::PlanResolver] No
|
|
107
|
+
log_debug "[PricingPlans::PlanResolver] No explicit pricing plan override found"
|
|
87
108
|
end
|
|
88
109
|
|
|
89
110
|
assignment
|
|
90
111
|
end
|
|
91
112
|
|
|
92
|
-
def
|
|
113
|
+
def subscription_resolution_for(subscription)
|
|
114
|
+
return nil unless subscription.respond_to?(:processor_plan)
|
|
115
|
+
|
|
116
|
+
processor_plan = subscription.processor_plan
|
|
117
|
+
log_debug "[PricingPlans::PlanResolver] resolution_for subscription processor_plan = #{processor_plan.inspect}"
|
|
118
|
+
plan = plan_for_processor_plan(processor_plan)
|
|
119
|
+
return nil unless plan
|
|
120
|
+
|
|
121
|
+
log_debug "[PricingPlans::PlanResolver] Returning subscription-backed resolution: #{plan.key}"
|
|
122
|
+
PlanResolution.new(
|
|
123
|
+
plan: plan,
|
|
124
|
+
source: :subscription,
|
|
125
|
+
assignment: nil,
|
|
126
|
+
subscription: subscription
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def current_subscription_for(plan_owner, preferred_plan_key: nil)
|
|
93
131
|
return nil unless plan_owner
|
|
94
132
|
|
|
95
133
|
pay_available = pay_available?
|
|
@@ -97,32 +135,30 @@ module PricingPlans
|
|
|
97
135
|
|
|
98
136
|
return nil unless pay_available
|
|
99
137
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# the same billing context wherever it is called.
|
|
103
|
-
subscription = PaySupport.current_subscription_for(plan_owner)
|
|
138
|
+
subscriptions = PaySupport.current_subscriptions_for(plan_owner)
|
|
139
|
+
subscription = select_subscription(subscriptions, preferred_plan_key: preferred_plan_key)
|
|
104
140
|
log_debug "[PricingPlans::PlanResolver] current_subscription_for returned: #{subscription ? subscription.class.name : 'nil'}"
|
|
105
141
|
subscription
|
|
106
142
|
end
|
|
107
143
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
Registry.plans.values.find do |plan|
|
|
111
|
-
stripe_price = plan.stripe_price
|
|
112
|
-
next unless stripe_price
|
|
144
|
+
def select_subscription(subscriptions, preferred_plan_key: nil)
|
|
145
|
+
subscriptions = Array(subscriptions)
|
|
113
146
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
stripe_price[:id] == processor_plan ||
|
|
119
|
-
stripe_price[:month] == processor_plan ||
|
|
120
|
-
stripe_price[:year] == processor_plan ||
|
|
121
|
-
stripe_price.values.include?(processor_plan)
|
|
122
|
-
else
|
|
123
|
-
false
|
|
147
|
+
if preferred_plan_key
|
|
148
|
+
preferred = subscriptions.find do |subscription|
|
|
149
|
+
subscription_plan = plan_for_subscription(subscription)
|
|
150
|
+
subscription_plan&.key == preferred_plan_key.to_sym
|
|
124
151
|
end
|
|
152
|
+
return preferred if preferred
|
|
125
153
|
end
|
|
154
|
+
|
|
155
|
+
subscriptions.find { |subscription| plan_for_subscription(subscription) } || subscriptions.first
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def plan_for_subscription(subscription)
|
|
159
|
+
return nil unless subscription.respond_to?(:processor_plan)
|
|
160
|
+
|
|
161
|
+
plan_for_processor_plan(subscription.processor_plan)
|
|
126
162
|
end
|
|
127
163
|
end
|
|
128
164
|
end
|
data/lib/pricing_plans.rb
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "pricing_plans/version"
|
|
4
|
+
require "active_support/deprecation"
|
|
4
5
|
require_relative "pricing_plans/engine" if defined?(Rails::Engine)
|
|
5
6
|
|
|
6
7
|
module PricingPlans
|
|
7
8
|
class Error < StandardError; end
|
|
8
9
|
class ConfigurationError < Error; end
|
|
9
10
|
class PlanNotFoundError < Error; end
|
|
11
|
+
|
|
12
|
+
class LegacyDefaultPlanAssignmentError < Error
|
|
13
|
+
attr_reader :configured_default_plan_key, :legacy_assignment_method_name
|
|
14
|
+
|
|
15
|
+
def initialize(message, configured_default_plan_key:, legacy_assignment_method_name:)
|
|
16
|
+
super(message)
|
|
17
|
+
@configured_default_plan_key = configured_default_plan_key.to_sym
|
|
18
|
+
@legacy_assignment_method_name = legacy_assignment_method_name
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
class FeatureDenied < Error
|
|
11
23
|
attr_reader :feature_key, :plan_owner
|
|
12
24
|
|
|
@@ -25,6 +37,8 @@ module PricingPlans
|
|
|
25
37
|
autoload :DSL, "pricing_plans/dsl"
|
|
26
38
|
autoload :IntegerRefinements, "pricing_plans/integer_refinements"
|
|
27
39
|
autoload :PlanResolver, "pricing_plans/plan_resolver"
|
|
40
|
+
autoload :PlanOwnerIdentity, "pricing_plans/plan_owner_identity"
|
|
41
|
+
autoload :LegacyPlanAssignmentApi, "pricing_plans/legacy_plan_assignment_api"
|
|
28
42
|
autoload :PaySupport, "pricing_plans/pay_support"
|
|
29
43
|
autoload :LimitChecker, "pricing_plans/limit_checker"
|
|
30
44
|
autoload :LimitableRegistry, "pricing_plans/limit_checker"
|
|
@@ -48,6 +62,7 @@ module PricingPlans
|
|
|
48
62
|
autoload :EnforcementState, "pricing_plans/models/enforcement_state"
|
|
49
63
|
autoload :Usage, "pricing_plans/models/usage"
|
|
50
64
|
autoload :Assignment, "pricing_plans/models/assignment"
|
|
65
|
+
autoload :FeatureGrant, "pricing_plans/models/feature_grant"
|
|
51
66
|
|
|
52
67
|
class << self
|
|
53
68
|
attr_writer :configuration
|
|
@@ -56,6 +71,13 @@ module PricingPlans
|
|
|
56
71
|
@configuration ||= Configuration.new
|
|
57
72
|
end
|
|
58
73
|
|
|
74
|
+
# A gem-specific deprecator lets host applications configure pricing_plans
|
|
75
|
+
# warnings independently and lets Rails include them in its deprecator
|
|
76
|
+
# collection.
|
|
77
|
+
def deprecator
|
|
78
|
+
@deprecator ||= ActiveSupport::Deprecation.new("0.7.0", "pricing_plans")
|
|
79
|
+
end
|
|
80
|
+
|
|
59
81
|
def configure(&block)
|
|
60
82
|
# Support both styles simultaneously inside the block:
|
|
61
83
|
# - Bare DSL: plan :free { ... }
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pricing_plans
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-09-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -77,12 +77,16 @@ files:
|
|
|
77
77
|
- docs/04-views.md
|
|
78
78
|
- docs/05-semantic-pricing.md
|
|
79
79
|
- docs/06-gem-compatibility.md
|
|
80
|
+
- docs/07-repricing.md
|
|
80
81
|
- docs/images/pricing_plans_ruby_rails_gem_pricing_table.jpg
|
|
81
82
|
- docs/images/pricing_plans_ruby_rails_gem_usage_alert_upgrade.jpg
|
|
82
83
|
- docs/images/pricing_plans_ruby_rails_gem_usage_meter.jpg
|
|
83
84
|
- docs/images/product_creation_blocked.jpg
|
|
85
|
+
- gemfiles/rails_7.1.gemfile
|
|
84
86
|
- gemfiles/rails_7.2.gemfile
|
|
85
87
|
- gemfiles/rails_8.1.gemfile
|
|
88
|
+
- lib/generators/pricing_plans/grants/grants_generator.rb
|
|
89
|
+
- lib/generators/pricing_plans/grants/templates/create_pricing_plans_feature_grants.rb.erb
|
|
86
90
|
- lib/generators/pricing_plans/install/install_generator.rb
|
|
87
91
|
- lib/generators/pricing_plans/install/templates/create_pricing_plans_tables.rb.erb
|
|
88
92
|
- lib/generators/pricing_plans/install/templates/initializer.rb
|
|
@@ -98,16 +102,19 @@ files:
|
|
|
98
102
|
- lib/pricing_plans/grace_manager.rb
|
|
99
103
|
- lib/pricing_plans/integer_refinements.rb
|
|
100
104
|
- lib/pricing_plans/job_guards.rb
|
|
105
|
+
- lib/pricing_plans/legacy_plan_assignment_api.rb
|
|
101
106
|
- lib/pricing_plans/limit_checker.rb
|
|
102
107
|
- lib/pricing_plans/limitable.rb
|
|
103
108
|
- lib/pricing_plans/models/assignment.rb
|
|
104
109
|
- lib/pricing_plans/models/enforcement_state.rb
|
|
110
|
+
- lib/pricing_plans/models/feature_grant.rb
|
|
105
111
|
- lib/pricing_plans/models/usage.rb
|
|
106
112
|
- lib/pricing_plans/overage_reporter.rb
|
|
107
113
|
- lib/pricing_plans/pay_support.rb
|
|
108
114
|
- lib/pricing_plans/period_calculator.rb
|
|
109
115
|
- lib/pricing_plans/plan.rb
|
|
110
116
|
- lib/pricing_plans/plan_owner.rb
|
|
117
|
+
- lib/pricing_plans/plan_owner_identity.rb
|
|
111
118
|
- lib/pricing_plans/plan_resolution.rb
|
|
112
119
|
- lib/pricing_plans/plan_resolver.rb
|
|
113
120
|
- lib/pricing_plans/price_components.rb
|
|
@@ -122,7 +129,7 @@ licenses:
|
|
|
122
129
|
- MIT
|
|
123
130
|
metadata:
|
|
124
131
|
homepage_uri: https://github.com/rameerez/pricing_plans
|
|
125
|
-
source_code_uri: https://github.com/rameerez/pricing_plans
|
|
132
|
+
source_code_uri: https://github.com/rameerez/pricing_plans/tree/v0.6.0
|
|
126
133
|
changelog_uri: https://github.com/rameerez/pricing_plans/blob/main/CHANGELOG.md
|
|
127
134
|
bug_tracker_uri: https://github.com/rameerez/pricing_plans/issues
|
|
128
135
|
documentation_uri: https://github.com/rameerez/pricing_plans#readme
|