activemerchant 1.75.0 → 1.76.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 50bb4461b27b49be6e87608a11f5db29a02d0adc
4
- data.tar.gz: d5b76878be3ec6d7334cb343d439837b6fb3d599
3
+ metadata.gz: fe5e2a6f611e503ee6acfb6453f46b914f9b93c4
4
+ data.tar.gz: ecdf4eaa3a3d3697aa769239ac59ac4121f5d46a
5
5
  SHA512:
6
- metadata.gz: 3eff219643c61371a81f0242548576dadfd8032cd3939a5859e6d5a8ca1aa4b822454754ed6aef844c14096af72a69b90b6ae5cb2ba8491fb662beba6aeb8447
7
- data.tar.gz: 2cc15eaa916a7b90c66e8686668ec01cc56cebdfe2029c555033e2e1907fc931842e24bad2c8fb7760d543940cdfb6d84da0d8e1da24ccaa4927adc7646950d9
6
+ metadata.gz: 506d1d259018973cc2d4c9b6c37442f7fdc8903595a5cd6056ab32fa2608bef32d0e94d1f09179f2b660b99e1197c85c146f0afb0a60cd07a42432ebff0d5fc8
7
+ data.tar.gz: 8e8ca9a39966245232a7b64a685939e7852eab12f28fc3f1dc540b9e10a39c04517fd912bc609d56101e4e16d0165fe5b7a1f45570b009a35aaf27be09760284
data/CHANGELOG CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  == HEAD
4
4
 
5
+ == Version 1.76.0 (January 3, 2018)
6
+ * PayU Latam: Change default text for description [nfarve] #2669
7
+ * Checkout V2: Allows AVS and CVV result details to come through on authorizations [deedeelavinder] #2650
8
+ * Global Collect: Adds boolean option for pre_authorization [deeedeelavinder] #2651
9
+ * Credorax: Pass Transaction Type field [curiousepic] #2653
10
+ * Add CardProcess Gateway [bpollack] #2659
11
+ * Safe Charge: Provision 3DS option for approved merchants [deedeelavinder] #2661
12
+ * PayU Latam: Require payment_country on initialize [curiousepic] #2663
13
+ * Adyen: Remove CVV as Required Field and Determines shopperInteraction [nfarve] #2665
14
+ * SafeCharge: add support for VendorID, WebsiteID, and IP logging [bpollack] #2667
15
+ * Safe Charge: Adds 3DS flag [deedeelavinder] #2668
16
+ * CardProcess: Fix success? to always return true or false [bpollack] #2674
17
+ * SagePay: Correct CVV, AVS codes for Sagepay [singhai0] #2670
18
+ * PayU Latam: Count pending Voids as successful [curiousepic] #2677
19
+ * Mercado Pago: Ensure acess tokens are URL escaped [bpollack] #2675
20
+ * MiGS: Update hash format to SHA256 and restore remote tests [bpollack] #2676
21
+ * MiGS: Support verify calls [bpollack] #2664
22
+ * iATS: Fix Messages with Failure on iATS Server [nfarve] #2680
23
+ * Barclaycard Smartpay: Correct repsonse for fraud rejects #2683
24
+ * Adyen: Allow incomplete addresses in some situations [bpollack] #2684
25
+ * Paymentez: Add new gateway [bpollack] #2685
26
+ * PayU Latam: Provide a mechanism to override the amount in verify [dtykocki] #2688
27
+ * Mercado Pago: Support X-Device-Session-ID Header [bpollack] #2689
28
+ * Mercado Pago: Support arbitrary additional_info JSON [bpollack] #2691
29
+ * FirstData E4: Override ECI value for Apple Pay transactions with Discover [jasonwebster] #2671
30
+ * Quickbooks: Add payment context to Quickbooks charges and refunds [bdewater] #2694
31
+
5
32
  == Version 1.75.0 (November 9, 2017)
6
33
  * Barclaycard Smartpay: Clean up test options hashes [bpollack] #2632
7
34
  * Barclaycard Smartpay: Extra data fields for credits [bpollack] #2631
@@ -45,6 +45,7 @@ module ActiveMerchant #:nodoc:
45
45
  add_invoice(post, money, options)
46
46
  add_payment(post, payment)
47
47
  add_extra_data(post, options)
48
+ add_shopper_interaction(post,payment,options)
48
49
  add_address(post, options)
49
50
  commit('authorise', post)
50
51
  end
@@ -97,17 +98,21 @@ module ActiveMerchant #:nodoc:
97
98
  post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
98
99
  post[:deliveryDate] = options[:delivery_date] if options[:delivery_date]
99
100
  post[:merchantOrderReference] = options[:merchant_order_reference] if options[:merchant_order_reference]
100
- post[:shopperInteraction] = options[:shopper_interaction] if options[:shopper_interaction]
101
+ end
102
+
103
+ def add_shopper_interaction(post, payment, options={})
104
+ shopper_interaction = payment.verification_value ? "Ecommerce" : "ContAuth"
105
+ post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
101
106
  end
102
107
 
103
108
  def add_address(post, options)
104
109
  return unless post[:card] && post[:card].kind_of?(Hash)
105
- if address = options[:billing_address] || options[:address]
110
+ if (address = options[:billing_address] || options[:address]) && address[:country]
106
111
  post[:card][:billingAddress] = {}
107
- post[:card][:billingAddress][:street] = address[:address1] if address[:address1]
108
- post[:card][:billingAddress][:houseNumberOrName] = address[:address2] if address[:address2]
112
+ post[:card][:billingAddress][:street] = address[:address1] || 'N/A'
113
+ post[:card][:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A'
109
114
  post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip]
110
- post[:card][:billingAddress][:city] = address[:city] if address[:city]
115
+ post[:card][:billingAddress][:city] = address[:city] || 'N/A'
111
116
  post[:card][:billingAddress][:stateOrProvince] = address[:state] if address[:state]
112
117
  post[:card][:billingAddress][:country] = address[:country] if address[:country]
113
118
  end
@@ -138,8 +143,9 @@ module ActiveMerchant #:nodoc:
138
143
  number: payment.number,
139
144
  cvc: payment.verification_value
140
145
  }
146
+
141
147
  card.delete_if{|k,v| v.blank? }
142
- requires!(card, :expiryMonth, :expiryYear, :holderName, :number, :cvc)
148
+ requires!(card, :expiryMonth, :expiryYear, :holderName, :number)
143
149
  post[:card] = card
144
150
  end
145
151
 
@@ -211,8 +211,8 @@ module ActiveMerchant #:nodoc:
211
211
  end
212
212
 
213
213
  def success_from(response)
214
- return true if response.has_key?('authCode')
215
214
  return true if response['result'] == 'Success'
215
+ return true if response['resultCode'] == 'Authorised'
216
216
  return true if response['resultCode'] == 'Received'
217
217
  successful_responses = %w([capture-received] [cancel-received] [refund-received])
218
218
  successful_responses.include?(response['response'])
@@ -0,0 +1,254 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class CardprocessGateway < Gateway
4
+ self.test_url = 'https://test.vr-pay-ecommerce.de/v1/payments'
5
+ self.live_url = 'https://vr-pay-ecommerce.de/v1/payments'
6
+
7
+ self.supported_countries = %w[ BE BG CZ DK DE EE IE ES FR HR IT CY LV LT LU
8
+ MT HU NL AT PL PT RO SI SK FI SE GB IS LI NO
9
+ CH ME MK AL RS TR BA ]
10
+ self.default_currency = 'EUR'
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
12
+
13
+ self.homepage_url = 'https://vr-pay-ecommerce.docs.oppwa.com/'
14
+ self.display_name = 'CardProcess VR-Pay'
15
+ self.money_format = :dollars
16
+
17
+ STANDARD_ERROR_CODE_MAPPING = {}
18
+
19
+ # Creates a new CardProcess Gateway
20
+ #
21
+ # The gateway requires a valid login, password, and entity ID
22
+ # to be passed in the +options+ hash.
23
+ #
24
+ # === Options
25
+ #
26
+ # * <tt>:user_id</tt> -- The CardProcess user ID
27
+ # * <tt>:password</tt> -- The CardProcess password
28
+ # * <tt>:entity_id</tt> -- The CardProcess channel or entity ID for any transactions
29
+ def initialize(options={})
30
+ requires!(options, :user_id, :password, :entity_id)
31
+ super
32
+ # This variable exists purely to allow remote tests to force error codes;
33
+ # the lack of a setter despite its usage is intentional.
34
+ @test_options = {}
35
+ end
36
+
37
+ def purchase(money, payment, options = {})
38
+ post = {}
39
+ add_invoice(post, money, options)
40
+ add_payment(post, payment)
41
+ add_address(post, payment, options)
42
+ add_customer_data(post, options)
43
+
44
+ commit('DB', post)
45
+ end
46
+
47
+ def authorize(money, payment, options = {})
48
+ post = {}
49
+ add_invoice(post, money, options)
50
+ add_payment(post, payment)
51
+ add_address(post, payment, options)
52
+ add_customer_data(post, options)
53
+
54
+ commit('PA', post)
55
+ end
56
+
57
+ def capture(money, authorization, options = {})
58
+ post = {
59
+ id: authorization
60
+ }
61
+ add_invoice(post, money, options)
62
+ commit('CP', post)
63
+ end
64
+
65
+ def refund(money, authorization, options = {})
66
+ post = {
67
+ id: authorization
68
+ }
69
+ add_invoice(post, money, options)
70
+ commit('RF', post)
71
+ end
72
+
73
+ def credit(money, payment, options = {})
74
+ post = {}
75
+ add_invoice(post, money, options)
76
+ add_payment(post, payment)
77
+ add_address(post, payment, options)
78
+ add_customer_data(post, options)
79
+
80
+ commit('CD', post)
81
+ end
82
+
83
+ def void(authorization, _options = {})
84
+ post = {
85
+ id: authorization
86
+ }
87
+ commit('RV', post)
88
+ end
89
+
90
+ def verify(credit_card, options = {})
91
+ MultiResponse.run do |r|
92
+ r.process { authorize(100, credit_card, options) }
93
+ r.process { void(r.authorization, options) }
94
+ end
95
+ end
96
+
97
+ def supports_scrubbing?
98
+ true
99
+ end
100
+
101
+ def scrub(transcript)
102
+ transcript
103
+ .gsub(%r{(authentication\.[^=]+=)[^&]+}, '\1[FILTERED]')
104
+ .gsub(%r{(card\.number=)\d+}, '\1[FILTERED]')
105
+ .gsub(%r{(cvv=)\d{3,4}}, '\1[FILTERED]\2')
106
+ end
107
+
108
+ private
109
+
110
+ def add_customer_data(post, options)
111
+ post['customer.ip'] = options[:ip] if options[:ip]
112
+ end
113
+
114
+ def add_address(post, _card, options)
115
+ if (address = options[:billing_address] || options[:address])
116
+ post[:billing] = hashify_address(address)
117
+ end
118
+
119
+ if (shipping = options[:shipping_address])
120
+ post[:shipping] = hashify_address(shipping)
121
+ end
122
+ end
123
+
124
+ def add_invoice(post, money, options)
125
+ return if money.nil?
126
+ post[:amount] = amount(money)
127
+ post[:currency] = (options[:currency] || currency(money))
128
+ post[:merchantInvoiceId] = options[:merchant_invoice_id] if options[:merchant_invoice_id]
129
+ post[:merchantTransactionId] = options[:merchant_transaction_id] if options[:merchant_transaction_id]
130
+ post[:transactionCategory] = options[:transaction_category] if options[:transaction_category]
131
+ end
132
+
133
+ def add_payment(post, payment)
134
+ return if payment.is_a?(String)
135
+ post[:paymentBrand] = payment.brand.upcase if payment.brand
136
+ post[:card] ||= {}
137
+ post[:card][:number] = payment.number
138
+ post[:card][:holder] = payment.name
139
+ post[:card][:expiryMonth] = sprintf('%02d', payment.month)
140
+ post[:card][:expiryYear] = sprintf('%02d', payment.year)
141
+ post[:card][:cvv] = payment.verification_value
142
+ end
143
+
144
+ def parse(body)
145
+ JSON.parse(body)
146
+ end
147
+
148
+ def commit(action, parameters)
149
+ url = (test? ? test_url : live_url)
150
+ if (id = parameters.delete(:id))
151
+ url += "/#{id}"
152
+ end
153
+
154
+ begin
155
+ raw_response = ssl_post(url, post_data(action, parameters.merge(@test_options)))
156
+ rescue ResponseError => e
157
+ raw_response = e.response.body
158
+ end
159
+ response = parse(raw_response)
160
+
161
+ Response.new(
162
+ success_from(response),
163
+ message_from(response),
164
+ response,
165
+ authorization: authorization_from(response),
166
+ avs_result: AVSResult.new(code: response['result']['avsResponse']),
167
+ cvv_result: CVVResult.new(response['result']['cvvResponse']),
168
+ test: test?,
169
+ error_code: error_code_from(response)
170
+ )
171
+ end
172
+
173
+ def success_from(response)
174
+ !(response['result']['code'] =~ /^(000\.000\.|000\.100\.1|000\.[36])/).nil?
175
+ end
176
+
177
+ def message_from(response)
178
+ response['result']['description']
179
+ end
180
+
181
+ def authorization_from(response)
182
+ response['id']
183
+ end
184
+
185
+ def post_data(action, parameters = {})
186
+ post = parameters.clone
187
+ post[:authentication] ||= {}
188
+ post[:authentication][:userId] = @options[:user_id]
189
+ post[:authentication][:password] = @options[:password]
190
+ post[:authentication][:entityId] = @options[:entity_id]
191
+ post[:paymentType] = action
192
+ dot_flatten_hash(post).map {|key, value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&")
193
+ end
194
+
195
+ def error_code_from(response)
196
+ unless success_from(response)
197
+ case response['result']['code']
198
+ when '100.100.101'
199
+ STANDARD_ERROR_CODE[:incorrect_number]
200
+ when '100.100.303'
201
+ STANDARD_ERROR_CODE[:expired_card]
202
+ when /100\.100\.(201|301|305)/
203
+ STANDARD_ERROR_CODE[:invalid_expiry_date]
204
+ when /100.100.60[01]/
205
+ STANDARD_ERROR_CODE[:invalid_cvc]
206
+ when '800.100.151'
207
+ STANDARD_ERROR_CODE[:invalid_number]
208
+ when '800.100.153'
209
+ STANDARD_ERROR_CODE[:incorrect_cvc]
210
+ when /800.800.(102|302)/
211
+ STANDARD_ERROR_CODE[:incorrect_address]
212
+ when '800.800.202'
213
+ STANDARD_ERROR_CODE[:invalid_zip]
214
+ when '800.100.166'
215
+ STANDARD_ERROR_CODE[:incorrect_pin]
216
+ when '800.100.171'
217
+ STANDARD_ERROR_CODE[:pickup_card]
218
+ when /^(200|700)\./
219
+ STANDARD_ERROR_CODE[:config_error]
220
+ when /^(800\.[17]00|800\.800\.[123])/
221
+ STANDARD_ERROR_CODE[:card_declined]
222
+ when /^(900\.[1234]00)/
223
+ STANDARD_ERROR_CODE[:processing_error]
224
+ else
225
+ STANDARD_ERROR_CODE[:processing_error]
226
+ end
227
+ end
228
+ end
229
+
230
+ def hashify_address(address)
231
+ hash = {}
232
+ hash[:street1] = address[:address1] if address[:address1]
233
+ hash[:street2] = address[:address2] if address[:address2]
234
+ hash[:city] = address[:city] if address[:city]
235
+ hash[:state] = address[:state] if address[:state]
236
+ hash[:postcode] = address[:zip] if address[:zip]
237
+ hash[:country] = address[:country] if address[:country]
238
+ hash
239
+ end
240
+
241
+ def dot_flatten_hash(hash, prefix = '')
242
+ h = {}
243
+ hash.each_pair do |k, v|
244
+ if v.is_a?(Hash)
245
+ h.merge!(dot_flatten_hash(v, prefix + k.to_s + '.'))
246
+ else
247
+ h[prefix + k.to_s] = v
248
+ end
249
+ end
250
+ h
251
+ end
252
+ end
253
+ end
254
+ end
@@ -25,16 +25,7 @@ module ActiveMerchant #:nodoc:
25
25
  merged_params = multi.responses.map { |r| r.params }.reduce({}, :merge)
26
26
  succeeded = success_from(merged_params)
27
27
 
28
- Response.new(
29
- succeeded,
30
- message_from(succeeded, merged_params),
31
- merged_params,
32
- authorization: authorization_from(merged_params),
33
- avs_result: avs_result(:purchase, succeeded, merged_params),
34
- cvv_result: cvv_result(:purchase, succeeded, merged_params),
35
- error_code: error_code_from(succeeded, merged_params),
36
- test: test?
37
- )
28
+ response(:purchase, succeeded, merged_params)
38
29
  end
39
30
 
40
31
  def authorize(amount, payment_method, options={})
@@ -132,6 +123,15 @@ module ActiveMerchant #:nodoc:
132
123
  end
133
124
 
134
125
  succeeded = success_from(response)
126
+
127
+ response(action, succeeded, response)
128
+ end
129
+
130
+ def response(action, succeeded, response)
131
+ successful_response = succeeded && action == :purchase || action == :authorize
132
+ avs_result = successful_response ? avs_result(response) : nil
133
+ cvv_result = successful_response ? cvv_result(response) : nil
134
+
135
135
  Response.new(
136
136
  succeeded,
137
137
  message_from(succeeded, response),
@@ -139,8 +139,9 @@ module ActiveMerchant #:nodoc:
139
139
  authorization: authorization_from(response),
140
140
  error_code: error_code_from(succeeded, response),
141
141
  test: test?,
142
- avs_result: avs_result(action, succeeded, response),
143
- cvv_result: cvv_result(action, succeeded, response))
142
+ avs_result: avs_result,
143
+ cvv_result: cvv_result
144
+ )
144
145
  end
145
146
 
146
147
  def headers
@@ -162,20 +163,12 @@ module ActiveMerchant #:nodoc:
162
163
  test? ? test_url : live_url
163
164
  end
164
165
 
165
- def avs_result(action, succeeded, response)
166
- if succeeded
167
- action == :purchase ? AVSResult.new(code: response["card"]["avsCheck"]) : nil
168
- else
169
- nil
170
- end
166
+ def avs_result(response)
167
+ response['card'] && response['card']['avsCheck'] ? AVSResult.new(code: response['card']['avsCheck']) : nil
171
168
  end
172
169
 
173
- def cvv_result(action, succeeded, response)
174
- if succeeded
175
- action == :purchase ? CVVResult.new(response["card"]["cvvCheck"]) : nil
176
- else
177
- nil
178
- end
170
+ def cvv_result(response)
171
+ response['card'] && response['card']['cvvCheck'] ? CVVResult.new(response['card']['cvvCheck']) : nil
179
172
  end
180
173
 
181
174
  def parse(body)
@@ -129,6 +129,7 @@ module ActiveMerchant #:nodoc:
129
129
  add_email(post, options)
130
130
  add_3d_secure(post, options)
131
131
  add_echo(post, options)
132
+ add_transaction_type(post, options)
132
133
 
133
134
  commit(:purchase, post)
134
135
  end
@@ -141,6 +142,7 @@ module ActiveMerchant #:nodoc:
141
142
  add_email(post, options)
142
143
  add_3d_secure(post, options)
143
144
  add_echo(post, options)
145
+ add_transaction_type(post, options)
144
146
 
145
147
  commit(:authorize, post)
146
148
  end
@@ -182,7 +184,8 @@ module ActiveMerchant #:nodoc:
182
184
  add_customer_data(post, options)
183
185
  add_email(post, options)
184
186
  add_echo(post, options)
185
-
187
+ add_transaction_type(post, options)
188
+
186
189
  commit(:credit, post)
187
190
  end
188
191
 
@@ -264,6 +267,10 @@ module ActiveMerchant #:nodoc:
264
267
  post[:d2] = options[:echo] unless options[:echo].blank?
265
268
  end
266
269
 
270
+ def add_transaction_type(post, options)
271
+ post[:a9] = options[:transaction_type] if options[:transaction_type]
272
+ end
273
+
267
274
  ACTIONS = {
268
275
  purchase: '1',
269
276
  authorize: '2',
@@ -237,13 +237,24 @@ module ActiveMerchant #:nodoc:
237
237
  xml.tag! "CardHoldersName", credit_card.name
238
238
  xml.tag! "CardType", card_type(credit_card.brand)
239
239
 
240
- eci = (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
241
- xml.tag! "Ecommerce_Flag", eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci
242
-
240
+ add_credit_card_eci(xml, credit_card, options)
243
241
  add_credit_card_verification_strings(xml, credit_card, options)
244
242
  end
245
243
  end
246
244
 
245
+ def add_credit_card_eci(xml, credit_card, options)
246
+ eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == "discover"
247
+ # Discover requires any Apple Pay transaction, regardless of in-app
248
+ # or web, and regardless of the ECI contained in the PKPaymentToken,
249
+ # to have an ECI value explicitly of 04.
250
+ "04"
251
+ else
252
+ (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
253
+ end
254
+
255
+ xml.tag! "Ecommerce_Flag", eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci
256
+ end
257
+
247
258
  def add_credit_card_verification_strings(xml, credit_card, options)
248
259
  address = options[:billing_address] || options[:address]
249
260
  if address
@@ -30,7 +30,7 @@ module ActiveMerchant #:nodoc:
30
30
  def authorize(money, payment, options={})
31
31
  post = nestable_hash
32
32
  add_order(post, money, options)
33
- add_payment(post, payment)
33
+ add_payment(post, payment, options)
34
34
  add_customer_data(post, options, payment)
35
35
  add_address(post, payment, options)
36
36
 
@@ -106,15 +106,17 @@ module ActiveMerchant #:nodoc:
106
106
  }
107
107
  end
108
108
 
109
- def add_payment(post, payment)
109
+ def add_payment(post, payment, options)
110
110
  year = format(payment.year, :two_digits)
111
111
  month = format(payment.month, :two_digits)
112
112
  expirydate = "#{month}#{year}"
113
+ pre_authorization = options[:pre_authorization] ? 'PRE_AUTHORIZATION' : 'FINAL_AUTHORIZATION'
113
114
 
114
115
  post["cardPaymentMethodSpecificInput"] = {
115
116
  "paymentProductId" => BRAND_MAP[payment.brand],
116
117
  "skipAuthentication" => "true", # refers to 3DSecure
117
- "skipFraudService" => "true"
118
+ "skipFraudService" => "true",
119
+ "authorizationMode" => pre_authorization
118
120
  }
119
121
  post["cardPaymentMethodSpecificInput"]["card"] = {
120
122
  "cvv" => payment.verification_value,
@@ -225,7 +225,7 @@ module ActiveMerchant #:nodoc:
225
225
  end
226
226
 
227
227
  def successful_result_message?(response)
228
- response[:authorization_result].start_with?('OK')
228
+ response[:authorization_result] ? response[:authorization_result].start_with?('OK') : false
229
229
  end
230
230
 
231
231
  def success_from(response)
@@ -233,7 +233,7 @@ module ActiveMerchant #:nodoc:
233
233
  end
234
234
 
235
235
  def message_from(response)
236
- if(!successful_result_message?(response))
236
+ if !successful_result_message?(response) && response[:authorization_result]
237
237
  return response[:authorization_result].strip
238
238
  elsif(response[:status] == 'Failure')
239
239
  return response[:errors]
@@ -114,9 +114,11 @@ module ActiveMerchant #:nodoc:
114
114
 
115
115
  def add_additional_data(post, options)
116
116
  post[:sponsor_id] = options[:sponsor_id]
117
+ post[:device_id] = options[:device_id] if options[:device_id]
117
118
  post[:additional_info] = {
118
119
  ip_address: options[:ip_address]
119
- }
120
+ }.merge(options[:additional_info] || {})
121
+
120
122
 
121
123
  add_address(post, options)
122
124
  add_shipping_address(post, options)
@@ -191,7 +193,7 @@ module ActiveMerchant #:nodoc:
191
193
  if ["capture", "void"].include?(action)
192
194
  response = parse(ssl_request(:put, url(path), post_data(parameters), headers))
193
195
  else
194
- response = parse(ssl_post(url(path), post_data(parameters), headers))
196
+ response = parse(ssl_post(url(path), post_data(parameters), headers(parameters)))
195
197
  end
196
198
 
197
199
  Response.new(
@@ -221,7 +223,7 @@ module ActiveMerchant #:nodoc:
221
223
  end
222
224
 
223
225
  def post_data(parameters = {})
224
- parameters.to_json
226
+ parameters.clone.tap { |p| p.delete(:device_id) }.to_json
225
227
  end
226
228
 
227
229
  def error_code_from(action, response)
@@ -236,13 +238,15 @@ module ActiveMerchant #:nodoc:
236
238
 
237
239
  def url(action)
238
240
  full_url = (test? ? test_url : live_url)
239
- full_url + "/#{action}?access_token=#{@options[:access_token]}"
241
+ full_url + "/#{action}?access_token=#{CGI.escape(@options[:access_token])}"
240
242
  end
241
243
 
242
- def headers
243
- {
244
+ def headers(options = {})
245
+ headers = {
244
246
  "Content-Type" => "application/json"
245
247
  }
248
+ headers['X-Device-Session-ID'] = options[:device_id] if options[:device_id]
249
+ headers
246
250
  end
247
251
 
248
252
  def handle_response(response)
@@ -1,6 +1,6 @@
1
1
  require 'active_merchant/billing/gateways/migs/migs_codes'
2
2
 
3
- require 'digest/md5' # Used in add_secure_hash
3
+ require 'openssl' # Used in add_secure_hash
4
4
 
5
5
  module ActiveMerchant #:nodoc:
6
6
  module Billing #:nodoc:
@@ -121,6 +121,13 @@ module ActiveMerchant #:nodoc:
121
121
  refund(money, authorization, options)
122
122
  end
123
123
 
124
+ def verify(credit_card, options={})
125
+ MultiResponse.run do |r|
126
+ r.process { authorize(100, credit_card, options) }
127
+ r.process(:ignore_result) { void(r.authorization, options) }
128
+ end
129
+ end
130
+
124
131
  # Checks the status of a previous transaction
125
132
  # This can be useful when a response is not received due to network issues
126
133
  #
@@ -187,7 +194,7 @@ module ActiveMerchant #:nodoc:
187
194
 
188
195
  response_hash = parse(data)
189
196
 
190
- expected_secure_hash = calculate_secure_hash(response_hash.reject{|k, v| k == :SecureHash}, @options[:secure_hash])
197
+ expected_secure_hash = calculate_secure_hash(response_hash, @options[:secure_hash])
191
198
  unless response_hash[:SecureHash] == expected_secure_hash
192
199
  raise SecurityError, "Secure Hash mismatch, response may be tampered with"
193
200
  end
@@ -245,6 +252,7 @@ module ActiveMerchant #:nodoc:
245
252
  end
246
253
 
247
254
  def commit(post)
255
+ add_secure_hash(post) if @options[:secure_hash]
248
256
  data = ssl_post self.merchant_hosted_url, post_data(post)
249
257
  response_hash = parse(data)
250
258
  response_object(response_hash)
@@ -290,12 +298,16 @@ module ActiveMerchant #:nodoc:
290
298
 
291
299
  def add_secure_hash(post)
292
300
  post[:SecureHash] = calculate_secure_hash(post, @options[:secure_hash])
301
+ post[:SecureHashType] = 'SHA256'
293
302
  end
294
303
 
295
304
  def calculate_secure_hash(post, secure_hash)
296
- sorted_values = post.sort_by(&:to_s).map(&:last)
297
- input = secure_hash + sorted_values.join
298
- Digest::MD5.hexdigest(input).upcase
305
+ input = post
306
+ .reject { |k| %i[SecureHash SecureHashType].include?(k) }
307
+ .sort
308
+ .map { |(k, v)| "vpc_#{k}=#{v}" }
309
+ .join('&')
310
+ OpenSSL::HMAC.hexdigest('SHA256', [secure_hash].pack('H*'), input).upcase
299
311
  end
300
312
  end
301
313
  end
@@ -0,0 +1,273 @@
1
+ require 'base64'
2
+ require 'digest'
3
+
4
+ module ActiveMerchant #:nodoc:
5
+ module Billing #:nodoc:
6
+ class PaymentezGateway < Gateway #:nodoc:
7
+ self.test_url = 'https://ccapi-stg.paymentez.com/v2/'
8
+ self.live_url = 'https://ccapi.paymentez.com/v2/'
9
+
10
+ self.supported_countries = %w[MX EC VE CO BR CL]
11
+ self.default_currency = 'USD'
12
+ self.supported_cardtypes = %i[visa master american_express diners_club]
13
+
14
+ self.homepage_url = 'https://secure.paymentez.com/'
15
+ self.display_name = 'Paymentez'
16
+
17
+ STANDARD_ERROR_CODE_MAPPING = {
18
+ 1 => :processing_error,
19
+ 6 => :card_declined,
20
+ 9 => :card_declined,
21
+ 10 => :processing_error,
22
+ 11 => :card_declined,
23
+ 12 => :config_error,
24
+ 13 => :config_error,
25
+ 19 => :invalid_cvc,
26
+ 20 => :config_error,
27
+ 21 => :card_declined,
28
+ 22 => :card_declined,
29
+ 23 => :card_declined,
30
+ 24 => :card_declined,
31
+ 25 => :card_declined,
32
+ 26 => :card_declined,
33
+ 27 => :card_declined,
34
+ 28 => :card_declined
35
+ }.freeze
36
+
37
+ CARD_MAPPING = {
38
+ 'visa' => 'vi',
39
+ 'master' => 'mc',
40
+ 'american_express' => 'ax',
41
+ 'diners_club' => 'di'
42
+ }.freeze
43
+
44
+ def initialize(options = {})
45
+ requires!(options, :application_code, :app_key)
46
+ super
47
+ end
48
+
49
+ def purchase(money, payment, options = {})
50
+ post = {}
51
+
52
+ add_invoice(post, money, options)
53
+ add_payment(post, payment)
54
+ add_customer_data(post, options)
55
+
56
+ commit_transaction('debit_cc', post)
57
+ end
58
+
59
+ def authorize(money, payment, options = {})
60
+ post = {}
61
+
62
+ add_invoice(post, money, options)
63
+ add_customer_data(post, options)
64
+
65
+ MultiResponse.run do |r|
66
+ r.process { store(payment, options) }
67
+ post[:card] = { token: r.authorization }
68
+ r.process { commit_transaction('authorize', post) }
69
+ end
70
+ end
71
+
72
+ def capture(_money, authorization, _options = {})
73
+ post = { transaction: { id: authorization } }
74
+
75
+ commit_transaction('capture', post)
76
+ end
77
+
78
+ def refund(_money, authorization, options = {})
79
+ void(authorization, options)
80
+ end
81
+
82
+ def void(authorization, _options = {})
83
+ post = { transaction: { id: authorization } }
84
+ commit_transaction('refund', post)
85
+ end
86
+
87
+ def verify(credit_card, options = {})
88
+ MultiResponse.run do |r|
89
+ r.process { authorize(100, credit_card, options) }
90
+ r.process { void(r.authorization, options) }
91
+ end
92
+ end
93
+
94
+ def store(credit_card, options = {})
95
+ post = {}
96
+
97
+ add_customer_data(post, options)
98
+ add_payment(post, credit_card)
99
+
100
+ response = commit_card('add', post)
101
+ if !response.success? && !(token = extract_previous_card_token(response)).nil?
102
+ unstore(token, options)
103
+ response = commit_card('add', post)
104
+ end
105
+ response
106
+ end
107
+
108
+ def unstore(identification, options = {})
109
+ post = { card: { token: identification }, user: { id: options[:user_id] }}
110
+ commit_card('delete', post)
111
+ end
112
+
113
+ def supports_scrubbing?
114
+ true
115
+ end
116
+
117
+ def scrub(transcript)
118
+ transcript
119
+ .gsub(%r{(\\?"number\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]')
120
+ .gsub(%r{(\\?"cvc\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]')
121
+ .gsub(%r{(Auth-Token: )([A-Za-z0-9=]+)}, '\1[FILTERED]')
122
+ end
123
+
124
+ private
125
+
126
+ def add_customer_data(post, options)
127
+ requires!(options, :user_id, :email)
128
+ post[:user] ||= {}
129
+ post[:user][:id] = options[:user_id]
130
+ post[:user][:email] = options[:email]
131
+ post[:user][:ip_address] = options[:ip] if options[:ip]
132
+ post[:user][:fiscal_number] = options[:fiscal_number] if options[:fiscal_number]
133
+ end
134
+
135
+ def add_invoice(post, money, options)
136
+ post[:session_id] = options[:session_id] if options[:session_id]
137
+
138
+ post[:order] ||= {}
139
+ post[:order][:amount] = amount(money).to_f
140
+ post[:order][:vat] = options[:vat] if options[:vat]
141
+ post[:order][:dev_reference] = options[:dev_reference] if options[:dev_reference]
142
+ post[:order][:description] = options[:description] if options[:description]
143
+ post[:order][:discount] = options[:discount] if options[:discount]
144
+ post[:order][:installments] = options[:installments] if options[:installments]
145
+ post[:order][:installments_type] = options[:installments_type] if options[:installments_type]
146
+ post[:order][:taxable_amount] = options[:taxable_amount] if options[:taxable_amount]
147
+ end
148
+
149
+ def add_payment(post, payment)
150
+ post[:card] ||= {}
151
+ post[:card][:number] = payment.number
152
+ post[:card][:holder_name] = payment.name
153
+ post[:card][:expiry_month] = payment.month
154
+ post[:card][:expiry_year] = payment.year
155
+ post[:card][:cvc] = payment.verification_value
156
+ post[:card][:type] = CARD_MAPPING[payment.brand]
157
+ end
158
+
159
+ def parse(body)
160
+ JSON.parse(body)
161
+ end
162
+
163
+ def commit_raw(object, action, parameters)
164
+ url = "#{(test? ? test_url : live_url)}#{object}/#{action}"
165
+
166
+ begin
167
+ raw_response = ssl_post(url, post_data(parameters), headers)
168
+ rescue ResponseError => e
169
+ raw_response = e.response.body
170
+ end
171
+ parse(raw_response)
172
+ end
173
+
174
+ def commit_transaction(action, parameters)
175
+ response = commit_raw('transaction', action, parameters)
176
+ Response.new(
177
+ success_from(response),
178
+ message_from(response),
179
+ response,
180
+ authorization: authorization_from(response),
181
+ test: test?,
182
+ error_code: error_code_from(response)
183
+ )
184
+ end
185
+
186
+ def commit_card(action, parameters)
187
+ response = commit_raw('card', action, parameters)
188
+ Response.new(
189
+ card_success_from(response),
190
+ card_message_from(response),
191
+ response,
192
+ authorization: card_authorization_from(response),
193
+ test: test?,
194
+ error_code: card_error_code_from(response)
195
+ )
196
+ end
197
+
198
+ def headers
199
+ {
200
+ 'Auth-Token' => authentication_code,
201
+ 'Content-Type' => 'application/json'
202
+ }
203
+ end
204
+
205
+ def success_from(response)
206
+ !response.include?('error') && (response['status'] || response['transaction']['status']) == 'success'
207
+ end
208
+
209
+ def card_success_from(response)
210
+ return false if response.include?('error')
211
+ return true if response['message'] == 'card deleted'
212
+ response['card']['status'] == 'valid'
213
+ end
214
+
215
+ def message_from(response)
216
+ if success_from(response)
217
+ response['transaction'] && response['transaction']['message']
218
+ else
219
+ response['error'] && response['error']['type']
220
+ end
221
+ end
222
+
223
+ def card_message_from(response)
224
+ if !response.include?('error')
225
+ response['message'] || response['card']['message']
226
+ else
227
+ response['error']['type']
228
+ end
229
+ end
230
+
231
+ def authorization_from(response)
232
+ response['transaction'] && response['transaction']['id']
233
+ end
234
+
235
+ def card_authorization_from(response)
236
+ response['card'] && response['card']['token']
237
+ end
238
+
239
+ def extract_previous_card_token(response)
240
+ match = /Card already added: (\d+)/.match(response.message)
241
+ match && match[1]
242
+ end
243
+
244
+ def post_data(parameters = {})
245
+ JSON.dump(parameters)
246
+ end
247
+
248
+ def error_code_from(response)
249
+ return if success_from(response)
250
+ if response['transaction']
251
+ detail = response['transaction']['status_detail']
252
+ if STANDARD_ERROR_CODE_MAPPING.include?(detail)
253
+ return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]]
254
+ end
255
+ elsif response['error']
256
+ return STANDARD_ERROR_CODE[:config_error]
257
+ end
258
+ STANDARD_ERROR_CODE[:processing_error]
259
+ end
260
+
261
+ def card_error_code_from(response)
262
+ STANDARD_ERROR_CODE[:processing_error] unless card_success_from(response)
263
+ end
264
+
265
+ def authentication_code
266
+ timestamp = Time.new.to_i
267
+ unique_token = Digest::SHA256.hexdigest("#{@options[:app_key]}#{timestamp}")
268
+ authentication_string = "#{@options[:application_code]};#{timestamp};#{unique_token}"
269
+ Base64.encode64(authentication_string).delete("\n")
270
+ end
271
+ end
272
+ end
273
+ end
@@ -29,9 +29,8 @@ module ActiveMerchant #:nodoc:
29
29
  }
30
30
 
31
31
  def initialize(options={})
32
- requires!(options, :merchant_id, :account_id, :api_login, :api_key)
32
+ requires!(options, :merchant_id, :account_id, :api_login, :api_key, :payment_country)
33
33
  super
34
- @options[:payment_country] ||= options[:payment_country] if options[:payment_country]
35
34
  end
36
35
 
37
36
  def purchase(amount, payment_method, options={})
@@ -78,7 +77,7 @@ module ActiveMerchant #:nodoc:
78
77
 
79
78
  def verify(credit_card, options={})
80
79
  minimum = MINIMUMS[options[:currency].upcase] if options[:currency]
81
- amount = minimum || 100
80
+ amount = options[:verify_amount] || minimum || 100
82
81
 
83
82
  MultiResponse.run(:use_first_response) do |r|
84
83
  r.process { authorize(amount, credit_card, options) }
@@ -139,7 +138,7 @@ module ActiveMerchant #:nodoc:
139
138
 
140
139
  def add_transaction_elements(post, type, options)
141
140
  transaction = {}
142
- transaction[:paymentCountry] = @options[:payment_country] || (options[:billing_address][:country] if options[:billing_address])
141
+ transaction[:paymentCountry] = @options[:payment_country]
143
142
  transaction[:type] = type
144
143
  transaction[:ipAddress] = options[:ip] || ''
145
144
  transaction[:userAgent] = options[:user_agent] if options[:user_agent]
@@ -153,7 +152,7 @@ module ActiveMerchant #:nodoc:
153
152
  order[:accountId] = @options[:account_id]
154
153
  order[:partnerId] = options[:partner_id] if options[:partner_id]
155
154
  order[:referenceCode] = options[:order_id] || generate_unique_id
156
- order[:description] = options[:description] || 'unspecified'
155
+ order[:description] = options[:description] || 'Compra en ' + @options[:merchant_id]
157
156
  order[:language] = 'en'
158
157
  order[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
159
158
  post[:transaction][:order] = order
@@ -167,7 +166,7 @@ module ActiveMerchant #:nodoc:
167
166
  payer[:dniNumber] = options[:dni_number] if options[:dni_number]
168
167
  payer[:dniType] = options[:dni_type] if options[:dni_type]
169
168
  payer[:emailAddress] = options[:email] if options[:email]
170
- payer[:birthdate] = options[:birth_date] if options[:birth_date] && options[:payment_country] == 'MX'
169
+ payer[:birthdate] = options[:birth_date] if options[:birth_date] && @options[:payment_country] == 'MX'
171
170
  payer[:billingAddress] = billing_address_fields(options)
172
171
  post[:transaction][:payer] = payer
173
172
  end
@@ -180,7 +179,7 @@ module ActiveMerchant #:nodoc:
180
179
  billing_address[:city] = address[:city]
181
180
  billing_address[:state] = address[:state]
182
181
  billing_address[:country] = address[:country]
183
- billing_address[:postalCode] = address[:zip] if options[:payment_country] == 'MX'
182
+ billing_address[:postalCode] = address[:zip] if @options[:payment_country] == 'MX'
184
183
  billing_address[:phone] = address[:phone]
185
184
  billing_address
186
185
  end
@@ -191,7 +190,7 @@ module ActiveMerchant #:nodoc:
191
190
  buyer[:fullName] = buyer_hash[:name]
192
191
  buyer[:dniNumber] = buyer_hash[:dni_number]
193
192
  buyer[:dniType] = buyer_hash[:dni_type]
194
- buyer[:cnpj] = buyer_hash[:cnpj] if options[:payment_country] == 'BR'
193
+ buyer[:cnpj] = buyer_hash[:cnpj] if @options[:payment_country] == 'BR'
195
194
  buyer[:emailAddress] = buyer_hash[:email]
196
195
  buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || ''
197
196
  buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
@@ -199,7 +198,7 @@ module ActiveMerchant #:nodoc:
199
198
  buyer[:fullName] = payment_method.name.strip
200
199
  buyer[:dniNumber] = options[:dni_number]
201
200
  buyer[:dniType] = options[:dni_type]
202
- buyer[:cnpj] = options[:cnpj] if options[:payment_country] == 'BR'
201
+ buyer[:cnpj] = options[:cnpj] if @options[:payment_country] == 'BR'
203
202
  buyer[:emailAddress] = options[:email]
204
203
  buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || ''
205
204
  buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
@@ -235,8 +234,8 @@ module ActiveMerchant #:nodoc:
235
234
 
236
235
  additional_values = {}
237
236
  additional_values[:TX_VALUE] = tx_value
238
- additional_values[:TX_TAX] = tx_tax if options[:payment_country] == 'CO'
239
- additional_values[:TX_TAX_RETURN_BASE] = tx_tax_return_base if options[:payment_country] == 'CO'
237
+ additional_values[:TX_TAX] = tx_tax if @options[:payment_country] == 'CO'
238
+ additional_values[:TX_TAX_RETURN_BASE] = tx_tax_return_base if @options[:payment_country] == 'CO'
240
239
 
241
240
  post[:transaction][:order][:additionalValues] = additional_values
242
241
  end
@@ -358,7 +357,7 @@ module ActiveMerchant #:nodoc:
358
357
  response["code"] == "SUCCESS" && response["creditCardToken"] && response["creditCardToken"]["creditCardTokenId"].present?
359
358
  when 'verify_credentials'
360
359
  response["code"] == "SUCCESS"
361
- when 'refund'
360
+ when 'refund', 'void'
362
361
  response["code"] == "SUCCESS" && response["transactionResponse"] && (response["transactionResponse"]["state"] == "PENDING" || response["transactionResponse"]["state"] == "APPROVED")
363
362
  else
364
363
  response["code"] == "SUCCESS" && response["transactionResponse"] && (response["transactionResponse"]["state"] == "APPROVED")
@@ -375,8 +374,10 @@ module ActiveMerchant #:nodoc:
375
374
  return "VERIFIED" if success
376
375
  "FAILED"
377
376
  else
378
- response_message = response["transactionResponse"]["responseMessage"] if response["transactionResponse"]
379
- response_code = response["transactionResponse"]["responseCode"] if response["transactionResponse"]
377
+ if response["transactionResponse"]
378
+ response_message = response["transactionResponse"]["responseMessage"]
379
+ response_code = response["transactionResponse"]["responseCode"] || response["transactionResponse"]["pendingReason"]
380
+ end
380
381
  return response_code if success
381
382
  response["error"] || response_message || response_code || "FAILED"
382
383
  end
@@ -78,6 +78,7 @@ module ActiveMerchant #:nodoc:
78
78
  post = {}
79
79
  capture_uri = "#{ENDPOINT}/#{CGI.escape(authorization)}/capture"
80
80
  post[:amount] = localized_amount(money, currency(money))
81
+ add_context(post, options)
81
82
 
82
83
  commit(capture_uri, post)
83
84
  end
@@ -85,6 +86,7 @@ module ActiveMerchant #:nodoc:
85
86
  def refund(money, authorization, options = {})
86
87
  post = {}
87
88
  post[:amount] = localized_amount(money, currency(money))
89
+ add_context(post, options)
88
90
 
89
91
  commit(refund_uri(authorization), post)
90
92
  end
@@ -137,6 +139,7 @@ module ActiveMerchant #:nodoc:
137
139
 
138
140
  def add_payment(post, payment, options = {})
139
141
  add_creditcard(post, payment, options)
142
+ add_context(post, options)
140
143
  end
141
144
 
142
145
  def add_creditcard(post, creditcard, options = {})
@@ -151,6 +154,13 @@ module ActiveMerchant #:nodoc:
151
154
  post[:card] = card
152
155
  end
153
156
 
157
+ def add_context(post, options = {})
158
+ post[:context] = {
159
+ mobile: options.fetch(:mobile, false),
160
+ isEcommerce: options.fetch(:ecommerce, true)
161
+ }
162
+ end
163
+
154
164
  def parse(body)
155
165
  JSON.parse(body)
156
166
  end
@@ -22,7 +22,9 @@ module ActiveMerchant #:nodoc:
22
22
 
23
23
  def purchase(money, payment, options={})
24
24
  post = {}
25
- add_transaction_data("Sale", post, money, options)
25
+ post[:sg_APIType] = 1 if options[:three_d_secure]
26
+ trans_type = options[:three_d_secure] ? "Sale3D" : "Sale"
27
+ add_transaction_data(trans_type, post, money, options)
26
28
  add_payment(post, payment)
27
29
  add_customer_details(post, payment, options)
28
30
 
@@ -120,6 +122,9 @@ module ActiveMerchant #:nodoc:
120
122
  post[:sg_UserID] = options[:user_id] if options[:user_id]
121
123
  post[:sg_AuthType] = options[:auth_type] if options[:auth_type]
122
124
  post[:sg_ExpectedFulfillmentCount] = options[:expected_fulfillment_count] if options[:expected_fulfillment_count]
125
+ post[:sg_WebsiteID] = options[:website_id] if options[:website_id]
126
+ post[:sg_IPAddress] = options[:ip] if options[:ip]
127
+ post[:sg_VendorID] = options[:vendor_id] if options[:vendor_id]
123
128
  end
124
129
 
125
130
  def add_payment(post, payment)
@@ -150,21 +155,32 @@ module ActiveMerchant #:nodoc:
150
155
 
151
156
  doc = Nokogiri::XML(xml)
152
157
  doc.root.xpath('*').each do |node|
153
- response[node.name.underscore.downcase.to_sym] = node.text
158
+ if node.elements.size == 0
159
+ response[node.name.underscore.downcase.to_sym] = node.text
160
+ else
161
+ node.traverse do |childnode|
162
+ childnode_to_response(response, childnode)
163
+ end
164
+ end
154
165
  end
155
-
156
166
  response
157
167
  end
158
168
 
159
- def childnode_to_response(response, node, childnode)
160
- name = "#{node.name.downcase}_#{childnode.name.downcase}"
161
- if name == 'payment_method_data' && !childnode.elements.empty?
162
- response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first
169
+ def childnode_to_response(response, childnode)
170
+ if childnode.elements.size == 0
171
+ element_name_to_symbol(response, childnode)
163
172
  else
164
- response[name.to_sym] = childnode.text
173
+ childnode.traverse do |childnode|
174
+ element_name_to_symbol(response, childnode)
175
+ end
165
176
  end
166
177
  end
167
178
 
179
+ def element_name_to_symbol(response, childnode)
180
+ name = "#{childnode.name.downcase}"
181
+ response[name.to_sym] = childnode.text
182
+ end
183
+
168
184
  def commit(parameters)
169
185
  url = (test? ? test_url : live_url)
170
186
  response = parse(ssl_post(url, post_data(parameters)))
@@ -37,13 +37,20 @@ module ActiveMerchant #:nodoc:
37
37
  :jcb => "JCB"
38
38
  }
39
39
 
40
- AVS_CVV_CODE = {
40
+ AVS_CODE = {
41
41
  "NOTPROVIDED" => nil,
42
42
  "NOTCHECKED" => 'X',
43
43
  "MATCHED" => 'Y',
44
44
  "NOTMATCHED" => 'N'
45
45
  }
46
46
 
47
+ CVV_CODE = {
48
+ "NOTPROVIDED" => 'S',
49
+ "NOTCHECKED" => 'X',
50
+ "MATCHED" => 'M',
51
+ "NOTMATCHED" => 'N'
52
+ }
53
+
47
54
  OPTIONAL_REQUEST_FIELDS = {
48
55
  paypal_callback_url: :PayPalCallbackURL,
49
56
  basket: :Basket,
@@ -348,10 +355,10 @@ module ActiveMerchant #:nodoc:
348
355
  :test => test?,
349
356
  :authorization => authorization_from(response, parameters, action),
350
357
  :avs_result => {
351
- :street_match => AVS_CVV_CODE[ response["AddressResult"] ],
352
- :postal_match => AVS_CVV_CODE[ response["PostCodeResult"] ],
358
+ :street_match => AVS_CODE[ response["AddressResult"] ],
359
+ :postal_match => AVS_CODE[ response["PostCodeResult"] ],
353
360
  },
354
- :cvv_result => AVS_CVV_CODE[ response["CV2Result"] ]
361
+ :cvv_result => CVV_CODE[ response["CV2Result"] ]
355
362
  )
356
363
  end
357
364
 
@@ -25,7 +25,7 @@ module ActiveMerchant
25
25
  attr_accessor :ca_path
26
26
  attr_accessor :pem
27
27
  attr_accessor :pem_password
28
- attr_accessor :wiredump_device
28
+ attr_reader :wiredump_device
29
29
  attr_accessor :logger
30
30
  attr_accessor :tag
31
31
  attr_accessor :ignore_http_status
@@ -48,6 +48,11 @@ module ActiveMerchant
48
48
  @proxy_port = nil
49
49
  end
50
50
 
51
+ def wiredump_device=(device)
52
+ raise ArgumentError, "can't wiredump to frozen #{device.class}" if device && device.frozen?
53
+ @wiredump_device = device
54
+ end
55
+
51
56
  def request(method, body, headers = {})
52
57
  request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
53
58
 
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = "1.75.0"
2
+ VERSION = "1.76.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.75.0
4
+ version: 1.76.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-09 00:00:00.000000000 Z
11
+ date: 2018-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -190,6 +190,7 @@ files:
190
190
  - lib/active_merchant/billing/gateways/card_save.rb
191
191
  - lib/active_merchant/billing/gateways/card_stream.rb
192
192
  - lib/active_merchant/billing/gateways/cardknox.rb
193
+ - lib/active_merchant/billing/gateways/cardprocess.rb
193
194
  - lib/active_merchant/billing/gateways/cashnet.rb
194
195
  - lib/active_merchant/billing/gateways/cc5.rb
195
196
  - lib/active_merchant/billing/gateways/cecabank.rb
@@ -307,6 +308,7 @@ files:
307
308
  - lib/active_merchant/billing/gateways/payflow_express_uk.rb
308
309
  - lib/active_merchant/billing/gateways/payflow_uk.rb
309
310
  - lib/active_merchant/billing/gateways/payment_express.rb
311
+ - lib/active_merchant/billing/gateways/paymentez.rb
310
312
  - lib/active_merchant/billing/gateways/paymill.rb
311
313
  - lib/active_merchant/billing/gateways/paypal.rb
312
314
  - lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb