Class: PricingPlans::Configuration

Inherits:
Object
  • Object
show all
Includes:
DSL
Defined in:
lib/pricing_plans/configuration.rb

Constant Summary

Constants included from DSL

DSL::PERIOD_OPTIONS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/pricing_plans/configuration.rb', line 55

def initialize
  @plan_owner_class = nil
  @default_plan = nil
  @highlighted_plan = nil
  @period_cycle = :billing_cycle
  @default_cta_text = nil
  @default_cta_url = nil
  @message_builder = nil
  @controller_plan_owner_method = nil
  @controller_plan_owner_proc = nil
  @redirect_on_blocked_limit = nil
  @price_label_resolver = nil
  @auto_price_labels_from_processor = true
  @price_components_resolver = nil
  @default_currency_symbol = "$"
  @price_cache = (defined?(Rails) && Rails.respond_to?(:cache)) ? Rails.cache : nil
  @price_cache_ttl = 600 # 10 minutes
  @free_price_caption = "Forever free"
  @interval_default_for_ui = :month
  @downgrade_policy = ->(from:, to:, plan_owner:) { [true, nil] }
  @debug = false
  @plans = {}
  @event_handlers = {
    warning: {},
    grace_start: {},
    block: {}
  }
end

Instance Attribute Details

#auto_price_labels_from_processorObject

Auto-fetch price labels from processor when possible (Stripe via stripe-ruby)



37
38
39
# File 'lib/pricing_plans/configuration.rb', line 37

def auto_price_labels_from_processor
  @auto_price_labels_from_processor
end

#controller_plan_owner_methodObject (readonly)

Global controller ergonomics Optional global resolver for controller plan owner. Per-controller settings still win. Accepts:

  • Symbol: a controller helper to call (e.g., :current_organization)
  • Proc: instance-exec'd in the controller (self is the controller)


19
20
21
# File 'lib/pricing_plans/configuration.rb', line 19

def controller_plan_owner_method
  @controller_plan_owner_method
end

#controller_plan_owner_procObject (readonly)

Global controller ergonomics Optional global resolver for controller plan owner. Per-controller settings still win. Accepts:

  • Symbol: a controller helper to call (e.g., :current_organization)
  • Proc: instance-exec'd in the controller (self is the controller)


19
20
21
# File 'lib/pricing_plans/configuration.rb', line 19

def controller_plan_owner_proc
  @controller_plan_owner_proc
end

#debugObject

Debug mode - set to true to enable debug output



13
14
15
# File 'lib/pricing_plans/configuration.rb', line 13

def debug
  @debug
end

#default_cta_textObject

Optional ergonomics



11
12
13
# File 'lib/pricing_plans/configuration.rb', line 11

def default_cta_text
  @default_cta_text
end

#default_cta_urlObject

Optional ergonomics



11
12
13
# File 'lib/pricing_plans/configuration.rb', line 11

def default_cta_url
  @default_cta_url
end

#default_currency_symbolObject

Default currency symbol when Stripe isn't available



41
42
43
# File 'lib/pricing_plans/configuration.rb', line 41

def default_currency_symbol
  @default_currency_symbol
end

#default_planObject

Returns the value of attribute default_plan.



9
10
11
# File 'lib/pricing_plans/configuration.rb', line 9

def default_plan
  @default_plan
end

#downgrade_policyObject

Optional downgrade policy hook for CTA ergonomics Signature: ->(from:, to:, plan_owner:) { [allowed_boolean, reason_string_or_nil] }



52
53
54
# File 'lib/pricing_plans/configuration.rb', line 52

def downgrade_policy
  @downgrade_policy
end

#event_handlersObject (readonly)

Returns the value of attribute event_handlers.



53
54
55
# File 'lib/pricing_plans/configuration.rb', line 53

def event_handlers
  @event_handlers
end

#free_price_captionObject

Optional free caption copy (UI copy holder)



47
48
49
# File 'lib/pricing_plans/configuration.rb', line 47

def free_price_caption
  @free_price_caption
end

#highlighted_planObject

Returns the value of attribute highlighted_plan.



9
10
11
# File 'lib/pricing_plans/configuration.rb', line 9

def highlighted_plan
  @highlighted_plan
end

#interval_default_for_uiObject

Optional default interval for UI toggles



49
50
51
# File 'lib/pricing_plans/configuration.rb', line 49

def interval_default_for_ui
  @interval_default_for_ui
end

#message_builderObject

Optional global message builder proc for human copy (i18n/hooks) Signature suggestion: (context:, **kwargs) -> string Contexts used: :over_limit, :grace, :feature_denied Example kwargs: limit_key:, current_usage:, limit_amount:, grace_ends_at:, feature_key:, plan_name:



31
32
33
# File 'lib/pricing_plans/configuration.rb', line 31

def message_builder
  @message_builder
end

#period_cycleObject

Returns the value of attribute period_cycle.



9
10
11
# File 'lib/pricing_plans/configuration.rb', line 9

def period_cycle
  @period_cycle
end

#plan_owner_classObject

Returns the value of attribute plan_owner_class.



32
33
34
# File 'lib/pricing_plans/configuration.rb', line 32

def plan_owner_class
  @plan_owner_class
end

#plansObject (readonly)

Returns the value of attribute plans.



53
54
55
# File 'lib/pricing_plans/configuration.rb', line 53

def plans
  @plans
end

#price_cacheObject

Cache for Stripe prices. Defaults to in-memory store if nil. Should respond to read/write with ttl.



43
44
45
# File 'lib/pricing_plans/configuration.rb', line 43

def price_cache
  @price_cache
end

#price_cache_ttlObject

Seconds for cache TTL for Stripe lookups



45
46
47
# File 'lib/pricing_plans/configuration.rb', line 45

def price_cache_ttl
  @price_cache_ttl
end

#price_components_resolverObject

Semantic pricing components resolver hook: ->(plan, interval) { PriceComponents | nil }



39
40
41
# File 'lib/pricing_plans/configuration.rb', line 39

def price_components_resolver
  @price_components_resolver
end

#price_label_resolverObject

Optional: custom resolver for displaying price labels from processor Signature: ->(plan) { "$amount/mo" }



35
36
37
# File 'lib/pricing_plans/configuration.rb', line 35

def price_label_resolver
  @price_label_resolver
end

#redirect_on_blocked_limitObject

When a limit check blocks, controllers can redirect to a global default target. Accepts:

  • Symbol: a controller helper to call (e.g., :pricing_path)
  • String: an absolute/relative path or full URL
  • Proc: instance-exec'd in the controller (self is the controller). Signature: ->(result) { ... } Result contains: limit_key, plan_owner, message, metadata


26
27
28
# File 'lib/pricing_plans/configuration.rb', line 26

def redirect_on_blocked_limit
  @redirect_on_blocked_limit
end

Instance Method Details

#controller_plan_owner(method_name = nil, &block) ⇒ Object

Global controller plan owner resolver API Usage:

config.controller_plan_owner :current_organization
# or
config.controller_plan_owner {  }


110
111
112
113
114
115
116
117
118
119
120
# File 'lib/pricing_plans/configuration.rb', line 110

def controller_plan_owner(method_name = nil, &block)
  if method_name
    @controller_plan_owner_method = method_name.to_sym
    @controller_plan_owner_proc = nil
  elsif block_given?
    @controller_plan_owner_proc = block
    @controller_plan_owner_method = nil
  else
    @controller_plan_owner_method
  end
end

#on_block(limit_key = nil) {|plan_owner, limit_key| ... } ⇒ Object

Register a callback for block events.

Parameters:

  • limit_key (Symbol, nil) (defaults to: nil)

    The specific limit key, or omit for wildcard (all limits)

Yields:

  • (plan_owner, limit_key)

    Block to execute when user is blocked

Raises:



143
144
145
146
147
# File 'lib/pricing_plans/configuration.rb', line 143

def on_block(limit_key = nil, &block)
  raise PricingPlans::ConfigurationError, "Block required for on_block" unless block_given?
  key = limit_key || :_all
  @event_handlers[:block][key] = block
end

#on_grace_start(limit_key = nil) {|plan_owner, limit_key, grace_ends_at| ... } ⇒ Object

Register a callback for grace period start events.

Parameters:

  • limit_key (Symbol, nil) (defaults to: nil)

    The specific limit key, or omit for wildcard (all limits)

Yields:

  • (plan_owner, limit_key, grace_ends_at)

    Block to execute when grace starts

Raises:



134
135
136
137
138
# File 'lib/pricing_plans/configuration.rb', line 134

def on_grace_start(limit_key = nil, &block)
  raise PricingPlans::ConfigurationError, "Block required for on_grace_start" unless block_given?
  key = limit_key || :_all
  @event_handlers[:grace_start][key] = block
end

#on_warning(limit_key = nil) {|plan_owner, limit_key, threshold| ... } ⇒ Object

Register a callback for warning events.

Parameters:

  • limit_key (Symbol, nil) (defaults to: nil)

    The specific limit key, or omit for wildcard (all limits)

Yields:

  • (plan_owner, limit_key, threshold)

    Block to execute when warning fires

Raises:



125
126
127
128
129
# File 'lib/pricing_plans/configuration.rb', line 125

def on_warning(limit_key = nil, &block)
  raise PricingPlans::ConfigurationError, "Block required for on_warning" unless block_given?
  key = limit_key || :_all
  @event_handlers[:warning][key] = block
end

#plan(key, &block) ⇒ Object



95
96
97
98
99
100
101
102
# File 'lib/pricing_plans/configuration.rb', line 95

def plan(key, &block)
  raise PricingPlans::ConfigurationError, "Plan key must be a symbol" unless key.is_a?(Symbol)
  raise PricingPlans::ConfigurationError, "Plan #{key} already defined" if @plans.key?(key)

  plan_instance = PricingPlans::Plan.new(key)
  plan_instance.instance_eval(&block)
  @plans[key] = plan_instance
end

#select_defaults_from_dsl!Object



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/pricing_plans/configuration.rb', line 156

def select_defaults_from_dsl!
  # If not explicitly configured, derive from any plan marked via DSL sugar
  if @default_plan.nil?
    dsl_default = @plans.values.find(&:default?)&.key
    @default_plan = dsl_default if dsl_default
  end

  if @highlighted_plan.nil?
    dsl_highlighted = @plans.values.find(&:highlighted?)&.key
    @highlighted_plan = dsl_highlighted if dsl_highlighted
  end
end

#validate!Object



149
150
151
152
153
154
155
# File 'lib/pricing_plans/configuration.rb', line 149

def validate!
  select_defaults_from_dsl!
  validate_required_settings!
  validate_plan_references!
  validate_dsl_markers!
  validate_plans!
end

#validate_dsl_markers!Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/pricing_plans/configuration.rb', line 169

def validate_dsl_markers!
  defaults = @plans.values.select(&:default?)
  highlights = @plans.values.select(&:highlighted?)

  if defaults.size > 1
    keys = defaults.map(&:key).join(", ")
    raise PricingPlans::ConfigurationError, "Multiple plans marked default via DSL: #{keys}. Only one plan can be default."
  end

  if highlights.size > 1
    keys = highlights.map(&:key).join(", ")
    raise PricingPlans::ConfigurationError, "Multiple plans marked highlighted via DSL: #{keys}. Only one plan can be highlighted."
  end
end