activemerchant 1.90.0 → 1.99.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +202 -0
  3. data/README.md +6 -2
  4. data/lib/active_merchant/billing/avs_result.rb +4 -5
  5. data/lib/active_merchant/billing/credit_card.rb +8 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +81 -5
  7. data/lib/active_merchant/billing/gateway.rb +10 -0
  8. data/lib/active_merchant/billing/gateways/adyen.rb +206 -54
  9. data/lib/active_merchant/billing/gateways/bambora_apac.rb +226 -0
  10. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +43 -10
  11. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +3 -0
  12. data/lib/active_merchant/billing/gateways/beanstream.rb +11 -6
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +10 -8
  14. data/lib/active_merchant/billing/gateways/blue_snap.rb +211 -36
  15. data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +79 -18
  17. data/lib/active_merchant/billing/gateways/card_connect.rb +6 -1
  18. data/lib/active_merchant/billing/gateways/cecabank.rb +20 -9
  19. data/lib/active_merchant/billing/gateways/checkout_v2.rb +98 -61
  20. data/lib/active_merchant/billing/gateways/credorax.rb +69 -4
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +85 -14
  22. data/lib/active_merchant/billing/gateways/d_local.rb +3 -3
  23. data/lib/active_merchant/billing/gateways/decidir.rb +245 -0
  24. data/lib/active_merchant/billing/gateways/elavon.rb +9 -0
  25. data/lib/active_merchant/billing/gateways/epay.rb +13 -2
  26. data/lib/active_merchant/billing/gateways/eway_rapid.rb +42 -12
  27. data/lib/active_merchant/billing/gateways/fat_zebra.rb +26 -7
  28. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +42 -3
  29. data/lib/active_merchant/billing/gateways/global_collect.rb +3 -7
  30. data/lib/active_merchant/billing/gateways/hps.rb +46 -1
  31. data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
  32. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  33. data/lib/active_merchant/billing/gateways/litle.rb +61 -3
  34. data/lib/active_merchant/billing/gateways/mastercard.rb +30 -5
  35. data/lib/active_merchant/billing/gateways/mercado_pago.rb +1 -1
  36. data/lib/active_merchant/billing/gateways/migs.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/monei.rb +31 -0
  38. data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
  39. data/lib/active_merchant/billing/gateways/mundipagg.rb +37 -9
  40. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
  42. data/lib/active_merchant/billing/gateways/nmi.rb +45 -5
  43. data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
  44. data/lib/active_merchant/billing/gateways/opp.rb +20 -1
  45. data/lib/active_merchant/billing/gateways/orbital.rb +92 -11
  46. data/lib/active_merchant/billing/gateways/payflow.rb +64 -14
  47. data/lib/active_merchant/billing/gateways/payment_express.rb +7 -0
  48. data/lib/active_merchant/billing/gateways/paymentez.rb +7 -11
  49. data/lib/active_merchant/billing/gateways/paymill.rb +5 -0
  50. data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
  51. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -1
  52. data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -2
  53. data/lib/active_merchant/billing/gateways/pin.rb +19 -6
  54. data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
  55. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +7 -1
  56. data/lib/active_merchant/billing/gateways/qvalent.rb +54 -1
  57. data/lib/active_merchant/billing/gateways/realex.rb +42 -5
  58. data/lib/active_merchant/billing/gateways/redsys.rb +113 -30
  59. data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
  60. data/lib/active_merchant/billing/gateways/stripe.rb +66 -34
  61. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +271 -0
  62. data/lib/active_merchant/billing/gateways/tns.rb +10 -5
  63. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +5 -5
  64. data/lib/active_merchant/billing/gateways/trust_commerce.rb +46 -6
  65. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +19 -8
  66. data/lib/active_merchant/billing/gateways/visanet_peru.rb +22 -10
  67. data/lib/active_merchant/billing/gateways/worldpay.rb +237 -34
  68. data/lib/active_merchant/country.rb +2 -1
  69. data/lib/active_merchant/version.rb +1 -1
  70. metadata +20 -4
@@ -4,18 +4,21 @@ module ActiveMerchant #:nodoc:
4
4
 
5
5
  # we recommend setting up merchant-specific endpoints.
6
6
  # https://docs.adyen.com/developers/api-manual#apiendpoints
7
- self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/v18'
8
- self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/v18'
7
+ self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/'
8
+ self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/'
9
9
 
10
10
  self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US']
11
11
  self.default_currency = 'USD'
12
- self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover]
12
+ self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
13
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja, :cabal]
13
14
 
14
15
  self.money_format = :cents
15
16
 
16
17
  self.homepage_url = 'https://www.adyen.com/'
17
18
  self.display_name = 'Adyen'
18
19
 
20
+ API_VERSION = 'v40'
21
+
19
22
  STANDARD_ERROR_CODE_MAPPING = {
20
23
  '101' => STANDARD_ERROR_CODE[:incorrect_number],
21
24
  '103' => STANDARD_ERROR_CODE[:invalid_cvc],
@@ -33,12 +36,12 @@ module ActiveMerchant #:nodoc:
33
36
  end
34
37
 
35
38
  def purchase(money, payment, options={})
36
- if options[:execute_threed]
39
+ if options[:execute_threed] || options[:threed_dynamic]
37
40
  authorize(money, payment, options)
38
41
  else
39
42
  MultiResponse.run do |r|
40
43
  r.process { authorize(money, payment, options) }
41
- r.process { capture(money, r.authorization, options) }
44
+ r.process { capture(money, r.authorization, capture_options(options)) }
42
45
  end
43
46
  end
44
47
  end
@@ -49,31 +52,40 @@ module ActiveMerchant #:nodoc:
49
52
  add_invoice(post, money, options)
50
53
  add_payment(post, payment)
51
54
  add_extra_data(post, payment, options)
52
- add_shopper_interaction(post, payment, options)
55
+ add_stored_credentials(post, payment, options)
53
56
  add_address(post, options)
54
57
  add_installments(post, options) if options[:installments]
55
- add_3ds(post, options) if options[:execute_threed]
56
- commit('authorise', post)
58
+ add_3ds(post, options)
59
+ add_3ds_authenticated_data(post, options)
60
+ commit('authorise', post, options)
57
61
  end
58
62
 
59
63
  def capture(money, authorization, options={})
60
64
  post = init_post(options)
61
65
  add_invoice_for_modification(post, money, options)
62
66
  add_reference(post, authorization, options)
63
- commit('capture', post)
67
+ commit('capture', post, options)
64
68
  end
65
69
 
66
70
  def refund(money, authorization, options={})
67
71
  post = init_post(options)
68
72
  add_invoice_for_modification(post, money, options)
69
73
  add_original_reference(post, authorization, options)
70
- commit('refund', post)
74
+ commit('refund', post, options)
71
75
  end
72
76
 
73
77
  def void(authorization, options={})
74
78
  post = init_post(options)
75
79
  add_reference(post, authorization, options)
76
- commit('cancel', post)
80
+ commit('cancel', post, options)
81
+ end
82
+
83
+ def adjust(money, authorization, options={})
84
+ post = init_post(options)
85
+ add_invoice_for_modification(post, money, options)
86
+ add_reference(post, authorization, options)
87
+ add_extra_data(post, nil, options)
88
+ commit('adjustAuthorisation', post, options)
77
89
  end
78
90
 
79
91
  def store(credit_card, options={})
@@ -82,14 +94,23 @@ module ActiveMerchant #:nodoc:
82
94
  add_invoice(post, 0, options)
83
95
  add_payment(post, credit_card)
84
96
  add_extra_data(post, credit_card, options)
97
+ add_stored_credentials(post, credit_card, options)
85
98
  add_recurring_contract(post, options)
86
99
  add_address(post, options)
87
- commit('authorise', post)
100
+
101
+ initial_response = commit('authorise', post, options)
102
+
103
+ if initial_response.success? && card_not_stored?(initial_response)
104
+ unsupported_failure_response(initial_response)
105
+ else
106
+ initial_response
107
+ end
88
108
  end
89
109
 
90
110
  def verify(credit_card, options={})
91
111
  MultiResponse.run(:use_first_response) do |r|
92
112
  r.process { authorize(0, credit_card, options) }
113
+ options[:idempotency_key] = nil
93
114
  r.process(:ignore_result) { void(r.authorization, options) }
94
115
  end
95
116
  end
@@ -110,24 +131,32 @@ module ActiveMerchant #:nodoc:
110
131
 
111
132
  AVS_MAPPING = {
112
133
  '0' => 'R', # Unknown
113
- '1' => 'A', # Address matches, postal code doesn't
114
- '2' => 'N', # Neither postal code nor address match
115
- '3' => 'R', # AVS unavailable
116
- '4' => 'E', # AVS not supported for this card type
117
- '5' => 'U', # No AVS data provided
118
- '6' => 'Z', # Postal code matches, address doesn't match
119
- '7' => 'D', # Both postal code and address match
120
- '8' => 'U', # Address not checked, postal code unknown
121
- '9' => 'B', # Address matches, postal code unknown
122
- '10' => 'N', # Address doesn't match, postal code unknown
123
- '11' => 'U', # Postal code not checked, address unknown
124
- '12' => 'B', # Address matches, postal code not checked
125
- '13' => 'U', # Address doesn't match, postal code not checked
126
- '14' => 'P', # Postal code matches, address unknown
127
- '15' => 'P', # Postal code matches, address not checked
128
- '16' => 'N', # Postal code doesn't match, address unknown
134
+ '1' => 'A', # Address matches, postal code doesn't
135
+ '2' => 'N', # Neither postal code nor address match
136
+ '3' => 'R', # AVS unavailable
137
+ '4' => 'E', # AVS not supported for this card type
138
+ '5' => 'U', # No AVS data provided
139
+ '6' => 'Z', # Postal code matches, address doesn't match
140
+ '7' => 'D', # Both postal code and address match
141
+ '8' => 'U', # Address not checked, postal code unknown
142
+ '9' => 'B', # Address matches, postal code unknown
143
+ '10' => 'N', # Address doesn't match, postal code unknown
144
+ '11' => 'U', # Postal code not checked, address unknown
145
+ '12' => 'B', # Address matches, postal code not checked
146
+ '13' => 'U', # Address doesn't match, postal code not checked
147
+ '14' => 'P', # Postal code matches, address unknown
148
+ '15' => 'P', # Postal code matches, address not checked
149
+ '16' => 'N', # Postal code doesn't match, address unknown
129
150
  '17' => 'U', # Postal code doesn't match, address not checked
130
- '18' => 'I' # Neither postal code nor address were checked
151
+ '18' => 'I', # Neither postal code nor address were checked
152
+ '19' => 'L', # Name and postal code matches.
153
+ '20' => 'V', # Name, address and postal code matches.
154
+ '21' => 'O', # Name and address matches.
155
+ '22' => 'K', # Name matches.
156
+ '23' => 'F', # Postal code matches, name doesn't match.
157
+ '24' => 'H', # Both postal code and address matches, name doesn't match.
158
+ '25' => 'T', # Address matches, name doesn't match.
159
+ '26' => 'N' # Neither postal code, address nor name matches.
131
160
  }
132
161
 
133
162
  CVC_MAPPING = {
@@ -147,9 +176,11 @@ module ActiveMerchant #:nodoc:
147
176
  }
148
177
 
149
178
  def add_extra_data(post, payment, options)
179
+ post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone)
150
180
  post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
151
181
  post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
152
182
  post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
183
+ post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
153
184
  post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
154
185
  post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
155
186
  post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
@@ -159,18 +190,29 @@ module ActiveMerchant #:nodoc:
159
190
  post[:additionalData][:overwriteBrand] = normalize(options[:overwrite_brand]) if options[:overwrite_brand]
160
191
  post[:additionalData][:customRoutingFlag] = options[:custom_routing_flag] if options[:custom_routing_flag]
161
192
  post[:additionalData]['paymentdatasource.type'] = NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
193
+ post[:additionalData][:authorisationType] = options[:authorisation_type] if options[:authorisation_type]
194
+ post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data]
195
+ post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage]
196
+ post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
197
+ post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test?
198
+ post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
162
199
  add_risk_data(post, options)
163
200
  end
164
201
 
165
202
  def add_risk_data(post, options)
166
- risk_data = {}
167
- risk_data.merge!(options[:risk_data]) if options[:risk_data]
203
+ if (risk_data = options[:risk_data])
204
+ risk_data = Hash[risk_data.map { |k, v| ["riskdata.#{k}", v] }]
205
+ post[:additionalData].merge!(risk_data)
206
+ end
207
+ end
168
208
 
169
- post[:additionalData][:riskData] = risk_data unless risk_data.empty?
209
+ def add_stored_credentials(post, payment, options)
210
+ add_shopper_interaction(post, payment, options)
211
+ add_recurring_processing_model(post, options)
170
212
  end
171
213
 
172
214
  def add_shopper_interaction(post, payment, options={})
173
- if (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
215
+ if options.dig(:stored_credential, :initial_transaction) || (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
174
216
  shopper_interaction = 'Ecommerce'
175
217
  else
176
218
  shopper_interaction = 'ContAuth'
@@ -179,32 +221,48 @@ module ActiveMerchant #:nodoc:
179
221
  post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
180
222
  end
181
223
 
224
+ def add_recurring_processing_model(post, options)
225
+ return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model]
226
+ if options.dig(:stored_credential, :reason_type) && options[:stored_credential][:reason_type] == 'unscheduled'
227
+ recurring_processing_model = 'CardOnFile'
228
+ else
229
+ recurring_processing_model = 'Subscription'
230
+ end
231
+
232
+ post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model
233
+ end
234
+
182
235
  def add_address(post, options)
183
236
  return unless post[:card]&.kind_of?(Hash)
184
237
  if (address = options[:billing_address] || options[:address]) && address[:country]
185
- post[:card][:billingAddress] = {}
186
- post[:card][:billingAddress][:street] = address[:address1] || 'N/A'
187
- post[:card][:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A'
188
- post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip]
189
- post[:card][:billingAddress][:city] = address[:city] || 'N/A'
190
- post[:card][:billingAddress][:stateOrProvince] = address[:state] if address[:state]
191
- post[:card][:billingAddress][:country] = address[:country] if address[:country]
238
+ post[:billingAddress] = {}
239
+ post[:billingAddress][:street] = address[:address1] || 'NA'
240
+ post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
241
+ post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
242
+ post[:billingAddress][:city] = address[:city] || 'NA'
243
+ post[:billingAddress][:stateOrProvince] = get_state(address)
244
+ post[:billingAddress][:country] = address[:country] if address[:country]
192
245
  end
193
246
  end
194
247
 
248
+ def get_state(address)
249
+ address[:state] && !address[:state].blank? ? address[:state] : 'NA'
250
+ end
251
+
195
252
  def add_invoice(post, money, options)
253
+ currency = options[:currency] || currency(money)
196
254
  amount = {
197
- value: amount(money),
198
- currency: options[:currency] || currency(money)
255
+ value: localized_amount(money, currency),
256
+ currency: currency
199
257
  }
200
258
  post[:amount] = amount
201
- post[:recurringProcessingModel] = options[:recurring_processing_model] if options[:recurring_processing_model]
202
259
  end
203
260
 
204
261
  def add_invoice_for_modification(post, money, options)
262
+ currency = options[:currency] || currency(money)
205
263
  amount = {
206
- value: amount(money),
207
- currency: options[:currency] || currency(money)
264
+ value: localized_amount(money, currency),
265
+ currency: currency
208
266
  }
209
267
  post[:modificationAmount] = amount
210
268
  end
@@ -235,6 +293,11 @@ module ActiveMerchant #:nodoc:
235
293
  post[:card] = card
236
294
  end
237
295
 
296
+ def capture_options(options)
297
+ return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key]
298
+ options
299
+ end
300
+
238
301
  def add_reference(post, authorization, options = {})
239
302
  _, psp_reference, _ = authorization.split('#')
240
303
  post[:originalReference] = single_reference(authorization) || psp_reference
@@ -272,8 +335,62 @@ module ActiveMerchant #:nodoc:
272
335
  end
273
336
 
274
337
  def add_3ds(post, options)
275
- post[:additionalData] = { executeThreeD: 'true' }
276
- post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
338
+ if three_ds_2_options = options[:three_ds_2]
339
+ device_channel = three_ds_2_options[:channel]
340
+ if device_channel == 'app'
341
+ post[:threeDS2RequestData] = { deviceChannel: device_channel }
342
+ else
343
+ add_browser_info(three_ds_2_options[:browser_info], post)
344
+ post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] }
345
+ end
346
+
347
+ if options.has_key?(:execute_threed)
348
+ post[:additionalData][:executeThreeD] = options[:execute_threed]
349
+ post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption]
350
+ end
351
+ else
352
+ return unless options[:execute_threed] || options[:threed_dynamic]
353
+ post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
354
+ post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed]
355
+ end
356
+ end
357
+
358
+ def add_3ds_authenticated_data(post, options)
359
+ if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid]
360
+ add_3ds1_authenticated_data(post, options)
361
+ elsif options[:three_d_secure]
362
+ add_3ds2_authenticated_data(post, options)
363
+ end
364
+ end
365
+
366
+ def add_3ds1_authenticated_data(post, options)
367
+ three_d_secure_options = options[:three_d_secure]
368
+ post[:mpiData] = {
369
+ cavv: three_d_secure_options[:cavv],
370
+ cavvAlgorithm: three_d_secure_options[:cavv_algorithm],
371
+ eci: three_d_secure_options[:eci],
372
+ xid: three_d_secure_options[:xid],
373
+ directoryResponse: three_d_secure_options[:directory_response_status],
374
+ authenticationResponse: three_d_secure_options[:authentication_response_status]
375
+ }
376
+ end
377
+
378
+ def add_3ds2_authenticated_data(post, options)
379
+ three_d_secure_options = options[:three_d_secure]
380
+ # If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes.
381
+ if(three_d_secure_options[:authentication_response_status].nil?)
382
+ authentication_response = three_d_secure_options[:directory_response_status]
383
+ else
384
+ authentication_response = three_d_secure_options[:authentication_response_status]
385
+ end
386
+ post[:mpiData] = {
387
+ threeDSVersion: three_d_secure_options[:version],
388
+ eci: three_d_secure_options[:eci],
389
+ cavv: three_d_secure_options[:cavv],
390
+ dsTransID: three_d_secure_options[:ds_transaction_id],
391
+ directoryResponse: three_d_secure_options[:directory_response_status],
392
+ authenticationResponse: authentication_response
393
+ }
277
394
  end
278
395
 
279
396
  def parse(body)
@@ -281,9 +398,9 @@ module ActiveMerchant #:nodoc:
281
398
  JSON.parse(body)
282
399
  end
283
400
 
284
- def commit(action, parameters)
401
+ def commit(action, parameters, options)
285
402
  begin
286
- raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers)
403
+ raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers(options))
287
404
  response = parse(raw_response)
288
405
  rescue ResponseError => e
289
406
  raw_response = e.response.body
@@ -312,11 +429,11 @@ module ActiveMerchant #:nodoc:
312
429
 
313
430
  def url
314
431
  if test?
315
- test_url
432
+ "#{test_url}#{API_VERSION}"
316
433
  elsif @options[:subdomain]
317
- "https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/Payment/v18"
434
+ "https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/Payment/#{API_VERSION}"
318
435
  else
319
- live_url
436
+ "#{live_url}#{API_VERSION}"
320
437
  end
321
438
  end
322
439
 
@@ -324,11 +441,13 @@ module ActiveMerchant #:nodoc:
324
441
  Base64.strict_encode64("#{@username}:#{@password}")
325
442
  end
326
443
 
327
- def request_headers
328
- {
444
+ def request_headers(options)
445
+ headers = {
329
446
  'Content-Type' => 'application/json',
330
447
  'Authorization' => "Basic #{basic_auth}"
331
448
  }
449
+ headers['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
450
+ headers
332
451
  end
333
452
 
334
453
  def success_from(action, response)
@@ -337,6 +456,8 @@ module ActiveMerchant #:nodoc:
337
456
  ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode'])
338
457
  when 'capture', 'refund', 'cancel'
339
458
  response['response'] == "[#{action}-received]"
459
+ when 'adjustAuthorisation'
460
+ response['response'] == 'Authorised' || response['response'] == '[adjustAuthorisation-received]'
340
461
  else
341
462
  false
342
463
  end
@@ -375,6 +496,37 @@ module ActiveMerchant #:nodoc:
375
496
  def error_code_from(response)
376
497
  STANDARD_ERROR_CODE_MAPPING[response['errorCode']]
377
498
  end
499
+
500
+ def add_browser_info(browser_info, post)
501
+ return unless browser_info
502
+ post[:browserInfo] = {
503
+ acceptHeader: browser_info[:accept_header],
504
+ colorDepth: browser_info[:depth],
505
+ javaEnabled: browser_info[:java],
506
+ language: browser_info[:language],
507
+ screenHeight: browser_info[:height],
508
+ screenWidth: browser_info[:width],
509
+ timeZoneOffset: browser_info[:timezone],
510
+ userAgent: browser_info[:user_agent]
511
+ }
512
+ end
513
+
514
+ def unsupported_failure_response(initial_response)
515
+ Response.new(
516
+ false,
517
+ 'Recurring transactions are not supported for this card type.',
518
+ initial_response.params,
519
+ authorization: initial_response.authorization,
520
+ test: initial_response.test,
521
+ error_code: initial_response.error_code,
522
+ avs_result: initial_response.avs_result,
523
+ cvv_result: initial_response.cvv_result[:code]
524
+ )
525
+ end
526
+
527
+ def card_not_stored?(response)
528
+ response.authorization ? response.authorization.split('#')[2].nil? : true
529
+ end
378
530
  end
379
531
  end
380
532
  end
@@ -0,0 +1,226 @@
1
+ require 'nokogiri'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class BamboraApacGateway < Gateway
6
+ self.live_url = 'https://www.bambora.co.nz/interface/api'
7
+ self.test_url = 'https://demo.bambora.co.nz/interface/api'
8
+
9
+ self.supported_countries = ['AU', 'NZ']
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
11
+
12
+ self.homepage_url = 'http://www.bambora.com/'
13
+ self.display_name = 'Bambora Asia-Pacific'
14
+
15
+ self.money_format = :cents
16
+
17
+ STANDARD_ERROR_CODE_MAPPING = {
18
+ '05' => STANDARD_ERROR_CODE[:card_declined],
19
+ '06' => STANDARD_ERROR_CODE[:processing_error],
20
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
21
+ '54' => STANDARD_ERROR_CODE[:expired_card],
22
+ }
23
+
24
+ def initialize(options={})
25
+ requires!(options, :username, :password)
26
+ super
27
+ end
28
+
29
+ def purchase(money, payment, options={})
30
+ commit('SubmitSinglePayment') do |xml|
31
+ xml.Transaction do
32
+ xml.CustRef options[:order_id]
33
+ add_amount(xml, money)
34
+ xml.TrnType '1'
35
+ add_payment(xml, payment)
36
+ add_credentials(xml, options)
37
+ xml.TrnSource options[:ip]
38
+ end
39
+ end
40
+ end
41
+
42
+ def authorize(money, payment, options={})
43
+ commit('SubmitSinglePayment') do |xml|
44
+ xml.Transaction do
45
+ xml.CustRef options[:order_id]
46
+ add_amount(xml, money)
47
+ xml.TrnType '2'
48
+ add_payment(xml, payment)
49
+ add_credentials(xml, options)
50
+ xml.TrnSource options[:ip]
51
+ end
52
+ end
53
+ end
54
+
55
+ def capture(money, authorization, options={})
56
+ commit('SubmitSingleCapture') do |xml|
57
+ xml.Capture do
58
+ xml.Receipt authorization
59
+ add_amount(xml, money)
60
+ add_credentials(xml, options)
61
+ end
62
+ end
63
+ end
64
+
65
+ def refund(money, authorization, options={})
66
+ commit('SubmitSingleRefund') do |xml|
67
+ xml.Refund do
68
+ xml.Receipt authorization
69
+ add_amount(xml, money)
70
+ add_credentials(xml, options)
71
+ end
72
+ end
73
+ end
74
+
75
+ def void(money, authorization, options={})
76
+ commit('SubmitSingleVoid') do |xml|
77
+ xml.Void do
78
+ xml.Receipt authorization
79
+ add_amount(xml, money)
80
+ add_credentials(xml, options)
81
+ end
82
+ end
83
+ end
84
+
85
+ def store(payment, options={})
86
+ commit('TokeniseCreditCard') do |xml|
87
+ xml.TokeniseCreditCard do
88
+ xml.CardNumber payment.number
89
+ xml.ExpM format(payment.month, :two_digits)
90
+ xml.ExpY format(payment.year, :four_digits)
91
+ xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
92
+ xml.UserName @options[:username]
93
+ xml.Password @options[:password]
94
+ end
95
+ end
96
+ end
97
+
98
+ def supports_scrubbing?
99
+ true
100
+ end
101
+
102
+ def scrub(transcript)
103
+ transcript.
104
+ gsub(%r((<CardNumber>)[^<]+(<))i, '\1[FILTERED]\2').
105
+ gsub(%r((<CVN>)[^<]+(<))i, '\1[FILTERED]\2').
106
+ gsub(%r((<Password>)[^<]+(<))i, '\1[FILTERED]\2')
107
+ end
108
+
109
+ private
110
+
111
+ def add_credentials(xml, options)
112
+ xml.AccountNumber options[:account_number] if options[:account_number]
113
+ xml.Security do
114
+ xml.UserName @options[:username]
115
+ xml.Password @options[:password]
116
+ end
117
+ end
118
+
119
+ def add_amount(xml, money)
120
+ xml.Amount amount(money)
121
+ end
122
+
123
+ def add_payment(xml, payment)
124
+ if payment.is_a?(String)
125
+ add_token(xml, payment)
126
+ else
127
+ add_credit_card(xml, payment)
128
+ end
129
+ end
130
+
131
+ def add_token(xml, payment)
132
+ xml.CreditCard do
133
+ xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
134
+ xml.CardNumber payment
135
+ end
136
+ end
137
+
138
+ def add_credit_card(xml, payment)
139
+ xml.CreditCard :Registered => 'False' do
140
+ xml.CardNumber payment.number
141
+ xml.ExpM format(payment.month, :two_digits)
142
+ xml.ExpY format(payment.year, :four_digits)
143
+ xml.CVN payment.verification_value
144
+ xml.CardHolderName payment.name
145
+ end
146
+ end
147
+
148
+ def parse(body)
149
+ element = Nokogiri::XML(body).root.first_element_child.first_element_child
150
+
151
+ response = {}
152
+ doc = Nokogiri::XML(element)
153
+ doc.root.elements.each do |e|
154
+ response[e.name.underscore.to_sym] = e.inner_text
155
+ end
156
+ response
157
+ end
158
+
159
+ def commit(action, &block)
160
+ headers = {
161
+ 'Content-Type' => 'text/xml; charset=utf-8',
162
+ 'SOAPAction' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}/#{action}"
163
+ }
164
+ response = parse(ssl_post("#{commit_url}/#{endpoint(action)}.asmx", new_submit_xml(action, &block), headers))
165
+
166
+ Response.new(
167
+ success_from(response),
168
+ message_from(response),
169
+ response,
170
+ authorization: authorization_from(response),
171
+ error_code: error_code_from(response),
172
+ test: test?
173
+ )
174
+ end
175
+
176
+ def new_submit_xml(action)
177
+ xml = Builder::XmlMarkup.new(indent: 2)
178
+ xml.instruct!
179
+ xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
180
+ xml.soap :Body do
181
+ xml.__send__(action, 'xmlns' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}") do
182
+ if action == 'TokeniseCreditCard'
183
+ xml.tokeniseCreditCardXML do
184
+ inner_xml = Builder::XmlMarkup.new(indent: 2)
185
+ yield(inner_xml)
186
+ xml.cdata!(inner_xml.target!)
187
+ end
188
+ else
189
+ xml.trnXML do
190
+ inner_xml = Builder::XmlMarkup.new(indent: 2)
191
+ yield(inner_xml)
192
+ xml.cdata!(inner_xml.target!)
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ xml.target!
199
+ end
200
+
201
+ def endpoint(action)
202
+ action == 'TokeniseCreditCard' ? 'sipp' : 'dts'
203
+ end
204
+
205
+ def commit_url
206
+ test? ? test_url : live_url
207
+ end
208
+
209
+ def success_from(response)
210
+ response[:response_code] == '0' || response[:return_value] == '0'
211
+ end
212
+
213
+ def error_code_from(response)
214
+ STANDARD_ERROR_CODE_MAPPING[response[:declined_code]]
215
+ end
216
+
217
+ def message_from(response)
218
+ success_from(response) ? 'Succeeded' : response[:declined_message]
219
+ end
220
+
221
+ def authorization_from(response)
222
+ response[:receipt] || response[:token]
223
+ end
224
+ end
225
+ end
226
+ end