activemerchant 1.121.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +86 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -16
  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 +21 -12
  8. data/lib/active_merchant/billing/gateways/adyen.rb +15 -19
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  10. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  11. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  12. data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
  13. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  14. data/lib/active_merchant/billing/gateways/cyber_source.rb +30 -3
  15. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  16. data/lib/active_merchant/billing/gateways/elavon.rb +60 -28
  17. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  18. data/lib/active_merchant/billing/gateways/global_collect.rb +19 -10
  19. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  20. data/lib/active_merchant/billing/gateways/mercado_pago.rb +3 -2
  21. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  22. data/lib/active_merchant/billing/gateways/moka.rb +277 -0
  23. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  24. data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
  25. data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
  26. data/lib/active_merchant/billing/gateways/orbital.rb +28 -6
  27. data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
  28. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  29. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  30. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  31. data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
  32. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  33. data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
  34. data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
  35. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  36. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  37. data/lib/active_merchant/billing/gateways/safe_charge.rb +2 -0
  38. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  39. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +19 -1
  40. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  42. data/lib/active_merchant/billing/gateways/worldpay.rb +39 -7
  43. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  44. data/lib/active_merchant/billing.rb +1 -0
  45. data/lib/active_merchant/version.rb +1 -1
  46. metadata +8 -3
@@ -129,6 +129,7 @@ module ActiveMerchant #:nodoc:
129
129
 
130
130
  def add_additional_data(post, options)
131
131
  post[:sponsor_id] = options[:sponsor_id]
132
+ post[:metadata] = options[:metadata] if options[:metadata]
132
133
  post[:device_id] = options[:device_id] if options[:device_id]
133
134
  post[:additional_info] = {
134
135
  ip_address: options[:ip_address]
@@ -143,7 +144,7 @@ module ActiveMerchant #:nodoc:
143
144
  email: options[:email],
144
145
  first_name: payment.first_name,
145
146
  last_name: payment.last_name
146
- }
147
+ }.merge(options[:payer] || {})
147
148
  end
148
149
 
149
150
  def add_address(post, options)
@@ -191,7 +192,7 @@ module ActiveMerchant #:nodoc:
191
192
  post[:description] = options[:description]
192
193
  post[:installments] = options[:installments] ? options[:installments].to_i : 1
193
194
  post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
194
- post[:external_reference] = options[:order_id] || SecureRandom.hex(16)
195
+ post[:external_reference] = options[:order_id] || options[:external_reference] || SecureRandom.hex(16)
195
196
  end
196
197
 
197
198
  def add_payment(post, options)
@@ -187,6 +187,8 @@ module ActiveMerchant #:nodoc:
187
187
  def parse(body)
188
188
  xml = REXML::Document.new(body)
189
189
 
190
+ return { response_message: 'Invalid gateway response' } unless xml.root.present?
191
+
190
192
  response = {}
191
193
  xml.root.elements.to_a.each do |node|
192
194
  parse_element(response, node)
@@ -0,0 +1,277 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class MokaGateway < Gateway
4
+ self.test_url = 'https://service.testmoka.com'
5
+ self.live_url = 'https://service.moka.com'
6
+
7
+ self.supported_countries = %w[GB TR US]
8
+ self.default_currency = 'TRY'
9
+ self.money_format = :dollars
10
+ self.supported_cardtypes = %i[visa master american_express discover]
11
+
12
+ self.homepage_url = 'http://developer.moka.com/'
13
+ self.display_name = 'Moka'
14
+
15
+ ERROR_CODE_MAPPING = {
16
+ '000' => 'General error',
17
+ '001' => '3D Not authenticated',
18
+ '002' => 'Limit is insufficient',
19
+ '003' => 'Credit card number format is wrong',
20
+ '004' => 'General decline',
21
+ '005' => 'This process is invalid for the card owner',
22
+ '006' => 'Expiration date is wrong',
23
+ '007' => 'Invalid transaction',
24
+ '008' => 'Connection with the bank not established',
25
+ '009' => 'Undefined error code',
26
+ '010' => 'Bank SSL error',
27
+ '011' => 'Call the bank for the manual authentication',
28
+ '012' => 'Card info is wrong - Kart Number or CVV2',
29
+ '013' => '3D secure is not supported other than Visa MC cards',
30
+ '014' => 'Invalid account number',
31
+ '015' => 'CVV is wrong',
32
+ '016' => 'Authentication process is not present',
33
+ '017' => 'System error',
34
+ '018' => 'Stolen card',
35
+ '019' => 'Lost card',
36
+ '020' => 'Card with limited properties',
37
+ '021' => 'Timeout',
38
+ '022' => 'Invalid merchant',
39
+ '023' => 'False authentication',
40
+ '024' => '3D authorization is successful but the process cannot be completed',
41
+ '025' => '3D authorization failure',
42
+ '026' => 'Either the issuer bank or the card is not enrolled to the 3D process',
43
+ '027' => 'The bank did not allow the process',
44
+ '028' => 'Fraud suspect',
45
+ '029' => 'The card is closed to the e-commerce operations'
46
+ }
47
+
48
+ def initialize(options = {})
49
+ requires!(options, :dealer_code, :username, :password)
50
+ super
51
+ end
52
+
53
+ def purchase(money, payment, options = {})
54
+ post = {}
55
+ post[:PaymentDealerRequest] = {}
56
+ options[:pre_auth] = 0
57
+ add_auth_purchase(post, money, payment, options)
58
+
59
+ commit('purchase', post)
60
+ end
61
+
62
+ def authorize(money, payment, options = {})
63
+ post = {}
64
+ post[:PaymentDealerRequest] = {}
65
+ options[:pre_auth] = 1
66
+ add_auth_purchase(post, money, payment, options)
67
+
68
+ commit('authorize', post)
69
+ end
70
+
71
+ def capture(money, authorization, options = {})
72
+ post = {}
73
+ post[:PaymentDealerRequest] = {}
74
+ add_payment_dealer_authentication(post)
75
+ add_transaction_reference(post, authorization)
76
+ add_additional_transaction_data(post, options)
77
+
78
+ commit('capture', post)
79
+ end
80
+
81
+ def refund(money, authorization, options = {})
82
+ post = {}
83
+ post[:PaymentDealerRequest] = {}
84
+ add_payment_dealer_authentication(post)
85
+ add_transaction_reference(post, authorization)
86
+ add_additional_transaction_data(post, options)
87
+ add_void_refund_reason(post)
88
+ add_amount(post, money)
89
+
90
+ commit('refund', post)
91
+ end
92
+
93
+ def void(authorization, options = {})
94
+ post = {}
95
+ post[:PaymentDealerRequest] = {}
96
+ add_payment_dealer_authentication(post)
97
+ add_transaction_reference(post, authorization)
98
+ add_additional_transaction_data(post, options)
99
+ add_void_refund_reason(post)
100
+
101
+ commit('void', post)
102
+ end
103
+
104
+ def verify(credit_card, options = {})
105
+ MultiResponse.run(:use_first_response) do |r|
106
+ r.process { authorize(100, credit_card, options) }
107
+ r.process(:ignore_result) { void(r.authorization, options) }
108
+ end
109
+ end
110
+
111
+ def supports_scrubbing?
112
+ true
113
+ end
114
+
115
+ def scrub(transcript)
116
+ transcript.
117
+ gsub(%r(("CardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
118
+ gsub(%r(("CvcNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
119
+ gsub(%r(("DealerCode\\?":\\?"?)[^"?]*)i, '\1[FILTERED]').
120
+ gsub(%r(("Username\\?":\\?")[^"]*)i, '\1[FILTERED]').
121
+ gsub(%r(("Password\\?":\\?")[^"]*)i, '\1[FILTERED]').
122
+ gsub(%r(("CheckKey\\?":\\?")[^"]*)i, '\1[FILTERED]')
123
+ end
124
+
125
+ private
126
+
127
+ def add_auth_purchase(post, money, payment, options)
128
+ add_payment_dealer_authentication(post)
129
+ add_invoice(post, money, options)
130
+ add_payment(post, payment)
131
+ add_additional_auth_purchase_data(post, options)
132
+ add_additional_transaction_data(post, options)
133
+ add_buyer_information(post, payment, options)
134
+ add_basket_product(post, options[:basket_product]) if options[:basket_product]
135
+ end
136
+
137
+ def add_payment_dealer_authentication(post)
138
+ post[:PaymentDealerAuthentication] = {
139
+ DealerCode: @options[:dealer_code],
140
+ Username: @options[:username],
141
+ Password: @options[:password],
142
+ CheckKey: check_key
143
+ }
144
+ end
145
+
146
+ def check_key
147
+ str = "#{@options[:dealer_code]}MK#{@options[:username]}PD#{@options[:password]}"
148
+ Digest::SHA256.hexdigest(str)
149
+ end
150
+
151
+ def add_invoice(post, money, options)
152
+ post[:PaymentDealerRequest][:Amount] = amount(money) || 0
153
+ post[:PaymentDealerRequest][:Currency] = options[:currency] || 'TL'
154
+ end
155
+
156
+ def add_payment(post, card)
157
+ post[:PaymentDealerRequest][:CardHolderFullName] = card.name
158
+ post[:PaymentDealerRequest][:CardNumber] = card.number
159
+ post[:PaymentDealerRequest][:ExpMonth] = card.month
160
+ post[:PaymentDealerRequest][:ExpYear] = card.year
161
+ post[:PaymentDealerRequest][:CvcNumber] = card.verification_value
162
+ end
163
+
164
+ def add_amount(post, money)
165
+ post[:PaymentDealerRequest][:Amount] = money || 0
166
+ end
167
+
168
+ def add_additional_auth_purchase_data(post, options)
169
+ post[:PaymentDealerRequest][:IsPreAuth] = options[:pre_auth]
170
+ post[:PaymentDealerRequest][:Description] = options[:order_id] if options[:order_id]
171
+ post[:SubMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
172
+ post[:IsPoolPayment] = options[:is_pool_payment] || 0
173
+ end
174
+
175
+ def add_buyer_information(post, card, options)
176
+ obj = {}
177
+
178
+ obj[:BuyerFullName] = card.name || ''
179
+ obj[:BuyerEmail] = options[:email] if options[:email]
180
+ obj[:BuyerAddress] = options[:billing_address][:address1] if options[:billing_address]
181
+ obj[:BuyerGsmNumber] = options[:billing_address][:phone] if options[:billing_address]
182
+
183
+ post[:PaymentDealerRequest][:BuyerInformation] = obj
184
+ end
185
+
186
+ def add_basket_product(post, basket_options)
187
+ basket = []
188
+
189
+ basket_options.each do |product|
190
+ obj = {}
191
+ obj[:ProductId] = product[:product_id] if product[:product_id]
192
+ obj[:ProductCode] = product[:product_code] if product[:product_code]
193
+ obj[:UnitPrice] = amount(product[:unit_price]) if product[:unit_price]
194
+ obj[:Quantity] = product[:quantity] if product[:quantity]
195
+ basket << obj
196
+ end
197
+
198
+ post[:PaymentDealerRequest][:BasketProduct] = basket
199
+ end
200
+
201
+ def add_additional_transaction_data(post, options)
202
+ post[:PaymentDealerRequest][:ClientIP] = options[:ip] if options[:ip]
203
+ post[:PaymentDealerRequest][:OtherTrxCode] = options[:order_id] if options[:order_id]
204
+ end
205
+
206
+ def add_transaction_reference(post, authorization)
207
+ post[:PaymentDealerRequest][:VirtualPosOrderId] = authorization
208
+ end
209
+
210
+ def add_void_refund_reason(post)
211
+ post[:PaymentDealerRequest][:VoidRefundReason] = 2
212
+ end
213
+
214
+ def commit(action, parameters)
215
+ response = parse(ssl_post(url(action), post_data(parameters), request_headers))
216
+ Response.new(
217
+ success_from(response),
218
+ message_from(response),
219
+ response,
220
+ authorization: authorization_from(response),
221
+ test: test?,
222
+ error_code: error_code_from(response)
223
+ )
224
+ end
225
+
226
+ def url(action)
227
+ host = (test? ? test_url : live_url)
228
+ endpoint = endpoint(action)
229
+
230
+ "#{host}/PaymentDealer/#{endpoint}"
231
+ end
232
+
233
+ def endpoint(action)
234
+ case action
235
+ when 'purchase', 'authorize'
236
+ 'DoDirectPayment'
237
+ when 'capture'
238
+ 'DoCapture'
239
+ when 'void'
240
+ 'DoVoid'
241
+ when 'refund'
242
+ 'DoCreateRefundRequest'
243
+ end
244
+ end
245
+
246
+ def request_headers
247
+ { 'Content-Type' => 'application/json' }
248
+ end
249
+
250
+ def post_data(parameters = {})
251
+ JSON.generate(parameters)
252
+ end
253
+
254
+ def parse(body)
255
+ JSON.parse(body)
256
+ end
257
+
258
+ def success_from(response)
259
+ response.dig('Data', 'IsSuccessful')
260
+ end
261
+
262
+ def message_from(response)
263
+ response.dig('Data', 'ResultMessage').presence || response['ResultCode']
264
+ end
265
+
266
+ def authorization_from(response)
267
+ response.dig('Data', 'VirtualPosOrderId')
268
+ end
269
+
270
+ def error_code_from(response)
271
+ codes = [response['ResultCode'], response.dig('Data', 'ResultCode')].flatten
272
+ codes.reject! { |code| code.blank? || code.casecmp('success').zero? }
273
+ codes.map { |code| ERROR_CODE_MAPPING[code] || code }.join(', ')
274
+ end
275
+ end
276
+ end
277
+ end
@@ -5,39 +5,36 @@ module ActiveMerchant #:nodoc:
5
5
  #
6
6
  # == Monei gateway
7
7
  # This class implements Monei gateway for Active Merchant. For more information about Monei
8
- # gateway please go to http://www.monei.net
8
+ # gateway please go to http://www.monei.com
9
9
  #
10
10
  # === Setup
11
- # In order to set-up the gateway you need four paramaters: sender_id, channel_id, login and pwd.
11
+ # In order to set-up the gateway you need only one paramater: the api_key
12
12
  # Request that data to Monei.
13
13
  class MoneiGateway < Gateway
14
- self.test_url = 'https://test.monei-api.net/payment/ctpe'
15
- self.live_url = 'https://monei-api.net/payment/ctpe'
14
+ self.live_url = self.test_url = 'https://api.monei.com/v1/payments'
16
15
 
17
16
  self.supported_countries = %w[AD AT BE BG CA CH CY CZ DE DK EE ES FI FO FR GB GI GR HU IE IL IS IT LI LT LU LV MT NL NO PL PT RO SE SI SK TR US VA]
18
17
  self.default_currency = 'EUR'
18
+ self.money_format = :cents
19
19
  self.supported_cardtypes = %i[visa master maestro jcb american_express]
20
20
 
21
- self.homepage_url = 'http://www.monei.net/'
22
- self.display_name = 'Monei'
21
+ self.homepage_url = 'https://monei.com/'
22
+ self.display_name = 'MONEI'
23
23
 
24
24
  # Constructor
25
25
  #
26
26
  # options - Hash containing the gateway credentials, ALL MANDATORY
27
- # :sender_id Sender ID
28
- # :channel_id Channel ID
29
- # :login User login
30
- # :pwd User password
27
+ # :api_key Account's API KEY
31
28
  #
32
29
  def initialize(options = {})
33
- requires!(options, :sender_id, :channel_id, :login, :pwd)
30
+ requires!(options, :api_key)
34
31
  super
35
32
  end
36
33
 
37
34
  # Public: Performs purchase operation
38
35
  #
39
36
  # money - Amount of purchase
40
- # credit_card - Credit card
37
+ # payment_method - Credit card
41
38
  # options - Hash containing purchase options
42
39
  # :order_id Merchant created id for the purchase
43
40
  # :billing_address Hash with billing address information
@@ -45,14 +42,14 @@ module ActiveMerchant #:nodoc:
45
42
  # :currency Sale currency to override money object or default (optional)
46
43
  #
47
44
  # Returns Active Merchant response object
48
- def purchase(money, credit_card, options = {})
49
- execute_new_order(:purchase, money, credit_card, options)
45
+ def purchase(money, payment_method, options = {})
46
+ execute_new_order(:purchase, money, payment_method, options)
50
47
  end
51
48
 
52
49
  # Public: Performs authorization operation
53
50
  #
54
51
  # money - Amount to authorize
55
- # credit_card - Credit card
52
+ # payment_method - Credit card
56
53
  # options - Hash containing authorization options
57
54
  # :order_id Merchant created id for the authorization
58
55
  # :billing_address Hash with billing address information
@@ -60,8 +57,8 @@ module ActiveMerchant #:nodoc:
60
57
  # :currency Sale currency to override money object or default (optional)
61
58
  #
62
59
  # Returns Active Merchant response object
63
- def authorize(money, credit_card, options = {})
64
- execute_new_order(:authorize, money, credit_card, options)
60
+ def authorize(money, payment_method, options = {})
61
+ execute_new_order(:authorize, money, payment_method, options)
65
62
  end
66
63
 
67
64
  # Public: Performs capture operation on previous authorization
@@ -109,7 +106,7 @@ module ActiveMerchant #:nodoc:
109
106
 
110
107
  # Public: Verifies credit card. Does this by doing a authorization of 1.00 Euro and then voiding it.
111
108
  #
112
- # credit_card - Credit card
109
+ # payment_method - Credit card
113
110
  # options - Hash containing authorization options
114
111
  # :order_id Merchant created id for the authorization
115
112
  # :billing_address Hash with billing address information
@@ -117,113 +114,122 @@ module ActiveMerchant #:nodoc:
117
114
  # :currency Sale currency to override money object or default (optional)
118
115
  #
119
116
  # Returns Active Merchant response object of Authorization operation
120
- def verify(credit_card, options = {})
117
+ def verify(payment_method, options = {})
121
118
  MultiResponse.run(:use_first_response) do |r|
122
- r.process { authorize(100, credit_card, options) }
119
+ r.process { authorize(100, payment_method, options) }
123
120
  r.process(:ignore_result) { void(r.authorization, options) }
124
121
  end
125
122
  end
126
123
 
124
+ def store(payment_method, options = {})
125
+ execute_new_order(:store, 0, payment_method, options)
126
+ end
127
+
128
+ def supports_scrubbing?
129
+ true
130
+ end
131
+
132
+ def scrub(transcript)
133
+ transcript.
134
+ gsub(%r((Authorization: )\w+), '\1[FILTERED]').
135
+ gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]').
136
+ gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]').
137
+ gsub(%r(("cavv\\?":\\?")[^"]*)i, '\1[FILTERED]')
138
+ end
139
+
127
140
  private
128
141
 
129
142
  # Private: Execute purchase or authorize operation
130
- def execute_new_order(action, money, credit_card, options)
131
- request = build_request do |xml|
132
- add_identification_new_order(xml, options)
133
- add_payment(xml, action, money, options)
134
- add_account(xml, credit_card)
135
- add_customer(xml, credit_card, options)
136
- add_three_d_secure(xml, options)
137
- end
138
-
139
- commit(request)
143
+ def execute_new_order(action, money, payment_method, options)
144
+ request = build_request
145
+ add_identification_new_order(request, options)
146
+ add_transaction(request, action, money, options)
147
+ add_payment(request, payment_method)
148
+ add_customer(request, payment_method, options)
149
+ add_3ds_authenticated_data(request, options)
150
+ add_browser_info(request, options)
151
+ commit(request, action, options)
140
152
  end
141
153
 
142
154
  # Private: Execute operation that depends on authorization code from previous purchase or authorize operation
143
155
  def execute_dependant(action, money, authorization, options)
144
- request = build_request do |xml|
145
- add_identification_authorization(xml, authorization, options)
146
- add_payment(xml, action, money, options)
147
- end
156
+ request = build_request
148
157
 
149
- commit(request)
158
+ add_identification_authorization(request, authorization, options)
159
+ add_transaction(request, action, money, options)
160
+
161
+ commit(request, action, options)
150
162
  end
151
163
 
152
- # Private: Build XML wrapping code yielding to code to fill the transaction information
164
+ # Private: Build request object
153
165
  def build_request
154
- builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
155
- xml.Request(version: '1.0') do
156
- xml.Header { xml.Security(sender: @options[:sender_id]) }
157
- xml.Transaction(mode: test? ? 'CONNECTOR_TEST' : 'LIVE', response: 'SYNC', channel: @options[:channel_id]) do
158
- xml.User(login: @options[:login], pwd: @options[:pwd])
159
- yield xml
160
- end
161
- end
162
- end
163
- builder.to_xml
166
+ request = {}
167
+ request[:livemode] = test? ? 'false' : 'true'
168
+ request
164
169
  end
165
170
 
166
- # Private: Add identification part to XML for new orders
167
- def add_identification_new_order(xml, options)
171
+ # Private: Add identification part to request for new orders
172
+ def add_identification_new_order(request, options)
168
173
  requires!(options, :order_id)
169
- xml.Identification do
170
- xml.TransactionID options[:order_id]
171
- end
174
+ request[:orderId] = options[:order_id]
172
175
  end
173
176
 
174
- # Private: Add identification part to XML for orders that depend on authorization from previous operation
175
- def add_identification_authorization(xml, authorization, options)
176
- xml.Identification do
177
- xml.ReferenceID authorization
178
- xml.TransactionID options[:order_id]
179
- end
177
+ # Private: Add identification part to request for orders that depend on authorization from previous operation
178
+ def add_identification_authorization(request, authorization, options)
179
+ options[:paymentId] = authorization
180
+ request[:orderId] = options[:order_id] if options[:order_id]
180
181
  end
181
182
 
182
- # Private: Add payment part to XML
183
- def add_payment(xml, action, money, options)
184
- code = tanslate_payment_code(action)
185
-
186
- xml.Payment(code: code) do
187
- xml.Presentation do
188
- xml.Amount amount(money)
189
- xml.Currency options[:currency] || currency(money)
190
- xml.Usage options[:description] || options[:order_id]
191
- end unless money.nil?
183
+ # Private: Add payment part to request
184
+ def add_transaction(request, action, money, options)
185
+ request[:transactionType] = translate_payment_code(action)
186
+ request[:description] = options[:description] || options[:order_id]
187
+ unless money.nil?
188
+ request[:amount] = amount(money).to_i
189
+ request[:currency] = options[:currency] || currency(money)
192
190
  end
193
191
  end
194
192
 
195
- # Private: Add account part to XML
196
- def add_account(xml, credit_card)
197
- xml.Account do
198
- xml.Holder credit_card.name
199
- xml.Number credit_card.number
200
- xml.Brand credit_card.brand.upcase
201
- xml.Expiry(month: credit_card.month, year: credit_card.year)
202
- xml.Verification credit_card.verification_value
193
+ # Private: Add payment method to request
194
+ def add_payment(request, payment_method)
195
+ if payment_method.is_a? String
196
+ request[:paymentToken] = payment_method
197
+ else
198
+ request[:paymentMethod] = {}
199
+ request[:paymentMethod][:card] = {}
200
+ request[:paymentMethod][:card][:number] = payment_method.number
201
+ request[:paymentMethod][:card][:expMonth] = format(payment_method.month, :two_digits)
202
+ request[:paymentMethod][:card][:expYear] = format(payment_method.year, :two_digits)
203
+ request[:paymentMethod][:card][:cvc] = payment_method.verification_value.to_s
203
204
  end
204
205
  end
205
206
 
206
- # Private: Add customer part to XML
207
- def add_customer(xml, credit_card, options)
208
- requires!(options, :billing_address)
209
- address = options[:billing_address]
210
- xml.Customer do
211
- xml.Name do
212
- xml.Given credit_card.first_name
213
- xml.Family credit_card.last_name
214
- end
215
- xml.Address do
216
- xml.Street address[:address1].to_s
217
- xml.Zip address[:zip].to_s
218
- xml.City address[:city].to_s
219
- xml.State address[:state].to_s if address.has_key? :state
220
- xml.Country address[:country].to_s
221
- end
222
- xml.Contact do
223
- xml.Email options[:email] || 'noemail@monei.net'
224
- xml.Ip options[:ip] || '0.0.0.0'
225
- end
207
+ # Private: Add customer part to request
208
+ def add_customer(request, payment_method, options)
209
+ address = options[:billing_address] || options[:address]
210
+
211
+ request[:customer] = {}
212
+ request[:customer][:email] = options[:email] || 'support@monei.net'
213
+
214
+ if address
215
+ request[:customer][:name] = address[:name].to_s if address[:name]
216
+
217
+ request[:billingDetails] = {}
218
+ request[:billingDetails][:email] = options[:email] if options[:email]
219
+ request[:billingDetails][:name] = address[:name] if address[:name]
220
+ request[:billingDetails][:company] = address[:company] if address[:company]
221
+ request[:billingDetails][:phone] = address[:phone] if address[:phone]
222
+ request[:billingDetails][:address] = {}
223
+ request[:billingDetails][:address][:line1] = address[:address1] if address[:address1]
224
+ request[:billingDetails][:address][:line2] = address[:address2] if address[:address2]
225
+ request[:billingDetails][:address][:city] = address[:city] if address[:city]
226
+ request[:billingDetails][:address][:state] = address[:state] if address[:state].present?
227
+ request[:billingDetails][:address][:zip] = address[:zip].to_s if address[:zip]
228
+ request[:billingDetails][:address][:country] = address[:country] if address[:country]
226
229
  end
230
+
231
+ request[:sessionDetails] = {}
232
+ request[:sessionDetails][:ip] = options[:ip] if options[:ip]
227
233
  end
228
234
 
229
235
  # Private : Convert ECI to ResultIndicator
@@ -245,92 +251,170 @@ module ActiveMerchant #:nodoc:
245
251
  end
246
252
  end
247
253
 
248
- # Private : Add the 3DSecure infos to XML
249
- def add_three_d_secure(xml, options)
250
- if options[:three_d_secure]
251
- xml.Authentication(type: '3DSecure') do
252
- xml.ResultIndicator eci_to_result_indicator options[:three_d_secure][:eci]
253
- xml.Parameter(name: 'VERIFICATION_ID') { xml.text options[:three_d_secure][:cavv] }
254
- xml.Parameter(name: 'XID') { xml.text options[:three_d_secure][:xid] }
255
- end
254
+ # Private: add the already validated 3DSecure info to request
255
+ def add_3ds_authenticated_data(request, options)
256
+ if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid]
257
+ add_3ds1_authenticated_data(request, options)
258
+ elsif options[:three_d_secure]
259
+ add_3ds2_authenticated_data(request, options)
256
260
  end
257
261
  end
258
262
 
259
- # Private: Parse XML response from Monei servers
263
+ def add_3ds1_authenticated_data(request, options)
264
+ three_d_secure_options = options[:three_d_secure]
265
+ request[:paymentMethod][:card][:auth] = {
266
+ cavv: three_d_secure_options[:cavv],
267
+ cavvAlgorithm: three_d_secure_options[:cavv_algorithm],
268
+ eci: three_d_secure_options[:eci],
269
+ xid: three_d_secure_options[:xid],
270
+ directoryResponse: three_d_secure_options[:enrolled],
271
+ authenticationResponse: three_d_secure_options[:authentication_response_status]
272
+ }
273
+ end
274
+
275
+ def add_3ds2_authenticated_data(request, options)
276
+ three_d_secure_options = options[:three_d_secure]
277
+ # If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes.
278
+ if three_d_secure_options[:authentication_response_status].nil?
279
+ authentication_response = three_d_secure_options[:directory_response_status]
280
+ else
281
+ authentication_response = three_d_secure_options[:authentication_response_status]
282
+ end
283
+ request[:paymentMethod][:card][:auth] = {
284
+ threeDSVersion: three_d_secure_options[:version],
285
+ eci: three_d_secure_options[:eci],
286
+ cavv: three_d_secure_options[:cavv],
287
+ dsTransID: three_d_secure_options[:ds_transaction_id],
288
+ directoryResponse: three_d_secure_options[:directory_response_status],
289
+ authenticationResponse: authentication_response
290
+ }
291
+ end
292
+
293
+ def add_browser_info(request, options)
294
+ request[:sessionDetails][:ip] = options[:ip] if options[:ip]
295
+ request[:sessionDetails][:userAgent] = options[:user_agent] if options[:user_agent]
296
+ end
297
+
298
+ # Private: Parse JSON response from Monei servers
260
299
  def parse(body)
261
- xml = Nokogiri::XML(body)
300
+ JSON.parse(body)
301
+ end
302
+
303
+ def json_error(raw_response)
304
+ msg = 'Invalid response received from the MONEI API. Please contact support@monei.net if you continue to receive this message.'
305
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
262
306
  {
263
- unique_id: xml.xpath('//Response/Transaction/Identification/UniqueID').text,
264
- status: translate_status_code(xml.xpath('//Response/Transaction/Processing/Status/@code').text),
265
- reason: translate_status_code(xml.xpath('//Response/Transaction/Processing/Reason/@code').text),
266
- message: xml.xpath('//Response/Transaction/Processing/Return').text
307
+ 'status' => 'error',
308
+ 'message' => msg
267
309
  }
268
310
  end
269
311
 
270
- # Private: Send XML transaction to Monei servers and create AM response
271
- def commit(xml)
312
+ def response_error(raw_response)
313
+ parse(raw_response)
314
+ rescue JSON::ParserError
315
+ json_error(raw_response)
316
+ end
317
+
318
+ def api_request(url, parameters, options = {})
319
+ raw_response = response = nil
320
+ begin
321
+ raw_response = ssl_post(url, post_data(parameters), options)
322
+ response = parse(raw_response)
323
+ rescue ResponseError => e
324
+ raw_response = e.response.body
325
+ response = response_error(raw_response)
326
+ rescue JSON::ParserError
327
+ response = json_error(raw_response)
328
+ end
329
+ response
330
+ end
331
+
332
+ # Private: Send transaction to Monei servers and create AM response
333
+ def commit(request, action, options)
272
334
  url = (test? ? test_url : live_url)
335
+ endpoint = translate_action_endpoint(action, options)
336
+ headers = {
337
+ 'Content-Type': 'application/json;charset=UTF-8',
338
+ 'Authorization': @options[:api_key],
339
+ 'User-Agent': 'MONEI/Shopify/0.1.0'
340
+ }
273
341
 
274
- response = parse(ssl_post(url, post_data(xml), 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'))
342
+ response = api_request(url + endpoint, params(request, action), headers)
343
+ success = success_from(response)
275
344
 
276
345
  Response.new(
277
- success_from(response),
278
- message_from(response),
346
+ success,
347
+ message_from(response, success),
279
348
  response,
280
- authorization: authorization_from(response),
349
+ authorization: authorization_from(response, action),
281
350
  test: test?,
282
- error_code: error_code_from(response)
351
+ error_code: error_code_from(response, success)
283
352
  )
284
353
  end
285
354
 
286
355
  # Private: Decide success from servers response
287
356
  def success_from(response)
288
- response[:status] == :success || response[:status] == :new
357
+ %w[
358
+ SUCCEEDED
359
+ AUTHORIZED
360
+ REFUNDED
361
+ PARTIALLY_REFUNDED
362
+ CANCELED
363
+ ].include? response['status']
289
364
  end
290
365
 
291
366
  # Private: Get message from servers response
292
- def message_from(response)
293
- response[:message]
367
+ def message_from(response, success)
368
+ success ? 'Transaction approved' : response.fetch('statusMessage', response.fetch('message', 'No error details'))
294
369
  end
295
370
 
296
371
  # Private: Get error code from servers response
297
- def error_code_from(response)
298
- success_from(response) ? nil : STANDARD_ERROR_CODE[:card_declined]
372
+ def error_code_from(response, success)
373
+ success ? nil : STANDARD_ERROR_CODE[:card_declined]
299
374
  end
300
375
 
301
376
  # Private: Get authorization code from servers response
302
- def authorization_from(response)
303
- response[:unique_id]
377
+ def authorization_from(response, action)
378
+ case action
379
+ when :store
380
+ return response['paymentToken']
381
+ else
382
+ return response['id']
383
+ end
304
384
  end
305
385
 
306
386
  # Private: Encode POST parameters
307
- def post_data(xml)
308
- "load=#{CGI.escape(xml)}"
387
+ def post_data(params)
388
+ params.clone.to_json
309
389
  end
310
390
 
311
- # Private: Translate Monei status code to native ruby symbols
312
- def translate_status_code(code)
313
- {
314
- '00' => :success,
315
- '40' => :neutral,
316
- '59' => :waiting_bank,
317
- '60' => :rejected_bank,
318
- '64' => :waiting_risk,
319
- '65' => :rejected_risk,
320
- '70' => :rejected_validation,
321
- '80' => :waiting,
322
- '90' => :new
323
- }[code]
391
+ # Private: generate request params depending on action
392
+ def params(request, action)
393
+ request[:generatePaymentToken] = true if action == :store
394
+ request
324
395
  end
325
396
 
326
397
  # Private: Translate AM operations to Monei operations codes
327
- def tanslate_payment_code(action)
398
+ def translate_payment_code(action)
399
+ {
400
+ purchase: 'SALE',
401
+ store: 'SALE',
402
+ authorize: 'AUTH',
403
+ capture: 'CAPTURE',
404
+ refund: 'REFUND',
405
+ void: 'CANCEL'
406
+ }[action]
407
+ end
408
+
409
+ # Private: Translate AM operations to Monei endpoints
410
+ def translate_action_endpoint(action, options)
328
411
  {
329
- purchase: 'CC.DB',
330
- authorize: 'CC.PA',
331
- capture: 'CC.CP',
332
- refund: 'CC.RF',
333
- void: 'CC.RV'
412
+ purchase: '',
413
+ store: '',
414
+ authorize: '',
415
+ capture: "/#{options[:paymentId]}/capture",
416
+ refund: "/#{options[:paymentId]}/refund",
417
+ void: "/#{options[:paymentId]}/cancel"
334
418
  }[action]
335
419
  end
336
420
  end