lib/killbill/helpers/active_merchant/gateway.rb in killbill-4.0.0 vs lib/killbill/helpers/active_merchant/gateway.rb in killbill-4.1.0
- old
+ new
@@ -2,19 +2,20 @@
module Plugin
module ActiveMerchant
require 'active_merchant'
class Gateway
- def self.wrap(gateway_builder, config)
- Gateway.new(config, gateway_builder.call(config))
+ def self.wrap(gateway_builder, config, logger)
+ Gateway.new(config, gateway_builder.call(config), logger)
end
attr_reader :config
- def initialize(config, am_gateway)
+ def initialize(config, am_gateway, logger)
@config = config
@gateway = am_gateway
+ @logger = logger
# Override urls if needed (there is no easy way to do it earlier, because AM uses class_attribute)
@gateway.class.test_url = @config[:test_url] unless @config[:test_url].nil?
@gateway.class.live_url = @config[:live_url] unless @config[:live_url].nil?
end
@@ -55,21 +56,91 @@
def refund(*args, &block)
method_missing(:refund, *args, &block)
end
def method_missing(m, *args, &block)
+ options = {}
+
# The options hash should be the last argument, iterate through all to be safe
args.reverse.each do |arg|
- if arg.respond_to?(:has_key?) && Utils.normalized(arg, :skip_gw)
- return ::ActiveMerchant::Billing::Response.new(true, 'Skipped Gateway call')
+ if arg.respond_to?(:has_key?)
+ options = arg
+ return ::ActiveMerchant::Billing::Response.new(true, 'Skipped Gateway call') if Utils.normalized(arg, :skip_gw)
end
end
@gateway.send(m, *args, &block)
+ rescue ::ActiveMerchant::ConnectionError => e
+ # Need to unwrap it
+ until e.triggering_exception.nil?
+ e = e.triggering_exception
+ break unless e.is_a?(::ActiveMerchant::ConnectionError)
+ end
+ handle_exception(e, options)
+ rescue => e
+ handle_exception(e, options)
end
def respond_to?(method, include_private=false)
@gateway.respond_to?(method, include_private) || super
+ end
+
+ private
+
+ UNKNOWN_CONNECTION_ERRORS = [
+ # Corrupted stream (e.g. Zlib::BufError)
+ ::ActiveMerchant::InvalidResponseError,
+ # We attempted a payment, but the gateway replied >= 300. This is gateway specific, hopefully the individual
+ # gateway implementation knows how to rescue from it and this is not risen
+ ::ActiveMerchant::ResponseError,
+ # Should not be risen directly
+ ::ActiveMerchant::RetriableConnectionError,
+ ::ActiveMerchant::ActiveMerchantError
+ ]
+
+ PROBABLY_UNKNOWN_CONNECTION_ERRORS = [
+ EOFError,
+ Errno::ECONNRESET,
+ Timeout::Error,
+ Errno::ETIMEDOUT
+ ]
+
+ SAFE_CONNECTION_ERRORS = [
+ SocketError,
+ Errno::EHOSTUNREACH,
+ Errno::ECONNREFUSED,
+ ::OpenSSL::SSL::SSLError,
+ # Invalid certificate (e.g. OpenSSL::X509::CertificateError)
+ ::ActiveMerchant::ClientCertificateError
+ ]
+
+ # See https://github.com/killbill/killbill-plugin-framework-ruby/issues/44
+ def handle_exception(e, options = {})
+ message = "#{e.class} #{e.message}"
+
+ if SAFE_CONNECTION_ERRORS.include?(e.class)
+ # Easy case: we didn't attempt the payment
+ @logger.warn("Connection error with the gateway: #{message}")
+ payment_plugin_status = :CANCELED
+ else
+ # For anything else, tell Kill Bill we don't know. If the gateway supports retrieving a payment status,
+ # the plugin should implement get_payment_info accordingly for the Janitor.
+ # Otherwise, the transaction will need to be fixed manually using the admin APIs.
+
+ # Note that PROBABLY_UNKNOWN_CONNECTION_ERRORS/UNKNOWN_CONNECTION_ERRORS are a bit _better_, as they can be expected and we don't have any control over them.
+ # Any other exception might be caused by a bug in our code!
+ if PROBABLY_UNKNOWN_CONNECTION_ERRORS.include?(e.class) || UNKNOWN_CONNECTION_ERRORS.include?(e.class)
+ @logger.warn("Unstable connection with the gateway: #{message}")
+ else
+ @logger.warn("Unexpected exception: #{message}")
+ end
+
+ # Allow clients to force a PLUGIN_FAILURE instead of UNKNOWN (the default is a conservative behavior)
+ payment_plugin_status = Utils.normalized(options, :connection_errors_safe) ? :CANCELED : :UNDEFINED
+ end
+
+ response_message = { :exception_class => e.class.to_s, :exception_message => e.message, :payment_plugin_status => payment_plugin_status }.to_json
+ ::ActiveMerchant::Billing::Response.new(false, response_message)
end
end
end
end
end