Files
openfoodnetwork/app/models/spree/gateway/stripe_sca.rb
Gaetan Craig-Riou edc40c9ea9 Refactor Spree::Gateway::StripeSCA
- fix confusing comment
- rename parameter to better reflect usage
2024-02-19 10:58:31 +11:00

183 lines
6.1 KiB
Ruby

# frozen_string_literal: true
require 'stripe/profile_storer'
require 'stripe/credit_card_cloner'
require 'stripe/authorize_response_patcher'
require 'stripe/payment_intent_validator'
require 'active_merchant/billing/gateways/stripe'
module Spree
class Gateway
class StripeSCA < Gateway
VOIDABLE_STATES = [
"requires_payment_method", "requires_capture", "requires_confirmation", "requires_action"
].freeze
preference :enterprise_id, :integer
validate :ensure_enterprise_selected
def external_gateway?
true
end
def external_payment_url(options)
return if options[:order].blank?
Checkout::StripeRedirect.new(self, options[:order]).path
end
def method_type
'stripe_sca'
end
def provider_class
ActiveMerchant::Billing::StripePaymentIntentsGateway
end
def payment_profiles_supported?
true
end
def stripe_account_id
StripeAccount.find_by(enterprise_id: preferred_enterprise_id)&.stripe_user_id
end
# NOTE: this method is required by Spree::Payment::Processing
def purchase(money, creditcard, gateway_options)
begin
payment_intent_id = fetch_payment_intent(creditcard, gateway_options)
rescue Stripe::StripeError => e
return failed_activemerchant_billing_response(e.message)
end
options = basic_options(gateway_options)
options[:customer] = creditcard.gateway_customer_profile_id
provider.capture(money, payment_intent_id, options)
rescue Stripe::StripeError => e
failed_activemerchant_billing_response(e.message)
end
def capture(money, payment_intent_id, gateway_options)
options = basic_options(gateway_options)
provider.capture(money, payment_intent_id, options)
end
# NOTE: this method is required by Spree::Payment::Processing
def charge_offline(money, creditcard, gateway_options)
customer, payment_method =
Stripe::CreditCardCloner.new(creditcard, stripe_account_id).find_or_clone
options = basic_options(gateway_options).merge(customer:, off_session: true)
provider.purchase(money, payment_method, options)
rescue Stripe::StripeError => e
failed_activemerchant_billing_response(e.message)
end
# NOTE: this method is required by Spree::Payment::Processing
def authorize(money, creditcard, gateway_options)
authorize_response =
provider.authorize(*options_for_authorize(money, creditcard, gateway_options))
Stripe::AuthorizeResponsePatcher.new(authorize_response).call!
rescue Stripe::StripeError => e
failed_activemerchant_billing_response(e.message)
end
# NOTE: this method is required by Spree::Payment::Processing
def void(payment_intent_id, _creditcard, gateway_options)
payment_intent_response = Stripe::PaymentIntent.retrieve(
payment_intent_id, stripe_account: stripe_account_id
)
gateway_options[:stripe_account] = stripe_account_id
# If a payment has been confirmed it can't be voided by Stripe, and must be refunded instead
if voidable?(payment_intent_response)
provider.void(payment_intent_id, gateway_options)
else
provider.refund(
payment_intent_response.amount_received, payment_intent_id, gateway_options
)
end
end
# NOTE: this method is required by Spree::Payment::Processing
def credit(money, _creditcard, payment_intent_id, gateway_options)
gateway_options[:stripe_account] = stripe_account_id
provider.refund(money, payment_intent_id, gateway_options)
end
def create_profile(payment)
return unless payment.source.gateway_customer_profile_id.nil?
profile_storer = Stripe::ProfileStorer.new(payment, provider)
profile_storer.create_customer_from_token
end
private
def voidable?(payment_intent_response)
VOIDABLE_STATES.include? payment_intent_response.status
end
# In this gateway, what we call 'secret_key' is the 'login'
def options
options = super
options.merge(login: Stripe.api_key)
end
def basic_options(gateway_options)
options = {}
options[:description] = "Spree Order ID: #{gateway_options[:order_id]}"
options[:currency] = gateway_options[:currency]
options[:stripe_account] = stripe_account_id
options[:execute_threed] = true # Handle 3DS responses
options
end
def options_for_authorize(money, creditcard, gateway_options)
options = basic_options(gateway_options)
options[:return_url] = gateway_options[:return_url] || payment_gateways_confirm_stripe_url
customer_id, payment_method_id =
Stripe::CreditCardCloner.new(creditcard, stripe_account_id).find_or_clone
options[:customer] = customer_id
[money, payment_method_id, options]
end
def fetch_payment_intent(creditcard, gateway_options)
payment = fetch_payment(creditcard, gateway_options)
raise Stripe::StripeError, I18n.t(:no_pending_payments) unless payment&.response_code
payment_intent_response = Stripe::PaymentIntentValidator.new(payment).call
raise_if_not_in_capture_state(payment_intent_response)
payment.response_code
end
def raise_if_not_in_capture_state(payment_intent_response)
state = payment_intent_response.status
return if state == 'requires_capture'
raise Stripe::StripeError, I18n.t(:invalid_payment_state, state:)
end
def fetch_payment(creditcard, gateway_options)
order_number = gateway_options[:order_id].split('-').first
Spree::Order.find_by(number: order_number).payments.merge(creditcard.payments).last
end
def failed_activemerchant_billing_response(error_message)
ActiveMerchant::Billing::Response.new(false, error_message)
end
def ensure_enterprise_selected
return if preferred_enterprise_id&.positive?
errors.add(:stripe_account_owner, I18n.t(:error_required))
end
end
end
end