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