activemerchant 1.117.0 → 1.123.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +5 -3
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +6 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +96 -22
  8. data/lib/active_merchant/billing/gateways/adyen.rb +38 -21
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +5 -3
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +58 -8
  14. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  15. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  16. data/lib/active_merchant/billing/gateways/credorax.rb +16 -9
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +67 -9
  18. data/lib/active_merchant/billing/gateways/d_local.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/decidir.rb +29 -3
  20. data/lib/active_merchant/billing/gateways/elavon.rb +110 -26
  21. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  22. data/lib/active_merchant/billing/gateways/eway_rapid.rb +13 -0
  23. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +17 -6
  24. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +25 -16
  26. data/lib/active_merchant/billing/gateways/hps.rb +65 -2
  27. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  28. data/lib/active_merchant/billing/gateways/litle.rb +9 -4
  29. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  30. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +277 -0
  32. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  33. data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
  34. data/lib/active_merchant/billing/gateways/netbanx.rb +37 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +202 -47
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +57 -11
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +10 -5
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +26 -1
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -0
  45. data/lib/active_merchant/billing/gateways/paypal.rb +10 -2
  46. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  47. data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
  48. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  49. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  50. data/lib/active_merchant/billing/gateways/pin.rb +11 -0
  51. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  52. data/lib/active_merchant/billing/gateways/redsys.rb +78 -30
  53. data/lib/active_merchant/billing/gateways/safe_charge.rb +19 -8
  54. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  55. data/lib/active_merchant/billing/gateways/stripe.rb +8 -8
  56. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +86 -25
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  58. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  59. data/lib/active_merchant/billing/gateways/worldpay.rb +68 -20
  60. data/lib/active_merchant/billing/response.rb +2 -1
  61. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  62. data/lib/active_merchant/billing.rb +1 -0
  63. data/lib/active_merchant/version.rb +1 -1
  64. data/lib/certs/cacert.pem +1582 -2431
  65. metadata +10 -3
@@ -58,7 +58,7 @@ module ActiveMerchant #:nodoc:
58
58
  xml = Builder::XmlMarkup.new indent: 2
59
59
  xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
60
60
  xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
61
- xml.tag! 'n2:Version', API_VERSION
61
+ xml.tag! 'n2:Version', api_version(options)
62
62
  xml.tag! 'n2:' + transaction_type + 'RequestDetails' do
63
63
  xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction'
64
64
  xml.tag! 'n2:PaymentAction', action
@@ -73,6 +73,12 @@ module ActiveMerchant #:nodoc:
73
73
  xml.target!
74
74
  end
75
75
 
76
+ def api_version(options)
77
+ return API_VERSION_3DS2 if options.dig(:three_d_secure, :version) =~ /^2/
78
+
79
+ API_VERSION
80
+ end
81
+
76
82
  def add_credit_card(xml, credit_card, address, options)
77
83
  xml.tag! 'n2:CreditCard' do
78
84
  xml.tag! 'n2:CreditCardType', credit_card_type(card_brand(credit_card))
@@ -104,10 +110,12 @@ module ActiveMerchant #:nodoc:
104
110
  three_d_secure = options[:three_d_secure]
105
111
  xml.tag! 'ThreeDSecureRequest' do
106
112
  xml.tag! 'MpiVendor3ds', 'Y'
107
- xml.tag! 'AuthStatus3ds', three_d_secure[:trans_status] unless three_d_secure[:trans_status].blank?
113
+ xml.tag! 'AuthStatus3ds', three_d_secure[:authentication_response_status] || three_d_secure[:trans_status] if three_d_secure[:authentication_response_status] || three_d_secure[:trans_status]
108
114
  xml.tag! 'Cavv', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
109
115
  xml.tag! 'Eci3ds', three_d_secure[:eci] unless three_d_secure[:eci].blank?
110
116
  xml.tag! 'Xid', three_d_secure[:xid] unless three_d_secure[:xid].blank?
117
+ xml.tag! 'ThreeDSVersion', three_d_secure[:version] unless three_d_secure[:version].blank?
118
+ xml.tag! 'DSTransactionId', three_d_secure[:ds_transaction_id] unless three_d_secure[:ds_transaction_id].blank?
111
119
  end
112
120
  end
113
121
 
@@ -250,6 +250,7 @@ module ActiveMerchant #:nodoc:
250
250
  xml.tag! 'n2:PaymentType', options[:payment_type] || 'Any'
251
251
  add_payment_details(xml, money, currency_code, options)
252
252
  xml.tag! 'n2:IPAddress', options[:ip]
253
+ xml.tag! 'n2:MerchantSessionId', options[:merchant_session_id] if options[:merchant_session_id].present?
253
254
  end
254
255
  end
255
256
  end
@@ -0,0 +1,291 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class PaysafeGateway < Gateway
4
+ self.test_url = 'https://api.test.paysafe.com'
5
+ self.live_url = 'https://api.paysafe.com'
6
+
7
+ self.supported_countries = %w(FR)
8
+ self.default_currency = 'EUR'
9
+ self.supported_cardtypes = %i[visa master american_express discover]
10
+
11
+ self.homepage_url = 'https://www.paysafe.com/'
12
+ self.display_name = 'Paysafe'
13
+
14
+ def initialize(options = {})
15
+ requires!(options, :username, :password, :account_id)
16
+ super
17
+ end
18
+
19
+ def purchase(money, payment, options = {})
20
+ post = {}
21
+ add_invoice(post, money, options)
22
+ add_payment(post, payment)
23
+ add_billing_address(post, options)
24
+ add_merchant_details(post, options)
25
+ add_customer_data(post, payment, options) unless payment.is_a?(String)
26
+ add_three_d_secure(post, payment, options) if options[:three_d_secure]
27
+ post[:settleWithAuth] = true
28
+
29
+ commit(:post, 'auths', post, options)
30
+ end
31
+
32
+ def authorize(money, payment, options = {})
33
+ post = {}
34
+ add_invoice(post, money, options)
35
+ add_payment(post, payment)
36
+ add_billing_address(post, options)
37
+ add_merchant_details(post, options)
38
+ add_customer_data(post, payment, options) unless payment.is_a?(String)
39
+ add_three_d_secure(post, payment, options) if options[:three_d_secure]
40
+
41
+ commit(:post, 'auths', post, options)
42
+ end
43
+
44
+ def capture(money, authorization, options = {})
45
+ post = {}
46
+ add_invoice(post, money, options)
47
+
48
+ commit(:post, "auths/#{authorization}/settlements", post, options)
49
+ end
50
+
51
+ def refund(money, authorization, options = {})
52
+ post = {}
53
+ add_invoice(post, money, options)
54
+
55
+ commit(:post, "settlements/#{authorization}/refunds", post, options)
56
+ end
57
+
58
+ def void(authorization, options = {})
59
+ post = {}
60
+ money = options[:amount]
61
+ add_invoice(post, money, options)
62
+
63
+ commit(:post, "auths/#{authorization}/voidauths", post, options)
64
+ end
65
+
66
+ def credit(money, payment, options = {})
67
+ post = {}
68
+ add_invoice(post, money, options)
69
+ add_payment(post, payment)
70
+
71
+ commit(:post, 'standalonecredits', post, options)
72
+ end
73
+
74
+ # This is a '$0 auth' done at a specific verification endpoint at the gateway
75
+ def verify(payment, options = {})
76
+ post = {}
77
+ add_payment(post, payment)
78
+ add_billing_address(post, options)
79
+ add_customer_data(post, payment, options) unless payment.is_a?(String)
80
+
81
+ commit(:post, 'verifications', post, options)
82
+ end
83
+
84
+ def store(payment, options = {})
85
+ post = {}
86
+ add_payment(post, payment)
87
+ add_address_for_vaulting(post, options)
88
+ add_profile_data(post, payment, options)
89
+ add_store_data(post, payment, options)
90
+
91
+ commit(:post, 'profiles', post, options)
92
+ end
93
+
94
+ def redact(pm_profile_id)
95
+ commit_for_redact(:delete, "profiles/#{pm_profile_id}", nil, nil)
96
+ end
97
+
98
+ def supports_scrubbing?
99
+ true
100
+ end
101
+
102
+ def scrub(transcript)
103
+ transcript.
104
+ gsub(%r((Authorization: Basic )[a-zA-Z0-9:_]+), '\1[FILTERED]').
105
+ gsub(%r(("cardNum\\?":\\?")\d+), '\1[FILTERED]').
106
+ gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]')
107
+ end
108
+
109
+ private
110
+
111
+ # Customer data can be included in transactions where the payment method is a credit card
112
+ # but should not be sent when the payment method is a token
113
+ def add_customer_data(post, creditcard, options)
114
+ post[:profile] = {}
115
+ post[:profile][:firstName] = creditcard.first_name
116
+ post[:profile][:lastName] = creditcard.last_name
117
+ post[:profile][:email] = options[:email] if options[:email]
118
+ post[:customerIp] = options[:ip] if options[:ip]
119
+ end
120
+
121
+ def add_billing_address(post, options)
122
+ return unless options[:billing_address] || options[:address]
123
+
124
+ address = options[:billing_address] || options[:address]
125
+ post[:billingDetails] = {}
126
+ post[:billingDetails][:street] = address[:address1]
127
+ post[:billingDetails][:city] = address[:city]
128
+ post[:billingDetails][:state] = address[:state]
129
+ post[:billingDetails][:country] = address[:country]
130
+ post[:billingDetails][:zip] = address[:zip]
131
+ post[:billingDetails][:phone] = address[:phone]
132
+ end
133
+
134
+ # The add_address_for_vaulting method is applicable to the store method, as the APIs address
135
+ # object is formatted differently from the standard transaction billing address
136
+ def add_address_for_vaulting(post, options)
137
+ return unless options[:billing_address || options[:address]]
138
+
139
+ address = options[:billing_address] || options[:address]
140
+ post[:billingAddress] = {}
141
+ post[:billingAddress][:street] = address[:address1]
142
+ post[:billingAddress][:city] = address[:city]
143
+ post[:billingAddress][:zip] = address[:zip]
144
+ post[:billingAddress][:country] = address[:country]
145
+ post[:billingAddress][:state] = address[:state] if address[:state]
146
+ end
147
+
148
+ # This data is specific to creating a profile at the gateway's vault level
149
+ def add_profile_data(post, payment, options)
150
+ address = options[:billing_address] || options[:address]
151
+
152
+ post[:firstName] = payment.first_name
153
+ post[:lastName] = payment.last_name
154
+ post[:dateOfBirth] = {}
155
+ post[:dateOfBirth][:year] = options[:date_of_birth][:year]
156
+ post[:dateOfBirth][:month] = options[:date_of_birth][:month]
157
+ post[:dateOfBirth][:day] = options[:date_of_birth][:day]
158
+ post[:email] = options[:email] if options[:email]
159
+ post[:phone] = (address[:phone] || options[:phone]) if address[:phone] || options[:phone]
160
+ post[:ip] = options[:ip] if options[:ip]
161
+ end
162
+
163
+ def add_store_data(post, payment, options)
164
+ post[:merchantCustomerId] = options[:customer_id] || SecureRandom.hex(12)
165
+ post[:locale] = options[:locale] || 'en_US'
166
+ post[:card][:holderName] = payment.name
167
+ end
168
+
169
+ # Paysafe expects minor units so we are not calling amount method on money parameter
170
+ def add_invoice(post, money, options)
171
+ post[:amount] = money
172
+ end
173
+
174
+ def add_payment(post, payment)
175
+ if payment.is_a?(String)
176
+ post[:card] = {}
177
+ post[:card][:paymentToken] = payment
178
+ else
179
+ post[:card] = { cardExpiry: {} }
180
+ post[:card][:cardNum] = payment.number
181
+ post[:card][:cardExpiry][:month] = payment.month
182
+ post[:card][:cardExpiry][:year] = payment.year
183
+ post[:card][:cvv] = payment.verification_value
184
+ end
185
+ end
186
+
187
+ def add_merchant_details(post, options)
188
+ return unless options[:merchant_descriptor]
189
+
190
+ post[:merchantDescriptor] = {}
191
+ post[:merchantDescriptor][:dynamicDescriptor] = options[:merchant_descriptor][:dynamic_descriptor] if options[:merchant_descriptor][:dynamic_descriptor]
192
+ post[:merchantDescriptor][:phone] = options[:merchant_descriptor][:phone] if options[:merchant_descriptor][:phone]
193
+ end
194
+
195
+ def add_three_d_secure(post, payment, options)
196
+ three_d_secure = options[:three_d_secure]
197
+
198
+ post[:authentication] = {}
199
+ post[:authentication][:eci] = three_d_secure[:eci]
200
+ post[:authentication][:cavv] = three_d_secure[:cavv]
201
+ post[:authentication][:xid] = three_d_secure[:xid] if three_d_secure[:xid]
202
+ post[:authentication][:threeDSecureVersion] = three_d_secure[:version]
203
+ post[:authentication][:directoryServerTransactionId] = three_d_secure[:ds_transaction_id] unless payment.is_a?(String) || payment.brand != 'mastercard'
204
+ end
205
+
206
+ def parse(body)
207
+ JSON.parse(body)
208
+ end
209
+
210
+ def commit(method, action, parameters, options)
211
+ url = url(action)
212
+ raw_response = ssl_request(method, url, post_data(parameters, options), headers)
213
+ response = parse(raw_response)
214
+ success = success_from(response)
215
+
216
+ Response.new(
217
+ success,
218
+ message_from(success, response),
219
+ response,
220
+ authorization: authorization_from(response),
221
+ avs_result: AVSResult.new(code: response['avsResponse']),
222
+ cvv_result: CVVResult.new(response['cvvVerification']),
223
+ test: test?,
224
+ error_code: success ? nil : error_code_from(response)
225
+ )
226
+ end
227
+
228
+ def commit_for_redact(method, action, parameters, options)
229
+ url = url(action)
230
+ response = raw_ssl_request(method, url, post_data(parameters, options), headers)
231
+ success = true if response.code == '200'
232
+
233
+ Response.new(
234
+ success,
235
+ message: response.message
236
+ )
237
+ end
238
+
239
+ def headers
240
+ {
241
+ 'Content-Type' => 'application/json',
242
+ 'Authorization' => "Basic #{Base64.strict_encode64(@options[:api_key].to_s)}"
243
+ }
244
+ end
245
+
246
+ def url(action, options = {})
247
+ base_url = (test? ? test_url : live_url)
248
+
249
+ if action.include? 'profiles'
250
+ "#{base_url}/customervault/v1/#{action}"
251
+ else
252
+ "#{base_url}/cardpayments/v1/accounts/#{@options[:account_id]}/#{action}"
253
+ end
254
+ end
255
+
256
+ def success_from(response)
257
+ return false if response['status'] == 'FAILED' || response['error']
258
+
259
+ true
260
+ end
261
+
262
+ def message_from(success, response)
263
+ return response['status'] unless response['error']
264
+
265
+ "Error(s)- code:#{response['error']['code']}, message:#{response['error']['message']}"
266
+ end
267
+
268
+ def authorization_from(response)
269
+ response['id']
270
+ end
271
+
272
+ def post_data(parameters = {}, options = {})
273
+ return unless parameters.present?
274
+
275
+ parameters[:merchantRefNum] = options[:merchant_ref_num] || SecureRandom.hex(16).to_s
276
+
277
+ parameters.to_json
278
+ end
279
+
280
+ def error_code_from(response)
281
+ return unless response['error']
282
+
283
+ response['error']['code']
284
+ end
285
+
286
+ def handle_response(response)
287
+ response.body
288
+ end
289
+ end
290
+ end
291
+ end
@@ -208,7 +208,7 @@ module ActiveMerchant #:nodoc:
208
208
  buyer[:merchantBuyerId] = buyer_hash[:merchant_buyer_id]
209
209
  buyer[:cnpj] = buyer_hash[:cnpj] if @options[:payment_country] == 'BR'
210
210
  buyer[:emailAddress] = buyer_hash[:email]
211
- buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || ''
211
+ buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone_number] if options[:shipping_address]) || ''
212
212
  buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
213
213
  else
214
214
  buyer[:fullName] = payment_method.name.strip
@@ -217,7 +217,7 @@ module ActiveMerchant #:nodoc:
217
217
  buyer[:merchantBuyerId] = options[:merchant_buyer_id]
218
218
  buyer[:cnpj] = options[:cnpj] if @options[:payment_country] == 'BR'
219
219
  buyer[:emailAddress] = options[:email]
220
- buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || ''
220
+ buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone_number] if options[:shipping_address]) || ''
221
221
  buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
222
222
  end
223
223
  post[:transaction][:order][:buyer] = buyer
@@ -233,7 +233,7 @@ module ActiveMerchant #:nodoc:
233
233
  shipping_address[:state] = address[:state]
234
234
  shipping_address[:country] = address[:country]
235
235
  shipping_address[:postalCode] = address[:zip]
236
- shipping_address[:phone] = address[:phone]
236
+ shipping_address[:phone] = address[:phone_number]
237
237
  shipping_address
238
238
  end
239
239
 
@@ -0,0 +1,253 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class PaywayDotComGateway < Gateway
4
+ self.test_url = 'https://devedgilpayway.net/PaywayWS/Payment/CreditCard'
5
+ self.live_url = 'https://edgilpayway.com/PaywayWS/Payment/CreditCard'
6
+
7
+ self.supported_countries = %w[US CA]
8
+ self.default_currency = 'USD'
9
+ self.supported_cardtypes = %i[visa master american_express discover]
10
+
11
+ self.money_format = :cents
12
+
13
+ self.homepage_url = 'http://www.payway.com'
14
+ self.display_name = 'Payway Gateway'
15
+
16
+ STANDARD_ERROR_CODE_MAPPING = {
17
+ '5012' => STANDARD_ERROR_CODE[:card_declined],
18
+ '5035' => STANDARD_ERROR_CODE[:invalid_number],
19
+ '5037' => STANDARD_ERROR_CODE[:invalid_expiry_date],
20
+ '5045' => STANDARD_ERROR_CODE[:incorrect_zip]
21
+ }
22
+
23
+ # Payway to standard AVSResult codes.
24
+ AVS_MAPPING = {
25
+ 'N1' => 'I', # No address given with order
26
+ 'N2' => 'I', # Bill-to address did not pass
27
+ '““' => 'R', # AVS not performed (Blanks returned)
28
+ 'IU' => 'G', # AVS not performed by Issuer
29
+ 'ID' => 'S', # Issuer does not participate in AVS
30
+ 'IE' => 'E', # Edit Error - AVS data is invalid
31
+ 'IS' => 'R', # System unavailable or time-out
32
+ 'IB' => 'B', # Street address match. Postal code not verified due to incompatible formats (both were sent).
33
+ 'IC' => 'C', # Street address and postal code not verified due to incompatible format (both were sent).
34
+ 'IP' => 'P', # Postal code match. Street address not verified due to incompatible formats (both were sent).
35
+ 'A1' => 'K', # Accountholder name matches
36
+ 'A3' => 'V', # Accountholder name, billing address and postal code.
37
+ 'A4' => 'L', # Accountholder name and billing postal code match
38
+ 'A7' => 'O', # Accountholder name and billing address match
39
+ 'B3' => 'H', # Accountholder name incorrect, billing address and postal code match
40
+ 'B4' => 'F', # Accountholder name incorrect, billing postal code matches
41
+ 'B7' => 'T', # Accountholder name incorrect, billing address matches
42
+ 'B8' => 'N', # Accountholder name, billing address and postal code are all incorrect
43
+ '??' => 'R', # A double question mark symbol indicates an unrecognized response from association
44
+ 'I1' => 'X', # Zip code +4 and Address Match
45
+ 'I2' => 'W', # Zip code +4 Match
46
+ 'I3' => 'Y', # Zip code and Address Match
47
+ 'I4' => 'Z', # Zip code Match
48
+ 'I5' => 'M', # +4 and Address Match
49
+ 'I6' => 'W', # +4 Match
50
+ 'I7' => 'A', # Address Match
51
+ 'I8' => 'C', # No Match
52
+ }
53
+
54
+ PAYWAY_WS_SUCCESS = '5000'
55
+
56
+ SCRUB_PATTERNS = [
57
+ %r(("password\\?":\\?")[^\\]+),
58
+ %r(("fsv\\?":\\?")\d+),
59
+ %r(("fsv\\?": \\?")\d+),
60
+ %r(("accountNumber\\?":\\?")\d+),
61
+ %r(("accountNumber\\?": \\?")[^\\]+),
62
+ %r(("Invalid account number: )\d+)
63
+ ].freeze
64
+
65
+ SCRUB_REPLACEMENT = '\1[FILTERED]'
66
+
67
+ def initialize(options = {})
68
+ requires!(options, :login, :password, :company_id, :source_id)
69
+ super
70
+ end
71
+
72
+ def purchase(money, payment, options = {})
73
+ post = {}
74
+ add_common(post, options)
75
+ add_card_payment(post, payment, options)
76
+ add_card_transaction_details(post, money, options)
77
+ add_address(post, payment, options)
78
+
79
+ commit('sale', post)
80
+ end
81
+
82
+ def authorize(money, payment, options = {})
83
+ post = {}
84
+ add_common(post, options)
85
+ add_card_payment(post, payment, options)
86
+ add_card_transaction_details(post, money, options)
87
+ add_address(post, payment, options)
88
+
89
+ commit('authorize', post)
90
+ end
91
+
92
+ def capture(money, authorization, options = {})
93
+ post = {}
94
+ add_common(post, options)
95
+ add_card_transaction_name(post, authorization, options)
96
+
97
+ commit('capture', post)
98
+ end
99
+
100
+ def credit(money, payment, options = {})
101
+ post = {}
102
+ add_common(post, options)
103
+ add_card_payment(post, payment, options)
104
+ add_card_transaction_details(post, money, options)
105
+ add_address(post, payment, options)
106
+
107
+ commit('credit', post)
108
+ end
109
+
110
+ def void(authorization, options = {})
111
+ post = {}
112
+ add_common(post, options)
113
+ add_card_transaction_name(post, authorization, options)
114
+
115
+ commit('void', post)
116
+ end
117
+
118
+ def supports_scrubbing?
119
+ true
120
+ end
121
+
122
+ def scrub(transcript)
123
+ SCRUB_PATTERNS.inject(transcript) do |text, pattern|
124
+ text.gsub(pattern, SCRUB_REPLACEMENT)
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def add_common(post, options)
131
+ post[:userName] = @options[:login]
132
+ post[:password] = @options[:password]
133
+ post[:companyId] = @options[:company_id]
134
+ post[:cardTransaction] = {}
135
+ post[:cardTransaction][:sourceId] = @options[:source_id]
136
+ end
137
+
138
+ def add_address(post, payment, options)
139
+ post[:cardAccount] ||= {}
140
+ address = options[:billing_address] || options[:address] || {}
141
+ first_name, last_name = split_names(address[:name])
142
+ full_address = "#{address[:address1]} #{address[:address2]}".strip
143
+ phone = address[:phone] || address[:phone_number]
144
+
145
+ post[:cardAccount][:firstName] = first_name if first_name
146
+ post[:cardAccount][:lastName] = last_name if last_name
147
+ post[:cardAccount][:address] = full_address if full_address
148
+ post[:cardAccount][:city] = address[:city] if address[:city]
149
+ post[:cardAccount][:state] = address[:state] if address[:state]
150
+ post[:cardAccount][:zip] = address[:zip] if address[:zip]
151
+ post[:cardAccount][:phone] = phone if phone
152
+ end
153
+
154
+ def add_card_transaction_details(post, money, options)
155
+ post[:cardTransaction][:amount] = amount(money)
156
+ eci_type = options[:eci_type].nil? ? '1' : options[:eci_type]
157
+ post[:cardTransaction][:eciType] = eci_type
158
+ post[:cardTransaction][:processorSoftDescriptor] = options[:processor_soft_descriptor] if options[:processor_soft_descriptor]
159
+ post[:cardTransaction][:tax] = options[:tax] if options[:tax]
160
+ end
161
+
162
+ def add_card_transaction_name(post, identifier, options)
163
+ post[:cardTransaction][:name] = identifier
164
+ end
165
+
166
+ def add_card_payment(post, payment, options)
167
+ # credit_card
168
+ post[:accountInputMode] = 'primaryAccountNumber'
169
+
170
+ post[:cardAccount] ||= {}
171
+ post[:cardAccount][:accountNumber] = payment.number
172
+ post[:cardAccount][:fsv] = payment.verification_value
173
+ post[:cardAccount][:expirationDate] = expdate(payment)
174
+ post[:cardAccount][:email] = options[:email] if options[:email]
175
+ end
176
+
177
+ def expdate(credit_card)
178
+ year = format(credit_card.year, :four_digits)
179
+ month = format(credit_card.month, :two_digits)
180
+
181
+ month + year
182
+ end
183
+
184
+ def parse(body)
185
+ body.blank? ? {} : JSON.parse(body)
186
+ end
187
+
188
+ def commit(action, parameters)
189
+ parameters[:request] = action
190
+
191
+ url = (test? ? test_url : live_url)
192
+ payload = parameters.to_json unless parameters.nil?
193
+
194
+ response =
195
+ begin
196
+ parse(ssl_request(:post, url, payload, headers))
197
+ rescue ResponseError => e
198
+ return Response.new(false, 'Invalid Login') if e.response.code == '401'
199
+
200
+ parse(e.response.body)
201
+ end
202
+
203
+ success = success_from(response)
204
+ avs_result_code = response['cardTransaction'].nil? || response['cardTransaction']['addressVerificationResults'].nil? ? '' : response['cardTransaction']['addressVerificationResults']
205
+ avs_result = AVSResult.new(code: AVS_MAPPING[avs_result_code])
206
+ cvv_result = CVVResult.new(response['cardTransaction']['fraudSecurityResults']) if response['cardTransaction'] && response['cardTransaction']['fraudSecurityResults']
207
+
208
+ Response.new(
209
+ success,
210
+ message_from(success, response),
211
+ response,
212
+ test: test?,
213
+ error_code: error_code_from(response),
214
+ authorization: authorization_from(response),
215
+ avs_result: avs_result,
216
+ cvv_result: cvv_result
217
+ )
218
+ end
219
+
220
+ def success_from(response)
221
+ response['paywayCode'] == PAYWAY_WS_SUCCESS
222
+ end
223
+
224
+ def error_code_from(response)
225
+ return '' if success_from(response)
226
+
227
+ error = !STANDARD_ERROR_CODE_MAPPING[response['paywayCode']].nil? ? STANDARD_ERROR_CODE_MAPPING[response['paywayCode']] : STANDARD_ERROR_CODE[:processing_error]
228
+ return error
229
+ end
230
+
231
+ def message_from(success, response)
232
+ return '' if response['paywayCode'].nil?
233
+
234
+ return response['paywayCode'] + '-' + 'success' if success
235
+
236
+ response['paywayCode'] + '-' + response['paywayMessage']
237
+ end
238
+
239
+ def authorization_from(response)
240
+ return '' if !success_from(response) || response['cardTransaction'].nil?
241
+
242
+ response['cardTransaction']['name']
243
+ end
244
+
245
+ def headers
246
+ {
247
+ 'Accept' => 'application/json',
248
+ 'Content-type' => 'application/json'
249
+ }
250
+ end
251
+ end
252
+ end
253
+ end
@@ -30,6 +30,7 @@ module ActiveMerchant #:nodoc:
30
30
  add_address(post, creditcard, options)
31
31
  add_capture(post, options)
32
32
  add_metadata(post, options)
33
+ add_3ds(post, options)
33
34
 
34
35
  commit(:post, 'charges', post, options)
35
36
  end
@@ -158,6 +159,16 @@ module ActiveMerchant #:nodoc:
158
159
  post[:metadata] = options[:metadata] if options[:metadata]
159
160
  end
160
161
 
162
+ def add_3ds(post, options)
163
+ if options[:three_d_secure]
164
+ post[:three_d_secure] = {}
165
+ post[:three_d_secure][:version] = options[:three_d_secure][:version] if options[:three_d_secure][:version]
166
+ post[:three_d_secure][:eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
167
+ post[:three_d_secure][:cavv] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv]
168
+ post[:three_d_secure][:transaction_id] = options[:three_d_secure][:ds_transaction_id] || options[:three_d_secure][:xid]
169
+ end
170
+ end
171
+
161
172
  def headers(params = {})
162
173
  result = {
163
174
  'Content-Type' => 'application/json',