binarylogic-shippinglogic 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
- == Usage
34
+ == Simple tracking example
35
35
 
36
- What's unique about this library is it's usage / syntax. Let me show you...
37
-
38
- === Calls to the web services are lazy
39
-
40
- A simple example where we want to track a FedEx package:
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
- fedex = Shippinglogic::FedEx.new(key, password, account, meter) # all credentials FedEx requires
43
- tracking = fedex.track(:tracking_number => "077973360403984")
53
+ == Calls to the web services are lazy
44
54
 
45
- Now here is why this is cool. Nothing has happened yet. No call has been made to the FedEx servers, etc. We do this because we don't need it yet. You don't need it until you start using the object. Lets continue with our example:
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
- tracking.first # call is made to FedEx and returns the first tracking result in the array
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
- === Flexibility
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
- 1. <b>Getting service rates</b> - See Shippinglogic::Fedex::Rates
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 should be fairly 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.
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
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :patch: 0
3
- :major: 0
4
- :minor: 9
3
+ :major: 1
4
+ :minor: 0
@@ -1,6 +1,9 @@
1
1
  require "shippinglogic/fedex/error"
2
2
  require "shippinglogic/fedex/service"
3
- require "shippinglogic/fedex/rates"
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 rates(attributes = {})
57
- @rates ||= Rates.new(self, attributes)
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
- self.class.attribute_names
67
+ real_class.attribute_names
68
68
  end
69
69
 
70
70
  def attribute_options(name)
71
- self.class.attribute_options(name)
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 :code, :message, :response
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
- self.message = "The response from FedEx was blank."
25
+ add_error("The response from FedEx was blank.")
26
26
  elsif !response.is_a?(Hash)
27
- self.message = "The response from FedEx was malformed and was not in a valid XML format."
27
+ add_error("The response from FedEx was malformed and was not in a valid XML format.")
28
28
  elsif notifications = response[:notifications]
29
- self.code = notifications[:code]
30
- self.message = notifications[:message]
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
- self.code = detail[:"con:error_code"]
33
- self.message = detail[:"con:reason"]
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
- self.message = "There was a problem with your fedex request, and we couldn't locate a specific error message. This means your response " +
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
- error_message = ""
40
- error_message += "(Error: #{code}) " if code
41
- error_message = message
42
-
43
- super(error_message)
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
- # == Accessor methods / options
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
- # * <tt>service_type</tt> - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all
21
- # available services. (default: nil)
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>packages</tt> - an array of packages included in the shipment. This should be an array of hashes with the following keys:
24
- # * <tt>:weight</tt> - the weight
25
- # * <tt>:weight_units</tt> - either LB or KG. (default: LB)
26
- # * <tt>:length</tt> - the length.
27
- # * <tt>:width</tt> - the width.
28
- # * <tt>:height</tt> - the height.
29
- # * <tt>:dimension_units</tt> - either IN or CM. (default: IN)
30
- # * <tt>ship_time</tt> - a Time object representing when you want to ship the package. (default: Time.now)
31
- # * <tt>dropoff_type</tt> - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP)
32
- # * <tt>include_transit_times</tt> - whether or not to include estimated transit times. (default: true)
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: nil)
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
- # === Simple Example
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.rates(
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
- # :packages => [{:weight => 24, :length => 12, :width => 12, :height => 12}]
74
+ # :recipient_country_code => "US",
75
+ # :package_weight => 24,
76
+ # :package_length => 12,
77
+ # :package_width => 12,
78
+ # :package_height => 12
55
79
  # )
56
- class Rates < Service
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, :cost, :currency; end
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
- attribute :service_type, :string
110
+ # packaging options
94
111
  attribute :packaging_type, :string, :default => "YOUR_PACKAGING"
95
- attribute :packages, :array
96
- attribute :ship_time, :datetime, :default => lambda { Time.now }
97
- attribute :dropoff_type, :boolean, :default => "REGULAR_PICKUP"
98
- attribute :include_transit_times, :boolean, :default => true
99
- attribute :delivery_deadline, :datetime
100
- attribute :special_services_requested, :array
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, :default => "US"
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 ||= parse_rate_response(request(build_rate_request))
140
+ @target ||= parse_response(request(build_request))
111
141
  end
112
142
 
113
- def build_rate_request
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
- if payor_account_number && payor_country_code
132
- b.Payor do
133
- b.AccountNumber payor_account_number
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.PackageCount packages.size
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 build_address(b, type)
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.cost = BigDecimal.new(cost[:amount])
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