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(pay_customer) ⇒ Billable

Returns a new instance of Billable.


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

def initialize(pay_customer)
  @pay_customer = pay_customer
end

Instance Attribute Details

#pay_customerObject (readonly)

Returns the value of attribute pay_customer.


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

def pay_customer
  @pay_customer
end

Class Method Details

.default_url_optionsObject


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

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

Instance Method Details

#add_payment_method(payment_method_id, default: false) ⇒ Object


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/pay/stripe/billable.rb', line 134

def add_payment_method(payment_method_id, default: false)
  customer unless processor_id?
  payment_method = ::Stripe::PaymentMethod.attach(payment_method_id, {customer: processor_id}, stripe_options)

  if default
    ::Stripe::Customer.update(processor_id, {
      invoice_settings: {
        default_payment_method: payment_method.id
      }
    }, stripe_options)
  end

  save_payment_method(payment_method, default: default)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

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


270
271
272
# File 'lib/pay/stripe/billable.rb', line 270

def authorize(amount, options = {})
  charge(amount, options.merge(capture_method: :manual))
end

#billing_portal(**options) ⇒ Object


261
262
263
264
265
266
267
268
# File 'lib/pay/stripe/billable.rb', line 261

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

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


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pay/stripe/billable.rb', line 83

def charge(amount, options = {})
  add_payment_method(payment_method_token, default: true) if payment_method_token?

  payment_method = pay_customer.default_payment_method
  args = {
    amount: amount,
    confirm: true,
    currency: "usd",
    customer: processor_id,
    payment_method: payment_method&.processor_id
  }.merge(options)

  payment_intent = ::Stripe::PaymentIntent.create(args, stripe_options)
  Pay::Payment.new(payment_intent).validate

  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)


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/pay/stripe/billable.rb', line 212

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

  # Line items are optional
  if (line_items = options.delete(:line_items))
    quantity = options.delete(:quantity) || 1

    args[:line_items] = Array.wrap(line_items).map { |item|
      if item.is_a? Hash
        item
      else
        {
          price: item,
          quantity: quantity
        }
      end
    }
  end

  ::Stripe::Checkout::Session.create(args.merge(options), stripe_options)
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)


245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/pay/stripe/billable.rb', line 245

def checkout_charge(amount:, name:, quantity: 1, **options)
  customer unless processor_id?
  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_intent(options = {}) ⇒ Object


180
181
182
183
184
185
# File 'lib/pay/stripe/billable.rb', line 180

def create_setup_intent(options = {})
  ::Stripe::SetupIntent.create({
    customer: processor_id,
    usage: :off_session
  }.merge(options), stripe_options)
end

#customerObject

Retrieves a Stripe::Customer object

Finds an existing Stripe::Customer if processor_id exists Creates a new Stripe::Customer using `customer_attributes` if empty processor_id

Updates the default payment method automatically if a payment_method_token is set

Returns a Stripe::Customer object


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

def customer
  stripe_customer = if processor_id?
    ::Stripe::Customer.retrieve({id: processor_id, expand: ["tax"]}, stripe_options)
  else
    sc = ::Stripe::Customer.create(customer_attributes.merge(expand: ["tax"]), stripe_options)
    pay_customer.update!(processor_id: sc.id, stripe_account: )
    sc
  end

  if payment_method_token?
    payment_method = ::Stripe::PaymentMethod.attach(payment_method_token, {customer: stripe_customer.id}, stripe_options)
    pay_payment_method = save_payment_method(payment_method, default: false)
    pay_payment_method.make_default!

    pay_customer.payment_method_token = nil
  end

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

#customer_attributesObject

Returns a hash of attributes for the Stripe::Customer object


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/pay/stripe/billable.rb', line 26

def customer_attributes
  owner = pay_customer.owner

  attributes = case owner.class.pay_stripe_customer_attributes
  when Symbol
    owner.send(owner.class.pay_stripe_customer_attributes, pay_customer)
  when Proc
    owner.class.pay_stripe_customer_attributes.call(pay_customer)
  end

  # Guard against attributes being returned nil
  attributes ||= {}

  {email: email, name: customer_name}.merge(attributes)
end

#invoice!(options = {}) ⇒ Object


171
172
173
174
# File 'lib/pay/stripe/billable.rb', line 171

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

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


167
168
169
# File 'lib/pay/stripe/billable.rb', line 167

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

#save_payment_method(payment_method, default:) ⇒ Object

Save the Stripe::PaymentMethod to the database


152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/pay/stripe/billable.rb', line 152

def save_payment_method(payment_method, default:)
  pay_payment_method = pay_customer.payment_methods.where(processor_id: payment_method.id).first_or_initialize

  attributes = Pay::Stripe::PaymentMethod.extract_attributes(payment_method).merge(default: default)

  # Ignore the payment method if it's already in the database
  pay_customer.payment_methods.where.not(id: pay_payment_method.id).update_all(default: false) if default
  pay_payment_method.update!(attributes)

  # Reload the Rails association
  pay_customer.reload_default_payment_method

  pay_payment_method
end

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


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/pay/stripe/billable.rb', line 104

def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
  quantity = options.delete(:quantity)
  opts = {
    expand: ["pending_setup_intent", "latest_invoice.payment_intent", "latest_invoice.charge.invoice"],
    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 payment method if needed
  opts[:customer] = customer.id

  # Create subscription on Stripe
  stripe_sub = ::Stripe::Subscription.create(opts.merge(Pay::Stripe::Subscription.expand_options), stripe_options)

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

  # No trial, payment method requires SCA
  if options[:payment_behavior].to_s != "default_incomplete" && subscription.incomplete?
    Pay::Payment.new(stripe_sub.latest_invoice.payment_intent).validate
  end

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

#sync_subscriptionsObject

Syncs a customer's subscriptions from Stripe to the database


193
194
195
196
197
198
199
200
# File 'lib/pay/stripe/billable.rb', line 193

def sync_subscriptions
  subscriptions = ::Stripe::Subscription.list({customer: customer}, stripe_options)
  subscriptions.map do |subscription|
    Pay::Stripe::Subscription.sync(subscription.id)
  end
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#trial_end_date(stripe_sub) ⇒ Object


187
188
189
190
# File 'lib/pay/stripe/billable.rb', line 187

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


176
177
178
# File 'lib/pay/stripe/billable.rb', line 176

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

#update_customer!(**attributes) ⇒ Object

Syncs name and email to Stripe::Customer You can also pass in other attributes that will be merged into the default attributes


74
75
76
77
78
79
80
81
# File 'lib/pay/stripe/billable.rb', line 74

def update_customer!(**attributes)
  customer unless processor_id?
  ::Stripe::Customer.update(
    processor_id,
    customer_attributes.merge(attributes),
    stripe_options
  )
end