Class: UsageCredits::CreditPack

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

Overview

A DSL to define credit packs that users can buy as one-time purchases.

Credit packs can be purchased independently and separately from any subscription.

The actual credit fulfillment is handled by the PayChargeExtension.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ CreditPack

Returns a new instance of CreditPack.



18
19
20
21
22
23
24
25
# File 'lib/usage_credits/models/credit_pack.rb', line 18

def initialize(name)
  @name = name
  @credits = 0
  @bonus_credits = 0
  @price_cents = 0
  @price_currency = UsageCredits.configuration.default_currency.to_s.upcase
   = {}
end

Instance Attribute Details

#bonus_creditsObject (readonly)

Returns the value of attribute bonus_credits.



13
14
15
# File 'lib/usage_credits/models/credit_pack.rb', line 13

def bonus_credits
  @bonus_credits
end

#creditsObject (readonly)

Returns the value of attribute credits.



13
14
15
# File 'lib/usage_credits/models/credit_pack.rb', line 13

def credits
  @credits
end

#metadataObject (readonly)

Returns the value of attribute metadata.



13
14
15
# File 'lib/usage_credits/models/credit_pack.rb', line 13

def 
  
end

#nameObject (readonly)

Returns the value of attribute name.



13
14
15
# File 'lib/usage_credits/models/credit_pack.rb', line 13

def name
  @name
end

#price_centsObject (readonly)

Returns the value of attribute price_cents.



13
14
15
# File 'lib/usage_credits/models/credit_pack.rb', line 13

def price_cents
  @price_cents
end

#price_currencyObject (readonly)

Returns the value of attribute price_currency.



13
14
15
# File 'lib/usage_credits/models/credit_pack.rb', line 13

def price_currency
  @price_currency
end

Instance Method Details

#base_metadataObject



195
196
197
198
199
200
201
202
203
204
# File 'lib/usage_credits/models/credit_pack.rb', line 195

def 
  {
    purchase_type: "credit_pack",
    pack_name: name,
    credits: credits,
    bonus_credits: bonus_credits,
    price_cents: price_cents,
    price_currency: price_currency
  }
end

#bonus(amount) ⇒ Object

Set bonus credits (e.g., for promotions)



37
38
39
# File 'lib/usage_credits/models/credit_pack.rb', line 37

def bonus(amount)
  @bonus_credits = amount.to_i
end

#button_textObject

Generate human-friendly button text for purchase links



118
119
120
# File 'lib/usage_credits/models/credit_pack.rb', line 118

def button_text
  "Get #{display_credits} for #{formatted_price}"
end

#costs(cents) ⇒ Object Also known as: cost

Set the price in cents (e.g., 4900 for $49.00)



42
43
44
# File 'lib/usage_credits/models/credit_pack.rb', line 42

def costs(cents)
  @price_cents = cents
end

#create_checkout_session(user, **options) ⇒ Object

Create a Stripe Checkout session for this pack.

Accepts optional checkout options that are passed through to the payment processor. Common options include:

- success_url: URL to redirect after successful payment
- cancel_url: URL to redirect if customer cancels
- allow_promotion_codes: Enable promotion code input (true/false)
- locale: Checkout page locale (e.g., "en", "es", "fr")
- customer_email: Pre-fill the customer's email

Note: The following parameters are protected and cannot be overridden:

- mode: Always "payment" for credit packs
- line_items: Always the configured credit pack

Custom metadata passed in options will be merged with (not replace) the base pack metadata required for credit fulfillment.

Examples:

Basic usage

pack.create_checkout_session(current_user)

With custom options

pack.create_checkout_session(current_user,
  success_url: credits_url,
  cancel_url: pricing_url,
  allow_promotion_codes: true
)

Raises:

Parameters:

  • A user with a payment processor (via Pay gem)

  • Additional checkout options passed to the payment processor

Returns:

  • The checkout session object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/usage_credits/models/credit_pack.rb', line 157

def create_checkout_session(user, **options)
  raise ArgumentError, "User must have a payment processor" unless user.respond_to?(:payment_processor) && user.payment_processor

  # Merge custom metadata with base_metadata (base_metadata takes precedence for critical fields)
   = options.delete(:metadata) || {}
   = .merge()

  # Handle payment_intent_data specially to preserve metadata
  # We dup to avoid mutating the caller's original hash
  custom_payment_intent_data = (options.delete(:payment_intent_data) || {}).dup
   = custom_payment_intent_data.delete(:metadata) || {}
  merged_payment_intent_data = custom_payment_intent_data.merge(
    metadata: .merge()
  )

  # Remove protected parameters that could break credit fulfillment
  options.delete(:mode)
  options.delete(:line_items)

  user.payment_processor.checkout(
    mode: "payment",
    line_items: [{
      price_data: {
        currency: price_currency.downcase,
        unit_amount: price_cents,
        product_data: {
          name: display_name,
          description: display_description
        }
      },
      quantity: 1
    }],
    payment_intent_data: merged_payment_intent_data,
    metadata: ,
    **options
  )
end

#credits_per_dollarObject

Get credits per dollar ratio (for comparison)



95
96
97
98
# File 'lib/usage_credits/models/credit_pack.rb', line 95

def credits_per_dollar
  return 0 if price.zero?
  total_credits / price
end

#currency(currency) ⇒ Object

Set the currency (defaults to configuration)



48
49
50
51
52
53
54
# File 'lib/usage_credits/models/credit_pack.rb', line 48

def currency(currency)
  currency = currency.to_s.downcase.to_sym
  unless UsageCredits::Configuration::VALID_CURRENCIES.include?(currency)
    raise ArgumentError, "Invalid currency. Must be one of: #{UsageCredits::Configuration::VALID_CURRENCIES.join(', ')}"
  end
  @price_currency = currency.to_s.upcase
end

#display_creditsObject Also known as: display_description

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

Display Formatting



104
105
106
107
108
109
110
# File 'lib/usage_credits/models/credit_pack.rb', line 104

def display_credits
  if bonus_credits.positive?
    "#{credits} + #{bonus_credits} bonus credits"
  else
    "#{credits} credits"
  end
end

#display_nameObject



113
114
115
# File 'lib/usage_credits/models/credit_pack.rb', line 113

def display_name
  "#{name.to_s.titleize} pack"
end

#formatted_priceObject

Get formatted price (e.g., "49.00 USD")



90
91
92
# File 'lib/usage_credits/models/credit_pack.rb', line 90

def formatted_price
  format("%.2f %s", price, price_currency)
end

#gives(amount) ⇒ Object

Set the base number of credits



32
33
34
# File 'lib/usage_credits/models/credit_pack.rb', line 32

def gives(amount)
  @credits = amount.to_i
end

#meta(hash) ⇒ Object

Add custom metadata



57
58
59
# File 'lib/usage_credits/models/credit_pack.rb', line 57

def meta(hash)
  .merge!(hash.transform_keys(&:to_sym))
end

#priceObject

Get price in dollars



85
86
87
# File 'lib/usage_credits/models/credit_pack.rb', line 85

def price
  price_cents / 100.0
end

#total_creditsObject

Get total credits (including bonus)



80
81
82
# File 'lib/usage_credits/models/credit_pack.rb', line 80

def total_credits
  credits + bonus_credits
end

#validate!Object

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

Validation

Raises:



65
66
67
68
69
70
71
72
73
# File 'lib/usage_credits/models/credit_pack.rb', line 65

def validate!
  raise ArgumentError, "Name can't be blank" if name.blank?
  raise ArgumentError, "Credits must be greater than 0" unless credits.to_i.positive?
  raise ArgumentError, "Bonus credits must be greater than or equal to 0" if bonus_credits.to_i.negative?
  raise ArgumentError, "Price must be greater than 0" unless price_cents.to_i.positive?
  raise ArgumentError, "Currency can't be blank" if price_currency.blank?
  # raise ArgumentError, "Price must be in whole cents ($49 = 4900)" if price_cents % 100 != 0 # Payment processors should handle this anyways
  true
end