Class: Pay::Stripe::Customer

Inherits:
Customer
  • Object
show all
Includes:
Routing
Defined in:
app/models/pay/stripe/customer.rb

Instance Method Summary collapse

Methods included from Routing

#default_url_options

Instance Method Details

#add_payment_method(payment_method_id, default: false) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'app/models/pay/stripe/customer.rb', line 85

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

  if default
    ::Stripe::Customer.update(stripe_customer_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

#api_record(expand: ["tax", "invoice_credit_balance"]) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/models/pay/stripe/customer.rb', line 26

def api_record(expand: ["tax", "invoice_credit_balance"])
  with_lock do
    if processor_id?
      ::Stripe::Customer.retrieve({id: processor_id, expand: expand}, stripe_options)
    else
      ::Stripe::Customer.create(api_record_attributes.merge(expand: expand), stripe_options).tap do |customer|
        update!(processor_id: customer.id, stripe_account: )
      end
    end
  end
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#api_record_attributesObject

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



12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'app/models/pay/stripe/customer.rb', line 12

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

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

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

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



251
252
253
# File 'app/models/pay/stripe/customer.rb', line 251

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

#billing_portal(**options) ⇒ Object



234
235
236
237
238
239
240
241
242
# File 'app/models/pay/stripe/customer.rb', line 234

def billing_portal(**options)
  args = {
    customer: stripe_customer_id,
    return_url: options.delete(:return_url) || root_url
  }
  ::Stripe::BillingPortal::Session.create(args.merge(options), stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

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

Charges an amount to the customer's default payment method



47
48
49
50
51
52
53
54
55
56
57
# File 'app/models/pay/stripe/customer.rb', line 47

def charge(amount, options = {})
  args = {confirm: true, payment_method: default_payment_method&.processor_id}.merge(options)
  payment_intent = create_payment_intent(amount, args)

  Pay::Payment.new(payment_intent).validate

  charge = payment_intent.latest_charge
  Pay::Stripe::Charge.sync(charge.id, object: charge, stripe_account: )
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#checkout(**options) ⇒ Object

https://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)



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/models/pay/stripe/customer.rb', line 178

def checkout(**options)
  args = {
    customer: stripe_customer_id,
    mode: "payment"
  }

  # Hosted (the default) checkout sessions require a success_url and cancel_url
  if ["", "hosted_page"].include?(options[:ui_mode].to_s)
    args[:success_url] = merge_session_id_param(options.delete(:success_url) || root_url)
    args[:cancel_url] = merge_session_id_param(options.delete(:cancel_url) || root_url)
  end

  if options[:return_url]
    args[:return_url] = merge_session_id_param(options.delete(:return_url))
  end

  # 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)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

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

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

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



219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'app/models/pay/stripe/customer.rb', line 219

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_meter_event(event_name, payload: {}, **options) ⇒ Object

Creates a meter event to bill for usage

create_meter_event(:api_request, value: 1) create_meter_event(:api_request, token: 7)



265
266
267
268
269
270
271
272
# File 'app/models/pay/stripe/customer.rb', line 265

def create_meter_event(event_name, payload: {}, **options)
  ::Stripe::Billing::MeterEvent.create({
    event_name: event_name,
    payload: {stripe_customer_id: stripe_customer_id}.merge(payload)
  }.merge(options), stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

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

Creates and returns a Stripe::PaymentIntent



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'app/models/pay/stripe/customer.rb', line 120

def create_payment_intent(amount, options = {})
  args = {
    amount: amount,
    currency: "usd",
    customer: stripe_customer_id,
    expand: Pay::Stripe::Charge::EXPAND.map { |option| "latest_charge.#{option}" },
    return_url: root_url
  }.merge(options)

  ::Stripe::PaymentIntent.create(args, stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#create_setup_intent(options = {}) ⇒ Object



139
140
141
142
143
# File 'app/models/pay/stripe/customer.rb', line 139

def create_setup_intent(options = {})
  ::Stripe::SetupIntent.create({customer: stripe_customer_id, usage: :off_session}.merge(options), stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#customer_session(**options) ⇒ Object



244
245
246
247
248
249
# File 'app/models/pay/stripe/customer.rb', line 244

def customer_session(**options)
  args = {customer: stripe_customer_id}
  ::Stripe::CustomerSession.create(args.merge(options), stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#invoice!(options = {}) ⇒ Object



145
146
147
148
149
# File 'app/models/pay/stripe/customer.rb', line 145

def invoice!(options = {})
  ::Stripe::Invoice.create(options.merge(customer: stripe_customer_id), stripe_options).pay
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#preview_invoice(**options) ⇒ Object



151
152
153
154
155
# File 'app/models/pay/stripe/customer.rb', line 151

def preview_invoice(**options)
  ::Stripe::Invoice.create_preview(options.merge(customer: stripe_customer_id), stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

#retry_past_due_subscriptions!(status: [:past_due]) ⇒ Object

Attempts to pay all past_due subscription invoices to bring them back to active state Pass in status: [] to include other subscription statuses



257
258
259
# File 'app/models/pay/stripe/customer.rb', line 257

def retry_past_due_subscriptions!(status: [:past_due])
  subscriptions.where(status: Array.wrap(status)).each(&:pay_open_invoices)
end

#save_payment_method(payment_method, default:) ⇒ Object

Save the Stripe::PaymentMethod to the database



102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/pay/stripe/customer.rb', line 102

def save_payment_method(payment_method, default:)
  pay_payment_method = 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
  payment_methods.where.not(id: pay_payment_method.id).update_all(default: false) if default
  pay_payment_method.update!(attributes)

  # Reload the Rails association
  reload_default_payment_method

  pay_payment_method
end

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



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/models/pay/stripe/customer.rb', line 59

def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)
  quantity = options.delete(:quantity)
  opts = {
    items: [price: plan, quantity: quantity]
  }.merge(options)

  # Load the Stripe customer to verify it exists and update payment method if needed
  opts[:customer] = stripe_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?
    payment_intent_id = stripe_sub.latest_invoice.payments.first.payment.payment_intent
    Pay::Payment.from_id(payment_intent_id, stripe_account: ).validate
  end

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

#sync_subscriptions(**options) ⇒ Object

Syncs a customer's subscriptions from Stripe to the database. Note that by default canceled subscriptions are NOT returned by Stripe. In order to include them, use sync_subscriptions(status: "all").



159
160
161
162
163
164
165
166
# File 'app/models/pay/stripe/customer.rb', line 159

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

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

Used for creating Stripe Terminal charges



135
136
137
# File 'app/models/pay/stripe/customer.rb', line 135

def terminal_charge(amount, options = {})
  create_payment_intent(amount, options.merge(payment_method_types: ["card_present"], capture_method: "manual"))
end

#update_api_record(**attributes) ⇒ Object



40
41
42
43
44
# File 'app/models/pay/stripe/customer.rb', line 40

def update_api_record(**attributes)
  ::Stripe::Customer.update(stripe_customer_id, api_record_attributes.merge(attributes), stripe_options)
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end