activemerchant 1.126.0 → 1.129.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 +241 -0
- data/lib/active_merchant/billing/check.rb +40 -8
- data/lib/active_merchant/billing/credit_card.rb +28 -1
- data/lib/active_merchant/billing/credit_card_methods.rb +79 -23
- data/lib/active_merchant/billing/gateways/adyen.rb +67 -8
- data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
- data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
- data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -4
- data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
- data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
- data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
- data/lib/active_merchant/billing/gateways/borgun.rb +56 -16
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +64 -17
- data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
- data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +228 -57
- data/lib/active_merchant/billing/gateways/commerce_hub.rb +361 -0
- data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
- data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
- data/lib/active_merchant/billing/gateways/cyber_source.rb +100 -26
- data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +456 -0
- data/lib/active_merchant/billing/gateways/d_local.rb +44 -5
- data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
- data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
- data/lib/active_merchant/billing/gateways/element.rb +21 -1
- data/lib/active_merchant/billing/gateways/global_collect.rb +73 -22
- data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
- data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
- data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
- data/lib/active_merchant/billing/gateways/litle.rb +25 -5
- data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
- data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
- data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
- data/lib/active_merchant/billing/gateways/monei.rb +2 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
- data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
- data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
- data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
- data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
- data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
- data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
- data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
- data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
- data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
- data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -0
- data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
- data/lib/active_merchant/billing/gateways/priority.rb +29 -6
- data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
- data/lib/active_merchant/billing/gateways/reach.rb +277 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +9 -5
- data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
- data/lib/active_merchant/billing/gateways/shift4.rb +342 -0
- data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
- data/lib/active_merchant/billing/gateways/stripe.rb +21 -1
- data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
- data/lib/active_merchant/billing/gateways/tns.rb +2 -5
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
- data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
- data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
- data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
- data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
- data/lib/active_merchant/billing/gateways/worldpay.rb +117 -9
- data/lib/active_merchant/billing/response.rb +15 -1
- data/lib/active_merchant/connection.rb +0 -2
- data/lib/active_merchant/country.rb +1 -0
- data/lib/active_merchant/errors.rb +4 -1
- data/lib/active_merchant/version.rb +1 -1
- metadata +24 -3
@@ -17,12 +17,16 @@ module ActiveMerchant #:nodoc:
|
|
17
17
|
self.homepage_url = 'https://www.adyen.com/'
|
18
18
|
self.display_name = 'Adyen'
|
19
19
|
|
20
|
-
PAYMENT_API_VERSION = '
|
21
|
-
RECURRING_API_VERSION = '
|
20
|
+
PAYMENT_API_VERSION = 'v68'
|
21
|
+
RECURRING_API_VERSION = 'v68'
|
22
22
|
|
23
23
|
STANDARD_ERROR_CODE_MAPPING = {
|
24
|
+
'0' => STANDARD_ERROR_CODE[:processing_error],
|
25
|
+
'10' => STANDARD_ERROR_CODE[:config_error],
|
26
|
+
'100' => STANDARD_ERROR_CODE[:invalid_amount],
|
24
27
|
'101' => STANDARD_ERROR_CODE[:incorrect_number],
|
25
28
|
'103' => STANDARD_ERROR_CODE[:invalid_cvc],
|
29
|
+
'104' => STANDARD_ERROR_CODE[:incorrect_address],
|
26
30
|
'131' => STANDARD_ERROR_CODE[:incorrect_address],
|
27
31
|
'132' => STANDARD_ERROR_CODE[:incorrect_address],
|
28
32
|
'133' => STANDARD_ERROR_CODE[:incorrect_address],
|
@@ -62,6 +66,8 @@ module ActiveMerchant #:nodoc:
|
|
62
66
|
add_recurring_contract(post, options)
|
63
67
|
add_network_transaction_reference(post, options)
|
64
68
|
add_application_info(post, options)
|
69
|
+
add_level_2_data(post, options)
|
70
|
+
add_level_3_data(post, options)
|
65
71
|
commit('authorise', post, options)
|
66
72
|
end
|
67
73
|
|
@@ -71,6 +77,7 @@ module ActiveMerchant #:nodoc:
|
|
71
77
|
add_reference(post, authorization, options)
|
72
78
|
add_splits(post, options)
|
73
79
|
add_network_transaction_reference(post, options)
|
80
|
+
add_shopper_statement(post, options)
|
74
81
|
commit('capture', post, options)
|
75
82
|
end
|
76
83
|
|
@@ -117,7 +124,7 @@ module ActiveMerchant #:nodoc:
|
|
117
124
|
add_extra_data(post, credit_card, options)
|
118
125
|
add_stored_credentials(post, credit_card, options)
|
119
126
|
add_address(post, options)
|
120
|
-
|
127
|
+
add_network_transaction_reference(post, options)
|
121
128
|
options[:recurring_contract_type] ||= 'RECURRING'
|
122
129
|
add_recurring_contract(post, options)
|
123
130
|
|
@@ -216,7 +223,7 @@ module ActiveMerchant #:nodoc:
|
|
216
223
|
NETWORK_TOKENIZATION_CARD_SOURCE = {
|
217
224
|
'apple_pay' => 'applepay',
|
218
225
|
'android_pay' => 'androidpay',
|
219
|
-
'google_pay' => '
|
226
|
+
'google_pay' => 'googlepay'
|
220
227
|
}
|
221
228
|
|
222
229
|
def add_extra_data(post, payment, options)
|
@@ -242,6 +249,48 @@ module ActiveMerchant #:nodoc:
|
|
242
249
|
add_merchant_data(post, options)
|
243
250
|
end
|
244
251
|
|
252
|
+
def extract_and_transform(mapper, from)
|
253
|
+
mapper.each_with_object({}) do |key_map, hsh|
|
254
|
+
key, item_key = key_map[0], key_map[1]
|
255
|
+
hsh[key] = from[item_key.to_sym]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def add_level_2_data(post, options)
|
260
|
+
return unless options[:level_2_data].present?
|
261
|
+
|
262
|
+
mapper = {
|
263
|
+
"enhancedSchemeData.totalTaxAmount": 'total_tax_amount',
|
264
|
+
"enhancedSchemeData.customerReference": 'customer_reference'
|
265
|
+
}
|
266
|
+
post[:additionalData].merge!(extract_and_transform(mapper, options[:level_2_data]))
|
267
|
+
end
|
268
|
+
|
269
|
+
def add_level_3_data(post, options)
|
270
|
+
return unless options[:level_3_data].present?
|
271
|
+
|
272
|
+
mapper = { "enhancedSchemeData.freightAmount": 'freight_amount',
|
273
|
+
"enhancedSchemeData.destinationStateProvinceCode": 'destination_state_province_code',
|
274
|
+
"enhancedSchemeData.shipFromPostalCode": 'ship_from_postal_code',
|
275
|
+
"enhancedSchemeData.orderDate": 'order_date',
|
276
|
+
"enhancedSchemeData.destinationPostalCode": 'destination_postal_code',
|
277
|
+
"enhancedSchemeData.destinationCountryCode": 'destination_country_code',
|
278
|
+
"enhancedSchemeData.dutyAmount": 'duty_amount' }
|
279
|
+
|
280
|
+
post[:additionalData].merge!(extract_and_transform(mapper, options[:level_3_data]))
|
281
|
+
|
282
|
+
item_detail_keys = %w[description product_code quantity unit_of_measure unit_price discount_amount total_amount commodity_code]
|
283
|
+
if options[:level_3_data][:items].present?
|
284
|
+
options[:level_3_data][:items].last(9).each.with_index(1) do |item, index|
|
285
|
+
mapper = item_detail_keys.each_with_object({}) do |key, hsh|
|
286
|
+
hsh["enhancedSchemeData.itemDetailLine#{index}.#{key.camelize(:lower)}"] = key
|
287
|
+
end
|
288
|
+
post[:additionalData].merge!(extract_and_transform(mapper, item))
|
289
|
+
end
|
290
|
+
end
|
291
|
+
post[:additionalData].compact!
|
292
|
+
end
|
293
|
+
|
245
294
|
def add_shopper_data(post, options)
|
246
295
|
post[:shopperEmail] = options[:email] if options[:email]
|
247
296
|
post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
|
@@ -251,6 +300,14 @@ module ActiveMerchant #:nodoc:
|
|
251
300
|
post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
|
252
301
|
end
|
253
302
|
|
303
|
+
def add_shopper_statement(post, options)
|
304
|
+
return unless options[:shopper_statement]
|
305
|
+
|
306
|
+
post[:additionalData] = {
|
307
|
+
shopperStatement: options[:shopper_statement]
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
254
311
|
def add_merchant_data(post, options)
|
255
312
|
post[:additionalData][:subMerchantID] = options[:sub_merchant_id] if options[:sub_merchant_id]
|
256
313
|
post[:additionalData][:subMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
|
@@ -387,7 +444,7 @@ module ActiveMerchant #:nodoc:
|
|
387
444
|
elsif payment.is_a?(Check)
|
388
445
|
add_bank_account(post, payment, options, action)
|
389
446
|
else
|
390
|
-
add_mpi_data_for_network_tokenization_card(post, payment) if payment.is_a?(NetworkTokenizationCreditCard)
|
447
|
+
add_mpi_data_for_network_tokenization_card(post, payment, options) if payment.is_a?(NetworkTokenizationCreditCard)
|
391
448
|
add_card(post, payment)
|
392
449
|
end
|
393
450
|
end
|
@@ -396,7 +453,7 @@ module ActiveMerchant #:nodoc:
|
|
396
453
|
bank = {
|
397
454
|
bankAccountNumber: bank_account.account_number,
|
398
455
|
ownerName: bank_account.name,
|
399
|
-
countryCode: options[:billing_address][:country
|
456
|
+
countryCode: options[:billing_address].try(:[], :country)
|
400
457
|
}
|
401
458
|
|
402
459
|
action == 'refundWithData' ? bank[:iban] = bank_account.routing_number : bank[:bankLocationId] = bank_account.routing_number
|
@@ -438,7 +495,9 @@ module ActiveMerchant #:nodoc:
|
|
438
495
|
post[:originalReference] = original_reference
|
439
496
|
end
|
440
497
|
|
441
|
-
def add_mpi_data_for_network_tokenization_card(post, payment)
|
498
|
+
def add_mpi_data_for_network_tokenization_card(post, payment, options)
|
499
|
+
return if options[:skip_mpi_data] == 'Y'
|
500
|
+
|
442
501
|
post[:mpiData] = {}
|
443
502
|
post[:mpiData][:authenticationResponse] = 'Y'
|
444
503
|
post[:mpiData][:cavv] = payment.payment_cryptogram
|
@@ -616,7 +675,7 @@ module ActiveMerchant #:nodoc:
|
|
616
675
|
|
617
676
|
def success_from(action, response, options)
|
618
677
|
if %w[RedirectShopper ChallengeShopper].include?(response.dig('resultCode')) && !options[:execute_threed] && !options[:threed_dynamic]
|
619
|
-
response['refusalReason'] = 'Received unexpected 3DS authentication response
|
678
|
+
response['refusalReason'] = 'Received unexpected 3DS authentication response, but a 3DS initiation flag was not included in the request.'
|
620
679
|
return false
|
621
680
|
end
|
622
681
|
case action.to_s
|
@@ -21,6 +21,12 @@ module ActiveMerchant #:nodoc:
|
|
21
21
|
void: '/pa/payment_intents/%{id}/cancel'
|
22
22
|
}
|
23
23
|
|
24
|
+
# Provided by Airwallex for testing purposes
|
25
|
+
TEST_NETWORK_TRANSACTION_IDS = {
|
26
|
+
visa: '123456789012345',
|
27
|
+
master: 'MCC123ABC0101'
|
28
|
+
}
|
29
|
+
|
24
30
|
def initialize(options = {})
|
25
31
|
requires!(options, :client_id, :client_api_key)
|
26
32
|
@client_id = options[:client_id]
|
@@ -30,17 +36,15 @@ module ActiveMerchant #:nodoc:
|
|
30
36
|
end
|
31
37
|
|
32
38
|
def purchase(money, card, options = {})
|
33
|
-
requires!(options, :return_url)
|
34
|
-
|
35
39
|
payment_intent_id = create_payment_intent(money, options)
|
36
40
|
post = {
|
37
41
|
'request_id' => request_id(options),
|
38
|
-
'merchant_order_id' => merchant_order_id(options)
|
39
|
-
'return_url' => options[:return_url]
|
42
|
+
'merchant_order_id' => merchant_order_id(options)
|
40
43
|
}
|
41
44
|
add_card(post, card, options)
|
42
45
|
add_descriptor(post, options)
|
43
46
|
add_stored_credential(post, options)
|
47
|
+
add_return_url(post, options)
|
44
48
|
post['payment_method_options'] = { 'card' => { 'auto_capture' => false } } if authorization_only?(options)
|
45
49
|
|
46
50
|
add_three_ds(post, options)
|
@@ -108,15 +112,19 @@ module ActiveMerchant #:nodoc:
|
|
108
112
|
private
|
109
113
|
|
110
114
|
def request_id(options)
|
111
|
-
options[:request_id] ||
|
115
|
+
options[:request_id] || generate_uuid
|
112
116
|
end
|
113
117
|
|
114
118
|
def merchant_order_id(options)
|
115
|
-
options[:merchant_order_id] || options[:order_id] ||
|
119
|
+
options[:merchant_order_id] || options[:order_id] || generate_uuid
|
116
120
|
end
|
117
121
|
|
118
|
-
def
|
119
|
-
|
122
|
+
def add_return_url(post, options)
|
123
|
+
post[:return_url] = options[:return_url] if options[:return_url]
|
124
|
+
end
|
125
|
+
|
126
|
+
def generate_uuid
|
127
|
+
SecureRandom.uuid
|
120
128
|
end
|
121
129
|
|
122
130
|
def setup_access_token
|
@@ -134,13 +142,19 @@ module ActiveMerchant #:nodoc:
|
|
134
142
|
base_url + ENDPOINTS[action].to_s % { id: id }
|
135
143
|
end
|
136
144
|
|
145
|
+
def add_referrer_data(post)
|
146
|
+
post[:referrer_data] = { type: 'spreedly' }
|
147
|
+
end
|
148
|
+
|
137
149
|
def create_payment_intent(money, options = {})
|
138
150
|
post = {}
|
139
151
|
add_invoice(post, money, options)
|
140
152
|
add_order(post, options)
|
141
153
|
post[:request_id] = "#{request_id(options)}_setup"
|
142
|
-
post[:merchant_order_id] =
|
154
|
+
post[:merchant_order_id] = merchant_order_id(options)
|
155
|
+
add_referrer_data(post)
|
143
156
|
add_descriptor(post, options)
|
157
|
+
post['payment_method_options'] = { 'card' => { 'risk_control' => { 'three_ds_action' => 'SKIP_3DS' } } } if options[:skip_3ds]
|
144
158
|
|
145
159
|
response = commit(:setup, post)
|
146
160
|
raise ArgumentError.new(response.message) unless response.success?
|
@@ -199,7 +213,8 @@ module ActiveMerchant #:nodoc:
|
|
199
213
|
'expiry_year' => card.year.to_s,
|
200
214
|
'number' => card.number.to_s,
|
201
215
|
'name' => card.name,
|
202
|
-
'cvc' => card.verification_value
|
216
|
+
'cvc' => card.verification_value,
|
217
|
+
'brand' => card.brand
|
203
218
|
}
|
204
219
|
}
|
205
220
|
add_billing(post, card, options)
|
@@ -240,10 +255,23 @@ module ActiveMerchant #:nodoc:
|
|
240
255
|
external_recurring_data[:merchant_trigger_reason] = 'unscheduled'
|
241
256
|
end
|
242
257
|
|
243
|
-
external_recurring_data[:original_transaction_id] = stored_credential.dig(:network_transaction_id)
|
258
|
+
external_recurring_data[:original_transaction_id] = test_mit?(options) ? test_network_transaction_id(post) : stored_credential.dig(:network_transaction_id)
|
244
259
|
external_recurring_data[:triggered_by] = stored_credential.dig(:initiator) == 'cardholder' ? 'customer' : 'merchant'
|
245
260
|
end
|
246
261
|
|
262
|
+
def test_network_transaction_id(post)
|
263
|
+
case post['payment_method']['card']['brand']
|
264
|
+
when 'visa'
|
265
|
+
TEST_NETWORK_TRANSACTION_IDS[:visa]
|
266
|
+
when 'master'
|
267
|
+
TEST_NETWORK_TRANSACTION_IDS[:master]
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_mit?(options)
|
272
|
+
test? && options.dig(:stored_credential, :initiator) == 'merchant'
|
273
|
+
end
|
274
|
+
|
247
275
|
def add_three_ds(post, options)
|
248
276
|
return unless three_d_secure = options[:three_d_secure]
|
249
277
|
|
@@ -293,6 +321,7 @@ module ActiveMerchant #:nodoc:
|
|
293
321
|
|
294
322
|
def commit(action, post, id = nil)
|
295
323
|
url = build_request_url(action, id)
|
324
|
+
|
296
325
|
post_headers = { 'Authorization' => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
297
326
|
response = parse(ssl_post(url, post_data(post), post_headers))
|
298
327
|
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'jose'
|
2
|
+
|
3
|
+
module ActiveMerchant #:nodoc:
|
4
|
+
module Billing #:nodoc:
|
5
|
+
class AleloGateway < Gateway
|
6
|
+
class_attribute :prelive_url
|
7
|
+
|
8
|
+
self.test_url = 'https://sandbox-api.alelo.com.br/alelo/sandbox/'
|
9
|
+
self.live_url = 'https://api.alelo.com.br/alelo/prd/'
|
10
|
+
self.prelive_url = 'https://api.homologacaoalelo.com.br/alelo/uat/'
|
11
|
+
|
12
|
+
self.supported_countries = ['BR']
|
13
|
+
self.default_currency = 'BRL'
|
14
|
+
self.supported_cardtypes = %i[visa master american_express discover]
|
15
|
+
|
16
|
+
self.homepage_url = 'https://www.alelo.com.br'
|
17
|
+
self.display_name = 'Alelo'
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
requires!(options, :client_id, :client_secret)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def purchase(money, payment, options = {})
|
25
|
+
post = {}
|
26
|
+
add_order(post, options)
|
27
|
+
add_amount(post, money)
|
28
|
+
add_payment(post, payment)
|
29
|
+
add_geolocation(post, options)
|
30
|
+
add_extra_data(post, options)
|
31
|
+
|
32
|
+
commit('capture/transaction', post, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def refund(money, authorization, options = {})
|
36
|
+
request_id = authorization.split('#').first
|
37
|
+
options[:http] = { method: :put, prevent_encrypt: true }
|
38
|
+
commit('capture/transaction/refund', { requestId: request_id }, options, :put)
|
39
|
+
end
|
40
|
+
|
41
|
+
def supports_scrubbing?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def scrub(transcript)
|
46
|
+
force_utf8(transcript.encode).
|
47
|
+
gsub(%r((Authorization: Bearer )[\w -]+), '\1[FILTERED]').
|
48
|
+
gsub(%r((client_id=|Client-Id:)[\w -]+), '\1[FILTERED]\2').
|
49
|
+
gsub(%r((client_secret=|Client-Secret:)[\w -]+), '\1[FILTERED]\2').
|
50
|
+
gsub(%r((access_token\":\")[^\"]*), '\1[FILTERED]').
|
51
|
+
gsub(%r((publicKey\":\")[^\"]*), '\1[FILTERED]')
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def force_utf8(string)
|
57
|
+
return nil unless string
|
58
|
+
|
59
|
+
# binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?')
|
60
|
+
string.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_amount(post, money)
|
64
|
+
post[:amount] = amount(money).to_f
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_order(post, options)
|
68
|
+
post[:requestId] = options[:order_id]
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_extra_data(post, options)
|
72
|
+
post.merge!({
|
73
|
+
establishmentCode: options[:establishment_code],
|
74
|
+
playerIdentification: options[:player_identification],
|
75
|
+
captureType: '3', # send fixed value 3 to ecommerce
|
76
|
+
subMerchantCode: options[:sub_merchant_mcc],
|
77
|
+
externalTraceNumber: options[:external_trace_number]
|
78
|
+
}.compact)
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_geolocation(post, options)
|
82
|
+
return if options[:geo_latitude].blank? || options[:geo_longitude].blank?
|
83
|
+
|
84
|
+
post.merge!(geolocation: {
|
85
|
+
latitude: options[:geo_latitude],
|
86
|
+
longitude: options[:geo_longitude]
|
87
|
+
})
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_payment(post, payment)
|
91
|
+
post.merge!({
|
92
|
+
cardNumber: payment.number,
|
93
|
+
cardholderName: payment.name,
|
94
|
+
expirationMonth: payment.month,
|
95
|
+
expirationYear: format(payment.year, :two_digits).to_i,
|
96
|
+
securityCode: payment.verification_value
|
97
|
+
})
|
98
|
+
end
|
99
|
+
|
100
|
+
def fetch_access_token
|
101
|
+
params = {
|
102
|
+
grant_type: 'client_credentials',
|
103
|
+
client_id: @options[:client_id],
|
104
|
+
client_secret: @options[:client_secret],
|
105
|
+
scope: '/capture'
|
106
|
+
}
|
107
|
+
|
108
|
+
headers = {
|
109
|
+
'Accept' => 'application/json',
|
110
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
111
|
+
}
|
112
|
+
|
113
|
+
parsed = parse(ssl_post(url('captura-oauth-provider/oauth/token'), post_data(params), headers))
|
114
|
+
Response.new(true, parsed[:access_token], parsed)
|
115
|
+
end
|
116
|
+
|
117
|
+
def remote_encryption_key(access_token)
|
118
|
+
response = parse(ssl_get(url('capture/key'), request_headers(access_token)))
|
119
|
+
Response.new(true, response[:publicKey], response)
|
120
|
+
end
|
121
|
+
|
122
|
+
def ensure_credentials(try_again = true)
|
123
|
+
multiresp = MultiResponse.new
|
124
|
+
access_token = @options[:access_token]
|
125
|
+
key = @options[:encryption_key]
|
126
|
+
uuid = @options[:encryption_uuid]
|
127
|
+
|
128
|
+
if access_token.blank?
|
129
|
+
multiresp.process { fetch_access_token }
|
130
|
+
access_token = multiresp.message
|
131
|
+
key = nil
|
132
|
+
uuid = nil
|
133
|
+
end
|
134
|
+
|
135
|
+
if key.blank?
|
136
|
+
multiresp.process { remote_encryption_key(access_token) }
|
137
|
+
key = multiresp.message
|
138
|
+
uuid = multiresp.params['uuid']
|
139
|
+
end
|
140
|
+
|
141
|
+
{
|
142
|
+
key: key,
|
143
|
+
uuid: uuid,
|
144
|
+
access_token: access_token,
|
145
|
+
multiresp: multiresp.responses.present? ? multiresp : nil
|
146
|
+
}
|
147
|
+
rescue ResponseError => error
|
148
|
+
# retry to generate a new access_token when the provided one is expired
|
149
|
+
raise error unless try_again && %w(401 404).include?(error.response.code) && @options[:access_token].present?
|
150
|
+
|
151
|
+
@options.delete(:access_token)
|
152
|
+
@options.delete(:encryption_key)
|
153
|
+
ensure_credentials false
|
154
|
+
end
|
155
|
+
|
156
|
+
def encrypt_payload(body, credentials, options)
|
157
|
+
key = OpenSSL::PKey::RSA.new(Base64.decode64(credentials[:key]))
|
158
|
+
jwk = JOSE::JWK.from_key(key)
|
159
|
+
alg_enc = { 'alg' => 'RSA-OAEP-256', 'enc' => 'A128CBC-HS256' }
|
160
|
+
|
161
|
+
token = JOSE::JWE.block_encrypt(jwk, body.to_json, alg_enc).compact
|
162
|
+
|
163
|
+
encrypted_body = {
|
164
|
+
token: token,
|
165
|
+
uuid: credentials[:uuid]
|
166
|
+
}
|
167
|
+
|
168
|
+
encrypted_body.to_json
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse(body)
|
172
|
+
JSON.parse(body, symbolize_names: true)
|
173
|
+
end
|
174
|
+
|
175
|
+
def post_data(params)
|
176
|
+
params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
|
177
|
+
end
|
178
|
+
|
179
|
+
def commit(action, body, options, try_again = true)
|
180
|
+
credentials = ensure_credentials
|
181
|
+
payload = encrypt_payload(body, credentials, options)
|
182
|
+
|
183
|
+
if options.dig :http, :method
|
184
|
+
payload = body.to_json if options.dig :http, :prevent_encrypt
|
185
|
+
response = parse ssl_request(options[:http][:method], url(action), payload, request_headers(credentials[:access_token]))
|
186
|
+
else
|
187
|
+
response = parse ssl_post(url(action), payload, request_headers(credentials[:access_token]))
|
188
|
+
end
|
189
|
+
|
190
|
+
resp = Response.new(
|
191
|
+
success_from(action, response),
|
192
|
+
message_from(response),
|
193
|
+
response,
|
194
|
+
authorization: authorization_from(response, options),
|
195
|
+
test: test?
|
196
|
+
)
|
197
|
+
|
198
|
+
return resp unless credentials[:multiresp].present?
|
199
|
+
|
200
|
+
multiresp = credentials[:multiresp]
|
201
|
+
resp.params.merge!({
|
202
|
+
'access_token' => credentials[:access_token],
|
203
|
+
'encryption_key' => credentials[:key],
|
204
|
+
'encryption_uuid' => credentials[:uuid]
|
205
|
+
})
|
206
|
+
multiresp.process { resp }
|
207
|
+
|
208
|
+
multiresp
|
209
|
+
rescue ActiveMerchant::ResponseError => e
|
210
|
+
# Retry on a possible expired encryption key
|
211
|
+
if try_again && %w(401 404).include?(e.response.code) && @options[:encryption_key].present?
|
212
|
+
@options.delete(:encryption_key)
|
213
|
+
commit(action, body, options, false)
|
214
|
+
else
|
215
|
+
res = parse(e.response.body)
|
216
|
+
Response.new(false, res[:messageUser] || res[:error], res, test: test?)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def success_from(action, response)
|
221
|
+
case action
|
222
|
+
when 'capture/transaction/refund'
|
223
|
+
response[:status] == 'ESTORNADA'
|
224
|
+
when 'capture/transaction'
|
225
|
+
response[:status] == 'CONFIRMADA'
|
226
|
+
else
|
227
|
+
false
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def message_from(response)
|
232
|
+
response[:messages] || response[:messageUser]
|
233
|
+
end
|
234
|
+
|
235
|
+
def authorization_from(response, options)
|
236
|
+
[response[:requestId]].join('#')
|
237
|
+
end
|
238
|
+
|
239
|
+
def url(action)
|
240
|
+
return prelive_url if @options[:url_override] == 'prelive'
|
241
|
+
|
242
|
+
"#{test? ? test_url : live_url}#{action}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def request_headers(access_token)
|
246
|
+
{
|
247
|
+
'Accept' => 'application/json',
|
248
|
+
'X-IBM-Client-Id' => @options[:client_id],
|
249
|
+
'X-IBM-Client-Secret' => @options[:client_secret],
|
250
|
+
'Content-Type' => 'application/json',
|
251
|
+
'Authorization' => "Bearer #{access_token}"
|
252
|
+
}
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -90,7 +90,6 @@ module ActiveMerchant
|
|
90
90
|
}.freeze
|
91
91
|
|
92
92
|
APPLE_PAY_DATA_DESCRIPTOR = 'COMMON.APPLE.INAPP.PAYMENT'
|
93
|
-
|
94
93
|
PAYMENT_METHOD_NOT_SUPPORTED_ERROR = '155'
|
95
94
|
INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = '54'
|
96
95
|
|
@@ -176,11 +175,29 @@ module ActiveMerchant
|
|
176
175
|
end
|
177
176
|
end
|
178
177
|
|
179
|
-
def verify(
|
178
|
+
def verify(payment_method, options = {})
|
179
|
+
amount = amount_for_verify(options)
|
180
|
+
|
180
181
|
MultiResponse.run(:use_first_response) do |r|
|
181
|
-
r.process { authorize(
|
182
|
-
r.process(:ignore_result) { void(r.authorization, options) }
|
182
|
+
r.process { authorize(amount, payment_method, options) }
|
183
|
+
r.process(:ignore_result) { void(r.authorization, options) } unless amount == 0
|
183
184
|
end
|
185
|
+
rescue ArgumentError => e
|
186
|
+
Response.new(false, e.message)
|
187
|
+
end
|
188
|
+
|
189
|
+
def amount_for_verify(options)
|
190
|
+
return 100 unless options[:verify_amount].present?
|
191
|
+
|
192
|
+
amount = options[:verify_amount]
|
193
|
+
raise ArgumentError.new 'verify_amount value must be an integer' unless amount.is_a?(Integer) && !amount.negative? || amount.is_a?(String) && amount.match?(/^\d+$/) && !amount.to_i.negative?
|
194
|
+
raise ArgumentError.new 'Billing address including zip code is required for a 0 amount verify' if amount.to_i.zero? && !validate_billing_address_values?(options)
|
195
|
+
|
196
|
+
amount.to_i
|
197
|
+
end
|
198
|
+
|
199
|
+
def validate_billing_address_values?(options)
|
200
|
+
options.dig(:billing_address, :zip).present? && options.dig(:billing_address, :address1).present?
|
184
201
|
end
|
185
202
|
|
186
203
|
def store(credit_card, options = {})
|
@@ -76,6 +76,7 @@ module ActiveMerchant #:nodoc:
|
|
76
76
|
add_transaction_type(post, :authorization)
|
77
77
|
add_customer_ip(post, options)
|
78
78
|
add_recurring_payment(post, options)
|
79
|
+
add_three_ds(post, options)
|
79
80
|
commit(post)
|
80
81
|
end
|
81
82
|
|
@@ -88,6 +89,7 @@ module ActiveMerchant #:nodoc:
|
|
88
89
|
add_transaction_type(post, purchase_action(source))
|
89
90
|
add_customer_ip(post, options)
|
90
91
|
add_recurring_payment(post, options)
|
92
|
+
add_three_ds(post, options)
|
91
93
|
commit(post)
|
92
94
|
end
|
93
95
|
|
@@ -215,6 +217,22 @@ module ActiveMerchant #:nodoc:
|
|
215
217
|
def build_response(*args)
|
216
218
|
Response.new(*args)
|
217
219
|
end
|
220
|
+
|
221
|
+
def add_three_ds(post, options)
|
222
|
+
return unless three_d_secure = options[:three_d_secure]
|
223
|
+
|
224
|
+
post[:SecureXID] = (three_d_secure[:ds_transaction_id] || three_d_secure[:xid]) if three_d_secure.slice(:ds_transaction_id, :xid).values.any?
|
225
|
+
post[:SecureECI] = formatted_three_ds_eci(three_d_secure[:eci]) if three_d_secure[:eci].present?
|
226
|
+
post[:SecureCAVV] = three_d_secure[:cavv] if three_d_secure[:cavv].present?
|
227
|
+
end
|
228
|
+
|
229
|
+
def formatted_three_ds_eci(val)
|
230
|
+
case val
|
231
|
+
when '05', '02' then 5
|
232
|
+
when '06', '01' then 6
|
233
|
+
else val.to_i
|
234
|
+
end
|
235
|
+
end
|
218
236
|
end
|
219
237
|
end
|
220
238
|
end
|
@@ -68,6 +68,8 @@ module ActiveMerchant
|
|
68
68
|
'business_savings' => 'CORPORATE_SAVINGS'
|
69
69
|
}
|
70
70
|
|
71
|
+
SHOPPER_INITIATOR = %w(CUSTOMER CARDHOLDER)
|
72
|
+
|
71
73
|
STATE_CODE_COUNTRIES = %w(US CA)
|
72
74
|
|
73
75
|
def initialize(options = {})
|
@@ -179,6 +181,7 @@ module ActiveMerchant
|
|
179
181
|
doc.send('store-card', options[:store_card] || false)
|
180
182
|
add_amount(doc, money, options)
|
181
183
|
add_fraud_info(doc, payment_method, options)
|
184
|
+
add_stored_credentials(doc, options)
|
182
185
|
|
183
186
|
if payment_method.is_a?(String)
|
184
187
|
doc.send('vaulted-shopper-id', payment_method)
|
@@ -190,6 +193,19 @@ module ActiveMerchant
|
|
190
193
|
end
|
191
194
|
end
|
192
195
|
|
196
|
+
def add_stored_credentials(doc, options)
|
197
|
+
return unless stored_credential = options[:stored_credential]
|
198
|
+
|
199
|
+
initiator = stored_credential[:initiator]&.upcase
|
200
|
+
initiator = 'SHOPPER' if SHOPPER_INITIATOR.include?(initiator)
|
201
|
+
doc.send('transaction-initiator', initiator) if stored_credential[:initiator]
|
202
|
+
if stored_credential[:network_transaction_id]
|
203
|
+
doc.send('network-transaction-info') do
|
204
|
+
doc.send('original-network-transaction-id', stored_credential[:network_transaction_id])
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
193
209
|
def add_amount(doc, money, options)
|
194
210
|
currency = options[:currency] || currency(money)
|
195
211
|
doc.amount(localized_amount(money, currency))
|
@@ -244,6 +260,7 @@ module ActiveMerchant
|
|
244
260
|
def add_order(doc, options)
|
245
261
|
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
|
246
262
|
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
|
263
|
+
doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
|
247
264
|
add_metadata(doc, options)
|
248
265
|
add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
|
249
266
|
add_level_3_data(doc, options)
|
@@ -350,6 +367,7 @@ module ActiveMerchant
|
|
350
367
|
def add_alt_transaction_purchase(doc, money, payment_method_details, options)
|
351
368
|
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
|
352
369
|
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
|
370
|
+
doc.send('descriptor-phone-number', options[:descriptor_phone_number]) if options[:descriptor_phone_number]
|
353
371
|
add_amount(doc, money, options)
|
354
372
|
|
355
373
|
vaulted_shopper_id = payment_method_details.vaulted_shopper_id
|
@@ -358,6 +376,7 @@ module ActiveMerchant
|
|
358
376
|
add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?) if payment_method_details.check?
|
359
377
|
|
360
378
|
add_fraud_info(doc, payment_method_details.payment_method, options)
|
379
|
+
add_stored_credentials(doc, options)
|
361
380
|
add_metadata(doc, options)
|
362
381
|
end
|
363
382
|
|
@@ -512,7 +531,9 @@ module ActiveMerchant
|
|
512
531
|
end
|
513
532
|
|
514
533
|
def authorization_from(action, parsed_response, payment_method_details)
|
515
|
-
|
534
|
+
return vaulted_shopper_id(parsed_response, payment_method_details) if action == :store
|
535
|
+
|
536
|
+
parsed_response['refund-transaction-id'] || parsed_response['transaction-id']
|
516
537
|
end
|
517
538
|
|
518
539
|
def vaulted_shopper_id(parsed_response, payment_method_details)
|