spree_vpago 2.0.6 → 2.0.8.pre.beta
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/Gemfile.lock +3 -3
- data/app/helpers/vpago/admin/base_helper_decorator.rb +1 -0
- data/app/models/spree/gateway/true_money.rb +90 -0
- data/app/models/spree/gateway/vattanac_mini_app.rb +2 -2
- data/app/models/vpago/payment_decorator.rb +1 -0
- data/app/models/vpago/payment_method_decorator.rb +7 -1
- data/app/services/vpago/payment_processor.rb +1 -1
- data/app/services/vpago/payment_url_constructor.rb +2 -0
- data/app/services/vpago/rsa_handler.rb +7 -1
- data/app/views/spree/admin/payments/source_views/_true_money.html.erb +6 -0
- data/app/views/spree/vpago_payments/forms/spree/gateway/_true_money.html.erb +12 -0
- data/lib/spree_vpago/engine.rb +2 -1
- data/lib/spree_vpago/version.rb +1 -1
- data/lib/vpago/true_money/base.rb +131 -0
- data/lib/vpago/true_money/checkout.rb +26 -0
- data/lib/vpago/true_money/refund_issuer.rb +21 -0
- data/lib/vpago/true_money/transaction_status.rb +25 -0
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a199976c2692ad4d201ea9890163e430f6ac62eaa65104585718ca081c303fa4
|
4
|
+
data.tar.gz: 076e9460e262219196e66ee01e280274d8920bba2d484989e5caf5eee86a9eeb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67e72026f867e78955f05fe93aed5dfa86d1ef51a919d8af699c372db0067f6a40ad11cbe39d96f6354f33f49117084ecf17f09fc5a23b3f7dfa116635eff961
|
7
|
+
data.tar.gz: 877c24547ea819b7b10fe4c2410b30f6776e859b72b382e11c712854247b02cfa44bbcb39f7c1807d8bd6e2cbda344549e1c151ed7f18a43b95a34bdc92be1a1
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
spree_vpago (2.0.
|
4
|
+
spree_vpago (2.0.8.pre.beta)
|
5
5
|
faraday
|
6
6
|
google-cloud-firestore
|
7
7
|
spree_api (>= 4.5)
|
@@ -242,7 +242,7 @@ GEM
|
|
242
242
|
gapic-common (~> 1.0)
|
243
243
|
google-cloud-errors (~> 1.0)
|
244
244
|
google-logging-utils (0.2.0)
|
245
|
-
google-protobuf (4.31.1)
|
245
|
+
google-protobuf (4.31.1-arm64-darwin)
|
246
246
|
bigdecimal
|
247
247
|
rake (>= 13)
|
248
248
|
googleapis-common-protos (1.8.0)
|
@@ -259,7 +259,7 @@ GEM
|
|
259
259
|
multi_json (~> 1.11)
|
260
260
|
os (>= 0.9, < 2.0)
|
261
261
|
signet (>= 0.16, < 2.a)
|
262
|
-
grpc (1.73.0)
|
262
|
+
grpc (1.73.0-arm64-darwin)
|
263
263
|
google-protobuf (>= 3.25, < 5.0)
|
264
264
|
googleapis-common-protos-types (~> 1.0)
|
265
265
|
hashdiff (1.1.0)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Spree
|
2
|
+
class Gateway::TrueMoney < PaymentMethod
|
3
|
+
preference :check_transaction_url, :string
|
4
|
+
preference :refund_url, :string
|
5
|
+
preference :generate_payment_url, :string
|
6
|
+
preference :access_token_url, :string
|
7
|
+
|
8
|
+
preference :client_id, :string
|
9
|
+
preference :client_secret, :string
|
10
|
+
preference :private_key, :text
|
11
|
+
preference :merchant_scheme, :string
|
12
|
+
preference :merchant_android_package_name, :string
|
13
|
+
|
14
|
+
def method_type
|
15
|
+
'true_money'
|
16
|
+
end
|
17
|
+
|
18
|
+
def payment_source_class
|
19
|
+
Spree::VpagoPaymentSource
|
20
|
+
end
|
21
|
+
|
22
|
+
# force to purchase instead of authorize
|
23
|
+
def auto_capture?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# override
|
28
|
+
# purchase is used when pre auth disabled
|
29
|
+
def purchase(_amount, _source, gateway_options = {})
|
30
|
+
_, payment_number = gateway_options[:order_id].split('-')
|
31
|
+
payment = Spree::Payment.find_by(number: payment_number)
|
32
|
+
|
33
|
+
checker = check_transaction(payment)
|
34
|
+
payment.update(transaction_response: checker.json_response)
|
35
|
+
|
36
|
+
success = checker.success?
|
37
|
+
params = {}
|
38
|
+
|
39
|
+
params[:payment_response] = payment.transaction_response
|
40
|
+
|
41
|
+
if success
|
42
|
+
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Purchased', params)
|
43
|
+
else
|
44
|
+
ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Purchasing Failed', params)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# override
|
49
|
+
def void(_response_code, gateway_options)
|
50
|
+
_, payment_number = gateway_options[:order_id].split('-')
|
51
|
+
payment = Spree::Payment.find_by(number: payment_number)
|
52
|
+
|
53
|
+
if payment.true_money_payment?
|
54
|
+
params = {}
|
55
|
+
success, params[:refund_response] = true_money_refund(payment)
|
56
|
+
|
57
|
+
if success
|
58
|
+
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: successfully canceled.', params)
|
59
|
+
else
|
60
|
+
ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Failed to canceleed', params)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Payment has been voided.')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def true_money_refund(payment)
|
68
|
+
refund_issuer = Vpago::TrueMoney::RefundIssuer.new(payment, {})
|
69
|
+
refund_issuer.call
|
70
|
+
|
71
|
+
[refund_issuer.success?, refund_issuer.response]
|
72
|
+
end
|
73
|
+
|
74
|
+
def cancel(_response_code, _payment)
|
75
|
+
# we can use this to send request to payment gateway api to cancel the payment ( void )
|
76
|
+
# currently Payway does not support to cancel the gateway
|
77
|
+
|
78
|
+
# in our case don't do anything
|
79
|
+
ActiveMerchant::Billing::Response.new(true, '')
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def check_transaction(payment)
|
85
|
+
checker = Vpago::TrueMoney::TransactionStatus.new(payment)
|
86
|
+
checker.call
|
87
|
+
checker
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -59,12 +59,12 @@ module Spree
|
|
59
59
|
[refund_issuer.success?, refund_issuer.response]
|
60
60
|
end
|
61
61
|
|
62
|
-
def cancel(_response_code)
|
62
|
+
def cancel(_response_code, _payment)
|
63
63
|
# we can use this to send request to payment gateway api to cancel the payment ( void )
|
64
64
|
# currently Payway does not support to cancel the gateway
|
65
65
|
|
66
66
|
# in our case don't do anything
|
67
|
-
ActiveMerchant::Billing::Response.new(true, '
|
67
|
+
ActiveMerchant::Billing::Response.new(true, 'Vattanac order has been cancelled.')
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
@@ -6,6 +6,7 @@ module Vpago
|
|
6
6
|
TYPE_ACLEDA = 'Spree::Gateway::Acleda'.freeze
|
7
7
|
TYPE_ACLEDA_MOBILE = 'Spree::Gateway::AcledaMobile'.freeze
|
8
8
|
TYPE_VATTANAC_MINI_APP = 'Spree::Gateway::VattanacMiniApp'.freeze
|
9
|
+
TYPE_TRUE_MONEY = 'Spree::Gateway::TrueMoney'.freeze
|
9
10
|
|
10
11
|
def self.prepended(base)
|
11
12
|
base.preference :icon_name, :string, default: 'cheque'
|
@@ -18,7 +19,8 @@ module Vpago
|
|
18
19
|
Spree::PaymentMethod::TYPE_WINGSDK,
|
19
20
|
Spree::PaymentMethod::TYPE_ACLEDA,
|
20
21
|
Spree::PaymentMethod::TYPE_ACLEDA_MOBILE,
|
21
|
-
Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP
|
22
|
+
Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP,
|
23
|
+
Spree::PaymentMethod::TYPE_TRUE_MONEY
|
22
24
|
]
|
23
25
|
end
|
24
26
|
end
|
@@ -91,6 +93,10 @@ module Vpago
|
|
91
93
|
def type_vattanac_mini_app?
|
92
94
|
type == Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP
|
93
95
|
end
|
96
|
+
|
97
|
+
def type_true_money?
|
98
|
+
type == Spree::PaymentMethod::TYPE_TRUE_MONEY
|
99
|
+
end
|
94
100
|
end
|
95
101
|
end
|
96
102
|
|
@@ -55,7 +55,7 @@ module Vpago
|
|
55
55
|
|
56
56
|
def handle_order_process_completed
|
57
57
|
log_process('handle_order_process_completed') do
|
58
|
-
Vpago::PaymentCapturerJob.
|
58
|
+
Vpago::PaymentCapturerJob.perform_later(@payment.id) if @payment.pending?
|
59
59
|
user_informer.order_is_completed(processing: false)
|
60
60
|
end
|
61
61
|
end
|
@@ -14,6 +14,8 @@ module Vpago
|
|
14
14
|
def success_url = "#{base_url}/vpago_payments/success?#{query}"
|
15
15
|
def process_payment_url = "#{base_url}/vpago_payments/process_payment?#{query}"
|
16
16
|
|
17
|
+
def processing_app_url = "#{@payment.payment_method.preferred_merchant_scheme}/vpago_payments/processing?#{query}"
|
18
|
+
|
17
19
|
def query
|
18
20
|
{ payment_number: payment.number, order_number: order.number, order_jwt_token: order_jwt_token }.to_query
|
19
21
|
end
|
@@ -23,5 +23,11 @@ module Vpago
|
|
23
23
|
signature_bytes = Base64.decode64(signature)
|
24
24
|
public_key_object.verify(OpenSSL::Digest.new('SHA256'), signature_bytes, data)
|
25
25
|
end
|
26
|
+
|
27
|
+
def generate_signature(payload)
|
28
|
+
sign_hash = OpenSSL::Digest.new('SHA256')
|
29
|
+
private_key = OpenSSL::PKey::RSA.new(@private_key)
|
30
|
+
Base64.strict_encode64(private_key.sign(sign_hash, payload))
|
31
|
+
end
|
26
32
|
end
|
27
|
-
end
|
33
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% @checkout = ::Vpago::TrueMoney::Checkout.new(@payment) %>
|
2
|
+
<% webview_url = @checkout.generate_payment_urls[:deeplink] rescue nil %>
|
3
|
+
|
4
|
+
<% if webview_url.present? %>
|
5
|
+
<script>
|
6
|
+
document.addEventListener("DOMContentLoaded", function () {
|
7
|
+
window.location.href = "<%= j webview_url %>";
|
8
|
+
});
|
9
|
+
</script>
|
10
|
+
<% else %>
|
11
|
+
<p>Unable to generate the payment URL. Please try again.</p>
|
12
|
+
<% end %>
|
data/lib/spree_vpago/engine.rb
CHANGED
data/lib/spree_vpago/version.rb
CHANGED
@@ -0,0 +1,131 @@
|
|
1
|
+
module Vpago
|
2
|
+
module TrueMoney
|
3
|
+
class Base
|
4
|
+
TOKEN_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded' }.freeze
|
5
|
+
CONTENT_TYPE_JSON = 'application/json'.freeze
|
6
|
+
DEFAULT_ALGORITHM = 'rsa-sha256'.freeze
|
7
|
+
DEFAULT_KEY_VERSION = 1
|
8
|
+
|
9
|
+
def initialize(payment, options = {})
|
10
|
+
@payment = payment
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def client_id
|
15
|
+
payment_method.preferred_client_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def client_secret
|
19
|
+
payment_method.preferred_client_secret
|
20
|
+
end
|
21
|
+
|
22
|
+
def private_key
|
23
|
+
payment_method.preferred_private_key
|
24
|
+
end
|
25
|
+
|
26
|
+
def external_ref_id
|
27
|
+
@payment.number
|
28
|
+
end
|
29
|
+
|
30
|
+
def service_type = '01'
|
31
|
+
def currency = 'USD'
|
32
|
+
def user_type = 'CUSTOMER'
|
33
|
+
|
34
|
+
def amount
|
35
|
+
@payment.amount
|
36
|
+
end
|
37
|
+
|
38
|
+
def timestamp
|
39
|
+
@timestamp ||= Time.now.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def access_token
|
43
|
+
@access_token ||= fetch_access_token
|
44
|
+
end
|
45
|
+
|
46
|
+
def payload
|
47
|
+
{
|
48
|
+
service_type: service_type,
|
49
|
+
external_ref_id: external_ref_id,
|
50
|
+
amount: amount,
|
51
|
+
currency: currency,
|
52
|
+
user_type: user_type
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def signature
|
57
|
+
Vpago::RsaHandler
|
58
|
+
.new(private_key: private_key)
|
59
|
+
.generate_signature(signature_input)
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_headers
|
63
|
+
{
|
64
|
+
'Client-Id' => client_id,
|
65
|
+
'Authorization' => "Bearer #{access_token}",
|
66
|
+
'Signature' => "algorithm=#{algorithm};keyVersion=#{key_version};signature=#{signature}",
|
67
|
+
'Timestamp' => timestamp.to_s,
|
68
|
+
'Content-Type' => CONTENT_TYPE_JSON
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_transaction_url
|
73
|
+
"#{payment_method.preferred_check_transaction_url}/#{external_ref_id}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def refund_url
|
77
|
+
payment_method.preferred_refund_url
|
78
|
+
end
|
79
|
+
|
80
|
+
def generate_payment_url
|
81
|
+
payment_method.preferred_generate_payment_url
|
82
|
+
end
|
83
|
+
|
84
|
+
def access_token_url
|
85
|
+
payment_method.preferred_access_token_url
|
86
|
+
end
|
87
|
+
|
88
|
+
def merchant_android_package_name
|
89
|
+
payment_method.preferred_merchant_android_package_name
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_json(body)
|
93
|
+
JSON.parse(body)
|
94
|
+
rescue JSON::ParserError
|
95
|
+
{}
|
96
|
+
end
|
97
|
+
|
98
|
+
def payment_method
|
99
|
+
@payment.payment_method
|
100
|
+
end
|
101
|
+
|
102
|
+
def fetch_access_token
|
103
|
+
response = Faraday.post(
|
104
|
+
access_token_url,
|
105
|
+
URI.encode_www_form(
|
106
|
+
grant_type: 'client_credentials',
|
107
|
+
client_id: client_id,
|
108
|
+
client_secret: client_secret
|
109
|
+
),
|
110
|
+
TOKEN_HEADERS
|
111
|
+
)
|
112
|
+
|
113
|
+
raise "Access Token Error: #{response.status} - #{response.body}" unless response.success?
|
114
|
+
|
115
|
+
JSON.parse(response.body).fetch('access_token')
|
116
|
+
end
|
117
|
+
|
118
|
+
def algorithm
|
119
|
+
DEFAULT_ALGORITHM
|
120
|
+
end
|
121
|
+
|
122
|
+
def key_version
|
123
|
+
DEFAULT_KEY_VERSION
|
124
|
+
end
|
125
|
+
|
126
|
+
def signature_input
|
127
|
+
"#{timestamp}#{payload.to_json}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Vpago
|
2
|
+
module TrueMoney
|
3
|
+
class Checkout < Base
|
4
|
+
def generate_payment_urls
|
5
|
+
response = Faraday.post(generate_payment_url) do |req|
|
6
|
+
req.headers = default_headers
|
7
|
+
req.body = {
|
8
|
+
payment_info: payload.to_json,
|
9
|
+
redirectionType: 'mobileapp',
|
10
|
+
merchantDeepLink: @payment.processing_app_url,
|
11
|
+
merchantAndroidPackageName: merchant_android_package_name
|
12
|
+
}.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
body = parse_json(response.body)
|
16
|
+
|
17
|
+
raise "Generate Payment Error: #{response.status} - #{body['message'] || response.body}" unless response.success? && body['status']['code'] == '000001'
|
18
|
+
|
19
|
+
{
|
20
|
+
webview: body['data']['webview'],
|
21
|
+
deeplink: body['data']['deeplink']
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Vpago
|
2
|
+
module TrueMoney
|
3
|
+
class RefundIssuer < Base
|
4
|
+
def call
|
5
|
+
@response = Faraday.post(refund_url, payload.to_json, default_headers)
|
6
|
+
end
|
7
|
+
|
8
|
+
def success?
|
9
|
+
parsed_response.dig('status', 'code') == '000001'
|
10
|
+
end
|
11
|
+
|
12
|
+
def payload
|
13
|
+
{ external_ref_id: external_ref_id }
|
14
|
+
end
|
15
|
+
|
16
|
+
def parsed_response
|
17
|
+
@parsed_response ||= parse_json(@response.body)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Vpago
|
2
|
+
module TrueMoney
|
3
|
+
class TransactionStatus < Base
|
4
|
+
def call
|
5
|
+
@response = Faraday.get(check_transaction_url, nil, default_headers)
|
6
|
+
end
|
7
|
+
|
8
|
+
def success?
|
9
|
+
response_code == '000001'
|
10
|
+
end
|
11
|
+
|
12
|
+
def response_code
|
13
|
+
json_response.dig('status', 'code')
|
14
|
+
end
|
15
|
+
|
16
|
+
def signature_input
|
17
|
+
timestamp.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def json_response
|
21
|
+
@json_response ||= parse_json(@response.body)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spree_vpago
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.8.pre.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- You
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -179,6 +179,7 @@ files:
|
|
179
179
|
- app/models/spree/gateway/acleda_mobile.rb
|
180
180
|
- app/models/spree/gateway/payway.rb
|
181
181
|
- app/models/spree/gateway/payway_v2.rb
|
182
|
+
- app/models/spree/gateway/true_money.rb
|
182
183
|
- app/models/spree/gateway/vattanac_mini_app.rb
|
183
184
|
- app/models/spree/gateway/wing_sdk.rb
|
184
185
|
- app/models/spree/payout.rb
|
@@ -262,6 +263,7 @@ files:
|
|
262
263
|
- app/views/spree/admin/payments/source_views/_acleda_mobile.html.erb
|
263
264
|
- app/views/spree/admin/payments/source_views/_payment_payway.html.erb
|
264
265
|
- app/views/spree/admin/payments/source_views/_payway_v2.html.erb
|
266
|
+
- app/views/spree/admin/payments/source_views/_true_money.html.erb
|
265
267
|
- app/views/spree/admin/payments/source_views/_vattanac_mini_app.html.erb
|
266
268
|
- app/views/spree/admin/payments/source_views/_vpago_payment_tmpl.html.erb
|
267
269
|
- app/views/spree/admin/payments/source_views/_wingsdk.html.erb
|
@@ -309,6 +311,7 @@ files:
|
|
309
311
|
- app/views/spree/payway_v2_card_popups/show.html.erb
|
310
312
|
- app/views/spree/vpago_payments/checkout.html.erb
|
311
313
|
- app/views/spree/vpago_payments/forms/spree/gateway/_payway_v2.html.erb
|
314
|
+
- app/views/spree/vpago_payments/forms/spree/gateway/_true_money.html.erb
|
312
315
|
- app/views/spree/vpago_payments/forms/spree/gateway/_vattanac_mini_app.html.erb
|
313
316
|
- app/views/spree/vpago_payments/processing.html.erb
|
314
317
|
- app/views/spree/vpago_payments/success.html.erb
|
@@ -378,6 +381,10 @@ files:
|
|
378
381
|
- lib/vpago/payway_v2/pre_auth_canceler.rb
|
379
382
|
- lib/vpago/payway_v2/pre_auth_completer.rb
|
380
383
|
- lib/vpago/payway_v2/transaction_status.rb
|
384
|
+
- lib/vpago/true_money/base.rb
|
385
|
+
- lib/vpago/true_money/checkout.rb
|
386
|
+
- lib/vpago/true_money/refund_issuer.rb
|
387
|
+
- lib/vpago/true_money/transaction_status.rb
|
381
388
|
- lib/vpago/vattanac_mini_app/base.rb
|
382
389
|
- lib/vpago/vattanac_mini_app/checkout.rb
|
383
390
|
- lib/vpago/vattanac_mini_app/refund_issuer.rb
|
@@ -408,9 +415,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
408
415
|
version: 3.2.0
|
409
416
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
410
417
|
requirements:
|
411
|
-
- - "
|
418
|
+
- - ">"
|
412
419
|
- !ruby/object:Gem::Version
|
413
|
-
version:
|
420
|
+
version: 1.3.1
|
414
421
|
requirements:
|
415
422
|
- none
|
416
423
|
rubygems_version: 3.4.1
|