Class: PricingPlans::Configuration

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

Constant Summary collapse

LEGACY_DEFAULT_PLAN_ASSIGNMENT_BEHAVIORS =
[:allow, :warn, :raise].freeze

Constants included from DSL

DSL::PERIOD_OPTIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/pricing_plans/configuration.rb', line 66

def initialize
  @plan_owner_class = nil
  @default_plan = nil
  @highlighted_plan = nil
  @period_cycle = :billing_cycle
  @legacy_default_plan_assignment_behavior = :warn
  @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)



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

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)


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

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)


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

def controller_plan_owner_proc
  @controller_plan_owner_proc
end

#debugObject

Debug mode - set to true to enable debug output



24
25
26
# File 'lib/pricing_plans/configuration.rb', line 24

def debug
  @debug
end

#default_cta_textObject

Optional ergonomics



22
23
24
# File 'lib/pricing_plans/configuration.rb', line 22

def default_cta_text
  @default_cta_text
end

#default_cta_urlObject

Optional ergonomics



22
23
24
# File 'lib/pricing_plans/configuration.rb', line 22

def default_cta_url
  @default_cta_url
end

#default_currency_symbolObject

Default currency symbol when Stripe isn't available



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

def default_currency_symbol
  @default_currency_symbol
end

#default_planObject

Returns the value of attribute default_plan.



16
17
18
# File 'lib/pricing_plans/configuration.rb', line 16

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] }



63
64
65
# File 'lib/pricing_plans/configuration.rb', line 63

def downgrade_policy
  @downgrade_policy
end

#event_handlersObject (readonly)

Returns the value of attribute event_handlers.



64
65
66
# File 'lib/pricing_plans/configuration.rb', line 64

def event_handlers
  @event_handlers
end

#free_price_captionObject

Optional free caption copy (UI copy holder)



58
59
60
# File 'lib/pricing_plans/configuration.rb', line 58

def free_price_caption
  @free_price_caption
end

#highlighted_planObject

Returns the value of attribute highlighted_plan.



16
17
18
# File 'lib/pricing_plans/configuration.rb', line 16

def highlighted_plan
  @highlighted_plan
end

#interval_default_for_uiObject

Optional default interval for UI toggles



60
61
62
# File 'lib/pricing_plans/configuration.rb', line 60

def interval_default_for_ui
  @interval_default_for_ui
end

#legacy_default_plan_assignment_behaviorObject

Controls what the deprecated assignment APIs do when they are asked to assign the configured default plan. Explicit override APIs are never affected because their names already communicate the caller's intent.



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

def legacy_default_plan_assignment_behavior
  @legacy_default_plan_assignment_behavior
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:



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

def message_builder
  @message_builder
end

#period_cycleObject

Returns the value of attribute period_cycle.



16
17
18
# File 'lib/pricing_plans/configuration.rb', line 16

def period_cycle
  @period_cycle
end

#plan_owner_classObject

Returns the value of attribute plan_owner_class.



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

def plan_owner_class
  @plan_owner_class
end

#plansObject (readonly)

Returns the value of attribute plans.



64
65
66
# File 'lib/pricing_plans/configuration.rb', line 64

def plans
  @plans
end

#price_cacheObject

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



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

def price_cache
  @price_cache
end

#price_cache_ttlObject

Seconds for cache TTL for Stripe lookups



56
57
58
# File 'lib/pricing_plans/configuration.rb', line 56

def price_cache_ttl
  @price_cache_ttl
end

#price_components_resolverObject

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



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

def price_components_resolver
  @price_components_resolver
end

#price_label_resolverObject

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



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

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


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

def redirect_on_blocked_limit
  @redirect_on_blocked_limit
end

Class Method Details

.legacy_default_plan_assignment_behaviors_descriptionObject



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

def self.legacy_default_plan_assignment_behaviors_description
  descriptions = LEGACY_DEFAULT_PLAN_ASSIGNMENT_BEHAVIORS.map(&:inspect)
  "#{descriptions[0..-2].join(', ')}, or #{descriptions.last}"
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 {  }


122
123
124
125
126
127
128
129
130
131
132
# File 'lib/pricing_plans/configuration.rb', line 122

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:



155
156
157
158
159
# File 'lib/pricing_plans/configuration.rb', line 155

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:



146
147
148
149
150
# File 'lib/pricing_plans/configuration.rb', line 146

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:



137
138
139
140
141
# File 'lib/pricing_plans/configuration.rb', line 137

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



107
108
109
110
111
112
113
114
# File 'lib/pricing_plans/configuration.rb', line 107

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



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

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



161
162
163
164
165
166
167
168
# File 'lib/pricing_plans/configuration.rb', line 161

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

#validate_dsl_markers!Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/pricing_plans/configuration.rb', line 182

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