activemerchant 1.123.0 → 1.126.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +206 -0
  3. data/lib/active_merchant/billing/check.rb +5 -8
  4. data/lib/active_merchant/billing/credit_card.rb +10 -0
  5. data/lib/active_merchant/billing/credit_card_methods.rb +18 -3
  6. data/lib/active_merchant/billing/gateway.rb +3 -2
  7. data/lib/active_merchant/billing/gateways/adyen.rb +66 -11
  8. data/lib/active_merchant/billing/gateways/airwallex.rb +341 -0
  9. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +2 -1
  10. data/lib/active_merchant/billing/gateways/blue_pay.rb +1 -1
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +31 -21
  12. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +6 -1
  13. data/lib/active_merchant/billing/gateways/braintree/token_nonce.rb +113 -0
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +87 -15
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +1 -1
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  17. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  18. data/lib/active_merchant/billing/gateways/checkout_v2.rb +34 -5
  19. data/lib/active_merchant/billing/gateways/credorax.rb +10 -0
  20. data/lib/active_merchant/billing/gateways/cyber_source.rb +24 -36
  21. data/lib/active_merchant/billing/gateways/d_local.rb +61 -6
  22. data/lib/active_merchant/billing/gateways/decidir.rb +17 -1
  23. data/lib/active_merchant/billing/gateways/decidir_plus.rb +344 -0
  24. data/lib/active_merchant/billing/gateways/ebanx.rb +19 -3
  25. data/lib/active_merchant/billing/gateways/elavon.rb +6 -3
  26. data/lib/active_merchant/billing/gateways/element.rb +20 -2
  27. data/lib/active_merchant/billing/gateways/global_collect.rb +137 -32
  28. data/lib/active_merchant/billing/gateways/ipg.rb +415 -0
  29. data/lib/active_merchant/billing/gateways/kushki.rb +7 -0
  30. data/lib/active_merchant/billing/gateways/litle.rb +93 -1
  31. data/lib/active_merchant/billing/gateways/mercado_pago.rb +3 -1
  32. data/lib/active_merchant/billing/gateways/mit.rb +260 -0
  33. data/lib/active_merchant/billing/gateways/moka.rb +24 -11
  34. data/lib/active_merchant/billing/gateways/moneris.rb +35 -8
  35. data/lib/active_merchant/billing/gateways/mundipagg.rb +8 -6
  36. data/lib/active_merchant/billing/gateways/nmi.rb +27 -8
  37. data/lib/active_merchant/billing/gateways/orbital.rb +357 -319
  38. data/lib/active_merchant/billing/gateways/pay_arc.rb +9 -7
  39. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  40. data/lib/active_merchant/billing/gateways/pay_trace.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/payflow.rb +76 -6
  42. data/lib/active_merchant/billing/gateways/paymentez.rb +35 -9
  43. data/lib/active_merchant/billing/gateways/paysafe.rb +155 -34
  44. data/lib/active_merchant/billing/gateways/payu_latam.rb +31 -16
  45. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  46. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  47. data/lib/active_merchant/billing/gateways/priority.rb +369 -0
  48. data/lib/active_merchant/billing/gateways/rapyd.rb +258 -0
  49. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  50. data/lib/active_merchant/billing/gateways/safe_charge.rb +7 -6
  51. data/lib/active_merchant/billing/gateways/simetrik.rb +362 -0
  52. data/lib/active_merchant/billing/gateways/stripe.rb +30 -8
  53. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +175 -72
  54. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  55. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  56. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +20 -6
  57. data/lib/active_merchant/billing/gateways/visanet_peru.rb +6 -2
  58. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  59. data/lib/active_merchant/billing/gateways/worldpay.rb +196 -64
  60. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  61. data/lib/active_merchant/billing/response.rb +4 -0
  62. data/lib/active_merchant/version.rb +1 -1
  63. metadata +11 -2
@@ -82,6 +82,7 @@ module ActiveMerchant #:nodoc:
82
82
  add_invoice(action, post, amount, options)
83
83
  add_payment_method(post, payment_method, options)
84
84
  add_full_response(post, options)
85
+ add_metadata(post, options)
85
86
 
86
87
  commit(action, post)
87
88
  end
@@ -94,6 +95,7 @@ module ActiveMerchant #:nodoc:
94
95
  add_invoice(action, post, amount, options)
95
96
  add_contact_details(post, options[:contact_details]) if options[:contact_details]
96
97
  add_full_response(post, options)
98
+ add_metadata(post, options)
97
99
 
98
100
  commit(action, post)
99
101
  end
@@ -105,6 +107,7 @@ module ActiveMerchant #:nodoc:
105
107
  add_reference(post, authorization, options)
106
108
  add_invoice(action, post, amount, options)
107
109
  add_full_response(post, options)
110
+ add_metadata(post, options)
108
111
 
109
112
  commit(action, post)
110
113
  end
@@ -177,6 +180,10 @@ module ActiveMerchant #:nodoc:
177
180
  post[:fullResponse] = options[:full_response].to_s.casecmp('true').zero? if options[:full_response]
178
181
  end
179
182
 
183
+ def add_metadata(post, options)
184
+ post[:metadata] = options[:metadata] if options[:metadata]
185
+ end
186
+
180
187
  ENDPOINT = {
181
188
  'tokenize' => 'tokens',
182
189
  'charge' => 'charges',
@@ -39,6 +39,96 @@ module ActiveMerchant #:nodoc:
39
39
  check?(payment_method) ? commit(:echeckSales, request, money) : commit(:sale, request, money)
40
40
  end
41
41
 
42
+ def add_level_two_data(doc, payment_method, options = {})
43
+ level_2_data = options[:level_2_data]
44
+ if level_2_data
45
+ doc.enhancedData do
46
+ case payment_method.brand
47
+ when 'visa'
48
+ doc.salesTax(level_2_data[:sales_tax]) if level_2_data[:sales_tax]
49
+ when 'master'
50
+ doc.customerReference(level_2_data[:customer_code]) if level_2_data[:customer_code]
51
+ doc.salesTax(level_2_data[:total_tax_amount]) if level_2_data[:total_tax_amount]
52
+ doc.detailTax do
53
+ doc.taxIncludedInTotal(level_2_data[:tax_included_in_total]) if level_2_data[:tax_included_in_total]
54
+ doc.taxAmount(level_2_data[:tax_amount]) if level_2_data[:tax_amount]
55
+ doc.cardAcceptorTaxId(level_2_data[:card_acceptor_tax_id]) if level_2_data[:card_acceptor_tax_id]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def add_level_three_data(doc, payment_method, options = {})
63
+ level_3_data = options[:level_3_data]
64
+ if level_3_data
65
+ doc.enhancedData do
66
+ case payment_method.brand
67
+ when 'visa'
68
+ add_level_three_information_tags_visa(doc, payment_method, level_3_data)
69
+ when 'master'
70
+ add_level_three_information_tags_master(doc, payment_method, level_3_data)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def add_level_three_information_tags_visa(doc, payment_method, level_3_data)
77
+ doc.discountAmount(level_3_data[:discount_amount]) if level_3_data[:discount_amount]
78
+ doc.shippingAmount(level_3_data[:shipping_amount]) if level_3_data[:shipping_amount]
79
+ doc.dutyAmount(level_3_data[:duty_amount]) if level_3_data[:duty_amount]
80
+ doc.detailTax do
81
+ doc.taxIncludedInTotal(level_3_data[:tax_included_in_total]) if level_3_data[:tax_included_in_total]
82
+ doc.taxAmount(level_3_data[:tax_amount]) if level_3_data[:tax_amount]
83
+ doc.taxRate(level_3_data[:tax_rate]) if level_3_data[:tax_rate]
84
+ doc.taxTypeIdentifier(level_3_data[:tax_type_identifier]) if level_3_data[:tax_type_identifier]
85
+ doc.cardAcceptorTaxId(level_3_data[:card_acceptor_tax_id]) if level_3_data[:card_acceptor_tax_id]
86
+ end
87
+ add_line_item_information_for_level_three_visa(doc, payment_method, level_3_data)
88
+ end
89
+
90
+ def add_level_three_information_tags_master(doc, payment_method, level_3_data)
91
+ doc.customerReference :customerReference, level_3_data[:customer_code] if level_3_data[:customer_code]
92
+ doc.salesTax(level_3_data[:total_tax_amount]) if level_3_data[:total_tax_amount]
93
+ doc.detailTax do
94
+ doc.taxIncludedInTotal(level_3_data[:tax_included_in_total]) if level_3_data[:tax_included_in_total]
95
+ doc.taxAmount(level_3_data[:tax_amount]) if level_3_data[:tax_amount]
96
+ doc.cardAcceptorTaxId :cardAcceptorTaxId, level_3_data[:card_acceptor_tax_id] if level_3_data[:card_acceptor_tax_id]
97
+ end
98
+ doc.lineItemData do
99
+ level_3_data[:line_items].each do |line_item|
100
+ doc.itemDescription(line_item[:item_description]) if line_item[:item_description]
101
+ doc.productCode(line_item[:product_code]) if line_item[:product_code]
102
+ doc.quantity(line_item[:quantity]) if line_item[:quantity]
103
+ doc.unitOfMeasure(line_item[:unit_of_measure]) if line_item[:unit_of_measure]
104
+ doc.lineItemTotal(line_item[:line_item_total]) if line_item[:line_item_total]
105
+ end
106
+ end
107
+ end
108
+
109
+ def add_line_item_information_for_level_three_visa(doc, payment_method, level_3_data)
110
+ doc.lineItemData do
111
+ level_3_data[:line_items].each do |line_item|
112
+ doc.itemSequenceNumber(line_item[:item_sequence_number]) if line_item[:item_sequence_number]
113
+ doc.commodityCode(line_item[:commodity_code]) if line_item[:commodity_code]
114
+ doc.itemDescription(line_item[:item_description]) if line_item[:item_description]
115
+ doc.productCode(line_item[:product_code]) if line_item[:product_code]
116
+ doc.quantity(line_item[:quantity]) if line_item[:quantity]
117
+ doc.unitOfMeasure(line_item[:unit_of_measure]) if line_item[:unit_of_measure]
118
+ doc.taxAmount(line_item[:tax_amount]) if line_item[:tax_amount]
119
+ doc.itemDiscountAmount(line_item[:discount_per_line_item]) unless line_item[:discount_per_line_item] < 0
120
+ doc.unitCost(line_item[:unit_cost]) unless line_item[:unit_cost] < 0
121
+ doc.detailTax do
122
+ doc.taxIncludedInTotal(line_item[:tax_included_in_total]) if line_item[:tax_included_in_total]
123
+ doc.taxAmount(line_item[:tax_amount]) if line_item[:tax_amount]
124
+ doc.taxRate(line_item[:tax_rate]) if line_item[:tax_rate]
125
+ doc.taxTypeIdentifier(line_item[:tax_type_identifier]) if line_item[:tax_type_identifier]
126
+ doc.cardAcceptorTaxId(line_item[:card_acceptor_tax_id]) if line_item[:card_acceptor_tax_id]
127
+ end
128
+ end
129
+ end
130
+ end
131
+
42
132
  def authorize(money, payment_method, options = {})
43
133
  request = build_xml_request do |doc|
44
134
  add_authentication(doc)
@@ -225,6 +315,8 @@ module ActiveMerchant #:nodoc:
225
315
  add_payment_method(doc, payment_method, options)
226
316
  add_pos(doc, payment_method)
227
317
  add_descriptor(doc, options)
318
+ add_level_two_data(doc, payment_method, options)
319
+ add_level_three_data(doc, payment_method, options)
228
320
  add_merchant_data(doc, options)
229
321
  add_debt_repayment(doc, options)
230
322
  add_stored_credential_params(doc, options)
@@ -386,7 +478,7 @@ module ActiveMerchant #:nodoc:
386
478
  doc.orderSource(order_source)
387
479
  elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
388
480
  doc.orderSource('applepay')
389
- elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :android_pay
481
+ elsif payment_method.is_a?(NetworkTokenizationCreditCard) && %i[google_pay android_pay].include?(payment_method.source)
390
482
  doc.orderSource('androidpay')
391
483
  elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present?
392
484
  doc.orderSource('retail')
@@ -53,8 +53,10 @@ module ActiveMerchant #:nodoc:
53
53
  end
54
54
 
55
55
  def verify(credit_card, options = {})
56
+ verify_amount = 100
57
+ verify_amount = options[:amount].to_i if options[:amount]
56
58
  MultiResponse.run(:use_first_response) do |r|
57
- r.process { authorize(100, credit_card, options) }
59
+ r.process { authorize(verify_amount, credit_card, options) }
58
60
  r.process(:ignore_result) { void(r.authorization, options) }
59
61
  end
60
62
  end
@@ -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
@@ -1,7 +1,7 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class MokaGateway < Gateway
4
- self.test_url = 'https://service.testmoka.com'
4
+ self.test_url = 'https://service.refmoka.com'
5
5
  self.live_url = 'https://service.moka.com'
6
6
 
7
7
  self.supported_countries = %w[GB TR US]
@@ -55,8 +55,10 @@ module ActiveMerchant #:nodoc:
55
55
  post[:PaymentDealerRequest] = {}
56
56
  options[:pre_auth] = 0
57
57
  add_auth_purchase(post, money, payment, options)
58
+ add_3ds_data(post, options) if options[:execute_threed]
58
59
 
59
- commit('purchase', post)
60
+ action = options[:execute_threed] ? 'three_ds_purchase' : 'purchase'
61
+ commit(action, post)
60
62
  end
61
63
 
62
64
  def authorize(money, payment, options = {})
@@ -64,8 +66,10 @@ module ActiveMerchant #:nodoc:
64
66
  post[:PaymentDealerRequest] = {}
65
67
  options[:pre_auth] = 1
66
68
  add_auth_purchase(post, money, payment, options)
69
+ add_3ds_data(post, options) if options[:execute_threed]
67
70
 
68
- commit('authorize', post)
71
+ action = options[:execute_threed] ? 'three_ds_authorize' : 'authorize'
72
+ commit(action, post)
69
73
  end
70
74
 
71
75
  def capture(money, authorization, options = {})
@@ -73,7 +77,7 @@ module ActiveMerchant #:nodoc:
73
77
  post[:PaymentDealerRequest] = {}
74
78
  add_payment_dealer_authentication(post)
75
79
  add_transaction_reference(post, authorization)
76
- add_additional_transaction_data(post, options)
80
+ add_invoice(post, money, options)
77
81
 
78
82
  commit('capture', post)
79
83
  end
@@ -83,7 +87,6 @@ module ActiveMerchant #:nodoc:
83
87
  post[:PaymentDealerRequest] = {}
84
88
  add_payment_dealer_authentication(post)
85
89
  add_transaction_reference(post, authorization)
86
- add_additional_transaction_data(post, options)
87
90
  add_void_refund_reason(post)
88
91
  add_amount(post, money)
89
92
 
@@ -95,7 +98,6 @@ module ActiveMerchant #:nodoc:
95
98
  post[:PaymentDealerRequest] = {}
96
99
  add_payment_dealer_authentication(post)
97
100
  add_transaction_reference(post, authorization)
98
- add_additional_transaction_data(post, options)
99
101
  add_void_refund_reason(post)
100
102
 
101
103
  commit('void', post)
@@ -134,6 +136,12 @@ module ActiveMerchant #:nodoc:
134
136
  add_basket_product(post, options[:basket_product]) if options[:basket_product]
135
137
  end
136
138
 
139
+ def add_3ds_data(post, options)
140
+ post[:PaymentDealerRequest][:ReturnHash] = 1
141
+ post[:PaymentDealerRequest][:RedirectUrl] = options[:redirect_url] || ''
142
+ post[:PaymentDealerRequest][:RedirectType] = options[:redirect_type] || 0
143
+ end
144
+
137
145
  def add_payment_dealer_authentication(post)
138
146
  post[:PaymentDealerAuthentication] = {
139
147
  DealerCode: @options[:dealer_code],
@@ -156,18 +164,19 @@ module ActiveMerchant #:nodoc:
156
164
  def add_payment(post, card)
157
165
  post[:PaymentDealerRequest][:CardHolderFullName] = card.name
158
166
  post[:PaymentDealerRequest][:CardNumber] = card.number
159
- post[:PaymentDealerRequest][:ExpMonth] = card.month
167
+ post[:PaymentDealerRequest][:ExpMonth] = card.month.to_s.rjust(2, '0')
160
168
  post[:PaymentDealerRequest][:ExpYear] = card.year
161
- post[:PaymentDealerRequest][:CvcNumber] = card.verification_value
169
+ post[:PaymentDealerRequest][:CvcNumber] = card.verification_value || ''
162
170
  end
163
171
 
164
172
  def add_amount(post, money)
165
- post[:PaymentDealerRequest][:Amount] = money || 0
173
+ post[:PaymentDealerRequest][:Amount] = amount(money) || 0
166
174
  end
167
175
 
168
176
  def add_additional_auth_purchase_data(post, options)
169
177
  post[:PaymentDealerRequest][:IsPreAuth] = options[:pre_auth]
170
- post[:PaymentDealerRequest][:Description] = options[:order_id] if options[:order_id]
178
+ post[:PaymentDealerRequest][:Description] = options[:description] if options[:description]
179
+ post[:PaymentDealerRequest][:InstallmentNumber] = options[:installment_number].to_i if options[:installment_number]
171
180
  post[:SubMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
172
181
  post[:IsPoolPayment] = options[:is_pool_payment] || 0
173
182
  end
@@ -232,6 +241,8 @@ module ActiveMerchant #:nodoc:
232
241
 
233
242
  def endpoint(action)
234
243
  case action
244
+ when 'three_ds_authorize', 'three_ds_purchase'
245
+ 'DoDirectPaymentThreeD'
235
246
  when 'purchase', 'authorize'
236
247
  'DoDirectPayment'
237
248
  when 'capture'
@@ -256,7 +267,9 @@ module ActiveMerchant #:nodoc:
256
267
  end
257
268
 
258
269
  def success_from(response)
259
- response.dig('Data', 'IsSuccessful')
270
+ return response.dig('Data', 'IsSuccessful') if response.dig('Data', 'IsSuccessful').to_s.present?
271
+
272
+ response['ResultCode']&.casecmp('success') == 0
260
273
  end
261
274
 
262
275
  def message_from(response)
@@ -50,15 +50,16 @@ module ActiveMerchant #:nodoc:
50
50
  post[:order_id] = options[:order_id]
51
51
  post[:address] = options[:billing_address] || options[:address]
52
52
  post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
53
+ add_external_mpi_fields(post, options)
53
54
  add_stored_credential(post, options)
54
- action = if post[:cavv]
55
+ action = if post[:cavv] || options[:three_d_secure]
55
56
  'cavv_preauth'
56
57
  elsif post[:data_key].blank?
57
58
  'preauth'
58
59
  else
59
60
  'res_preauth_cc'
60
61
  end
61
- commit(action, post)
62
+ commit(action, post, options)
62
63
  end
63
64
 
64
65
  # This action verifies funding on a customer's card and readies them for
@@ -73,15 +74,16 @@ module ActiveMerchant #:nodoc:
73
74
  post[:order_id] = options[:order_id]
74
75
  post[:address] = options[:billing_address] || options[:address]
75
76
  post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
77
+ add_external_mpi_fields(post, options)
76
78
  add_stored_credential(post, options)
77
- action = if post[:cavv]
79
+ action = if post[:cavv] || options[:three_d_secure]
78
80
  'cavv_purchase'
79
81
  elsif post[:data_key].blank?
80
82
  'purchase'
81
83
  else
82
84
  'res_purchase_cc'
83
85
  end
84
- commit(action, post)
86
+ commit(action, post, options)
85
87
  end
86
88
 
87
89
  # This method retrieves locked funds from a customer's account (from a
@@ -203,6 +205,21 @@ module ActiveMerchant #:nodoc:
203
205
  sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month)
204
206
  end
205
207
 
208
+ def add_external_mpi_fields(post, options)
209
+ # See these pages:
210
+ # https://developer.moneris.com/livedemo/3ds2/cavv_purchase/tool/php
211
+ # https://developer.moneris.com/livedemo/3ds2/cavv_preauth/guide/php
212
+ return unless options[:three_d_secure]
213
+
214
+ three_d_secure_options = options[:three_d_secure]
215
+
216
+ post[:threeds_version] = three_d_secure_options[:version]
217
+ post[:crypt_type] = three_d_secure_options[:eci]
218
+ post[:cavv] = three_d_secure_options[:cavv]
219
+ post[:threeds_server_trans_id] = three_d_secure_options[:three_ds_server_trans_id]
220
+ post[:ds_trans_id] = three_d_secure_options[:ds_transaction_id]
221
+ end
222
+
206
223
  def add_payment_source(post, payment_method, options)
207
224
  if payment_method.is_a?(String)
208
225
  post[:data_key] = payment_method
@@ -291,14 +308,16 @@ module ActiveMerchant #:nodoc:
291
308
  end
292
309
  end
293
310
 
294
- def commit(action, parameters = {})
311
+ def commit(action, parameters = {}, options = {})
312
+ threed_ds_transaction = options[:three_d_secure].present?
313
+
295
314
  data = post_data(action, parameters)
296
315
  url = test? ? self.test_url : self.live_url
297
316
  raw = ssl_post(url, data)
298
317
  response = parse(raw)
299
318
 
300
319
  Response.new(
301
- successful?(response),
320
+ successful?(action, response, threed_ds_transaction),
302
321
  message_from(response[:message]),
303
322
  response,
304
323
  test: test?,
@@ -314,8 +333,16 @@ module ActiveMerchant #:nodoc:
314
333
  end
315
334
 
316
335
  # Tests for a successful response from Moneris' servers
317
- def successful?(response)
318
- response[:response_code] &&
336
+ def successful?(action, response, threed_ds_transaction = false)
337
+ # See 9.4 CAVV Result Codes in https://developer.moneris.com/livedemo/3ds2/reference/guide/php
338
+ cavv_accepted = if threed_ds_transaction
339
+ response[:cavv_result_code] && response[:cavv_result_code] == '2'
340
+ else
341
+ true
342
+ end
343
+
344
+ cavv_accepted &&
345
+ response[:response_code] &&
319
346
  response[:complete] &&
320
347
  (0..49).cover?(response[:response_code].to_i)
321
348
  end
@@ -276,14 +276,14 @@ module ActiveMerchant #:nodoc:
276
276
  end
277
277
 
278
278
  Response.new(
279
- success_from(response),
279
+ success_from(response, action),
280
280
  message_from(response),
281
281
  response,
282
282
  authorization: authorization_from(response, action),
283
283
  avs_result: AVSResult.new(code: response['some_avs_response_key']),
284
284
  cvv_result: CVVResult.new(response['some_cvv_response_key']),
285
285
  test: test?,
286
- error_code: error_code_from(response)
286
+ error_code: error_code_from(response, action)
287
287
  )
288
288
  rescue ResponseError => e
289
289
  message = get_error_messages(e)
@@ -297,8 +297,10 @@ module ActiveMerchant #:nodoc:
297
297
  )
298
298
  end
299
299
 
300
- def success_from(response)
301
- %w[pending paid processing canceled active].include? response['status']
300
+ def success_from(response, action)
301
+ success = response.try(:[], 'last_transaction').try(:[], 'success') unless action == 'store'
302
+ success = !response.try(:[], 'id').nil? if action == 'store'
303
+ success
302
304
  end
303
305
 
304
306
  def message_from(response)
@@ -350,8 +352,8 @@ module ActiveMerchant #:nodoc:
350
352
  parameters.to_json
351
353
  end
352
354
 
353
- def error_code_from(response)
354
- return if success_from(response)
355
+ def error_code_from(response, action)
356
+ return if success_from(response, action)
355
357
  return response['last_transaction']['acquirer_return_code'] if response['last_transaction']
356
358
 
357
359
  STANDARD_ERROR_CODE[:processing_error]