mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-22 00:57:26 +00:00
159 lines
5.0 KiB
Ruby
159 lines
5.0 KiB
Ruby
module Spree
|
|
class Payment < ActiveRecord::Base
|
|
include Spree::Payment::Processing
|
|
|
|
IDENTIFIER_CHARS = (('A'..'Z').to_a + ('0'..'9').to_a - %w(0 1 I O)).freeze
|
|
|
|
belongs_to :order, class_name: 'Spree::Order'
|
|
belongs_to :source, polymorphic: true
|
|
belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
|
|
|
|
has_many :offsets, -> { where("source_type = 'Spree::Payment' AND amount < 0 AND state = 'completed'") },
|
|
class_name: "Spree::Payment", foreign_key: :source_id
|
|
has_many :log_entries, as: :source
|
|
|
|
before_validation :validate_source
|
|
before_save :set_unique_identifier
|
|
|
|
after_save :create_payment_profile, if: :profiles_supported?
|
|
|
|
# update the order totals, etc.
|
|
after_save :update_order
|
|
# invalidate previously entered payments
|
|
after_create :invalidate_old_payments
|
|
|
|
attr_accessor :source_attributes
|
|
after_initialize :build_source
|
|
|
|
scope :from_credit_card, -> { where(source_type: 'Spree::CreditCard') }
|
|
scope :with_state, ->(s) { where(state: s.to_s) }
|
|
scope :completed, -> { with_state('completed') }
|
|
scope :pending, -> { with_state('pending') }
|
|
scope :failed, -> { with_state('failed') }
|
|
scope :valid, -> { where('state NOT IN (?)', %w(failed invalid)) }
|
|
|
|
after_rollback :persist_invalid
|
|
|
|
def persist_invalid
|
|
return unless ['failed', 'invalid'].include?(state)
|
|
state_will_change!
|
|
save
|
|
end
|
|
|
|
# order state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
|
|
state_machine initial: :checkout do
|
|
# With card payments, happens before purchase or authorization happens
|
|
event :started_processing do
|
|
transition from: [:checkout, :pending, :completed, :processing], to: :processing
|
|
end
|
|
# When processing during checkout fails
|
|
event :failure do
|
|
transition from: [:pending, :processing], to: :failed
|
|
end
|
|
# With card payments this represents authorizing the payment
|
|
event :pend do
|
|
transition from: [:checkout, :processing], to: :pending
|
|
end
|
|
# With card payments this represents completing a purchase or capture transaction
|
|
event :complete do
|
|
transition from: [:processing, :pending, :checkout], to: :completed
|
|
end
|
|
event :void do
|
|
transition from: [:pending, :completed, :checkout], to: :void
|
|
end
|
|
# when the card brand isnt supported
|
|
event :invalidate do
|
|
transition from: [:checkout], to: :invalid
|
|
end
|
|
end
|
|
|
|
def currency
|
|
order.currency
|
|
end
|
|
|
|
def money
|
|
Spree::Money.new(amount, { currency: currency })
|
|
end
|
|
alias display_amount money
|
|
|
|
def offsets_total
|
|
offsets.pluck(:amount).sum
|
|
end
|
|
|
|
def credit_allowed
|
|
amount - offsets_total
|
|
end
|
|
|
|
def can_credit?
|
|
credit_allowed > 0
|
|
end
|
|
|
|
# see https://github.com/spree/spree/issues/981
|
|
def build_source
|
|
return if source_attributes.nil?
|
|
if payment_method and payment_method.payment_source_class
|
|
self.source = payment_method.payment_source_class.new(source_attributes)
|
|
end
|
|
end
|
|
|
|
def actions
|
|
return [] unless payment_source and payment_source.respond_to? :actions
|
|
payment_source.actions.select { |action| !payment_source.respond_to?("can_#{action}?") or payment_source.send("can_#{action}?", self) }
|
|
end
|
|
|
|
def payment_source
|
|
res = source.is_a?(Payment) ? source.source : source
|
|
res || payment_method
|
|
end
|
|
|
|
private
|
|
|
|
def validate_source
|
|
if source && !source.valid?
|
|
source.errors.each do |field, error|
|
|
field_name = I18n.t("activerecord.attributes.#{source.class.to_s.underscore}.#{field}")
|
|
self.errors.add(Spree.t(source.class.to_s.demodulize.underscore), "#{field_name} #{error}")
|
|
end
|
|
end
|
|
return !errors.present?
|
|
end
|
|
|
|
def profiles_supported?
|
|
payment_method.respond_to?(:payment_profiles_supported?) && payment_method.payment_profiles_supported?
|
|
end
|
|
|
|
def create_payment_profile
|
|
return unless source.is_a?(CreditCard) && source.number && !source.has_payment_profile?
|
|
payment_method.create_profile(self)
|
|
rescue ActiveMerchant::ConnectionError => e
|
|
gateway_error e
|
|
end
|
|
|
|
def invalidate_old_payments
|
|
order.payments.with_state('checkout').where("id != ?", self.id).each do |payment|
|
|
payment.invalidate!
|
|
end
|
|
end
|
|
|
|
def update_order
|
|
order.payments.reload
|
|
order.update!
|
|
end
|
|
|
|
# Necessary because some payment gateways will refuse payments with
|
|
# duplicate IDs. We *were* using the Order number, but that's set once and
|
|
# is unchanging. What we need is a unique identifier on a per-payment basis,
|
|
# and this is it. Related to #1998.
|
|
# See https://github.com/spree/spree/issues/1998#issuecomment-12869105
|
|
def set_unique_identifier
|
|
begin
|
|
self.identifier = generate_identifier
|
|
end while self.class.exists?(identifier: self.identifier)
|
|
end
|
|
|
|
def generate_identifier
|
|
Array.new(8){ IDENTIFIER_CHARS.sample }.join
|
|
end
|
|
end
|
|
end
|