shippo 1.0.4 → 2.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 +4 -4
- data/.atom-build.json +22 -0
- data/.codeclimate.yml +30 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +50 -0
- data/Gemfile +5 -0
- data/Guardfile +39 -0
- data/README.md +288 -0
- data/Rakefile +24 -0
- data/bin/example +114 -0
- data/lib/shippo.rb +23 -97
- data/lib/shippo/api.rb +52 -0
- data/lib/shippo/api/api_object.rb +133 -0
- data/lib/shippo/api/category.rb +49 -0
- data/lib/shippo/api/category/base.rb +144 -0
- data/lib/shippo/api/category/purpose.rb +13 -0
- data/lib/shippo/api/category/source.rb +16 -0
- data/lib/shippo/api/category/state.rb +13 -0
- data/lib/shippo/api/category/status.rb +17 -0
- data/lib/shippo/api/extend/operation.rb +21 -0
- data/lib/shippo/api/extend/transformers.rb +12 -0
- data/lib/shippo/api/extend/url.rb +26 -0
- data/lib/shippo/api/operations.rb +8 -0
- data/lib/shippo/api/operations/create.rb +33 -0
- data/lib/shippo/api/operations/list.rb +22 -0
- data/lib/shippo/api/operations/rates.rb +16 -0
- data/lib/shippo/api/operations/update.rb +12 -0
- data/lib/shippo/api/operations/validate.rb +12 -0
- data/lib/shippo/api/request.rb +159 -0
- data/lib/shippo/api/resource.rb +104 -0
- data/lib/shippo/api/transformers/list.rb +73 -0
- data/lib/shippo/api/version.rb +5 -0
- data/lib/shippo/exceptions.rb +7 -0
- data/lib/shippo/exceptions/api_error.rb +20 -0
- data/lib/shippo/exceptions/error.rb +22 -0
- data/lib/shippo/model/address.rb +5 -0
- data/lib/shippo/model/carrieraccount.rb +5 -0
- data/lib/shippo/model/customs_declaration.rb +6 -0
- data/lib/shippo/model/customs_item.rb +5 -0
- data/lib/shippo/model/manifest.rb +5 -0
- data/lib/shippo/model/parcel.rb +5 -0
- data/lib/shippo/model/rate.rb +5 -0
- data/lib/shippo/model/refund.rb +5 -0
- data/lib/shippo/model/shipment.rb +5 -0
- data/lib/shippo/model/transaction.rb +5 -0
- data/lib/shippo/tasks/shippo.rb +22 -0
- data/shippo.gemspec +34 -0
- metadata +226 -40
- data/example.rb +0 -71
- data/lib/shippo/address.rb +0 -10
- data/lib/shippo/api_object.rb +0 -89
- data/lib/shippo/carrieraccount.rb +0 -8
- data/lib/shippo/container_object.rb +0 -28
- data/lib/shippo/create.rb +0 -18
- data/lib/shippo/customs_declaration.rb +0 -7
- data/lib/shippo/customs_item.rb +0 -7
- data/lib/shippo/error.rb +0 -18
- data/lib/shippo/list.rb +0 -23
- data/lib/shippo/manifest.rb +0 -6
- data/lib/shippo/parcel.rb +0 -6
- data/lib/shippo/rate.rb +0 -5
- data/lib/shippo/refund.rb +0 -6
- data/lib/shippo/resource.rb +0 -29
- data/lib/shippo/shipment.rb +0 -14
- data/lib/shippo/transaction.rb +0 -6
- data/lib/shippo/update.rb +0 -15
- data/test/test.rb +0 -26
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
module Shippo
|
3
|
+
module API
|
4
|
+
module Category
|
5
|
+
# Indicates whether a shipment can be used to purchase Labels or only to obtain quote Rates.
|
6
|
+
# Note that if at least one quote Address is passed in the original request,
|
7
|
+
# the Shipment will be eligible for quotes only.
|
8
|
+
class Purpose < Base
|
9
|
+
allowed_values :purchase, :quote
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
module Shippo
|
3
|
+
module API
|
4
|
+
module Category
|
5
|
+
#
|
6
|
+
# +Source+ category describes the origin of an address in the following way:
|
7
|
+
# * +:fully_entered+ addresses only contain user-given values and are eligible for purchasing a label.
|
8
|
+
# * +:partially_entered+ addresses lack some required information and only qualify for requesting rates.
|
9
|
+
# * +:validator+ addresses have been created by the address validation service.
|
10
|
+
#
|
11
|
+
class Source < Base
|
12
|
+
allowed_values :fully_entered, :partially_entered, :validator
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
module Shippo
|
3
|
+
module API
|
4
|
+
module Category
|
5
|
+
# "VALID" shipments contain all required values and can be used to get rates and purchase labels.
|
6
|
+
# "INCOMPLETE" shipments lack required values but can be used for getting rates.
|
7
|
+
# "INVALID" shipments can't be used for getting rates or labels.
|
8
|
+
class State < Base
|
9
|
+
allowed_values :valid, :invalid, :incomplete
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
module Shippo
|
3
|
+
module API
|
4
|
+
module Category
|
5
|
+
# +Status+ is a category class with the following possible values:
|
6
|
+
#
|
7
|
+
# * "Waiting" shipments have been successfully submitted but not yet been processed.
|
8
|
+
# * "Queued" shipments are currently being processed.
|
9
|
+
# * "Success" shipments have been processed successfully,
|
10
|
+
# meaning that rate generation has concluded.
|
11
|
+
# * "Error" does not occur currently and is reserved for future use.
|
12
|
+
class Status < Base
|
13
|
+
allowed_values :waiting, :queued, :success, :error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Shippo
|
2
|
+
module API
|
3
|
+
module Extend
|
4
|
+
module Operation
|
5
|
+
def self.included(klass)
|
6
|
+
klass.instance_eval do
|
7
|
+
class << self
|
8
|
+
def operations(*ops)
|
9
|
+
ops.each do |operation|
|
10
|
+
module_name = "Shippo::API::Operations::#{operation.to_s.capitalize}"
|
11
|
+
# noinspection RubyResolve
|
12
|
+
self.extend(module_name.constantize)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shippo
|
2
|
+
module API
|
3
|
+
module Extend
|
4
|
+
module Url
|
5
|
+
def self.included(klass)
|
6
|
+
klass.instance_eval do
|
7
|
+
@url = nil
|
8
|
+
class << self
|
9
|
+
# It's a getter and a class-level setter
|
10
|
+
def url(value = nil)
|
11
|
+
return @url if @url
|
12
|
+
@url ||= value if value
|
13
|
+
@url ||= class_to_url
|
14
|
+
end
|
15
|
+
|
16
|
+
def class_to_url
|
17
|
+
words = self.short_name.underscore.split(/_/)
|
18
|
+
words.map { |w| "/#{w == words.last ? w.pluralize : w}" }.join
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'colored2'
|
2
|
+
require 'awesome_print'
|
3
|
+
module Shippo
|
4
|
+
module API
|
5
|
+
module Operations
|
6
|
+
module Create
|
7
|
+
# Creates an item in the database
|
8
|
+
# @param [Hash] params tacked onto the URL as URI parameters
|
9
|
+
def create(params={})
|
10
|
+
api_params = params.dup
|
11
|
+
Hashie::Extensions::StringifyKeys.stringify_keys!(api_params)
|
12
|
+
|
13
|
+
api_params.dup.each { |k, v| api_params[k] = v.id if v.is_a?(::Shippo::API::Resource) && v.id }
|
14
|
+
|
15
|
+
response = Shippo::API.post("#{url}/", api_params)
|
16
|
+
instance = self.from(response)
|
17
|
+
|
18
|
+
debug_log!(api_params, response, instance) if Shippo::API.debug?
|
19
|
+
instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def debug_log!(api_params, response, instance)
|
23
|
+
puts "#{self.name}->create / request : \n".bold.green.underlined
|
24
|
+
ap(api_params)
|
25
|
+
puts "#{self.name}->create / response: \n".bold.yellow.underlined
|
26
|
+
ap(response)
|
27
|
+
puts "#{self.name}->create / from: \n".bold.blue.underlined
|
28
|
+
ap(instance)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Shippo
|
2
|
+
module API
|
3
|
+
module Operations
|
4
|
+
module List
|
5
|
+
# Return all items
|
6
|
+
# @param [Hash] params of additional URI parameters tacked onto the query URL
|
7
|
+
def all(params={})
|
8
|
+
response = Shippo::API.get("#{url}/", params)
|
9
|
+
self.from(response)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Retrieve a concrete item by it's ID
|
13
|
+
# @param [Fixnum] id database ID of the item to be retrieved
|
14
|
+
# @param [Hash] params of additional URI parameters tacked onto the query URL
|
15
|
+
def get(id, params={})
|
16
|
+
response = Shippo::API.get("#{url}/#{CGI.escape(id)}/", params)
|
17
|
+
self.from(response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Shippo
|
2
|
+
module API
|
3
|
+
module Operations
|
4
|
+
module Rates
|
5
|
+
def rates(currency = nil, params = {})
|
6
|
+
if !currency.nil?
|
7
|
+
response = Shippo::API.get("#{url}/rates/#{currency}/", params)
|
8
|
+
else
|
9
|
+
response = Shippo::API.get("#{url}/rates/", params)
|
10
|
+
end
|
11
|
+
Shippo::Rate.from(response[:results])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'socket'
|
3
|
+
require 'json'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
require 'shippo/exceptions'
|
7
|
+
|
8
|
+
module Shippo
|
9
|
+
module API
|
10
|
+
#
|
11
|
+
# This class is the primary *internal* Interface to the Shippo API.
|
12
|
+
#
|
13
|
+
# Public consumers should use Model API, and perform actions on models
|
14
|
+
# rather than submit requests directly using this class.
|
15
|
+
#
|
16
|
+
# +Request+ instance is created with the intention to execute a single
|
17
|
+
# API call and once executed, it stores +response+ object. Used
|
18
|
+
# requests can not be re-executed.
|
19
|
+
#
|
20
|
+
# == Example
|
21
|
+
#
|
22
|
+
# @request = Shippo::API::Request.new(
|
23
|
+
# method: :get,
|
24
|
+
# uri: '/address,
|
25
|
+
# params: { object_id: 1 },
|
26
|
+
# headers: { 'Last-Modified' => '1213145' }
|
27
|
+
# begin
|
28
|
+
# @response = @request.execute
|
29
|
+
# Shippo::Address.from(@response)
|
30
|
+
# # =>
|
31
|
+
#
|
32
|
+
class Request
|
33
|
+
attr_accessor :username, :password
|
34
|
+
attr_accessor :method, :url, :params, :headers
|
35
|
+
|
36
|
+
# Result of the execute method is stored in #response and #parsed_response
|
37
|
+
attr_accessor :response, :parsed_response
|
38
|
+
|
39
|
+
# @param [symbol] method :get or any other method such as :put, :post, etc.
|
40
|
+
# @param [String] uri URI component appended to the base URL
|
41
|
+
# @param [Hash] params parameters to append to the URL
|
42
|
+
# @param [Hash] headers headers hash sent to the server
|
43
|
+
def initialize(method:, uri:, params: {}, headers: {})
|
44
|
+
self.method = method
|
45
|
+
self.params = params
|
46
|
+
self.headers = headers
|
47
|
+
self.url = api_url(uri)
|
48
|
+
self.response = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute
|
52
|
+
raise ArgumentError.new('Response is already defined, create another Request object.') if self.response
|
53
|
+
validate!
|
54
|
+
begin
|
55
|
+
self.response = shippo_phone_home
|
56
|
+
self.parsed_response = JSON::parse(response.body, { symbolize_names: true })
|
57
|
+
|
58
|
+
rescue ::RestClient::Unauthorized => e
|
59
|
+
raise Shippo::Exceptions::AuthenticationError.new(e.message)
|
60
|
+
|
61
|
+
rescue ::JSON::JSONError, ::JSON::ParserError, ::RestClient::BadRequest
|
62
|
+
raise Shippo::Exceptions::APIServerError.new('Unable to read data received back from the server', self)
|
63
|
+
|
64
|
+
rescue ::RestClient::Exception => e
|
65
|
+
raise Shippo::Exceptions::ConnectionError.new(connection_error_message(url, e))
|
66
|
+
|
67
|
+
rescue StandardError => e
|
68
|
+
raise Shippo::Exceptions::ConnectionError.new(connection_error_message(url, e)) if e.message =~ /TCP|connection|getaddrinfo/
|
69
|
+
|
70
|
+
STDERR.puts "#{self.class.name}: Internal error occurred while connecting to #{url}: #{e.message}".bold.red
|
71
|
+
STDERR.puts 'Stack Trace'.bold.yellow.underlined
|
72
|
+
STDERR.puts e.backtrace.join("\n").yellow
|
73
|
+
raise Shippo::Exceptions::Error.new(e)
|
74
|
+
end
|
75
|
+
self.parsed_response
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def shippo_phone_home
|
81
|
+
payload = {}
|
82
|
+
request_url = url
|
83
|
+
(method == :get) ? request_url = params_to_url(params, url) : payload = params.to_json
|
84
|
+
setup_headers!(headers)
|
85
|
+
opts = make_opts!(headers, method, payload, request_url)
|
86
|
+
make_request!(opts)
|
87
|
+
end
|
88
|
+
|
89
|
+
def make_request!(opts)
|
90
|
+
RestClient::Request.execute(opts) { |response, request, result, &block|
|
91
|
+
if [301, 302, 307].include? response.code
|
92
|
+
response.follow_redirection(request, result, &block)
|
93
|
+
else
|
94
|
+
response.return!(request, result, &block)
|
95
|
+
end
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def make_opts!(headers, method, payload, url)
|
100
|
+
{ :headers => headers,
|
101
|
+
:method => method,
|
102
|
+
:payload => payload,
|
103
|
+
:url => url,
|
104
|
+
:open_timeout => 15,
|
105
|
+
:timeout => 30,
|
106
|
+
:user => username,
|
107
|
+
:password => password,
|
108
|
+
:user_agent => 'Shippo/v2.0 RubyBindings'
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def setup_headers!(headers)
|
113
|
+
headers.merge!(
|
114
|
+
:accept => :json,
|
115
|
+
:content_type => :json,
|
116
|
+
:Authorization => "ShippoToken #{token}"
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def base
|
121
|
+
::Shippo::API.base
|
122
|
+
end
|
123
|
+
|
124
|
+
def token
|
125
|
+
::Shippo::API.token
|
126
|
+
end
|
127
|
+
|
128
|
+
def connection_error_message(url, error)
|
129
|
+
%Q[Could not connect to the Shippo API, via URL
|
130
|
+
#{url}.
|
131
|
+
|
132
|
+
Please check your Internet connection, try again, if the problem
|
133
|
+
persists please contact Shippo Customer Support.
|
134
|
+
|
135
|
+
Error Description:
|
136
|
+
#{error.class.name} ⇨ #{error.message}].gsub(/^\s*/, '')
|
137
|
+
end
|
138
|
+
|
139
|
+
def api_url(uri_component = '')
|
140
|
+
base + uri_component
|
141
|
+
end
|
142
|
+
|
143
|
+
def params_to_url(params, url)
|
144
|
+
pairs = []
|
145
|
+
params.each { |k, v|
|
146
|
+
pairs.push "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
147
|
+
}
|
148
|
+
url += "?#{pairs.join('&')}" unless pairs.empty?
|
149
|
+
url
|
150
|
+
end
|
151
|
+
|
152
|
+
def validate!
|
153
|
+
raise Shippo::Exceptions::AuthenticationError.new(
|
154
|
+
'API credentials seems to be missing, perhaps you forgot to set Shippo::API.token?') \
|
155
|
+
unless token
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'hashie/mash'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
require 'shippo/exceptions'
|
7
|
+
|
8
|
+
require_relative 'api_object'
|
9
|
+
require_relative 'category/status'
|
10
|
+
require_relative 'transformers/list'
|
11
|
+
require_relative 'extend/operation'
|
12
|
+
require_relative 'extend/transformers'
|
13
|
+
require_relative 'extend/url'
|
14
|
+
|
15
|
+
module Shippo
|
16
|
+
module API
|
17
|
+
class Resource < Hashie::Mash
|
18
|
+
include Hashie::Extensions::StringifyKeys
|
19
|
+
include Enumerable
|
20
|
+
extend Forwardable
|
21
|
+
|
22
|
+
def self.object_properties
|
23
|
+
Shippo::API::ApiObject::PROPS
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :object
|
27
|
+
def_delegators :@object, *object_properties
|
28
|
+
|
29
|
+
# Creates a possibly recursive chain (map of lists, etc) of Resource
|
30
|
+
# instances based on whether each value is a scalar, array or a hash.
|
31
|
+
def self.from(values)
|
32
|
+
# recursive on arrays
|
33
|
+
if values.is_a?(Array)
|
34
|
+
values.map { |list| from(list) }
|
35
|
+
elsif values.respond_to?(:keys) # a Hash or a Hash derivative
|
36
|
+
new(values)
|
37
|
+
else
|
38
|
+
values
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.short_name(name = self.name)
|
43
|
+
name.split('::')[-1]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generate object_ accessors.
|
47
|
+
object_properties.each do |property|
|
48
|
+
method_name = ApiObject.field_name(property)
|
49
|
+
define_method method_name do
|
50
|
+
STDOUT.puts "#{method_name} style accessors are deprecated in favor of #resource.object.#{property}" if Shippo::API.warnings
|
51
|
+
self.object.send(property)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# allows resources to use default or a custom url
|
56
|
+
include Shippo::API::Extend::Url
|
57
|
+
# allows resources to set supported operations
|
58
|
+
include Shippo::API::Extend::Operation
|
59
|
+
# adds #transformers method that enumerates available transformers
|
60
|
+
# of the hashes into other types.
|
61
|
+
include Shippo::API::Extend::Transformers
|
62
|
+
|
63
|
+
# As a Hashie::Mash subclass, Resource can initialize from another hash
|
64
|
+
def initialize(*args)
|
65
|
+
if args.first.is_a?(Fixnum) or
|
66
|
+
(args.first.is_a?(String) && args.first =~ /^[0-9A-Fa-f]+$/)
|
67
|
+
self.id = args.first
|
68
|
+
elsif args.first.respond_to?(:keys)
|
69
|
+
h = Hashie::Mash.new(args.first)
|
70
|
+
self.deep_merge!(h)
|
71
|
+
self.object = ApiObject.create_object(self)
|
72
|
+
transformers.each do |transformer|
|
73
|
+
transformer.new(self).transform
|
74
|
+
end
|
75
|
+
else
|
76
|
+
super(*args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
"#<#{self.class.short_name}:0x#{self.object_id.to_s(16)}#{id.nil? ? '' : "[id=#{id}]"}#{to_hash.inspect}->#{object.inspect}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
self.class.short_name + self.to_hash.to_s + '->' + self.object.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def url
|
89
|
+
raise Shippo::Exceptions::MissingDataError.new("#{self.class} has no object_id") unless id
|
90
|
+
"#{self.class.url}/#{CGI.escape(id)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def refresh
|
94
|
+
response = Shippo::API.get(url)
|
95
|
+
self.from(response)
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def success?
|
100
|
+
self.object && self.object.status && self.object.status.eql?(Shippo::API::Category::Status::SUCCESS)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|