activemerchant 1.123.0 → 1.124.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
  SHA256:
3
- metadata.gz: ce71317ac7aa50f5b0bcbfb6fe301f804eea7919153b9ade64cbd0d34ea7a25c
4
- data.tar.gz: a62776247abfdb56c2a0cb36e336242191b3809d989999aebb7a906f8b7afa9b
3
+ metadata.gz: 6af1ccdc1542e628e6825023b1d26d67177e6630e08b6b07896772bcf2ddde7b
4
+ data.tar.gz: ebeb390672f779ee8e660f5bac6baab8423fab0e4d8fb89d2ef626f5f5086c6f
5
5
  SHA512:
6
- metadata.gz: 83869085801337d35208b4c41cfebe89e315a9ea9590acb8b168402ab3acf398df011a201dcafd85139af7d01c3b75b35e278b18cd5bc7198655606181fd17ea
7
- data.tar.gz: 60a54eae2330f8d00959931d933025cd32f302144c37e17a5d11d35ebd3f3356bb80fc8cfb15f3b060410782a667bdae985f07442bd9af4d9a9f03e5df2faa88
6
+ metadata.gz: a64e4885c13f8b95907d15058b6644b03f2baff32a5df8e54ea1d5ee968df7f50455f7dcab3cd07d5acacd7e940504bc728dfa9b39874b9f57ba7d9011617e38
7
+ data.tar.gz: dd867effc34ba7939ac9dbacfa5583ea199dd3eb1387e34c57101a5b9ef282d2562fe4b564109db6c5157fca5cc25c0981deec960a06c64d66ab3861f0d5df9d
data/CHANGELOG CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  == HEAD
4
4
 
5
+ == Version 1.124.0 (October 28th, 2021)
6
+ * Worldpay: Add Support for Submerchant Data on Worldpay [almalee24] #4147
7
+ * dlocal: Add device_id and ip to payer object and add additional_data [aenand] #4116
8
+ * Adyen: Add network tokenization support to Adyen gateway [mymir] #4101
9
+ * Adyen: Add ACH Support [almalee24] #4105
10
+ * Moka: Support 3DS endpoint and update test url [dsmcclain] #4110
11
+ * Paysafe: Adjust profile data [meagabeth] #4112
12
+ * Stripe Payment Intents: Add support for claim_without_transaction_id field [BritneyS] #4111
13
+ * Mit: Add New Gateway [EsporaInfra] #3820
14
+ * Routex: add card type [rachelkirk] #4115
15
+ * Orbital: Scrub Payment Cryptogram [naashton] #4121
16
+ * Paysafe: Add support for airline fields [meagabeth] #4120
17
+ * Stripe and Stripe PI: Add Radar Session Option [tatsianaclifton] #4119
18
+ * PayArc: Fix billing address nil and phone_number issues [dsmcclain] #4114
19
+ * Routex: Update BIN numbers [rachelkirk] #4123
20
+ * UnionPay: Add Stripe's UnionPay test card to UnionPay BIN range #4122
21
+ * GlobalCollect: Support URL override [naashton] #4127
22
+ * PayConex: scrub bank account info from transcripts [mbreenlyles] #4128
23
+ * Moka: Remove additional transaction data from subsequent calls [naashton] #4129
24
+ * Moka: Ensure CvcNumber can be an empty string [jessiagee] #4130
25
+ * Maestro: Allow more card lengths for Luhnless bins [therufs] #4131
26
+ * Paysafe: Update supported countries [meagabeth] #4135
27
+ * Paysafe: Update field mapping for split_pay [meagabeth] #4136
28
+ * SafeCharge: Add handling for non-fractional currencies [dsmcclain] #4137
29
+ * CardStream: Support passing country_code in request [dsmcclain] #4139
30
+ * Adyen: Adjust phone number mapping [aenand] #4138
31
+ * Mit: Change how parameters are converted to JSON [tatsianaclifton] #4140
32
+ * Stripe: Add account_number to scrubbing [aenand] #4145
33
+ * Stripe PI: add name on card to billing_details [dsmcclain] #4146
34
+ * TrustCommerce: Scrub bank account info [mbreenlyles] #4149
35
+ * TransFirst: Scrub account number [aenand] #4152
36
+ * Paysafe: Update supported countries list [meagabeth] #4154
37
+ * dLocal: Update supported countries list [mbreenlyles] #4155
38
+ * SafeCharge: Add support for email field in capture [rachelkirk] #4153
39
+ * Paysafe: Remove invalid code [meagabeth] #4156
40
+ * NMI: Add descriptor fields [ajawadmirza] #4157
41
+ * Authorize.net: Add tests for scrubbing banking account info (in addition to BluePay, BridgePay, Forte, HPS, and Vanco Gateways)[aenand] #4159
42
+ * Moka: Send refund amount with decimal [dsmcclain] #4160
43
+ * GlobalCollect: Append URI to the URL [naashton] #4162
44
+ * Adyen: Add application info fields [aenand] #4163
45
+ * Adyen: Send NTID from stored cred hash [curiousepic] #4164
46
+ * Payflow: use proper case for 3DS 2.x element names [bbraschi] #4113
47
+ * Realex: Add support for stored credentials [dsmcclain] #4170
48
+ * Moka: Add support for InstallmentNumber field [dsmcclain] #4172
49
+ * Payflow: include AuthenticationStatus for 3DS 2.x [bbraschi] #4168
50
+
5
51
  == Version 1.123.0 (September 10th, 2021)
6
52
  * Paysafe: Add gateway integration [meagabeth] #4085
7
53
  * Elavon: Support recurring transactions with stored credentials [cdmackeyfree] #4086
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module ActiveMerchant #:nodoc:
2
4
  module Billing #:nodoc:
3
5
  # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object.
@@ -19,7 +21,7 @@ module ActiveMerchant #:nodoc:
19
21
  MAESTRO_BINS.any? { |bin| num.slice(0, bin.size) == bin }
20
22
  )
21
23
  },
22
- 'maestro_no_luhn' => ->(num) { num =~ /^(501080|501081|501082)\d{10}$/ },
24
+ 'maestro_no_luhn' => ->(num) { num =~ /^(501080|501081|501082)\d{6,13}$/ },
23
25
  'forbrugsforeningen' => ->(num) { num =~ /^600722\d{10}$/ },
24
26
  'sodexo' => ->(num) { num =~ /^(606071|603389|606070|606069|606068|600818)\d{10}$/ },
25
27
  'alia' => ->(num) { num =~ /^(504997|505878|601030|601073|505874)\d{10}$/ },
@@ -35,7 +37,8 @@ module ActiveMerchant #:nodoc:
35
37
  'olimpica' => ->(num) { num =~ /^636853\d{10}$/ },
36
38
  'creditel' => ->(num) { num =~ /^601933\d{10}$/ },
37
39
  'confiable' => ->(num) { num =~ /^560718\d{10}$/ },
38
- 'synchrony' => ->(num) { num =~ /^700600\d{10}$/ }
40
+ 'synchrony' => ->(num) { num =~ /^700600\d{10}$/ },
41
+ 'routex' => ->(num) { num =~ /^(700676|700678)\d{13}$/ }
39
42
  }
40
43
 
41
44
  # http://www.barclaycard.co.uk/business/files/bin_rules.pdf
@@ -198,7 +201,7 @@ module ActiveMerchant #:nodoc:
198
201
 
199
202
  # https://www.discoverglobalnetwork.com/content/dam/discover/en_us/dgn/pdfs/IPP-VAR-Enabler-Compliance.pdf
200
203
  UNIONPAY_RANGES = [
201
- 62212600..62379699, 62400000..62699999, 62820000..62889999,
204
+ 62000000..62000000, 62212600..62379699, 62400000..62699999, 62820000..62889999,
202
205
  81000000..81099999, 81100000..81319999, 81320000..81519999, 81520000..81639999, 81640000..81719999
203
206
  ]
204
207
 
@@ -60,6 +60,7 @@ module ActiveMerchant #:nodoc:
60
60
  add_splits(post, options)
61
61
  add_recurring_contract(post, options)
62
62
  add_network_transaction_reference(post, options)
63
+ add_application_info(post, options)
63
64
  commit('authorise', post, options)
64
65
  end
65
66
 
@@ -82,12 +83,13 @@ module ActiveMerchant #:nodoc:
82
83
  end
83
84
 
84
85
  def credit(money, payment, options = {})
86
+ action = 'refundWithData'
85
87
  post = init_post(options)
86
88
  add_invoice(post, money, options)
87
- add_payment(post, payment, options)
89
+ add_payment(post, payment, options, action)
88
90
  add_shopper_reference(post, options)
89
91
  add_network_transaction_reference(post, options)
90
- commit('refundWithData', post, options)
92
+ commit(action, post, options)
91
93
  end
92
94
 
93
95
  def void(authorization, options = {})
@@ -152,12 +154,19 @@ module ActiveMerchant #:nodoc:
152
154
  true
153
155
  end
154
156
 
157
+ def supports_network_tokenization?
158
+ true
159
+ end
160
+
155
161
  def scrub(transcript)
156
162
  transcript.
157
163
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
158
- gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]').
159
- gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]').
160
- gsub(%r(("cavv\\?":\\?")[^"]*)i, '\1[FILTERED]')
164
+ gsub(%r(("number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
165
+ gsub(%r(("cvc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
166
+ gsub(%r(("cavv\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
167
+ gsub(%r(("bankLocationId\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
168
+ gsub(%r(("iban\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
169
+ gsub(%r(("bankAccountNumber\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
161
170
  end
162
171
 
163
172
  private
@@ -209,7 +218,7 @@ module ActiveMerchant #:nodoc:
209
218
  }
210
219
 
211
220
  def add_extra_data(post, payment, options)
212
- post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone)
221
+ post[:telephoneNumber] = (options[:billing_address][:phone_number] if options.dig(:billing_address, :phone_number)) || (options[:billing_address][:phone] if options.dig(:billing_address, :phone)) || ''
213
222
  post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
214
223
  post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
215
224
  post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
@@ -332,7 +341,7 @@ module ActiveMerchant #:nodoc:
332
341
  post[:deliveryAddress][:stateOrProvince] = get_state(address)
333
342
  post[:deliveryAddress][:country] = address[:country] if address[:country]
334
343
  end
335
- return unless post[:card]&.kind_of?(Hash)
344
+ return unless post[:bankAccount]&.kind_of?(Hash) || post[:card]&.kind_of?(Hash)
336
345
 
337
346
  if (address = options[:billing_address] || options[:address]) && address[:country]
338
347
  post[:billingAddress] = {}
@@ -355,6 +364,7 @@ module ActiveMerchant #:nodoc:
355
364
  value: localized_amount(money, currency),
356
365
  currency: currency
357
366
  }
367
+
358
368
  post[:amount] = amount
359
369
  end
360
370
 
@@ -367,17 +377,32 @@ module ActiveMerchant #:nodoc:
367
377
  post[:modificationAmount] = amount
368
378
  end
369
379
 
370
- def add_payment(post, payment, options)
380
+ def add_payment(post, payment, options, action = nil)
371
381
  if payment.is_a?(String)
372
382
  _, _, recurring_detail_reference = payment.split('#')
373
383
  post[:selectedRecurringDetailReference] = recurring_detail_reference
374
384
  options[:recurring_contract_type] ||= 'RECURRING'
385
+ elsif payment.is_a?(Check)
386
+ add_bank_account(post, payment, options, action)
375
387
  else
376
388
  add_mpi_data_for_network_tokenization_card(post, payment) if payment.is_a?(NetworkTokenizationCreditCard)
377
389
  add_card(post, payment)
378
390
  end
379
391
  end
380
392
 
393
+ def add_bank_account(post, bank_account, options, action)
394
+ bank = {
395
+ bankAccountNumber: bank_account.account_number,
396
+ ownerName: bank_account.name,
397
+ countryCode: options[:billing_address][:country]
398
+ }
399
+
400
+ action == 'refundWithData' ? bank[:iban] = bank_account.routing_number : bank[:bankLocationId] = bank_account.routing_number
401
+
402
+ requires!(bank, :bankAccountNumber, :ownerName, :countryCode)
403
+ post[:bankAccount] = bank
404
+ end
405
+
381
406
  def add_card(post, credit_card)
382
407
  card = {
383
408
  expiryMonth: credit_card.month,
@@ -400,8 +425,10 @@ module ActiveMerchant #:nodoc:
400
425
  end
401
426
 
402
427
  def add_network_transaction_reference(post, options)
428
+ return unless ntid = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id)
429
+
403
430
  post[:additionalData] = {} unless post[:additionalData]
404
- post[:additionalData][:networkTxReference] = options[:network_transaction_id] if options[:network_transaction_id]
431
+ post[:additionalData][:networkTxReference] = ntid
405
432
  end
406
433
 
407
434
  def add_reference(post, authorization, options = {})
@@ -427,6 +454,31 @@ module ActiveMerchant #:nodoc:
427
454
  post[:recurring] = recurring
428
455
  end
429
456
 
457
+ def add_application_info(post, options)
458
+ post[:applicationInfo] ||= {}
459
+ add_external_platform(post, options)
460
+ add_merchant_application(post, options)
461
+ end
462
+
463
+ def add_external_platform(post, options)
464
+ return unless options[:externalPlatform]
465
+
466
+ post[:applicationInfo][:externalPlatform] = {
467
+ name: options[:externalPlatform][:name],
468
+ version: options[:externalPlatform][:version],
469
+ integrator: options[:externalPlatform][:integrator]
470
+ }
471
+ end
472
+
473
+ def add_merchant_application(post, options)
474
+ return unless options[:merchantApplication]
475
+
476
+ post[:applicationInfo][:merchantApplication] = {
477
+ name: options[:merchantApplication][:name],
478
+ version: options[:merchantApplication][:version]
479
+ }
480
+ end
481
+
430
482
  def add_installments(post, options)
431
483
  post[:installments] = {
432
484
  value: options[:installments]
@@ -150,23 +150,13 @@ module ActiveMerchant #:nodoc:
150
150
 
151
151
  def authorize(money, credit_card_or_reference, options = {})
152
152
  post = {}
153
- add_pair(post, :captureDelay, -1)
154
- add_amount(post, money, options)
155
- add_invoice(post, credit_card_or_reference, money, options)
156
- add_credit_card_or_reference(post, credit_card_or_reference)
157
- add_customer_data(post, options)
158
- add_remote_address(post, options)
153
+ add_auth_purchase(post, -1, money, credit_card_or_reference, options)
159
154
  commit('SALE', post)
160
155
  end
161
156
 
162
157
  def purchase(money, credit_card_or_reference, options = {})
163
158
  post = {}
164
- add_pair(post, :captureDelay, 0)
165
- add_amount(post, money, options)
166
- add_invoice(post, credit_card_or_reference, money, options)
167
- add_credit_card_or_reference(post, credit_card_or_reference)
168
- add_customer_data(post, options)
169
- add_remote_address(post, options)
159
+ add_auth_purchase(post, 0, money, credit_card_or_reference, options)
170
160
  commit('SALE', post)
171
161
  end
172
162
 
@@ -184,6 +174,7 @@ module ActiveMerchant #:nodoc:
184
174
  add_pair(post, :xref, authorization)
185
175
  add_amount(post, money, options)
186
176
  add_remote_address(post, options)
177
+ add_country_code(post, options)
187
178
  response = commit('REFUND_SALE', post)
188
179
 
189
180
  return response if response.success?
@@ -223,6 +214,16 @@ module ActiveMerchant #:nodoc:
223
214
 
224
215
  private
225
216
 
217
+ def add_auth_purchase(post, pair_value, money, credit_card_or_reference, options)
218
+ add_pair(post, :captureDelay, pair_value)
219
+ add_amount(post, money, options)
220
+ add_invoice(post, credit_card_or_reference, money, options)
221
+ add_credit_card_or_reference(post, credit_card_or_reference)
222
+ add_customer_data(post, options)
223
+ add_remote_address(post, options)
224
+ add_country_code(post, options)
225
+ end
226
+
226
227
  def add_amount(post, money, options)
227
228
  currency = options[:currency] || currency(money)
228
229
  add_pair(post, :amount, localized_amount(money, currency), required: true)
@@ -286,6 +287,10 @@ module ActiveMerchant #:nodoc:
286
287
  add_pair(post, :remoteAddress, options[:ip] || '1.1.1.1')
287
288
  end
288
289
 
290
+ def add_country_code(post, options)
291
+ post[:countryCode] = options[:country_code] || self.supported_countries[0]
292
+ end
293
+
289
294
  def normalize_line_endings(str)
290
295
  str.gsub(/%0D%0A|%0A%0D|%0D/, '%0A')
291
296
  end
@@ -309,7 +314,6 @@ module ActiveMerchant #:nodoc:
309
314
  end
310
315
 
311
316
  def commit(action, parameters)
312
- parameters.update(countryCode: self.supported_countries[0]) unless %w[CAPTURE CANCEL].include?(action)
313
317
  parameters.update(
314
318
  merchantID: @options[:login],
315
319
  action: action
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
4
4
  self.test_url = 'https://sandbox.dlocal.com'
5
5
  self.live_url = 'https://api.dlocal.com'
6
6
 
7
- self.supported_countries = %w[AR BR CL CO MX PE UY TR]
7
+ self.supported_countries = %w[AR BD BO BR CL CM CN CO CR DO EC EG GH IN ID KE MY MX MA NG PA PY PE PH SN ZA TR UY VN]
8
8
  self.default_currency = 'USD'
9
9
  self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal elo alia carnet]
10
10
 
@@ -78,6 +78,7 @@ module ActiveMerchant #:nodoc:
78
78
  add_country(post, card, options)
79
79
  add_payer(post, card, options)
80
80
  add_card(post, card, action, options)
81
+ add_additional_data(post, options)
81
82
  post[:order_id] = options[:order_id] || generate_unique_id
82
83
  post[:description] = options[:description] if options[:description]
83
84
  end
@@ -87,6 +88,10 @@ module ActiveMerchant #:nodoc:
87
88
  post[:currency] = (options[:currency] || currency(money))
88
89
  end
89
90
 
91
+ def add_additional_data(post, options)
92
+ post[:additional_risk_data] = options[:additional_data]
93
+ end
94
+
90
95
  def add_country(post, card, options)
91
96
  return unless address = options[:billing_address] || options[:address]
92
97
 
@@ -109,6 +114,8 @@ module ActiveMerchant #:nodoc:
109
114
  post[:payer][:document] = options[:document] if options[:document]
110
115
  post[:payer][:document2] = options[:document2] if options[:document2]
111
116
  post[:payer][:user_reference] = options[:user_reference] if options[:user_reference]
117
+ post[:payer][:event_uuid] = options[:device_id] if options[:device_id]
118
+ post[:payer][:onboarding_ip_address] = options[:ip] if options[:ip]
112
119
  post[:payer][:address] = add_address(post, card, options)
113
120
  end
114
121
 
@@ -1,10 +1,13 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class GlobalCollectGateway < Gateway
4
+ class_attribute :preproduction_url
5
+
4
6
  self.display_name = 'GlobalCollect'
5
7
  self.homepage_url = 'http://www.globalcollect.com/'
6
8
 
7
9
  self.test_url = 'https://eu.sandbox.api-ingenico.com'
10
+ self.preproduction_url = 'https://world.preprod.api-ingenico.com'
8
11
  self.live_url = 'https://api.globalcollect.com'
9
12
 
10
13
  self.supported_countries = %w[AD AE AG AI AL AM AO AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BW BY BZ CA CC CD CF CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HN HR HT HU ID IE IL IM IN IS IT JM JO JP KE KG KH KI KM KN KR KW KY KZ LA LB LC LI LK LR LS LT LU LV MA MC MD ME MF MG MH MK MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PL PN PS PT PW QA RE RO RS RU RW SA SB SC SE SG SH SI SJ SK SL SM SN SR ST SV SZ TC TD TG TH TJ TL TM TN TO TR TT TV TW TZ UA UG US UY UZ VC VE VG VI VN WF WS ZA ZM ZW]
@@ -260,6 +263,8 @@ module ActiveMerchant #:nodoc:
260
263
  end
261
264
 
262
265
  def url(action, authorization)
266
+ return preproduction_url + uri(action, authorization) if @options[:url_override].to_s == 'preproduction'
267
+
263
268
  (test? ? test_url : live_url) + uri(action, authorization)
264
269
  end
265
270
 
@@ -0,0 +1,260 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'digest'
4
+ require 'base64'
5
+
6
+ module ActiveMerchant #:nodoc:
7
+ module Billing #:nodoc:
8
+ class MitGateway < Gateway
9
+ self.live_url = 'https://wpy.mitec.com.mx/ModuloUtilWS/activeCDP.htm'
10
+
11
+ self.supported_countries = ['MX']
12
+ self.default_currency = 'MXN'
13
+ self.supported_cardtypes = %i[visa master]
14
+
15
+ self.homepage_url = 'http://www.centrodepagos.com.mx/'
16
+ self.display_name = 'MIT Centro de pagos'
17
+
18
+ self.money_format = :dollars
19
+
20
+ def initialize(options = {})
21
+ requires!(options, :commerce_id, :user, :api_key, :key_session)
22
+ super
23
+ end
24
+
25
+ def purchase(money, payment, options = {})
26
+ MultiResponse.run do |r|
27
+ r.process { authorize(money, payment, options) }
28
+ r.process { capture(money, r.authorization, options) }
29
+ end
30
+ end
31
+
32
+ def cipher_key
33
+ @options[:key_session]
34
+ end
35
+
36
+ def decrypt(val, keyinhex)
37
+ # Splits the first 16 bytes (the IV bytes) in array format
38
+ unpacked = val.unpack('m')
39
+ iv_base64 = unpacked[0].bytes.slice(0, 16)
40
+ # Splits the second bytes (the encrypted text bytes) these would be the
41
+ # original message
42
+ full_data = unpacked[0].bytes.slice(16, unpacked[0].bytes.length)
43
+ # Creates the engine
44
+ engine = OpenSSL::Cipher::AES128.new(:CBC)
45
+ # Set engine as decrypt mode
46
+ engine.decrypt
47
+ # Converts the key from hex to bytes
48
+ engine.key = [keyinhex].pack('H*')
49
+ # Converts the ivBase64 array into bytes
50
+ engine.iv = iv_base64.pack('c*')
51
+ # Decrypts the texts and returns the original string
52
+ engine.update(full_data.pack('c*')) + engine.final
53
+ end
54
+
55
+ def encrypt(val, keyinhex)
56
+ # Creates the engine motor
57
+ engine = OpenSSL::Cipher::AES128.new(:CBC)
58
+ # Set engine as encrypt mode
59
+ engine.encrypt
60
+ # Converts the key from hex to bytes
61
+ engine.key = [keyinhex].pack('H*')
62
+ # Generates a random iv with this settings
63
+ iv_rand = engine.random_iv
64
+ # Packs IV as a Base64 string
65
+ iv_base64 = [iv_rand].pack('m')
66
+ # Converts the packed key into bytes
67
+ unpacked = iv_base64.unpack('m')
68
+ iv = unpacked[0]
69
+ # Sets the IV into the engine
70
+ engine.iv = iv
71
+ # Encrypts the texts and stores the bytes
72
+ encrypted_bytes = engine.update(val) + engine.final
73
+ # Concatenates the (a) IV bytes and (b) the encrypted bytes then returns a
74
+ # base64 representation
75
+ [iv << encrypted_bytes].pack('m')
76
+ end
77
+
78
+ def authorize(money, payment, options = {})
79
+ post = {
80
+ operation: 'Authorize',
81
+ commerce_id: @options[:commerce_id],
82
+ user: @options[:user],
83
+ apikey: @options[:api_key],
84
+ testMode: (test? ? 'YES' : 'NO')
85
+ }
86
+ add_invoice(post, money, options)
87
+ # Payments contains the card information
88
+ add_payment(post, payment)
89
+ add_customer_data(post, options)
90
+ post[:key_session] = @options[:key_session]
91
+
92
+ post_to_json = post.to_json
93
+ post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
94
+
95
+ final_post = '<authorization>' + post_to_json_encrypt + '</authorization><dataID>' + @options[:user] + '</dataID>'
96
+ json_post = {}
97
+ json_post[:payload] = final_post
98
+ commit('sale', json_post)
99
+ end
100
+
101
+ def capture(money, authorization, options = {})
102
+ post = {
103
+ operation: 'Capture',
104
+ commerce_id: @options[:commerce_id],
105
+ user: @options[:user],
106
+ apikey: @options[:api_key],
107
+ testMode: (test? ? 'YES' : 'NO'),
108
+ transaction_id: authorization,
109
+ amount: amount(money)
110
+ }
111
+ post[:key_session] = @options[:key_session]
112
+
113
+ post_to_json = post.to_json
114
+ post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
115
+
116
+ final_post = '<capture>' + post_to_json_encrypt + '</capture><dataID>' + @options[:user] + '</dataID>'
117
+ json_post = {}
118
+ json_post[:payload] = final_post
119
+ commit('capture', json_post)
120
+ end
121
+
122
+ def refund(money, authorization, options = {})
123
+ post = {
124
+ operation: 'Refund',
125
+ commerce_id: @options[:commerce_id],
126
+ user: @options[:user],
127
+ apikey: @options[:api_key],
128
+ testMode: (test? ? 'YES' : 'NO'),
129
+ transaction_id: authorization,
130
+ auth: authorization,
131
+ amount: amount(money)
132
+ }
133
+ post[:key_session] = @options[:key_session]
134
+
135
+ post_to_json = post.to_json
136
+ post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
137
+
138
+ final_post = '<refund>' + post_to_json_encrypt + '</refund><dataID>' + @options[:user] + '</dataID>'
139
+ json_post = {}
140
+ json_post[:payload] = final_post
141
+ commit('refund', json_post)
142
+ end
143
+
144
+ def supports_scrubbing?
145
+ true
146
+ end
147
+
148
+ def scrub(transcript)
149
+ ret_transcript = transcript
150
+ auth_origin = ret_transcript[/<authorization>(.*?)<\/authorization>/, 1]
151
+ unless auth_origin.nil?
152
+ auth_decrypted = decrypt(auth_origin, @options[:key_session])
153
+ auth_json = JSON.parse(auth_decrypted)
154
+ auth_json['card'] = '[FILTERED]'
155
+ auth_json['cvv'] = '[FILTERED]'
156
+ auth_json['apikey'] = '[FILTERED]'
157
+ auth_json['key_session'] = '[FILTERED]'
158
+ auth_to_json = auth_json.to_json
159
+ auth_tagged = '<authorization>' + auth_to_json + '</authorization>'
160
+ ret_transcript = ret_transcript.gsub(/<authorization>(.*?)<\/authorization>/, auth_tagged)
161
+ end
162
+
163
+ cap_origin = ret_transcript[/<capture>(.*?)<\/capture>/, 1]
164
+ unless cap_origin.nil?
165
+ cap_decrypted = decrypt(cap_origin, @options[:key_session])
166
+ cap_json = JSON.parse(cap_decrypted)
167
+ cap_json['apikey'] = '[FILTERED]'
168
+ cap_json['key_session'] = '[FILTERED]'
169
+ cap_to_json = cap_json.to_json
170
+ cap_tagged = '<capture>' + cap_to_json + '</capture>'
171
+ ret_transcript = ret_transcript.gsub(/<capture>(.*?)<\/capture>/, cap_tagged)
172
+ end
173
+
174
+ ref_origin = ret_transcript[/<refund>(.*?)<\/refund>/, 1]
175
+ unless ref_origin.nil?
176
+ ref_decrypted = decrypt(ref_origin, @options[:key_session])
177
+ ref_json = JSON.parse(ref_decrypted)
178
+ ref_json['apikey'] = '[FILTERED]'
179
+ ref_json['key_session'] = '[FILTERED]'
180
+ ref_to_json = ref_json.to_json
181
+ ref_tagged = '<refund>' + ref_to_json + '</refund>'
182
+ ret_transcript = ret_transcript.gsub(/<refund>(.*?)<\/refund>/, ref_tagged)
183
+ end
184
+
185
+ res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
186
+ loop do
187
+ break if res_origin.nil?
188
+
189
+ resp_origin = res_origin[/#{Regexp.escape('"')}(.*?)#{Regexp.escape('"')}/m, 1]
190
+ resp_decrypted = decrypt(resp_origin, @options[:key_session])
191
+ ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1] = resp_decrypted
192
+ ret_transcript = ret_transcript.sub('reading ', 'response: ')
193
+ res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
194
+ end
195
+
196
+ ret_transcript
197
+ end
198
+
199
+ private
200
+
201
+ def add_customer_data(post, options)
202
+ post[:email] = options[:email] || 'nadie@mit.test'
203
+ end
204
+
205
+ def add_invoice(post, money, options)
206
+ post[:amount] = amount(money)
207
+ post[:currency] = (options[:currency] || currency(money))
208
+ post[:reference] = options[:order_id]
209
+ post[:transaction_id] = options[:order_id]
210
+ end
211
+
212
+ def add_payment(post, payment)
213
+ post[:installments] = 1
214
+ post[:card] = payment.number
215
+ post[:expmonth] = payment.month
216
+ post[:expyear] = payment.year
217
+ post[:cvv] = payment.verification_value
218
+ post[:name_client] = [payment.first_name, payment.last_name].join(' ')
219
+ end
220
+
221
+ def commit(action, parameters)
222
+ json_str = JSON.generate(parameters)
223
+ cleaned_str = json_str.gsub('\n', '')
224
+ raw_response = ssl_post(live_url, cleaned_str, { 'Content-type' => 'application/json' })
225
+ response = JSON.parse(decrypt(raw_response, @options[:key_session]))
226
+
227
+ Response.new(
228
+ success_from(response),
229
+ message_from(response),
230
+ response,
231
+ authorization: authorization_from(response),
232
+ avs_result: AVSResult.new(code: response['some_avs_response_key']),
233
+ cvv_result: CVVResult.new(response['some_cvv_response_key']),
234
+ test: test?,
235
+ error_code: error_code_from(response)
236
+ )
237
+ end
238
+
239
+ def success_from(response)
240
+ response['response'] == 'approved'
241
+ end
242
+
243
+ def message_from(response)
244
+ response['response']
245
+ end
246
+
247
+ def authorization_from(response)
248
+ response['reference']
249
+ end
250
+
251
+ def post_data(action, parameters = {})
252
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
253
+ end
254
+
255
+ def error_code_from(response)
256
+ response['message'].split(' -- ', 2)[0] unless success_from(response)
257
+ end
258
+ end
259
+ end
260
+ end