Class: Pay::Stripe::Billable

Inherits:
Object
  • Object
show all
Defined in:
lib/pay/stripe/billable.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(billable) ⇒ Billable

Returns a new instance of Billable.



22
23
24
# File 'lib/pay/stripe/billable.rb', line 22

def initialize(billable)
  @billable = billable
end

Instance Attribute Details

#billableObject (readonly)

Returns the value of attribute billable.



6
7
8
# File 'lib/pay/stripe/billable.rb', line 6

def billable
  @billable
end

Class Method Details

.default_url_optionsObject



17
18
19
# File 'lib/pay/stripe/billable.rb', line 17

def default_url_options
  Rails.application.config.action_mailer.default_url_options || {}
end

Instance Method Details

#billing_portal(**options) ⇒ Object



228
229
230
231
232
233
234
# File 'lib/pay/stripe/billable.rb', line 228

def billing_portal(**options)
  args = {
    customer: processor_id,
    return_url: options.delete(:return_url) || root_url
  }
  ::Stripe::BillingPortal::Session.create(args.merge(options), {stripe_account: })
end

#charge(amount, options = {}) ⇒ Object

Handles Billable#charge

Returns Pay::Charge



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/pay/stripe/billable.rb', line 53

def charge(amount, options = {})
  stripe_customer = customer
  args = {
    amount: amount,
    confirm: true,
    confirmation_method: :automatic,
    currency: "usd",
    customer: stripe_customer.id,
    payment_method: stripe_customer.invoice_settings.default_payment_method
  }.merge(options)

  payment_intent = ::Stripe::PaymentIntent.create(args, {stripe_account: })
  Pay::Payment.new(payment_intent).validate

  # Create a new charge object
  charge = payment_intent.charges.first
  Pay::Stripe::Charge.sync(charge.id, object: charge)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#checkout(**options) ⇒ Object

stripe.com/docs/api/checkout/sessions/create

checkout(mode: “payment”) checkout(mode: “setup”) checkout(mode: “subscription”)

checkout(line_items: “price_12345”, quantity: 2) checkout(line_items [{ price: “price_123” }, { price: “price_456” }]) checkout(line_items, “price_12345”, allow_promotion_codes: true)



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/pay/stripe/billable.rb', line 185

def checkout(**options)
  args = {
    customer: processor_id,
    payment_method_types: ["card"],
    mode: "payment",
    # These placeholder URLs will be replaced in a following step.
    success_url: options.delete(:success_url) || root_url,
    cancel_url: options.delete(:cancel_url) || root_url
  }

  # Line items are optional
  if (line_items = options.delete(:line_items))
    args[:line_items] = Array.wrap(line_items).map { |item|
      if item.is_a? Hash
        item
      else
        {price: item, quantity: options.fetch(:quantity, 1)}
      end
    }
  end

  ::Stripe::Checkout::Session.create(args.merge(options), {stripe_account: })
end

#checkout_charge(amount:, name:, quantity: 1, **options) ⇒ Object

stripe.com/docs/api/checkout/sessions/create

checkout_charge(amount: 15_00, name: “T-shirt”, quantity: 2)



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/pay/stripe/billable.rb', line 213

def checkout_charge(amount:, name:, quantity: 1, **options)
  currency = options.delete(:currency) || "usd"
  checkout(
    line_items: {
      price_data: {
        currency: currency,
        product_data: {name: name},
        unit_amount: amount
      },
      quantity: quantity
    },
    **options
  )
end

#create_setup_intentObject



154
155
156
# File 'lib/pay/stripe/billable.rb', line 154

def create_setup_intent
  ::Stripe::SetupIntent.create({customer: processor_id, usage: :off_session}, {stripe_account: })
end

#customerObject

Handles Billable#customer

Returns Stripe::Customer



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/pay/stripe/billable.rb', line 29

def customer
  stripe_customer = if processor_id?
    ::Stripe::Customer.retrieve(processor_id, {stripe_account: })
  else
    sc = ::Stripe::Customer.create({email: email, name: customer_name}, {stripe_account: })
    billable.update(processor: :stripe, processor_id: sc.id, stripe_account: )
    sc
  end

  # Update the user's card on file if a token was passed in
  if card_token.present?
    payment_method = ::Stripe::PaymentMethod.attach(card_token, {customer: stripe_customer.id}, {stripe_account: })
    stripe_customer = ::Stripe::Customer.update(stripe_customer.id, {invoice_settings: {default_payment_method: payment_method.id}}, {stripe_account: })
    update_card_on_file(payment_method.card)
  end

  stripe_customer
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#invoice!(options = {}) ⇒ Object



136
137
138
139
# File 'lib/pay/stripe/billable.rb', line 136

def invoice!(options = {})
  return unless processor_id?
  ::Stripe::Invoice.create(options.merge(customer: processor_id), {stripe_account: }).pay
end

#processor_subscription(subscription_id, options = {}) ⇒ Object



132
133
134
# File 'lib/pay/stripe/billable.rb', line 132

def processor_subscription(subscription_id, options = {})
  ::Stripe::Subscription.retrieve(options.merge(id: subscription_id), {stripe_account: })
end

#subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options) ⇒ Object

Handles Billable#subscribe

Returns Pay::Subscription



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
102
103
104
105
106
107
108
109
# File 'lib/pay/stripe/billable.rb', line 77

def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
  quantity = options.delete(:quantity) || 1
  opts = {
    expand: ["pending_setup_intent", "latest_invoice.payment_intent"],
    items: [plan: plan, quantity: quantity],
    off_session: true
  }.merge(options)

  # Inherit trial from plan unless trial override was specified
  opts[:trial_from_plan] = true unless opts[:trial_period_days]

  # Load the Stripe customer to verify it exists and update card if needed
  opts[:customer] = customer.id

  # Create subscription on Stripe
  stripe_sub = ::Stripe::Subscription.create(opts, {stripe_account: })

  # Save Pay::Subscription
  subscription = Pay::Stripe::Subscription.sync(stripe_sub.id, object: stripe_sub, name: name)

  # No trial, card requires SCA
  if subscription.incomplete?
    Pay::Payment.new(stripe_sub.latest_invoice.payment_intent).validate

  # Trial, card requires SCA
  elsif subscription.on_trial? && stripe_sub.pending_setup_intent
    Pay::Payment.new(stripe_sub.pending_setup_intent).validate
  end

  subscription
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#sync_card_from_stripeObject

Used by webhooks when the customer or source changes



146
147
148
149
150
151
152
# File 'lib/pay/stripe/billable.rb', line 146

def sync_card_from_stripe
  if (payment_method_id = customer.invoice_settings.default_payment_method)
    update_card_on_file ::Stripe::PaymentMethod.retrieve(payment_method_id, {stripe_account: }).card
  else
    billable.update(card_type: nil, card_last4: nil)
  end
end

#trial_end_date(stripe_sub) ⇒ Object



158
159
160
161
# File 'lib/pay/stripe/billable.rb', line 158

def trial_end_date(stripe_sub)
  # Times in Stripe are returned in UTC
  stripe_sub.trial_end.present? ? Time.at(stripe_sub.trial_end) : nil
end

#upcoming_invoiceObject



141
142
143
# File 'lib/pay/stripe/billable.rb', line 141

def upcoming_invoice
  ::Stripe::Invoice.upcoming({customer: processor_id}, {stripe_account: })
end

#update_card(payment_method_id) ⇒ Object

Handles Billable#update_card

Returns true if successful



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/pay/stripe/billable.rb', line 114

def update_card(payment_method_id)
  stripe_customer = customer

  return true if payment_method_id == stripe_customer.invoice_settings.default_payment_method

  payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: stripe_customer.id}, {stripe_account: })
  ::Stripe::Customer.update(stripe_customer.id, {invoice_settings: {default_payment_method: payment_method.id}}, {stripe_account: })

  update_card_on_file(payment_method.card)
  true
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#update_card_on_file(card) ⇒ Object

Save the card to the database as the user’s current card



164
165
166
167
168
169
170
171
172
173
# File 'lib/pay/stripe/billable.rb', line 164

def update_card_on_file(card)
  billable.update!(
    card_type: card.brand.capitalize,
    card_last4: card.last4,
    card_exp_month: card.exp_month,
    card_exp_year: card.exp_year
  )

  billable.card_token = nil
end

#update_email!Object



128
129
130
# File 'lib/pay/stripe/billable.rb', line 128

def update_email!
  ::Stripe::Customer.update(processor_id, {email: email, name: customer_name}, {stripe_account: })
end