mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-04 22:16:08 +00:00
Merge pull request #5879 from luisramos0/payments
[Bye bye Spree] Bring models payment_method, credit_card and gateway from spree_core
This commit is contained in:
155
app/models/spree/credit_card.rb
Normal file
155
app/models/spree/credit_card.rb
Normal file
@@ -0,0 +1,155 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class CreditCard < ActiveRecord::Base
|
||||
belongs_to :payment_method
|
||||
belongs_to :user
|
||||
|
||||
has_many :payments, as: :source
|
||||
|
||||
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('gateway_customer_profile_id IS NOT NULL') }
|
||||
|
||||
# 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
|
||||
|
||||
# cc_type is set by jquery.payment, which helpfully provides different
|
||||
# types from Active Merchant. Converting them is necessary.
|
||||
def cc_type=(type)
|
||||
real_type = case type
|
||||
when 'mastercard', 'maestro'
|
||||
'master'
|
||||
when 'amex'
|
||||
'american_express'
|
||||
when 'dinersclub'
|
||||
'diners_club'
|
||||
else
|
||||
type
|
||||
end
|
||||
self[:cc_type] = real_type
|
||||
end
|
||||
|
||||
def set_last_digits
|
||||
number.to_s.gsub!(/\s/, '')
|
||||
verification_value.to_s.gsub!(/\s/, '')
|
||||
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 void credit}
|
||||
end
|
||||
|
||||
# Indicates whether its possible to capture the payment
|
||||
def can_capture?(payment)
|
||||
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: number,
|
||||
month: month,
|
||||
year: year,
|
||||
verification_value: verification_value,
|
||||
first_name: first_name,
|
||||
last_name: last_name
|
||||
)
|
||||
end
|
||||
|
||||
def save_requested_by_customer?
|
||||
!!@save_requested_by_customer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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.exists?(is_default: true)
|
||||
end
|
||||
|
||||
def default_card_needs_updating?
|
||||
is_default_changed? || gateway_customer_profile_id_changed?
|
||||
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
|
||||
@@ -1,47 +0,0 @@
|
||||
Spree::CreditCard.class_eval do
|
||||
# For holding customer preference in memory
|
||||
attr_writer :save_requested_by_customer
|
||||
|
||||
# Should be able to remove once we reach Spree v2.2.0
|
||||
# https://github.com/spree/spree/commit/411010f3975c919ab298cb63962ee492455b415c
|
||||
belongs_to :payment_method
|
||||
|
||||
belongs_to :user
|
||||
|
||||
after_create :ensure_single_default_card
|
||||
after_save :ensure_single_default_card, if: :default_card_needs_updating?
|
||||
|
||||
# Allows us to use a gateway_payment_profile_id to store Stripe Tokens
|
||||
# Should be able to remove once we reach Spree v2.2.0
|
||||
# Commit: https://github.com/spree/spree/commit/5a4d690ebc64b264bf12904a70187e7a8735ef3f
|
||||
# See also: https://github.com/spree/spree_gateway/issues/111
|
||||
def has_payment_profile? # rubocop:disable Naming/PredicateName
|
||||
gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
|
||||
end
|
||||
|
||||
def save_requested_by_customer?
|
||||
!!@save_requested_by_customer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reusable?
|
||||
gateway_customer_profile_id.present?
|
||||
end
|
||||
|
||||
def default_missing?
|
||||
!user.credit_cards.exists?(is_default: true)
|
||||
end
|
||||
|
||||
def default_card_needs_updating?
|
||||
is_default_changed? || gateway_customer_profile_id_changed?
|
||||
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
|
||||
62
app/models/spree/gateway.rb
Normal file
62
app/models/spree/gateway.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spree/concerns/payment_method_distributors'
|
||||
|
||||
module Spree
|
||||
class Gateway < PaymentMethod
|
||||
include Spree::PaymentMethodDistributors
|
||||
|
||||
delegate_belongs_to :provider, :authorize, :purchase, :capture, :void, :credit
|
||||
|
||||
validates :name, :type, presence: true
|
||||
|
||||
# Default to live
|
||||
preference :server, :string, default: 'live'
|
||||
preference :test_mode, :boolean, default: false
|
||||
|
||||
def payment_source_class
|
||||
CreditCard
|
||||
end
|
||||
|
||||
# instantiates the selected gateway and configures with the options stored in the database
|
||||
def self.current
|
||||
super
|
||||
end
|
||||
|
||||
def provider
|
||||
gateway_options = options
|
||||
gateway_options.delete :login if gateway_options.key?(:login) && gateway_options[:login].nil?
|
||||
if gateway_options[:server]
|
||||
ActiveMerchant::Billing::Base.gateway_mode = gateway_options[:server].to_sym
|
||||
end
|
||||
@provider ||= provider_class.new(gateway_options)
|
||||
end
|
||||
|
||||
def options
|
||||
preferences.each_with_object({}){ |(key, value), memo| memo[key.to_sym] = value; }
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
if @provider.nil? || !@provider.respond_to?(method)
|
||||
super
|
||||
else
|
||||
provider.__send__(method, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def payment_profiles_supported?
|
||||
false
|
||||
end
|
||||
|
||||
def method_type
|
||||
'gateway'
|
||||
end
|
||||
|
||||
def supports?(source)
|
||||
return true unless provider_class.respond_to? :supports?
|
||||
return false unless source.brand
|
||||
|
||||
provider_class.supports?(source.brand)
|
||||
end
|
||||
end
|
||||
end
|
||||
102
app/models/spree/gateway/bogus.rb
Normal file
102
app/models/spree/gateway/bogus.rb
Normal file
@@ -0,0 +1,102 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class Gateway
|
||||
class Bogus < Spree::Gateway
|
||||
TEST_VISA = ['4111111111111111', '4012888888881881', '4222222222222'].freeze
|
||||
TEST_MC = ['5500000000000004', '5555555555554444', '5105105105105100'].freeze
|
||||
TEST_AMEX = ['378282246310005', '371449635398431',
|
||||
'378734493671000', '340000000000009'].freeze
|
||||
TEST_DISC = ['6011000000000004', '6011111111111117', '6011000990139424'].freeze
|
||||
|
||||
VALID_CCS = ['1', TEST_VISA, TEST_MC, TEST_AMEX, TEST_DISC].flatten
|
||||
|
||||
attr_accessor :test
|
||||
|
||||
def provider_class
|
||||
self.class
|
||||
end
|
||||
|
||||
def preferences
|
||||
{}
|
||||
end
|
||||
|
||||
def create_profile(payment)
|
||||
# simulate the storage of credit card profile using remote service
|
||||
success = VALID_CCS.include? payment.source.number
|
||||
payment.source.update(gateway_customer_profile_id: generate_profile_id(success))
|
||||
end
|
||||
|
||||
def authorize(_money, credit_card, _options = {})
|
||||
profile_id = credit_card.gateway_customer_profile_id
|
||||
if VALID_CCS.include?(credit_card.number) || profile_id&.starts_with?('BGS-')
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '12345',
|
||||
avs_result: { code: 'A' })
|
||||
else
|
||||
ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure',
|
||||
{ message: 'Bogus Gateway: Forced failure' },
|
||||
test: true)
|
||||
end
|
||||
end
|
||||
|
||||
def purchase(_money, credit_card, _options = {})
|
||||
profile_id = credit_card.gateway_customer_profile_id
|
||||
if VALID_CCS.include?(credit_card.number) || profile_id&.starts_with?('BGS-')
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '12345',
|
||||
avs_result: { code: 'A' })
|
||||
else
|
||||
ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure',
|
||||
message: 'Bogus Gateway: Forced failure',
|
||||
test: true)
|
||||
end
|
||||
end
|
||||
|
||||
def credit(_money, _credit_card, _response_code, _options = {})
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '12345')
|
||||
end
|
||||
|
||||
def capture(authorization, _credit_card, _gateway_options)
|
||||
if authorization.response_code == '12345'
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '67890')
|
||||
else
|
||||
ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure',
|
||||
error: 'Bogus Gateway: Forced failure', test: true)
|
||||
end
|
||||
end
|
||||
|
||||
def void(_response_code, _credit_card, _options = {})
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '12345')
|
||||
end
|
||||
|
||||
def test?
|
||||
# Test mode is not really relevant with bogus gateway (no such thing as live server)
|
||||
true
|
||||
end
|
||||
|
||||
def payment_profiles_supported?
|
||||
true
|
||||
end
|
||||
|
||||
def actions
|
||||
%w(capture void credit)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_profile_id(success)
|
||||
record = true
|
||||
prefix = success ? 'BGS' : 'FAIL'
|
||||
while record
|
||||
random = "#{prefix}-#{Array.new(6){ rand(6) }.join}"
|
||||
record = CreditCard.find_by(gateway_customer_profile_id: random)
|
||||
end
|
||||
random
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/models/spree/gateway/bogus_simple.rb
Normal file
36
app/models/spree/gateway/bogus_simple.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Bogus Gateway that doesn't support payment profiles
|
||||
module Spree
|
||||
class Gateway
|
||||
class BogusSimple < Spree::Gateway::Bogus
|
||||
def payment_profiles_supported?
|
||||
false
|
||||
end
|
||||
|
||||
def authorize(_money, credit_card, _options = {})
|
||||
if VALID_CCS.include? credit_card.number
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '12345',
|
||||
avs_result: { code: 'A' })
|
||||
else
|
||||
ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure',
|
||||
{ message: 'Bogus Gateway: Forced failure' },
|
||||
test: true)
|
||||
end
|
||||
end
|
||||
|
||||
def purchase(_money, credit_card, _options = {})
|
||||
if VALID_CCS.include? credit_card.number
|
||||
ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {},
|
||||
test: true, authorization: '12345',
|
||||
avs_result: { code: 'A' })
|
||||
else
|
||||
ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure',
|
||||
message: 'Bogus Gateway: Forced failure',
|
||||
test: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
require 'spree/concerns/payment_method_distributors'
|
||||
|
||||
Spree::Gateway.class_eval do
|
||||
include Spree::PaymentMethodDistributors
|
||||
|
||||
# Default to live
|
||||
preference :server, :string, default: 'live'
|
||||
preference :test_mode, :boolean, default: false
|
||||
end
|
||||
137
app/models/spree/payment_method.rb
Normal file
137
app/models/spree/payment_method.rb
Normal file
@@ -0,0 +1,137 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spree/concerns/payment_method_distributors'
|
||||
|
||||
module Spree
|
||||
class PaymentMethod < ActiveRecord::Base
|
||||
include Spree::Core::CalculatedAdjustments
|
||||
include Spree::PaymentMethodDistributors
|
||||
|
||||
acts_as_taggable
|
||||
acts_as_paranoid
|
||||
|
||||
DISPLAY = [:both, :front_end, :back_end].freeze
|
||||
default_scope -> { where(deleted_at: nil) }
|
||||
|
||||
has_many :credit_cards, class_name: "Spree::CreditCard"
|
||||
|
||||
validates :name, presence: true
|
||||
validate :distributor_validation
|
||||
|
||||
after_initialize :init
|
||||
|
||||
scope :production, -> { where(environment: 'production') }
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
where(nil)
|
||||
else
|
||||
joins(:distributors).
|
||||
where('distributors_payment_methods.distributor_id IN (?)',
|
||||
user.enterprises.select(&:id)).
|
||||
select('DISTINCT spree_payment_methods.*')
|
||||
end
|
||||
}
|
||||
|
||||
scope :for_distributors, ->(distributors) {
|
||||
non_unique_matches = unscoped.joins(:distributors).where(enterprises: { id: distributors })
|
||||
where(id: non_unique_matches.map(&:id))
|
||||
}
|
||||
|
||||
scope :for_distributor, lambda { |distributor|
|
||||
joins(:distributors).
|
||||
where('enterprises.id = ?', distributor)
|
||||
}
|
||||
|
||||
scope :for_subscriptions, -> { where(type: Subscription::ALLOWED_PAYMENT_METHOD_TYPES) }
|
||||
|
||||
scope :by_name, -> { order('spree_payment_methods.name ASC') }
|
||||
|
||||
scope :available, lambda { |display_on = 'both'|
|
||||
where(active: true).
|
||||
where('spree_payment_methods.display_on=? OR spree_payment_methods.display_on=? OR spree_payment_methods.display_on IS NULL', display_on, '').
|
||||
where('spree_payment_methods.environment=? OR spree_payment_methods.environment=? OR spree_payment_methods.environment IS NULL', Rails.env, '')
|
||||
}
|
||||
|
||||
def self.providers
|
||||
Rails.application.config.spree.payment_methods
|
||||
end
|
||||
|
||||
def provider_class
|
||||
raise 'You must implement provider_class method for this gateway.'
|
||||
end
|
||||
|
||||
# The class that will process payments for this payment type, used for @payment.source
|
||||
# e.g. CreditCard in the case of a the Gateway payment type
|
||||
# nil means the payment method doesn't require a source e.g. check
|
||||
def payment_source_class
|
||||
raise 'You must implement payment_source_class method for this gateway.'
|
||||
end
|
||||
|
||||
def self.active?
|
||||
where(type: to_s, environment: Rails.env, active: true).count.positive?
|
||||
end
|
||||
|
||||
def method_type
|
||||
type.demodulize.downcase
|
||||
end
|
||||
|
||||
def self.find_with_destroyed(*args)
|
||||
unscoped { find(*args) }
|
||||
end
|
||||
|
||||
def payment_profiles_supported?
|
||||
false
|
||||
end
|
||||
|
||||
def source_required?
|
||||
true
|
||||
end
|
||||
|
||||
def auto_capture?
|
||||
Spree::Config[:auto_capture]
|
||||
end
|
||||
|
||||
def supports?(_source)
|
||||
true
|
||||
end
|
||||
|
||||
def init
|
||||
unless reflections.key?(:calculator)
|
||||
self.class.include Spree::Core::CalculatedAdjustments
|
||||
end
|
||||
|
||||
self.calculator ||= Calculator::FlatRate.new(preferred_amount: 0)
|
||||
end
|
||||
|
||||
def has_distributor?(distributor)
|
||||
distributors.include?(distributor)
|
||||
end
|
||||
|
||||
def self.clean_name
|
||||
case name
|
||||
when "Spree::PaymentMethod::Check"
|
||||
"Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
when "Spree::Gateway::Migs"
|
||||
"MasterCard Internet Gateway Service (MIGS)"
|
||||
when "Spree::Gateway::Pin"
|
||||
"Pin Payments"
|
||||
when "Spree::Gateway::StripeConnect"
|
||||
"Stripe"
|
||||
when "Spree::Gateway::StripeSCA"
|
||||
"Stripe SCA"
|
||||
when "Spree::Gateway::PayPalExpress"
|
||||
"PayPal Express"
|
||||
else
|
||||
i = name.rindex('::') + 2
|
||||
name[i..-1]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def distributor_validation
|
||||
validates_with DistributorsValidator
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/models/spree/payment_method/check.rb
Normal file
33
app/models/spree/payment_method/check.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class PaymentMethod
|
||||
class Check < Spree::PaymentMethod
|
||||
def actions
|
||||
%w{capture void}
|
||||
end
|
||||
|
||||
# Indicates whether its possible to capture the payment
|
||||
def can_capture?(payment)
|
||||
['checkout', 'pending'].include?(payment.state)
|
||||
end
|
||||
|
||||
# Indicates whether its possible to void the payment.
|
||||
def can_void?(payment)
|
||||
payment.state != 'void'
|
||||
end
|
||||
|
||||
def capture(*_args)
|
||||
ActiveMerchant::Billing::Response.new(true, "", {}, {})
|
||||
end
|
||||
|
||||
def void(*_args)
|
||||
ActiveMerchant::Billing::Response.new(true, "", {}, {})
|
||||
end
|
||||
|
||||
def source_required?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,84 +0,0 @@
|
||||
require 'spree/concerns/payment_method_distributors'
|
||||
|
||||
Spree::PaymentMethod.class_eval do
|
||||
include Spree::Core::CalculatedAdjustments
|
||||
include Spree::PaymentMethodDistributors
|
||||
|
||||
acts_as_taggable
|
||||
|
||||
has_many :credit_cards, class_name: "Spree::CreditCard" # from Spree v.2.3.0 d470b31798f37
|
||||
|
||||
after_initialize :init
|
||||
|
||||
validate :distributor_validation
|
||||
|
||||
# -- Scopes
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
where(nil)
|
||||
else
|
||||
joins(:distributors).
|
||||
where('distributors_payment_methods.distributor_id IN (?)', user.enterprises.select(&:id)).
|
||||
select('DISTINCT spree_payment_methods.*')
|
||||
end
|
||||
}
|
||||
|
||||
scope :for_distributors, ->(distributors) {
|
||||
non_unique_matches = unscoped.joins(:distributors).where(enterprises: { id: distributors })
|
||||
where(id: non_unique_matches.map(&:id))
|
||||
}
|
||||
|
||||
scope :for_distributor, lambda { |distributor|
|
||||
joins(:distributors).
|
||||
where('enterprises.id = ?', distributor)
|
||||
}
|
||||
|
||||
scope :for_subscriptions, -> { where(type: Subscription::ALLOWED_PAYMENT_METHOD_TYPES) }
|
||||
|
||||
scope :by_name, -> { order('spree_payment_methods.name ASC') }
|
||||
|
||||
# Rewrite Spree's ruby-land class method as a scope
|
||||
scope :available, lambda { |display_on = 'both'|
|
||||
where(active: true).
|
||||
where('spree_payment_methods.display_on=? OR spree_payment_methods.display_on=? OR spree_payment_methods.display_on IS NULL', display_on, '').
|
||||
where('spree_payment_methods.environment=? OR spree_payment_methods.environment=? OR spree_payment_methods.environment IS NULL', Rails.env, '')
|
||||
}
|
||||
|
||||
def init
|
||||
unless reflections.key?(:calculator)
|
||||
self.class.include Spree::Core::CalculatedAdjustments
|
||||
end
|
||||
|
||||
self.calculator ||= Calculator::FlatRate.new(preferred_amount: 0)
|
||||
end
|
||||
|
||||
def has_distributor?(distributor)
|
||||
distributors.include?(distributor)
|
||||
end
|
||||
|
||||
def self.clean_name
|
||||
case name
|
||||
when "Spree::PaymentMethod::Check"
|
||||
"Cash/EFT/etc. (payments for which automatic validation is not required)"
|
||||
when "Spree::Gateway::Migs"
|
||||
"MasterCard Internet Gateway Service (MIGS)"
|
||||
when "Spree::Gateway::Pin"
|
||||
"Pin Payments"
|
||||
when "Spree::Gateway::StripeConnect"
|
||||
"Stripe"
|
||||
when "Spree::Gateway::StripeSCA"
|
||||
"Stripe SCA"
|
||||
when "Spree::Gateway::PayPalExpress"
|
||||
"PayPal Express"
|
||||
else
|
||||
i = name.rindex('::') + 2
|
||||
name[i..-1]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def distributor_validation
|
||||
validates_with DistributorsValidator
|
||||
end
|
||||
end
|
||||
@@ -18,9 +18,6 @@ Spree::Gateway.class_eval do
|
||||
acts_as_taggable
|
||||
end
|
||||
|
||||
require "#{Rails.root}/app/models/spree/payment_method_decorator"
|
||||
require "#{Rails.root}/app/models/spree/gateway_decorator"
|
||||
|
||||
Spree.config do |config|
|
||||
config.shipping_instructions = true
|
||||
config.address_requires_state = true
|
||||
|
||||
@@ -81,7 +81,8 @@ feature '
|
||||
end
|
||||
|
||||
scenario "updating a payment method", js: true do
|
||||
payment_method = create(:payment_method, distributors: [@distributors[0]])
|
||||
payment_method = create(:payment_method, distributors: [@distributors[0]],
|
||||
calculator: build(:calculator_flat_rate))
|
||||
login_as_admin_and_visit spree.edit_admin_payment_method_path payment_method
|
||||
|
||||
fill_in 'payment_method_name', with: 'New PM Name'
|
||||
|
||||
@@ -2,6 +2,220 @@ require 'spec_helper'
|
||||
|
||||
module Spree
|
||||
describe CreditCard do
|
||||
describe "original specs from Spree" do
|
||||
let(:valid_credit_card_attributes) {
|
||||
{
|
||||
number: '4111111111111111',
|
||||
verification_value: '123',
|
||||
month: 12,
|
||||
year: Time.zone.now.year + 1
|
||||
}
|
||||
}
|
||||
|
||||
def self.payment_states
|
||||
Spree::Payment.state_machine.states.keys
|
||||
end
|
||||
|
||||
def stub_rails_env(environment)
|
||||
allow(Rails).to receive_messages(env: ActiveSupport::StringInquirer.new(environment))
|
||||
end
|
||||
|
||||
let(:credit_card) { Spree::CreditCard.new }
|
||||
|
||||
before(:each) do
|
||||
@order = create(:order)
|
||||
@payment = create(:payment, amount: 100, order: @order)
|
||||
|
||||
@success_response = double('gateway_response', success?: true,
|
||||
authorization: '123',
|
||||
avs_result: { 'code' => 'avs-code' })
|
||||
@fail_response = double('gateway_response', success?: false)
|
||||
|
||||
@payment_gateway = create(:payment_method,
|
||||
environment: 'test')
|
||||
allow(@payment_gateway).to receive_messages :payment_profiles_supported? => true,
|
||||
:authorize => @success_response,
|
||||
:purchase => @success_response,
|
||||
:capture => @success_response,
|
||||
:void => @success_response,
|
||||
:credit => @success_response
|
||||
allow(@payment).to receive_messages payment_method: @payment_gateway
|
||||
end
|
||||
|
||||
context "#can_capture?" do
|
||||
it "should be true if payment is pending" do
|
||||
payment = create(:payment, created_at: Time.zone.now)
|
||||
allow(payment).to receive(:pending?) { true }
|
||||
expect(credit_card.can_capture?(payment)).to be_truthy
|
||||
end
|
||||
|
||||
it "should be true if payment is checkout" do
|
||||
payment = create(:payment, created_at: Time.zone.now)
|
||||
allow(payment).to receive_messages :pending? => false,
|
||||
:checkout? => true
|
||||
expect(credit_card.can_capture?(payment)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "#can_void?" do
|
||||
it "should be true if payment is not void" do
|
||||
payment = create(:payment)
|
||||
allow(payment).to receive(:void?) { false }
|
||||
expect(credit_card.can_void?(payment)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "#can_credit?" do
|
||||
it "should be false if payment is not completed" do
|
||||
payment = create(:payment)
|
||||
allow(payment).to receive(:completed?) { false }
|
||||
expect(credit_card.can_credit?(payment)).to be_falsy
|
||||
end
|
||||
|
||||
it "should be false when order payment_state is not 'credit_owed'" do
|
||||
payment = create(:payment,
|
||||
order: create(:order, payment_state: 'paid'))
|
||||
allow(payment).to receive(:completed?) { true }
|
||||
expect(credit_card.can_credit?(payment)).to be_falsy
|
||||
end
|
||||
|
||||
it "should be false when credit_allowed is zero" do
|
||||
payment = create(:payment,
|
||||
order: create(:order, payment_state: 'credit_owed'))
|
||||
allow(payment).to receive_messages :completed? => true,
|
||||
:credit_allowed => 0
|
||||
|
||||
expect(credit_card.can_credit?(payment)).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context "#valid?" do
|
||||
it "should validate presence of number" do
|
||||
credit_card.attributes = valid_credit_card_attributes.except(:number)
|
||||
expect(credit_card).to_not be_valid
|
||||
expect(credit_card.errors[:number]).to eq ["can't be blank"]
|
||||
end
|
||||
|
||||
it "should validate presence of security code" do
|
||||
credit_card.attributes = valid_credit_card_attributes.except(:verification_value)
|
||||
expect(credit_card).to_not be_valid
|
||||
expect(credit_card.errors[:verification_value]).to eq ["can't be blank"]
|
||||
end
|
||||
|
||||
it "should validate expiration is not in the past" do
|
||||
credit_card.month = 1.month.ago.month
|
||||
credit_card.year = 1.month.ago.year
|
||||
expect(credit_card).to_not be_valid
|
||||
expect(credit_card.errors[:base]).to eq ["has expired"]
|
||||
end
|
||||
|
||||
it "does not run expiration in the past validation if month is not set" do
|
||||
credit_card.month = nil
|
||||
credit_card.year = Time.zone.now.year
|
||||
expect(credit_card).to_not be_valid
|
||||
expect(credit_card.errors[:base]).to be_blank
|
||||
end
|
||||
|
||||
it "does not run expiration in the past validation if year is not set" do
|
||||
credit_card.month = Time.zone.now.month
|
||||
credit_card.year = nil
|
||||
expect(credit_card).to_not be_valid
|
||||
expect(credit_card.errors[:base]).to be_blank
|
||||
end
|
||||
|
||||
it "does not run expiration in the past validation if year and month are empty" do
|
||||
credit_card.year = ""
|
||||
credit_card.month = ""
|
||||
expect(credit_card).to_not be_valid
|
||||
expect(credit_card.errors[:card]).to be_blank
|
||||
end
|
||||
|
||||
it "should only validate on create" do
|
||||
credit_card.attributes = valid_credit_card_attributes
|
||||
credit_card.save
|
||||
expect(credit_card).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "#save" do
|
||||
before do
|
||||
credit_card.attributes = valid_credit_card_attributes
|
||||
credit_card.save!
|
||||
end
|
||||
|
||||
let!(:persisted_card) { Spree::CreditCard.find(credit_card.id) }
|
||||
|
||||
it "should not actually store the number" do
|
||||
expect(persisted_card.number).to be_blank
|
||||
end
|
||||
|
||||
it "should not actually store the security code" do
|
||||
expect(persisted_card.verification_value).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context "#number=" do
|
||||
it "should strip non-numeric characters from card input" do
|
||||
credit_card.number = "6011000990139424"
|
||||
expect(credit_card.number).to eq "6011000990139424"
|
||||
|
||||
credit_card.number = " 6011-0009-9013-9424 "
|
||||
expect(credit_card.number).to eq "6011000990139424"
|
||||
end
|
||||
|
||||
it "should not raise an exception on non-string input" do
|
||||
credit_card.number = ({})
|
||||
expect(credit_card.number).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "#cc_type=" do
|
||||
it "converts between the different types" do
|
||||
credit_card.cc_type = 'mastercard'
|
||||
expect(credit_card.cc_type).to eq 'master'
|
||||
|
||||
credit_card.cc_type = 'maestro'
|
||||
expect(credit_card.cc_type).to eq 'master'
|
||||
|
||||
credit_card.cc_type = 'amex'
|
||||
expect(credit_card.cc_type).to eq 'american_express'
|
||||
|
||||
credit_card.cc_type = 'dinersclub'
|
||||
expect(credit_card.cc_type).to eq 'diners_club'
|
||||
|
||||
credit_card.cc_type = 'some_outlandish_cc_type'
|
||||
expect(credit_card.cc_type).to eq 'some_outlandish_cc_type'
|
||||
end
|
||||
end
|
||||
|
||||
context "#associations" do
|
||||
it "should be able to access its payments" do
|
||||
expect { credit_card.payments.to_a }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "#to_active_merchant" do
|
||||
before do
|
||||
credit_card.number = "4111111111111111"
|
||||
credit_card.year = Time.zone.now.year
|
||||
credit_card.month = Time.zone.now.month
|
||||
credit_card.first_name = "Bob"
|
||||
credit_card.last_name = "Boblaw"
|
||||
credit_card.verification_value = 123
|
||||
end
|
||||
|
||||
it "converts to an ActiveMerchant::Billing::CreditCard object" do
|
||||
am_card = credit_card.to_active_merchant
|
||||
expect(am_card.number).to eq "4111111111111111"
|
||||
expect(am_card.year).to eq Time.zone.now.year
|
||||
expect(am_card.month).to eq Time.zone.now.month
|
||||
expect(am_card.first_name).to eq "Bob"
|
||||
am_card.last_name = "Boblaw"
|
||||
expect(am_card.verification_value).to eq 123
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "setting default credit card for a user" do
|
||||
let(:user) { create(:user) }
|
||||
let(:onetime_card_attrs) do
|
||||
|
||||
23
spec/models/spree/gateway_spec.rb
Normal file
23
spec/models/spree/gateway_spec.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Spree::Gateway do
|
||||
class Provider
|
||||
def initialize(options); end
|
||||
|
||||
def imaginary_method; end
|
||||
end
|
||||
|
||||
class TestGateway < Spree::Gateway
|
||||
def provider_class
|
||||
Provider
|
||||
end
|
||||
end
|
||||
|
||||
it "passes through all arguments on a method_missing call" do
|
||||
gateway = TestGateway.new
|
||||
expect(gateway.provider).to receive(:imaginary_method).with('foo')
|
||||
gateway.imaginary_method('foo')
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,46 @@
|
||||
require 'spec_helper'
|
||||
|
||||
class Spree::Gateway::Test < Spree::Gateway
|
||||
end
|
||||
|
||||
module Spree
|
||||
describe PaymentMethod do
|
||||
describe "#available" do
|
||||
let(:enterprise) { create(:enterprise) }
|
||||
|
||||
before do
|
||||
Spree::PaymentMethod.delete_all
|
||||
|
||||
[nil, 'both', 'front_end', 'back_end'].each do |display_on|
|
||||
Spree::Gateway::Test.create(
|
||||
name: 'Display Both',
|
||||
display_on: display_on,
|
||||
active: true,
|
||||
environment: 'test',
|
||||
description: 'foofah',
|
||||
distributors: [enterprise]
|
||||
)
|
||||
end
|
||||
expect(Spree::PaymentMethod.all.size).to eq 4
|
||||
end
|
||||
|
||||
it "should return all methods available to front-end/back-end when no parameter is passed" do
|
||||
expect(Spree::PaymentMethod.available.size).to eq 2
|
||||
end
|
||||
|
||||
it "should return all methods available to front-end/back-end when display_on = :both" do
|
||||
expect(Spree::PaymentMethod.available(:both).size).to eq 2
|
||||
end
|
||||
|
||||
it "should return all methods available to front-end when display_on = :front_end" do
|
||||
expect(Spree::PaymentMethod.available(:front_end).size).to eq 2
|
||||
end
|
||||
|
||||
it "should return all methods available to back-end when display_on = :back_end" do
|
||||
expect(Spree::PaymentMethod.available(:back_end).size).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
it "orders payment methods by name" do
|
||||
pm1 = create(:payment_method, name: 'ZZ')
|
||||
pm2 = create(:payment_method, name: 'AA')
|
||||
|
||||
Reference in New Issue
Block a user