coinbase_commerce_client 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.
- checksums.yaml +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/coinbase_commerce_client.iml +85 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +99 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +267 -0
- data/Rakefile +8 -0
- data/assets/banner.png +0 -0
- data/badge.svg +1 -0
- data/lib/coinbase_commerce_client/api_errors.rb +150 -0
- data/lib/coinbase_commerce_client/api_resources/base/api_object.rb +203 -0
- data/lib/coinbase_commerce_client/api_resources/base/api_resource.rb +23 -0
- data/lib/coinbase_commerce_client/api_resources/base/create.rb +12 -0
- data/lib/coinbase_commerce_client/api_resources/base/delete.rb +13 -0
- data/lib/coinbase_commerce_client/api_resources/base/list.rb +23 -0
- data/lib/coinbase_commerce_client/api_resources/base/save.rb +15 -0
- data/lib/coinbase_commerce_client/api_resources/base/update.rb +12 -0
- data/lib/coinbase_commerce_client/api_resources/charge.rb +11 -0
- data/lib/coinbase_commerce_client/api_resources/checkout.rb +15 -0
- data/lib/coinbase_commerce_client/api_resources/event.rb +10 -0
- data/lib/coinbase_commerce_client/api_response.rb +36 -0
- data/lib/coinbase_commerce_client/client.rb +115 -0
- data/lib/coinbase_commerce_client/util.rb +57 -0
- data/lib/coinbase_commerce_client/version.rb +5 -0
- data/lib/coinbase_commerce_client/webhooks.rb +48 -0
- data/lib/coinbase_commerce_client.rb +45 -0
- data/sig/coinbase_commerce_client.rbs +4 -0
- metadata +150 -0
data/Rakefile
ADDED
data/assets/banner.png
ADDED
|
Binary file
|
data/badge.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="114" height="20" role="img" aria-label="coverage: 88.92%"><title>coverage: 88.92%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#97ca00"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">88.92%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">88.92%</text></g></svg>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module Errors
|
|
3
|
+
class APIError < StandardError
|
|
4
|
+
attr_reader :message, :http_body, :http_headers, :http_status, :json_body, :request_id
|
|
5
|
+
|
|
6
|
+
# Response contains a CoinbaseCommerceResponse object
|
|
7
|
+
attr_accessor :response
|
|
8
|
+
|
|
9
|
+
# Initializes a API error.
|
|
10
|
+
def initialize(message = nil, http_status = nil, http_body = nil, json_body = nil, http_headers = nil)
|
|
11
|
+
@message = message
|
|
12
|
+
@http_status = http_status
|
|
13
|
+
@http_body = http_body
|
|
14
|
+
@http_headers = http_headers || {}
|
|
15
|
+
@json_body = json_body
|
|
16
|
+
@request_id = @http_headers['x-request-id']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
status_string = @http_status.nil? ? '' : "(Status #{@http_status}) "
|
|
21
|
+
id_string = @request_id.nil? ? '' : "(Request #{@request_id}) "
|
|
22
|
+
"#{status_string}#{id_string}#{@message}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# in case error connecting to coinbase commerce server
|
|
27
|
+
class APIConnectionError < APIError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Status 400
|
|
31
|
+
class BadRequestError < APIError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class ParamRequiredError < APIError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class InvalidRequestError < APIError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Status 401
|
|
41
|
+
class AuthenticationError < APIError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Status 404
|
|
45
|
+
class ResourceNotFoundError < APIError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Status 422
|
|
49
|
+
class ValidationError < APIError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Status 429
|
|
53
|
+
class RateLimitExceededError < APIError
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Status 500
|
|
57
|
+
class InternalServerError < APIError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Status 503
|
|
61
|
+
class ServiceUnavailableError < APIError
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Webhook errors
|
|
65
|
+
class WebhookError < APIError
|
|
66
|
+
attr_accessor :sig_header
|
|
67
|
+
|
|
68
|
+
def initialize(message, sig_header, http_body: nil)
|
|
69
|
+
super(message, http_body: http_body)
|
|
70
|
+
@sig_header = sig_header
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class SignatureVerificationError < WebhookError
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class WebhookInvalidPayload < WebhookError
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Errors handling
|
|
81
|
+
def self.handle_error_response(http_resp)
|
|
82
|
+
begin
|
|
83
|
+
resp = CoinbaseCommerceClientResponse.from_faraday_hash(http_resp)
|
|
84
|
+
error_data = resp.data[:error]
|
|
85
|
+
|
|
86
|
+
raise APIError, 'Unknown error' unless error_data
|
|
87
|
+
rescue JSON::ParserError, APIError
|
|
88
|
+
raise general_api_error(http_resp[:status], http_resp[:body])
|
|
89
|
+
end
|
|
90
|
+
error = specific_api_error(resp, error_data)
|
|
91
|
+
error.response = resp
|
|
92
|
+
raise(error)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.general_api_error(status, body)
|
|
96
|
+
APIError.new("Invalid response object from API: #{body.inspect} " \
|
|
97
|
+
"(HTTP response code: #{status} http_body: #{body}")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.specific_api_error(resp, error_data)
|
|
101
|
+
opts = {
|
|
102
|
+
http_body: resp.http_body,
|
|
103
|
+
http_headers: resp.http_headers,
|
|
104
|
+
http_status: resp.http_status,
|
|
105
|
+
json_body: resp.data
|
|
106
|
+
}
|
|
107
|
+
case resp.http_status
|
|
108
|
+
when 400
|
|
109
|
+
# in case of known error code
|
|
110
|
+
case error_data[:type]
|
|
111
|
+
when 'param_required'
|
|
112
|
+
ParamRequiredError.new(error_data[:message], opts)
|
|
113
|
+
when 'validation_error'
|
|
114
|
+
ValidationError.new(error_data[:message], opts)
|
|
115
|
+
when 'invalid_request'
|
|
116
|
+
InvalidRequestError.new(error_data[:message], opts)
|
|
117
|
+
else
|
|
118
|
+
InvalidRequestError.new(error_data[:message], opts)
|
|
119
|
+
end
|
|
120
|
+
when 401
|
|
121
|
+
AuthenticationError.new(error_data[:message], opts)
|
|
122
|
+
when 404
|
|
123
|
+
ResourceNotFoundError.new(error_data[:message], opts)
|
|
124
|
+
when 429
|
|
125
|
+
RateLimitExceededError.new(error_data[:message], opts)
|
|
126
|
+
when 500
|
|
127
|
+
InternalServerError.new(error_data[:message], opts)
|
|
128
|
+
when 503
|
|
129
|
+
ServiceUnavailableError.new(error_data[:message], opts)
|
|
130
|
+
else
|
|
131
|
+
APIError.new(error_data[:message], opts)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.handle_network_error(e, api_base = nil)
|
|
136
|
+
api_base ||= @api_uri
|
|
137
|
+
message = case e
|
|
138
|
+
when Faraday::ConnectionFailed
|
|
139
|
+
'Unexpected error communicating when trying to connect to Coinbase Commerce.'
|
|
140
|
+
when Faraday::SSLError
|
|
141
|
+
'Could not establish a secure connection to Coinbase Commerce.'
|
|
142
|
+
when Faraday::TimeoutError
|
|
143
|
+
"Could not connect to Coinbase Commerce (#{api_base})."
|
|
144
|
+
else
|
|
145
|
+
'Unexpected error communicating with Coinbase Commerce.'
|
|
146
|
+
end
|
|
147
|
+
raise APIConnectionError, message + "\n\n(Network error: #{e.message})"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
module Base
|
|
4
|
+
class APIObject
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
def initialize(id = nil, client = nil)
|
|
8
|
+
@data = {}
|
|
9
|
+
@data[:id] = id if id
|
|
10
|
+
@client = client
|
|
11
|
+
@unsaved_values = Set.new
|
|
12
|
+
@transient_values = Set.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Base object options section
|
|
16
|
+
def [](k)
|
|
17
|
+
@data[k.to_sym]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def []=(k, v)
|
|
21
|
+
send(:"#{k}=", v)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def keys
|
|
25
|
+
@data.keys
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def values
|
|
29
|
+
@data.values
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def each(&blk)
|
|
33
|
+
@data.each(&blk)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s(*_args)
|
|
37
|
+
JSON.pretty_generate(to_hash)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_hash
|
|
41
|
+
@data.each_with_object({}) do |(key, value), output|
|
|
42
|
+
case value
|
|
43
|
+
when Array
|
|
44
|
+
output[key] = value.map {|v| v.respond_to?(:to_hash) ? v.to_hash : v}
|
|
45
|
+
else
|
|
46
|
+
output[key] = value.respond_to?(:to_hash) ? value.to_hash : value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_json(*_a)
|
|
52
|
+
JSON.generate(@data)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def inspect
|
|
56
|
+
item_id = respond_to?(:id) && !id.nil? ? "id=#{id}" : "No ID"
|
|
57
|
+
"#{self.class}: #{item_id}> Serialized: " + JSON.pretty_generate(@data)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def respond_to_missing?(symbol, include_private = false)
|
|
61
|
+
@data && @data.key?(symbol) || super
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def method_missing(name, *args)
|
|
65
|
+
if name.to_s.end_with?("=")
|
|
66
|
+
|
|
67
|
+
attr = name.to_s[0...-1].to_sym
|
|
68
|
+
val = args.first
|
|
69
|
+
add_accessors([attr], attr => val)
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
mth = method(name)
|
|
73
|
+
rescue NameError
|
|
74
|
+
raise NoMethodError, "Cannot set #{attr} on this object."
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return mth.call(args[0])
|
|
78
|
+
|
|
79
|
+
elsif @data.key?(name)
|
|
80
|
+
return @data[name]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
super
|
|
85
|
+
rescue NoMethodError => e
|
|
86
|
+
raise unless @transient_values.include?(name)
|
|
87
|
+
raise NoMethodError, e.message + " Available attributes: #{@data.keys.join(', ')}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Object serialize section
|
|
92
|
+
def serialize_params(options = {})
|
|
93
|
+
update_hash = {}
|
|
94
|
+
|
|
95
|
+
@data.each do |k, v|
|
|
96
|
+
if options[:push] || @unsaved_values.include?(k) || v.is_a?(APIObject)
|
|
97
|
+
push = options[:push] || @unsaved_values.include?(k)
|
|
98
|
+
update_hash[k.to_sym] = serialize_params_value(@data[k], push)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
update_hash.reject! {|_, v| v.nil? || v.empty?}
|
|
103
|
+
update_hash
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def serialize_params_value(value, push)
|
|
107
|
+
if value.nil?
|
|
108
|
+
""
|
|
109
|
+
elsif value.is_a?(Array)
|
|
110
|
+
value.map {|v| serialize_params_value(v, push)}
|
|
111
|
+
|
|
112
|
+
elsif value.is_a?(Hash)
|
|
113
|
+
Util.convert_to_api_object(value, @opts).serialize_params
|
|
114
|
+
|
|
115
|
+
elsif value.is_a?(APIObject)
|
|
116
|
+
value.serialize_params(push: push)
|
|
117
|
+
else
|
|
118
|
+
value
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Object initialize/update section
|
|
123
|
+
def self.create_from(values, client = nil)
|
|
124
|
+
values = Util.symbolize_names(values)
|
|
125
|
+
new(values[:id], client).send(:initialize_from, values)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def initialize_from(values, partial = false)
|
|
129
|
+
removed = partial ? Set.new : Set.new(@data.keys - values.keys)
|
|
130
|
+
added = Set.new(values.keys - @data.keys)
|
|
131
|
+
|
|
132
|
+
remove_accessors(removed)
|
|
133
|
+
add_accessors(added, values)
|
|
134
|
+
|
|
135
|
+
removed.each do |k|
|
|
136
|
+
@data.delete(k)
|
|
137
|
+
@transient_values.add(k)
|
|
138
|
+
@unsaved_values.delete(k)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
update_attributes(values)
|
|
142
|
+
values.each_key do |k|
|
|
143
|
+
@transient_values.delete(k)
|
|
144
|
+
@unsaved_values.delete(k)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
self
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def update_attributes(values)
|
|
151
|
+
values.each do |k, v|
|
|
152
|
+
add_accessors([k], values) unless metaclass.method_defined?(k.to_sym)
|
|
153
|
+
@data[k] = Util.convert_to_api_object(v, @client)
|
|
154
|
+
@unsaved_values.add(k)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
protected
|
|
160
|
+
|
|
161
|
+
def metaclass
|
|
162
|
+
class << self
|
|
163
|
+
self
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def remove_accessors(keys)
|
|
168
|
+
metaclass.instance_eval do
|
|
169
|
+
keys.each do |k|
|
|
170
|
+
# Remove methods for the accessor's reader and writer.
|
|
171
|
+
[k, :"#{k}=", :"#{k}?"].each do |method_name|
|
|
172
|
+
remove_method(method_name) if method_defined?(method_name)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def add_accessors(keys, values)
|
|
179
|
+
metaclass.instance_eval do
|
|
180
|
+
keys.each do |k|
|
|
181
|
+
if k == :method
|
|
182
|
+
define_method(k) {|*args| args.empty? ? @data[k] : super(*args)}
|
|
183
|
+
else
|
|
184
|
+
define_method(k) {@data[k]}
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
define_method(:"#{k}=") do |v|
|
|
188
|
+
if v != ""
|
|
189
|
+
@data[k] = Util.convert_to_api_object(v, @opts)
|
|
190
|
+
@unsaved_values.add(k)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if [FalseClass, TrueClass].include?(values[k].class)
|
|
195
|
+
define_method(:"#{k}?") {@data[k]}
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
module Base
|
|
4
|
+
class APIResource < APIObject
|
|
5
|
+
class << self
|
|
6
|
+
attr_accessor :client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
@client = nil
|
|
10
|
+
|
|
11
|
+
def self.retrieve(id, params = {})
|
|
12
|
+
resp = @client.request(:get, "#{self::RESOURCE_PATH}/#{id}", params)
|
|
13
|
+
Util.convert_to_api_object(resp.data, @client, self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def refresh(params = {})
|
|
17
|
+
resp = @client.request(:get, "#{self.class::RESOURCE_PATH}/#{self[:id]}", params)
|
|
18
|
+
initialize_from(resp.data)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
module Base
|
|
4
|
+
module Create
|
|
5
|
+
def create(params = {})
|
|
6
|
+
response = @client.request(:post, "#{self::RESOURCE_PATH}", params)
|
|
7
|
+
Util.convert_to_api_object(response.data, @client, self)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
module Base
|
|
4
|
+
module List
|
|
5
|
+
def list(params = {})
|
|
6
|
+
resp = @client.request(:get, self::RESOURCE_PATH.to_s, params)
|
|
7
|
+
Util.convert_to_api_object(resp.data, @client, self)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def auto_paging(params = {}, &blk)
|
|
11
|
+
loop do
|
|
12
|
+
page = list(params)
|
|
13
|
+
last_id = page.data.empty? ? nil : page.data.last.id
|
|
14
|
+
break if last_id.nil?
|
|
15
|
+
|
|
16
|
+
params[:starting_after] = last_id
|
|
17
|
+
page.data.each(&blk)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
module Base
|
|
4
|
+
module Save
|
|
5
|
+
def save
|
|
6
|
+
values = serialize_params(self)
|
|
7
|
+
values.delete(:id)
|
|
8
|
+
resp = @client.request(:put, "#{self.class::RESOURCE_PATH}/#{self[:id]}", self)
|
|
9
|
+
initialize_from(resp.data)
|
|
10
|
+
self
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
module Base
|
|
4
|
+
module Update
|
|
5
|
+
def modify(id, params={})
|
|
6
|
+
resp = @client.request(:put, "#{self::RESOURCE_PATH}/#{id}", params)
|
|
7
|
+
Util.convert_to_api_object(resp.data, @client, self)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
module APIResources
|
|
3
|
+
class Checkout < Base::APIResource
|
|
4
|
+
extend Base::List
|
|
5
|
+
extend Base::Create
|
|
6
|
+
extend Base::Update
|
|
7
|
+
|
|
8
|
+
include Base::Save
|
|
9
|
+
include Base::Delete
|
|
10
|
+
|
|
11
|
+
OBJECT_NAME = 'checkout'.freeze
|
|
12
|
+
RESOURCE_PATH = 'checkouts'.freeze
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
class CoinbaseCommerceClientResponse
|
|
3
|
+
attr_accessor :data, :http_body, :http_headers, :http_status, :request_id
|
|
4
|
+
|
|
5
|
+
# Initializes a CoinbaseCommerceResponse object
|
|
6
|
+
# from a Hash like the kind returned as part of a Faraday exception.
|
|
7
|
+
def self.from_faraday_hash(http_resp)
|
|
8
|
+
resp = CoinbaseCommerceClientResponse.new
|
|
9
|
+
resp.data = JSON.parse(http_resp[:body], symbolize_names: true)
|
|
10
|
+
resp.http_body = http_resp[:body]
|
|
11
|
+
resp.http_headers = http_resp[:headers]
|
|
12
|
+
resp.http_status = http_resp[:status]
|
|
13
|
+
resp.request_id = http_resp[:headers]['x-request-id']
|
|
14
|
+
resp
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Initializes a CoinbaseCommerceResponse object
|
|
18
|
+
# from a Faraday HTTP response object.
|
|
19
|
+
def self.from_faraday_response(http_resp)
|
|
20
|
+
resp = CoinbaseCommerceClientResponse.new
|
|
21
|
+
resp.data = JSON.parse(http_resp.body, symbolize_names: true)
|
|
22
|
+
resp.http_body = http_resp.body
|
|
23
|
+
resp.http_headers = http_resp.headers
|
|
24
|
+
resp.http_status = http_resp.status
|
|
25
|
+
resp.request_id = http_resp.headers['x-request-id']
|
|
26
|
+
|
|
27
|
+
# unpack nested data field if it exist
|
|
28
|
+
resp.data.update(resp.data.delete(:data)) if resp.data.is_a?(Hash) && resp.data.fetch(:data, nil).is_a?(Hash)
|
|
29
|
+
|
|
30
|
+
# warn in there warnings in response
|
|
31
|
+
warn(resp.data[:warnings].first.to_s) if resp.data.is_a?(Hash) && resp.data.fetch(:warnings, nil).is_a?(Array)
|
|
32
|
+
|
|
33
|
+
resp
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module CoinbaseCommerceClient
|
|
2
|
+
BASE_API_URL = 'https://api.commerce.coinbase.com/'.freeze
|
|
3
|
+
API_VERSION = '2018-03-22'.freeze
|
|
4
|
+
|
|
5
|
+
class Client
|
|
6
|
+
def initialize(options = {})
|
|
7
|
+
check_api_key!(options[:api_key])
|
|
8
|
+
@api_key = options[:api_key]
|
|
9
|
+
@api_uri = URI.parse(options[:api_url] || BASE_API_URL)
|
|
10
|
+
@api_ver = options[:api_ver] || API_VERSION
|
|
11
|
+
|
|
12
|
+
@conn = Faraday.new do |c|
|
|
13
|
+
c.use Faraday::Request::UrlEncoded
|
|
14
|
+
c.use Faraday::Response::RaiseError
|
|
15
|
+
c.adapter Faraday.default_adapter
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def charge
|
|
20
|
+
APIResources::Charge.client = self
|
|
21
|
+
APIResources::Charge
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def checkout
|
|
25
|
+
APIResources::Checkout.client = self
|
|
26
|
+
APIResources::Checkout
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def event
|
|
30
|
+
APIResources::Event.client = self
|
|
31
|
+
APIResources::Event
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def api_url(url = "", api_base = nil)
|
|
35
|
+
(api_base || CoinbaseCommerceClient::BASE_API_URL) + url
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def request_headers(api_key)
|
|
39
|
+
{
|
|
40
|
+
"User-Agent" => "CoinbaseCommerce/#{CoinbaseCommerceClient::VERSION}",
|
|
41
|
+
"Accept" => "application/json",
|
|
42
|
+
"X-CC-Api-Key" => api_key,
|
|
43
|
+
"X-CC-Version" => @api_ver,
|
|
44
|
+
"Content-Type" => "application/json",
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_api_key!(api_key)
|
|
49
|
+
raise AuthenticationError, 'No API key provided' unless api_key
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def request(method, path, params = {})
|
|
53
|
+
@last_response = nil
|
|
54
|
+
url = api_url(path, @api_uri)
|
|
55
|
+
headers = request_headers(@api_key)
|
|
56
|
+
|
|
57
|
+
body = nil
|
|
58
|
+
query_params = nil
|
|
59
|
+
|
|
60
|
+
case method.to_s.downcase.to_sym
|
|
61
|
+
when :get, :head, :delete
|
|
62
|
+
query_params = params
|
|
63
|
+
else
|
|
64
|
+
body = params.to_json
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
u = URI.parse(path)
|
|
68
|
+
unless u.query.nil?
|
|
69
|
+
query_params ||= {}
|
|
70
|
+
query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
http_resp = execute_request_with_rescues(@api_uri) do
|
|
75
|
+
@conn.run_request(method, url, body, headers) do |req|
|
|
76
|
+
req.params = query_params unless query_params.nil?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
resp = CoinbaseCommerceClientResponse.from_faraday_response(http_resp)
|
|
82
|
+
rescue JSON::ParserError
|
|
83
|
+
raise Errors.general_api_error(http_resp.status, http_resp.body)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@last_response = resp
|
|
87
|
+
resp
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def execute_request_with_rescues(api_base)
|
|
91
|
+
begin
|
|
92
|
+
resp = yield
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
case e
|
|
95
|
+
when Faraday::ClientError
|
|
96
|
+
if e.response
|
|
97
|
+
Errors.handle_error_response(e.response)
|
|
98
|
+
else
|
|
99
|
+
Errors.handle_network_error(e, api_base)
|
|
100
|
+
end
|
|
101
|
+
when Faraday::ServerError
|
|
102
|
+
if e.response
|
|
103
|
+
Errors.handle_error_response(e.response)
|
|
104
|
+
else
|
|
105
|
+
Errors.handle_network_error(e, api_base)
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
raise
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
resp
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
end
|
|
115
|
+
end
|