activemerchant 1.121.0 → 1.123.0

Sign up to get free protection for your applications and to get access to all the features.
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