heartland-retail 5.0.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/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/.yardopts +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +84 -0
- data/LICENSE +21 -0
- data/README.md +272 -0
- data/Rakefile +18 -0
- data/heartland-retail.gemspec +17 -0
- data/lib/heartland-retail.rb +1 -0
- data/lib/heartland/client.rb +299 -0
- data/lib/heartland/client/body.rb +13 -0
- data/lib/heartland/client/collection.rb +121 -0
- data/lib/heartland/client/errors.rb +17 -0
- data/lib/heartland/client/resource.rb +215 -0
- data/lib/heartland/client/response.rb +84 -0
- data/lib/heartland/client/uri.rb +117 -0
- data/spec/heartland/client/body_spec.rb +31 -0
- data/spec/heartland/client/resource_spec.rb +318 -0
- data/spec/heartland/client/response_spec.rb +105 -0
- data/spec/heartland/client/uri_spec.rb +157 -0
- data/spec/heartland/client_spec.rb +361 -0
- data/spec/shared_client_context.rb +5 -0
- data/spec/spec_helper.rb +17 -0
- data/vendor/cache/addressable-2.2.8.gem +0 -0
- data/vendor/cache/codeclimate-test-reporter-1.0.8.gem +0 -0
- data/vendor/cache/coderay-1.1.0.gem +0 -0
- data/vendor/cache/coveralls-0.7.11.gem +0 -0
- data/vendor/cache/crack-0.4.2.gem +0 -0
- data/vendor/cache/diff-lcs-1.2.5.gem +0 -0
- data/vendor/cache/docile-1.1.5.gem +0 -0
- data/vendor/cache/faraday-0.17.3.gem +0 -0
- data/vendor/cache/hashie-4.1.0.gem +0 -0
- data/vendor/cache/json-2.3.0.gem +0 -0
- data/vendor/cache/method_source-0.8.2.gem +0 -0
- data/vendor/cache/mime-types-2.4.3.gem +0 -0
- data/vendor/cache/multi_json-1.11.0.gem +0 -0
- data/vendor/cache/multipart-post-2.1.1.gem +0 -0
- data/vendor/cache/netrc-0.10.3.gem +0 -0
- data/vendor/cache/pry-0.10.1.gem +0 -0
- data/vendor/cache/rake-10.4.2.gem +0 -0
- data/vendor/cache/rest-client-1.7.3.gem +0 -0
- data/vendor/cache/rspec-3.2.0.gem +0 -0
- data/vendor/cache/rspec-core-3.2.2.gem +0 -0
- data/vendor/cache/rspec-expectations-3.2.0.gem +0 -0
- data/vendor/cache/rspec-mocks-3.2.1.gem +0 -0
- data/vendor/cache/rspec-support-3.2.2.gem +0 -0
- data/vendor/cache/safe_yaml-1.0.4.gem +0 -0
- data/vendor/cache/simplecov-0.9.2.gem +0 -0
- data/vendor/cache/simplecov-html-0.9.0.gem +0 -0
- data/vendor/cache/slop-3.6.0.gem +0 -0
- data/vendor/cache/term-ansicolor-1.3.0.gem +0 -0
- data/vendor/cache/thor-0.19.1.gem +0 -0
- data/vendor/cache/tins-1.3.5.gem +0 -0
- data/vendor/cache/webmock-1.17.4.gem +0 -0
- metadata +140 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'heartland-retail'
|
3
|
+
s.version = '5.0.0'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = ['Jay Stotz', 'Derek Stotz']
|
6
|
+
s.summary = 'Heartland Retail API client library'
|
7
|
+
|
8
|
+
s.required_rubygems_version = '>= 1.3.6'
|
9
|
+
|
10
|
+
s.add_runtime_dependency 'faraday', '< 1.0'
|
11
|
+
s.add_runtime_dependency 'json', '>= 1.7.4'
|
12
|
+
s.add_runtime_dependency 'hashie'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
|
16
|
+
s.require_path = 'lib'
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'heartland/client'
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'faraday'
|
3
|
+
require 'json'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
require_relative 'client/errors'
|
7
|
+
|
8
|
+
##
|
9
|
+
# HeartlandRetail namespace
|
10
|
+
module HeartlandRetail
|
11
|
+
##
|
12
|
+
# The main point of interaction for the Heartland Retail Client library.
|
13
|
+
#
|
14
|
+
# Client code must successfully authenticate with the API via the {#auth}
|
15
|
+
# method before calling any HTTP methods or the API will return authorization
|
16
|
+
# errors.
|
17
|
+
#
|
18
|
+
# Provides direct access to the URI-oriented interface via the HTTP methods.
|
19
|
+
# Provides access to the URI-oriented interface via the {#[]} method.
|
20
|
+
class Client
|
21
|
+
##
|
22
|
+
# Default number of records per page when iterating over collection resources
|
23
|
+
DEFAULT_PER_PAGE = 20
|
24
|
+
|
25
|
+
##
|
26
|
+
# Default request timeout in seconds
|
27
|
+
DEFAULT_TIMEOUT = 60
|
28
|
+
|
29
|
+
##
|
30
|
+
# Default connection timeout in seconds
|
31
|
+
DEFAULT_CONNECT_TIMEOUT = 10
|
32
|
+
|
33
|
+
##
|
34
|
+
# @return [URI] The client's base URI
|
35
|
+
attr_reader :base_uri
|
36
|
+
|
37
|
+
##
|
38
|
+
# @return [Faraday::Connection] Faraday's connection
|
39
|
+
attr_reader :connection
|
40
|
+
|
41
|
+
##
|
42
|
+
# @param [String] base_uri Base URI
|
43
|
+
# @option opts [Boolean, String] :debug Pass true to debug to stdout. Pass a String to debug to given filename.
|
44
|
+
# @option opts [Boolean] :insecure Disable SSL certificate verification
|
45
|
+
# @option opts [String] :token Heartland Retail API Token
|
46
|
+
def initialize(base_uri, opts={})
|
47
|
+
@base_uri = URI.parse(base_uri)
|
48
|
+
@opts = opts
|
49
|
+
configure_connection!
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Set to true to enable debugging to STDOUT or a string to write to the file
|
54
|
+
# at that path.
|
55
|
+
#
|
56
|
+
# @param [String, Boolean] debug
|
57
|
+
#
|
58
|
+
# @return [String, Boolean] The debug argument
|
59
|
+
def debug=(debug)
|
60
|
+
@opts[:debug] = debug
|
61
|
+
configure_connection!
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# @deprecated see {#initialize}.
|
66
|
+
# Passes the given credentials to the server, storing the session token on success.
|
67
|
+
#
|
68
|
+
# @raise [AuthFailed] If the credentials were invalid or the server returned an error
|
69
|
+
#
|
70
|
+
# @return [true]
|
71
|
+
#
|
72
|
+
# @option opts [String] :username Heartland Retail username
|
73
|
+
# @option opts [String] :password Heartland Retail password
|
74
|
+
def auth(opts={})
|
75
|
+
warn "[DEPRECATION] `auth` is deprecated. Please use `HeartlandRetail::Client.new '#{base_uri}', token: 'secret_token'` instead."
|
76
|
+
|
77
|
+
unless opts[:username] && opts[:password]
|
78
|
+
raise "Must specify :username and :password"
|
79
|
+
end
|
80
|
+
body = ::URI.encode_www_form \
|
81
|
+
:auth_key => opts[:username],
|
82
|
+
:password => opts[:password]
|
83
|
+
response = post '/auth/identity/callback', body,
|
84
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
85
|
+
|
86
|
+
if response.success?
|
87
|
+
@session_cookie = response.headers['set-cookie']
|
88
|
+
return true
|
89
|
+
else
|
90
|
+
raise AuthFailed, "Heartland Retail auth failed"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Performs a HEAD request against the given URI and returns the {Response}.
|
96
|
+
#
|
97
|
+
# @return [Response]
|
98
|
+
def head(uri, headers=false); make_request(:head, uri, headers); end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Performs a HEAD request against the given URI. Returns the {Response}
|
102
|
+
# on success and raises a {RequestFailed} on failure.
|
103
|
+
#
|
104
|
+
# @raise [RequestFailed] On error response
|
105
|
+
#
|
106
|
+
# @return [Response]
|
107
|
+
def head!(uri, headers=false); raise_on_fail head(uri, headers); end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Performs a GET request against the given URI and returns the {Response}.
|
111
|
+
#
|
112
|
+
# @return [Response]
|
113
|
+
def get(uri, headers=false); make_request(:get, uri, headers); end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Performs a GET request against the given URI. Returns the {Response}
|
117
|
+
# on success and raises a {RequestFailed} on failure.
|
118
|
+
#
|
119
|
+
# @raise [RequestFailed] On error response
|
120
|
+
#
|
121
|
+
# @return [Response]
|
122
|
+
def get!(uri, headers=false); raise_on_fail get(uri, headers); end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Performs a DELETE request against the given URI and returns the {Response}.
|
126
|
+
#
|
127
|
+
# @return [Response]
|
128
|
+
def delete(uri, headers=false); make_request(:delete, uri, headers); end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Performs a DELETE request against the given URI. Returns the {Response}
|
132
|
+
# on success and raises a {RequestFailed} on failure.
|
133
|
+
#
|
134
|
+
# @raise [RequestFailed] On error response
|
135
|
+
#
|
136
|
+
# @return [Response]
|
137
|
+
def delete!(uri, headers=false); raise_on_fail delete(uri, headers); end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Performs a PUT request against the given URI and returns the {Response}.
|
141
|
+
#
|
142
|
+
# @return [Response]
|
143
|
+
def put(uri, body, headers=false); make_request(:put, uri, headers, body); end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Performs a PUT request against the given URI. Returns the {Response}
|
147
|
+
# on success and raises a {RequestFailed} on failure.
|
148
|
+
#
|
149
|
+
# @raise [RequestFailed] On error response
|
150
|
+
#
|
151
|
+
# @return [Response]
|
152
|
+
def put!(uri, body, headers=false); raise_on_fail put(uri, body, headers); end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Performs a POST request against the given URI and returns the {Response}.
|
156
|
+
#
|
157
|
+
# @return [Response]
|
158
|
+
def post(uri, body, headers=false); make_request(:post, uri, headers, body); end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Performs a POST request against the given URI. Returns the {Response}
|
162
|
+
# on success and raises a {RequestFailed} on failure.
|
163
|
+
#
|
164
|
+
# @raise [RequestFailed] On error response
|
165
|
+
#
|
166
|
+
# @return [Response]
|
167
|
+
def post!(uri, body, headers=false); raise_on_fail post(uri, body, headers); end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Returns a Resource for the given URI path.
|
171
|
+
#
|
172
|
+
# @return [Resource]
|
173
|
+
def [](uri)
|
174
|
+
Resource.new(self, uri)
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Iterates over each page of subordinate resources of the given collection
|
179
|
+
# resource URI and yields the {Response} to the block.
|
180
|
+
def each_page(uri)
|
181
|
+
uri = URI.parse(uri)
|
182
|
+
total_pages = nil
|
183
|
+
page = 1
|
184
|
+
uri.query_values = {'per_page' => DEFAULT_PER_PAGE}.merge(uri.query_values || {})
|
185
|
+
while total_pages.nil? or page <= total_pages
|
186
|
+
uri.merge_query_values! 'page' => page
|
187
|
+
response = get!(uri)
|
188
|
+
yield response
|
189
|
+
total_pages ||= response['pages']
|
190
|
+
page += 1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Iterates over each subordinate resource of the given collection resource
|
196
|
+
# URI and yields its representation to the given block.
|
197
|
+
def each(uri)
|
198
|
+
each_page(uri) do |page|
|
199
|
+
page['results'].each do |result|
|
200
|
+
yield result
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Returns a count of subordinate resources of the given collection resource
|
207
|
+
# URI.
|
208
|
+
#
|
209
|
+
# @param [#to_s] uri
|
210
|
+
# @raise [RequestFailed] If the GET fails
|
211
|
+
# @return [Integer] The subordinate resource count
|
212
|
+
def count(uri)
|
213
|
+
uri = URI.parse(uri)
|
214
|
+
uri.merge_query_values! 'page' => 1, 'per_page' => 1
|
215
|
+
get!(uri)['total']
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
attr_reader :opts, :session_cookie
|
221
|
+
|
222
|
+
def prepare_request_body(body)
|
223
|
+
body.is_a?(Hash) ? JSON.dump(body) : body
|
224
|
+
end
|
225
|
+
|
226
|
+
def make_request(method, uri, headers=false, body=false)
|
227
|
+
response = connection.__send__( method, prepare_uri(uri)) do |request|
|
228
|
+
request.headers = headers unless headers === false
|
229
|
+
request.headers['Cookie'] = session_cookie if session_cookie
|
230
|
+
|
231
|
+
request.body = prepare_request_body(body) unless body === false
|
232
|
+
end
|
233
|
+
|
234
|
+
new_response(response)
|
235
|
+
end
|
236
|
+
|
237
|
+
def raise_on_fail(response)
|
238
|
+
if !response.success?
|
239
|
+
error = RequestFailed.new "Request failed with status: #{response.status}"
|
240
|
+
error.response = response
|
241
|
+
raise error
|
242
|
+
end
|
243
|
+
response
|
244
|
+
end
|
245
|
+
|
246
|
+
def prepare_uri(uri)
|
247
|
+
uri = URI.parse(uri)
|
248
|
+
uri.to_s
|
249
|
+
.gsub(/^#{base_uri.to_s}|^#{base_uri.path}/, '')
|
250
|
+
.gsub(/^\//, '')
|
251
|
+
end
|
252
|
+
|
253
|
+
def new_response(faraday_response)
|
254
|
+
Response.new faraday_response, self
|
255
|
+
end
|
256
|
+
|
257
|
+
def configure_connection!
|
258
|
+
@connection = Faraday.new
|
259
|
+
|
260
|
+
connection.url_prefix= base_uri.to_s
|
261
|
+
|
262
|
+
connection.headers['Content-Type'] = 'application/json'
|
263
|
+
connection.headers['Authorization'] = "Bearer #{opts[:token]}" if opts[:token]
|
264
|
+
|
265
|
+
connection.ssl[:verify] = false if opts.has_key?(:insecure)
|
266
|
+
|
267
|
+
connection.options.timeout = DEFAULT_TIMEOUT
|
268
|
+
connection.options.open_timeout = DEFAULT_CONNECT_TIMEOUT
|
269
|
+
|
270
|
+
if debug = opts[:debug]
|
271
|
+
connection.response :logger, debug_logger(debug), bodies: true
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def debug_logger(debug)
|
276
|
+
Logger.new(debug == true ? STDOUT : debug)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
##
|
282
|
+
# Springboard namespace as alias of HeartlandRetail namespace for backwards compatability
|
283
|
+
module Springboard
|
284
|
+
include HeartlandRetail
|
285
|
+
|
286
|
+
##
|
287
|
+
# HeartlandRetail::Client with added deprecation warning for Springboard namespace
|
288
|
+
class Client < HeartlandRetail::Client
|
289
|
+
def initialize(base_uri, opts={})
|
290
|
+
warn "[DEPRECATION] `Springboard::Client.new` is deprecated. Please use `HeartlandRetail::Client.new` instead."
|
291
|
+
super
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
require_relative 'client/resource'
|
297
|
+
require_relative 'client/response'
|
298
|
+
require_relative 'client/body'
|
299
|
+
require_relative 'client/uri'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
module HeartlandRetail
|
4
|
+
class Client
|
5
|
+
##
|
6
|
+
# An indifferent Hash to represent parsed response bodies.
|
7
|
+
#
|
8
|
+
# @see http://rdoc.info/github/intridea/hashie/Hashie/Mash See the Hashie::Mash docs for usage details
|
9
|
+
class Body < ::Hashie::Mash
|
10
|
+
disable_warnings if respond_to?(:disable_warnings)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module HeartlandRetail
|
2
|
+
class Client
|
3
|
+
##
|
4
|
+
# Mixin provides {Resource} with special methods for convenient interaction
|
5
|
+
# with collection resources.
|
6
|
+
module Collection
|
7
|
+
include ::Enumerable
|
8
|
+
|
9
|
+
##
|
10
|
+
# Iterates over all results from the collection and yields each one to
|
11
|
+
# the block, fetching pages as needed.
|
12
|
+
#
|
13
|
+
# @raise [RequestFailed]
|
14
|
+
def each(&block)
|
15
|
+
call_client(:each, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Iterates over each page of results and yields the page to the block,
|
20
|
+
# fetching as needed.
|
21
|
+
#
|
22
|
+
# @raise [RequestFailed]
|
23
|
+
def each_page(&block)
|
24
|
+
call_client(:each_page, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Performs a request and returns the number of resources in the collection.
|
29
|
+
#
|
30
|
+
# @raise [RequestFailed]
|
31
|
+
#
|
32
|
+
# @return [Integer] The subordinate resource count
|
33
|
+
def count
|
34
|
+
call_client(:count)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Returns true if count is greater than zero, else false.
|
39
|
+
#
|
40
|
+
# @see #count
|
41
|
+
#
|
42
|
+
# @raise [RequestFailed]
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
def empty?
|
46
|
+
count <= 0
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Returns a new resource with the given filters added to the query string.
|
51
|
+
#
|
52
|
+
# @see https://github.com/springboard/springboard-retail/blob/master/api/doc/filtering.md Heartland Retail collection API filtering docs
|
53
|
+
#
|
54
|
+
# @param [String, Hash] new_filters Hash or JSON string of new filters
|
55
|
+
#
|
56
|
+
# @return [Resource]
|
57
|
+
def filter(new_filters)
|
58
|
+
new_filters = JSON.parse(new_filters) if new_filters.is_a?(String)
|
59
|
+
if filters = query['_filter']
|
60
|
+
filters = JSON.parse(filters)
|
61
|
+
filters = [filters] unless filters.is_a?(Array)
|
62
|
+
filters.push(new_filters)
|
63
|
+
else
|
64
|
+
filters = new_filters
|
65
|
+
end
|
66
|
+
query('_filter' => filters.to_json)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Returns a new resource with the given sorts added to the query string.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# resource.sort('id,desc', 'name', 'custom@category,desc', :description)
|
74
|
+
#
|
75
|
+
# @param [#to_s] sorts One or more sort strings
|
76
|
+
#
|
77
|
+
# @return [Resource]
|
78
|
+
def sort(*sorts)
|
79
|
+
query('sort' => sorts)
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Returns a new resource with the given fields added to the query string as _only parameters.
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# resource.only('id', :public_id)
|
87
|
+
#
|
88
|
+
# @param [#to_s] returns One or more fields
|
89
|
+
#
|
90
|
+
# @return [Resource]
|
91
|
+
def only(*fields)
|
92
|
+
query('_only' => fields)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Performs a request to get the first result of the first page of the
|
97
|
+
# collection and returns it.
|
98
|
+
#
|
99
|
+
# @raise [RequestFailed]
|
100
|
+
#
|
101
|
+
# @return [Body] The first entry in the response :results array
|
102
|
+
def first
|
103
|
+
response = query(:per_page => 1, :page => 1).get!
|
104
|
+
response[:results].first
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Performs repeated GET requests to the resource and yields results to
|
109
|
+
# the given block as long as the response includes more results.
|
110
|
+
#
|
111
|
+
# @raise [RequestFailed]
|
112
|
+
def while_results(&block)
|
113
|
+
loop do
|
114
|
+
results = get![:results]
|
115
|
+
break if results.nil? || results.empty?
|
116
|
+
results.each(&block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|