btcpay 0.1.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.
@@ -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: []