module Spree class Payment < ActiveRecord::Base module Processing def process! if payment_method && payment_method.source_required? if source if !processing? if Spree::Config[:auto_capture] purchase! else authorize! end end else raise Core::GatewayError.new(I18n.t(:payment_processing_failed)) end end end def authorize! started_processing! gateway_action(source, :authorize, :pend) end def purchase! started_processing! gateway_action(source, :purchase, :complete) end def capture! protect_from_connection_error do check_environment if payment_method.payment_profiles_supported? # Gateways supporting payment profiles will need access to creditcard object because this stores the payment profile information # so supply the authorization itself as well as the creditcard, rather than just the authorization code response = payment_method.capture(self, source, gateway_options) else # Standard ActiveMerchant capture usage response = payment_method.capture((amount * 100).round, response_code, gateway_options) end handle_response(response, :complete, :failure) end end def void_transaction! protect_from_connection_error do check_environment response = payment_method.void(self.response_code, gateway_options) record_response(response) if response.success? self.response_code = response.authorization self.void else gateway_error(response) end end end end def credit!(credit_amount=nil) protect_from_connection_error do check_environment credit_amount ||= credit_allowed >= order.outstanding_balance.abs ? order.outstanding_balance.abs : credit_allowed.abs credit_amount = credit_amount.to_f if payment_method.payment_profiles_supported? response = payment_method.credit((credit_amount * 100).round, source, response_code, gateway_options) else response = payment_method.credit((credit_amount * 100).round, response_code, gateway_options) end record_response(response) if response.success? self.class.create({ :order => order, :source => self, :payment_method => payment_method, :amount => credit_amount.abs * -1, :response_code => response.authorization, :state => 'completed' }, :without_protection => true) else gateway_error(response) end end end def partial_credit(amount) return if amount > credit_allowed started_processing! credit!(amount) end private def gateway_action(source, action, success_state) protect_from_connection_error do check_environment response = payment_method.send(action, (amount * 100).round, source, gateway_options) handle_response(response, success_state, :failure) end end def handle_response(response, success_state, failure_state) record_response(response) if response.success? self.response_code = response.authorization self.avs_response = response.avs_result['code'] self.send("#{success_state}!") else self.send("#{failure_state}!") gateway_error(response) end end def record_response(response) log_entries.create({:details => response.to_yaml}, :without_protection => true) end def gateway_options options = { :email => order.email, :customer => order.email, :ip => order.ip_address, :order_id => order.number } options.merge!({ :shipping => order.ship_total * 100, :tax => order.tax_total * 100, :subtotal => order.item_total * 100 }) options.merge({ :billing_address => order.bill_address.try(:active_merchant_hash), :shipping_address => order.ship_address.try(:active_merchant_hash) }) end def protect_from_connection_error begin yield rescue ActiveMerchant::ConnectionError => e gateway_error(e) end end def record_log(response) log_entries.create({:details => response.to_yaml}, :without_protection => true) end def gateway_error(error) if error.is_a? ActiveMerchant::Billing::Response text = error.params['message'] || error.params['response_reason_text'] || error.message elsif error.is_a? ActiveMerchant::ConnectionError text = I18n.t(:unable_to_connect_to_gateway) else text = error.to_s end logger.error(I18n.t(:gateway_error)) logger.error(" #{error.to_yaml}") raise Core::GatewayError.new(text) end # Saftey check to make sure we're not accidentally performing operations on a live gateway. # Ex. When testing in staging environment with a copy of production data. def check_environment return if payment_method.environment == Rails.env message = I18n.t(:gateway_config_unavailable) + " - #{Rails.env}" raise Core::GatewayError.new(message) end end end