activemerchant 1.123.0 → 1.126.0

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