activemerchant 1.45.0 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -1
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG +25 -0
  5. data/CONTRIBUTORS +9 -0
  6. data/README.md +6 -20
  7. data/lib/active_merchant.rb +9 -50
  8. data/lib/active_merchant/billing.rb +1 -0
  9. data/lib/active_merchant/billing/gateway.rb +10 -1
  10. data/lib/active_merchant/billing/gateways.rb +6 -11
  11. data/lib/active_merchant/billing/gateways/authorize_net.rb +58 -47
  12. data/lib/active_merchant/billing/gateways/beanstream.rb +1 -1
  13. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +8 -8
  14. data/lib/active_merchant/billing/gateways/braintree.rb +2 -2
  15. data/lib/active_merchant/billing/gateways/braintree_blue.rb +69 -22
  16. data/lib/active_merchant/billing/gateways/braintree_orange.rb +2 -2
  17. data/lib/active_merchant/billing/gateways/checkout.rb +7 -2
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +25 -9
  19. data/lib/active_merchant/billing/gateways/elavon.rb +1 -1
  20. data/lib/active_merchant/billing/gateways/eway_rapid.rb +6 -3
  21. data/lib/active_merchant/billing/gateways/finansbank.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/hps.rb +0 -1
  23. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +1 -1
  24. data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +0 -0
  25. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +1 -1
  26. data/lib/active_merchant/billing/gateways/instapay.rb +0 -0
  27. data/lib/active_merchant/billing/gateways/ipp.rb +175 -0
  28. data/lib/active_merchant/billing/gateways/mercury.rb +4 -11
  29. data/lib/active_merchant/billing/gateways/migs.rb +1 -1
  30. data/lib/active_merchant/billing/gateways/modern_payments.rb +1 -1
  31. data/lib/active_merchant/billing/gateways/orbital.rb +2 -2
  32. data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +26 -0
  33. data/lib/active_merchant/billing/gateways/payflow.rb +3 -3
  34. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +16 -5
  35. data/lib/active_merchant/billing/gateways/payflow_express.rb +3 -3
  36. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +2 -2
  37. data/lib/active_merchant/billing/gateways/payflow_uk.rb +4 -4
  38. data/lib/active_merchant/billing/gateways/paypal.rb +15 -3
  39. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +10 -1
  40. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/paypal_ca.rb +1 -1
  42. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +3 -3
  43. data/lib/active_merchant/billing/gateways/paypal_express.rb +4 -4
  44. data/lib/active_merchant/billing/gateways/pin.rb +10 -1
  45. data/lib/active_merchant/billing/gateways/quickbooks.rb +278 -0
  46. data/lib/active_merchant/billing/gateways/redsys.rb +2 -2
  47. data/lib/active_merchant/billing/gateways/sage.rb +3 -3
  48. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +1 -1
  49. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +1 -1
  50. data/lib/active_merchant/billing/gateways/secure_pay.rb +1 -1
  51. data/lib/active_merchant/billing/gateways/skip_jack.rb +0 -1
  52. data/lib/active_merchant/billing/gateways/stripe.rb +13 -2
  53. data/lib/active_merchant/billing/gateways/wirecard.rb +0 -1
  54. data/lib/active_merchant/billing/payment_token.rb +1 -1
  55. data/lib/active_merchant/connection.rb +169 -0
  56. data/lib/active_merchant/network_connection_retries.rb +78 -0
  57. data/lib/active_merchant/post_data.rb +24 -0
  58. data/lib/active_merchant/posts_data.rb +78 -0
  59. data/lib/active_merchant/version.rb +1 -1
  60. data/lib/certs/cacert.pem +3866 -0
  61. metadata +22 -44
  62. metadata.gz.sig +0 -0
  63. data/lib/active_merchant/offsite_payments_shim.rb +0 -19
@@ -100,7 +100,7 @@ module ActiveMerchant #:nodoc:
100
100
  191 => "Expiry date incorrect",
101
101
 
102
102
  201 => "Card expired",
103
- 202 => "Card blocked temporarily or under suscipition of fraud",
103
+ 202 => "Card blocked temporarily or under suspicion of fraud",
104
104
  204 => "Transaction not permitted",
105
105
  207 => "Contact the card issuer",
106
106
  208 => "Lost or stolen card",
@@ -381,7 +381,7 @@ module ActiveMerchant #:nodoc:
381
381
  def clean_order_id(order_id)
382
382
  cleansed = order_id.gsub(/[^\da-zA-Z]/, '')
383
383
  if cleansed =~ /^\d{4}/
384
- cleansed[0..12]
384
+ cleansed[0..11]
385
385
  else
386
386
  "%04d%s" % [rand(0..9999), cleansed[0...8]]
387
387
  end
@@ -1,6 +1,6 @@
1
- require File.dirname(__FILE__) + '/sage/sage_bankcard'
2
- require File.dirname(__FILE__) + '/sage/sage_virtual_check'
3
- require File.dirname(__FILE__) + '/sage/sage_vault'
1
+ require 'active_merchant/billing/gateways/sage/sage_bankcard'
2
+ require 'active_merchant/billing/gateways/sage/sage_virtual_check'
3
+ require 'active_merchant/billing/gateways/sage/sage_vault'
4
4
 
5
5
  module ActiveMerchant #:nodoc:
6
6
  module Billing #:nodoc:
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/sage_core'
1
+ require 'active_merchant/billing/gateways/sage/sage_core'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/sage_core'
1
+ require 'active_merchant/billing/gateways/sage/sage_core'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/authorize_net'
1
+ require 'active_merchant/billing/gateways/authorize_net'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
@@ -46,7 +46,6 @@ module ActiveMerchant #:nodoc:
46
46
  "N" => "Neither street address nor zip/postal match billing information",
47
47
  "O" => "Non-US issuer does not participate",
48
48
  "P" => "Postal codes match for international transaction but street address not verified due to incompatible formats",
49
- "P" => "Address verification not applicable for this transaction",
50
49
  "R" => "Payment gateway was unavailable or timed out",
51
50
  "S" => "Address verification service not supported by issuer",
52
51
  "U" => "Address information is unavailable",
@@ -60,7 +60,7 @@ module ActiveMerchant #:nodoc:
60
60
  r.process { tokenize_apple_pay_token(payment) }
61
61
  payment = StripePaymentToken.new(r.params["token"]) if r.success?
62
62
  end
63
- r.process do |r|
63
+ r.process do
64
64
  post = create_post_for_auth_or_purchase(money, payment, options)
65
65
  post[:capture] = "false"
66
66
  commit(:post, 'charges', post, options)
@@ -81,7 +81,7 @@ module ActiveMerchant #:nodoc:
81
81
  r.process { tokenize_apple_pay_token(payment) }
82
82
  payment = StripePaymentToken.new(r.params["token"]) if r.success?
83
83
  end
84
- r.process do |r|
84
+ r.process do
85
85
  post = create_post_for_auth_or_purchase(money, payment, options)
86
86
  commit(:post, 'charges', post, options)
87
87
  end
@@ -204,6 +204,17 @@ module ActiveMerchant #:nodoc:
204
204
  end
205
205
  end
206
206
 
207
+ def supports_scrubbing?
208
+ true
209
+ end
210
+
211
+ def scrub(transcript)
212
+ transcript.
213
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
214
+ gsub(%r((card\[number\]=)\d+), '\1[FILTERED]').
215
+ gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]')
216
+ end
217
+
207
218
  private
208
219
 
209
220
  class StripePaymentToken < PaymentToken
@@ -395,7 +395,6 @@ module ActiveMerchant #:nodoc:
395
395
  "U" => "U", # Data Not Checked
396
396
  "Y" => "D", # All Data Matched
397
397
  "Z" => "P", # CSC and Postcode Matched
398
- "F" => "D" # Street address and zip code match
399
398
  }
400
399
 
401
400
  # Amex have different AVS response codes to visa etc
@@ -6,7 +6,7 @@ module ActiveMerchant #:nodoc:
6
6
  # implementations of your given cryptographer. Like credit cards, you must also return a string representing
7
7
  # the token's type, like 'apple_pay' or 'stripe' should your target payment gateway process these tokens.
8
8
  class PaymentToken
9
- attr_reader :payment_data, :type
9
+ attr_reader :payment_data
10
10
 
11
11
  def initialize(payment_data, options = {})
12
12
  @payment_data = payment_data
@@ -0,0 +1,169 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'benchmark'
5
+
6
+ module ActiveMerchant
7
+ class Connection
8
+ include NetworkConnectionRetries
9
+
10
+ MAX_RETRIES = 3
11
+ OPEN_TIMEOUT = 60
12
+ READ_TIMEOUT = 60
13
+ VERIFY_PEER = true
14
+ CA_FILE = File.expand_path('../certs/cacert.pem', File.dirname(__FILE__))
15
+ CA_PATH = nil
16
+ RETRY_SAFE = false
17
+ RUBY_184_POST_HEADERS = { "Content-Type" => "application/x-www-form-urlencoded" }
18
+
19
+ attr_accessor :endpoint
20
+ attr_accessor :open_timeout
21
+ attr_accessor :read_timeout
22
+ attr_accessor :verify_peer
23
+ attr_accessor :ssl_version
24
+ attr_accessor :ca_file
25
+ attr_accessor :ca_path
26
+ attr_accessor :pem
27
+ attr_accessor :pem_password
28
+ attr_accessor :wiredump_device
29
+ attr_accessor :logger
30
+ attr_accessor :tag
31
+ attr_accessor :ignore_http_status
32
+ attr_accessor :max_retries
33
+ attr_accessor :proxy_address
34
+ attr_accessor :proxy_port
35
+
36
+ def initialize(endpoint)
37
+ @endpoint = endpoint.is_a?(URI) ? endpoint : URI.parse(endpoint)
38
+ @open_timeout = OPEN_TIMEOUT
39
+ @read_timeout = READ_TIMEOUT
40
+ @retry_safe = RETRY_SAFE
41
+ @verify_peer = VERIFY_PEER
42
+ @ca_file = CA_FILE
43
+ @ca_path = CA_PATH
44
+ @max_retries = MAX_RETRIES
45
+ @ignore_http_status = false
46
+ @ssl_version = nil
47
+ @proxy_address = nil
48
+ @proxy_port = nil
49
+ end
50
+
51
+ def request(method, body, headers = {})
52
+ request_start = Time.now.to_f
53
+
54
+ retry_exceptions(:max_retries => max_retries, :logger => logger, :tag => tag) do
55
+ begin
56
+ info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint}", tag
57
+
58
+ result = nil
59
+
60
+ realtime = Benchmark.realtime do
61
+ result = case method
62
+ when :get
63
+ raise ArgumentError, "GET requests do not support a request body" if body
64
+ http.get(endpoint.request_uri, headers)
65
+ when :post
66
+ debug body
67
+ http.post(endpoint.request_uri, body, RUBY_184_POST_HEADERS.merge(headers))
68
+ when :put
69
+ debug body
70
+ http.put(endpoint.request_uri, body, headers)
71
+ when :delete
72
+ # It's kind of ambiguous whether the RFC allows bodies
73
+ # for DELETE requests. But Net::HTTP's delete method
74
+ # very unambiguously does not.
75
+ raise ArgumentError, "DELETE requests do not support a request body" if body
76
+ http.delete(endpoint.request_uri, headers)
77
+ else
78
+ raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
79
+ end
80
+ end
81
+
82
+ info "--> %d %s (%d %.4fs)" % [result.code, result.message, result.body ? result.body.length : 0, realtime], tag
83
+ debug result.body
84
+ result
85
+ end
86
+ end
87
+
88
+ ensure
89
+ info "connection_request_total_time=%.4fs" % [Time.now.to_f - request_start], tag
90
+ end
91
+
92
+ private
93
+ def http
94
+ http = Net::HTTP.new(endpoint.host, endpoint.port, proxy_address, proxy_port)
95
+ configure_debugging(http)
96
+ configure_timeouts(http)
97
+ configure_ssl(http)
98
+ configure_cert(http)
99
+ http
100
+ end
101
+
102
+ def configure_debugging(http)
103
+ http.set_debug_output(wiredump_device)
104
+ end
105
+
106
+ def configure_timeouts(http)
107
+ http.open_timeout = open_timeout
108
+ http.read_timeout = read_timeout
109
+ end
110
+
111
+ def configure_ssl(http)
112
+ return unless endpoint.scheme == "https"
113
+
114
+ http.use_ssl = true
115
+ http.ssl_version = ssl_version if ssl_version
116
+
117
+ if verify_peer
118
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
119
+ http.ca_file = ca_file
120
+ http.ca_path = ca_path
121
+ else
122
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
123
+ end
124
+
125
+ end
126
+
127
+ def configure_cert(http)
128
+ return if pem.blank?
129
+
130
+ http.cert = OpenSSL::X509::Certificate.new(pem)
131
+
132
+ if pem_password
133
+ http.key = OpenSSL::PKey::RSA.new(pem, pem_password)
134
+ else
135
+ http.key = OpenSSL::PKey::RSA.new(pem)
136
+ end
137
+ end
138
+
139
+ def handle_response(response)
140
+ if @ignore_http_status then
141
+ return response.body
142
+ else
143
+ case response.code.to_i
144
+ when 200...300
145
+ response.body
146
+ else
147
+ raise ResponseError.new(response)
148
+ end
149
+ end
150
+ end
151
+
152
+ def debug(message, tag = nil)
153
+ log(:debug, message, tag)
154
+ end
155
+
156
+ def info(message, tag = nil)
157
+ log(:info, message, tag)
158
+ end
159
+
160
+ def error(message, tag = nil)
161
+ log(:error, message, tag)
162
+ end
163
+
164
+ def log(level, message, tag)
165
+ message = "[#{tag}] #{message}" if tag
166
+ logger.send(level, message) if logger
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,78 @@
1
+ module ActiveMerchant
2
+ module NetworkConnectionRetries
3
+ DEFAULT_RETRIES = 3
4
+ DEFAULT_CONNECTION_ERRORS = {
5
+ EOFError => "The remote server dropped the connection",
6
+ Errno::ECONNRESET => "The remote server reset the connection",
7
+ Timeout::Error => "The connection to the remote server timed out",
8
+ Errno::ETIMEDOUT => "The connection to the remote server timed out",
9
+ SocketError => "The connection to the remote server could not be established",
10
+ OpenSSL::SSL::SSLError => "The SSL connection to the remote server could not be established"
11
+ }
12
+
13
+ def self.included(base)
14
+ base.send(:attr_accessor, :retry_safe)
15
+ end
16
+
17
+ def retry_exceptions(options={})
18
+ connection_errors = DEFAULT_CONNECTION_ERRORS.merge(options[:connection_exceptions] || {})
19
+
20
+ retry_network_exceptions(options) do
21
+ begin
22
+ yield
23
+ rescue Errno::ECONNREFUSED => e
24
+ raise ActiveMerchant::RetriableConnectionError, "The remote server refused the connection"
25
+ rescue OpenSSL::X509::CertificateError => e
26
+ NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag])
27
+ raise ActiveMerchant::ClientCertificateError, "The remote server did not accept the provided SSL certificate"
28
+ rescue Zlib::BufError => e
29
+ raise ActiveMerchant::InvalidResponseError, "The remote server replied with an invalid response"
30
+ rescue *connection_errors.keys => e
31
+ raise ActiveMerchant::ConnectionError, derived_error_message(connection_errors, e.class)
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def retry_network_exceptions(options = {})
39
+ initial_retries = options[:max_retries] || DEFAULT_RETRIES
40
+ retries = initial_retries
41
+ request_start = nil
42
+
43
+ begin
44
+ request_start = Time.now.to_f
45
+ result = yield
46
+ log_with_retry_details(options[:logger], initial_retries-retries + 1, Time.now.to_f - request_start, "success", options[:tag])
47
+ result
48
+ rescue ActiveMerchant::RetriableConnectionError => e
49
+ retries -= 1
50
+
51
+ log_with_retry_details(options[:logger], initial_retries-retries, Time.now.to_f - request_start, e.message, options[:tag])
52
+ retry unless retries.zero?
53
+ raise ActiveMerchant::ConnectionError, e.message
54
+ rescue ActiveMerchant::ConnectionError, ActiveMerchant::InvalidResponseError => e
55
+ retries -= 1
56
+ log_with_retry_details(options[:logger], initial_retries-retries, Time.now.to_f - request_start, e.message, options[:tag])
57
+ retry if (options[:retry_safe] || retry_safe) && !retries.zero?
58
+ raise
59
+ end
60
+ end
61
+
62
+ def self.log(logger, level, message, tag=nil)
63
+ tag ||= self.class.to_s
64
+ message = "[#{tag}] #{message}"
65
+ logger.send(level, message) if logger
66
+ end
67
+
68
+ private
69
+ def log_with_retry_details(logger, attempts, time, message, tag)
70
+ NetworkConnectionRetries.log(logger, :info, "connection_attempt=%d connection_request_time=%.4fs connection_msg=\"%s\"" % [attempts, time, message], tag)
71
+ end
72
+
73
+ def derived_error_message(errors, klass)
74
+ key = (errors.keys & klass.ancestors).first
75
+ key ? errors[key] : nil
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,24 @@
1
+ require 'cgi'
2
+
3
+ module ActiveMerchant
4
+ class PostData < Hash
5
+ class_attribute :required_fields, :instance_writer => false
6
+ self.required_fields = []
7
+
8
+ def []=(key, value)
9
+ return if value.blank? && !required?(key)
10
+ super
11
+ end
12
+
13
+ def to_post_data
14
+ collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
15
+ end
16
+
17
+ alias_method :to_s, :to_post_data
18
+
19
+ private
20
+ def required?(key)
21
+ required_fields.include?(key)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,78 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module PostsData #:nodoc:
3
+
4
+ def self.included(base)
5
+ base.class_attribute :ssl_strict
6
+ base.ssl_strict = true
7
+
8
+ base.class_attribute :ssl_version
9
+ base.ssl_version = nil
10
+
11
+ base.class_attribute :retry_safe
12
+ base.retry_safe = false
13
+
14
+ base.class_attribute :open_timeout
15
+ base.open_timeout = 60
16
+
17
+ base.class_attribute :read_timeout
18
+ base.read_timeout = 60
19
+
20
+ base.class_attribute :max_retries
21
+ base.max_retries = Connection::MAX_RETRIES
22
+
23
+ base.class_attribute :logger
24
+ base.class_attribute :wiredump_device
25
+ end
26
+
27
+ def ssl_get(endpoint, headers={})
28
+ ssl_request(:get, endpoint, nil, headers)
29
+ end
30
+
31
+ def ssl_post(endpoint, data, headers = {})
32
+ ssl_request(:post, endpoint, data, headers)
33
+ end
34
+
35
+ def ssl_request(method, endpoint, data, headers)
36
+ handle_response(raw_ssl_request(method, endpoint, data, headers))
37
+ end
38
+
39
+ def raw_ssl_request(method, endpoint, data, headers = {})
40
+ logger.warn "#{self.class} using ssl_strict=false, which is insecure" if logger unless ssl_strict
41
+ logger.warn "#{self.class} posting to plaintext endpoint, which is insecure" if logger unless endpoint =~ /^https:/
42
+
43
+ connection = new_connection(endpoint)
44
+ connection.open_timeout = open_timeout
45
+ connection.read_timeout = read_timeout
46
+ connection.retry_safe = retry_safe
47
+ connection.verify_peer = ssl_strict
48
+ connection.ssl_version = ssl_version
49
+ connection.logger = logger
50
+ connection.max_retries = max_retries
51
+ connection.tag = self.class.name
52
+ connection.wiredump_device = wiredump_device
53
+
54
+ connection.pem = @options[:pem] if @options
55
+ connection.pem_password = @options[:pem_password] if @options
56
+
57
+ connection.ignore_http_status = @options[:ignore_http_status] if @options
58
+
59
+ connection.request(method, data, headers)
60
+ end
61
+
62
+ private
63
+
64
+ def new_connection(endpoint)
65
+ Connection.new(endpoint)
66
+ end
67
+
68
+ def handle_response(response)
69
+ case response.code.to_i
70
+ when 200...300
71
+ response.body
72
+ else
73
+ raise ResponseError.new(response)
74
+ end
75
+ end
76
+
77
+ end
78
+ end