btcpay 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/object'
5
+ require 'rest_client'
6
+
7
+ require_relative './result'
8
+ require_relative './api/base'
9
+ require_relative './helpers/base'
10
+
11
+ module BtcPay
12
+ module Client
13
+ class Error < RuntimeError
14
+ def initialize(message)
15
+ super(message)
16
+ end
17
+ end
18
+
19
+ class Base
20
+ API_PATH = '/api/v1'
21
+ DEFAULT_TIMEOUT = 10
22
+
23
+ attr_reader :logger
24
+
25
+ # @param config [BtcPay::Client::Config]
26
+ # @param logger [Logger]
27
+ # @param timeout [Integer] Defaults to DEFAULT_TIMEOUT
28
+ def initialize(config:, logger: Logger.new(STDOUT), timeout: DEFAULT_TIMEOUT, **_kwargs)
29
+ @config = config
30
+ @logger = logger
31
+ @timeout = timeout
32
+ end
33
+
34
+ # GET request
35
+ #
36
+ # @param uri [String]
37
+ # @param options [Hash]
38
+ # @param headers [Hash]
39
+ # @return [Result]
40
+ def get(uri, options: {}, headers: {}, **kwargs)
41
+ request(uri, method: :get, options: options, headers: headers, **kwargs)
42
+ end
43
+
44
+ # POST request
45
+ #
46
+ # @param uri [String]
47
+ # @param payload [Hash]
48
+ # @param options [Hash]
49
+ # @param headers [Hash]
50
+ # @return [Result]
51
+ def post(uri, payload: {}, options: {}, headers: {})
52
+ data = payload.is_a?(Hash) ? payload.to_json : payload
53
+ request(uri, method: :post, payload: data, options: options, headers: headers)
54
+ end
55
+
56
+ # PUT request
57
+ #
58
+ # @param uri [String]
59
+ # @param payload [Hash]
60
+ # @param options [Hash]
61
+ # @param headers [Hash]
62
+ # @return [Result]
63
+ def put(uri, payload:, options: {}, headers: {})
64
+ data = payload.is_a?(Hash) ? payload.to_json : payload
65
+ request(uri, method: :put, payload: data, options: options, headers: headers)
66
+ end
67
+
68
+ # DELETE request
69
+ #
70
+ # @param uri [String]
71
+ # @param options [Hash]
72
+ # @param headers [Hash]
73
+ # @return [Result]
74
+ def delete(uri, options: {}, headers: {})
75
+ request(uri, method: :delete, options: options, headers: headers)
76
+ end
77
+
78
+ def api_keys
79
+ @api_keys ||= Api::ApiKeys.new(client: self)
80
+ end
81
+
82
+ def api_keys_helper
83
+ @api_keys_helper ||= Helpers::ApiKeys.new(client: self)
84
+ end
85
+
86
+ def health
87
+ @health ||= Api::Health.new(client: self)
88
+ end
89
+
90
+ def lightning
91
+ @lightning ||= OpenStruct.new(node: Api::LightningNode.new(client: self))
92
+ end
93
+
94
+ def pull_payments
95
+ @pull_payments ||= Api::PullPayments.new(client: self)
96
+ end
97
+
98
+ alias payments pull_payments
99
+
100
+ def server
101
+ @server ||= Api::Server.new(client: self)
102
+ end
103
+
104
+ def store
105
+ @store ||= Api::Store.new(client: self)
106
+ end
107
+
108
+ def users
109
+ @users ||= Api::Users.new(client: self)
110
+ end
111
+
112
+ private
113
+
114
+ attr_reader :config
115
+
116
+ # @return [Hash]
117
+ def default_headers
118
+ {
119
+ 'Content-Type' => 'application/json',
120
+ 'Accept' => 'application/json',
121
+ 'User-Agent' => config.user_agent,
122
+ 'Accept-Encoding' => 'deflate, gzip',
123
+ 'Authorization' => config.authorization
124
+ }
125
+ end
126
+
127
+ # @yield [Result]
128
+ def request(uri, **kwargs)
129
+ params = request_builder(uri, **kwargs)
130
+ return params if kwargs[:skip_request]
131
+
132
+ response = RestClient::Request.execute(params)
133
+ logger.debug(message: 'GET Request', url: params[:url], options: kwargs[:options], status: response.code)
134
+ result = success?(response.code) ? Result.success(response) : Result.failed(response)
135
+
136
+ logger.warn(error: 'Request Error', code: result.code, body: result.raw) if result.failure?
137
+ result
138
+ rescue ::RestClient::GatewayTimeout
139
+ raise Error.new('Gateway timeout')
140
+ rescue ::RestClient::RequestTimeout
141
+ raise Error.new('Request timeout')
142
+ rescue ::RestClient::Exception => e
143
+ handle_error(e)
144
+ end
145
+
146
+ # @param uri
147
+ # @option
148
+ def request_builder(uri, **kwargs)
149
+ options = kwargs[:options] ||= {}
150
+ headers = kwargs[:headers] ||= {}
151
+
152
+ url = URI(config.base_url)
153
+ url.path = kwargs[:skip_api_path] ? uri : API_PATH + uri
154
+ url.query = CGI.unescape(options.to_query).presence
155
+
156
+ return url.to_s if kwargs[:skip_request]
157
+
158
+ {
159
+ method: kwargs[:method],
160
+ url: url.to_s,
161
+ payload: kwargs[:payload].presence,
162
+ headers: default_headers.merge(headers),
163
+ open_timeout: open_timeout,
164
+ timeout: timeout
165
+ }.compact
166
+ end
167
+
168
+ # Handle errors
169
+ # @param error [Error]
170
+ # @return [Result]
171
+ def handle_error(error)
172
+ logger.error(error: 'Request Exception', code: error.response.code, message: error.message)
173
+
174
+ Result.failed(error.response)
175
+ end
176
+
177
+ # @return [Integer]
178
+ def open_timeout
179
+ @open_timeout || DEFAULT_TIMEOUT
180
+ end
181
+
182
+ # @return [Integer]
183
+ def timeout
184
+ @timeout || DEFAULT_TIMEOUT
185
+ end
186
+
187
+ def success?(response_code)
188
+ response_code.in?(200..299)
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtcPay
4
+ module Client
5
+ class Config
6
+ AUTH_TOKEN_TYPE = 'token'
7
+ BASIC_TOKEN_TYPE = 'Basic'
8
+
9
+ attr_reader :authorization, :auth_token, :basic_auth_token, :base_url, :user_agent
10
+
11
+ def initialize(**kwargs)
12
+ @base_url = load_url(kwargs[:base_url])
13
+ @user_agent = kwargs[:user_agent] || "btcpay_ruby/#{BtcPay::VERSION}"
14
+
15
+ load_auth_token(kwargs)
16
+ set_authorization
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ auth_token: auth_token,
22
+ basic_auth_token: basic_auth_token,
23
+ base_url: base_url,
24
+ user_agent: user_agent
25
+ }.compact
26
+ end
27
+
28
+ private
29
+
30
+ def load_url(url)
31
+ base_url = url || ENV['BTCPAY_BASE_URL'].to_s
32
+ uri = URI(base_url)
33
+ return uri.to_s if uri.scheme && uri.host
34
+
35
+ raise ArgumentError.new('invalid base_url')
36
+ end
37
+
38
+ def load_auth_token(kwargs)
39
+ @auth_token = kwargs[:auth_token] || ENV['BTCPAY_AUTH_TOKEN']
40
+ @basic_auth_token = kwargs[:basic_auth_token] || ENV['BTCPAY_BASIC_AUTH_TOKEN']
41
+
42
+ raise ArgumentError.new('auth_token or basic_auth_token required') unless @auth_token || @basic_auth_token
43
+ end
44
+
45
+ def set_authorization
46
+ @authorization = if @auth_token
47
+ "#{AUTH_TOKEN_TYPE} #{@auth_token}"
48
+ elsif @basic_auth_token
49
+ "#{BASIC_TOKEN_TYPE} #{@basic_auth_token}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtcPay
4
+ module Client
5
+ module Helpers
6
+ class ApiKeys < Helpers::Base
7
+ def authorize
8
+ Authorize.new(client: client)
9
+ end
10
+
11
+ def set_base_path; end
12
+
13
+ class Authorize
14
+ PATH = '/api-keys/authorize'
15
+
16
+ def initialize(client:)
17
+ @client = client
18
+ @logger = @client.logger
19
+ end
20
+
21
+ def html(**opts)
22
+ get(**opts)
23
+ end
24
+
25
+ def link(**opts)
26
+ get(skip_request: true, **opts)
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :client, :logger
32
+
33
+ def get(permissions: [], application_name:, strict: true, selective_stores: false, **opts)
34
+ opts.merge!(
35
+ {
36
+ permissions: Array(permissions),
37
+ applicationName: application_name,
38
+ strict: strict,
39
+ selectiveStores: selective_stores
40
+ }
41
+ )
42
+
43
+ skip_request = opts.delete(:skip_request)
44
+ client.get(PATH, options: opts, skip_api_path: true, skip_request: skip_request)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../service'
4
+
5
+ module BtcPay
6
+ module Client
7
+ module Helpers
8
+ class Base < Client::Service
9
+ require_relative './api_keys'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'multi_json'
5
+
6
+ module BtcPay
7
+ module Client
8
+ ##
9
+ # Status object to capture result from an HTTP request
10
+ #
11
+ # Gives callers context of the result and allows them to
12
+ # implement successful strategies to handle success/failure
13
+ class Result
14
+ def self.success(response)
15
+ new(:success, response)
16
+ end
17
+
18
+ def self.failed(response)
19
+ new(:failed, response)
20
+ end
21
+
22
+ attr_reader :body, :code, :headers, :raw, :status
23
+
24
+ def initialize(status, response)
25
+ @code = response.code
26
+ @headers = response.headers # e.g. "Content-Type" will become :content_type.
27
+ @status = status
28
+
29
+ @raw = raw_parse(response.body)
30
+ @body = rubify_body
31
+ end
32
+
33
+ def success?
34
+ status == :success
35
+ end
36
+
37
+ def failure?
38
+ !success?
39
+ end
40
+
41
+ def to_h
42
+ {
43
+ status: status,
44
+ headers: headers,
45
+ code: code,
46
+ body: body
47
+ }
48
+ end
49
+
50
+ alias to_hash to_h
51
+
52
+ private
53
+
54
+ def method_missing(method, *args, &blk)
55
+ to_h.send(method, *args, &blk) || super
56
+ end
57
+
58
+ def respond_to_missing?(method, include_private = false)
59
+ to_h.respond_to?(method) || super
60
+ end
61
+
62
+ # @param body [JSON] Raw JSON body
63
+ def raw_parse(response)
64
+ return if response.blank?
65
+
66
+ body = MultiJson.load(response)
67
+ return body.with_indifferent_access if body.respond_to?(:with_indifferent_access)
68
+ raise NotImplemented.new('Unknown response type') unless body.is_a?(Array)
69
+
70
+ key = success? ? :data : :errors
71
+ {
72
+ key => body.map(&:with_indifferent_access)
73
+ }
74
+ rescue MultiJson::ParseError
75
+ response
76
+ rescue StandardError => e
77
+ raise ResponseBodyParseError.new(error: 'JSON parse error', message: e.message, body: response)
78
+ end
79
+
80
+ def rubify_body
81
+ return if raw.blank?
82
+ return unless raw.respond_to?(:deep_transform_keys)
83
+
84
+ raw.deep_transform_keys { |key| key.to_s.underscore }.with_indifferent_access
85
+ end
86
+
87
+ class ResponseBodyParseError < StandardError; end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtcPay
4
+ module Client
5
+ class Service
6
+ def initialize(client:)
7
+ @base_path = set_base_path
8
+ @client = client
9
+ @logger = @client.logger
10
+ end
11
+
12
+ protected
13
+
14
+ def path(*args)
15
+ request_path = args.prepend(base_path.presence).compact.join('/')
16
+ request_path[0].eql?('/') ? request_path : '/' + request_path
17
+ end
18
+
19
+ def store_path(store_id, *args)
20
+ base_path.gsub!(':store_id', store_id)
21
+ path(*args)
22
+ end
23
+
24
+ def set_base_path
25
+ raise NotImplementedError.new
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :client, :logger, :base_path
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BtcPay
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: btcpay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - snogrammer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rest-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: factory_bot
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">"
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">"
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">"
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: vcr
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '6.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '6.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.8'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.8'
153
+ description:
154
+ email:
155
+ - btcpay-gem@snogram.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".rspec"
162
+ - ".rubocop.yml"
163
+ - ".travis.yml"
164
+ - Gemfile
165
+ - Gemfile.lock
166
+ - LICENSE.txt
167
+ - README.md
168
+ - Rakefile
169
+ - bin/console
170
+ - bin/setup
171
+ - btcpay.gemspec
172
+ - docker-compose.btcpay.yml
173
+ - lib/btcpay.rb
174
+ - lib/btcpay/client/api/api_keys.rb
175
+ - lib/btcpay/client/api/base.rb
176
+ - lib/btcpay/client/api/health.rb
177
+ - lib/btcpay/client/api/lightning_node.rb
178
+ - lib/btcpay/client/api/pull_payments.rb
179
+ - lib/btcpay/client/api/server.rb
180
+ - lib/btcpay/client/api/store.rb
181
+ - lib/btcpay/client/api/store_payment_requests.rb
182
+ - lib/btcpay/client/api/store_payouts.rb
183
+ - lib/btcpay/client/api/store_pull_payments.rb
184
+ - lib/btcpay/client/api/users.rb
185
+ - lib/btcpay/client/base.rb
186
+ - lib/btcpay/client/config.rb
187
+ - lib/btcpay/client/helpers/api_keys.rb
188
+ - lib/btcpay/client/helpers/base.rb
189
+ - lib/btcpay/client/result.rb
190
+ - lib/btcpay/client/service.rb
191
+ - lib/btcpay/version.rb
192
+ homepage: https://docs.btcpayserver.org/API/Greenfield/v1/
193
+ licenses:
194
+ - MIT
195
+ metadata:
196
+ homepage_uri: https://docs.btcpayserver.org/API/Greenfield/v1/
197
+ source_code_uri: https://gitlab.com/snogrammer/btpay
198
+ changelog_uri: https://gitlab.com/snogrammer/btpay/CHANGELOG.md
199
+ post_install_message:
200
+ rdoc_options: []
201
+ require_paths:
202
+ - lib
203
+ required_ruby_version: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: 2.3.0
208
+ required_rubygems_version: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ version: '0'
213
+ requirements: []
214
+ rubygems_version: 3.1.2
215
+ signing_key:
216
+ specification_version: 4
217
+ summary: BTCPay Server v1 API wrapper. Be your own Bitpay!
218
+ test_files: []