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
@@ -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
 
@@ -19,6 +19,7 @@ module ActiveMerchant #:nodoc:
19
19
  def purchase(money, payment, options = {})
20
20
  post = {}
21
21
  add_auth_purchase_params(post, money, payment, 'purchase', options)
22
+ add_three_ds(post, options)
22
23
 
23
24
  commit('purchase', post, options)
24
25
  end
@@ -26,6 +27,8 @@ module ActiveMerchant #:nodoc:
26
27
  def authorize(money, payment, options = {})
27
28
  post = {}
28
29
  add_auth_purchase_params(post, money, payment, 'authorize', options)
30
+ add_three_ds(post, options)
31
+ post[:card][:verify] = true if options[:verify].to_s == 'true'
29
32
 
30
33
  commit('authorize', post, options)
31
34
  end
@@ -52,10 +55,7 @@ module ActiveMerchant #:nodoc:
52
55
  end
53
56
 
54
57
  def verify(credit_card, options = {})
55
- MultiResponse.run(:use_first_response) do |r|
56
- r.process { authorize(100, credit_card, options) }
57
- r.process(:ignore_result) { void(r.authorization, options) }
58
- end
58
+ authorize(0, credit_card, options.merge(verify: 'true'))
59
59
  end
60
60
 
61
61
  def supports_scrubbing?
@@ -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
 
@@ -149,6 +156,7 @@ module ActiveMerchant #:nodoc:
149
156
  post[:card][:capture] = (action == 'purchase')
150
157
  post[:card][:installments] = options[:installments] if options[:installments]
151
158
  post[:card][:installments_id] = options[:installments_id] if options[:installments_id]
159
+ post[:card][:force_type] = options[:force_type].to_s.upcase if options[:force_type]
152
160
  end
153
161
 
154
162
  def parse(body)
@@ -156,6 +164,9 @@ module ActiveMerchant #:nodoc:
156
164
  end
157
165
 
158
166
  def commit(action, parameters, options = {})
167
+ three_ds_errors = validate_three_ds_params(parameters[:three_dsecure]) if parameters[:three_dsecure].present?
168
+ return three_ds_errors if three_ds_errors
169
+
159
170
  url = url(action, parameters, options)
160
171
  post = post_data(action, parameters)
161
172
  begin
@@ -184,7 +195,7 @@ module ActiveMerchant #:nodoc:
184
195
  def success_from(action, response)
185
196
  return false unless response['status_code']
186
197
 
187
- %w[100 200 400 600].include? response['status_code'].to_s
198
+ %w[100 200 400 600 700].include? response['status_code'].to_s
188
199
  end
189
200
 
190
201
  def message_from(action, response)
@@ -228,6 +239,7 @@ module ActiveMerchant #:nodoc:
228
239
  'X-Date' => timestamp,
229
240
  'X-Login' => @options[:login],
230
241
  'X-Trans-Key' => @options[:trans_key],
242
+ 'X-Version' => '2.1',
231
243
  'Authorization' => signature(post, timestamp)
232
244
  }
233
245
  headers.merge('X-Idempotency-Key' => options[:idempotency_key]) if options[:idempotency_key]
@@ -243,6 +255,49 @@ module ActiveMerchant #:nodoc:
243
255
  def post_data(action, parameters = {})
244
256
  parameters.to_json
245
257
  end
258
+
259
+ def xid_or_ds_trans_id(three_d_secure)
260
+ if three_d_secure[:version].to_f >= 2
261
+ { ds_transaction_id: three_d_secure[:ds_transaction_id] }
262
+ else
263
+ { xid: three_d_secure[:xid] }
264
+ end
265
+ end
266
+
267
+ def add_three_ds(post, options)
268
+ return unless three_d_secure = options[:three_d_secure]
269
+
270
+ post[:three_dsecure] = {
271
+ mpi: true,
272
+ three_dsecure_version: three_d_secure[:version],
273
+ cavv: three_d_secure[:cavv],
274
+ eci: three_d_secure[:eci],
275
+ enrollment_response: formatted_enrollment(three_d_secure[:enrolled]),
276
+ authentication_response: three_d_secure[:authentication_response_status]
277
+ }.merge(xid_or_ds_trans_id(three_d_secure))
278
+ end
279
+
280
+ def validate_three_ds_params(three_ds)
281
+ errors = {}
282
+ supported_version = %w{1.0 2.0 2.1.0 2.2.0}.include?(three_ds[:three_dsecure_version])
283
+ supported_enrollment = ['Y', 'N', 'U', nil].include?(three_ds[:enrollment_response])
284
+ supported_auth_response = ['Y', 'N', 'U', nil].include?(three_ds[:authentication_response])
285
+
286
+ errors[:three_ds_version] = 'ThreeDs version not supported' unless supported_version
287
+ errors[:enrollment] = 'Enrollment value not supported' unless supported_enrollment
288
+ errors[:auth_response] = 'Authentication response value not supported' unless supported_auth_response
289
+ errors.compact!
290
+
291
+ errors.present? ? Response.new(false, 'ThreeDs data is invalid', errors) : nil
292
+ end
293
+
294
+ def formatted_enrollment(val)
295
+ case val
296
+ when 'Y', 'N', 'U' then val
297
+ when true, 'true' then 'Y'
298
+ when false, 'false' then 'N'
299
+ end
300
+ end
246
301
  end
247
302
  end
248
303
  end
@@ -117,11 +117,11 @@ module ActiveMerchant #:nodoc:
117
117
  post[:establishment_name] = options[:establishment_name] if options[:establishment_name]
118
118
  post[:fraud_detection] = add_fraud_detection(options[:fraud_detection]) if options[:fraud_detection].present?
119
119
  post[:site_id] = options[:site_id] if options[:site_id]
120
- post[:sub_payments] = []
121
120
 
122
121
  add_invoice(post, money, options)
123
122
  add_payment(post, credit_card, options)
124
123
  add_aggregate_data(post, options) if options[:aggregate_data]
124
+ add_sub_payments(post, options)
125
125
  end
126
126
 
127
127
  def add_payment_method_id(credit_card, options)
@@ -210,6 +210,22 @@ module ActiveMerchant #:nodoc:
210
210
  post[:aggregate_data] = aggregate_data
211
211
  end
212
212
 
213
+ def add_sub_payments(post, options)
214
+ # sub_payments field is required for purchase transactions, even if empty
215
+ post[:sub_payments] = []
216
+
217
+ return unless sub_payments = options[:sub_payments]
218
+
219
+ sub_payments.each do |sub_payment|
220
+ sub_payment_hash = {
221
+ site_id: sub_payment[:site_id],
222
+ installments: sub_payment[:installments].to_i,
223
+ amount: sub_payment[:amount].to_i
224
+ }
225
+ post[:sub_payments] << sub_payment_hash
226
+ end
227
+ end
228
+
213
229
  def add_fraud_detection(options = {})
214
230
  {}.tap do |hsh|
215
231
  hsh[:send_to_cs] = options[:send_to_cs] if valid_fraud_detection_option?(options[:send_to_cs]) # true/false
@@ -0,0 +1,344 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class DecidirPlusGateway < Gateway
4
+ self.test_url = 'https://developers.decidir.com/api/v2'
5
+ self.live_url = 'https://live.decidir.com/api/v2'
6
+
7
+ self.supported_countries = ['AR']
8
+ self.default_currency = 'ARS'
9
+ self.supported_cardtypes = %i[visa master american_express discover diners_club naranja cabal]
10
+
11
+ self.homepage_url = 'http://decidir.com.ar/home'
12
+ self.display_name = 'Decidir Plus'
13
+
14
+ def initialize(options = {})
15
+ requires!(options, :public_key, :private_key)
16
+ super
17
+ end
18
+
19
+ def purchase(money, payment, options = {})
20
+ post = {}
21
+ build_purchase_authorize_request(post, money, payment, options)
22
+
23
+ commit(:post, 'payments', post)
24
+ end
25
+
26
+ def authorize(money, payment, options = {})
27
+ post = {}
28
+ build_purchase_authorize_request(post, money, payment, options)
29
+
30
+ commit(:post, 'payments', post)
31
+ end
32
+
33
+ def capture(money, authorization, options = {})
34
+ post = {}
35
+ post[:amount] = money
36
+
37
+ commit(:put, "payments/#{add_reference(authorization)}", post)
38
+ end
39
+
40
+ def refund(money, authorization, options = {})
41
+ post = {}
42
+ post[:amount] = money
43
+
44
+ commit(:post, "payments/#{add_reference(authorization)}/refunds", post)
45
+ end
46
+
47
+ def void(authorization, options = {})
48
+ commit(:post, "payments/#{add_reference(authorization)}/refunds")
49
+ end
50
+
51
+ def verify(credit_card, options = {})
52
+ MultiResponse.run(:use_first_response) do |r|
53
+ r.process { store(credit_card, options) }
54
+ r.process { authorize(100, r.authorization, options) }
55
+ r.process(:ignore_result) { void(r.authorization, options) }
56
+ end
57
+ end
58
+
59
+ def store(payment, options = {})
60
+ post = {}
61
+ add_payment(post, payment, options)
62
+
63
+ commit(:post, 'tokens', post)
64
+ end
65
+
66
+ def unstore(customer_token)
67
+ commit(:delete, "cardtokens/#{customer_token}")
68
+ end
69
+
70
+ def supports_scrubbing?
71
+ true
72
+ end
73
+
74
+ def scrub(transcript)
75
+ transcript.
76
+ gsub(%r((Apikey: )\w+), '\1[FILTERED]').
77
+ gsub(%r(("card_number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
78
+ gsub(%r(("security_code\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]')
79
+ end
80
+
81
+ private
82
+
83
+ def build_purchase_authorize_request(post, money, payment, options)
84
+ add_customer_data(post, options)
85
+ add_payment(post, payment, options)
86
+ add_purchase_data(post, money, payment, options)
87
+ add_fraud_detection(post, options)
88
+ end
89
+
90
+ def add_reference(authorization)
91
+ return unless authorization
92
+
93
+ authorization.split('|')[0]
94
+ end
95
+
96
+ def add_payment(post, payment, options = {})
97
+ if payment.is_a?(String)
98
+ token, bin = payment.split('|')
99
+ post[:token] = token
100
+ post[:bin] = bin
101
+ else
102
+ post[:card_number] = payment.number
103
+ post[:card_expiration_month] = format(payment.month, :two_digits)
104
+ post[:card_expiration_year] = format(payment.year, :two_digits)
105
+ post[:security_code] = payment.verification_value.to_s
106
+ post[:card_holder_name] = payment.name.empty? ? options[:name_override] : payment.name
107
+ post[:card_holder_identification] = {}
108
+ post[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
109
+ post[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
110
+
111
+ # additional data used for Visa transactions
112
+ post[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
113
+ post[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
114
+ end
115
+ end
116
+
117
+ def add_customer_data(post, options = {})
118
+ return unless customer = options[:customer]
119
+
120
+ post[:customer] = {}
121
+ post[:customer][:id] = customer[:id] if customer[:id]
122
+ post[:customer][:email] = customer[:email] if customer[:email]
123
+ end
124
+
125
+ def add_purchase_data(post, money, payment, options = {})
126
+ post[:site_transaction_id] = options[:site_transaction_id] || SecureRandom.hex
127
+ post[:payment_method_id] = add_payment_method_id(options)
128
+ post[:amount] = money
129
+ post[:currency] = options[:currency] || self.default_currency
130
+ post[:installments] = options[:installments] || 1
131
+ post[:payment_type] = options[:payment_type] || 'single'
132
+ post[:establishment_name] = options[:establishment_name] if options[:establishment_name]
133
+
134
+ add_aggregate_data(post, options) if options[:aggregate_data]
135
+ add_sub_payments(post, options)
136
+ end
137
+
138
+ def add_aggregate_data(post, options)
139
+ aggregate_data = {}
140
+ data = options[:aggregate_data]
141
+ aggregate_data[:indicator] = data[:indicator] if data[:indicator]
142
+ aggregate_data[:identification_number] = data[:identification_number] if data[:identification_number]
143
+ aggregate_data[:bill_to_pay] = data[:bill_to_pay] if data[:bill_to_pay]
144
+ aggregate_data[:bill_to_refund] = data[:bill_to_refund] if data[:bill_to_refund]
145
+ aggregate_data[:merchant_name] = data[:merchant_name] if data[:merchant_name]
146
+ aggregate_data[:street] = data[:street] if data[:street]
147
+ aggregate_data[:number] = data[:number] if data[:number]
148
+ aggregate_data[:postal_code] = data[:postal_code] if data[:postal_code]
149
+ aggregate_data[:category] = data[:category] if data[:category]
150
+ aggregate_data[:channel] = data[:channel] if data[:channel]
151
+ aggregate_data[:geographic_code] = data[:geographic_code] if data[:geographic_code]
152
+ aggregate_data[:city] = data[:city] if data[:city]
153
+ aggregate_data[:merchant_id] = data[:merchant_id] if data[:merchant_id]
154
+ aggregate_data[:province] = data[:province] if data[:province]
155
+ aggregate_data[:country] = data[:country] if data[:country]
156
+ aggregate_data[:merchant_email] = data[:merchant_email] if data[:merchant_email]
157
+ aggregate_data[:merchant_phone] = data[:merchant_phone] if data[:merchant_phone]
158
+ post[:aggregate_data] = aggregate_data
159
+ end
160
+
161
+ def add_sub_payments(post, options)
162
+ # sub_payments field is required for purchase transactions, even if empty
163
+ post[:sub_payments] = []
164
+
165
+ return unless sub_payments = options[:sub_payments]
166
+
167
+ sub_payments.each do |sub_payment|
168
+ sub_payment_hash = {
169
+ site_id: sub_payment[:site_id],
170
+ installments: sub_payment[:installments].to_i,
171
+ amount: sub_payment[:amount].to_i
172
+ }
173
+ post[:sub_payments] << sub_payment_hash
174
+ end
175
+ end
176
+
177
+ def add_payment_method_id(options)
178
+ return options[:payment_method_id].to_i if options[:payment_method_id]
179
+
180
+ if options[:debit]
181
+ case options[:card_brand]
182
+ when 'visa'
183
+ 31
184
+ when 'master'
185
+ 105
186
+ when 'maestro'
187
+ 106
188
+ when 'cabal'
189
+ 108
190
+ else
191
+ 31
192
+ end
193
+ else
194
+ case options[:card_brand]
195
+ when 'visa'
196
+ 1
197
+ when 'master'
198
+ 104
199
+ when 'american_express'
200
+ 65
201
+ when 'american_express_prisma'
202
+ 111
203
+ when 'cabal'
204
+ 63
205
+ when 'diners_club'
206
+ 8
207
+ else
208
+ 1
209
+ end
210
+ end
211
+ end
212
+
213
+ def add_fraud_detection(post, options)
214
+ return unless fraud_detection = options[:fraud_detection]
215
+
216
+ {}.tap do |hsh|
217
+ hsh[:send_to_cs] = fraud_detection[:send_to_cs] == 'true' # true/false
218
+ hsh[:channel] = fraud_detection[:channel] if fraud_detection[:channel]
219
+ hsh[:dispatch_method] = fraud_detection[:dispatch_method] if fraud_detection[:dispatch_method]
220
+ add_csmdds(hsh, fraud_detection)
221
+
222
+ post[:fraud_detection] = hsh
223
+ end
224
+ end
225
+
226
+ def add_csmdds(hsh, fraud_detection)
227
+ return unless fraud_detection[:csmdds]
228
+
229
+ csmdds_arr = []
230
+ fraud_detection[:csmdds].each do |csmdds|
231
+ csmdds_hsh = {}
232
+ csmdds_hsh[:code] = csmdds[:code].to_i
233
+ csmdds_hsh[:description] = csmdds[:description]
234
+ csmdds_arr.append(csmdds_hsh)
235
+ end
236
+ hsh[:csmdds] = csmdds_arr unless csmdds_arr.empty?
237
+ end
238
+
239
+ def parse(body)
240
+ return {} if body.nil?
241
+
242
+ JSON.parse(body)
243
+ end
244
+
245
+ def commit(method, endpoint, parameters = {}, options = {})
246
+ begin
247
+ raw_response = ssl_request(method, url(endpoint), post_data(parameters), headers(endpoint))
248
+ response = parse(raw_response)
249
+ rescue ResponseError => e
250
+ raw_response = e.response.body
251
+ response = parse(raw_response)
252
+ end
253
+
254
+ Response.new(
255
+ success_from(response),
256
+ message_from(response),
257
+ response,
258
+ authorization: authorization_from(response),
259
+ avs_result: AVSResult.new(code: response['some_avs_response_key']),
260
+ cvv_result: CVVResult.new(response['some_cvv_response_key']),
261
+ test: test?,
262
+ error_code: error_code_from(response)
263
+ )
264
+ end
265
+
266
+ def headers(endpoint)
267
+ {
268
+ 'Content-Type' => 'application/json',
269
+ 'apikey' => endpoint == 'tokens' ? @options[:public_key] : @options[:private_key]
270
+ }
271
+ end
272
+
273
+ def url(action, options = {})
274
+ base_url = (test? ? test_url : live_url)
275
+
276
+ return "#{base_url}/#{action}"
277
+ end
278
+
279
+ def success_from(response)
280
+ response.dig('status') == 'approved' || response.dig('status') == 'active' || response.dig('status') == 'pre_approved' || response.empty?
281
+ end
282
+
283
+ def message_from(response)
284
+ return '' if response.empty?
285
+
286
+ rejected?(response) ? message_from_status_details(response) : response.dig('status') || error_message(response) || response.dig('message')
287
+ end
288
+
289
+ def authorization_from(response)
290
+ return nil unless response.dig('id') || response.dig('bin')
291
+
292
+ "#{response.dig('id')}|#{response.dig('bin')}"
293
+ end
294
+
295
+ def post_data(parameters = {})
296
+ parameters.to_json
297
+ end
298
+
299
+ def error_code_from(response)
300
+ return if success_from(response)
301
+
302
+ error_code = nil
303
+ if error = response.dig('status_details', 'error')
304
+ error_code = error.dig('reason', 'id') || error['type']
305
+ elsif response['error_type']
306
+ error_code = response['error_type']
307
+ elsif response.dig('error', 'validation_errors')
308
+ error = response.dig('error')
309
+ validation_errors = error.dig('validation_errors', 0)
310
+ code = validation_errors['code'] if validation_errors && validation_errors['code']
311
+ param = validation_errors['param'] if validation_errors && validation_errors['param']
312
+ error_code = "#{error['error_type']} | #{code} | #{param}" if error['error_type']
313
+ elsif error = response.dig('error')
314
+ error_code = error.dig('reason', 'id')
315
+ end
316
+
317
+ error_code
318
+ end
319
+
320
+ def error_message(response)
321
+ return error_code_from(response) unless validation_errors = response.dig('validation_errors')
322
+
323
+ validation_errors = validation_errors[0]
324
+
325
+ "#{validation_errors.dig('code')}: #{validation_errors.dig('param')}"
326
+ end
327
+
328
+ def rejected?(response)
329
+ return response.dig('status') == 'rejected'
330
+ end
331
+
332
+ def message_from_status_details(response)
333
+ return unless error = response.dig('status_details', 'error')
334
+ return message_from_fraud_detection(response) if error.dig('type') == 'cybersource_error'
335
+
336
+ "#{error.dig('type')}: #{error.dig('reason', 'description')}"
337
+ end
338
+
339
+ def message_from_fraud_detection(response)
340
+ return error_message(response.dig('fraud_detection', 'status', 'details'))
341
+ end
342
+ end
343
+ end
344
+ end
@@ -42,8 +42,8 @@ module ActiveMerchant #:nodoc:
42
42
  'ar' => 100,
43
43
  'co' => 100,
44
44
  'pe' => 300,
45
- 'mx' => 300,
46
- 'cl' => 5000
45
+ 'mx' => 2000,
46
+ 'cl' => 80000
47
47
  }
48
48
 
49
49
  def initialize(options = {})
@@ -183,6 +183,7 @@ module ActiveMerchant #:nodoc:
183
183
  post[:payment][:currency_code] = (options[:currency] || currency(money))
184
184
  post[:payment][:merchant_payment_code] = Digest::MD5.hexdigest(options[:order_id])
185
185
  post[:payment][:instalments] = options[:instalments] || 1
186
+ post[:payment][:order_number] = options[:order_id][0..39] if options[:order_id]
186
187
  end
187
188
 
188
189
  def add_card_or_token(post, payment)
@@ -214,6 +215,7 @@ module ActiveMerchant #:nodoc:
214
215
  post[:metadata] = options[:metadata] if options[:metadata]
215
216
  post[:metadata] = {} if post[:metadata].nil?
216
217
  post[:metadata][:merchant_payment_code] = options[:order_id] if options[:order_id]
218
+ post[:processing_type] = options[:processing_type] if options[:processing_type]
217
219
  end
218
220
 
219
221
  def parse(body)
@@ -222,7 +224,8 @@ module ActiveMerchant #:nodoc:
222
224
 
223
225
  def commit(action, parameters)
224
226
  url = url_for((test? ? test_url : live_url), action, parameters)
225
- response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), { 'x-ebanx-client-user-agent': "ActiveMerchant/#{ActiveMerchant::VERSION}" }))
227
+
228
+ response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), headers(parameters)))
226
229
 
227
230
  success = success_from(action, response)
228
231
 
@@ -236,6 +239,19 @@ module ActiveMerchant #:nodoc:
236
239
  )
237
240
  end
238
241
 
242
+ def headers(params)
243
+ processing_type = params[:processing_type]
244
+ commit_headers = { 'x-ebanx-client-user-agent': "ActiveMerchant/#{ActiveMerchant::VERSION}" }
245
+
246
+ add_processing_type_to_commit_headers(commit_headers, processing_type) if processing_type == 'local'
247
+
248
+ commit_headers
249
+ end
250
+
251
+ def add_processing_type_to_commit_headers(commit_headers, processing_type)
252
+ commit_headers['x-ebanx-api-processing-type'] = processing_type
253
+ end
254
+
239
255
  def success_from(action, response)
240
256
  if %i[purchase capture refund].include?(action)
241
257
  response.try(:[], 'payment').try(:[], 'status') == 'CO'
@@ -298,7 +298,7 @@ module ActiveMerchant #:nodoc:
298
298
  xml.ssl_dynamic_dba options[:dba] if options.has_key?(:dba)
299
299
  xml.ssl_merchant_initiated_unscheduled merchant_initiated_unscheduled(options) if merchant_initiated_unscheduled(options)
300
300
  xml.ssl_add_token options[:add_recurring_token] if options.has_key?(:add_recurring_token)
301
- xml.ssl_token options[:ssl_token] if options.has_key?(:ssl_token)
301
+ xml.ssl_token options[:ssl_token] if options[:ssl_token]
302
302
  xml.ssl_customer_code options[:customer] if options.has_key?(:customer)
303
303
  xml.ssl_customer_number options[:customer_number] if options.has_key?(:customer_number)
304
304
  xml.ssl_entry_mode entry_mode(options) if entry_mode(options)
@@ -393,6 +393,7 @@ module ActiveMerchant #:nodoc:
393
393
 
394
394
  def commit(request)
395
395
  request = "xmldata=#{request}".delete('&')
396
+ store_action = request.match?('CCGETTOKEN')
396
397
 
397
398
  response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
398
399
  response = hash_html_decode(response)
@@ -402,7 +403,7 @@ module ActiveMerchant #:nodoc:
402
403
  response[:result_message] || response[:errorMessage],
403
404
  response,
404
405
  test: @options[:test] || test?,
405
- authorization: authorization_from(response),
406
+ authorization: authorization_from(response, store_action),
406
407
  error_code: response[:errorCode],
407
408
  avs_result: { code: response[:avs_response] },
408
409
  cvv_result: response[:cvv2_response],
@@ -428,7 +429,9 @@ module ActiveMerchant #:nodoc:
428
429
  response.deep_transform_keys { |key| key.gsub('ssl_', '').to_sym }
429
430
  end
430
431
 
431
- def authorization_from(response)
432
+ def authorization_from(response, store_action)
433
+ return response[:token] if store_action
434
+
432
435
  [response[:approval_code], response[:txn_id]].join(';')
433
436
  end
434
437
 
@@ -82,6 +82,19 @@ module ActiveMerchant #:nodoc:
82
82
  commit('CreditCardReturn', request, money)
83
83
  end
84
84
 
85
+ def credit(money, payment, options = {})
86
+ request = build_soap_request do |xml|
87
+ xml.CreditCardCredit(xmlns: 'https://transaction.elementexpress.com') do
88
+ add_credentials(xml)
89
+ add_payment_method(xml, payment)
90
+ add_transaction(xml, money, options)
91
+ add_terminal(xml, options)
92
+ end
93
+ end
94
+
95
+ commit('CreditCardCredit', request, money)
96
+ end
97
+
85
98
  def void(authorization, options = {})
86
99
  trans_id, trans_amount = split_authorization(authorization)
87
100
  options.merge!({ trans_id: trans_id, trans_amount: trans_amount, reversal_type: 'Full' })
@@ -186,9 +199,10 @@ module ActiveMerchant #:nodoc:
186
199
  xml.ReversalType options[:reversal_type] if options[:reversal_type]
187
200
  xml.TransactionID options[:trans_id] if options[:trans_id]
188
201
  xml.TransactionAmount amount(money.to_i) if money
189
- xml.MarketCode 'Default' if money
202
+ xml.MarketCode market_code(money, options) if options[:market_code] || money
190
203
  xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20)
191
-
204
+ xml.TicketNumber options[:ticket_number] if options[:ticket_number]
205
+ xml.MerchantSuppliedTransactionId options[:merchant_supplied_transaction_id] if options[:merchant_supplied_transaction_id]
192
206
  xml.PaymentType options[:payment_type] if options[:payment_type]
193
207
  xml.SubmissionType options[:submission_type] if options[:submission_type]
194
208
  xml.DuplicateCheckDisableFlag options[:duplicate_check_disable_flag].to_s == 'true' ? 'True' : 'False' unless options[:duplicate_check_disable_flag].nil?
@@ -197,6 +211,10 @@ module ActiveMerchant #:nodoc:
197
211
  end
198
212
  end
199
213
 
214
+ def market_code(money, options)
215
+ options[:market_code] || 'Default'
216
+ end
217
+
200
218
  def add_terminal(xml, options)
201
219
  xml.terminal do
202
220
  xml.TerminalID options[:terminal_id] || '01'