activemerchant 1.126.0 → 1.129.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +241 -0
  3. data/lib/active_merchant/billing/check.rb +40 -8
  4. data/lib/active_merchant/billing/credit_card.rb +28 -1
  5. data/lib/active_merchant/billing/credit_card_methods.rb +79 -23
  6. data/lib/active_merchant/billing/gateways/adyen.rb +67 -8
  7. data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
  8. data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -4
  10. data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
  12. data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
  13. data/lib/active_merchant/billing/gateways/borgun.rb +56 -16
  14. data/lib/active_merchant/billing/gateways/braintree_blue.rb +64 -17
  15. data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
  17. data/lib/active_merchant/billing/gateways/checkout_v2.rb +228 -57
  18. data/lib/active_merchant/billing/gateways/commerce_hub.rb +361 -0
  19. data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
  20. data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +100 -26
  22. data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +456 -0
  23. data/lib/active_merchant/billing/gateways/d_local.rb +44 -5
  24. data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
  25. data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
  26. data/lib/active_merchant/billing/gateways/element.rb +21 -1
  27. data/lib/active_merchant/billing/gateways/global_collect.rb +73 -22
  28. data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
  29. data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
  30. data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
  31. data/lib/active_merchant/billing/gateways/litle.rb +25 -5
  32. data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
  33. data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
  34. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
  35. data/lib/active_merchant/billing/gateways/monei.rb +2 -0
  36. data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
  37. data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
  38. data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
  39. data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
  40. data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
  41. data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
  42. data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
  45. data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
  46. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -0
  47. data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
  48. data/lib/active_merchant/billing/gateways/priority.rb +29 -6
  49. data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
  50. data/lib/active_merchant/billing/gateways/reach.rb +277 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +9 -5
  52. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
  53. data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
  54. data/lib/active_merchant/billing/gateways/shift4.rb +342 -0
  55. data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
  56. data/lib/active_merchant/billing/gateways/stripe.rb +21 -1
  57. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
  58. data/lib/active_merchant/billing/gateways/tns.rb +2 -5
  59. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
  60. data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
  61. data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
  62. data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
  63. data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
  64. data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
  65. data/lib/active_merchant/billing/gateways/worldpay.rb +117 -9
  66. data/lib/active_merchant/billing/response.rb +15 -1
  67. data/lib/active_merchant/connection.rb +0 -2
  68. data/lib/active_merchant/country.rb +1 -0
  69. data/lib/active_merchant/errors.rb +4 -1
  70. data/lib/active_merchant/version.rb +1 -1
  71. metadata +24 -3
@@ -0,0 +1,277 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class ReachGateway < Gateway
4
+ self.test_url = 'https://checkout.rch.how/'
5
+ self.live_url = 'https://checkout.rch.io/'
6
+
7
+ self.supported_countries = ['US']
8
+ self.default_currency = 'USD'
9
+ self.supported_cardtypes = %i[visa diners_club american_express jcb master discover maestro]
10
+
11
+ self.homepage_url = 'https://www.withreach.com/'
12
+ self.display_name = 'Reach'
13
+ self.currencies_without_fractions = %w(BIF BYR CLF CLP CVE DJF GNF ISK JPY KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF IDR MGA MRO)
14
+
15
+ API_VERSION = 'v2.22'.freeze
16
+ STANDARD_ERROR_CODE_MAPPING = {}
17
+ PAYMENT_METHOD_MAP = {
18
+ american_express: 'AMEX',
19
+ cabal: 'CABAL',
20
+ check: 'ACH',
21
+ dankort: 'DANKORT',
22
+ diners_club: 'DINERS',
23
+ discover: 'DISC',
24
+ elo: 'ELO',
25
+ jcb: 'JCB',
26
+ maestro: 'MAESTRO',
27
+ master: 'MC',
28
+ naranja: 'NARANJA',
29
+ union_pay: 'UNIONPAY',
30
+ visa: 'VISA'
31
+ }
32
+
33
+ def initialize(options = {})
34
+ requires!(options, :merchant_id, :secret)
35
+ super
36
+ end
37
+
38
+ def authorize(money, payment, options = {})
39
+ request = build_checkout_request(money, payment, options)
40
+ add_custom_fields_data(request, options)
41
+ add_customer_data(request, options, payment)
42
+ add_stored_credentials(request, options)
43
+ post = { request: request, card: add_payment(payment, options) }
44
+ if options[:stored_credential]
45
+ MultiResponse.run(:use_first_response) do |r|
46
+ r.process { commit('checkout', post) }
47
+ r.process do
48
+ r2 = get_network_payment_reference(r.responses[0])
49
+ r.params[:network_transaction_id] = r2.message
50
+ r2
51
+ end
52
+ end
53
+ else
54
+ commit('checkout', post)
55
+ end
56
+ end
57
+
58
+ def purchase(money, payment, options = {})
59
+ options[:capture] = true
60
+ authorize(money, payment, options)
61
+ end
62
+
63
+ def capture(money, authorization, options = {})
64
+ post = { request: { MerchantId: @options[:merchant_id], OrderId: authorization } }
65
+ commit('capture', post)
66
+ end
67
+
68
+ def supports_scrubbing?
69
+ true
70
+ end
71
+
72
+ def scrub(transcript)
73
+ transcript.
74
+ gsub(%r(((MerchantId)[% \w]+[%]\d{2})[\w -]+), '\1[FILTERED]').
75
+ gsub(%r((signature=)[\w%]+), '\1[FILTERED]\2').
76
+ gsub(%r((Number%22%3A%22)[\d]+), '\1[FILTERED]\2').
77
+ gsub(%r((VerificationCode%22%3A)[\d]+), '\1[FILTERED]\2')
78
+ end
79
+
80
+ def refund(amount, authorization, options = {})
81
+ currency = options[:currency] || currency(options[:amount])
82
+ post = {
83
+ request: {
84
+ MerchantId: @options[:merchant_id],
85
+ OrderId: authorization,
86
+ ReferenceId: options[:order_id] || options[:reference_id],
87
+ Amount: localized_amount(amount, currency)
88
+ }
89
+ }
90
+ commit('refund', post)
91
+ end
92
+
93
+ def void(authorization, options = {})
94
+ post = {
95
+ request: {
96
+ MerchantId: @options[:merchant_id],
97
+ OrderId: authorization
98
+ }
99
+ }
100
+
101
+ commit('cancel', post)
102
+ end
103
+
104
+ def verify(credit_card, options = {})
105
+ MultiResponse.run(:use_first_response) do |r|
106
+ r.process { authorize(100, credit_card, options) }
107
+ r.process(:ignore_result) { void(r.authorization, options) }
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def build_checkout_request(amount, payment, options)
114
+ currency = options[:currency] || currency(options[:amount])
115
+ {
116
+ MerchantId: @options[:merchant_id],
117
+ ReferenceId: options[:order_id],
118
+ ConsumerCurrency: currency,
119
+ Capture: options[:capture] || false,
120
+ PaymentMethod: PAYMENT_METHOD_MAP.fetch(payment.brand.to_sym, 'unsupported'),
121
+ Items: [
122
+ Sku: options[:item_sku] || SecureRandom.alphanumeric,
123
+ ConsumerPrice: localized_amount(amount, currency),
124
+ Quantity: (options[:item_quantity] || 1)
125
+ ]
126
+ }
127
+ end
128
+
129
+ def add_payment(payment, options)
130
+ ntid = options.dig(:stored_credential, :network_transaction_id)
131
+ cvv_or_previos_reference = (ntid ? { PreviousNetworkPaymentReference: ntid } : { VerificationCode: payment.verification_value })
132
+ {
133
+ Name: payment.name,
134
+ Number: payment.number,
135
+ Expiry: { Month: payment.month, Year: payment.year }
136
+ }.merge!(cvv_or_previos_reference)
137
+ end
138
+
139
+ def add_customer_data(request, options, payment)
140
+ address = options[:billing_address] || options[:address]
141
+
142
+ return if address.blank?
143
+
144
+ request[:Consumer] = {
145
+ Name: payment.respond_to?(:name) ? payment.name : address[:name],
146
+ Email: options[:email],
147
+ Address: address[:address1],
148
+ City: address[:city],
149
+ Country: address[:country]
150
+ }.compact
151
+ end
152
+
153
+ def add_stored_credentials(request, options)
154
+ request[:PaymentModel] = payment_model(options) || ''
155
+ request[:DeviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
156
+ end
157
+
158
+ def payment_model(options)
159
+ stored_credential = options[:stored_credential]
160
+ return options[:payment_model] if options[:payment_model]
161
+ return 'CIT-One-Time' unless stored_credential
162
+
163
+ payment_model_options = {
164
+ initial_transaction: {
165
+ 'cardholder' => {
166
+ 'installment' => 'CIT-Setup-Scheduled',
167
+ 'unscheduled' => 'CIT-Setup-Unscheduled-MIT',
168
+ 'recurring' => 'CIT-Setup-Unscheduled'
169
+ }
170
+ },
171
+ no_initial_transaction: {
172
+ 'cardholder' => {
173
+ 'unscheduled' => 'CIT-Subsequent-Unscheduled'
174
+ },
175
+ 'merchant' => {
176
+ 'recurring' => 'MIT-Subsequent-Scheduled',
177
+ 'unscheduled' => 'MIT-Subsequent-Unscheduled'
178
+ }
179
+ }
180
+ }
181
+ initial = stored_credential[:initial_transaction] ? :initial_transaction : :no_initial_transaction
182
+ payment_model_options[initial].dig(stored_credential[:initiator], stored_credential[:reason_type])
183
+ end
184
+
185
+ def add_custom_fields_data(request, options)
186
+ add_shipping_data(request, options) if options[:taxes].present?
187
+ request[:RateOfferId] = options[:rate_offer_id] if options[:rate_offer_id].present?
188
+ request[:Items] = options[:items] if options[:items].present?
189
+ end
190
+
191
+ def add_shipping_data(request, options)
192
+ request[:Shipping] = {
193
+ ConsumerPrice: options[:price],
194
+ ConsumerTaxes: options[:taxes],
195
+ ConsumerDuty: options[:duty]
196
+ }
197
+ request[:Consignee] = {
198
+ Name: options[:consignee_name],
199
+ Address: options[:consignee_address],
200
+ City: options[:consignee_city],
201
+ Country: options[:consignee_country]
202
+ }
203
+ end
204
+
205
+ def sign_body(body)
206
+ Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', @options[:secret].encode('utf-8'), body.encode('utf-8')))
207
+ end
208
+
209
+ def parse(body)
210
+ hash_response = URI.decode_www_form(body).to_h
211
+ hash_response['response'] = JSON.parse(hash_response['response'])
212
+
213
+ hash_response
214
+ end
215
+
216
+ def format_and_sign(post)
217
+ post[:request] = post[:request].to_json
218
+ post[:card] = post[:card].to_json if post[:card].present?
219
+ post[:signature] = sign_body(post[:request])
220
+ post
221
+ end
222
+
223
+ def get_network_payment_reference(response)
224
+ parameters = { request: { MerchantId: @options[:merchant_id], OrderId: response.params['response']['OrderId'] } }
225
+ body = post_data format_and_sign(parameters)
226
+
227
+ raw_response = ssl_request :post, url('query'), body, {}
228
+ response = parse(raw_response)
229
+ message = response.dig('response', 'Payment', 'NetworkPaymentReference')
230
+ Response.new(true, message, {})
231
+ end
232
+
233
+ def commit(action, parameters)
234
+ body = post_data format_and_sign(parameters)
235
+ raw_response = ssl_post url(action), body
236
+ response = parse(raw_response)
237
+
238
+ Response.new(
239
+ success_from(response),
240
+ message_from(response) || '',
241
+ response,
242
+ authorization: authorization_from(response['response']),
243
+ # avs_result: AVSResult.new(code: response['some_avs_response_key']),
244
+ # cvv_result: CVVResult.new(response['some_cvv_response_key']),
245
+ test: test?,
246
+ error_code: error_code_from(response)
247
+ )
248
+ rescue ActiveMerchant::ResponseError => e
249
+ Response.new(false, (e.response.body.present? ? e.response.body : e.response.msg), {}, test: test?)
250
+ end
251
+
252
+ def success_from(response)
253
+ response.dig('response', 'Error').blank?
254
+ end
255
+
256
+ def message_from(response)
257
+ success_from(response) ? '' : response.dig('response', 'Error', 'ReasonCode')
258
+ end
259
+
260
+ def authorization_from(response)
261
+ response['OrderId']
262
+ end
263
+
264
+ def post_data(params)
265
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
266
+ end
267
+
268
+ def error_code_from(response)
269
+ response['response']['Error']['Code'] unless success_from(response)
270
+ end
271
+
272
+ def url(action)
273
+ "#{test? ? test_url : live_url}#{API_VERSION}/#{action}"
274
+ end
275
+ end
276
+ end
277
+ end
@@ -91,7 +91,7 @@ module ActiveMerchant #:nodoc:
91
91
  # More operations are supported by the gateway itself, but
92
92
  # are not supported in this library.
93
93
  SUPPORTED_TRANSACTIONS = {
94
- purchase: 'A',
94
+ purchase: '0',
95
95
  authorize: '1',
96
96
  capture: '2',
97
97
  refund: '3',
@@ -266,9 +266,13 @@ module ActiveMerchant #:nodoc:
266
266
  end
267
267
 
268
268
  def verify(creditcard, options = {})
269
- MultiResponse.run(:use_first_response) do |r|
270
- r.process { authorize(100, creditcard, options) }
271
- r.process(:ignore_result) { void(r.authorization, options) }
269
+ if options[:sca_exemption_behavior_override] == 'endpoint_and_ntid'
270
+ purchase(0, creditcard, options)
271
+ else
272
+ MultiResponse.run(:use_first_response) do |r|
273
+ r.process { authorize(100, creditcard, options) }
274
+ r.process(:ignore_result) { void(r.authorization, options) }
275
+ end
272
276
  end
273
277
  end
274
278
 
@@ -685,7 +689,7 @@ module ActiveMerchant #:nodoc:
685
689
  cipher = OpenSSL::Cipher.new('DES3')
686
690
  cipher.encrypt
687
691
 
688
- cipher.key = Base64.strict_decode64(key)
692
+ cipher.key = Base64.urlsafe_decode64(key)
689
693
  # The OpenSSL default of an all-zeroes ("\\0") IV is used.
690
694
  cipher.padding = 0
691
695
 
@@ -427,7 +427,7 @@ module ActiveMerchant #:nodoc:
427
427
  def past_purchase_reference?(payment_method)
428
428
  return false unless payment_method.is_a?(String)
429
429
 
430
- payment_method.split(';').last == 'purchase'
430
+ %w(purchase repeat).include?(payment_method.split(';').last)
431
431
  end
432
432
  end
433
433
  end
@@ -133,6 +133,7 @@ module ActiveMerchant #:nodoc:
133
133
  add_creditcard(post, payment, options)
134
134
  add_customer(post, payment, options)
135
135
  add_customer_data(post, options)
136
+ add_external_three_ds(post, options)
136
137
  if options[:email]
137
138
  post[:metadata] = {}
138
139
  post[:metadata][:email] = options[:email]
@@ -140,6 +141,40 @@ module ActiveMerchant #:nodoc:
140
141
  post
141
142
  end
142
143
 
144
+ def add_external_three_ds(post, options)
145
+ return if options[:three_d_secure].blank?
146
+
147
+ post[:threeDSecure] = {
148
+ external: {
149
+ version: options[:three_d_secure][:version],
150
+ authenticationValue: options[:three_d_secure][:cavv],
151
+ acsTransactionId: options[:three_d_secure][:acs_transaction_id],
152
+ status: options[:three_d_secure][:authentication_response_status],
153
+ eci: options[:three_d_secure][:eci]
154
+ }.merge(xid_or_ds_trans_id(options[:three_d_secure]))
155
+ }
156
+ end
157
+
158
+ def xid_or_ds_trans_id(three_ds)
159
+ if three_ds[:version].to_f >= 2.0
160
+ { dsTransactionId: three_ds[:ds_transaction_id] }
161
+ else
162
+ { xid: three_ds[:xid] }
163
+ end
164
+ end
165
+
166
+ def validate_three_ds_params(three_ds)
167
+ errors = {}
168
+ supported_version = %w{1.0.2 2.1.0 2.2.0}.include?(three_ds[:version])
169
+ supported_auth_response = ['Y', 'N', 'U', 'R', 'E', 'A', nil].include?(three_ds[:status])
170
+
171
+ errors[:three_ds_version] = 'ThreeDs version not supported' unless supported_version
172
+ errors[:auth_response] = 'Authentication response value not supported' unless supported_auth_response
173
+ errors.compact!
174
+
175
+ errors.present? ? Response.new(false, 'ThreeDs data is invalid', errors) : nil
176
+ end
177
+
143
178
  def add_amount(post, money, options, include_currency = false)
144
179
  currency = (options[:currency] || default_currency)
145
180
  post[:amount] = localized_amount(money, currency)
@@ -182,6 +217,11 @@ module ActiveMerchant #:nodoc:
182
217
  end
183
218
 
184
219
  def commit(url, parameters = nil, options = {}, method = nil)
220
+ if parameters.present? && parameters[:threeDSecure].present?
221
+ three_ds_errors = validate_three_ds_params(parameters[:threeDSecure][:external])
222
+ return three_ds_errors if three_ds_errors
223
+ end
224
+
185
225
  response = api_request(url, parameters, options, method)
186
226
  success = !response.key?('error')
187
227