easypost 1.1.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +27 -0
- data/CHANGELOG +0 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +21 -0
- data/README.md +99 -0
- data/Rakefile +0 -0
- data/VERSION +1 -0
- data/bin/easypost-irb +7 -0
- data/easypost.gemspec +24 -0
- data/lib/easypost.rb +105 -34
- data/lib/easypost/address.rb +14 -7
- data/lib/easypost/batch.rb +34 -0
- data/lib/easypost/customs_info.rb +4 -0
- data/lib/easypost/customs_item.rb +4 -0
- data/lib/easypost/{errors/easypost_error.rb → error.rb} +10 -1
- data/lib/easypost/object.rb +120 -0
- data/lib/easypost/parcel.rb +4 -0
- data/lib/easypost/postage_label.rb +4 -0
- data/lib/easypost/rate.rb +4 -0
- data/lib/easypost/refund.rb +4 -0
- data/lib/easypost/resource.rb +67 -0
- data/lib/easypost/scan_form.rb +4 -0
- data/lib/easypost/shipment.rb +82 -0
- data/lib/easypost/util.rb +102 -0
- data/lib/easypost/version.rb +3 -0
- data/spec/address_spec.rb +75 -0
- data/spec/batch_spec.rb +96 -0
- data/spec/refund_spec.rb +65 -0
- data/spec/scan_form_spec.rb +63 -0
- data/spec/shipment_spec.rb +141 -0
- data/spec/spec_helper.rb +84 -0
- metadata +66 -15
- data/lib/easypost/errors/authentication_error.rb +0 -4
- data/lib/easypost/postage.rb +0 -52
@@ -1,15 +1,24 @@
|
|
1
1
|
module EasyPost
|
2
|
-
class
|
2
|
+
class Error < StandardError
|
3
3
|
attr_reader :message
|
4
4
|
attr_reader :http_status
|
5
5
|
attr_reader :http_body
|
6
6
|
attr_reader :json_body
|
7
|
+
attr_reader :param
|
7
8
|
|
8
9
|
def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
|
9
10
|
@message = message
|
10
11
|
@http_status = http_status
|
11
12
|
@http_body = http_body
|
12
13
|
@json_body = json_body
|
14
|
+
|
15
|
+
begin
|
16
|
+
@param = @json_body[:error][:param]
|
17
|
+
rescue
|
18
|
+
@param = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
super(message)
|
13
22
|
end
|
14
23
|
|
15
24
|
def to_s
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module EasyPost
|
2
|
+
class EasyPostObject
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :api_key
|
6
|
+
@@immutable_values = Set.new([:api_key, :id])
|
7
|
+
|
8
|
+
def initialize(id=nil, api_key=nil)
|
9
|
+
@api_key = api_key
|
10
|
+
@values = {}
|
11
|
+
@unsaved_values = Set.new
|
12
|
+
@transient_values = Set.new
|
13
|
+
self.id = id if id
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.construct_from(values, api_key=nil)
|
17
|
+
obj = self.new(values[:id], api_key)
|
18
|
+
obj.refresh_from(values, api_key)
|
19
|
+
obj
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s(*args)
|
23
|
+
MultiJson.dump(@values, :pretty => true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect()
|
27
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
28
|
+
"#<#{self.class}:#{id_string}> JSON: " + MultiJson.dump(@values, :pretty => true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def refresh_from(values, api_key, partial=false)
|
32
|
+
@api_key = api_key
|
33
|
+
|
34
|
+
added = Set.new(values.keys - @values.keys)
|
35
|
+
|
36
|
+
instance_eval do
|
37
|
+
add_accessors(added)
|
38
|
+
end
|
39
|
+
|
40
|
+
values.each do |k, v|
|
41
|
+
@values[k] = Util.convert_to_easypost_object(v, api_key)
|
42
|
+
@transient_values.delete(k)
|
43
|
+
@unsaved_values.delete(k)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](k)
|
48
|
+
k = k.to_sym if k.is_a?(String)
|
49
|
+
@values[k]
|
50
|
+
end
|
51
|
+
|
52
|
+
def []=(k, v)
|
53
|
+
send(:"#{k}=", v)
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
@values.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def values
|
61
|
+
@values.values
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json(options = {})
|
65
|
+
MultiJson.dump(@values)
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_json(options = {})
|
69
|
+
@values.as_json
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_hash
|
73
|
+
@values
|
74
|
+
end
|
75
|
+
|
76
|
+
def each(&blk)
|
77
|
+
@values.each(&blk)
|
78
|
+
end
|
79
|
+
|
80
|
+
def id=(id)
|
81
|
+
@values[:id] = id
|
82
|
+
end
|
83
|
+
|
84
|
+
def id
|
85
|
+
@values[:id]
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def metaclass
|
91
|
+
class << self; self; end
|
92
|
+
end
|
93
|
+
|
94
|
+
def remove_accessors(keys)
|
95
|
+
metaclass.instance_eval do
|
96
|
+
keys.each do |k|
|
97
|
+
next if @@immutable_values.include?(k)
|
98
|
+
k_eq = :"#{k}="
|
99
|
+
remove_method(k) if method_defined?(k)
|
100
|
+
remove_method(k_eq) if method_defined?(k_eq)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_accessors(keys)
|
106
|
+
metaclass.instance_eval do
|
107
|
+
keys.each do |k|
|
108
|
+
next if @@immutable_values.include?(k)
|
109
|
+
k_eq = :"#{k}="
|
110
|
+
define_method(k) { @values[k] }
|
111
|
+
define_method(k_eq) do |v|
|
112
|
+
@values[k] = v
|
113
|
+
@unsaved_values.add(k)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module EasyPost
|
2
|
+
class Resource < EasyPostObject
|
3
|
+
def self.class_name
|
4
|
+
camel = self.name.split('::')[-1]
|
5
|
+
snake = camel[0..0] + camel[1..-1].gsub(/([A-Z])/, '_\1')
|
6
|
+
return snake.downcase
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.url
|
10
|
+
if self.class_name == 'resource'
|
11
|
+
raise NotImplementedError.new('Resource is an abstract class. You should perform actions on its subclasses (Address, Shipment, etc.)')
|
12
|
+
end
|
13
|
+
if(self.class_name[-1..-1] == 's' || self.class_name[-1..-1] == 'h')
|
14
|
+
return "/#{CGI.escape(self.class_name.downcase)}es"
|
15
|
+
else
|
16
|
+
return "/#{CGI.escape(class_name.downcase)}s"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def url
|
21
|
+
unless self.id
|
22
|
+
raise Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{self.id.inspect}")
|
23
|
+
end
|
24
|
+
return "#{self.class.url}/#{CGI.escape(id)}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh
|
28
|
+
response, api_key = EasyPost.request(:get, url, @api_key, @retrieve_options)
|
29
|
+
refresh_from(response, api_key)
|
30
|
+
return self
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.all(filters={}, api_key=nil)
|
34
|
+
response, api_key = EasyPost.request(:get, url, api_key, filters)
|
35
|
+
return Util.convert_to_easypost_object(response, api_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.retrieve(id, api_key=nil)
|
39
|
+
instance = self.new(id, api_key)
|
40
|
+
instance.refresh
|
41
|
+
return instance
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create(params={}, api_key=nil)
|
45
|
+
wrapped_params = {}
|
46
|
+
wrapped_params[self.class_name().to_sym] = params
|
47
|
+
response, api_key = EasyPost.request(:post, self.url, api_key, wrapped_params)
|
48
|
+
return Util.convert_to_easypost_object(response, api_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete
|
52
|
+
response, api_key = EasyPost.request(:delete, url, @api_key)
|
53
|
+
refresh_from(response, api_key)
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
def save
|
58
|
+
if @unsaved_values.length > 0
|
59
|
+
values = {}
|
60
|
+
@unsaved_values.each { |k| values[k] = @values[k] }
|
61
|
+
response, api_key = EasyPost.request(:post, url, @api_key, values)
|
62
|
+
refresh_from(response, api_key)
|
63
|
+
end
|
64
|
+
return self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module EasyPost
|
2
|
+
class Shipment < Resource
|
3
|
+
|
4
|
+
def get_rates(params={})
|
5
|
+
response, api_key = EasyPost.request(:get, url + '/rates', @api_key, params)
|
6
|
+
self.refresh_from(response, @api_key, true)
|
7
|
+
|
8
|
+
return self
|
9
|
+
end
|
10
|
+
|
11
|
+
def buy(params={})
|
12
|
+
response, api_key = EasyPost.request(:post, url + '/buy', @api_key, params)
|
13
|
+
self.refresh_from(response, @api_key, true)
|
14
|
+
|
15
|
+
return self
|
16
|
+
end
|
17
|
+
|
18
|
+
def refund(params={})
|
19
|
+
response, api_key = EasyPost.request(:get, url + '/refund', @api_key, params)
|
20
|
+
self.refresh_from(response, @api_key, true)
|
21
|
+
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
def track(params={})
|
26
|
+
response, api_key = EasyPost.request(:get, url + '/track', @api_key, params)
|
27
|
+
self.refresh_from(response, @api_key, true)
|
28
|
+
|
29
|
+
return self
|
30
|
+
end
|
31
|
+
|
32
|
+
def stamp(params={})
|
33
|
+
response, api_key = EasyPost.request(:get, url + '/stamp', @api_key, params)
|
34
|
+
|
35
|
+
return response[:stamp_url]
|
36
|
+
end
|
37
|
+
|
38
|
+
def barcode(params={})
|
39
|
+
response, api_key = EasyPost.request(:get, url + '/barcode', @api_key, params)
|
40
|
+
|
41
|
+
return response[:barcode_url]
|
42
|
+
end
|
43
|
+
|
44
|
+
def lowest_rate(carriers=[], services=[])
|
45
|
+
lowest = nil
|
46
|
+
|
47
|
+
self.get_rates unless self.rates
|
48
|
+
|
49
|
+
if !carriers.is_a?(Array)
|
50
|
+
carriers = carriers.split(',')
|
51
|
+
end
|
52
|
+
carriers.map!(&:downcase)
|
53
|
+
|
54
|
+
if !services.is_a?(Array)
|
55
|
+
services = services.split(',')
|
56
|
+
end
|
57
|
+
services.map!(&:downcase)
|
58
|
+
|
59
|
+
self.rates.each do |k|
|
60
|
+
|
61
|
+
rate_carrier = k.carrier.downcase
|
62
|
+
if carriers.size() > 0 && !carriers.include?(rate_carrier)
|
63
|
+
next
|
64
|
+
end
|
65
|
+
|
66
|
+
rate_service = k.service.downcase
|
67
|
+
if services.size() > 0 && !services.include?(rate_service)
|
68
|
+
next
|
69
|
+
end
|
70
|
+
|
71
|
+
if lowest == nil || k.rate.to_f < lowest.rate.to_f
|
72
|
+
lowest = k
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
raise Error.new('No rates found.') if lowest == nil
|
77
|
+
|
78
|
+
return lowest
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module EasyPost
|
2
|
+
module Util
|
3
|
+
def self.objects_to_ids(obj)
|
4
|
+
case obj
|
5
|
+
when Resource
|
6
|
+
return {:id => obj.id}
|
7
|
+
when Hash
|
8
|
+
result = {}
|
9
|
+
obj.each { |k, v| result[k] = objects_to_ids(v) unless v.nil? }
|
10
|
+
return result
|
11
|
+
when Array
|
12
|
+
return obj.map { |v| objects_to_ids(v) }
|
13
|
+
else
|
14
|
+
return obj
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.convert_to_easypost_object(response, api_key)
|
19
|
+
types = { 'Address' => Address,
|
20
|
+
'ScanForm' => ScanForm,
|
21
|
+
'CustomsItem' => CustomsItem,
|
22
|
+
'CustomsInfo' => CustomsInfo,
|
23
|
+
'Parcel' => Parcel,
|
24
|
+
'Shipment' => Shipment,
|
25
|
+
'Rate' => Rate,
|
26
|
+
'Refund' => Refund,
|
27
|
+
'Batch' => Batch,
|
28
|
+
'PostageLabel' => PostageLabel }
|
29
|
+
|
30
|
+
prefixes = { 'adr' => Address,
|
31
|
+
'sf' => ScanForm,
|
32
|
+
'cstitem' => CustomsItem,
|
33
|
+
'cstinfo' => CustomsInfo,
|
34
|
+
'prcl' => Parcel,
|
35
|
+
'shp' => Shipment,
|
36
|
+
'rate' => Rate,
|
37
|
+
'rfnd' => Refund,
|
38
|
+
'batch' => Batch,
|
39
|
+
'pl' => PostageLabel }
|
40
|
+
|
41
|
+
case response
|
42
|
+
when Array
|
43
|
+
return response.map { |i| convert_to_easypost_object(i, api_key) }
|
44
|
+
when Hash
|
45
|
+
if cls_name = response[:object]
|
46
|
+
cls = types[cls_name]
|
47
|
+
elsif response[:id] && cls_prefix = response[:id][0..response[:id].index('_')]
|
48
|
+
cls = prefixes[cls_prefix[0..-2]]
|
49
|
+
end
|
50
|
+
|
51
|
+
cls ||= EasyPostObject
|
52
|
+
return cls.construct_from(response, api_key)
|
53
|
+
else
|
54
|
+
return response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.symbolize_names(obj)
|
59
|
+
case obj
|
60
|
+
when Hash
|
61
|
+
result = {}
|
62
|
+
obj.each do |k, v|
|
63
|
+
k = (k.to_sym rescue k) || k
|
64
|
+
obj[k] = symbolize_names(v)
|
65
|
+
end
|
66
|
+
return result
|
67
|
+
when Array
|
68
|
+
return obj.map { |v| symbolize_names(v) }
|
69
|
+
else
|
70
|
+
return obj
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.url_encode(key)
|
75
|
+
URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.flatten_params(params, parent_key=nil)
|
79
|
+
result = []
|
80
|
+
if params.is_a?(Hash)
|
81
|
+
params.each do |k, v|
|
82
|
+
calculated_key = parent_key ? "#{parent_key}[#{url_encode(k)}]" : url_encode(k)
|
83
|
+
if v.is_a?(Hash) or v.is_a?(Array)
|
84
|
+
result += flatten_params(v, calculated_key)
|
85
|
+
else
|
86
|
+
result << [calculated_key, v]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
elsif params.is_a?(Array)
|
90
|
+
params.each_with_index do |v, i|
|
91
|
+
calculated_key = parent_key ? "#{parent_key}[#{i}]" : i
|
92
|
+
if v.is_a?(Hash) or v.is_a?(Array)
|
93
|
+
result += flatten_params(v, calculated_key)
|
94
|
+
else
|
95
|
+
result << [calculated_key, v]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|