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.
- checksums.yaml +4 -4
- data/CHANGELOG +86 -0
- data/README.md +1 -1
- data/lib/active_merchant/billing/check.rb +13 -16
- data/lib/active_merchant/billing/credit_card.rb +3 -0
- data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +21 -12
- data/lib/active_merchant/billing/gateways/adyen.rb +15 -19
- data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
- data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
- data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
- data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
- data/lib/active_merchant/billing/gateways/cyber_source.rb +30 -3
- data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
- data/lib/active_merchant/billing/gateways/elavon.rb +60 -28
- data/lib/active_merchant/billing/gateways/element.rb +2 -0
- data/lib/active_merchant/billing/gateways/global_collect.rb +19 -10
- data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
- data/lib/active_merchant/billing/gateways/mercado_pago.rb +3 -2
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
- data/lib/active_merchant/billing/gateways/moka.rb +277 -0
- data/lib/active_merchant/billing/gateways/monei.rb +228 -144
- data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
- data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
- data/lib/active_merchant/billing/gateways/orbital.rb +28 -6
- data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
- data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
- data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
- data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
- data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
- data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
- data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
- data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
- data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
- data/lib/active_merchant/billing/gateways/safe_charge.rb +2 -0
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
- data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +19 -1
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
- data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
- data/lib/active_merchant/billing/gateways/worldpay.rb +39 -7
- data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
- data/lib/active_merchant/billing.rb +1 -0
- data/lib/active_merchant/version.rb +1 -1
- 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.
|
8
|
+
# gateway please go to http://www.monei.com
|
9
9
|
#
|
10
10
|
# === Setup
|
11
|
-
# In order to set-up the gateway you need
|
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://
|
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 = '
|
22
|
-
self.display_name = '
|
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
|
-
# :
|
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, :
|
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
|
-
#
|
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,
|
49
|
-
execute_new_order(:purchase, money,
|
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
|
-
#
|
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,
|
64
|
-
execute_new_order(:authorize, money,
|
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
|
-
#
|
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(
|
117
|
+
def verify(payment_method, options = {})
|
121
118
|
MultiResponse.run(:use_first_response) do |r|
|
122
|
-
r.process { authorize(100,
|
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,
|
131
|
-
request = build_request
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
145
|
-
add_identification_authorization(xml, authorization, options)
|
146
|
-
add_payment(xml, action, money, options)
|
147
|
-
end
|
156
|
+
request = build_request
|
148
157
|
|
149
|
-
|
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
|
164
|
+
# Private: Build request object
|
153
165
|
def build_request
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
167
|
-
def add_identification_new_order(
|
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
|
-
|
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
|
175
|
-
def add_identification_authorization(
|
176
|
-
|
177
|
-
|
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
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
207
|
-
def add_customer(
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
249
|
-
def
|
250
|
-
if options[:three_d_secure]
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
264
|
-
|
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
|
-
|
271
|
-
|
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 =
|
342
|
+
response = api_request(url + endpoint, params(request, action), headers)
|
343
|
+
success = success_from(response)
|
275
344
|
|
276
345
|
Response.new(
|
277
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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(
|
308
|
-
|
387
|
+
def post_data(params)
|
388
|
+
params.clone.to_json
|
309
389
|
end
|
310
390
|
|
311
|
-
# Private:
|
312
|
-
def
|
313
|
-
|
314
|
-
|
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
|
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: '
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
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
|