activemerchant 1.119.0 → 1.124.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +216 -1
  3. data/README.md +4 -2
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +3 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +32 -14
  8. data/lib/active_merchant/billing/gateways/adyen.rb +94 -25
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +3 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +52 -8
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  17. data/lib/active_merchant/billing/gateways/credorax.rb +15 -9
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +53 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +9 -2
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/elavon.rb +70 -28
  22. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  23. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  24. data/lib/active_merchant/billing/gateways/global_collect.rb +24 -10
  25. data/lib/active_merchant/billing/gateways/hps.rb +55 -1
  26. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  27. data/lib/active_merchant/billing/gateways/litle.rb +1 -1
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  29. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  30. data/lib/active_merchant/billing/gateways/mit.rb +260 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +290 -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 +26 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +27 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +99 -59
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  38. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  39. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  40. data/lib/active_merchant/billing/gateways/payeezy.rb +34 -6
  41. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  42. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  43. data/lib/active_merchant/billing/gateways/payment_express.rb +5 -5
  44. data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
  45. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  46. data/lib/active_merchant/billing/gateways/paysafe.rb +376 -0
  47. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  48. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  49. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  50. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  51. data/lib/active_merchant/billing/gateways/redsys.rb +42 -24
  52. data/lib/active_merchant/billing/gateways/safe_charge.rb +25 -13
  53. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  54. data/lib/active_merchant/billing/gateways/stripe.rb +18 -8
  55. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +126 -48
  56. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  58. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  59. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +78 -18
  61. data/lib/active_merchant/billing/response.rb +4 -0
  62. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  63. data/lib/active_merchant/billing.rb +1 -0
  64. data/lib/active_merchant/version.rb +1 -1
  65. data/lib/certs/cacert.pem +1582 -2431
  66. metadata +11 -3
@@ -0,0 +1,376 @@
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(AL AT BE BA BG CA HR CY CZ DK EE FI FR DE GR HU IS IE IT LV LI LT LU MT ME NL MK NO PL PT RO RS SK SI ES SE CH TR GB US)
8
+ self.supported_cardtypes = %i[visa master american_express discover]
9
+
10
+ self.homepage_url = 'https://www.paysafe.com/'
11
+ self.display_name = 'Paysafe'
12
+
13
+ def initialize(options = {})
14
+ requires!(options, :username, :password, :account_id)
15
+ super
16
+ end
17
+
18
+ def purchase(money, payment, options = {})
19
+ post = {}
20
+ add_invoice(post, money, options)
21
+ add_payment(post, payment)
22
+ add_billing_address(post, options)
23
+ add_merchant_details(post, options)
24
+ add_airline_travel_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
+ add_split_pay_details(post, options)
28
+ post[:settleWithAuth] = true
29
+
30
+ commit(:post, 'auths', post, options)
31
+ end
32
+
33
+ def authorize(money, payment, options = {})
34
+ post = {}
35
+ add_invoice(post, money, options)
36
+ add_payment(post, payment)
37
+ add_billing_address(post, options)
38
+ add_merchant_details(post, options)
39
+ add_customer_data(post, payment, options) unless payment.is_a?(String)
40
+ add_three_d_secure(post, payment, options) if options[:three_d_secure]
41
+
42
+ commit(:post, 'auths', post, options)
43
+ end
44
+
45
+ def capture(money, authorization, options = {})
46
+ post = {}
47
+ add_invoice(post, money, options)
48
+
49
+ commit(:post, "auths/#{authorization}/settlements", post, options)
50
+ end
51
+
52
+ def refund(money, authorization, options = {})
53
+ post = {}
54
+ add_invoice(post, money, options)
55
+
56
+ commit(:post, "settlements/#{authorization}/refunds", post, options)
57
+ end
58
+
59
+ def void(authorization, options = {})
60
+ post = {}
61
+ money = options[:amount]
62
+ add_invoice(post, money, options)
63
+
64
+ commit(:post, "auths/#{authorization}/voidauths", post, options)
65
+ end
66
+
67
+ def credit(money, payment, options = {})
68
+ post = {}
69
+ add_invoice(post, money, options)
70
+ add_payment(post, payment)
71
+
72
+ commit(:post, 'standalonecredits', post, options)
73
+ end
74
+
75
+ # This is a '$0 auth' done at a specific verification endpoint at the gateway
76
+ def verify(payment, options = {})
77
+ post = {}
78
+ add_payment(post, payment)
79
+ add_billing_address(post, options)
80
+ add_customer_data(post, payment, options) unless payment.is_a?(String)
81
+
82
+ commit(:post, 'verifications', post, options)
83
+ end
84
+
85
+ def store(payment, options = {})
86
+ post = {}
87
+ add_payment(post, payment)
88
+ add_address_for_vaulting(post, options)
89
+ add_profile_data(post, payment, options)
90
+ add_store_data(post, payment, options)
91
+
92
+ commit(:post, 'profiles', post, options)
93
+ end
94
+
95
+ def redact(pm_profile_id)
96
+ commit_for_redact(:delete, "profiles/#{pm_profile_id}", nil, nil)
97
+ end
98
+
99
+ def supports_scrubbing?
100
+ true
101
+ end
102
+
103
+ def scrub(transcript)
104
+ transcript.
105
+ gsub(%r((Authorization: Basic )[a-zA-Z0-9:_]+), '\1[FILTERED]').
106
+ gsub(%r(("cardNum\\?":\\?")\d+), '\1[FILTERED]').
107
+ gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]')
108
+ end
109
+
110
+ private
111
+
112
+ # Customer data can be included in transactions where the payment method is a credit card
113
+ # but should not be sent when the payment method is a token
114
+ def add_customer_data(post, creditcard, options)
115
+ post[:profile] = {}
116
+ post[:profile][:firstName] = creditcard.first_name
117
+ post[:profile][:lastName] = creditcard.last_name
118
+ post[:profile][:email] = options[:email] if options[:email]
119
+ post[:customerIp] = options[:ip] if options[:ip]
120
+ end
121
+
122
+ def add_billing_address(post, options)
123
+ return unless address = options[:billing_address] || options[:address]
124
+
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 address = options[:billing_address] || options[:address]
138
+
139
+ post[:card][:billingAddress] = {}
140
+ post[:card][:billingAddress][:street] = address[:address1]
141
+ post[:card][:billingAddress][:street2] = address[:address2]
142
+ post[:card][:billingAddress][:city] = address[:city]
143
+ post[:card][:billingAddress][:zip] = address[:zip]
144
+ post[:card][:billingAddress][:country] = address[:country]
145
+ post[:card][: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
+ post[:firstName] = payment.first_name
151
+ post[:lastName] = payment.last_name
152
+ post[:dateOfBirth] = {}
153
+ post[:dateOfBirth][:year] = options[:date_of_birth][:year]
154
+ post[:dateOfBirth][:month] = options[:date_of_birth][:month]
155
+ post[:dateOfBirth][:day] = options[:date_of_birth][:day]
156
+ post[:email] = options[:email] if options[:email]
157
+ post[:ip] = options[:ip] if options[:ip]
158
+
159
+ if options[:phone]
160
+ post[:phone] = options[:phone]
161
+ elsif address = options[:billing_address] || options[:address]
162
+ post[:phone] = address[:phone] if address[:phone]
163
+ end
164
+ end
165
+
166
+ def add_store_data(post, payment, options)
167
+ post[:merchantCustomerId] = options[:customer_id] || SecureRandom.hex(12)
168
+ post[:locale] = options[:locale] || 'en_US'
169
+ post[:card][:holderName] = payment.name
170
+ end
171
+
172
+ # Paysafe expects minor units so we are not calling amount method on money parameter
173
+ def add_invoice(post, money, options)
174
+ post[:amount] = money
175
+ end
176
+
177
+ def add_payment(post, payment)
178
+ if payment.is_a?(String)
179
+ post[:card] = {}
180
+ post[:card][:paymentToken] = payment
181
+ else
182
+ post[:card] = { cardExpiry: {} }
183
+ post[:card][:cardNum] = payment.number
184
+ post[:card][:cardExpiry][:month] = payment.month
185
+ post[:card][:cardExpiry][:year] = payment.year
186
+ post[:card][:cvv] = payment.verification_value
187
+ end
188
+ end
189
+
190
+ def add_merchant_details(post, options)
191
+ return unless options[:merchant_descriptor]
192
+
193
+ post[:merchantDescriptor] = {}
194
+ post[:merchantDescriptor][:dynamicDescriptor] = options[:merchant_descriptor][:dynamic_descriptor] if options[:merchant_descriptor][:dynamic_descriptor]
195
+ post[:merchantDescriptor][:phone] = options[:merchant_descriptor][:phone] if options[:merchant_descriptor][:phone]
196
+ end
197
+
198
+ def add_three_d_secure(post, payment, options)
199
+ three_d_secure = options[:three_d_secure]
200
+
201
+ post[:authentication] = {}
202
+ post[:authentication][:eci] = three_d_secure[:eci]
203
+ post[:authentication][:cavv] = three_d_secure[:cavv]
204
+ post[:authentication][:xid] = three_d_secure[:xid] if three_d_secure[:xid]
205
+ post[:authentication][:threeDSecureVersion] = three_d_secure[:version]
206
+ post[:authentication][:directoryServerTransactionId] = three_d_secure[:ds_transaction_id] unless payment.is_a?(String) || payment.brand != 'mastercard'
207
+ end
208
+
209
+ def add_airline_travel_details(post, options)
210
+ return unless options[:airline_travel_details]
211
+
212
+ post[:airlineTravelDetails] = {}
213
+ post[:airlineTravelDetails][:passengerName] = options[:airline_travel_details][:passenger_name] if options[:airline_travel_details][:passenger_name]
214
+ post[:airlineTravelDetails][:departureDate] = options[:airline_travel_details][:departure_date] if options[:airline_travel_details][:departure_date]
215
+ post[:airlineTravelDetails][:origin] = options[:airline_travel_details][:origin] if options[:airline_travel_details][:origin]
216
+ post[:airlineTravelDetails][:computerizedReservationSystem] = options[:airline_travel_details][:computerized_reservation_system] if options[:airline_travel_details][:computerized_reservation_system]
217
+ post[:airlineTravelDetails][:customerReferenceNumber] = options[:airline_travel_details][:customer_reference_number] if options[:airline_travel_details][:customer_reference_number]
218
+
219
+ add_ticket_details(post, options)
220
+ add_travel_agency_details(post, options)
221
+ add_trip_legs(post, options)
222
+ end
223
+
224
+ def add_ticket_details(post, options)
225
+ return unless ticket = options[:airline_travel_details][:ticket]
226
+
227
+ post[:airlineTravelDetails][:ticket] = {}
228
+ post[:airlineTravelDetails][:ticket][:ticketNumber] = ticket[:ticket_number] if ticket[:ticket_number]
229
+ post[:airlineTravelDetails][:ticket][:isRestrictedTicket] = ticket[:is_restricted_ticket] if ticket[:is_restricted_ticket]
230
+ end
231
+
232
+ def add_travel_agency_details(post, options)
233
+ return unless agency = options[:airline_travel_details][:travel_agency]
234
+
235
+ post[:airlineTravelDetails][:travelAgency] = {}
236
+ post[:airlineTravelDetails][:travelAgency][:name] = agency[:name] if agency[:name]
237
+ post[:airlineTravelDetails][:travelAgency][:code] = agency[:code] if agency[:code]
238
+ end
239
+
240
+ def add_trip_legs(post, options)
241
+ return unless trip_legs = options[:airline_travel_details][:trip_legs]
242
+
243
+ trip_legs_hash = {}
244
+ trip_legs.each.with_index(1) do |leg, i|
245
+ my_leg = "leg#{i}".to_sym
246
+ details = add_leg_details(my_leg, leg[1])
247
+
248
+ trip_legs_hash[my_leg] = details
249
+ end
250
+ post[:airlineTravelDetails][:tripLegs] = trip_legs_hash
251
+ end
252
+
253
+ def add_leg_details(obj, leg)
254
+ details = {}
255
+ add_flight_details(details, obj, leg)
256
+ details[:serviceClass] = leg[:service_class] if leg[:service_class]
257
+ details[:isStopOverAllowed] = leg[:is_stop_over_allowed] if leg[:is_stop_over_allowed]
258
+ details[:destination] = leg[:destination] if leg[:destination]
259
+ details[:fareBasis] = leg[:fare_basis] if leg[:fare_basis]
260
+ details[:departureDate] = leg[:departure_date] if leg[:departure_date]
261
+
262
+ details
263
+ end
264
+
265
+ def add_flight_details(details, obj, leg)
266
+ details[:flight] = {}
267
+ details[:flight][:carrierCode] = leg[:flight][:carrier_code] if leg[:flight][:carrier_code]
268
+ details[:flight][:flightNumber] = leg[:flight][:flight_number] if leg[:flight][:flight_number]
269
+ end
270
+
271
+ def add_split_pay_details(post, options)
272
+ return unless options[:split_pay]
273
+
274
+ split_pay = []
275
+ options[:split_pay].each do |pmnt|
276
+ split = {}
277
+
278
+ split[:linkedAccount] = pmnt[:linked_account]
279
+ split[:amount] = pmnt[:amount].to_i if pmnt[:amount]
280
+ split[:percent] = pmnt[:percent].to_i if pmnt[:percent]
281
+
282
+ split_pay << split
283
+ end
284
+ post[:splitpay] = split_pay
285
+ end
286
+
287
+ def parse(body)
288
+ JSON.parse(body)
289
+ end
290
+
291
+ def commit(method, action, parameters, options)
292
+ url = url(action)
293
+ raw_response = ssl_request(method, url, post_data(parameters, options), headers)
294
+ response = parse(raw_response)
295
+ success = success_from(response)
296
+
297
+ Response.new(
298
+ success,
299
+ message_from(success, response),
300
+ response,
301
+ authorization: authorization_from(action, response),
302
+ avs_result: AVSResult.new(code: response['avsResponse']),
303
+ cvv_result: CVVResult.new(response['cvvVerification']),
304
+ test: test?,
305
+ error_code: success ? nil : error_code_from(response)
306
+ )
307
+ end
308
+
309
+ def commit_for_redact(method, action, parameters, options)
310
+ url = url(action)
311
+ response = raw_ssl_request(method, url, post_data(parameters, options), headers)
312
+ success = true if response.code == '200'
313
+
314
+ Response.new(
315
+ success,
316
+ message: response.message
317
+ )
318
+ end
319
+
320
+ def headers
321
+ {
322
+ 'Content-Type' => 'application/json',
323
+ 'Authorization' => "Basic #{Base64.strict_encode64(@options[:api_key].to_s)}"
324
+ }
325
+ end
326
+
327
+ def url(action, options = {})
328
+ base_url = (test? ? test_url : live_url)
329
+
330
+ if action.include? 'profiles'
331
+ "#{base_url}/customervault/v1/#{action}"
332
+ else
333
+ "#{base_url}/cardpayments/v1/accounts/#{@options[:account_id]}/#{action}"
334
+ end
335
+ end
336
+
337
+ def success_from(response)
338
+ return false if response['status'] == 'FAILED' || response['error']
339
+
340
+ true
341
+ end
342
+
343
+ def message_from(success, response)
344
+ return response['status'] unless response['error']
345
+
346
+ "Error(s)- code:#{response['error']['code']}, message:#{response['error']['message']}"
347
+ end
348
+
349
+ def authorization_from(action, response)
350
+ if action == 'profiles'
351
+ response['cards'].first['paymentToken']
352
+ else
353
+ response['id']
354
+ end
355
+ end
356
+
357
+ def post_data(parameters = {}, options = {})
358
+ return unless parameters.present?
359
+
360
+ parameters[:merchantRefNum] = options[:merchant_ref_num] || SecureRandom.hex(16).to_s
361
+
362
+ parameters.to_json
363
+ end
364
+
365
+ def error_code_from(response)
366
+ return unless response['error']
367
+
368
+ response['error']['code']
369
+ end
370
+
371
+ def handle_response(response)
372
+ response.body
373
+ end
374
+ end
375
+ end
376
+ 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