activemerchant 1.121.0 → 1.125.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 +217 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -19
  5. data/lib/active_merchant/billing/credit_card.rb +13 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +24 -12
  8. data/lib/active_merchant/billing/gateway.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/adyen.rb +75 -27
  10. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  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 +6 -3
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
  17. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +41 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +12 -6
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
  22. data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
  23. data/lib/active_merchant/billing/gateways/elavon.rb +65 -30
  24. data/lib/active_merchant/billing/gateways/element.rb +22 -2
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +130 -26
  26. data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
  27. data/lib/active_merchant/billing/gateways/kushki.rb +30 -0
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +6 -3
  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 +22 -11
  34. data/lib/active_merchant/billing/gateways/nmi.rb +29 -10
  35. data/lib/active_merchant/billing/gateways/orbital.rb +46 -8
  36. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  37. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
  44. data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
  45. data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
  46. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  47. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  48. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  49. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  50. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  51. data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
  52. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  53. data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
  54. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
  55. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  56. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
  58. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  59. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
  61. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  62. data/lib/active_merchant/billing/response.rb +4 -0
  63. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  64. data/lib/active_merchant/billing.rb +1 -0
  65. data/lib/active_merchant/version.rb +1 -1
  66. metadata +13 -3
@@ -1,11 +1,14 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class GlobalCollectGateway < Gateway
4
+ class_attribute :preproduction_url
5
+
4
6
  self.display_name = 'GlobalCollect'
5
7
  self.homepage_url = 'http://www.globalcollect.com/'
6
8
 
7
9
  self.test_url = 'https://eu.sandbox.api-ingenico.com'
8
- self.live_url = 'https://api.globalcollect.com'
10
+ self.preproduction_url = 'https://world.preprod.api-ingenico.com'
11
+ self.live_url = 'https://world.api-ingenico.com'
9
12
 
10
13
  self.supported_countries = %w[AD AE AG AI AL AM AO AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BW BY BZ CA CC CD CF CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HN HR HT HU ID IE IL IM IN IS IT JM JO JP KE KG KH KI KM KN KR KW KY KZ LA LB LC LI LK LR LS LT LU LV MA MC MD ME MF MG MH MK MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PL PN PS PT PW QA RE RO RS RU RW SA SB SC SE SG SH SI SJ SK SL SM SN SR ST SV SZ TC TD TG TH TJ TL TM TN TO TR TT TV TW TZ UA UG US UY UZ VC VE VG VI VN WF WS ZA ZM ZW]
11
14
  self.default_currency = 'USD'
@@ -33,7 +36,7 @@ module ActiveMerchant #:nodoc:
33
36
  add_creator_info(post, options)
34
37
  add_fraud_fields(post, options)
35
38
  add_external_cardholder_authentication_data(post, options)
36
- commit(:authorize, post)
39
+ commit(:authorize, post, options: options)
37
40
  end
38
41
 
39
42
  def capture(money, authorization, options = {})
@@ -41,7 +44,7 @@ module ActiveMerchant #:nodoc:
41
44
  add_order(post, money, options, capture: true)
42
45
  add_customer_data(post, options)
43
46
  add_creator_info(post, options)
44
- commit(:capture, post, authorization)
47
+ commit(:capture, post, authorization: authorization)
45
48
  end
46
49
 
47
50
  def refund(money, authorization, options = {})
@@ -49,13 +52,13 @@ module ActiveMerchant #:nodoc:
49
52
  add_amount(post, money, options)
50
53
  add_refund_customer_data(post, options)
51
54
  add_creator_info(post, options)
52
- commit(:refund, post, authorization)
55
+ commit(:refund, post, authorization: authorization)
53
56
  end
54
57
 
55
58
  def void(authorization, options = {})
56
59
  post = nestable_hash
57
60
  add_creator_info(post, options)
58
- commit(:void, post, authorization)
61
+ commit(:void, post, authorization: authorization)
59
62
  end
60
63
 
61
64
  def verify(payment, options = {})
@@ -100,36 +103,126 @@ module ActiveMerchant #:nodoc:
100
103
  post['order']['references']['invoiceData'] = {
101
104
  'invoiceNumber' => options[:invoice]
102
105
  }
103
- add_airline_data(post, options) if options[:airline_data]
106
+ add_airline_data(post, options)
107
+ add_lodging_data(post, options)
104
108
  add_number_of_installments(post, options) if options[:number_of_installments]
105
109
  end
106
110
 
107
111
  def add_airline_data(post, options)
112
+ return unless airline_options = options[:airline_data]
113
+
108
114
  airline_data = {}
109
115
 
110
- flight_date = options[:airline_data][:flight_date]
111
- passenger_name = options[:airline_data][:passenger_name]
112
- code = options[:airline_data][:code]
113
- name = options[:airline_data][:name]
116
+ airline_data['flightDate'] = airline_options[:flight_date] if airline_options[:flight_date]
117
+ airline_data['passengerName'] = airline_options[:passenger_name] if airline_options[:passenger_name]
118
+ airline_data['code'] = airline_options[:code] if airline_options[:code]
119
+ airline_data['name'] = airline_options[:name] if airline_options[:name]
120
+ airline_data['invoiceNumber'] = options[:airline_data][:invoice_number] if options[:airline_data][:invoice_number]
121
+ airline_data['isETicket'] = options[:airline_data][:is_eticket] if options[:airline_data][:is_eticket]
122
+ airline_data['isRestrictedTicket'] = options[:airline_data][:is_restricted_ticket] if options[:airline_data][:is_restricted_ticket]
123
+ airline_data['isThirdParty'] = options[:airline_data][:is_third_party] if options[:airline_data][:is_third_party]
124
+ airline_data['issueDate'] = options[:airline_data][:issue_date] if options[:airline_data][:issue_date]
125
+ airline_data['merchantCustomerId'] = options[:airline_data][:merchant_customer_id] if options[:airline_data][:merchant_customer_id]
126
+ airline_data['flightLegs'] = add_flight_legs(airline_options)
127
+ airline_data['passengers'] = add_passengers(airline_options)
114
128
 
115
- airline_data['flightDate'] = flight_date if flight_date
116
- airline_data['passengerName'] = passenger_name if passenger_name
117
- airline_data['code'] = code if code
118
- airline_data['name'] = name if name
129
+ post['order']['additionalInput']['airlineData'] = airline_data
130
+ end
119
131
 
132
+ def add_flight_legs(airline_options)
120
133
  flight_legs = []
121
- options[:airline_data][:flight_legs]&.each do |fl|
134
+ airline_options[:flight_legs]&.each do |fl|
122
135
  leg = {}
136
+ leg['airlineClass'] = fl[:airline_class] if fl[:airline_class]
123
137
  leg['arrivalAirport'] = fl[:arrival_airport] if fl[:arrival_airport]
124
- leg['originAirport'] = fl[:origin_airport] if fl[:origin_airport]
138
+ leg['arrivalTime'] = fl[:arrival_time] if fl[:arrival_time]
139
+ leg['carrierCode'] = fl[:carrier_code] if fl[:carrier_code]
140
+ leg['conjunctionTicket'] = fl[:conjunction_ticket] if fl[:conjunction_ticket]
141
+ leg['couponNumber'] = fl[:coupon_number] if fl[:coupon_number]
125
142
  leg['date'] = fl[:date] if fl[:date]
143
+ leg['departureTime'] = fl[:departure_time] if fl[:departure_time]
144
+ leg['endorsementOrRestriction'] = fl[:endorsement_or_restriction] if fl[:endorsement_or_restriction]
145
+ leg['exchangeTicket'] = fl[:exchange_ticket] if fl[:exchange_ticket]
146
+ leg['fare'] = fl[:fare] if fl[:fare]
147
+ leg['fareBasis'] = fl[:fare_basis] if fl[:fare_basis]
148
+ leg['fee'] = fl[:fee] if fl[:fee]
149
+ leg['flightNumber'] = fl[:flight_number] if fl[:flight_number]
126
150
  leg['number'] = fl[:number] if fl[:number]
127
- leg['carrierCode'] = fl[:carrier_code] if fl[:carrier_code]
128
- leg['airlineClass'] = fl[:carrier_code] if fl[:airline_class]
151
+ leg['originAirport'] = fl[:origin_airport] if fl[:origin_airport]
152
+ leg['passengerClass'] = fl[:passenger_class] if fl[:passenger_class]
153
+ leg['stopoverCode'] = fl[:stopover_code] if fl[:stopover_code]
154
+ leg['taxes'] = fl[:taxes] if fl[:taxes]
129
155
  flight_legs << leg
130
156
  end
131
- airline_data['flightLegs'] = flight_legs
132
- post['order']['additionalInput']['airlineData'] = airline_data
157
+ flight_legs
158
+ end
159
+
160
+ def add_passengers(airline_options)
161
+ passengers = []
162
+ airline_options[:passengers]&.each do |flyer|
163
+ passenger = {}
164
+ passenger['firstName'] = flyer[:first_name] if flyer[:first_name]
165
+ passenger['surname'] = flyer[:surname] if flyer[:surname]
166
+ passenger['surnamePrefix'] = flyer[:surname_prefix] if flyer[:surname_prefix]
167
+ passenger['title'] = flyer[:title] if flyer[:title]
168
+ passengers << passenger
169
+ end
170
+ passengers
171
+ end
172
+
173
+ def add_lodging_data(post, options)
174
+ return unless lodging_options = options[:lodging_data]
175
+
176
+ lodging_data = {}
177
+
178
+ lodging_data['charges'] = add_charges(lodging_options)
179
+ lodging_data['checkInDate'] = lodging_options[:check_in_date] if lodging_options[:check_in_date]
180
+ lodging_data['checkOutDate'] = lodging_options[:check_out_date] if lodging_options[:check_out_date]
181
+ lodging_data['folioNumber'] = lodging_options[:folio_number] if lodging_options[:folio_number]
182
+ lodging_data['isConfirmedReservation'] = lodging_options[:is_confirmed_reservation] if lodging_options[:is_confirmed_reservation]
183
+ lodging_data['isFacilityFireSafetyConform'] = lodging_options[:is_facility_fire_safety_conform] if lodging_options[:is_facility_fire_safety_conform]
184
+ lodging_data['isNoShow'] = lodging_options[:is_no_show] if lodging_options[:is_no_show]
185
+ lodging_data['isPreferenceSmokingRoom'] = lodging_options[:is_preference_smoking_room] if lodging_options[:is_preference_smoking_room]
186
+ lodging_data['numberOfAdults'] = lodging_options[:number_of_adults] if lodging_options[:number_of_adults]
187
+ lodging_data['numberOfNights'] = lodging_options[:number_of_nights] if lodging_options[:number_of_nights]
188
+ lodging_data['numberOfRooms'] = lodging_options[:number_of_rooms] if lodging_options[:number_of_rooms]
189
+ lodging_data['programCode'] = lodging_options[:program_code] if lodging_options[:program_code]
190
+ lodging_data['propertyCustomerServicePhoneNumber'] = lodging_options[:property_customer_service_phone_number] if lodging_options[:property_customer_service_phone_number]
191
+ lodging_data['propertyPhoneNumber'] = lodging_options[:property_phone_number] if lodging_options[:property_phone_number]
192
+ lodging_data['renterName'] = lodging_options[:renter_name] if lodging_options[:renter_name]
193
+ lodging_data['rooms'] = add_rooms(lodging_options)
194
+
195
+ post['order']['additionalInput']['lodgingData'] = lodging_data
196
+ end
197
+
198
+ def add_charges(lodging_options)
199
+ charges = []
200
+ lodging_options[:charges]&.each do |item|
201
+ charge = {}
202
+ charge['chargeAmount'] = item[:charge_amount] if item[:charge_amount]
203
+ charge['chargeAmountCurrencyCode'] = item[:charge_amount_currency_code] if item[:charge_amount_currency_code]
204
+ charge['chargeType'] = item[:charge_type] if item[:charge_type]
205
+ charges << charge
206
+ end
207
+ charges
208
+ end
209
+
210
+ def add_rooms(lodging_options)
211
+ rooms = []
212
+ lodging_options[:rooms]&.each do |item|
213
+ room = {}
214
+ room['dailyRoomRate'] = item[:daily_room_rate] if item[:daily_room_rate]
215
+ room['dailyRoomRateCurrencyCode'] = item[:daily_room_rate_currency_code] if item[:daily_room_rate_currency_code]
216
+ room['dailyRoomTaxAmount'] = item[:daily_room_tax_amount] if item[:daily_room_tax_amount]
217
+ room['dailyRoomTaxAmountCurrencyCode'] = item[:daily_room_tax_amount_currency_code] if item[:daily_room_tax_amount_currency_code]
218
+ room['numberOfNightsAtRoomRate'] = item[:number_of_nights_at_room_rate] if item[:number_of_nights_at_room_rate]
219
+ room['roomLocation'] = item[:room_location] if item[:room_location]
220
+ room['roomNumber'] = item[:room_number] if item[:room_number]
221
+ room['typeOfBed'] = item[:type_of_bed] if item[:type_of_bed]
222
+ room['typeOfRoom'] = item[:type_of_room] if item[:type_of_room]
223
+ rooms << room
224
+ end
225
+ rooms
133
226
  end
134
227
 
135
228
  def add_creator_info(post, options)
@@ -260,6 +353,8 @@ module ActiveMerchant #:nodoc:
260
353
  end
261
354
 
262
355
  def url(action, authorization)
356
+ return preproduction_url + uri(action, authorization) if @options[:url_override].to_s == 'preproduction'
357
+
263
358
  (test? ? test_url : live_url) + uri(action, authorization)
264
359
  end
265
360
 
@@ -277,9 +372,13 @@ module ActiveMerchant #:nodoc:
277
372
  end
278
373
  end
279
374
 
280
- def commit(action, post, authorization = nil)
375
+ def idempotency_key_for_signature(options)
376
+ "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
377
+ end
378
+
379
+ def commit(action, post, authorization: nil, options: {})
281
380
  begin
282
- raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization))
381
+ raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization, options))
283
382
  response = parse(raw_response)
284
383
  rescue ResponseError => e
285
384
  response = parse(e.response.body) if e.response.code.to_i >= 400
@@ -306,21 +405,26 @@ module ActiveMerchant #:nodoc:
306
405
  }
307
406
  end
308
407
 
309
- def headers(action, post, authorization = nil)
310
- {
408
+ def headers(action, post, authorization = nil, options = {})
409
+ headers = {
311
410
  'Content-Type' => content_type,
312
- 'Authorization' => auth_digest(action, post, authorization),
411
+ 'Authorization' => auth_digest(action, post, authorization, options),
313
412
  'Date' => date
314
413
  }
414
+
415
+ headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key]
416
+ headers
315
417
  end
316
418
 
317
- def auth_digest(action, post, authorization = nil)
419
+ def auth_digest(action, post, authorization = nil, options = {})
318
420
  data = <<~REQUEST
319
421
  POST
320
422
  #{content_type}
321
423
  #{date}
424
+ #{idempotency_key_for_signature(options)}
322
425
  #{uri(action, authorization)}
323
426
  REQUEST
427
+ data = data.each_line.reject { |line| line.strip == '' }.join
324
428
  digest = OpenSSL::Digest.new('sha256')
325
429
  key = @options[:secret_api_key]
326
430
  "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}"
@@ -0,0 +1,416 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class IpgGateway < Gateway
4
+ self.test_url = 'https://test.ipg-online.com/ipgapi/services'
5
+ self.live_url = 'https://www5.ipg-online.com'
6
+
7
+ self.supported_countries = %w(UY AR)
8
+ self.default_currency = 'ARS'
9
+ self.supported_cardtypes = %i[visa master american_express discover]
10
+
11
+ self.homepage_url = 'https://www.ipg-online.com'
12
+ self.display_name = 'IPG'
13
+
14
+ CURRENCY_CODES = {
15
+ 'UYU' => '858',
16
+ 'ARS' => '032'
17
+ }
18
+
19
+ ACTION_REQUEST_ITEMS = %w(vault unstore)
20
+
21
+ def initialize(options = {})
22
+ requires!(options, :store_id, :user_id, :password, :pem, :pem_password)
23
+ @credentials = options
24
+ @hosted_data_id = nil
25
+ super
26
+ end
27
+
28
+ def purchase(money, payment, options = {})
29
+ xml = build_purchase_and_authorize_request(money, payment, options)
30
+
31
+ commit('sale', xml)
32
+ end
33
+
34
+ def authorize(money, payment, options = {})
35
+ xml = build_purchase_and_authorize_request(money, payment, options)
36
+
37
+ commit('preAuth', xml)
38
+ end
39
+
40
+ def capture(money, authorization, options = {})
41
+ xml = build_capture_and_refund_request(money, authorization, options)
42
+
43
+ commit('postAuth', xml)
44
+ end
45
+
46
+ def refund(money, authorization, options = {})
47
+ xml = build_capture_and_refund_request(money, authorization, options)
48
+
49
+ commit('return', xml)
50
+ end
51
+
52
+ def void(authorization, options = {})
53
+ xml = Builder::XmlMarkup.new(indent: 2)
54
+ add_transaction_details(xml, options.merge!({ order_id: authorization }))
55
+
56
+ commit('void', xml)
57
+ end
58
+
59
+ def store(credit_card, options = {})
60
+ @hosted_data_id = options[:hosted_data_id] || generate_unique_id
61
+ xml = Builder::XmlMarkup.new(indent: 2)
62
+ add_storage_item(xml, credit_card, options)
63
+
64
+ commit('vault', xml)
65
+ end
66
+
67
+ def unstore(hosted_data_id)
68
+ xml = Builder::XmlMarkup.new(indent: 2)
69
+ add_unstore_item(xml, hosted_data_id)
70
+
71
+ commit('unstore', xml)
72
+ end
73
+
74
+ def verify(credit_card, options = {})
75
+ options[:currency] = self.default_currency unless options[:currency] && !options[:currency].empty?
76
+ MultiResponse.run(:use_first_response) do |r|
77
+ r.process { authorize(100, credit_card, options) }
78
+ r.process(:ignore_result) { void(r.authorization, options) }
79
+ end
80
+ end
81
+
82
+ def supports_scrubbing?
83
+ true
84
+ end
85
+
86
+ def scrub(transcript)
87
+ transcript.
88
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
89
+ gsub(%r((<v1:CardNumber>).+(</v1:CardNumber>)), '\1[FILTERED]\2').
90
+ gsub(%r((<v1:CardCodeValue>).+(</v1:CardCodeValue>)), '\1[FILTERED]\2').
91
+ gsub(%r((<v1:StoreId>).+(</v1:StoreId>)), '\1[FILTERED]\2')
92
+ end
93
+
94
+ private
95
+
96
+ NAMESPACE_BASE_URL = 'http://ipg-online.com'
97
+
98
+ def build_purchase_and_authorize_request(money, payment, options)
99
+ xml = Builder::XmlMarkup.new(indent: 2)
100
+
101
+ add_credit_card(xml, payment, options)
102
+ add_sub_merchant(xml, options[:submerchant]) if options[:submerchant]
103
+ add_three_d_secure(xml, options[:three_d_secure]) if options[:three_d_secure]
104
+ add_stored_credentials(xml, options) if options[:stored_credential] || options[:recurring_type]
105
+ add_payment(xml, money, payment, options)
106
+ add_transaction_details(xml, options)
107
+ add_billing(xml, options[:billing]) if options[:billing]
108
+ add_shipping(xml, options[:shipping]) if options[:shipping]
109
+ xml
110
+ end
111
+
112
+ def build_capture_and_refund_request(money, authorization, options)
113
+ xml = Builder::XmlMarkup.new(indent: 2)
114
+ add_payment(xml, money, nil, options)
115
+ add_transaction_details(xml, options.merge!({ order_id: authorization }), true)
116
+ xml
117
+ end
118
+
119
+ def build_order_request(xml, action, body)
120
+ xml.tag!('ipg:IPGApiOrderRequest') do
121
+ xml.tag!('v1:Transaction') do
122
+ add_transaction_type(xml, action)
123
+ xml << body.target!
124
+ end
125
+ end
126
+ end
127
+
128
+ def build_action_request(xml, action, body)
129
+ xml.tag!('ns4:IPGApiActionRequest', ipg_action_namespaces) do
130
+ xml.tag!('ns2:Action') do
131
+ xml << body.target!
132
+ end
133
+ end
134
+ end
135
+
136
+ def build_soap_request(action, body)
137
+ xml = Builder::XmlMarkup.new(indent: 2)
138
+ xml.tag!('soapenv:Envelope', envelope_namespaces) do
139
+ xml.tag!('soapenv:Header')
140
+ xml.tag!('soapenv:Body') do
141
+ build_order_request(xml, action, body) unless ACTION_REQUEST_ITEMS.include?(action)
142
+ build_action_request(xml, action, body) if ACTION_REQUEST_ITEMS.include?(action)
143
+ end
144
+ end
145
+ xml.target!
146
+ end
147
+
148
+ def add_stored_credentials(xml, params)
149
+ recurring_type = params[:stored_credential][:initial_transaction] ? 'FIRST' : 'REPEAT' if params[:stored_credential]
150
+ recurring_type = params[:recurring_type] if params[:recurring_type]
151
+ xml.tag!('v1:recurringType', recurring_type)
152
+ end
153
+
154
+ def add_storage_item(xml, credit_card, options)
155
+ requires!(options.merge!({ credit_card: credit_card, hosted_data_id: @hosted_data_id }), :credit_card, :hosted_data_id)
156
+ xml.tag!('ns2:StoreHostedData') do
157
+ xml.tag!('ns2:DataStorageItem') do
158
+ add_credit_card(xml, credit_card, {}, 'ns2')
159
+ add_three_d_secure(xml, options[:three_d_secure]) if options[:three_d_secure]
160
+ xml.tag!('ns2:HostedDataID', @hosted_data_id) if @hosted_data_id
161
+ end
162
+ end
163
+ end
164
+
165
+ def add_unstore_item(xml, hosted_data_id)
166
+ requires!({}.merge!({ hosted_data_id: hosted_data_id }), :hosted_data_id)
167
+ xml.tag!('ns2:StoreHostedData') do
168
+ xml.tag!('ns2:DataStorageItem') do
169
+ xml.tag!('ns2:Function', 'delete')
170
+ xml.tag!('ns2:HostedDataID', hosted_data_id)
171
+ end
172
+ end
173
+ end
174
+
175
+ def add_transaction_type(xml, type)
176
+ xml.tag!('v1:CreditCardTxType') do
177
+ xml.tag!('v1:StoreId', @credentials[:store_id])
178
+ xml.tag!('v1:Type', type)
179
+ end
180
+ end
181
+
182
+ def add_credit_card(xml, payment, options = {}, credit_envelope = 'v1')
183
+ if payment&.is_a?(CreditCard)
184
+ requires!(options.merge!({ card_number: payment.number, month: payment.month, year: payment.year }), :card_number, :month, :year)
185
+
186
+ xml.tag!("#{credit_envelope}:CreditCardData") do
187
+ xml.tag!('v1:CardNumber', payment.number) if payment.number
188
+ xml.tag!('v1:ExpMonth', format(payment.month, :two_digits)) if payment.month
189
+ xml.tag!('v1:ExpYear', format(payment.year, :two_digits)) if payment.year
190
+ xml.tag!('v1:CardCodeValue', payment.verification_value) if payment.verification_value
191
+ xml.tag!('v1:Brand', options[:brand]) if options[:brand]
192
+ end
193
+ end
194
+
195
+ if options[:card_function_type]
196
+ xml.tag!('v1:cardFunction') do
197
+ xml.tag!('v1:Type', options[:card_function_type])
198
+ end
199
+ end
200
+
201
+ if options[:track_data]
202
+ xml.tag!("#{credit_envelope}:CreditCardData") do
203
+ xml.tag!('v1:TrackData', options[:track_data])
204
+ end
205
+ end
206
+ end
207
+
208
+ def add_sub_merchant(xml, submerchant)
209
+ xml.tag!('v1:SubMerchant') do
210
+ xml.tag!('v1:Mcc', submerchant[:mcc]) if submerchant[:mcc]
211
+ xml.tag!('v1:LegalName', submerchant[:legal_name]) if submerchant[:legal_name]
212
+ add_address(xml, submerchant[:address]) if submerchant[:address]
213
+ add_document(xml, submerchant[:document]) if submerchant[:document]
214
+ xml.tag!('v1:MerchantID', submerchant[:merchant_id]) if submerchant[:merchant_id]
215
+ end
216
+ end
217
+
218
+ def add_address(xml, address)
219
+ xml.tag!('v1:Address') do
220
+ xml.tag!('v1:Address1', address[:address1]) if address[:address1]
221
+ xml.tag!('v1:Address2', address[:address2]) if address[:address2]
222
+ xml.tag!('v1:Zip', address[:zip]) if address[:zip]
223
+ xml.tag!('v1:City', address[:city]) if address[:city]
224
+ xml.tag!('v1:State', address[:state]) if address[:state]
225
+ xml.tag!('v1:Country', address[:country]) if address[:country]
226
+ end
227
+ end
228
+
229
+ def add_document(xml, document)
230
+ xml.tag!('v1:Document') do
231
+ xml.tag!('v1:Type', document[:type]) if document[:type]
232
+ xml.tag!('v1:Number', document[:number]) if document[:number]
233
+ end
234
+ end
235
+
236
+ def add_three_d_secure(xml, three_d_secure)
237
+ xml.tag!('v1:CreditCard3DSecure') do
238
+ xml.tag!('v1:AuthenticationValue', three_d_secure[:cavv]) if three_d_secure[:cavv]
239
+ xml.tag!('v1:XID', three_d_secure[:xid]) if three_d_secure[:xid]
240
+ xml.tag!('v1:Secure3D2TransactionStatus', three_d_secure[:directory_response_status]) if three_d_secure[:directory_response_status]
241
+ xml.tag!('v1:Secure3D2AuthenticationResponse', three_d_secure[:authentication_response_status]) if three_d_secure[:authentication_response_status]
242
+ xml.tag!('v1:Secure3DProtocolVersion', three_d_secure[:version]) if three_d_secure[:version]
243
+ xml.tag!('v1:DirectoryServerTransactionId', three_d_secure[:ds_transaction_id]) if three_d_secure[:ds_transaction_id]
244
+ end
245
+ end
246
+
247
+ def add_transaction_details(xml, options, pre_order = false)
248
+ requires!(options, :order_id) if pre_order
249
+ xml.tag!('v1:TransactionDetails') do
250
+ xml.tag!('v1:OrderId', options[:order_id]) if options[:order_id]
251
+ xml.tag!('v1:MerchantTransactionId', options[:merchant_transaction_id]) if options[:merchant_transaction_id]
252
+ xml.tag!('v1:Ip', options[:ip]) if options[:ip]
253
+ xml.tag!('v1:Tdate', options[:t_date]) if options[:t_date]
254
+ xml.tag!('v1:IpgTransactionId', options[:ipg_transaction_id]) if options[:ipg_transaction_id]
255
+ xml.tag!('v1:ReferencedMerchantTransactionId', options[:referenced_merchant_transaction_id]) if options[:referenced_merchant_transaction_id]
256
+ xml.tag!('v1:TransactionOrigin', options[:transaction_origin]) if options[:transaction_origin]
257
+ xml.tag!('v1:InvoiceNumber', options[:invoice_number]) if options[:invoice_number]
258
+ xml.tag!('v1:DynamicMerchantName', options[:dynamic_merchant_name]) if options[:dynamic_merchant_name]
259
+ xml.tag!('v1:Comments', options[:comments]) if options[:comments]
260
+ if options[:terminal_id]
261
+ xml.tag!('v1:Terminal') do
262
+ xml.tag!('v1:TerminalID', options[:terminal_id]) if options[:terminal_id]
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ def add_payment(xml, money, payment, options)
269
+ requires!(options.merge!({ money: money }), :currency, :money)
270
+ xml.tag!('v1:Payment') do
271
+ xml.tag!('v1:HostedDataID', payment) if payment&.is_a?(String)
272
+ xml.tag!('v1:HostedDataStoreID', options[:hosted_data_store_id]) if options[:hosted_data_store_id]
273
+ xml.tag!('v1:DeclineHostedDataDuplicates', options[:decline_hosted_data_duplicates]) if options[:decline_hosted_data_duplicates]
274
+ xml.tag!('v1:SubTotal', options[:sub_total]) if options[:sub_total]
275
+ xml.tag!('v1:ValueAddedTax', options[:value_added_tax]) if options[:value_added_tax]
276
+ xml.tag!('v1:DeliveryAmount', options[:delivery_amount]) if options[:delivery_amount]
277
+ xml.tag!('v1:ChargeTotal', money)
278
+ xml.tag!('v1:Currency', CURRENCY_CODES[options[:currency]])
279
+ xml.tag!('v1:numberOfInstallments', options[:number_of_installments]) if options[:number_of_installments]
280
+ end
281
+ end
282
+
283
+ def add_billing(xml, billing)
284
+ xml.tag!('v1:Billing') do
285
+ xml.tag!('v1:CustomerID', billing[:customer_id]) if billing[:customer_id]
286
+ xml.tag!('v1:Name', billing[:name]) if billing[:name]
287
+ xml.tag!('v1:Company', billing[:company]) if billing[:company]
288
+ xml.tag!('v1:Address1', billing[:address_1]) if billing[:address_1]
289
+ xml.tag!('v1:Address2', billing[:address_2]) if billing[:address_2]
290
+ xml.tag!('v1:City', billing[:city]) if billing[:city]
291
+ xml.tag!('v1:State', billing[:state]) if billing[:state]
292
+ xml.tag!('v1:Zip', billing[:zip]) if billing[:zip]
293
+ xml.tag!('v1:Country', billing[:country]) if billing[:country]
294
+ xml.tag!('v1:Phone', billing[:phone]) if billing[:phone]
295
+ xml.tag!('v1:Fax', billing[:fax]) if billing[:fax]
296
+ xml.tag!('v1:Email', billing[:email]) if billing[:email]
297
+ end
298
+ end
299
+
300
+ def add_shipping(xml, shipping)
301
+ xml.tag!('v1:Shipping') do
302
+ xml.tag!('v1:Type', shipping[:type]) if shipping[:type]
303
+ xml.tag!('v1:Name', shipping[:name]) if shipping[:name]
304
+ xml.tag!('v1:Address1', shipping[:address_1]) if shipping[:address_1]
305
+ xml.tag!('v1:Address2', shipping[:address_2]) if shipping[:address_2]
306
+ xml.tag!('v1:City', shipping[:city]) if shipping[:city]
307
+ xml.tag!('v1:State', shipping[:state]) if shipping[:state]
308
+ xml.tag!('v1:Zip', shipping[:zip]) if shipping[:zip]
309
+ xml.tag!('v1:Country', shipping[:country]) if shipping[:country]
310
+ end
311
+ end
312
+
313
+ def build_header
314
+ {
315
+ 'Content-Type' => 'text/xml; charset=utf-8',
316
+ 'Authorization' => "Basic #{encoded_credentials}"
317
+ }
318
+ end
319
+
320
+ def encoded_credentials
321
+ Base64.encode64("WS#{@credentials[:store_id]}._.#{@credentials[:user_id]}:#{@credentials[:password]}").delete("\n")
322
+ end
323
+
324
+ def envelope_namespaces
325
+ {
326
+ 'xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/',
327
+ 'xmlns:ipg' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi",
328
+ 'xmlns:v1' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1"
329
+ }
330
+ end
331
+
332
+ def ipg_order_namespaces
333
+ {
334
+ 'xmlns:v1' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1",
335
+ 'xmlns:ipgapi' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi"
336
+ }
337
+ end
338
+
339
+ def ipg_action_namespaces
340
+ {
341
+ 'xmlns:ns4' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/ipgapi",
342
+ 'xmlns:ns2' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/a1",
343
+ 'xmlns:ns3' => "#{NAMESPACE_BASE_URL}/ipgapi/schemas/v1"
344
+ }
345
+ end
346
+
347
+ def commit(action, request)
348
+ url = (test? ? test_url : live_url)
349
+ soap_request = build_soap_request(action, request)
350
+ response = parse(ssl_post(url, soap_request, build_header))
351
+ Response.new(
352
+ response[:success],
353
+ message_from(response),
354
+ response,
355
+ authorization: authorization_from(action, response),
356
+ avs_result: AVSResult.new(code: response[:AVSResponse]),
357
+ cvv_result: CVVResult.new(response[:ProcessorCCVResponse]),
358
+ test: test?,
359
+ error_code: error_code_from(response)
360
+ )
361
+ end
362
+
363
+ def parse(xml)
364
+ reply = {}
365
+ xml = REXML::Document.new(xml)
366
+ root = REXML::XPath.first(xml, '//ipgapi:IPGApiOrderResponse') || REXML::XPath.first(xml, '//ipgapi:IPGApiActionResponse') || REXML::XPath.first(xml, '//SOAP-ENV:Fault') || REXML::XPath.first(xml, '//ns4:IPGApiActionResponse')
367
+ reply[:success] = REXML::XPath.first(xml, '//faultcode') ? false : true
368
+ if REXML::XPath.first(xml, '//ns4:IPGApiActionResponse')
369
+ reply[:tpv_error_code] = REXML::XPath.first(root, '//ns2:Error').attributes['Code']
370
+ reply[:tpv_error_msg] = REXML::XPath.first(root, '//ns2:ErrorMessage').text
371
+ reply[:success] = false
372
+ end
373
+ root.elements.to_a.each do |node|
374
+ parse_element(reply, node)
375
+ end
376
+ reply[:hosted_data_id] = @hosted_data_id if @hosted_data_id
377
+ return reply
378
+ end
379
+
380
+ def parse_element(reply, node)
381
+ if node.has_elements?
382
+ node.elements.each { |e| parse_element(reply, e) }
383
+ else
384
+ if /item/.match?(node.parent.name)
385
+ parent = node.parent.name
386
+ parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
387
+ parent += '_'
388
+ end
389
+ reply["#{parent}#{node.name}".to_sym] ||= node.text
390
+ end
391
+ return reply
392
+ end
393
+
394
+ def message_from(response)
395
+ response[:TransactionResult]
396
+ end
397
+
398
+ def authorization_from(action, response)
399
+ return (action == 'vault' ? response[:hosted_data_id] : response[:OrderId])
400
+ end
401
+
402
+ def error_code_from(response)
403
+ response[:ErrorMessage]&.split(':')&.first unless response[:success]
404
+ end
405
+
406
+ def handle_response(response)
407
+ case response.code.to_i
408
+ when 200...300, 500
409
+ response.body
410
+ else
411
+ raise ResponseError.new(response)
412
+ end
413
+ end
414
+ end
415
+ end
416
+ end