activemerchant 1.123.0 → 1.124.0

Sign up to get free protection for your applications and to get access to all the features.
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