Class: Pay::Stripe::Customer

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

Instance Method Summary collapse

Methods included from Routing

#default_url_options

Methods inherited from Customer

#active?, #customer_name, #deleted?, #has_incomplete_payment?, #on_generic_trial?, #on_trial?, #on_trial_or_subscribed?, #retry_past_due_subscriptions!, #subscribed?, #subscription, #update_payment_method

Instance Method Details

#add_payment_method(payment_method_id, default: false) ⇒ Object



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

def add_payment_method(payment_method_id, default: false)
  api_record 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

#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



241
242
243
# File 'app/models/pay/stripe/customer.rb', line 241

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

#billing_portal(**options) ⇒ Object



226
227
228
229
230
231
232
233
# File 'app/models/pay/stripe/customer.rb', line 226

def billing_portal(**options)
  api_record 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

Charges an amount to the customer’s default payment method



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

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)
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)



170
171
172
173
174
175
176
177
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
# File 'app/models/pay/stripe/customer.rb', line 170

def checkout(**options)
  api_record unless processor_id?
  args = {
    customer: processor_id,
    mode: "payment"
  }

  # Hosted (the default) checkout sessions require a success_url and cancel_url
  if ["", "hosted"].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)
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)



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'app/models/pay/stripe/customer.rb', line 210

def checkout_charge(amount:, name:, quantity: 1, **options)
  api_record 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_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)



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

def create_meter_event(event_name, payload: {}, **options)
  api_record unless processor_id?
  ::Stripe::Billing::MeterEvent.create({
    event_name: event_name,
    payload: {stripe_customer_id: processor_id}.merge(payload)
  }.merge(options))
end

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

Creates and returns a Stripe::PaymentIntent



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

def create_payment_intent(amount, options = {})
  args = {
    amount: amount,
    currency: "usd",
    customer: processor_id || api_record.id,
    expand: ["latest_charge.refunds"],
    return_url: root_url
  }.merge(options)

  ::Stripe::PaymentIntent.create(args, stripe_options)
end

#create_setup_intent(options = {}) ⇒ Object



137
138
139
# File 'app/models/pay/stripe/customer.rb', line 137

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

#customer_session(**options) ⇒ Object



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

def customer_session(**options)
  api_record unless processor_id?
  args = {customer: processor_id}
  ::Stripe::CustomerSession.create(args.merge(options), stripe_options)
end

#invoice!(options = {}) ⇒ Object



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

def invoice!(options = {})
  ::Stripe::Invoice.create(options.merge(customer: processor_id || api_record.id), stripe_options).pay
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



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

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"],
    items: [plan: plan, quantity: quantity]
  }.merge(options)

  # Load the Stripe customer to verify it exists and update payment method if needed
  opts[:customer] = processor_id || api_record.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_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”)`.



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

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

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

Used for creating Stripe Terminal charges



133
134
135
# File 'app/models/pay/stripe/customer.rb', line 133

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

#upcoming_invoiceObject



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

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

#update_api_record(**attributes) ⇒ Object



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

def update_api_record(**attributes)
  api_record unless processor_id?
  ::Stripe::Customer.update(processor_id, api_record_attributes.merge(attributes), stripe_options)
end