activemerchant 1.95.0 → 1.96.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +45 -0
  3. data/README.md +3 -0
  4. data/lib/active_merchant/billing/avs_result.rb +4 -5
  5. data/lib/active_merchant/billing/credit_card.rb +2 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +14 -0
  7. data/lib/active_merchant/billing/gateway.rb +10 -0
  8. data/lib/active_merchant/billing/gateways/adyen.rb +19 -6
  9. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +39 -10
  10. data/lib/active_merchant/billing/gateways/blue_snap.rb +4 -1
  11. data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
  12. data/lib/active_merchant/billing/gateways/braintree_blue.rb +11 -1
  13. data/lib/active_merchant/billing/gateways/card_connect.rb +1 -0
  14. data/lib/active_merchant/billing/gateways/cecabank.rb +7 -7
  15. data/lib/active_merchant/billing/gateways/checkout_v2.rb +24 -24
  16. data/lib/active_merchant/billing/gateways/credorax.rb +29 -3
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +2 -2
  18. data/lib/active_merchant/billing/gateways/decidir.rb +232 -0
  19. data/lib/active_merchant/billing/gateways/global_collect.rb +2 -6
  20. data/lib/active_merchant/billing/gateways/hps.rb +46 -1
  21. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/migs.rb +8 -0
  23. data/lib/active_merchant/billing/gateways/mundipagg.rb +1 -1
  24. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  25. data/lib/active_merchant/billing/gateways/nmi.rb +39 -1
  26. data/lib/active_merchant/billing/gateways/opp.rb +20 -1
  27. data/lib/active_merchant/billing/gateways/payflow.rb +40 -2
  28. data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
  29. data/lib/active_merchant/billing/gateways/realex.rb +11 -5
  30. data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
  31. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -2
  32. data/lib/active_merchant/billing/gateways/trust_commerce.rb +24 -5
  33. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +8 -5
  34. data/lib/active_merchant/billing/gateways/worldpay.rb +157 -37
  35. data/lib/active_merchant/country.rb +1 -0
  36. data/lib/active_merchant/version.rb +1 -1
  37. metadata +3 -2
@@ -6,6 +6,10 @@ module ActiveMerchant #:nodoc:
6
6
  self.display_name = 'Credorax Gateway'
7
7
  self.homepage_url = 'https://www.credorax.com/'
8
8
 
9
+ # NOTE: the IP address you run the remote tests from will need to be
10
+ # whitelisted by Credorax; contact support@credorax.com as necessary to
11
+ # request your IP address be added to the whitelist for your test
12
+ # account.
9
13
  self.test_url = 'https://intconsole.credorax.com/intenv/service/gateway'
10
14
 
11
15
  # The live URL is assigned on a per merchant basis once certification has passed
@@ -15,7 +19,7 @@ module ActiveMerchant #:nodoc:
15
19
  # ActiveMerchant::Billing::CredoraxGateway.live_url = "https://assigned-subdomain.credorax.net/crax_gate/service/gateway"
16
20
  self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway'
17
21
 
18
- self.supported_countries = %w(DE GB FR IT ES PL NL BE GR CZ PT SE HU RS AT CH BG DK FI SK NO IE HR BA AL LT MK SI LV EE ME LU MT IS AD MC LI SM)
22
+ self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB)
19
23
  self.default_currency = 'EUR'
20
24
  self.currencies_without_fractions = %w(CLP JPY KRW PYG VND)
21
25
  self.currencies_with_three_decimal_places = %w(BHD JOD KWD OMR RSD TND)
@@ -264,8 +268,30 @@ module ActiveMerchant #:nodoc:
264
268
  end
265
269
 
266
270
  def add_3d_secure(post, options)
267
- return unless options[:eci] && options[:xid]
268
- post[:i8] = "#{options[:eci]}:#{(options[:cavv] || "none")}:#{options[:xid]}"
271
+ if options[:eci] && options[:xid]
272
+ add_3d_secure_1_data(post, options)
273
+ elsif options[:three_d_secure]
274
+ add_normalized_3d_secure_2_data(post, options)
275
+ end
276
+ end
277
+
278
+ def add_3d_secure_1_data(post, options)
279
+ post[:i8] = build_i8(options[:eci], options[:cavv], options[:xid])
280
+ end
281
+
282
+ def add_normalized_3d_secure_2_data(post, options)
283
+ three_d_secure_options = options[:three_d_secure]
284
+
285
+ post[:i8] = build_i8(
286
+ three_d_secure_options[:eci],
287
+ three_d_secure_options[:cavv]
288
+ )
289
+ post[:'3ds_version'] = three_d_secure_options[:version]
290
+ post[:'3ds_dstrxid'] = three_d_secure_options[:ds_transaction_id]
291
+ end
292
+
293
+ def build_i8(eci, cavv=nil, xid=nil)
294
+ "#{eci}:#{cavv || 'none'}:#{xid || 'none'}"
269
295
  end
270
296
 
271
297
  def add_echo(post, options)
@@ -1,7 +1,7 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  # Initial setup instructions can be found in
4
- # http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf
4
+ # http://apps.cybersource.com/library/documentation/dev_guides/SOAP_Toolkits/SOAP_toolkits.pdf
5
5
  #
6
6
  # Important Notes
7
7
  # * For checks you can purchase and store.
@@ -27,7 +27,7 @@ module ActiveMerchant #:nodoc:
27
27
  XSD_VERSION = '1.153'
28
28
 
29
29
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro, :elo]
30
- self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB)
30
+ self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK)
31
31
 
32
32
  self.default_currency = 'USD'
33
33
  self.currencies_without_fractions = %w(JPY)
@@ -0,0 +1,232 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class DecidirGateway < 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.money_format = :cents
9
+ self.default_currency = 'ARS'
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
11
+
12
+ self.homepage_url = 'http://www.decidir.com'
13
+ self.display_name = 'Decidir'
14
+
15
+ STANDARD_ERROR_CODE_MAPPING = {
16
+ 1 => STANDARD_ERROR_CODE[:call_issuer],
17
+ 2 => STANDARD_ERROR_CODE[:call_issuer],
18
+ 3 => STANDARD_ERROR_CODE[:config_error],
19
+ 4 => STANDARD_ERROR_CODE[:pickup_card],
20
+ 5 => STANDARD_ERROR_CODE[:card_declined],
21
+ 7 => STANDARD_ERROR_CODE[:pickup_card],
22
+ 12 => STANDARD_ERROR_CODE[:processing_error],
23
+ 14 => STANDARD_ERROR_CODE[:invalid_number],
24
+ 28 => STANDARD_ERROR_CODE[:processing_error],
25
+ 38 => STANDARD_ERROR_CODE[:incorrect_pin],
26
+ 39 => STANDARD_ERROR_CODE[:invalid_number],
27
+ 43 => STANDARD_ERROR_CODE[:pickup_card],
28
+ 45 => STANDARD_ERROR_CODE[:card_declined],
29
+ 46 => STANDARD_ERROR_CODE[:invalid_number],
30
+ 47 => STANDARD_ERROR_CODE[:card_declined],
31
+ 48 => STANDARD_ERROR_CODE[:card_declined],
32
+ 49 => STANDARD_ERROR_CODE[:invalid_expiry_date],
33
+ 51 => STANDARD_ERROR_CODE[:card_declined],
34
+ 53 => STANDARD_ERROR_CODE[:card_declined],
35
+ 54 => STANDARD_ERROR_CODE[:expired_card],
36
+ 55 => STANDARD_ERROR_CODE[:incorrect_pin],
37
+ 56 => STANDARD_ERROR_CODE[:card_declined],
38
+ 57 => STANDARD_ERROR_CODE[:card_declined],
39
+ 76 => STANDARD_ERROR_CODE[:call_issuer],
40
+ 96 => STANDARD_ERROR_CODE[:processing_error],
41
+ 97 => STANDARD_ERROR_CODE[:processing_error],
42
+ }
43
+
44
+ def initialize(options={})
45
+ requires!(options, :api_key)
46
+ super
47
+ @options[:preauth_mode] ||= false
48
+ end
49
+
50
+ def purchase(money, payment, options={})
51
+ raise ArgumentError, 'Purchase is not supported on Decidir gateways configured with the preauth_mode option' if @options[:preauth_mode]
52
+
53
+ post = {}
54
+ add_auth_purchase_params(post, money, payment, options)
55
+ commit(:post, 'payments', post)
56
+ end
57
+
58
+ def authorize(money, payment, options={})
59
+ raise ArgumentError, 'Authorize is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
60
+
61
+ post = {}
62
+ add_auth_purchase_params(post, money, payment, options)
63
+ commit(:post, 'payments', post)
64
+ end
65
+
66
+ def capture(money, authorization, options={})
67
+ raise ArgumentError, 'Capture is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
68
+
69
+ post = {}
70
+ add_amount(post, money, options)
71
+ commit(:put, "payments/#{authorization}", post)
72
+ end
73
+
74
+ def refund(money, authorization, options={})
75
+ post = {}
76
+ add_amount(post, money, options)
77
+ commit(:post, "payments/#{authorization}/refunds", post)
78
+ end
79
+
80
+ def void(authorization, options={})
81
+ post = {}
82
+ commit(:post, "payments/#{authorization}/refunds", post)
83
+ end
84
+
85
+ def verify(credit_card, options={})
86
+ raise ArgumentError, 'Verify is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
87
+
88
+ MultiResponse.run(:use_first_response) do |r|
89
+ r.process { authorize(100, credit_card, options) }
90
+ r.process(:ignore_result) { void(r.authorization, options) }
91
+ end
92
+ end
93
+
94
+ def supports_scrubbing?
95
+ true
96
+ end
97
+
98
+ def scrub(transcript)
99
+ transcript.
100
+ gsub(%r((apikey: )\w+)i, '\1[FILTERED]').
101
+ gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]').
102
+ gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]')
103
+ end
104
+
105
+ private
106
+
107
+ def add_auth_purchase_params(post, money, credit_card, options)
108
+ post[:payment_method_id] = options[:payment_method_id] ? options[:payment_method_id].to_i : 1
109
+ post[:site_transaction_id] = options[:order_id]
110
+ post[:bin] = credit_card.number[0..5]
111
+ post[:payment_type] = options[:payment_type] || 'single'
112
+ post[:installments] = options[:installments] ? options[:installments].to_i : 1
113
+ post[:description] = options[:description] if options[:description]
114
+ post[:email] = options[:email] if options[:email]
115
+ post[:sub_payments] = []
116
+
117
+ add_invoice(post, money, options)
118
+ add_payment(post, credit_card, options)
119
+ end
120
+
121
+ def add_invoice(post, money, options)
122
+ add_amount(post, money, options)
123
+ post[:currency] = (options[:currency] || currency(money))
124
+ end
125
+
126
+ def add_amount(post, money, options)
127
+ currency = (options[:currency] || currency(money))
128
+ post[:amount] = localized_amount(money, currency).to_i
129
+ end
130
+
131
+ def add_payment(post, credit_card, options)
132
+ card_data = {}
133
+ card_data[:card_number] = credit_card.number
134
+ card_data[:card_expiration_month] = format(credit_card.month, :two_digits)
135
+ card_data[:card_expiration_year] = format(credit_card.year, :two_digits)
136
+ card_data[:security_code] = credit_card.verification_value if credit_card.verification_value?
137
+ card_data[:card_holder_name] = credit_card.name if credit_card.name
138
+
139
+ # additional data used for Visa transactions
140
+ card_data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
141
+ card_data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
142
+
143
+ card_data[:card_holder_identification] = {}
144
+ card_data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
145
+ card_data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
146
+
147
+ post[:card_data] = card_data
148
+ end
149
+
150
+ def headers(options = {})
151
+ {
152
+ 'apikey' => @options[:api_key],
153
+ 'Content-type' => 'application/json',
154
+ 'Cache-Control' => 'no-cache'
155
+ }
156
+ end
157
+
158
+ def commit(method, endpoint, parameters, options={})
159
+ url = "#{(test? ? test_url : live_url)}/#{endpoint}"
160
+
161
+ begin
162
+ raw_response = ssl_request(method, url, post_data(parameters), headers(options))
163
+ response = parse(raw_response)
164
+ rescue ResponseError => e
165
+ raw_response = e.response.body
166
+ response = parse(raw_response)
167
+ end
168
+
169
+ success = success_from(response)
170
+ Response.new(
171
+ success,
172
+ message_from(success, response),
173
+ response,
174
+ authorization: authorization_from(response),
175
+ test: test?,
176
+ error_code: success ? nil : error_code_from(response)
177
+ )
178
+ end
179
+
180
+ def post_data(parameters = {})
181
+ parameters.to_json
182
+ end
183
+
184
+ def parse(body)
185
+ JSON.parse(body)
186
+ rescue JSON::ParserError
187
+ {
188
+ 'message' => "A non-JSON response was received from Decidir where one was expected. The raw response was:\n\n#{body}"
189
+ }
190
+ end
191
+
192
+ def message_from(success, response)
193
+ return response['status'] if success
194
+ return response['message'] if response['message']
195
+
196
+ message = nil
197
+
198
+ if error = response.dig('status_details', 'error')
199
+ message = error.dig('reason', 'description')
200
+ elsif response['error_type']
201
+ if response['validation_errors']
202
+ message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ')
203
+ end
204
+ message ||= response['error_type']
205
+ end
206
+
207
+ message
208
+ end
209
+
210
+ def success_from(response)
211
+ response['status'] == 'approved' || response['status'] == 'pre_approved'
212
+ end
213
+
214
+ def authorization_from(response)
215
+ response['id']
216
+ end
217
+
218
+ def error_code_from(response)
219
+ error_code = nil
220
+ if error = response.dig('status_details', 'error')
221
+ code = error.dig('reason', 'id')
222
+ error_code = STANDARD_ERROR_CODE_MAPPING[code]
223
+ error_code ||= error['type']
224
+ elsif response['error_type']
225
+ error_code = response['error_type'] if response['validation_errors']
226
+ end
227
+
228
+ error_code || STANDARD_ERROR_CODE[:processing_error]
229
+ end
230
+ end
231
+ end
232
+ end
@@ -142,12 +142,8 @@ module ActiveMerchant #:nodoc:
142
142
 
143
143
  def add_customer_data(post, options, payment = nil)
144
144
  if payment
145
- post['order']['customer']['personalInformation'] = {
146
- 'name' => {
147
- 'firstName' => payment.first_name[0..14],
148
- 'surname' => payment.last_name[0..69]
149
- }
150
- }
145
+ post['order']['customer']['personalInformation']['name']['firstName'] = payment.first_name[0..14] if payment.first_name
146
+ post['order']['customer']['personalInformation']['name']['surname'] = payment.last_name[0..69] if payment.last_name
151
147
  end
152
148
  post['order']['customer']['merchantCustomerId'] = options[:customer] if options[:customer]
153
149
  post['order']['customer']['companyInformation']['name'] = options[:company] if options[:company]
@@ -15,6 +15,14 @@ module ActiveMerchant #:nodoc:
15
15
 
16
16
  self.money_format = :dollars
17
17
 
18
+ PAYMENT_DATA_SOURCE_MAPPING = {
19
+ apple_pay: 'ApplePay',
20
+ master: 'MasterCard 3DSecure',
21
+ visa: 'Visa 3DSecure',
22
+ american_express: 'AMEX 3DSecure',
23
+ discover: 'Discover 3DSecure',
24
+ }
25
+
18
26
  def initialize(options={})
19
27
  requires!(options, :secret_api_key)
20
28
  super
@@ -28,6 +36,7 @@ module ActiveMerchant #:nodoc:
28
36
  add_details(xml, options)
29
37
  add_descriptor_name(xml, options)
30
38
  add_payment(xml, card_or_token, options)
39
+ add_three_d_secure(xml, card_or_token, options)
31
40
  end
32
41
  end
33
42
 
@@ -46,6 +55,7 @@ module ActiveMerchant #:nodoc:
46
55
  add_details(xml, options)
47
56
  add_descriptor_name(xml, options)
48
57
  add_payment(xml, card_or_token, options)
58
+ add_three_d_secure(xml, card_or_token, options)
49
59
  end
50
60
  end
51
61
 
@@ -81,7 +91,8 @@ module ActiveMerchant #:nodoc:
81
91
  transcript.
82
92
  gsub(%r((<hps:CardNbr>)[^<]*(<\/hps:CardNbr>))i, '\1[FILTERED]\2').
83
93
  gsub(%r((<hps:CVV2>)[^<]*(<\/hps:CVV2>))i, '\1[FILTERED]\2').
84
- gsub(%r((<hps:SecretAPIKey>)[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2')
94
+ gsub(%r((<hps:SecretAPIKey>)[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2').
95
+ gsub(%r((<hps:PaymentData>)[^<]*(<\/hps:PaymentData>))i, '\1[FILTERED]\2')
85
96
  end
86
97
 
87
98
  private
@@ -164,6 +175,40 @@ module ActiveMerchant #:nodoc:
164
175
  xml.hps :TxnDescriptor, options[:descriptor_name] if options[:descriptor_name]
165
176
  end
166
177
 
178
+ def add_three_d_secure(xml, card_or_token, options)
179
+ if card_or_token.is_a?(NetworkTokenizationCreditCard)
180
+ build_three_d_secure(xml, {
181
+ source: card_or_token.source,
182
+ cavv: card_or_token.payment_cryptogram,
183
+ eci: card_or_token.eci,
184
+ xid: card_or_token.transaction_id,
185
+ })
186
+ elsif options[:three_d_secure]
187
+ options[:three_d_secure][:source] ||= card_brand(card_or_token)
188
+ build_three_d_secure(xml, options[:three_d_secure])
189
+ end
190
+ end
191
+
192
+ def build_three_d_secure(xml, three_d_secure)
193
+ # PaymentDataSource is required when supplying the SecureECommerce data group,
194
+ # and the gateway currently only allows the values within the mapping
195
+ return unless PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym]
196
+
197
+ xml.hps :SecureECommerce do
198
+ xml.hps :PaymentDataSource, PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym]
199
+ xml.hps :TypeOfPaymentData, '3DSecure' # Only type currently supported
200
+ xml.hps :PaymentData, three_d_secure[:cavv] if three_d_secure[:cavv]
201
+ # the gateway only allows a single character for the ECI
202
+ xml.hps :ECommerceIndicator, strip_leading_zero(three_d_secure[:eci]) if three_d_secure[:eci]
203
+ xml.hps :XID, three_d_secure[:xid] if three_d_secure[:xid]
204
+ end
205
+ end
206
+
207
+ def strip_leading_zero(value)
208
+ return value unless value[0] == '0'
209
+ value[1, 1]
210
+ end
211
+
167
212
  def build_request(action)
168
213
  xml = Builder::XmlMarkup.new(encoding: 'UTF-8')
169
214
  xml.instruct!(:xml, encoding: 'UTF-8')
@@ -7,7 +7,7 @@ module ActiveMerchant #:nodoc:
7
7
  self.test_url = 'https://api-uat.kushkipagos.com/v1/'
8
8
  self.live_url = 'https://api.kushkipagos.com/v1/'
9
9
 
10
- self.supported_countries = ['CO', 'EC']
10
+ self.supported_countries = ['CL', 'CO', 'EC', 'MX', 'PE']
11
11
  self.default_currency = 'USD'
12
12
  self.money_format = :dollars
13
13
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
@@ -63,6 +63,7 @@ module ActiveMerchant #:nodoc:
63
63
  add_creditcard(post, creditcard)
64
64
  add_standard_parameters('pay', post, options[:unique_id])
65
65
  add_3ds(post, options)
66
+ add_tx_source(post, options)
66
67
 
67
68
  commit(post)
68
69
  end
@@ -83,6 +84,7 @@ module ActiveMerchant #:nodoc:
83
84
  add_amount(post, money, options)
84
85
  add_advanced_user(post)
85
86
  add_standard_parameters('capture', post, options[:unique_id])
87
+ add_tx_source(post, options)
86
88
 
87
89
  commit(post)
88
90
  end
@@ -99,6 +101,7 @@ module ActiveMerchant #:nodoc:
99
101
  add_amount(post, money, options)
100
102
  add_advanced_user(post)
101
103
  add_standard_parameters('refund', post, options[:unique_id])
104
+ add_tx_source(post, options)
102
105
 
103
106
  commit(post)
104
107
  end
@@ -110,6 +113,7 @@ module ActiveMerchant #:nodoc:
110
113
 
111
114
  add_advanced_user(post)
112
115
  add_standard_parameters('voidAuthorisation', post, options[:unique_id])
116
+ add_tx_source(post, options)
113
117
 
114
118
  commit(post)
115
119
  end
@@ -241,6 +245,10 @@ module ActiveMerchant #:nodoc:
241
245
  post['3DSstatus'] = options[:three_ds_status] if options[:three_ds_status]
242
246
  end
243
247
 
248
+ def add_tx_source(post, options)
249
+ post[:TxSource] = options[:tx_source] if options[:tx_source]
250
+ end
251
+
244
252
  def add_creditcard(post, creditcard)
245
253
  post[:CardNum] = creditcard.number
246
254
  post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value?