monerorequest 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/Gemfile.lock +11 -1
- data/README.md +69 -9
- data/lib/monerorequest/cron.rb +53 -0
- data/lib/monerorequest/decoder.rb +30 -6
- data/lib/monerorequest/encoder.rb +22 -81
- data/lib/monerorequest/pipeline/base64_decoder.rb +14 -0
- data/lib/monerorequest/pipeline/base64_encoder.rb +14 -0
- data/lib/monerorequest/pipeline/gzip_reader.rb +15 -0
- data/lib/monerorequest/pipeline/gzip_writer.rb +20 -0
- data/lib/monerorequest/pipeline/json_encoder.rb +16 -0
- data/lib/monerorequest/pipeline/json_parser.rb +14 -0
- data/lib/monerorequest/v1.rb +49 -0
- data/lib/monerorequest/v2.rb +49 -0
- data/lib/monerorequest/validator/amount.rb +16 -0
- data/lib/monerorequest/validator/change_indicator_url.rb +24 -0
- data/lib/monerorequest/validator/currency.rb +15 -0
- data/lib/monerorequest/validator/custom_label.rb +15 -0
- data/lib/monerorequest/validator/days_per_billing_cycle.rb +16 -0
- data/lib/monerorequest/validator/number_of_payments.rb +16 -0
- data/lib/monerorequest/validator/payment_id.rb +15 -0
- data/lib/monerorequest/validator/schedule.rb +15 -0
- data/lib/monerorequest/validator/sellers_wallet.rb +17 -0
- data/lib/monerorequest/validator/start_date.rb +22 -0
- data/lib/monerorequest/version.rb +1 -1
- data/lib/monerorequest.rb +19 -10
- data/monerorequest.gemspec +39 -0
- metadata +22 -3
- data/sig/monerorequest.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 944c04e247c34be236e6071a5b8f384d11f12a21598b487068f8086ef9fdd7cb
|
4
|
+
data.tar.gz: e681abc0a5490f7fa86811a735d53f091b4ab8effd906cfa00b0737db11ba9c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de8f025de01a42d3a6a4cb701e40122e6c115eee4683331da0789e5886f27ce82d99e5fd81811443b5a00c5f62c811ea3d2540bd1036579404fbe869301a5b04
|
7
|
+
data.tar.gz: c81846b18ec909a1c7b84130a17c1b6a7d7b88d884da06aa6634ee915842b4e14d6eaaf862361fa53af48d5f22b21cf56639b07fd2800d67390e1667711602ff
|
data/Gemfile
CHANGED
@@ -6,6 +6,7 @@ source "https://rubygems.org"
|
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
group :development, :test do
|
9
|
+
gem "byebug", "~> 11.1"
|
9
10
|
gem "faker", "~> 3.3"
|
10
11
|
gem "rake", "~> 13.0"
|
11
12
|
gem "rspec", "~> 3.0"
|
@@ -13,3 +14,7 @@ group :development, :test do
|
|
13
14
|
gem "rubocop-rake", "~> 0.6"
|
14
15
|
gem "rubocop-rspec", "~> 2.29"
|
15
16
|
end
|
17
|
+
|
18
|
+
group :test do
|
19
|
+
gem "simplecov", "~> 0.22", require: false
|
20
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
monerorequest (
|
4
|
+
monerorequest (1.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.4.2)
|
10
|
+
byebug (11.1.3)
|
10
11
|
concurrent-ruby (1.2.3)
|
11
12
|
diff-lcs (1.5.1)
|
13
|
+
docile (1.4.1)
|
12
14
|
faker (3.3.1)
|
13
15
|
i18n (>= 1.8.11, < 2)
|
14
16
|
i18n (1.14.5)
|
@@ -64,12 +66,19 @@ GEM
|
|
64
66
|
rubocop-rspec_rails (2.28.3)
|
65
67
|
rubocop (~> 1.40)
|
66
68
|
ruby-progressbar (1.13.0)
|
69
|
+
simplecov (0.22.0)
|
70
|
+
docile (~> 1.1)
|
71
|
+
simplecov-html (~> 0.11)
|
72
|
+
simplecov_json_formatter (~> 0.1)
|
73
|
+
simplecov-html (0.13.1)
|
74
|
+
simplecov_json_formatter (0.1.4)
|
67
75
|
unicode-display_width (2.5.0)
|
68
76
|
|
69
77
|
PLATFORMS
|
70
78
|
x86_64-linux
|
71
79
|
|
72
80
|
DEPENDENCIES
|
81
|
+
byebug (~> 11.1)
|
73
82
|
faker (~> 3.3)
|
74
83
|
monerorequest!
|
75
84
|
rake (~> 13.0)
|
@@ -77,6 +86,7 @@ DEPENDENCIES
|
|
77
86
|
rubocop (~> 1.21)
|
78
87
|
rubocop-rake (~> 0.6)
|
79
88
|
rubocop-rspec (~> 2.29)
|
89
|
+
simplecov (~> 0.22)
|
80
90
|
|
81
91
|
BUNDLED WITH
|
82
92
|
2.4.10
|
data/README.md
CHANGED
@@ -1,24 +1,75 @@
|
|
1
1
|
# Monerorequest
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/monerorequest`. To experiment with that code, run `bin/console` for an interactive prompt.
|
3
|
+
ruby gem for encoding and decoding [Monero Payment Request Standard](https://github.com/lukeprofits/Monero_Payment_Request_Standard?tab=readme-ov-file#introduction) requests.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
|
-
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
10
|
-
|
11
7
|
Install the gem and add to the application's Gemfile by executing:
|
12
8
|
|
13
|
-
$ bundle add
|
9
|
+
$ bundle add monerorequest
|
14
10
|
|
15
11
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
12
|
|
17
|
-
$ gem install
|
13
|
+
$ gem install monerorequest
|
18
14
|
|
19
15
|
## Usage
|
20
16
|
|
21
|
-
|
17
|
+
|
18
|
+
### V1
|
19
|
+
Encode a request:
|
20
|
+
```ruby
|
21
|
+
require 'monerorequest'
|
22
|
+
|
23
|
+
req = {
|
24
|
+
"custom_label" => "[some string here]",
|
25
|
+
"sellers_wallet" => "[monero main address, starts with 4]",
|
26
|
+
"currency" => "USD",
|
27
|
+
"amount" => 420.69,
|
28
|
+
"payment_id" => "[monero payment ID]",
|
29
|
+
"start_date" => "[timestamp in rfc3339 format]",
|
30
|
+
"days_per_billing_cycle" => 30,
|
31
|
+
"number_of_payments" => 12,
|
32
|
+
"change_indicator_url" => "[some url here]"
|
33
|
+
}
|
34
|
+
enc = Monerorequest::Encoder.new(req, 1)
|
35
|
+
```
|
36
|
+
|
37
|
+
Decode a request:
|
38
|
+
```ruby
|
39
|
+
require 'monerorequest'
|
40
|
+
|
41
|
+
req = "monero-request:1:H4sIAAAAAAACAy2QS4vUQBSF/0qondA9XanqJJ3sZjEozGYW4kooKpWbTjmVqnQ9pjuKID7HlStxZiEo4pMBQRAcFPwvCq5c2O0fMD24utxzOR/n3FuItyZoj4o0iWm+k8YjJBqu58CkrqTg3lgWrEIFarzvisnEGlOOG5AVaAtSNDuw4m2nYCJsuIkGd7AWtOgHx8GVgwvBedMyxUvYYtafHrz49fT13+8nb+rNnS/nm7ff3v04vb+/fvj8w/WAcQw/3z++dDnai67tbp4cP4ouRP7787M/Z69eJtF+tNycnd/bW9/9+NUN+Ir3jnVgWSmVknrORC8UoCKnI6RDWw4XU7OO9y1o71BB8hH6vzFZDYFIlRFe1yTPeVblpB6YDpQC69iSD3P4DZruUtybtGxXcqZS08yrlMeYmMNF1lF+o1FULvzMxwL3C3DikAM/8jSmSpbG9NbWqll1mQtJ6jgJJqFHIe+obDQsLU22NZzn1rOKe9hGwmQ6xskYp1fjWTElBaFjnBUYo9v/ANjVPGWxAQAA"
|
42
|
+
dec = Monerorequest::Decoder.new(req)
|
43
|
+
dec.decode
|
44
|
+
```
|
45
|
+
|
46
|
+
### V2
|
47
|
+
Encode a request:
|
48
|
+
```ruby
|
49
|
+
require 'monerorequest'
|
50
|
+
|
51
|
+
req = {
|
52
|
+
"custom_label" => "[some string here]",
|
53
|
+
"sellers_wallet" => "[monero main address, starts with 4]",
|
54
|
+
"currency" => "USD",
|
55
|
+
"amount" => 420.69,
|
56
|
+
"payment_id" => "[monero payment ID]",
|
57
|
+
"start_date" => "[timestamp in rfc3339 format]",
|
58
|
+
"schedule" => '* * 1 * *',
|
59
|
+
"number_of_payments" => 12,
|
60
|
+
"change_indicator_url" => "[some url here]"
|
61
|
+
}
|
62
|
+
enc = Monerorequest::Encoder.new(req, 2)
|
63
|
+
```
|
64
|
+
|
65
|
+
Decode a request:
|
66
|
+
```ruby
|
67
|
+
require 'monerorequest'
|
68
|
+
|
69
|
+
req = "monero-request:2:H4sIAAAAAAACAy2PTW/CMAyG/wrKEVFIE9rS3nbbkcPukZu4JCNNSj6Abtp/X5h2sCw/9vva/iYw++wSGdqm5v2+rXdEanAXFMYpIyH5IHKwZCA6pWU4HIL3Y6XRKHQBjdR7fMK8WDzIkL9IUecQ0Mm1KM7v5z8Qk5+FhRFfNgljKtTlecQg/CQWWGd0KZKB9TvyXwmjyixTHYNpYn0PnerZVHRRalTZYuluN9tNXWL7wmgthigeUHL5hhzfOF19O85Pc7Kt1xfVQk2Zv966hcOnttzc0inVkq43jPIKCPfEa27N6P0awmT1c+libtoILPuG33O/cKMdPgJv4mtlgpCEgvS6hVF2rGhT0fajPg1HNjBe0W6glPz8AihAH5JjAQAA"
|
70
|
+
dec = Monerorequest::Decoder.new(req)
|
71
|
+
dec.decode
|
72
|
+
```
|
22
73
|
|
23
74
|
## Development
|
24
75
|
|
@@ -26,9 +77,18 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
26
77
|
|
27
78
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
28
79
|
|
80
|
+
## Adding a new Monerorequest version
|
81
|
+
|
82
|
+
Adding a new Monerorequest version should be simple.
|
83
|
+
|
84
|
+
1. Update SUPPORTED_MR_VERSIONS in ```lib/monerorequest.rb```.
|
85
|
+
2. Add any necessary pipelines and validators in their respective folders.
|
86
|
+
3. Define a file named "vX.rb" where X is your version that pulls in the pipelines and validators it needs.
|
87
|
+
4. Make sure you add spec coverage for any new code you write and run ```bundle exec rubocop``` to ensure it passes the linter.
|
88
|
+
|
29
89
|
## Contributing
|
30
90
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
91
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/snex/monerorequest.
|
32
92
|
|
33
93
|
## License
|
34
94
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
# Parses and validates cron syntax string.
|
5
|
+
class Cron
|
6
|
+
MONTH_CODES = %w[jan feb mar apr may jun jul aug sep oct nov dec].freeze
|
7
|
+
DOW_CODES = %w[mon tue wed thu fri sat sun].freeze
|
8
|
+
DELIMITERS = %r{,|-|/}.freeze
|
9
|
+
def self.valid?(schedule)
|
10
|
+
parsed_schedule = parse(schedule)
|
11
|
+
return false unless parsed_schedule
|
12
|
+
|
13
|
+
valid_minutes?(parsed_schedule["minutes"]) && valid_hours?(parsed_schedule["hours"]) &&
|
14
|
+
valid_days?(parsed_schedule["days"]) && valid_months?(parsed_schedule["months"]) &&
|
15
|
+
valid_dow?(parsed_schedule["dow"])
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.valid_minutes?(minutes)
|
19
|
+
minutes.all? { |min| ("0".."59").member?(min.to_s) } || minutes == ["*"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.valid_hours?(hours)
|
23
|
+
hours.all? { |hr| ("0".."23").member?(hr.to_s) } || hours == ["*"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.valid_days?(days)
|
27
|
+
days.all? { |dy| ("1".."31").member?(dy.to_s) } || days == ["*"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.valid_months?(months)
|
31
|
+
months.all? do |mth|
|
32
|
+
("1".."12").member?(mth.to_s) || MONTH_CODES.include?(mth.to_s.downcase)
|
33
|
+
end || months == ["*"] || months == ["L"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.valid_dow?(dow)
|
37
|
+
dow.all? { |d| DOW_CODES.include?(d.downcase) } || dow == ["*"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.parse(schedule)
|
41
|
+
schedule_parts = schedule.split
|
42
|
+
return false if schedule_parts.length != 5
|
43
|
+
|
44
|
+
{
|
45
|
+
"minutes" => schedule_parts[0].split(DELIMITERS),
|
46
|
+
"hours" => schedule_parts[1].split(DELIMITERS),
|
47
|
+
"days" => schedule_parts[2].split(DELIMITERS),
|
48
|
+
"months" => schedule_parts[3].split(DELIMITERS),
|
49
|
+
"dow" => schedule_parts[4].split(DELIMITERS)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,19 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
Monerorequest::SUPPORTED_MR_VERSIONS.each do |mr_v|
|
4
|
+
require_relative "v#{mr_v}"
|
5
|
+
end
|
6
|
+
|
3
7
|
module Monerorequest
|
4
|
-
# class to
|
8
|
+
# class to choose a proper Decoder Version and then pass the encoded data to it for decoding
|
5
9
|
class Decoder
|
10
|
+
attr_reader :errors
|
11
|
+
|
6
12
|
def initialize(request)
|
7
13
|
@request = request
|
14
|
+
_, @version, @encoded_str = @request.split(":")
|
15
|
+
raise RequestVersionError, @version unless Monerorequest::SUPPORTED_MR_VERSIONS.include?(@version.to_i)
|
16
|
+
|
17
|
+
@decoder = Object.const_get("Monerorequest::V#{@version}")
|
8
18
|
end
|
9
19
|
|
10
20
|
def decode
|
11
|
-
|
12
|
-
|
21
|
+
@data = @encoded_str
|
22
|
+
|
23
|
+
@decoder::Decoder::PIPELINES.each do |pipeline|
|
24
|
+
@data = pipeline.call(@data)
|
25
|
+
end
|
26
|
+
|
27
|
+
@data["version"] = @version.to_i
|
28
|
+
validate!
|
29
|
+
@data
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def validate!
|
35
|
+
@errors = []
|
36
|
+
@decoder::VALIDATORS.each do |validator|
|
37
|
+
@errors += validator.validate!(@data)
|
38
|
+
end
|
13
39
|
|
14
|
-
|
15
|
-
json_str = Zlib::GzipReader.new(StringIO.new(compressed_data)).read
|
16
|
-
JSON.parse(json_str)
|
40
|
+
raise InvalidRequestError, "Invalid request: #{@errors}" unless @errors.empty?
|
17
41
|
end
|
18
42
|
end
|
19
43
|
end
|
@@ -1,105 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require "stringio"
|
7
|
-
require "uri"
|
8
|
-
require "zlib"
|
3
|
+
Monerorequest::SUPPORTED_MR_VERSIONS.each do |mr_v|
|
4
|
+
require_relative "v#{mr_v}"
|
5
|
+
end
|
9
6
|
|
10
7
|
module Monerorequest
|
11
8
|
# class to Encode a Monerorequest hash
|
12
9
|
class Encoder
|
13
|
-
class InvalidRequest < StandardError; end
|
14
|
-
|
15
|
-
attr_accessor :request
|
16
10
|
attr_reader :errors
|
17
11
|
|
18
|
-
def initialize(request)
|
19
|
-
|
20
|
-
@errors = []
|
12
|
+
def initialize(request, version)
|
13
|
+
raise RequestVersionError, version unless Monerorequest::SUPPORTED_MR_VERSIONS.include?(version.to_i)
|
21
14
|
|
15
|
+
@request = request
|
16
|
+
@version = version.to_i
|
17
|
+
set_encoder_version
|
22
18
|
validate!
|
23
19
|
end
|
24
20
|
|
25
|
-
def encode
|
26
|
-
|
27
|
-
|
28
|
-
json_str = @request.sort.to_h.to_json.force_encoding("ascii")
|
29
|
-
compressed_data = StringIO.new
|
30
|
-
gz = Zlib::GzipWriter.new(compressed_data, 9)
|
31
|
-
gz.mtime = 0
|
32
|
-
gz.write(json_str)
|
33
|
-
gz.close
|
34
|
-
encoded_str = Base64.encode64(compressed_data.string).gsub("\n", "")
|
35
|
-
"monero-request:1:#{encoded_str}"
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def validate!
|
41
|
-
validate_custom_label!
|
42
|
-
validate_sellers_wallet!
|
43
|
-
validate_currency!
|
44
|
-
validate_amount!
|
45
|
-
validate_payment_id!
|
46
|
-
validate_start_date!
|
47
|
-
validate_days_per_billing_cycle!
|
48
|
-
validate_change_indicator_url!
|
49
|
-
end
|
50
|
-
|
51
|
-
def validate_custom_label!
|
52
|
-
@errors.push("custom_label must be present.") unless @request.key?("custom_label")
|
53
|
-
@errors.push("custom_label must be a String.") unless @request["custom_label"].is_a?(String)
|
54
|
-
end
|
21
|
+
def encode
|
22
|
+
data = @request
|
55
23
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@errors.push("sellers_wallet must be a main Monero address.")
|
61
|
-
end
|
62
|
-
|
63
|
-
def validate_currency!
|
64
|
-
@errors.push("currency must be present.") unless @request.key?("currency")
|
65
|
-
@errors.push("currency must be a String.") unless @request["currency"].is_a?(String)
|
66
|
-
end
|
67
|
-
|
68
|
-
def validate_amount!
|
69
|
-
@errors.push("amount must be present.") unless @request.key?("amount")
|
70
|
-
@errors.push("amount must be a Numeric.") unless @request["amount"].is_a?(Numeric)
|
71
|
-
end
|
24
|
+
@encoder::Encoder::PIPELINES.each do |pipeline|
|
25
|
+
data = pipeline.call(data)
|
26
|
+
end
|
72
27
|
|
73
|
-
|
74
|
-
@errors.push("payment_id must be present.") unless @request.key?("payment_id")
|
75
|
-
@errors.push("payment_id must be a Monero Payment ID.") unless MoneroPaymentID.valid?(@request["payment_id"])
|
28
|
+
"monero-request:#{@version}:#{data}"
|
76
29
|
end
|
77
30
|
|
78
|
-
|
79
|
-
@errors.push("start_date must be present.") unless @request.key?("start_date")
|
80
|
-
unless @request["start_date"].is_a?(String)
|
81
|
-
@errors.push("start_date must be a String.") && @request["start_date"] = ""
|
82
|
-
end
|
83
|
-
begin
|
84
|
-
DateTime.rfc3339(@request["start_date"])
|
85
|
-
rescue Date::Error
|
86
|
-
@errors.push("start_date must be an RFC3339 timestamp.")
|
87
|
-
end
|
88
|
-
end
|
31
|
+
private
|
89
32
|
|
90
|
-
def
|
91
|
-
@
|
92
|
-
@errors.push("days_per_billing_cycle must be a Integer.") unless @request["days_per_billing_cycle"].is_a?(Integer)
|
33
|
+
def set_encoder_version
|
34
|
+
@encoder = Object.const_get("Monerorequest::V#{@version}")
|
93
35
|
end
|
94
36
|
|
95
|
-
def
|
96
|
-
@errors
|
97
|
-
|
98
|
-
@errors
|
37
|
+
def validate!
|
38
|
+
@errors = []
|
39
|
+
@encoder::VALIDATORS.each do |validator|
|
40
|
+
@errors += validator.validate!(@request)
|
99
41
|
end
|
100
|
-
return if @request["change_indicator_url"] =~ URI::DEFAULT_PARSER.make_regexp
|
101
42
|
|
102
|
-
@errors
|
43
|
+
raise InvalidRequestError, "Invalid request: #{@errors}" unless @errors.empty?
|
103
44
|
end
|
104
45
|
end
|
105
46
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Monerorequest
|
6
|
+
module Pipeline
|
7
|
+
# pipeline that takes a base64 encoded string and decodes it
|
8
|
+
class Base64Decoder
|
9
|
+
def self.call(input)
|
10
|
+
Base64.strict_decode64(input)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Monerorequest
|
6
|
+
module Pipeline
|
7
|
+
# pipeline that takes a string and base64 encodes it
|
8
|
+
class Base64Encoder
|
9
|
+
def self.call(input)
|
10
|
+
Base64.strict_encode64(input)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
module Monerorequest
|
7
|
+
module Pipeline
|
8
|
+
# pipeline that takes a gzip blob and unzips it
|
9
|
+
class GzipReader
|
10
|
+
def self.call(input)
|
11
|
+
Zlib::GzipReader.new(StringIO.new(input)).read
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
module Monerorequest
|
7
|
+
module Pipeline
|
8
|
+
# pipeline that takes a string and gzips it
|
9
|
+
class GzipWriter
|
10
|
+
def self.call(input)
|
11
|
+
output = StringIO.new
|
12
|
+
gz = Zlib::GzipWriter.new(output, 9)
|
13
|
+
gz.mtime = 0
|
14
|
+
gz.write(input)
|
15
|
+
gz.close
|
16
|
+
output.string
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Monerorequest
|
6
|
+
module Pipeline
|
7
|
+
# pipeline that takes a hash, sorts by keys, then converts to valid JSON in ASCII encoding
|
8
|
+
class JSONEncoder
|
9
|
+
def self.call(input)
|
10
|
+
raise InvalidRequestError, "Request must be a Hash." unless input.is_a?(Hash)
|
11
|
+
|
12
|
+
input.sort.to_h.to_json.force_encoding("ascii")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Monerorequest
|
6
|
+
module Pipeline
|
7
|
+
# pipeline that takes a JSON string and converts it into a ruby object
|
8
|
+
class JSONParser
|
9
|
+
def self.call(input)
|
10
|
+
JSON.parse(input)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "pipeline/base64_decoder"
|
4
|
+
require_relative "pipeline/base64_encoder"
|
5
|
+
require_relative "pipeline/gzip_reader"
|
6
|
+
require_relative "pipeline/gzip_writer"
|
7
|
+
require_relative "pipeline/json_parser"
|
8
|
+
require_relative "pipeline/json_encoder"
|
9
|
+
require_relative "validator/custom_label"
|
10
|
+
require_relative "validator/sellers_wallet"
|
11
|
+
require_relative "validator/currency"
|
12
|
+
require_relative "validator/amount"
|
13
|
+
require_relative "validator/payment_id"
|
14
|
+
require_relative "validator/start_date"
|
15
|
+
require_relative "validator/days_per_billing_cycle"
|
16
|
+
require_relative "validator/number_of_payments"
|
17
|
+
require_relative "validator/change_indicator_url"
|
18
|
+
|
19
|
+
module Monerorequest
|
20
|
+
module V1
|
21
|
+
VALIDATORS = [
|
22
|
+
Monerorequest::Validator::CustomLabel,
|
23
|
+
Monerorequest::Validator::SellersWallet,
|
24
|
+
Monerorequest::Validator::Currency,
|
25
|
+
Monerorequest::Validator::Amount,
|
26
|
+
Monerorequest::Validator::PaymentID,
|
27
|
+
Monerorequest::Validator::StartDate,
|
28
|
+
Monerorequest::Validator::DaysPerBillingCycle,
|
29
|
+
Monerorequest::Validator::NumberOfPayments,
|
30
|
+
Monerorequest::Validator::ChangeIndicatorURL
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
module Encoder
|
34
|
+
PIPELINES = [
|
35
|
+
Monerorequest::Pipeline::JSONEncoder,
|
36
|
+
Monerorequest::Pipeline::GzipWriter,
|
37
|
+
Monerorequest::Pipeline::Base64Encoder
|
38
|
+
].freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
module Decoder
|
42
|
+
PIPELINES = [
|
43
|
+
Monerorequest::Pipeline::Base64Decoder,
|
44
|
+
Monerorequest::Pipeline::GzipReader,
|
45
|
+
Monerorequest::Pipeline::JSONParser
|
46
|
+
].freeze
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "pipeline/base64_decoder"
|
4
|
+
require_relative "pipeline/base64_encoder"
|
5
|
+
require_relative "pipeline/gzip_reader"
|
6
|
+
require_relative "pipeline/gzip_writer"
|
7
|
+
require_relative "pipeline/json_parser"
|
8
|
+
require_relative "pipeline/json_encoder"
|
9
|
+
require_relative "validator/custom_label"
|
10
|
+
require_relative "validator/sellers_wallet"
|
11
|
+
require_relative "validator/currency"
|
12
|
+
require_relative "validator/amount"
|
13
|
+
require_relative "validator/payment_id"
|
14
|
+
require_relative "validator/start_date"
|
15
|
+
require_relative "validator/schedule"
|
16
|
+
require_relative "validator/number_of_payments"
|
17
|
+
require_relative "validator/change_indicator_url"
|
18
|
+
|
19
|
+
module Monerorequest
|
20
|
+
module V2
|
21
|
+
VALIDATORS = [
|
22
|
+
Monerorequest::Validator::CustomLabel,
|
23
|
+
Monerorequest::Validator::SellersWallet,
|
24
|
+
Monerorequest::Validator::Currency,
|
25
|
+
Monerorequest::Validator::Amount,
|
26
|
+
Monerorequest::Validator::PaymentID,
|
27
|
+
Monerorequest::Validator::StartDate,
|
28
|
+
Monerorequest::Validator::Schedule,
|
29
|
+
Monerorequest::Validator::NumberOfPayments,
|
30
|
+
Monerorequest::Validator::ChangeIndicatorURL
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
module Encoder
|
34
|
+
PIPELINES = [
|
35
|
+
Monerorequest::Pipeline::JSONEncoder,
|
36
|
+
Monerorequest::Pipeline::GzipWriter,
|
37
|
+
Monerorequest::Pipeline::Base64Encoder
|
38
|
+
].freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
module Decoder
|
42
|
+
PIPELINES = [
|
43
|
+
Monerorequest::Pipeline::Base64Decoder,
|
44
|
+
Monerorequest::Pipeline::GzipReader,
|
45
|
+
Monerorequest::Pipeline::JSONParser
|
46
|
+
].freeze
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the amount field
|
6
|
+
class Amount
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("amount must be present.") unless data.key?("amount")
|
10
|
+
errors.push("amount must be a Numeric.") unless data["amount"].is_a?(Numeric)
|
11
|
+
errors.push("amount must be positive.") unless data["amount"].to_f.positive?
|
12
|
+
errors
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module Monerorequest
|
6
|
+
module Validator
|
7
|
+
# validates the change_indicator_url field
|
8
|
+
class ChangeIndicatorURL
|
9
|
+
def self.validate!(data)
|
10
|
+
return [] unless data.key?("change_indicator_url")
|
11
|
+
|
12
|
+
errors = []
|
13
|
+
unless data["change_indicator_url"].is_a?(String)
|
14
|
+
errors.push("change_indicator_url must be a String.")
|
15
|
+
data["change_indicator_url"] = ""
|
16
|
+
end
|
17
|
+
return errors if data["change_indicator_url"] =~ URI::DEFAULT_PARSER.make_regexp
|
18
|
+
|
19
|
+
errors.push("change_indicator_url must be a URL.")
|
20
|
+
errors
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the currency field
|
6
|
+
class Currency
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("currency must be present.") unless data.key?("currency")
|
10
|
+
errors.push("currency must be a String.") unless data["currency"].is_a?(String)
|
11
|
+
errors
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the custom_label field
|
6
|
+
class CustomLabel
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("custom_label must be present.") unless data.key?("custom_label")
|
10
|
+
errors.push("custom_label must be a String.") unless data["custom_label"].is_a?(String)
|
11
|
+
errors
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the days_per_billing_cycle field
|
6
|
+
class DaysPerBillingCycle
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("days_per_billing_cycle must be present.") unless data.key?("days_per_billing_cycle")
|
10
|
+
errors.push("days_per_billing_cycle must be an Integer.") unless data["days_per_billing_cycle"].is_a?(Integer)
|
11
|
+
errors.push("days_per_billing_cycle must be positive.") unless data["days_per_billing_cycle"].to_i.positive?
|
12
|
+
errors
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the number_of_payments field
|
6
|
+
class NumberOfPayments
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("number_of_payments must be present.") unless data.key?("number_of_payments")
|
10
|
+
errors.push("number_of_payments must be an Integer.") unless data["number_of_payments"].is_a?(Integer)
|
11
|
+
errors.push("number_of_payments must be 0 or positive.") if data["number_of_payments"].to_i.negative?
|
12
|
+
errors
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the payment_id field
|
6
|
+
class PaymentID
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("payment_id must be present.") unless data.key?("payment_id")
|
10
|
+
errors.push("payment_id must be a Monero Payment ID.") unless MoneroPaymentID.valid?(data["payment_id"])
|
11
|
+
errors
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the schedule field
|
6
|
+
class Schedule
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("schedule must be present.") unless data.key?("schedule")
|
10
|
+
errors.push("schedule must be a valid Cron.") unless Cron.valid?(data.fetch("schedule", ""))
|
11
|
+
errors
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Monerorequest
|
4
|
+
module Validator
|
5
|
+
# validates the sellers_wallet field
|
6
|
+
class SellersWallet
|
7
|
+
def self.validate!(data)
|
8
|
+
errors = []
|
9
|
+
errors.push("sellers_wallet must be present.") unless data.key?("sellers_wallet")
|
10
|
+
return errors if MoneroAddress.valid?(data["sellers_wallet"])
|
11
|
+
|
12
|
+
errors.push("sellers_wallet must be a main Monero address.")
|
13
|
+
errors
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module Monerorequest
|
6
|
+
module Validator
|
7
|
+
# validates the start_date field
|
8
|
+
class StartDate
|
9
|
+
def self.validate!(data)
|
10
|
+
errors = []
|
11
|
+
errors.push("start_date must be present.") unless data.key?("start_date")
|
12
|
+
errors.push("start_date must be a String.") && data["start_date"] = "" unless data["start_date"].is_a?(String)
|
13
|
+
begin
|
14
|
+
DateTime.rfc3339(data["start_date"])
|
15
|
+
rescue Date::Error
|
16
|
+
errors.push("start_date must be an RFC3339 timestamp.")
|
17
|
+
end
|
18
|
+
errors
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/monerorequest.rb
CHANGED
@@ -1,17 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module Monerorequest
|
4
|
+
SUPPORTED_MR_VERSIONS = [1, 2].freeze
|
5
|
+
|
6
|
+
# error raised when an unsupported Monerorequest version is supplied
|
7
|
+
class RequestVersionError < StandardError
|
8
|
+
def initialize(version)
|
9
|
+
@version = version
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
"Unknown version: #{@version}. Allowed versions: #{Monerorequest::SUPPORTED_MR_VERSIONS}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidRequestError < StandardError; end
|
19
|
+
end
|
20
|
+
|
9
21
|
require_relative "monerorequest/decoder"
|
10
22
|
require_relative "monerorequest/encoder"
|
11
23
|
require_relative "monerorequest/monero_address"
|
12
24
|
require_relative "monerorequest/monero_payment_id"
|
13
25
|
require_relative "monerorequest/version"
|
14
|
-
|
15
|
-
module Monerorequest
|
16
|
-
class RequestVersionError < StandardError; end
|
17
|
-
end
|
26
|
+
require_relative "monerorequest/cron"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/monerorequest/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "monerorequest"
|
7
|
+
spec.version = Monerorequest::VERSION
|
8
|
+
spec.authors = ["David Havlicek"]
|
9
|
+
spec.email = ["snex00@protonmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Ruby Implementation of the Monero Payment Request Standard. https://github.com/lukeprofits/Monero_Payment_Request_Standard"
|
12
|
+
spec.homepage = "https://github.com/snex/monerorequest-ruby"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/snex/monerorequest-ruby/blob/master/CHANGELOG.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Uncomment to register a new dependency of your gem
|
34
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
35
|
+
|
36
|
+
# For more information and examples about making a new gem, check out our
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
38
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
39
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: monerorequest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Havlicek
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -26,12 +26,31 @@ files:
|
|
26
26
|
- README.md
|
27
27
|
- Rakefile
|
28
28
|
- lib/monerorequest.rb
|
29
|
+
- lib/monerorequest/cron.rb
|
29
30
|
- lib/monerorequest/decoder.rb
|
30
31
|
- lib/monerorequest/encoder.rb
|
31
32
|
- lib/monerorequest/monero_address.rb
|
32
33
|
- lib/monerorequest/monero_payment_id.rb
|
34
|
+
- lib/monerorequest/pipeline/base64_decoder.rb
|
35
|
+
- lib/monerorequest/pipeline/base64_encoder.rb
|
36
|
+
- lib/monerorequest/pipeline/gzip_reader.rb
|
37
|
+
- lib/monerorequest/pipeline/gzip_writer.rb
|
38
|
+
- lib/monerorequest/pipeline/json_encoder.rb
|
39
|
+
- lib/monerorequest/pipeline/json_parser.rb
|
40
|
+
- lib/monerorequest/v1.rb
|
41
|
+
- lib/monerorequest/v2.rb
|
42
|
+
- lib/monerorequest/validator/amount.rb
|
43
|
+
- lib/monerorequest/validator/change_indicator_url.rb
|
44
|
+
- lib/monerorequest/validator/currency.rb
|
45
|
+
- lib/monerorequest/validator/custom_label.rb
|
46
|
+
- lib/monerorequest/validator/days_per_billing_cycle.rb
|
47
|
+
- lib/monerorequest/validator/number_of_payments.rb
|
48
|
+
- lib/monerorequest/validator/payment_id.rb
|
49
|
+
- lib/monerorequest/validator/schedule.rb
|
50
|
+
- lib/monerorequest/validator/sellers_wallet.rb
|
51
|
+
- lib/monerorequest/validator/start_date.rb
|
33
52
|
- lib/monerorequest/version.rb
|
34
|
-
-
|
53
|
+
- monerorequest.gemspec
|
35
54
|
homepage: https://github.com/snex/monerorequest-ruby
|
36
55
|
licenses:
|
37
56
|
- MIT
|
data/sig/monerorequest.rbs
DELETED