pricing_plans 0.5.0 → 0.7.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 +4 -7
- data/.simplecov +6 -5
- data/Appraisals +5 -1
- data/CHANGELOG.md +34 -0
- data/README.md +64 -1
- data/docs/01-define-pricing-plans.md +6 -1
- data/docs/03-model-helpers.md +16 -0
- data/docs/06-gem-compatibility.md +35 -0
- data/docs/07-repricing.md +103 -0
- data/docs/08-feature-passes.md +192 -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 +44 -0
- data/lib/generators/pricing_plans/install/templates/create_pricing_plans_tables.rb.erb +29 -8
- data/lib/generators/pricing_plans/install/templates/initializer.rb +11 -0
- data/lib/generators/pricing_plans/passes/passes_generator.rb +31 -0
- data/lib/generators/pricing_plans/passes/templates/add_feature_pass_limits.rb.erb +19 -0
- data/lib/pricing_plans/engine.rb +1 -0
- data/lib/pricing_plans/feature_access.rb +110 -0
- data/lib/pricing_plans/models/assignment.rb +2 -4
- data/lib/pricing_plans/models/feature_grant.rb +212 -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 +113 -7
- data/lib/pricing_plans/plan_owner.rb +97 -7
- data/lib/pricing_plans/plan_owner_identity.rb +30 -0
- data/lib/pricing_plans/plan_resolver.rb +53 -37
- data/lib/pricing_plans/version.rb +1 -1
- data/lib/pricing_plans.rb +6 -1
- metadata +14 -4
|
@@ -19,7 +19,7 @@ module PricingPlans
|
|
|
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
21
|
assignment = pricing_plan_override_for(plan_owner)
|
|
22
|
-
subscription = current_subscription_for(plan_owner)
|
|
22
|
+
subscription = current_subscription_for(plan_owner, preferred_plan_key: assignment&.plan_key)
|
|
23
23
|
|
|
24
24
|
if assignment
|
|
25
25
|
log_debug "[PricingPlans::PlanResolver] Returning explicit-override resolution: #{assignment.plan_key}"
|
|
@@ -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'}"
|
|
@@ -84,6 +72,20 @@ module PricingPlans
|
|
|
84
72
|
)
|
|
85
73
|
end
|
|
86
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
|
|
87
|
+
end
|
|
88
|
+
|
|
87
89
|
private
|
|
88
90
|
|
|
89
91
|
# Backward-compatible shim for tests that stub pay_available?
|
|
@@ -96,8 +98,7 @@ module PricingPlans
|
|
|
96
98
|
return nil unless plan_owner.respond_to?(:id)
|
|
97
99
|
|
|
98
100
|
assignment = Assignment.find_by(
|
|
99
|
-
|
|
100
|
-
plan_owner_id: plan_owner.id
|
|
101
|
+
PlanOwnerIdentity.conditions_for(plan_owner)
|
|
101
102
|
)
|
|
102
103
|
|
|
103
104
|
if assignment
|
|
@@ -109,7 +110,24 @@ module PricingPlans
|
|
|
109
110
|
assignment
|
|
110
111
|
end
|
|
111
112
|
|
|
112
|
-
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)
|
|
113
131
|
return nil unless plan_owner
|
|
114
132
|
|
|
115
133
|
pay_available = pay_available?
|
|
@@ -117,32 +135,30 @@ module PricingPlans
|
|
|
117
135
|
|
|
118
136
|
return nil unless pay_available
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# the same billing context wherever it is called.
|
|
123
|
-
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)
|
|
124
140
|
log_debug "[PricingPlans::PlanResolver] current_subscription_for returned: #{subscription ? subscription.class.name : 'nil'}"
|
|
125
141
|
subscription
|
|
126
142
|
end
|
|
127
143
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
Registry.plans.values.find do |plan|
|
|
131
|
-
stripe_price = plan.stripe_price
|
|
132
|
-
next unless stripe_price
|
|
144
|
+
def select_subscription(subscriptions, preferred_plan_key: nil)
|
|
145
|
+
subscriptions = Array(subscriptions)
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
stripe_price[:id] == processor_plan ||
|
|
139
|
-
stripe_price[:month] == processor_plan ||
|
|
140
|
-
stripe_price[:year] == processor_plan ||
|
|
141
|
-
stripe_price.values.include?(processor_plan)
|
|
142
|
-
else
|
|
143
|
-
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
|
|
144
151
|
end
|
|
152
|
+
return preferred if preferred
|
|
145
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)
|
|
146
162
|
end
|
|
147
163
|
end
|
|
148
164
|
end
|
data/lib/pricing_plans.rb
CHANGED
|
@@ -30,6 +30,9 @@ module PricingPlans
|
|
|
30
30
|
end
|
|
31
31
|
class InvalidOperation < Error; end
|
|
32
32
|
|
|
33
|
+
autoload :FeatureAccess, "pricing_plans/feature_access"
|
|
34
|
+
autoload :FeatureLimitExceeded, "pricing_plans/feature_access"
|
|
35
|
+
autoload :FeatureGrantConflict, "pricing_plans/feature_access"
|
|
33
36
|
autoload :Configuration, "pricing_plans/configuration"
|
|
34
37
|
autoload :Registry, "pricing_plans/registry"
|
|
35
38
|
autoload :Plan, "pricing_plans/plan"
|
|
@@ -37,6 +40,7 @@ module PricingPlans
|
|
|
37
40
|
autoload :DSL, "pricing_plans/dsl"
|
|
38
41
|
autoload :IntegerRefinements, "pricing_plans/integer_refinements"
|
|
39
42
|
autoload :PlanResolver, "pricing_plans/plan_resolver"
|
|
43
|
+
autoload :PlanOwnerIdentity, "pricing_plans/plan_owner_identity"
|
|
40
44
|
autoload :LegacyPlanAssignmentApi, "pricing_plans/legacy_plan_assignment_api"
|
|
41
45
|
autoload :PaySupport, "pricing_plans/pay_support"
|
|
42
46
|
autoload :LimitChecker, "pricing_plans/limit_checker"
|
|
@@ -61,6 +65,7 @@ module PricingPlans
|
|
|
61
65
|
autoload :EnforcementState, "pricing_plans/models/enforcement_state"
|
|
62
66
|
autoload :Usage, "pricing_plans/models/usage"
|
|
63
67
|
autoload :Assignment, "pricing_plans/models/assignment"
|
|
68
|
+
autoload :FeatureGrant, "pricing_plans/models/feature_grant"
|
|
64
69
|
|
|
65
70
|
class << self
|
|
66
71
|
attr_writer :configuration
|
|
@@ -73,7 +78,7 @@ module PricingPlans
|
|
|
73
78
|
# warnings independently and lets Rails include them in its deprecator
|
|
74
79
|
# collection.
|
|
75
80
|
def deprecator
|
|
76
|
-
@deprecator ||= ActiveSupport::Deprecation.new("0.
|
|
81
|
+
@deprecator ||= ActiveSupport::Deprecation.new("0.8.0", "pricing_plans")
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
def configure(&block)
|
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.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-09-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -77,15 +77,22 @@ 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
|
|
81
|
+
- docs/08-feature-passes.md
|
|
80
82
|
- docs/images/pricing_plans_ruby_rails_gem_pricing_table.jpg
|
|
81
83
|
- docs/images/pricing_plans_ruby_rails_gem_usage_alert_upgrade.jpg
|
|
82
84
|
- docs/images/pricing_plans_ruby_rails_gem_usage_meter.jpg
|
|
83
85
|
- docs/images/product_creation_blocked.jpg
|
|
86
|
+
- gemfiles/rails_7.1.gemfile
|
|
84
87
|
- gemfiles/rails_7.2.gemfile
|
|
85
88
|
- gemfiles/rails_8.1.gemfile
|
|
89
|
+
- lib/generators/pricing_plans/grants/grants_generator.rb
|
|
90
|
+
- lib/generators/pricing_plans/grants/templates/create_pricing_plans_feature_grants.rb.erb
|
|
86
91
|
- lib/generators/pricing_plans/install/install_generator.rb
|
|
87
92
|
- lib/generators/pricing_plans/install/templates/create_pricing_plans_tables.rb.erb
|
|
88
93
|
- lib/generators/pricing_plans/install/templates/initializer.rb
|
|
94
|
+
- lib/generators/pricing_plans/passes/passes_generator.rb
|
|
95
|
+
- lib/generators/pricing_plans/passes/templates/add_feature_pass_limits.rb.erb
|
|
89
96
|
- lib/pricing_plans.rb
|
|
90
97
|
- lib/pricing_plans/association_limit_registry.rb
|
|
91
98
|
- lib/pricing_plans/callbacks.rb
|
|
@@ -95,6 +102,7 @@ files:
|
|
|
95
102
|
- lib/pricing_plans/dsl.rb
|
|
96
103
|
- lib/pricing_plans/engine.rb
|
|
97
104
|
- lib/pricing_plans/exceeded_state_utils.rb
|
|
105
|
+
- lib/pricing_plans/feature_access.rb
|
|
98
106
|
- lib/pricing_plans/grace_manager.rb
|
|
99
107
|
- lib/pricing_plans/integer_refinements.rb
|
|
100
108
|
- lib/pricing_plans/job_guards.rb
|
|
@@ -103,12 +111,14 @@ files:
|
|
|
103
111
|
- lib/pricing_plans/limitable.rb
|
|
104
112
|
- lib/pricing_plans/models/assignment.rb
|
|
105
113
|
- lib/pricing_plans/models/enforcement_state.rb
|
|
114
|
+
- lib/pricing_plans/models/feature_grant.rb
|
|
106
115
|
- lib/pricing_plans/models/usage.rb
|
|
107
116
|
- lib/pricing_plans/overage_reporter.rb
|
|
108
117
|
- lib/pricing_plans/pay_support.rb
|
|
109
118
|
- lib/pricing_plans/period_calculator.rb
|
|
110
119
|
- lib/pricing_plans/plan.rb
|
|
111
120
|
- lib/pricing_plans/plan_owner.rb
|
|
121
|
+
- lib/pricing_plans/plan_owner_identity.rb
|
|
112
122
|
- lib/pricing_plans/plan_resolution.rb
|
|
113
123
|
- lib/pricing_plans/plan_resolver.rb
|
|
114
124
|
- lib/pricing_plans/price_components.rb
|
|
@@ -123,7 +133,7 @@ licenses:
|
|
|
123
133
|
- MIT
|
|
124
134
|
metadata:
|
|
125
135
|
homepage_uri: https://github.com/rameerez/pricing_plans
|
|
126
|
-
source_code_uri: https://github.com/rameerez/pricing_plans
|
|
136
|
+
source_code_uri: https://github.com/rameerez/pricing_plans/tree/v0.7.0
|
|
127
137
|
changelog_uri: https://github.com/rameerez/pricing_plans/blob/main/CHANGELOG.md
|
|
128
138
|
bug_tracker_uri: https://github.com/rameerez/pricing_plans/issues
|
|
129
139
|
documentation_uri: https://github.com/rameerez/pricing_plans#readme
|
|
@@ -142,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
142
152
|
- !ruby/object:Gem::Version
|
|
143
153
|
version: '0'
|
|
144
154
|
requirements: []
|
|
145
|
-
rubygems_version: 3.6.
|
|
155
|
+
rubygems_version: 3.6.2
|
|
146
156
|
specification_version: 4
|
|
147
157
|
summary: Define and enforce pricing plan limits (entitlements, quotas, feature gating)
|
|
148
158
|
in your Rails SaaS
|