Files
openfoodnetwork/app/models/spree/credit_card.rb
2024-06-11 15:17:32 +10:00

171 lines
4.5 KiB
Ruby

# frozen_string_literal: true
module Spree
class CreditCard < ApplicationRecord
self.belongs_to_required_by_default = false
belongs_to :payment_method
belongs_to :user
has_many :payments, as: :source, dependent: :nullify
before_save :set_last_digits
attr_accessor :verification_value
attr_reader :number
attr_writer :save_requested_by_customer # For holding customer preference in memory
validates :month, :year, numericality: { only_integer: true }
validates :number, presence: true, unless: :has_payment_profile?, on: :create
validates :verification_value, presence: true, unless: :has_payment_profile?, on: :create
validate :expiry_not_in_the_past
after_create :ensure_single_default_card
after_save :ensure_single_default_card, if: :default_card_needs_updating?
scope :with_payment_profile, -> { where.not(gateway_customer_profile_id: nil) }
# needed for some of the ActiveMerchant gateways (eg. SagePay)
alias_attribute :brand, :cc_type
def expiry=(expiry)
self[:month], self[:year] = expiry.split(" / ")
self[:year] = "20#{self[:year]}"
end
def number=(num)
@number = begin
num.gsub(/[^0-9]/, '')
rescue StandardError
nil
end
end
def cc_type=(type)
reformat_card_type!(type)
end
def set_last_digits
self.last_digits ||= number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1)
end
def name?
first_name? && last_name?
end
def name
"#{first_name} #{last_name}"
end
def verification_value?
verification_value.present?
end
# Show the card number, with all but last 4 numbers replace with "X". (XXXX-XXXX-XXXX-4338)
def display_number
"XXXX-XXXX-XXXX-#{last_digits}"
end
def actions
%w{capture_and_complete_order void credit resend_authorization_email}
end
def can_resend_authorization_email?(payment)
payment.requires_authorization?
end
# Indicates whether its possible to capture the payment
def can_capture_and_complete_order?(payment)
return false if payment.requires_authorization?
payment.pending? || payment.checkout?
end
# Indicates whether its possible to void the payment.
def can_void?(payment)
!payment.void?
end
# Indicates whether its possible to credit the payment. Note that most gateways require that the
# payment be settled first which generally happens within 12-24 hours of the transaction.
def can_credit?(payment)
return false unless payment.completed?
return false unless payment.order.payment_state == 'credit_owed'
payment.credit_allowed.positive?
end
# Allows us to use a gateway_payment_profile_id to store Stripe Tokens
def has_payment_profile?
gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
end
def to_active_merchant
ActiveMerchant::Billing::CreditCard.new(
number:,
month:,
year:,
verification_value:,
first_name:,
last_name:
)
end
def save_requested_by_customer?
!!@save_requested_by_customer
end
private
def reformat_card_type!(type)
self[:cc_type] = active_merchant_card_type(type)
end
# ActiveMerchant requires certain credit card brand names to be stored in a specific format.
# See: https://github.com/activemerchant/active_merchant/blob/master/lib/active_merchant/billing/credit_card.rb#L89
def active_merchant_card_type(type)
card_type = type.to_s.downcase
case card_type
when "mastercard", "maestro", "master card"
"master"
when "amex", "american express"
"american_express"
when "dinersclub", "diners club"
"diners_club"
else
card_type
end
end
def expiry_not_in_the_past
return unless year.present? && month.present?
time = "#{year}-#{month}-1".to_time
return unless time < Time.zone.now.to_time.beginning_of_month
errors.add(:base, :card_expired)
end
def reusable?
gateway_customer_profile_id.present?
end
def default_missing?
!user.credit_cards.where(is_default: true).exists?
end
def default_card_needs_updating?
saved_change_to_is_default? || saved_change_to_gateway_customer_profile_id?
end
def ensure_single_default_card
return unless user
return unless is_default? || (reusable? && default_missing?)
user.credit_cards.update_all(['is_default=(id=?)', id])
self.is_default = true
end
end
end