binarylogic-shippinglogic 0.9.0 → 1.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.
- data/README.rdoc +29 -16
- data/VERSION.yml +2 -2
- data/lib/shippinglogic/fedex.rb +18 -3
- data/lib/shippinglogic/fedex/attributes.rb +3 -3
- data/lib/shippinglogic/fedex/cancel.rb +47 -0
- data/lib/shippinglogic/fedex/error.rb +23 -13
- data/lib/shippinglogic/fedex/{rates.rb → rate.rb} +99 -102
- data/lib/shippinglogic/fedex/request.rb +43 -1
- data/lib/shippinglogic/fedex/response.rb +1 -1
- data/lib/shippinglogic/fedex/service.rb +63 -2
- data/lib/shippinglogic/fedex/ship.rb +227 -0
- data/lib/shippinglogic/fedex/signature.rb +66 -0
- data/lib/shippinglogic/fedex/track.rb +16 -4
- data/shippinglogic.gemspec +96 -0
- data/spec/fedex/attributes_spec.rb +4 -4
- data/spec/fedex/cancel_spec.rb +10 -0
- data/spec/fedex/{rates_spec.rb → rate_spec.rb} +4 -3
- data/spec/fedex/service_spec.rb +5 -0
- data/spec/fedex/ship_spec.rb +22 -0
- data/spec/fedex/signature_spec.rb +10 -0
- data/spec/fedex/validation_spec.rb +1 -1
- data/spec/{fedex_spec_no.rb → fedex_spec.rb} +0 -0
- data/spec/lib/interceptor.rb +1 -1
- data/spec/responses/basic_ship.xml +7 -0
- data/spec/responses/basic_signature.xml +7 -0
- data/spec/responses/cancel_not_found.xml +7 -0
- data/spec/spec_helper.rb +8 -4
- metadata +20 -7
data/README.rdoc
CHANGED
@@ -31,25 +31,34 @@ You can also install this as a plugin:
|
|
31
31
|
|
32
32
|
See below for usage examples.
|
33
33
|
|
34
|
-
==
|
34
|
+
== Simple tracking example
|
35
35
|
|
36
|
-
What
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
A
|
36
|
+
What I think is unique about this library is it's usage / syntax:
|
37
|
+
|
38
|
+
fedex = Shippinglogic::FedEx.new(key, password, account, meter)
|
39
|
+
tracking = fedex.track(:tracking_number => "XXXXXXXXXXXXXXXXX")
|
40
|
+
# => A proxy object that delegates calls to an array of Shippinglogic::FedEx::Track::Event objects
|
41
|
+
|
42
|
+
# this shows that the tracking object is a proxy for the underlying array
|
43
|
+
tracking.tracking_number
|
44
|
+
# => "XXXXXXXXXXXXXXXXX"
|
45
|
+
|
46
|
+
tracking.first
|
47
|
+
# => #<Shippinglogic::FedEx::Track::Event @postal_code="95817", @name="Delivered", @state="CA", @residential=false,
|
48
|
+
# @city="Sacramento", @type="DL", @country="US", @occured_at=Mon Dec 08 10:43:37 -0500 2008>
|
49
|
+
|
50
|
+
tracking.first.name
|
51
|
+
# => "Delivered"
|
41
52
|
|
42
|
-
|
43
|
-
tracking = fedex.track(:tracking_number => "077973360403984")
|
53
|
+
== Calls to the web services are lazy
|
44
54
|
|
45
|
-
|
55
|
+
In our above example, you will notice we are able to access attributes, while at the same time able to treat the object as an array. That's because the object is not actually an array, it acts as a proxy for the underlying array.
|
46
56
|
|
47
|
-
|
48
|
-
tracking.size # cache is used to determine the size of the array
|
57
|
+
That being said, a request is not sent to FedEx until we need to deal with the underlying array. Meaning it's lazy, which is more efficient. Most would think the request to FedEx was sent when we initialized the object. This is not the case. The request to FedEx was sent when we executed "tracking.first".
|
49
58
|
|
50
|
-
This is similar to how ActiveRecord's association proxies work. When you call user.orders no database activity occurs until you actually use the object.
|
59
|
+
This is similar to how ActiveRecord's association proxies work. When you call "user.orders" no database activity occurs until you actually use the object (Ex: user.orders.each).
|
51
60
|
|
52
|
-
|
61
|
+
== Flexibility
|
53
62
|
|
54
63
|
You will notice above we assign the result of the 'track' method to a variable called 'tracking'. That object has more to it:
|
55
64
|
|
@@ -68,7 +77,6 @@ You will notice above we assign the result of the 'track' method to a variable c
|
|
68
77
|
tracking.tracking_number
|
69
78
|
# => "ZZZZZZZZZZZZZZZZ"
|
70
79
|
|
71
|
-
# Mass attribute reading
|
72
80
|
tracking.attributes
|
73
81
|
# => {:tracking_number => "ZZZZZZZZZZZZZZZZ"}
|
74
82
|
|
@@ -76,14 +84,19 @@ You will notice above we assign the result of the 'track' method to a variable c
|
|
76
84
|
|
77
85
|
This library is still very new, as a result only FedEx is support at this time. More will come.
|
78
86
|
|
87
|
+
I spent a lot of time on the documentation, for examples of how to use each service please see the docs for their respective classes.
|
88
|
+
|
79
89
|
=== FedEx
|
80
90
|
|
81
91
|
1. <b>Tracking</b> - See Shippinglogic::Fedex::Track
|
82
|
-
|
92
|
+
2. <b>Signature proof of delivery</b> - See Shippinglogic::Fedex::Signature
|
93
|
+
3. <b>Getting service rates</b> - See Shippinglogic::Fedex::Rate
|
94
|
+
4. <b>Creating shipments w/ labels</b> - See Shippinglogic::Fedex::Ship
|
95
|
+
5. <b>Canceling shipments</b> - See Shippinglogic::Fedex::Cancel
|
83
96
|
|
84
97
|
=== Add your own services
|
85
98
|
|
86
|
-
Simply fork the project and make your changes. If you want to add support for a new service it
|
99
|
+
Simply fork the project and make your changes. If you want to add support for a new service it is very straight forward. Checkout the code in Shippinglogic::Fedex::Track, it very simple and easy to follow. It's a great place to start because its the simplest of services.
|
87
100
|
|
88
101
|
== Interface integration
|
89
102
|
|
data/VERSION.yml
CHANGED
data/lib/shippinglogic/fedex.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require "shippinglogic/fedex/error"
|
2
2
|
require "shippinglogic/fedex/service"
|
3
|
-
require "shippinglogic/fedex/
|
3
|
+
require "shippinglogic/fedex/cancel"
|
4
|
+
require "shippinglogic/fedex/rate"
|
5
|
+
require "shippinglogic/fedex/ship"
|
6
|
+
require "shippinglogic/fedex/signature"
|
4
7
|
require "shippinglogic/fedex/track"
|
5
8
|
|
6
9
|
module Shippinglogic
|
@@ -53,8 +56,20 @@ module Shippinglogic
|
|
53
56
|
self.options = self.class.options.merge(options)
|
54
57
|
end
|
55
58
|
|
56
|
-
def
|
57
|
-
@
|
59
|
+
def cancel(attributes = {})
|
60
|
+
@cancel ||= Cancel.new(self, attributes)
|
61
|
+
end
|
62
|
+
|
63
|
+
def rate(attributes = {})
|
64
|
+
@rate ||= Rate.new(self, attributes)
|
65
|
+
end
|
66
|
+
|
67
|
+
def ship(attributes = {})
|
68
|
+
@ship ||= Ship.new(self, attributes)
|
69
|
+
end
|
70
|
+
|
71
|
+
def signature(attributes = {})
|
72
|
+
@signature ||= Signature.new(self, attributes)
|
58
73
|
end
|
59
74
|
|
60
75
|
def track(attributes = {})
|
@@ -64,11 +64,11 @@ module Shippinglogic
|
|
64
64
|
|
65
65
|
private
|
66
66
|
def attribute_names
|
67
|
-
|
67
|
+
real_class.attribute_names
|
68
68
|
end
|
69
69
|
|
70
70
|
def attribute_options(name)
|
71
|
-
self.
|
71
|
+
self.real_class.attribute_options(name)
|
72
72
|
end
|
73
73
|
|
74
74
|
def attribute_type(name)
|
@@ -79,7 +79,7 @@ module Shippinglogic
|
|
79
79
|
default = attribute_options(name)[:default]
|
80
80
|
case default
|
81
81
|
when Proc
|
82
|
-
default.call
|
82
|
+
default.call(self)
|
83
83
|
else
|
84
84
|
default
|
85
85
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Shippinglogic
|
2
|
+
class FedEx
|
3
|
+
# An interface to the shipment canceling service provided by FedEx. Allows you to cancel a shipment
|
4
|
+
#
|
5
|
+
# == Accessor methods / options
|
6
|
+
#
|
7
|
+
# * <tt>tracking_number</tt> - the tracking number
|
8
|
+
# * <tt>deletion_control</tt> - one of DELETION_CONTROL (default: DELETE_ALL_PACKAGES)
|
9
|
+
#
|
10
|
+
# === Simple Example
|
11
|
+
#
|
12
|
+
# fedex = Shippinglogic::FedEx.new(key, password, account, meter)
|
13
|
+
# cancel = fedex.cancel(:tracking_number => "my number")
|
14
|
+
# cancel.perform
|
15
|
+
# # => true
|
16
|
+
class Cancel < Service
|
17
|
+
VERSION = {:major => 6, :intermediate => 0, :minor => 0}
|
18
|
+
|
19
|
+
attribute :tracking_number, :string
|
20
|
+
attribute :deletion_control, :string, :default => "DELETE_ALL_PACKAGES"
|
21
|
+
|
22
|
+
# Our services are set up as a proxy. We need to access the underlying object, to trigger the request
|
23
|
+
# to fedex. So calling this method is a way to do that since there really is no underlying object
|
24
|
+
def perform
|
25
|
+
target && true
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
# The parent class Service requires that we define this method. This is our kicker. This method is only
|
30
|
+
# called when we need to deal with information from FedEx. Notice the caching into the @target variable.
|
31
|
+
def target
|
32
|
+
@target ||= request(build_request)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Just building some XML to send off to FedEx. FedEx require this particualr format.
|
36
|
+
def build_request
|
37
|
+
b = builder
|
38
|
+
xml = b.DeleteShipmentRequest(:xmlns => "http://fedex.com/ws/ship/v#{VERSION[:major]}") do
|
39
|
+
build_authentication(b)
|
40
|
+
build_version(b, "ship", VERSION[:major], VERSION[:intermediate], VERSION[:minor])
|
41
|
+
b.TrackingNumber tracking_number
|
42
|
+
b.DeletionControl deletion_control
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -16,31 +16,41 @@ module Shippinglogic
|
|
16
16
|
# # to get the raw response from fedex
|
17
17
|
# end
|
18
18
|
class Error < StandardError
|
19
|
-
attr_accessor :
|
19
|
+
attr_accessor :errors, :response
|
20
20
|
|
21
21
|
def initialize(response)
|
22
22
|
self.response = response
|
23
23
|
|
24
24
|
if response.blank?
|
25
|
-
|
25
|
+
add_error("The response from FedEx was blank.")
|
26
26
|
elsif !response.is_a?(Hash)
|
27
|
-
|
27
|
+
add_error("The response from FedEx was malformed and was not in a valid XML format.")
|
28
28
|
elsif notifications = response[:notifications]
|
29
|
-
|
30
|
-
|
29
|
+
notifications = notifications.is_a?(Array) ? notifications : [notifications]
|
30
|
+
notifications.each { |notification| add_error(notification[:message], notification[:code]) }
|
31
31
|
elsif response[:"soapenv:fault"] && detail = response[:"soapenv:fault"][:detail][:"con:fault"]
|
32
|
-
|
33
|
-
|
32
|
+
add_error(detail[:"con:reason"], detail[:"con:error_code"])
|
33
|
+
|
34
|
+
if detail[:"con:details"] && detail[:"con:details"][:"con1:validation_failure_detail"] && messages = detail[:"con:details"][:"con1:validation_failure_detail"][:"con1:message"]
|
35
|
+
messages = messages.is_a?(Array) ? messages : [messages]
|
36
|
+
messages.each { |message| add_error(message) }
|
37
|
+
end
|
34
38
|
else
|
35
|
-
|
39
|
+
add_error(
|
40
|
+
"There was a problem with your fedex request, and we couldn't locate a specific error message. This means your response " +
|
36
41
|
"was in an unexpected format. You might try glancing at the raw response by using the 'response' method on this error object."
|
42
|
+
)
|
37
43
|
end
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
45
|
+
super(errors.collect { |error| error[:message] }.to_sentence)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_error(error, code = nil)
|
49
|
+
errors << {:message => error, :code => code}
|
50
|
+
end
|
51
|
+
|
52
|
+
def errors
|
53
|
+
@errors ||= []
|
44
54
|
end
|
45
55
|
end
|
46
56
|
end
|
@@ -3,7 +3,8 @@ module Shippinglogic
|
|
3
3
|
# An interface to the rate services provided by FedEx. Allows you to get an array of rates from fedex for a shipment,
|
4
4
|
# or a single rate for a specific service.
|
5
5
|
#
|
6
|
-
# ==
|
6
|
+
# == Options
|
7
|
+
# === Shipper options
|
7
8
|
#
|
8
9
|
# * <tt>shipper_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
9
10
|
# * <tt>shipper_city</tt> - city part of the address.
|
@@ -11,71 +12,86 @@ module Shippinglogic
|
|
11
12
|
# * <tt>shipper_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
12
13
|
# * <tt>shipper_country</tt> - country code part of the address, use abbreviations, ex: 'US'
|
13
14
|
# * <tt>shipper_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
15
|
+
#
|
16
|
+
# === Recipient options
|
17
|
+
#
|
14
18
|
# * <tt>recipient_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
|
15
19
|
# * <tt>recipient_city</tt> - city part of the address.
|
16
20
|
# * <tt>recipient_state</tt> - state part of the address, use state abreviations.
|
17
21
|
# * <tt>recipient_postal_code</tt> - postal code part of the address. Ex: zip for the US.
|
18
22
|
# * <tt>recipient_country</tt> - country code part of the address, use abbreviations, ex: 'US'
|
19
23
|
# * <tt>recipient_residential</tt> - a boolean value representing if the address is redential or not (default: false)
|
20
|
-
#
|
21
|
-
#
|
24
|
+
#
|
25
|
+
# === Packaging options
|
26
|
+
#
|
27
|
+
# One thing to note is that FedEx does support multiple package shipments. The problem is that all of the packages must be identical.
|
28
|
+
# FedEx specifically notes in their documentation that mutiple package specifications are not allowed. So your only option for a
|
29
|
+
# multi package shipment is to increase the package_count option and keep the dimensions and weight the same for all packages. Then again,
|
30
|
+
# the documentation for the FedEx web services is terrible, so I could be wrong. Any tests I tried resulted in an error though.
|
31
|
+
#
|
22
32
|
# * <tt>packaging_type</tt> - one of PACKAGE_TYPES. (default: YOUR_PACKAGING)
|
23
|
-
# * <tt>
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true)
|
34
|
-
# * <tt>special_services_requested</tt> - any exceptions or special services FedEx needs to be aware of, this should be
|
35
|
-
# one or more of SPECIAL_SERVICES. (default: nil)
|
33
|
+
# * <tt>package_count</tt> - the number of packages in your shipment. (default: 1)
|
34
|
+
# * <tt>package_weight</tt> - a single packages weight.
|
35
|
+
# * <tt>package_weight_units</tt> - either LB or KG. (default: LB)
|
36
|
+
# * <tt>package_length</tt> - a single packages length.
|
37
|
+
# * <tt>package_width</tt> - a single packages width.
|
38
|
+
# * <tt>package_height</tt> - a single packages height.
|
39
|
+
# * <tt>package_dimension_units</tt> - either IN or CM. (default: IN)
|
40
|
+
#
|
41
|
+
# === Monetary options
|
42
|
+
#
|
36
43
|
# * <tt>currency_type</tt> - the type of currency. (default: nil, because FedEx will default to your account preferences)
|
37
|
-
# * <tt>rate_request_types</tt> - one or more of RATE_REQUEST_TYPES. (default: ACCOUNT)
|
38
44
|
# * <tt>insured_value</tt> - the value you want to insure, if any. (default: nil)
|
39
45
|
# * <tt>payment_type</tt> - one of PAYMENT_TYPES. (default: SENDER)
|
40
46
|
# * <tt>payor_account_number</tt> - if the account paying for this ship is different than the account you specified then
|
41
|
-
# you can specify that here. (default:
|
47
|
+
# you can specify that here. (default: your account number)
|
42
48
|
# * <tt>payor_country</tt> - the country code for the account number. (default: US)
|
43
49
|
#
|
44
|
-
# ===
|
50
|
+
# === Delivery options
|
51
|
+
#
|
52
|
+
# * <tt>ship_time</tt> - a Time object representing when you want to ship the package. (default: Time.now)
|
53
|
+
# * <tt>service_type</tt> - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all
|
54
|
+
# available services. (default: nil)
|
55
|
+
# * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true)
|
56
|
+
# * <tt>dropoff_type</tt> - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP)
|
57
|
+
# * <tt>special_services_requested</tt> - any exceptions or special services FedEx needs to be aware of, this should be
|
58
|
+
# one or more of SPECIAL_SERVICES. (default: nil)
|
59
|
+
#
|
60
|
+
# === Misc options
|
61
|
+
#
|
62
|
+
# * <tt>rate_request_types</tt> - one or more of RATE_REQUEST_TYPES. (default: ACCOUNT)
|
63
|
+
# * <tt>include_transit_times</tt> - whether or not to include estimated transit times. (default: true)
|
64
|
+
#
|
65
|
+
# == Simple Example
|
45
66
|
#
|
46
67
|
# Here is a very simple example. Mix and match the options above to get more accurate rates:
|
47
68
|
#
|
48
69
|
# fedex = Shippinglogic::FedEx.new(key, password, account, meter)
|
49
|
-
# fedex.
|
70
|
+
# rates = fedex.rate(
|
50
71
|
# :shipper_postal_code => "10007",
|
51
72
|
# :shipper_country => "US",
|
52
73
|
# :recipient_postal_code => "75201",
|
53
|
-
# :recipient_country_code => "US"
|
54
|
-
# :
|
74
|
+
# :recipient_country_code => "US",
|
75
|
+
# :package_weight => 24,
|
76
|
+
# :package_length => 12,
|
77
|
+
# :package_width => 12,
|
78
|
+
# :package_height => 12
|
55
79
|
# )
|
56
|
-
|
80
|
+
#
|
81
|
+
# rates.first
|
82
|
+
# #<Shippinglogic::FedEx::Rates::Rate @currency="USD", @name="First Overnight", @cost=#<BigDecimal:19ea290,'0.7001E2',8(8)>,
|
83
|
+
# @deadline=Fri Aug 07 08:00:00 -0400 2009, @type="FIRST_OVERNIGHT", @saturday=false>
|
84
|
+
#
|
85
|
+
# # to show accessor methods
|
86
|
+
# rates.first.name
|
87
|
+
# # => "First Overnight"
|
88
|
+
class Rate < Service
|
57
89
|
# Each rate result is an object of this class
|
58
|
-
class Rate; attr_accessor :name, :type, :saturday, :deadline, :
|
90
|
+
class Rate; attr_accessor :name, :type, :saturday, :deadline, :rate, :currency; end
|
59
91
|
|
60
92
|
VERSION = {:major => 6, :intermediate => 0, :minor => 0}
|
61
|
-
DROP_OFF_TYPES = ["REGULAR_PICKUP", "REQUEST_COURIER", "DROP_BOX", "BUSINESS_SERVICE_CENTER", "STATION"]
|
62
|
-
PACKAGE_TYPES = ["FEDEX_ENVELOPE", "FEDEX_PAK", "FEDEX_BOX", "FEDEX_TUBE", "FEDEX_10KG_BOX", "FEDEX_25KG_BOX", "YOUR_PACKAGING"]
|
63
|
-
SPECIAL_SERVICES = [
|
64
|
-
"APPOINTMENT_DELIVERY", "DANGEROUS_GOODS", "DRY_ICE", "NON_STANDARD_CONTAINER", "PRIORITY_ALERT", "SIGNATURE_OPTION",
|
65
|
-
"FEDEX_FREIGHT", "FEDEX_NATIONAL_FREIGHT", "INSIDE_PICKUP", "INSIDE_DELIVERY", "EXHIBITION", "EXTREME_LENGTH", "FLATBED_TRAILER",
|
66
|
-
"FREIGHT_GUARANTEE", "LIFTGATE_DELIVERY", "LIFTGATE_PICKUP", "LIMITED_ACCESS_DELIVERY", "LIMITED_ACCESS_PICKUP", "PRE_DELIVERY_NOTIFICATION",
|
67
|
-
"PROTECTION_FROM_FREEZING", "REGIONAL_MALL_DELIVERY", "REGIONAL_MALL_PICKUP"
|
68
|
-
]
|
69
|
-
RATE_REQUEST_TYPES = ["ACCOUNT", "LIST", "MULTIWEIGHT"]
|
70
|
-
PAYMENT_TYPES = ["SENDER", "CASH", "CREDIT_CARD"]
|
71
|
-
SERVICE_TYPES = [
|
72
|
-
"INTERNATIONAL_FIRST", "FEDEX_3_DAY_FREIGHT", "STANDARD_OVERNIGHT", "PRIORITY_OVERNIGHT", "FEDEX_GROUND", "INTERNATIONAL_PRIORITY",
|
73
|
-
"FIRST_OVERNIGHT", "FEDEX_1_DAY_FREIGHT", "FEDEX_2_DAY_FREIGHT", "INTERNATIONAL_GROUND", "INTERNATIONAL_ECONOMY_FREIGHT", "INTERNATIONAL_ECONOMY",
|
74
|
-
"FEDEX_1_DAY_FREIGHT_SATURDAY_DELIVERY", "INTERNATIONAL_PRIORITY_FREIGHT", "FEDEX_2_DAY_FREIGHT_SATURDAY_DELIVERY", "FEDEX_2_DAY_SATURDAY_DELIVERY",
|
75
|
-
"FEDEX_3_DAY_FREIGHT_SATURDAY_DELIVERY", "FEDEX_2_DAY", "PRIORITY_OVERNIGHT_SATURDAY_DELIVERY", "INTERNATIONAL_PRIORITY_SATURDAY_DELIVERY",
|
76
|
-
"FEDEX_EXPRESS_SAVER", "GROUND_HOME_DELIVERY"
|
77
|
-
]
|
78
93
|
|
94
|
+
# shipper options
|
79
95
|
attribute :shipper_streets, :string
|
80
96
|
attribute :shipper_city, :string
|
81
97
|
attribute :shipper_state, :string
|
@@ -83,6 +99,7 @@ module Shippinglogic
|
|
83
99
|
attribute :shipper_country, :string
|
84
100
|
attribute :shipper_residential, :boolean, :default => false
|
85
101
|
|
102
|
+
# recipient options
|
86
103
|
attribute :recipient_streets, :string
|
87
104
|
attribute :recipient_city, :string
|
88
105
|
attribute :recipient_state, :string
|
@@ -90,27 +107,40 @@ module Shippinglogic
|
|
90
107
|
attribute :recipient_country, :string
|
91
108
|
attribute :recipient_residential, :boolean, :default => false
|
92
109
|
|
93
|
-
|
110
|
+
# packaging options
|
94
111
|
attribute :packaging_type, :string, :default => "YOUR_PACKAGING"
|
95
|
-
attribute :
|
96
|
-
attribute :
|
97
|
-
attribute :
|
98
|
-
attribute :
|
99
|
-
attribute :
|
100
|
-
attribute :
|
112
|
+
attribute :package_count, :integer, :default => 1
|
113
|
+
attribute :package_weight, :integer
|
114
|
+
attribute :package_weight_units, :string, :default => "LB"
|
115
|
+
attribute :package_length, :integer
|
116
|
+
attribute :package_width, :integer
|
117
|
+
attribute :package_height, :integer
|
118
|
+
attribute :package_dimension_units, :string, :default => "IN"
|
119
|
+
|
120
|
+
# monetary options
|
101
121
|
attribute :currency_type, :string
|
102
|
-
attribute :rate_request_types, :array, :default => ["ACCOUNT"]
|
103
122
|
attribute :insured_value, :big_decimal
|
104
123
|
attribute :payment_type, :string, :default => "SENDER"
|
105
|
-
attribute :payor_account_number, :string
|
106
|
-
attribute :payor_country, :string
|
124
|
+
attribute :payor_account_number, :string, :default => lambda { |shipment| shipment.base.account }
|
125
|
+
attribute :payor_country, :string
|
126
|
+
|
127
|
+
# delivery options
|
128
|
+
attribute :ship_time, :datetime, :default => lambda { |rate| Time.now }
|
129
|
+
attribute :service_type, :string
|
130
|
+
attribute :delivery_deadline, :datetime
|
131
|
+
attribute :dropoff_type, :string, :default => "REGULAR_PICKUP"
|
132
|
+
attribute :special_services_requested, :array
|
133
|
+
|
134
|
+
# misc options
|
135
|
+
attribute :rate_request_types, :array, :default => ["ACCOUNT"]
|
136
|
+
attribute :include_transit_times, :boolean, :default => true
|
107
137
|
|
108
138
|
private
|
109
139
|
def target
|
110
|
-
@target ||=
|
140
|
+
@target ||= parse_response(request(build_request))
|
111
141
|
end
|
112
142
|
|
113
|
-
def
|
143
|
+
def build_request
|
114
144
|
b = builder
|
115
145
|
xml = b.RateRequest(:xmlns => "http://fedex.com/ws/rate/v#{VERSION[:major]}") do
|
116
146
|
build_authentication(b)
|
@@ -119,7 +149,7 @@ module Shippinglogic
|
|
119
149
|
b.SpecialServicesRequested special_services_requested.join(",") if special_services_requested.any?
|
120
150
|
|
121
151
|
b.RequestedShipment do
|
122
|
-
b.ShipTimestamp ship_time.xmlschema
|
152
|
+
b.ShipTimestamp ship_time.xmlschema if ship_time
|
123
153
|
b.ServiceType service_type if service_type
|
124
154
|
b.DropoffType dropoff_type if dropoff_type
|
125
155
|
b.PackagingType packaging_type if packaging_type
|
@@ -127,73 +157,40 @@ module Shippinglogic
|
|
127
157
|
b.Shipper { build_address(b, :shipper) }
|
128
158
|
b.Recipient { build_address(b, :recipient) }
|
129
159
|
b.ShippingChargesPayment do
|
130
|
-
b.PaymentType payment_type
|
131
|
-
|
132
|
-
b.
|
133
|
-
|
134
|
-
b.CountryCode payor_country
|
135
|
-
end
|
160
|
+
b.PaymentType payment_type if payment_type
|
161
|
+
b.Payor do
|
162
|
+
b.AccountNumber payor_account_number if payor_account_number
|
163
|
+
b.CountryCode payor_country if payor_country
|
136
164
|
end
|
137
165
|
end
|
138
|
-
b.RateRequestTypes rate_request_types.join(",")
|
139
|
-
b
|
140
|
-
b.PackageDetail "INDIVIDUAL_PACKAGES"
|
141
|
-
|
142
|
-
packages.each { |package| build_package(b, package) }
|
166
|
+
b.RateRequestTypes rate_request_types.join(",") if rate_request_types
|
167
|
+
build_package(b)
|
143
168
|
end
|
144
169
|
end
|
145
170
|
end
|
146
171
|
|
147
|
-
def
|
148
|
-
b.Address do
|
149
|
-
b.StreetLines send("#{type}_streets") if send("#{type}_streets")
|
150
|
-
b.City send("#{type}_city") if send("#{type}_city")
|
151
|
-
b.StateOrProvinceCode send("#{type}_state") if send("#{type}_state")
|
152
|
-
b.PostalCode send("#{type}_postal_code") if send("#{type}_postal_code")
|
153
|
-
b.CountryCode send("#{type}_country") if send("#{type}_country")
|
154
|
-
b.Residential send("#{type}_residential")
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def build_package(b, package)
|
159
|
-
package.symbolize_keys! if package.respond_to?(:symbolize_keys!)
|
160
|
-
validate_package(package)
|
161
|
-
|
162
|
-
b.RequestedPackages do
|
163
|
-
b.Weight do
|
164
|
-
b.Units package[:weight_units] || "LB"
|
165
|
-
b.Value package[:weight]
|
166
|
-
end
|
167
|
-
|
168
|
-
b.Dimensions do
|
169
|
-
b.Length package[:length]
|
170
|
-
b.Width package[:width]
|
171
|
-
b.Height package[:height]
|
172
|
-
b.Units package[:dimension_units] || "IN"
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def validate_package(package)
|
178
|
-
raise ArgumentError.new("Each package much be in a Hash format") if !package.is_a?(Hash)
|
179
|
-
package.assert_valid_keys(:weight, :weight_units, :length, :height, :width, :dimension_units)
|
180
|
-
end
|
181
|
-
|
182
|
-
def parse_rate_response(response)
|
172
|
+
def parse_response(response)
|
183
173
|
response[:rate_reply_details].collect do |details|
|
184
174
|
shipment_detail = details[:rated_shipment_details].is_a?(Array) ? details[:rated_shipment_details].first : details[:rated_shipment_details]
|
185
175
|
cost = shipment_detail[:shipment_rate_detail][:total_net_charge]
|
176
|
+
deadline = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp])
|
177
|
+
|
178
|
+
next if delivery_deadline && !meets_deadline?(delivery_deadline)
|
186
179
|
|
187
180
|
rate = Rate.new
|
188
181
|
rate.name = details[:service_type].titleize
|
189
182
|
rate.type = details[:service_type]
|
190
183
|
rate.saturday = details[:applied_options] == "SATURDAY_DELIVERY"
|
191
184
|
rate.deadline = details[:delivery_timestamp] && Time.parse(details[:delivery_timestamp])
|
192
|
-
rate.
|
185
|
+
rate.rate = BigDecimal.new(cost[:amount])
|
193
186
|
rate.currency = cost[:currency]
|
194
187
|
rate
|
195
188
|
end
|
196
189
|
end
|
190
|
+
|
191
|
+
def meets_deadline?(delivery_deadline)
|
192
|
+
!deadline || deadline > delivery_deadline
|
193
|
+
end
|
197
194
|
end
|
198
195
|
end
|
199
196
|
end
|