activemerchant 1.102.0 → 1.103.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +12 -0
- data/lib/active_merchant/billing/gateways/adyen.rb +69 -14
- data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +6 -0
- data/lib/active_merchant/billing/gateways/credorax.rb +8 -2
- data/lib/active_merchant/billing/gateways/cyber_source.rb +2 -1
- data/lib/active_merchant/billing/gateways/d_local.rb +4 -4
- data/lib/active_merchant/billing/gateways/ixopay.rb +21 -10
- data/lib/active_merchant/billing/gateways/quickbooks.rb +6 -2
- data/lib/active_merchant/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a51e307707d9ffa9cd0d32779623b7095ea9af27f16078de9736be65fc8c0090
|
4
|
+
data.tar.gz: 27b16b69f6bb974795ddc1fae4d924257bee5c0620d0ab009b771b107d59be24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d0e03c0a615dabf2ef0299baaac647eabe906f08bfbb3343e545d3a7ee1390678a963a4a1433b24cb478264b7ad260a30c947d827e91e835f6892ca31644399
|
7
|
+
data.tar.gz: db9a355a166614c1ebe4b018ba1a5634448bd5be14aa34e9db55837d529b1aad4f10994cf476793e38619fb7d8c7785ab014b5dac4d2a33bcea3720da8a98689
|
data/CHANGELOG
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
== HEAD
|
4
4
|
|
5
|
+
== Version 1.103.0 (Dec 2, 2019)
|
6
|
+
* Quickbooks: Mark transactions that returned `AuthorizationFailed` as failures [britth] #3447
|
7
|
+
* Credorax: Add referral CFT transactions [leila-alderman] #3432
|
8
|
+
* DLocal: Updates for version 2.1 [molbrown] #3449
|
9
|
+
* CyberSource: Send MDD on capture [leila-alderman] #3453
|
10
|
+
* Ixopay: Include extra_data gateway specific field [therufs] #3450
|
11
|
+
* CyberSource: Fix XML error on capture [leila-alderman] #3454
|
12
|
+
* Adyen: Add gateway specific field for splits [leila-alderman] #3448
|
13
|
+
* Adyen: Add `unstore` and `storeToken` actions with '/Recurring' endpoint [deedeelavinder][davidsantoso] #3438
|
14
|
+
* Barclaycard Smartpay: Add functionality to set 3DS exemptions via API [britth] #3457
|
15
|
+
* Use null@cybersource.com when option[:email] is an empty string [pi3r] #3462
|
16
|
+
|
5
17
|
== Version 1.102.0 (Nov 14, 2019)
|
6
18
|
* Quickbooks: Make token refresh optional with allow_refresh flag [britth] #3419
|
7
19
|
* Paymentez: Update supported countries [curiousepic] #3425
|
@@ -4,10 +4,10 @@ module ActiveMerchant #:nodoc:
|
|
4
4
|
|
5
5
|
# we recommend setting up merchant-specific endpoints.
|
6
6
|
# https://docs.adyen.com/developers/api-manual#apiendpoints
|
7
|
-
self.test_url = 'https://pal-test.adyen.com/pal/servlet/
|
8
|
-
self.live_url = 'https://pal-live.adyen.com/pal/servlet/
|
7
|
+
self.test_url = 'https://pal-test.adyen.com/pal/servlet/'
|
8
|
+
self.live_url = 'https://pal-live.adyen.com/pal/servlet/'
|
9
9
|
|
10
|
-
self.supported_countries =
|
10
|
+
self.supported_countries = %w(AT AU BE BG BR CH CY CZ DE DK EE ES FI FR GB GI GR HK HU IE IS IT LI LT LU LV MC MT MX NL NO PL PT RO SE SG SK SI US)
|
11
11
|
self.default_currency = 'USD'
|
12
12
|
self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
|
13
13
|
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja, :cabal]
|
@@ -17,7 +17,8 @@ module ActiveMerchant #:nodoc:
|
|
17
17
|
self.homepage_url = 'https://www.adyen.com/'
|
18
18
|
self.display_name = 'Adyen'
|
19
19
|
|
20
|
-
|
20
|
+
PAYMENT_API_VERSION = 'v40'
|
21
|
+
RECURRING_API_VERSION = 'v30'
|
21
22
|
|
22
23
|
STANDARD_ERROR_CODE_MAPPING = {
|
23
24
|
'101' => STANDARD_ERROR_CODE[:incorrect_number],
|
@@ -57,6 +58,7 @@ module ActiveMerchant #:nodoc:
|
|
57
58
|
add_installments(post, options) if options[:installments]
|
58
59
|
add_3ds(post, options)
|
59
60
|
add_3ds_authenticated_data(post, options)
|
61
|
+
add_splits(post, options)
|
60
62
|
commit('authorise', post, options)
|
61
63
|
end
|
62
64
|
|
@@ -64,6 +66,7 @@ module ActiveMerchant #:nodoc:
|
|
64
66
|
post = init_post(options)
|
65
67
|
add_invoice_for_modification(post, money, options)
|
66
68
|
add_reference(post, authorization, options)
|
69
|
+
add_splits(post, options)
|
67
70
|
commit('capture', post, options)
|
68
71
|
end
|
69
72
|
|
@@ -71,6 +74,7 @@ module ActiveMerchant #:nodoc:
|
|
71
74
|
post = init_post(options)
|
72
75
|
add_invoice_for_modification(post, money, options)
|
73
76
|
add_original_reference(post, authorization, options)
|
77
|
+
add_splits(post, options)
|
74
78
|
commit('refund', post, options)
|
75
79
|
end
|
76
80
|
|
@@ -98,7 +102,9 @@ module ActiveMerchant #:nodoc:
|
|
98
102
|
add_recurring_contract(post, options)
|
99
103
|
add_address(post, options)
|
100
104
|
|
101
|
-
|
105
|
+
action = options[:tokenize_only] ? 'storeToken' : 'authorise'
|
106
|
+
|
107
|
+
initial_response = commit(action, post, options)
|
102
108
|
|
103
109
|
if initial_response.success? && card_not_stored?(initial_response)
|
104
110
|
unsupported_failure_response(initial_response)
|
@@ -107,6 +113,17 @@ module ActiveMerchant #:nodoc:
|
|
107
113
|
end
|
108
114
|
end
|
109
115
|
|
116
|
+
def unstore(options={})
|
117
|
+
requires!(options, :shopper_reference, :recurring_detail_reference)
|
118
|
+
post = {}
|
119
|
+
|
120
|
+
add_shopper_reference(post, options)
|
121
|
+
add_merchant_account(post, options)
|
122
|
+
post[:recurringDetailReference] = options[:recurring_detail_reference]
|
123
|
+
|
124
|
+
commit('disable', post, options)
|
125
|
+
end
|
126
|
+
|
110
127
|
def verify(credit_card, options={})
|
111
128
|
MultiResponse.run(:use_first_response) do |r|
|
112
129
|
r.process { authorize(0, credit_card, options) }
|
@@ -179,7 +196,6 @@ module ActiveMerchant #:nodoc:
|
|
179
196
|
post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone)
|
180
197
|
post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
|
181
198
|
post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
|
182
|
-
post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
|
183
199
|
post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
|
184
200
|
post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
|
185
201
|
post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
|
@@ -198,6 +214,7 @@ module ActiveMerchant #:nodoc:
|
|
198
214
|
post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test?
|
199
215
|
post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
|
200
216
|
add_risk_data(post, options)
|
217
|
+
add_shopper_reference(post, options)
|
201
218
|
end
|
202
219
|
|
203
220
|
def add_risk_data(post, options)
|
@@ -207,11 +224,39 @@ module ActiveMerchant #:nodoc:
|
|
207
224
|
end
|
208
225
|
end
|
209
226
|
|
227
|
+
def add_splits(post, options)
|
228
|
+
return unless split_data = options[:splits]
|
229
|
+
splits = []
|
230
|
+
split_data.each do |split|
|
231
|
+
amount = {
|
232
|
+
value: split['amount']['value'],
|
233
|
+
}
|
234
|
+
amount[:currency] = split['amount']['currency'] if split['amount']['currency']
|
235
|
+
|
236
|
+
split_hash = {
|
237
|
+
amount: amount,
|
238
|
+
type: split['type'],
|
239
|
+
reference: split['reference']
|
240
|
+
}
|
241
|
+
split_hash['account'] = split['account'] if split['account']
|
242
|
+
splits.push(split_hash)
|
243
|
+
end
|
244
|
+
post[:splits] = splits
|
245
|
+
end
|
246
|
+
|
210
247
|
def add_stored_credentials(post, payment, options)
|
211
248
|
add_shopper_interaction(post, payment, options)
|
212
249
|
add_recurring_processing_model(post, options)
|
213
250
|
end
|
214
251
|
|
252
|
+
def add_merchant_account(post, options)
|
253
|
+
post[:merchantAccount] = options[:merchant_account] || @merchant_account
|
254
|
+
end
|
255
|
+
|
256
|
+
def add_shopper_reference(post, options)
|
257
|
+
post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
|
258
|
+
end
|
259
|
+
|
215
260
|
def add_shopper_interaction(post, payment, options={})
|
216
261
|
if options.dig(:stored_credential, :initial_transaction) || (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
|
217
262
|
shopper_interaction = 'Ecommerce'
|
@@ -401,7 +446,7 @@ module ActiveMerchant #:nodoc:
|
|
401
446
|
|
402
447
|
def commit(action, parameters, options)
|
403
448
|
begin
|
404
|
-
raw_response = ssl_post(
|
449
|
+
raw_response = ssl_post(url(action), post_data(action, parameters), request_headers(options))
|
405
450
|
response = parse(raw_response)
|
406
451
|
rescue ResponseError => e
|
407
452
|
raw_response = e.response.body
|
@@ -428,13 +473,18 @@ module ActiveMerchant #:nodoc:
|
|
428
473
|
CVC_MAPPING[response['additionalData']['cvcResult'][0]] if response.dig('additionalData', 'cvcResult')
|
429
474
|
end
|
430
475
|
|
431
|
-
def
|
476
|
+
def endpoint(action)
|
477
|
+
recurring = %w(disable storeToken).include?(action)
|
478
|
+
recurring ? "Recurring/#{RECURRING_API_VERSION}/#{action}" : "Payment/#{PAYMENT_API_VERSION}/#{action}"
|
479
|
+
end
|
480
|
+
|
481
|
+
def url(action)
|
432
482
|
if test?
|
433
|
-
"#{test_url}#{
|
483
|
+
"#{test_url}#{endpoint(action)}"
|
434
484
|
elsif @options[:subdomain]
|
435
|
-
"https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet
|
485
|
+
"https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/#{endpoint(action)}"
|
436
486
|
else
|
437
|
-
"#{live_url}#{
|
487
|
+
"#{live_url}#{endpoint(action)}"
|
438
488
|
end
|
439
489
|
end
|
440
490
|
|
@@ -459,6 +509,8 @@ module ActiveMerchant #:nodoc:
|
|
459
509
|
response['response'] == "[#{action}-received]"
|
460
510
|
when 'adjustAuthorisation'
|
461
511
|
response['response'] == 'Authorised' || response['response'] == '[adjustAuthorisation-received]'
|
512
|
+
when 'storeToken'
|
513
|
+
response['result'] == 'Success'
|
462
514
|
else
|
463
515
|
false
|
464
516
|
end
|
@@ -466,26 +518,29 @@ module ActiveMerchant #:nodoc:
|
|
466
518
|
|
467
519
|
def message_from(action, response)
|
468
520
|
return authorize_message_from(response) if action.to_s == 'authorise' || action.to_s == 'authorise3d'
|
469
|
-
response['response'] || response['message']
|
521
|
+
response['response'] || response['message'] || response['result']
|
470
522
|
end
|
471
523
|
|
472
524
|
def authorize_message_from(response)
|
473
525
|
if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw']
|
474
526
|
"#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}"
|
475
527
|
else
|
476
|
-
response['refusalReason'] || response['resultCode'] || response['message']
|
528
|
+
response['refusalReason'] || response['resultCode'] || response['message'] || response['result']
|
477
529
|
end
|
478
530
|
end
|
479
531
|
|
480
532
|
def authorization_from(action, parameters, response)
|
481
533
|
return nil if response['pspReference'].nil?
|
534
|
+
|
482
535
|
recurring = response['additionalData']['recurring.recurringDetailReference'] if response['additionalData']
|
536
|
+
recurring = response['recurringDetailReference'] if action == 'storeToken'
|
537
|
+
|
483
538
|
"#{parameters[:originalReference]}##{response['pspReference']}##{recurring}"
|
484
539
|
end
|
485
540
|
|
486
541
|
def init_post(options = {})
|
487
542
|
post = {}
|
488
|
-
post
|
543
|
+
add_merchant_account(post, options)
|
489
544
|
post[:reference] = options[:order_id] if options[:order_id]
|
490
545
|
post
|
491
546
|
end
|
@@ -359,6 +359,12 @@ module ActiveMerchant #:nodoc:
|
|
359
359
|
add_browser_info(three_ds_2_options[:browser_info], post)
|
360
360
|
post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] }
|
361
361
|
end
|
362
|
+
|
363
|
+
if options.has_key?(:execute_threed)
|
364
|
+
post[:additionalData] ||= {}
|
365
|
+
post[:additionalData][:executeThreeD] = options[:execute_threed]
|
366
|
+
post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption]
|
367
|
+
end
|
362
368
|
else
|
363
369
|
return unless options[:execute_threed] || options[:threed_dynamic]
|
364
370
|
post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
|
@@ -187,8 +187,13 @@ module ActiveMerchant #:nodoc:
|
|
187
187
|
add_echo(post, options)
|
188
188
|
add_submerchant_id(post, options)
|
189
189
|
add_processor(post, options)
|
190
|
+
add_email(post, options)
|
190
191
|
|
191
|
-
|
192
|
+
if options[:referral_cft]
|
193
|
+
commit(:referral_cft, post)
|
194
|
+
else
|
195
|
+
commit(:refund, post)
|
196
|
+
end
|
192
197
|
end
|
193
198
|
|
194
199
|
def credit(amount, payment_method, options={})
|
@@ -372,7 +377,8 @@ module ActiveMerchant #:nodoc:
|
|
372
377
|
purchase_void: '7',
|
373
378
|
refund_void: '8',
|
374
379
|
capture_void: '9',
|
375
|
-
threeds_completion: '92'
|
380
|
+
threeds_completion: '92',
|
381
|
+
referral_cft: '34'
|
376
382
|
}
|
377
383
|
|
378
384
|
def commit(action, params, reference_action = nil)
|
@@ -288,6 +288,7 @@ module ActiveMerchant #:nodoc:
|
|
288
288
|
|
289
289
|
xml = Builder::XmlMarkup.new :indent => 2
|
290
290
|
add_purchase_data(xml, money, true, options)
|
291
|
+
add_mdd_fields(xml, options)
|
291
292
|
add_capture_service(xml, request_id, request_token)
|
292
293
|
add_business_rules_data(xml, authorization, options)
|
293
294
|
add_issuer_additional_data(xml, options)
|
@@ -468,7 +469,7 @@ module ActiveMerchant #:nodoc:
|
|
468
469
|
xml.tag! 'company', address[:company] unless address[:company].blank?
|
469
470
|
xml.tag! 'companyTaxID', address[:companyTaxID] unless address[:company_tax_id].blank?
|
470
471
|
xml.tag! 'phoneNumber', address[:phone] unless address[:phone].blank?
|
471
|
-
xml.tag! 'email', options[:email] || 'null@cybersource.com'
|
472
|
+
xml.tag! 'email', options[:email].presence || 'null@cybersource.com'
|
472
473
|
xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo
|
473
474
|
xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank?
|
474
475
|
xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank?
|
@@ -32,7 +32,7 @@ module ActiveMerchant #:nodoc:
|
|
32
32
|
|
33
33
|
def capture(money, authorization, options={})
|
34
34
|
post = {}
|
35
|
-
post[:
|
35
|
+
post[:authorization_id] = authorization
|
36
36
|
add_invoice(post, money, options) if money
|
37
37
|
commit('capture', post, options)
|
38
38
|
end
|
@@ -47,7 +47,7 @@ module ActiveMerchant #:nodoc:
|
|
47
47
|
|
48
48
|
def void(authorization, options={})
|
49
49
|
post = {}
|
50
|
-
post[:
|
50
|
+
post[:authorization_id] = authorization
|
51
51
|
commit('void', post, options)
|
52
52
|
end
|
53
53
|
|
@@ -193,9 +193,9 @@ module ActiveMerchant #:nodoc:
|
|
193
193
|
when 'refund'
|
194
194
|
'refunds'
|
195
195
|
when 'capture'
|
196
|
-
|
196
|
+
'payments'
|
197
197
|
when 'void'
|
198
|
-
"payments/#{parameters[:
|
198
|
+
"payments/#{parameters[:authorization_id]}/cancel"
|
199
199
|
end
|
200
200
|
end
|
201
201
|
|
@@ -145,6 +145,7 @@ module ActiveMerchant #:nodoc:
|
|
145
145
|
xml.transactionId new_transaction_id
|
146
146
|
|
147
147
|
add_customer_data(xml, options)
|
148
|
+
add_extra_data(xml, options[:extra_data]) if options[:extra_data]
|
148
149
|
|
149
150
|
xml.amount localized_amount(money, currency)
|
150
151
|
xml.currency currency
|
@@ -162,6 +163,7 @@ module ActiveMerchant #:nodoc:
|
|
162
163
|
xml.transactionId new_transaction_id
|
163
164
|
|
164
165
|
add_customer_data(xml, options)
|
166
|
+
add_extra_data(xml, options[:extra_data]) if options[:extra_data]
|
165
167
|
|
166
168
|
xml.amount localized_amount(money, currency)
|
167
169
|
xml.currency currency
|
@@ -174,17 +176,19 @@ module ActiveMerchant #:nodoc:
|
|
174
176
|
currency = options[:currency] || currency(money)
|
175
177
|
|
176
178
|
xml.refund do
|
177
|
-
xml.transactionId
|
178
|
-
xml
|
179
|
-
xml.
|
180
|
-
xml.currency
|
179
|
+
xml.transactionId new_transaction_id
|
180
|
+
add_extra_data(xml, options[:extra_data]) if options[:extra_data]
|
181
|
+
xml.referenceTransactionId authorization&.split('|')&.first
|
182
|
+
xml.amount localized_amount(money, currency)
|
183
|
+
xml.currency currency
|
181
184
|
end
|
182
185
|
end
|
183
186
|
|
184
187
|
def add_void(xml, authorization)
|
185
188
|
xml.void do
|
186
|
-
xml.transactionId
|
187
|
-
xml
|
189
|
+
xml.transactionId new_transaction_id
|
190
|
+
add_extra_data(xml, options[:extra_data]) if options[:extra_data]
|
191
|
+
xml.referenceTransactionId authorization&.split('|')&.first
|
188
192
|
end
|
189
193
|
end
|
190
194
|
|
@@ -192,10 +196,11 @@ module ActiveMerchant #:nodoc:
|
|
192
196
|
currency = options[:currency] || currency(money)
|
193
197
|
|
194
198
|
xml.capture_ do
|
195
|
-
xml.transactionId
|
196
|
-
xml
|
197
|
-
xml.
|
198
|
-
xml.currency
|
199
|
+
xml.transactionId new_transaction_id
|
200
|
+
add_extra_data(xml, options[:extra_data]) if options[:extra_data]
|
201
|
+
xml.referenceTransactionId authorization&.split('|')&.first
|
202
|
+
xml.amount localized_amount(money, currency)
|
203
|
+
xml.currency currency
|
199
204
|
end
|
200
205
|
end
|
201
206
|
|
@@ -246,6 +251,12 @@ module ActiveMerchant #:nodoc:
|
|
246
251
|
SecureRandom.uuid
|
247
252
|
end
|
248
253
|
|
254
|
+
def add_extra_data(xml, extra_data)
|
255
|
+
extra_data.each do |k, v|
|
256
|
+
xml.extraData(v, key: k)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
249
260
|
def commit(request)
|
250
261
|
url = (test? ? test_url : live_url)
|
251
262
|
|
@@ -321,7 +321,7 @@ module ActiveMerchant #:nodoc:
|
|
321
321
|
def success?(response)
|
322
322
|
return FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) if response['errors']
|
323
323
|
|
324
|
-
!['DECLINED', 'CANCELLED'].include?(response['status']) && !['AuthenticationFailed'].include?(response['code'])
|
324
|
+
!['DECLINED', 'CANCELLED'].include?(response['status']) && !['AuthenticationFailed', 'AuthorizationFailed'].include?(response['code'])
|
325
325
|
end
|
326
326
|
|
327
327
|
def message_from(response)
|
@@ -329,7 +329,11 @@ module ActiveMerchant #:nodoc:
|
|
329
329
|
end
|
330
330
|
|
331
331
|
def errors_from(response)
|
332
|
-
|
332
|
+
if ['AuthenticationFailed', 'AuthorizationFailed'].include?(response['code'])
|
333
|
+
response['code']
|
334
|
+
else
|
335
|
+
response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : ''
|
336
|
+
end
|
333
337
|
end
|
334
338
|
|
335
339
|
def authorization_from(response, headers = {})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activemerchant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.103.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tobias Luetke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|