class Pay::Stripe::Customer

def add_payment_method(payment_method_id, default: false)

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

def api_record(expand: ["tax", "invoice_credit_balance"])

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

def api_record_attributes

Returns a hash of attributes for the Stripe::Customer object
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

def authorize(amount, options = {})

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

def billing_portal(**options)

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

def charge(amount, options = {})

Charges an amount to the customer's default payment method
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

def checkout(**options)


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

checkout(mode: "subscription")
checkout(mode: "setup")
checkout(mode: "payment")

https://stripe.com/docs/api/checkout/sessions/create
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

def checkout_charge(amount:, name:, quantity: 1, **options)


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

https://stripe.com/docs/api/checkout/sessions/create
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

def create_meter_event(event_name, payload: {}, **options)

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

Creates a meter event to bill for usage
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

def create_payment_intent(amount, options = {})

Creates and returns a Stripe::PaymentIntent
def create_payment_intent(amount, options = {})
  args = {
    amount: amount,
    currency: "usd",
    customer: processor_id || api_record.id,
    expand: Pay::Stripe::Charge::EXPAND.map { |option| "latest_charge.#{option}" },
    return_url: root_url
  }.merge(options)
  ::Stripe::PaymentIntent.create(args, stripe_options)
end

def create_setup_intent(options = {})

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

def customer_session(**options)

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

def invoice!(options = {})

def invoice!(options = {})
  ::Stripe::Invoice.create(options.merge(customer: processor_id || api_record.id), stripe_options).pay
end

def merge_session_id_param(url)

Includes the `session_id` param for Stripe Checkout with existing params (and makes sure the curly braces aren't escaped)
def merge_session_id_param(url)
  uri = URI.parse(url)
  uri.query = URI.encode_www_form(URI.decode_www_form(uri.query.to_s).to_h.merge("stripe_checkout_session_id" => "{CHECKOUT_SESSION_ID}").to_a)
  uri.to_s.gsub("%7BCHECKOUT_SESSION_ID%7D", "{CHECKOUT_SESSION_ID}")
end

def preview_invoice(**options)

def preview_invoice(**options)
  ::Stripe::Invoice.create_preview(options.merge(customer: processor_id || api_record.id), stripe_options)
end

def save_payment_method(payment_method, default:)

Save the Stripe::PaymentMethod to the database
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

def stripe_options

Options for Stripe requests
def stripe_options
  {stripe_account: stripe_account}.compact
end

def subscribe(name: Pay.default_product_name, plan: Pay.default_plan_name, **options)

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] = 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?
    payment_intent_id = stripe_sub.latest_invoice.payments.first.payment.payment_intent
    Pay::Payment.from_id(payment_intent_id).validate
  end
  subscription
rescue ::Stripe::StripeError => e
  raise Pay::Stripe::Error, e
end

def sync_subscriptions(**options)

Note that by default canceled subscriptions are NOT returned by Stripe. In order to include them, use `sync_subscriptions(status: "all")`.
Syncs a customer's subscriptions from Stripe to the database.
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

def terminal_charge(amount, options = {})

Used for creating Stripe Terminal charges
def terminal_charge(amount, options = {})
  create_payment_intent(amount, options.merge(payment_method_types: ["card_present"], capture_method: "manual"))
end

def update_api_record(**attributes)

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