pricing_plans 0.3.2 → 0.4.1

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: 5d4d6193dae586244d9a5eb2780ca9f1b2447636fac1b832cca17832adb19280
4
- data.tar.gz: aacfdf85b8f20fca5d2c81d4118a053e47f2636304330c399804d978035b2275
3
+ metadata.gz: 1a22c4cf12c06ba9c2e7d649811c286044ae8ff5f9586bea223584fa10b054e4
4
+ data.tar.gz: 55ee30afed8af0fd32018ca6d706ab0efe4cd745346fc24787d90ddbcf815071
5
5
  SHA512:
6
- metadata.gz: b245d32bc4c997d3422aae208fbdd345d06cc6b4473fc2c1712cbabf5be48eb0962879de5dd3410e07191a629f6ea5df296f09525841d60aaa519f978619128a
7
- data.tar.gz: 65ac67157b979b0d84590463082d71101cd5ff0d73afc528e54357eecb0e4db365f03a50e57ef5949db699f50d1a010ec72f3e211312dbeeae030bbc9c119e96
6
+ metadata.gz: bf40f8507ad67ca84c0e46e390db6fdb65e13ea247f5f1a12446c88ec6eaff59d0b519f2951538a59858f2b4222c4257112c5f13d4697a78fc84124e6c713782
7
+ data.tar.gz: 2a3906414a684300257e1b20b1f4b3cbd278ef7631c7a4f7c675f006483cb107145c323e61072e0fe2fe91976ca140b86dc617d3a14cc1495a010cf8c897c19a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [0.4.1] - 2026-08-24
2
+
3
+ - **Never 500 a pricing page when Stripe is unreachable**: `Plan#currency_symbol` was the one Stripe lookup without a rescue — a Stripe outage, rate limit, or missing API key (any test/CI environment) raised straight through the pricing page. It now degrades to `default_currency_symbol` like every other presentation method (#24)
4
+ - **`price` and `stripe_price` can be declared together**: the numeric price is the local source of truth for display and plan comparison (no network in the request path); the Stripe id stays the billing identity for checkout and subscription matching. Previously `validate_pricing!` forced a choice, which meant Stripe-priced plans had no local number — and when the live lookup failed, `comparable_price_cents` silently became 0 for every paid plan, so `upgrade_from?` and `next_upgrade_plan` stopped offering upgrades with no error and no log line (#25)
5
+ - Only `price_string` remains exclusive, since a label cannot be compared numerically
6
+ - Known edge: for a both-declared plan the yearly figure derives as 12x monthly; a discounted yearly Stripe price needs `price_components_resolver`
7
+
8
+ ## [0.4.0] - 2026-03-19
9
+
10
+ - **Add plan provenance helpers**: `current_pricing_plan_resolution`, `current_pricing_plan_source`, and `PlanResolver.resolution_for(plan_owner)` now expose whether the effective plan comes from a manual assignment, a Pay subscription, or the default plan
11
+ - **Preserve underlying billing context**: resolution objects include the current subscription when present, even when a manual assignment overrides it for entitlements
12
+ - **Clarify effective plan vs billing state**: docs now explicitly distinguish the effective pricing plan from Pay/Stripe subscription status
13
+
1
14
  ## [0.3.2] - 2026-02-25
2
15
 
3
16
  - **Fix stale grace warnings after plan upgrades**: Grace/blocked flags now auto-clear when usage drops below limit (self-healing state)
data/README.md CHANGED
@@ -165,6 +165,7 @@ The `pricing_plans` gem needs three new models in the schema in order to work: `
165
165
  - `PricingPlans::Assignment` allow manual plan overrides independent of billing system (or before you wire up Stripe/Pay). Great for admin toggles, trials, demos.
166
166
  - What: The arbitrary `plan_key` and a `source` label (default "manual"). Unique per plan_owner.
167
167
  - How it's used: `PlanResolver` checks manual assignment → Pay → default plan. Manual assignments (admin overrides) take precedence over subscription-based plans. You can call `assign_pricing_plan!` and `remove_pricing_plan!` on the plan_owner.
168
+ - Provenance helpers: `current_pricing_plan_source` tells you whether the effective plan came from `:assignment`, `:subscription`, or `:default`, and `current_pricing_plan_resolution` exposes the assignment and current subscription objects when you need both entitlement and billing context.
168
169
 
169
170
  - `PricingPlans::EnforcementState` tracks per-plan_owner per-limit enforcement state for persistent caps and per-period allowances (grace/warnings/block state) in a race-safe way.
170
171
  - What: `exceeded_at`, `blocked_at`, last warning info, and a small JSON `data` column where we persist plan-derived parameters like grace period seconds.
data/context7.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "url": "https://context7.com/rameerez/pricing_plans",
3
+ "public_key": "pk_HibNJE5rTFvy1txHHXUot"
4
+ }
@@ -493,6 +493,26 @@ end
493
493
 
494
494
  `stripe_price` accepts String or Hash (e.g., `{ month:, year:, id: }`) and the `pricing_plans` PlanResolver maps against Pay's `subscription.processor_plan`.
495
495
 
496
+ ### Declaring both a `price` and a `stripe_price`
497
+
498
+ You can declare both, and it's recommended for paid plans:
499
+
500
+ ```ruby
501
+ plan :pro do
502
+ price 29
503
+ stripe_price month: "price_123abc", year: "price_456def"
504
+ end
505
+ ```
506
+
507
+ They answer different questions, so they're not alternatives:
508
+
509
+ - `stripe_price` is the **billing identity**: what checkout charges, and what a Pay subscription is matched against.
510
+ - `price` is the **local source of truth for display and plan comparison**.
511
+
512
+ When both are set, the number wins for `price_label`, `price_components`, `currency_symbol` and upgrade/downgrade comparisons, so pricing pages and upgrade CTAs render without a single Stripe API call. Without a numeric price, all of that depends on a live `Stripe::Price.retrieve`; if that fails (Stripe unreachable, missing API key, cold cache), every paid plan compares as $0 and upgrade prompts silently vanish.
513
+
514
+ `price_string` remains exclusive with both: it's a label ("Contact us"), not a number, so it can't be compared.
515
+
496
516
 
497
517
  ## Example: define an enterprise plan
498
518
 
@@ -295,10 +295,32 @@ You can also use the top-level equivalents if you prefer: `PricingPlans.severity
295
295
  You can also check and override the current pricing plan for any user, which comes handy as an admin:
296
296
  ```ruby
297
297
  user.current_pricing_plan # => PricingPlans::Plan
298
+ user.current_pricing_plan_source # => :assignment, :subscription, :default
299
+ user.current_pricing_plan_resolution # => PricingPlans::PlanResolution
298
300
  user.assign_pricing_plan!(:pro) # manual assignment override
299
301
  user.remove_pricing_plan! # remove manual override (fallback to default)
300
302
  ```
301
303
 
304
+ **Performance note:** Each call to `current_pricing_plan`, `current_pricing_plan_source`, or `current_pricing_plan_resolution` performs a fresh database lookup. If you need both the plan and its provenance, call `current_pricing_plan_resolution` once and read both values from that object — this avoids duplicate queries.
305
+
306
+ If you need the full provenance, use the resolution object:
307
+
308
+ ```ruby
309
+ resolution = user.current_pricing_plan_resolution
310
+
311
+ resolution.plan.key # => :enterprise
312
+ resolution.source # => :assignment
313
+ resolution.assignment # => PricingPlans::Assignment | nil
314
+ resolution.assignment_source # => "admin" | "manual" | nil
315
+ resolution.subscription # => Pay subscription | nil
316
+ ```
317
+
318
+ This distinction matters: the **effective pricing plan** is what controls entitlements and limits inside your app. The **Pay/Stripe subscription state** is billing-facing. A manual assignment may intentionally override the subscription-backed plan while still leaving the underlying subscription present for billing operations.
319
+
320
+ **Edge case:** `source` can be `:default` even when `subscription` is non-nil. This happens when a Pay subscription exists but its `processor_plan` (Stripe price ID) doesn't map to any plan in your registry. The subscription is preserved for billing context, but the effective plan falls back to your configured default.
321
+
322
+ `resolution.to_h` is handy for inspection and tests, but it preserves the raw `plan`, `assignment`, and `subscription` objects. If you need a JSON-safe payload, build one explicitly from the scalar fields you care about.
323
+
302
324
  ### Misc
303
325
 
304
326
  ```ruby
@@ -311,7 +333,8 @@ And finally, you get very thin convenient wrappers if you're using the `pay` gem
311
333
  ```ruby
312
334
  # Pay (Stripe) convenience (returns false/nil when Pay is absent)
313
335
  # Note: this is billing-facing state, distinct from our in-app
314
- # enforcement grace which is tracked per-limit.
336
+ # enforcement grace which is tracked per-limit, and distinct from
337
+ # the effective plan resolved by current_pricing_plan.
315
338
  user.pay_subscription_active? # => true/false
316
339
  user.pay_on_trial? # => true/false
317
340
  user.pay_on_grace_period? # => true/false
data/docs/04-views.md CHANGED
@@ -15,7 +15,7 @@ Each `PricingPlans::Plan` responds to:
15
15
  - `plan.name`
16
16
  - `plan.description`
17
17
  - `plan.bullets` → Array of strings
18
- - `plan.price_label` → The `price` or `price_string` you've defined for the plan. If `stripe_price` is set and the Stripe gem is available, it auto-fetches the live price from Stripe. You can override or disable this.
18
+ - `plan.price_label` → The `price` or `price_string` you've defined for the plan. If `stripe_price` is set (and no numeric `price` is declared) and the Stripe gem is available, it auto-fetches the live price from Stripe. You can override or disable this.
19
19
  - `plan.cta_text`
20
20
  - `plan.cta_url`
21
21
  - `plan.metadata` → Optional hash for UI/presentation attributes (icons, colors, badges)
@@ -42,6 +42,7 @@ plan.currency_symbol # "$" or derived from Stripe
42
42
  Notes:
43
43
 
44
44
  - If `stripe_price` is configured, we derive cents, currency, and interval from the Stripe Price (and cache it).
45
+ - If a numeric `price` is declared alongside `stripe_price`, the local number wins and no Stripe call is made (the yearly interval is then derived as 12× the monthly number; use `price_components_resolver` if your yearly price is discounted).
45
46
  - If `price 0` (free), we return components with `present? == true`, amount 0 and the configured default currency symbol.
46
47
  - If only `price_string` is set (e.g., "Contact us"), components return `present? == false`, `label == price_string`.
47
48
 
@@ -139,6 +140,8 @@ end
139
140
 
140
141
  By default, if a plan has `stripe_price` configured and the `stripe` gem is present, we auto-fetch the Stripe Price and render a friendly label (e.g., `$29/mo`). This mirrors Pay’s use of Stripe Prices.
141
142
 
143
+ Declaring a numeric `price` next to `stripe_price` opts that plan out: the local number is rendered and no Stripe call is made.
144
+
142
145
 
143
146
  To disable auto-fetching globally:
144
147
 
@@ -15,7 +15,8 @@ module PricingPlans
15
15
  def subscription_active_for?(plan_owner)
16
16
  return false unless plan_owner
17
17
 
18
- log_debug "[PricingPlans::PaySupport] subscription_active_for? called for #{plan_owner.class.name}##{plan_owner.id}"
18
+ owner_id = plan_owner.respond_to?(:id) ? plan_owner.id : "N/A"
19
+ log_debug "[PricingPlans::PaySupport] subscription_active_for? called for #{plan_owner.class.name}##{owner_id}"
19
20
 
20
21
  # Prefer Pay's official API on the payment_processor
21
22
  if plan_owner.respond_to?(:payment_processor) && (pp = plan_owner.payment_processor)
@@ -66,7 +67,8 @@ module PricingPlans
66
67
  def current_subscription_for(plan_owner)
67
68
  return nil unless pay_available?
68
69
 
69
- log_debug "[PricingPlans::PaySupport] current_subscription_for called for #{plan_owner.class.name}##{plan_owner.id}"
70
+ owner_id = plan_owner.respond_to?(:id) ? plan_owner.id : "N/A"
71
+ log_debug "[PricingPlans::PaySupport] current_subscription_for called for #{plan_owner.class.name}##{owner_id}"
70
72
 
71
73
  # Prefer Pay's payment_processor API
72
74
  if plan_owner.respond_to?(:payment_processor) && (pp = plan_owner.payment_processor)
@@ -301,9 +301,11 @@ module PricingPlans
301
301
 
302
302
  # Human label to display price in UIs. Prefers explicit string, then numeric, else contact.
303
303
  def price_label
304
- # Auto-fetch from processor (Stripe) if enabled and plan has stripe_price
304
+ # Auto-fetch from processor (Stripe) if enabled and plan has stripe_price.
305
+ # A locally declared numeric price wins: it is the source of truth for
306
+ # display, and honoring it keeps rendering off the network entirely.
305
307
  cfg = PricingPlans.configuration
306
- if cfg&.auto_price_labels_from_processor && stripe_price
308
+ if cfg&.auto_price_labels_from_processor && stripe_price && price.nil?
307
309
  begin
308
310
  if defined?(::Stripe)
309
311
  price_id = stripe_price.is_a?(Hash) ? (stripe_price[:id] || stripe_price[:month] || stripe_price[:year]) : stripe_price
@@ -443,11 +445,17 @@ module PricingPlans
443
445
  end
444
446
 
445
447
  def currency_symbol
446
- if stripe_price
448
+ # A locally declared numeric price is rendered in the configured currency
449
+ # (see #price_components), so don't ask Stripe when we have one.
450
+ if stripe_price && price.nil?
447
451
  # Try to derive from Stripe API/cache; fall back to default
448
- pr = fetch_stripe_price_record(preferred_price_id(:month) || preferred_price_id(:year))
449
- if pr
450
- return currency_symbol_from(pr)
452
+ begin
453
+ pr = fetch_stripe_price_record(preferred_price_id(:month) || preferred_price_id(:year))
454
+ if pr
455
+ return currency_symbol_from(pr)
456
+ end
457
+ rescue StandardError
458
+ # Stripe unreachable, rate-limited or unconfigured: never take down a pricing page
451
459
  end
452
460
  end
453
461
  PricingPlans.configuration.default_currency_symbol
@@ -544,8 +552,12 @@ module PricingPlans
544
552
  end
545
553
 
546
554
  def validate_pricing!
547
- pricing_fields = [@price, @price_string, @stripe_price].compact
548
- if pricing_fields.size > 1
555
+ # `price` and `stripe_price` are not alternatives: the number is the local
556
+ # source of truth for display and plan comparison (resolved with no network
557
+ # call), while the Stripe id stays the billing identity used by checkout and
558
+ # by subscription -> plan matching. Only `price_string` remains exclusive,
559
+ # since it is a label that cannot be compared numerically.
560
+ if @price_string && (@price || @stripe_price)
549
561
  raise ConfigurationError, "Plan #{@key} can only have one of: price, price_string, or stripe_price"
550
562
  end
551
563
  end
@@ -198,6 +198,14 @@ module PricingPlans
198
198
  PlanResolver.effective_plan_for(self)
199
199
  end
200
200
 
201
+ def current_pricing_plan_resolution
202
+ PlanResolver.resolution_for(self)
203
+ end
204
+
205
+ def current_pricing_plan_source
206
+ current_pricing_plan_resolution.source
207
+ end
208
+
201
209
  def on_free_plan?
202
210
  plan = current_pricing_plan || PricingPlans::Registry.default_plan
203
211
  plan&.free? || false
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PricingPlans
4
+ class PlanResolution < Struct.new(:plan, :source, :assignment, :subscription, keyword_init: true)
5
+ SOURCES = [:assignment, :subscription, :default].freeze
6
+
7
+ def initialize(**attributes)
8
+ super
9
+
10
+ unless SOURCES.include?(source)
11
+ raise ArgumentError, "Invalid source: #{source.inspect}. Must be one of: #{SOURCES.inspect}"
12
+ end
13
+
14
+ freeze
15
+ end
16
+
17
+ def assignment?
18
+ source == :assignment
19
+ end
20
+
21
+ def subscription?
22
+ source == :subscription
23
+ end
24
+
25
+ def default?
26
+ source == :default
27
+ end
28
+
29
+ def plan_key
30
+ plan&.key
31
+ end
32
+
33
+ def assignment_source
34
+ assignment&.source
35
+ end
36
+
37
+ # Extends Struct#to_h with derived fields.
38
+ # Note: this preserves the raw plan / assignment / subscription objects.
39
+ def to_h
40
+ super.merge(
41
+ plan_key: plan_key,
42
+ assignment_source: assignment_source
43
+ )
44
+ end
45
+ end
46
+ end
@@ -8,43 +8,52 @@ module PricingPlans
8
8
  end
9
9
 
10
10
  def effective_plan_for(plan_owner)
11
- log_debug "[PricingPlans::PlanResolver] effective_plan_for called for #{plan_owner.class.name}##{plan_owner.respond_to?(:id) ? plan_owner.id : 'N/A'}"
11
+ resolution_for(plan_owner).plan
12
+ end
12
13
 
13
- # 1. Check manual assignment FIRST (admin overrides take precedence)
14
- log_debug "[PricingPlans::PlanResolver] Checking for manual assignment..."
15
- if plan_owner.respond_to?(:id)
16
- assignment = Assignment.find_by(
17
- plan_owner_type: plan_owner.class.name,
18
- plan_owner_id: plan_owner.id
14
+ def plan_key_for(plan_owner)
15
+ resolution_for(plan_owner).plan_key
16
+ end
17
+
18
+ def resolution_for(plan_owner)
19
+ log_debug "[PricingPlans::PlanResolver] resolution_for called for #{plan_owner.class.name}##{plan_owner.respond_to?(:id) ? plan_owner.id : 'N/A'}"
20
+
21
+ assignment = assignment_for(plan_owner)
22
+ subscription = current_subscription_for(plan_owner)
23
+
24
+ if assignment
25
+ log_debug "[PricingPlans::PlanResolver] Returning assignment-backed resolution: #{assignment.plan_key}"
26
+ return PlanResolution.new(
27
+ plan: Registry.plan(assignment.plan_key),
28
+ source: :assignment,
29
+ assignment: assignment,
30
+ subscription: subscription
19
31
  )
20
- if assignment
21
- log_debug "[PricingPlans::PlanResolver] Found manual assignment: #{assignment.plan_key}"
22
- return Registry.plan(assignment.plan_key)
23
- else
24
- log_debug "[PricingPlans::PlanResolver] No manual assignment found"
25
- end
26
32
  end
27
33
 
28
- # 2. Check Pay subscription status
29
- pay_available = PaySupport.pay_available?
30
- log_debug "[PricingPlans::PlanResolver] PaySupport.pay_available? = #{pay_available}"
31
- log_debug "[PricingPlans::PlanResolver] defined?(Pay) = #{defined?(Pay)}"
32
-
33
- if pay_available
34
- log_debug "[PricingPlans::PlanResolver] Calling resolve_plan_from_pay..."
35
- plan_from_pay = resolve_plan_from_pay(plan_owner)
36
- log_debug "[PricingPlans::PlanResolver] resolve_plan_from_pay returned: #{plan_from_pay ? plan_from_pay.key : 'nil'}"
37
- return plan_from_pay if plan_from_pay
34
+ if subscription
35
+ processor_plan = subscription.processor_plan
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
38
47
  end
39
48
 
40
- # 3. Fall back to default plan
41
49
  default = Registry.default_plan
42
- log_debug "[PricingPlans::PlanResolver] Returning default plan: #{default ? default.key : 'nil'}"
43
- default
44
- end
45
-
46
- def plan_key_for(plan_owner)
47
- effective_plan_for(plan_owner)&.key
50
+ log_debug "[PricingPlans::PlanResolver] Returning default-backed resolution: #{default ? default.key : 'nil'}"
51
+ PlanResolution.new(
52
+ plan: default,
53
+ source: :default,
54
+ assignment: nil,
55
+ subscription: subscription
56
+ )
48
57
  end
49
58
 
50
59
  def assign_plan_manually!(plan_owner, plan_key, source: "manual")
@@ -62,46 +71,38 @@ module PricingPlans
62
71
  PaySupport.pay_available?
63
72
  end
64
73
 
65
- def resolve_plan_from_pay(plan_owner)
66
- log_debug "[PricingPlans::PlanResolver] resolve_plan_from_pay: checking if plan_owner has payment_processor or Pay methods..."
67
-
68
- # Check if plan_owner has payment_processor (preferred) or Pay methods directly (fallback)
69
- has_payment_processor = plan_owner.respond_to?(:payment_processor)
70
- has_pay_methods = plan_owner.respond_to?(:subscribed?) ||
71
- plan_owner.respond_to?(:on_trial?) ||
72
- plan_owner.respond_to?(:on_grace_period?) ||
73
- plan_owner.respond_to?(:subscriptions)
74
+ def assignment_for(plan_owner)
75
+ log_debug "[PricingPlans::PlanResolver] Checking for manual assignment..."
76
+ return nil unless plan_owner.respond_to?(:id)
74
77
 
75
- log_debug "[PricingPlans::PlanResolver] has_payment_processor? #{has_payment_processor}"
76
- log_debug "[PricingPlans::PlanResolver] has_pay_methods? #{has_pay_methods}"
78
+ assignment = Assignment.find_by(
79
+ plan_owner_type: plan_owner.class.name,
80
+ plan_owner_id: plan_owner.id
81
+ )
77
82
 
78
- # PaySupport will handle both payment_processor and direct Pay methods
79
- return nil unless has_payment_processor || has_pay_methods
83
+ if assignment
84
+ log_debug "[PricingPlans::PlanResolver] Found manual assignment: #{assignment.plan_key}"
85
+ else
86
+ log_debug "[PricingPlans::PlanResolver] No manual assignment found"
87
+ end
80
88
 
81
- # Check if plan_owner has active subscription, trial, or grace period
82
- log_debug "[PricingPlans::PlanResolver] Calling PaySupport.subscription_active_for?..."
83
- is_active = PaySupport.subscription_active_for?(plan_owner)
84
- log_debug "[PricingPlans::PlanResolver] subscription_active_for? returned: #{is_active}"
89
+ assignment
90
+ end
85
91
 
86
- if is_active
87
- log_debug "[PricingPlans::PlanResolver] Calling PaySupport.current_subscription_for..."
88
- subscription = PaySupport.current_subscription_for(plan_owner)
89
- log_debug "[PricingPlans::PlanResolver] current_subscription_for returned: #{subscription ? subscription.class.name : 'nil'}"
90
- return nil unless subscription
92
+ def current_subscription_for(plan_owner)
93
+ return nil unless plan_owner
91
94
 
92
- # Map processor plan to our plan
93
- processor_plan = subscription.processor_plan
94
- log_debug "[PricingPlans::PlanResolver] subscription.processor_plan = #{processor_plan.inspect}"
95
+ pay_available = pay_available?
96
+ log_debug "[PricingPlans::PlanResolver] PaySupport.pay_available? = #{pay_available}"
95
97
 
96
- if processor_plan
97
- matched_plan = plan_from_processor_plan(processor_plan)
98
- log_debug "[PricingPlans::PlanResolver] plan_from_processor_plan returned: #{matched_plan ? matched_plan.key : 'nil'}"
99
- return matched_plan
100
- end
101
- end
98
+ return nil unless pay_available
102
99
 
103
- log_debug "[PricingPlans::PlanResolver] resolve_plan_from_pay returning nil"
104
- nil
100
+ # This intentionally delegates the active/trial/grace filtering contract
101
+ # to PaySupport.current_subscription_for so resolution_for can preserve
102
+ # the same billing context wherever it is called.
103
+ subscription = PaySupport.current_subscription_for(plan_owner)
104
+ log_debug "[PricingPlans::PlanResolver] current_subscription_for returned: #{subscription ? subscription.class.name : 'nil'}"
105
+ subscription
105
106
  end
106
107
 
107
108
  def plan_from_processor_plan(processor_plan)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PricingPlans
4
- VERSION = "0.3.2"
4
+ VERSION = "0.4.1"
5
5
  end
data/lib/pricing_plans.rb CHANGED
@@ -21,6 +21,7 @@ module PricingPlans
21
21
  autoload :Configuration, "pricing_plans/configuration"
22
22
  autoload :Registry, "pricing_plans/registry"
23
23
  autoload :Plan, "pricing_plans/plan"
24
+ autoload :PlanResolution, "pricing_plans/plan_resolution"
24
25
  autoload :DSL, "pricing_plans/dsl"
25
26
  autoload :IntegerRefinements, "pricing_plans/integer_refinements"
26
27
  autoload :PlanResolver, "pricing_plans/plan_resolver"
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.3.2
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - rameerez
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-02-25 00:00:00.000000000 Z
10
+ date: 2026-08-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activerecord
@@ -70,6 +70,7 @@ files:
70
70
  - LICENSE.txt
71
71
  - README.md
72
72
  - Rakefile
73
+ - context7.json
73
74
  - docs/01-define-pricing-plans.md
74
75
  - docs/02-controller-helpers.md
75
76
  - docs/03-model-helpers.md
@@ -107,6 +108,7 @@ files:
107
108
  - lib/pricing_plans/period_calculator.rb
108
109
  - lib/pricing_plans/plan.rb
109
110
  - lib/pricing_plans/plan_owner.rb
111
+ - lib/pricing_plans/plan_resolution.rb
110
112
  - lib/pricing_plans/plan_resolver.rb
111
113
  - lib/pricing_plans/price_components.rb
112
114
  - lib/pricing_plans/registry.rb