Class: UsageCredits::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/usage_credits/configuration.rb

Overview

Configuration for the UsageCredits gem. This is the single source of truth for all settings. This is what turns what's defined in the initializer DSL into actual objects we can use and operate with.

Constant Summary collapse

VALID_ROUNDING_STRATEGIES =
[:ceil, :floor, :round].freeze
VALID_CURRENCIES =
[:usd, :eur, :gbp, :sgd, :chf].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



63
64
65
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
95
96
97
98
99
100
101
# File 'lib/usage_credits/configuration.rb', line 63

def initialize
  # Initialize empty data stores
  @operations = {}                  # Credit-consuming operations (e.g., "send_email: 1 credit")
  @credit_packs = {}                # One-time purchases (e.g., "100 credits for $49")
  @credit_subscription_plans = {}   # Recurring plans (e.g., "1000 credits/month for $99")

  # Set sensible defaults
  @default_currency = :usd
  @rounding_strategy = :ceil  # Always round up to ensure we never undercharge
  @credit_formatter = ->(amount) { "#{amount} credits" }  # How to format credit amounts in the UI

  # Grace period for credit expiration after fulfillment period ends.
  # For how long will expiring credits "overlap" the following fulfillment period.
  # This ensures smooth transition between fulfillment periods.
  # For this amount of time, old, already expired credits will be erroneously counted as available in the user's balance.
  # Keep it short enough that users don't notice they have the last period's credits still available, but
  # long enough that there's a smooth transition and users never get zero credits in between fulfillment periods
  # A good setting is to match the frequency of your UsageCredits::FulfillmentJob runs
  @fulfillment_grace_period = 5.minutes # If you run your fulfillment job every 5 minutes, this should be enough

  # Minimum fulfillment period - prevents accidental 1-second refill loops in production
  @min_fulfillment_period = 1.day

  # Custom transaction categories (empty by default, apps can extend)
  @additional_categories = []

  @allow_negative_balance = false
  @low_balance_threshold = nil
  @low_balance_callback = nil # Called when user hits low_balance_threshold

  # Lifecycle callbacks (all nil by default)
  @on_credits_added_callback = nil
  @on_credits_deducted_callback = nil
  @on_low_balance_reached_callback = nil
  @on_balance_depleted_callback = nil
  @on_insufficient_credits_callback = nil
  @on_subscription_credits_awarded_callback = nil
  @on_credit_pack_purchased_callback = nil
end

Instance Attribute Details

#additional_categoriesObject

Custom transaction categories that extend the default set



34
35
36
# File 'lib/usage_credits/configuration.rb', line 34

def additional_categories
  @additional_categories
end

#allow_negative_balanceObject

=========================================

Low balance



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

def allow_negative_balance
  @allow_negative_balance
end

#credit_formatterObject (readonly)

Returns the value of attribute credit_formatter.



29
30
31
# File 'lib/usage_credits/configuration.rb', line 29

def credit_formatter
  @credit_formatter
end

#credit_packsObject (readonly)

Stores all the things users can buy or subscribe to



18
19
20
# File 'lib/usage_credits/configuration.rb', line 18

def credit_packs
  @credit_packs
end

#credit_subscription_plansObject (readonly)

Returns the value of attribute credit_subscription_plans.



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

def credit_subscription_plans
  @credit_subscription_plans
end

#default_currencyObject

=========================================

Basic Settings



25
26
27
# File 'lib/usage_credits/configuration.rb', line 25

def default_currency
  @default_currency
end

#fulfillment_grace_periodObject

Returns the value of attribute fulfillment_grace_period.



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

def fulfillment_grace_period
  @fulfillment_grace_period
end

#low_balance_callbackObject (readonly)

Returns the value of attribute low_balance_callback.



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

def low_balance_callback
  @low_balance_callback
end

#low_balance_thresholdObject

Returns the value of attribute low_balance_threshold.



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

def low_balance_threshold
  @low_balance_threshold
end

#min_fulfillment_periodObject

Minimum allowed fulfillment period for subscription plans. Defaults to 1.day to prevent accidental 1-second refill loops in production. Can be set to shorter periods (e.g., 2.seconds) in development/test for faster iteration.



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

def min_fulfillment_period
  @min_fulfillment_period
end

#on_balance_depleted_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_balance_depleted_callback
  @on_balance_depleted_callback
end

#on_credit_pack_purchased_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_credit_pack_purchased_callback
  @on_credit_pack_purchased_callback
end

#on_credits_added_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_credits_added_callback
  @on_credits_added_callback
end

#on_credits_deducted_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_credits_deducted_callback
  @on_credits_deducted_callback
end

#on_insufficient_credits_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_insufficient_credits_callback
  @on_insufficient_credits_callback
end

#on_low_balance_reached_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_low_balance_reached_callback
  @on_low_balance_reached_callback
end

#on_subscription_credits_awarded_callbackObject (readonly)

=========================================

Lifecycle Callbacks



55
56
57
# File 'lib/usage_credits/configuration.rb', line 55

def on_subscription_credits_awarded_callback
  @on_subscription_credits_awarded_callback
end

#operationsObject (readonly)

Stores all the things users can do with credits



15
16
17
# File 'lib/usage_credits/configuration.rb', line 15

def operations
  @operations
end

#rounding_strategyObject

Returns the value of attribute rounding_strategy.



27
28
29
# File 'lib/usage_credits/configuration.rb', line 27

def rounding_strategy
  @rounding_strategy
end

Instance Method Details

#credit_pack(name, &block) ⇒ Object

Define a one-time purchase credit pack

Raises:

  • (ArgumentError)


117
118
119
120
121
122
123
124
125
126
# File 'lib/usage_credits/configuration.rb', line 117

def credit_pack(name, &block)
  raise ArgumentError, "Block is required for credit pack definition" unless block_given?
  raise ArgumentError, "Credit pack name can't be blank" if name.blank?

  name = name.to_sym
  pack = CreditPack.new(name)
  pack.instance_eval(&block)
  pack.validate!
  @credit_packs[name] = pack
end

#find_subscription_plan_by_processor_id(processor_id) ⇒ CreditSubscriptionPlan?

Find a subscription plan by its processor-specific ID Works with both single-price and multi-period plans

Parameters:

  • processor_id (String)

    The price ID to search for

Returns:



148
149
150
151
152
# File 'lib/usage_credits/configuration.rb', line 148

def find_subscription_plan_by_processor_id(processor_id)
  @credit_subscription_plans.values.find do |plan|
    plan.matches_processor_id?(processor_id)
  end
end

#format_credits(&block) ⇒ Object

Set how credits are displayed in the UI



227
228
229
# File 'lib/usage_credits/configuration.rb', line 227

def format_credits(&block)
  @credit_formatter = block
end

#on_balance_depleted(&block) ⇒ Object

Called when balance reaches exactly zero



253
254
255
# File 'lib/usage_credits/configuration.rb', line 253

def on_balance_depleted(&block)
  @on_balance_depleted_callback = block
end

#on_credit_pack_purchased(&block) ⇒ Object

Called after a credit pack is purchased



268
269
270
# File 'lib/usage_credits/configuration.rb', line 268

def on_credit_pack_purchased(&block)
  @on_credit_pack_purchased_callback = block
end

#on_credits_added(&block) ⇒ Object

Called after credits are added to a wallet



237
238
239
# File 'lib/usage_credits/configuration.rb', line 237

def on_credits_added(&block)
  @on_credits_added_callback = block
end

#on_credits_deducted(&block) ⇒ Object

Called after credits are deducted from a wallet



242
243
244
# File 'lib/usage_credits/configuration.rb', line 242

def on_credits_deducted(&block)
  @on_credits_deducted_callback = block
end

#on_insufficient_credits(&block) ⇒ Object

Called when an operation fails due to insufficient credits



258
259
260
# File 'lib/usage_credits/configuration.rb', line 258

def on_insufficient_credits(&block)
  @on_insufficient_credits_callback = block
end

#on_low_balance(&block) ⇒ Object

BACKWARD COMPATIBILITY: Legacy method that receives owner, not context Existing users' code: config.on_low_balance { |owner| ... }

Raises:

  • (ArgumentError)


274
275
276
277
278
279
280
# File 'lib/usage_credits/configuration.rb', line 274

def on_low_balance(&block)
  raise ArgumentError, "Block is required for low balance callback" unless block_given?
  # Store legacy callback as before (for backward compat with direct calls)
  @low_balance_callback = block
  # Also create a wrapper for new callback system that extracts owner from context
  @on_low_balance_reached_callback = ->(ctx) { block.call(ctx.owner) }
end

#on_low_balance_reached(&block) ⇒ Object

Called when balance crosses below the low_balance_threshold Receives CallbackContext with full event data



248
249
250
# File 'lib/usage_credits/configuration.rb', line 248

def on_low_balance_reached(&block)
  @on_low_balance_reached_callback = block
end

#on_subscription_credits_awarded(&block) ⇒ Object

Called after subscription credits are awarded



263
264
265
# File 'lib/usage_credits/configuration.rb', line 263

def on_subscription_credits_awarded(&block)
  @on_subscription_credits_awarded_callback = block
end

#operation(name, &block) ⇒ Object

Define a credit-consuming operation

Raises:

  • (ArgumentError)


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

def operation(name, &block)
  raise ArgumentError, "Block is required for operation definition" unless block_given?
  operation = Operation.new(name)
  operation.instance_eval(&block)
  @operations[name.to_sym] = operation
  operation
end

#subscription_plan(name, &block) ⇒ Object

Define a recurring subscription plan

Raises:

  • (ArgumentError)


129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/usage_credits/configuration.rb', line 129

def subscription_plan(name, &block)
  raise ArgumentError, "Block is required for subscription plan definition" unless block_given?
  raise ArgumentError, "Subscription plan name can't be blank" if name.blank?

  name = name.to_sym
  plan = CreditSubscriptionPlan.new(name)
  plan.instance_eval(&block)
  plan.validate!

  # Warn if fulfillment period is shorter than grace period (grace will be auto-capped)
  warn_if_grace_period_exceeds_fulfillment(plan)

  @credit_subscription_plans[name] = plan
end

#validate!Object

Ensure configuration is valid



287
288
289
290
291
292
# File 'lib/usage_credits/configuration.rb', line 287

def validate!
  validate_currency!
  validate_threshold!
  validate_rounding_strategy!
  true
end