activemerchant 1.94.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +120 -1
  3. data/README.md +3 -0
  4. data/lib/active_merchant/billing/avs_result.rb +4 -5
  5. data/lib/active_merchant/billing/credit_card.rb +6 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +65 -2
  7. data/lib/active_merchant/billing/gateway.rb +10 -0
  8. data/lib/active_merchant/billing/gateways/adyen.rb +119 -34
  9. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +43 -10
  10. data/lib/active_merchant/billing/gateways/beanstream.rb +11 -6
  11. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +3 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -2
  13. data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +56 -9
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +2 -1
  16. data/lib/active_merchant/billing/gateways/cecabank.rb +7 -7
  17. data/lib/active_merchant/billing/gateways/checkout_v2.rb +37 -27
  18. data/lib/active_merchant/billing/gateways/credorax.rb +69 -4
  19. data/lib/active_merchant/billing/gateways/cyber_source.rb +51 -11
  20. data/lib/active_merchant/billing/gateways/d_local.rb +1 -1
  21. data/lib/active_merchant/billing/gateways/decidir.rb +245 -0
  22. data/lib/active_merchant/billing/gateways/epay.rb +13 -2
  23. data/lib/active_merchant/billing/gateways/eway_rapid.rb +42 -12
  24. data/lib/active_merchant/billing/gateways/fat_zebra.rb +6 -0
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +3 -7
  26. data/lib/active_merchant/billing/gateways/hps.rb +46 -1
  27. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  28. data/lib/active_merchant/billing/gateways/mastercard.rb +30 -5
  29. data/lib/active_merchant/billing/gateways/mercado_pago.rb +1 -1
  30. data/lib/active_merchant/billing/gateways/migs.rb +8 -0
  31. data/lib/active_merchant/billing/gateways/monei.rb +31 -0
  32. data/lib/active_merchant/billing/gateways/mundipagg.rb +33 -6
  33. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  34. data/lib/active_merchant/billing/gateways/nmi.rb +39 -1
  35. data/lib/active_merchant/billing/gateways/opp.rb +20 -1
  36. data/lib/active_merchant/billing/gateways/orbital.rb +60 -10
  37. data/lib/active_merchant/billing/gateways/payflow.rb +64 -14
  38. data/lib/active_merchant/billing/gateways/paymill.rb +5 -0
  39. data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
  40. data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -2
  41. data/lib/active_merchant/billing/gateways/qvalent.rb +43 -1
  42. data/lib/active_merchant/billing/gateways/realex.rb +32 -9
  43. data/lib/active_merchant/billing/gateways/redsys.rb +113 -30
  44. data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
  45. data/lib/active_merchant/billing/gateways/stripe.rb +38 -9
  46. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +271 -0
  47. data/lib/active_merchant/billing/gateways/tns.rb +10 -5
  48. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -2
  49. data/lib/active_merchant/billing/gateways/trust_commerce.rb +45 -6
  50. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +8 -5
  51. data/lib/active_merchant/billing/gateways/worldpay.rb +177 -39
  52. data/lib/active_merchant/country.rb +1 -0
  53. data/lib/active_merchant/version.rb +1 -1
  54. metadata +19 -4
@@ -6,6 +6,10 @@ module ActiveMerchant #:nodoc:
6
6
  self.display_name = 'Credorax Gateway'
7
7
  self.homepage_url = 'https://www.credorax.com/'
8
8
 
9
+ # NOTE: the IP address you run the remote tests from will need to be
10
+ # whitelisted by Credorax; contact support@credorax.com as necessary to
11
+ # request your IP address be added to the whitelist for your test
12
+ # account.
9
13
  self.test_url = 'https://intconsole.credorax.com/intenv/service/gateway'
10
14
 
11
15
  # The live URL is assigned on a per merchant basis once certification has passed
@@ -15,7 +19,7 @@ module ActiveMerchant #:nodoc:
15
19
  # ActiveMerchant::Billing::CredoraxGateway.live_url = "https://assigned-subdomain.credorax.net/crax_gate/service/gateway"
16
20
  self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway'
17
21
 
18
- self.supported_countries = %w(DE GB FR IT ES PL NL BE GR CZ PT SE HU RS AT CH BG DK FI SK NO IE HR BA AL LT MK SI LV EE ME LU MT IS AD MC LI SM)
22
+ self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB)
19
23
  self.default_currency = 'EUR'
20
24
  self.currencies_without_fractions = %w(CLP JPY KRW PYG VND)
21
25
  self.currencies_with_three_decimal_places = %w(BHD JOD KWD OMR RSD TND)
@@ -131,6 +135,7 @@ module ActiveMerchant #:nodoc:
131
135
  add_echo(post, options)
132
136
  add_submerchant_id(post, options)
133
137
  add_transaction_type(post, options)
138
+ add_processor(post, options)
134
139
 
135
140
  commit(:purchase, post)
136
141
  end
@@ -145,6 +150,7 @@ module ActiveMerchant #:nodoc:
145
150
  add_echo(post, options)
146
151
  add_submerchant_id(post, options)
147
152
  add_transaction_type(post, options)
153
+ add_processor(post, options)
148
154
 
149
155
  commit(:authorize, post)
150
156
  end
@@ -156,6 +162,7 @@ module ActiveMerchant #:nodoc:
156
162
  add_customer_data(post, options)
157
163
  add_echo(post, options)
158
164
  add_submerchant_id(post, options)
165
+ add_processor(post, options)
159
166
 
160
167
  commit(:capture, post)
161
168
  end
@@ -167,6 +174,7 @@ module ActiveMerchant #:nodoc:
167
174
  add_echo(post, options)
168
175
  add_submerchant_id(post, options)
169
176
  post[:a1] = generate_unique_id
177
+ add_processor(post, options)
170
178
 
171
179
  commit(:void, post, reference_action)
172
180
  end
@@ -178,6 +186,7 @@ module ActiveMerchant #:nodoc:
178
186
  add_customer_data(post, options)
179
187
  add_echo(post, options)
180
188
  add_submerchant_id(post, options)
189
+ add_processor(post, options)
181
190
 
182
191
  commit(:refund, post)
183
192
  end
@@ -191,6 +200,7 @@ module ActiveMerchant #:nodoc:
191
200
  add_echo(post, options)
192
201
  add_submerchant_id(post, options)
193
202
  add_transaction_type(post, options)
203
+ add_processor(post, options)
194
204
 
195
205
  commit(:credit, post)
196
206
  end
@@ -264,8 +274,56 @@ module ActiveMerchant #:nodoc:
264
274
  end
265
275
 
266
276
  def add_3d_secure(post, options)
267
- return unless options[:eci] && options[:xid]
268
- post[:i8] = "#{options[:eci]}:#{(options[:cavv] || "none")}:#{options[:xid]}"
277
+ if options[:eci] && options[:xid]
278
+ add_3d_secure_1_data(post, options)
279
+ elsif options[:execute_threed] && options[:three_ds_2]
280
+ three_ds_2_options = options[:three_ds_2]
281
+ browser_info = three_ds_2_options[:browser_info]
282
+ post[:'3ds_initiate'] = options[:three_ds_initiate] || '01'
283
+ post[:'3ds_purchasedate'] = Time.now.utc.strftime('%Y%m%d%I%M%S')
284
+ post[:'3ds_channel'] = '02'
285
+ post[:'3ds_redirect_url'] = three_ds_2_options[:notification_url]
286
+ post[:'3ds_challengewindowsize'] = options[:three_ds_challenge_window_size] || '03'
287
+ post[:'3ds_version'] = options[:three_ds_version] if options[:three_ds_version]
288
+ post[:d5] = browser_info[:user_agent]
289
+ post[:'3ds_transtype'] = options[:transaction_type] || '01'
290
+ post[:'3ds_browsertz'] = browser_info[:timezone]
291
+ post[:'3ds_browserscreenwidth'] = browser_info[:width]
292
+ post[:'3ds_browserscreenheight'] = browser_info[:height]
293
+ post[:'3ds_browsercolordepth'] = browser_info[:depth]
294
+ post[:d6] = browser_info[:language]
295
+ post[:'3ds_browserjavaenabled'] = browser_info[:java]
296
+ post[:'3ds_browseracceptheader'] = browser_info[:accept_header]
297
+ if (shipping_address = options[:shipping_address])
298
+ post[:'3ds_shipaddrstate'] = shipping_address[:state]
299
+ post[:'3ds_shipaddrpostcode'] = shipping_address[:zip]
300
+ post[:'3ds_shipaddrline2'] = shipping_address[:address2]
301
+ post[:'3ds_shipaddrline1'] = shipping_address[:address1]
302
+ post[:'3ds_shipaddrcountry'] = shipping_address[:country]
303
+ post[:'3ds_shipaddrcity'] = shipping_address[:city]
304
+ end
305
+ elsif options[:three_d_secure]
306
+ add_normalized_3d_secure_2_data(post, options)
307
+ end
308
+ end
309
+
310
+ def add_3d_secure_1_data(post, options)
311
+ post[:i8] = build_i8(options[:eci], options[:cavv], options[:xid])
312
+ end
313
+
314
+ def add_normalized_3d_secure_2_data(post, options)
315
+ three_d_secure_options = options[:three_d_secure]
316
+
317
+ post[:i8] = build_i8(
318
+ three_d_secure_options[:eci],
319
+ three_d_secure_options[:cavv]
320
+ )
321
+ post[:'3ds_version'] = three_d_secure_options[:version]
322
+ post[:'3ds_dstrxid'] = three_d_secure_options[:ds_transaction_id]
323
+ end
324
+
325
+ def build_i8(eci, cavv=nil, xid=nil)
326
+ "#{eci}:#{cavv || 'none'}:#{xid || 'none'}"
269
327
  end
270
328
 
271
329
  def add_echo(post, options)
@@ -280,6 +338,12 @@ module ActiveMerchant #:nodoc:
280
338
 
281
339
  def add_transaction_type(post, options)
282
340
  post[:a9] = options[:transaction_type] if options[:transaction_type]
341
+ post[:a2] = '3' if options.dig(:metadata, :manual_entry)
342
+ end
343
+
344
+ def add_processor(post, options)
345
+ post[:r1] = options[:processor] || 'CREDORAX'
346
+ post[:r2] = options[:processor_merchant_id] if options[:processor_merchant_id]
283
347
  end
284
348
 
285
349
  ACTIONS = {
@@ -291,7 +355,8 @@ module ActiveMerchant #:nodoc:
291
355
  credit: '6',
292
356
  purchase_void: '7',
293
357
  refund_void: '8',
294
- capture_void: '9'
358
+ capture_void: '9',
359
+ threeds_completion: '92'
295
360
  }
296
361
 
297
362
  def commit(action, params, reference_action = nil)
@@ -1,7 +1,7 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  # Initial setup instructions can be found in
4
- # http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf
4
+ # http://apps.cybersource.com/library/documentation/dev_guides/SOAP_Toolkits/SOAP_toolkits.pdf
5
5
  #
6
6
  # Important Notes
7
7
  # * For checks you can purchase and store.
@@ -24,10 +24,12 @@ module ActiveMerchant #:nodoc:
24
24
  self.test_url = 'https://ics2wstesta.ic3.com/commerce/1.x/transactionProcessor'
25
25
  self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor'
26
26
 
27
- XSD_VERSION = '1.153'
27
+ # Schema files can be found here: https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/
28
+ TEST_XSD_VERSION = '1.159'
29
+ PRODUCTION_XSD_VERSION = '1.159'
28
30
 
29
31
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro, :elo]
30
- self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB)
32
+ self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK)
31
33
 
32
34
  self.default_currency = 'USD'
33
35
  self.currencies_without_fractions = %w(JPY)
@@ -256,6 +258,7 @@ module ActiveMerchant #:nodoc:
256
258
  def build_auth_request(money, creditcard_or_reference, options)
257
259
  xml = Builder::XmlMarkup.new :indent => 2
258
260
  add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
261
+ add_threeds_2_ucaf_data(xml, creditcard_or_reference, options)
259
262
  add_decision_manager_fields(xml, options)
260
263
  add_mdd_fields(xml, options)
261
264
  add_auth_service(xml, creditcard_or_reference, options)
@@ -263,6 +266,8 @@ module ActiveMerchant #:nodoc:
263
266
  add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
264
267
  add_business_rules_data(xml, creditcard_or_reference, options)
265
268
  add_stored_credential_options(xml, options)
269
+ add_issuer_additional_data(xml, options)
270
+
266
271
  xml.target!
267
272
  end
268
273
 
@@ -291,6 +296,7 @@ module ActiveMerchant #:nodoc:
291
296
  def build_purchase_request(money, payment_method_or_reference, options)
292
297
  xml = Builder::XmlMarkup.new :indent => 2
293
298
  add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
299
+ add_threeds_2_ucaf_data(xml, payment_method_or_reference, options)
294
300
  add_decision_manager_fields(xml, options)
295
301
  add_mdd_fields(xml, options)
296
302
  if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check'
@@ -301,6 +307,8 @@ module ActiveMerchant #:nodoc:
301
307
  add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
302
308
  add_business_rules_data(xml, payment_method_or_reference, options) unless options[:pinless_debit_card]
303
309
  end
310
+ add_issuer_additional_data(xml, options)
311
+
304
312
  xml.target!
305
313
  end
306
314
 
@@ -485,6 +493,14 @@ module ActiveMerchant #:nodoc:
485
493
  end
486
494
  end
487
495
 
496
+ def add_issuer_additional_data(xml, options)
497
+ return unless options[:issuer_additional_data]
498
+
499
+ xml.tag! 'issuer' do
500
+ xml.tag! 'additionalData', options[:issuer_additional_data]
501
+ end
502
+ end
503
+
488
504
  def add_mdd_fields(xml, options)
489
505
  return unless options.keys.any? { |key| key.to_s.start_with?('mdd_field') }
490
506
 
@@ -516,19 +532,41 @@ module ActiveMerchant #:nodoc:
516
532
  add_auth_network_tokenization(xml, payment_method, options)
517
533
  else
518
534
  xml.tag! 'ccAuthService', {'run' => 'true'} do
519
- check_for_stored_cred_commerce_indicator(xml, options)
535
+ if options[:three_d_secure]
536
+ add_normalized_threeds_2_data(xml, payment_method, options)
537
+ else
538
+ indicator = options[:commerce_indicator] || stored_credential_commerce_indicator(options)
539
+ xml.tag!('commerceIndicator', indicator) if indicator
540
+ end
520
541
  end
521
542
  end
522
543
  end
523
544
 
524
- def check_for_stored_cred_commerce_indicator(xml, options)
525
- return unless options[:stored_credential]
526
- if commerce_indicator(options)
527
- xml.tag!('commerceIndicator', commerce_indicator(options))
545
+ def add_normalized_threeds_2_data(xml, payment_method, options)
546
+ threeds_2_options = options[:three_d_secure]
547
+
548
+ xml.tag!('cavv', threeds_2_options[:cavv]) if threeds_2_options[:cavv] && card_brand(payment_method).to_sym != :master
549
+ xml.tag!('cavvAlgorithm', threeds_2_options[:cavv_algorithm]) if threeds_2_options[:cavv_algorithm]
550
+ xml.tag!('paSpecificationVersion', threeds_2_options[:version]) if threeds_2_options[:version]
551
+ xml.tag!('directoryServerTransactionID', threeds_2_options[:ds_transaction_id]) if threeds_2_options[:ds_transaction_id]
552
+ xml.tag!('commerceIndicator', options[:commerce_indicator]) if options[:commerce_indicator]
553
+ xml.tag!('eciRaw', threeds_2_options[:eci]) if threeds_2_options[:eci]
554
+ xml.tag!('xid', threeds_2_options[:xid]) if threeds_2_options[:xid]
555
+ xml.tag!('veresEnrolled', threeds_2_options[:enrolled]) if threeds_2_options[:enrolled]
556
+ xml.tag!('paresStatus', threeds_2_options[:authentication_response_status]) if threeds_2_options[:authentication_response_status]
557
+ end
558
+
559
+ def add_threeds_2_ucaf_data(xml, payment_method, options)
560
+ return unless options[:three_d_secure] && card_brand(payment_method).to_sym == :master
561
+
562
+ xml.tag! 'ucaf' do
563
+ xml.tag!('authenticationData', options[:three_d_secure][:cavv])
564
+ xml.tag!('collectionIndicator', options[:collection_indicator]) if options[:collection_indicator]
528
565
  end
529
566
  end
530
567
 
531
- def commerce_indicator(options)
568
+ def stored_credential_commerce_indicator(options)
569
+ return unless options[:stored_credential]
532
570
  return if options[:stored_credential][:initial_transaction]
533
571
  case options[:stored_credential][:reason_type]
534
572
  when 'installment' then 'install'
@@ -550,7 +588,7 @@ module ActiveMerchant #:nodoc:
550
588
  xml.tag!('commerceIndicator', 'vbv')
551
589
  xml.tag!('xid', payment_method.payment_cryptogram)
552
590
  end
553
- when :mastercard
591
+ when :master
554
592
  xml.tag! 'ucaf' do
555
593
  xml.tag!('authenticationData', payment_method.payment_cryptogram)
556
594
  xml.tag!('collectionIndicator', '2')
@@ -713,6 +751,8 @@ module ActiveMerchant #:nodoc:
713
751
 
714
752
  # Where we actually build the full SOAP request using builder
715
753
  def build_request(body, options)
754
+ xsd_version = test? ? TEST_XSD_VERSION : PRODUCTION_XSD_VERSION
755
+
716
756
  xml = Builder::XmlMarkup.new :indent => 2
717
757
  xml.instruct!
718
758
  xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do
@@ -725,7 +765,7 @@ module ActiveMerchant #:nodoc:
725
765
  end
726
766
  end
727
767
  xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do
728
- xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{XSD_VERSION}"} do
768
+ xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{xsd_version}"} do
729
769
  add_merchant_data(xml, options)
730
770
  xml << body
731
771
  end
@@ -6,7 +6,7 @@ module ActiveMerchant #:nodoc:
6
6
 
7
7
  self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY', 'TR']
8
8
  self.default_currency = 'USD'
9
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
9
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja, :cabal]
10
10
 
11
11
  self.homepage_url = 'https://dlocal.com/'
12
12
  self.display_name = 'dLocal'
@@ -0,0 +1,245 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class DecidirGateway < Gateway
4
+ self.test_url = 'https://developers.decidir.com/api/v2'
5
+ self.live_url = 'https://live.decidir.com/api/v2'
6
+
7
+ self.supported_countries = ['AR']
8
+ self.money_format = :cents
9
+ self.default_currency = 'ARS'
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :naranja, :cabal]
11
+
12
+ self.homepage_url = 'http://www.decidir.com'
13
+ self.display_name = 'Decidir'
14
+
15
+ STANDARD_ERROR_CODE_MAPPING = {
16
+ 1 => STANDARD_ERROR_CODE[:call_issuer],
17
+ 2 => STANDARD_ERROR_CODE[:call_issuer],
18
+ 3 => STANDARD_ERROR_CODE[:config_error],
19
+ 4 => STANDARD_ERROR_CODE[:pickup_card],
20
+ 5 => STANDARD_ERROR_CODE[:card_declined],
21
+ 7 => STANDARD_ERROR_CODE[:pickup_card],
22
+ 12 => STANDARD_ERROR_CODE[:processing_error],
23
+ 14 => STANDARD_ERROR_CODE[:invalid_number],
24
+ 28 => STANDARD_ERROR_CODE[:processing_error],
25
+ 38 => STANDARD_ERROR_CODE[:incorrect_pin],
26
+ 39 => STANDARD_ERROR_CODE[:invalid_number],
27
+ 43 => STANDARD_ERROR_CODE[:pickup_card],
28
+ 45 => STANDARD_ERROR_CODE[:card_declined],
29
+ 46 => STANDARD_ERROR_CODE[:invalid_number],
30
+ 47 => STANDARD_ERROR_CODE[:card_declined],
31
+ 48 => STANDARD_ERROR_CODE[:card_declined],
32
+ 49 => STANDARD_ERROR_CODE[:invalid_expiry_date],
33
+ 51 => STANDARD_ERROR_CODE[:card_declined],
34
+ 53 => STANDARD_ERROR_CODE[:card_declined],
35
+ 54 => STANDARD_ERROR_CODE[:expired_card],
36
+ 55 => STANDARD_ERROR_CODE[:incorrect_pin],
37
+ 56 => STANDARD_ERROR_CODE[:card_declined],
38
+ 57 => STANDARD_ERROR_CODE[:card_declined],
39
+ 76 => STANDARD_ERROR_CODE[:call_issuer],
40
+ 96 => STANDARD_ERROR_CODE[:processing_error],
41
+ 97 => STANDARD_ERROR_CODE[:processing_error],
42
+ }
43
+
44
+ def initialize(options={})
45
+ requires!(options, :api_key)
46
+ super
47
+ @options[:preauth_mode] ||= false
48
+ end
49
+
50
+ def purchase(money, payment, options={})
51
+ raise ArgumentError, 'Purchase is not supported on Decidir gateways configured with the preauth_mode option' if @options[:preauth_mode]
52
+
53
+ post = {}
54
+ add_auth_purchase_params(post, money, payment, options)
55
+ commit(:post, 'payments', post)
56
+ end
57
+
58
+ def authorize(money, payment, options={})
59
+ raise ArgumentError, 'Authorize is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
60
+
61
+ post = {}
62
+ add_auth_purchase_params(post, money, payment, options)
63
+ commit(:post, 'payments', post)
64
+ end
65
+
66
+ def capture(money, authorization, options={})
67
+ raise ArgumentError, 'Capture is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
68
+
69
+ post = {}
70
+ add_amount(post, money, options)
71
+ commit(:put, "payments/#{authorization}", post)
72
+ end
73
+
74
+ def refund(money, authorization, options={})
75
+ post = {}
76
+ add_amount(post, money, options)
77
+ commit(:post, "payments/#{authorization}/refunds", post)
78
+ end
79
+
80
+ def void(authorization, options={})
81
+ post = {}
82
+ commit(:post, "payments/#{authorization}/refunds", post)
83
+ end
84
+
85
+ def verify(credit_card, options={})
86
+ raise ArgumentError, 'Verify is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
87
+
88
+ MultiResponse.run(:use_first_response) do |r|
89
+ r.process { authorize(100, credit_card, options) }
90
+ r.process(:ignore_result) { void(r.authorization, options) }
91
+ end
92
+ end
93
+
94
+ def supports_scrubbing?
95
+ true
96
+ end
97
+
98
+ def scrub(transcript)
99
+ transcript.
100
+ gsub(%r((apikey: )\w+)i, '\1[FILTERED]').
101
+ gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]').
102
+ gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]').
103
+ gsub(%r((\"emv_issuer_data\\\":\\\")\d+), '\1[FILTERED]')
104
+ end
105
+
106
+ private
107
+
108
+ def add_auth_purchase_params(post, money, credit_card, options)
109
+ post[:payment_method_id] = add_payment_method_id(credit_card)
110
+ post[:site_transaction_id] = options[:order_id]
111
+ post[:bin] = credit_card.number[0..5]
112
+ post[:payment_type] = options[:payment_type] || 'single'
113
+ post[:installments] = options[:installments] ? options[:installments].to_i : 1
114
+ post[:description] = options[:description] if options[:description]
115
+ post[:email] = options[:email] if options[:email]
116
+ post[:sub_payments] = []
117
+
118
+ add_invoice(post, money, options)
119
+ add_payment(post, credit_card, options)
120
+ end
121
+
122
+ def add_payment_method_id(credit_card)
123
+ if options[:payment_method_id]
124
+ options[:payment_method_id].to_i
125
+ elsif CreditCard.brand?(credit_card.number) == 'cabal'
126
+ 63
127
+ elsif CreditCard.brand?(credit_card.number) == 'naranja'
128
+ 24
129
+ else
130
+ 1
131
+ end
132
+ end
133
+
134
+ def add_invoice(post, money, options)
135
+ add_amount(post, money, options)
136
+ post[:currency] = (options[:currency] || currency(money))
137
+ end
138
+
139
+ def add_amount(post, money, options)
140
+ currency = (options[:currency] || currency(money))
141
+ post[:amount] = localized_amount(money, currency).to_i
142
+ end
143
+
144
+ def add_payment(post, credit_card, options)
145
+ card_data = {}
146
+ card_data[:card_number] = credit_card.number
147
+ card_data[:card_expiration_month] = format(credit_card.month, :two_digits)
148
+ card_data[:card_expiration_year] = format(credit_card.year, :two_digits)
149
+ card_data[:security_code] = credit_card.verification_value if credit_card.verification_value?
150
+ card_data[:card_holder_name] = credit_card.name if credit_card.name
151
+
152
+ # additional data used for Visa transactions
153
+ card_data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
154
+ card_data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
155
+
156
+ card_data[:card_holder_identification] = {}
157
+ card_data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
158
+ card_data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
159
+
160
+ post[:card_data] = card_data
161
+ end
162
+
163
+ def headers(options = {})
164
+ {
165
+ 'apikey' => @options[:api_key],
166
+ 'Content-type' => 'application/json',
167
+ 'Cache-Control' => 'no-cache'
168
+ }
169
+ end
170
+
171
+ def commit(method, endpoint, parameters, options={})
172
+ url = "#{(test? ? test_url : live_url)}/#{endpoint}"
173
+
174
+ begin
175
+ raw_response = ssl_request(method, url, post_data(parameters), headers(options))
176
+ response = parse(raw_response)
177
+ rescue ResponseError => e
178
+ raw_response = e.response.body
179
+ response = parse(raw_response)
180
+ end
181
+
182
+ success = success_from(response)
183
+ Response.new(
184
+ success,
185
+ message_from(success, response),
186
+ response,
187
+ authorization: authorization_from(response),
188
+ test: test?,
189
+ error_code: success ? nil : error_code_from(response)
190
+ )
191
+ end
192
+
193
+ def post_data(parameters = {})
194
+ parameters.to_json
195
+ end
196
+
197
+ def parse(body)
198
+ JSON.parse(body)
199
+ rescue JSON::ParserError
200
+ {
201
+ 'message' => "A non-JSON response was received from Decidir where one was expected. The raw response was:\n\n#{body}"
202
+ }
203
+ end
204
+
205
+ def message_from(success, response)
206
+ return response['status'] if success
207
+ return response['message'] if response['message']
208
+
209
+ message = nil
210
+
211
+ if error = response.dig('status_details', 'error')
212
+ message = error.dig('reason', 'description')
213
+ elsif response['error_type']
214
+ if response['validation_errors']
215
+ message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ')
216
+ end
217
+ message ||= response['error_type']
218
+ end
219
+
220
+ message
221
+ end
222
+
223
+ def success_from(response)
224
+ response['status'] == 'approved' || response['status'] == 'pre_approved'
225
+ end
226
+
227
+ def authorization_from(response)
228
+ response['id']
229
+ end
230
+
231
+ def error_code_from(response)
232
+ error_code = nil
233
+ if error = response.dig('status_details', 'error')
234
+ code = error.dig('reason', 'id')
235
+ error_code = STANDARD_ERROR_CODE_MAPPING[code]
236
+ error_code ||= error['type']
237
+ elsif response['error_type']
238
+ error_code = response['error_type'] if response['validation_errors']
239
+ end
240
+
241
+ error_code || STANDARD_ERROR_CODE[:processing_error]
242
+ end
243
+ end
244
+ end
245
+ end