module Pay::Stripe

def self.configure_webhooks

def self.configure_webhooks
  Pay::Webhooks.configure do |events|
    # Listen to the charge event to make sure we get non-subscription
    # purchases as well. Invoice is only for subscriptions and manual creation
    # so it does not include individual charges.
    events.subscribe "stripe.charge.refunded", Pay::Stripe::Webhooks::ChargeRefunded.new
    events.subscribe "stripe.charge.succeeded", Pay::Stripe::Webhooks::ChargeSucceeded.new
    events.subscribe "stripe.charge.updated", Pay::Stripe::Webhooks::ChargeUpdated.new
    events.subscribe "stripe.payment_intent.succeeded", Pay::Stripe::Webhooks::PaymentIntentSucceeded.new
    # Warn user of upcoming charges for their subscription. This is handy for
    # notifying annual users their subscription will renew shortly.
    # This probably should be ignored for monthly subscriptions.
    events.subscribe "stripe.invoice.upcoming", Pay::Stripe::Webhooks::SubscriptionRenewing.new
    # Payment action is required to process an invoice
    events.subscribe "stripe.invoice.payment_action_required", Pay::Stripe::Webhooks::PaymentActionRequired.new
    # If an invoice payment fails, we want to notify the user via email to update their payment details
    events.subscribe "stripe.invoice.payment_failed", Pay::Stripe::Webhooks::PaymentFailed.new
    # If a subscription is manually created on Stripe, we want to sync
    events.subscribe "stripe.customer.subscription.created", Pay::Stripe::Webhooks::SubscriptionCreated.new
    # If the plan, quantity, or trial ending date is updated on Stripe, we want to sync
    events.subscribe "stripe.customer.subscription.updated", Pay::Stripe::Webhooks::SubscriptionUpdated.new
    # When a customers subscription is canceled, we want to update our records
    events.subscribe "stripe.customer.subscription.deleted", Pay::Stripe::Webhooks::SubscriptionDeleted.new
    # When a customers subscription trial period is 3 days from ending or ended immediately this event is fired
    events.subscribe "stripe.customer.subscription.trial_will_end", Pay::Stripe::Webhooks::SubscriptionTrialWillEnd.new
    # Monitor changes for customer's default card changing and invoice credit updates
    events.subscribe "stripe.customer.updated", Pay::Stripe::Webhooks::CustomerUpdated.new
    # If a customer was deleted in Stripe, their subscriptions should be cancelled
    events.subscribe "stripe.customer.deleted", Pay::Stripe::Webhooks::CustomerDeleted.new
    # If a customer's payment source was deleted in Stripe, we should update as well
    events.subscribe "stripe.payment_method.attached", Pay::Stripe::Webhooks::PaymentMethodAttached.new
    events.subscribe "stripe.payment_method.updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
    events.subscribe "stripe.payment_method.card_automatically_updated", Pay::Stripe::Webhooks::PaymentMethodUpdated.new
    events.subscribe "stripe.payment_method.detached", Pay::Stripe::Webhooks::PaymentMethodDetached.new
    # If an account is updated in stripe, we should update it as well
    events.subscribe "stripe.account.updated", Pay::Stripe::Webhooks::AccountUpdated.new
    # Handle subscriptions in Stripe Checkout Sessions
    events.subscribe "stripe.checkout.session.completed", Pay::Stripe::Webhooks::CheckoutSessionCompleted.new
    events.subscribe "stripe.checkout.session.async_payment_succeeded", Pay::Stripe::Webhooks::CheckoutSessionAsyncPaymentSucceeded.new
  end
end

def self.enabled?

def self.enabled?
  return false unless Pay.enabled_processors.include?(:stripe) && defined?(::Stripe)
  Pay::Engine.version_matches?(required: REQUIRED_VERSION, current: ::Stripe::VERSION) || (raise "[Pay] stripe gem must be version #{REQUIRED_VERSION}")
end

def self.find_by_client_reference_id(client_reference_id)

def self.find_by_client_reference_id(client_reference_id)
  # If there is a client reference ID, make sure we have a Pay::Customer record
  # client_reference_id should be in the format of "User/1"
  model_name, id = client_reference_id.split("_", 2)
  # Only allow model names that use Pay
  return unless model_names.include?(model_name)
  model_name.constantize.find(id)
rescue ActiveRecord::RecordNotFound
  Rails.logger.error "[Pay] Unable to locate record with: #{client_reference_id}"
  nil
end

def self.private_key

def self.private_key
  find_value_by_name(:stripe, :private_key)
end

def self.public_key

def self.public_key
  find_value_by_name(:stripe, :public_key)
end

def self.setup

def self.setup
  ::Stripe.api_key = private_key
  # Used by Stripe to identify Pay for support
  ::Stripe.set_app_info("PayRails", partner_id: "pp_partner_IqhY0UExnJYLxg", version: Pay::VERSION, url: "https://github.com/pay-rails/pay")
  # Automatically retry requests that fail
  # This automatically includes idempotency keys in the request to guarantee that retires are safe
  # https://github.com/stripe/stripe-ruby#configuring-automatic-retries
  ::Stripe.max_network_retries = 2
end

def self.signing_secret

def self.signing_secret
  find_value_by_name(:stripe, :signing_secret)
end

def self.sync_checkout_session(session_id, stripe_account: nil, try: 0, retries: 5)

Subscriptions aren't always immediately associated, so we want to retry by default
def self.sync_checkout_session(session_id, stripe_account: nil, try: 0, retries: 5)
  checkout_session = ::Stripe::Checkout::Session.retrieve({id: session_id, expand: ["payment_intent.latest_charge"]}, {stripe_account: stripe_account}.compact)
  case checkout_session.mode
  when "payment"
    if (id = checkout_session.payment_intent.try(:latest_charge)&.id)
      Pay::Stripe::Charge.sync(id, stripe_account: stripe_account, retries: 5)
    end
  when "subscription"
    Pay::Stripe::Subscription.sync(checkout_session.subscription, stripe_account: stripe_account)
  end
rescue ::Stripe::InvalidRequestError
  if try > retries
    raise
  else
    try += 1
    sleep 0.15**try
    retry
  end
end

def self.to_client_reference_id(record)

def self.to_client_reference_id(record)
  raise ArgumentError, "#{record.class.name} does not include Pay. Allowed models: #{model_names.to_a.join(", ")}" unless model_names.include?(record.class.name)
  [record.class.name, record.id].join("_")
end

def self.webhook_receive_test_events

def self.webhook_receive_test_events
  value = find_value_by_name(:stripe, :webhook_receive_test_events)
  value.blank? || ActiveModel::Type::Boolean.new.cast(value)
end