shippinglogic 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/.document +5 -0
  2. data/CHANGELOG.rdoc +55 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +175 -0
  5. data/Rakefile +37 -0
  6. data/VERSION.yml +5 -0
  7. data/init.rb +1 -0
  8. data/lib/shippinglogic.rb +3 -0
  9. data/lib/shippinglogic/attributes.rb +121 -0
  10. data/lib/shippinglogic/error.rb +22 -0
  11. data/lib/shippinglogic/fedex.rb +84 -0
  12. data/lib/shippinglogic/fedex/cancel.rb +47 -0
  13. data/lib/shippinglogic/fedex/enumerations.rb +348 -0
  14. data/lib/shippinglogic/fedex/error.rb +47 -0
  15. data/lib/shippinglogic/fedex/rate.rb +229 -0
  16. data/lib/shippinglogic/fedex/request.rb +134 -0
  17. data/lib/shippinglogic/fedex/response.rb +72 -0
  18. data/lib/shippinglogic/fedex/service.rb +11 -0
  19. data/lib/shippinglogic/fedex/ship.rb +238 -0
  20. data/lib/shippinglogic/fedex/signature.rb +68 -0
  21. data/lib/shippinglogic/fedex/track.rb +124 -0
  22. data/lib/shippinglogic/proxy.rb +23 -0
  23. data/lib/shippinglogic/service.rb +42 -0
  24. data/lib/shippinglogic/ups.rb +83 -0
  25. data/lib/shippinglogic/ups/cancel.rb +52 -0
  26. data/lib/shippinglogic/ups/enumerations.rb +56 -0
  27. data/lib/shippinglogic/ups/error.rb +42 -0
  28. data/lib/shippinglogic/ups/label.rb +50 -0
  29. data/lib/shippinglogic/ups/rate.rb +228 -0
  30. data/lib/shippinglogic/ups/request.rb +49 -0
  31. data/lib/shippinglogic/ups/response.rb +58 -0
  32. data/lib/shippinglogic/ups/service.rb +11 -0
  33. data/lib/shippinglogic/ups/ship_accept.rb +53 -0
  34. data/lib/shippinglogic/ups/ship_confirm.rb +170 -0
  35. data/lib/shippinglogic/ups/track.rb +118 -0
  36. data/lib/shippinglogic/validation.rb +32 -0
  37. data/shippinglogic.gemspec +120 -0
  38. data/spec/attributes_spec.rb +67 -0
  39. data/spec/config/fedex_credentials.example.yml +4 -0
  40. data/spec/config/ups_credentials.example.yml +3 -0
  41. data/spec/error_spec.rb +43 -0
  42. data/spec/fedex/cancel_spec.rb +10 -0
  43. data/spec/fedex/error_spec.rb +26 -0
  44. data/spec/fedex/rate_spec.rb +87 -0
  45. data/spec/fedex/request_spec.rb +15 -0
  46. data/spec/fedex/responses/blank.xml +0 -0
  47. data/spec/fedex/responses/cancel_not_found.xml +7 -0
  48. data/spec/fedex/responses/failed_authentication.xml +7 -0
  49. data/spec/fedex/responses/malformed.xml +8 -0
  50. data/spec/fedex/responses/rate_defaults.xml +7 -0
  51. data/spec/fedex/responses/rate_insurance.xml +9 -0
  52. data/spec/fedex/responses/rate_no_services.xml +7 -0
  53. data/spec/fedex/responses/rate_non_custom_packaging.xml +7 -0
  54. data/spec/fedex/responses/ship_defaults.xml +7 -0
  55. data/spec/fedex/responses/ship_with_no_signature.xml +7 -0
  56. data/spec/fedex/responses/signature_defaults.xml +7 -0
  57. data/spec/fedex/responses/track_defaults.xml +7 -0
  58. data/spec/fedex/responses/unexpected.xml +1 -0
  59. data/spec/fedex/service_spec.rb +19 -0
  60. data/spec/fedex/ship_spec.rb +37 -0
  61. data/spec/fedex/signature_spec.rb +11 -0
  62. data/spec/fedex/spec_helper.rb +84 -0
  63. data/spec/fedex/track_spec.rb +37 -0
  64. data/spec/fedex_spec.rb +16 -0
  65. data/spec/lib/interceptor.rb +17 -0
  66. data/spec/proxy_spec.rb +42 -0
  67. data/spec/service_spec.rb +23 -0
  68. data/spec/spec_helper.rb +12 -0
  69. data/spec/ups/responses/blank.xml +0 -0
  70. data/spec/ups/responses/track_defaults.xml +2 -0
  71. data/spec/ups/spec_helper.rb +43 -0
  72. data/spec/ups_spec.rb +16 -0
  73. data/spec/validation_spec.rb +49 -0
  74. metadata +163 -0
@@ -0,0 +1,42 @@
1
+ require "httparty"
2
+ require "shippinglogic/proxy"
3
+ require "shippinglogic/attributes"
4
+ require "shippinglogic/validation"
5
+
6
+ module Shippinglogic
7
+ class Service < Proxy
8
+ include Attributes
9
+ include HTTParty
10
+ include Validation
11
+
12
+ attr_accessor :base
13
+
14
+ # Accepts the base service object as a single parameter so that we can access
15
+ # authentication credentials and options.
16
+ def initialize(base, attributes = {})
17
+ self.base = base
18
+ super
19
+ end
20
+
21
+ private
22
+ # Allows the cached response to be reset, specifically when an attribute changes
23
+ def reset_target
24
+ @target = nil
25
+ end
26
+
27
+ # Resets the target before deferring to the +write_attribute+ method as defined by the
28
+ # +Attributes+ module. This keeps +Attributes+ dissociated from any proxy or service specific
29
+ # code, making testing simpler.
30
+ def write_attribute(*args)
31
+ reset_target
32
+ super
33
+ end
34
+
35
+ # For each service you need to overwrite this method. This is where you make the call to the API
36
+ # and do your magic. See the child classes for examples on how to define this method. It is very
37
+ # important that you cache the result into a variable to avoid uneccessary requests.
38
+ def target
39
+ raise NotImplementedError.new("You need to implement a target method that the proxy class can delegate method calls to")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,83 @@
1
+ require "shippinglogic/ups/service"
2
+ require "shippinglogic/ups/cancel"
3
+ require "shippinglogic/ups/rate"
4
+ require "shippinglogic/ups/track"
5
+
6
+ module Shippinglogic
7
+ class UPS
8
+ # A hash representing default the options. If you are using this in a Rails app the best place
9
+ # to modify or change these options is either in an initializer or your specific environment file. Keep
10
+ # in mind that these options can be modified on the instance level when creating an object. See #initialize
11
+ # for more details.
12
+ #
13
+ # === Options
14
+ #
15
+ # * <tt>:test</tt> - this basically tells us which url to use. If set to true we will use the UPS test URL, if false we
16
+ # will use the production URL. If you are using this in a rails app, unless you are in your production environment, this
17
+ # will default to true automatically.
18
+ # * <tt>:test_url</tt> - the test URL for UPS's webservices. (default: https://wwwcie.ups.com:443/ups.app/xml)
19
+ # * <tt>:production_url</tt> - the production URL for UPS's webservices. (default: https://www.ups.com:443/ups.app/xml)
20
+ def self.options
21
+ @options ||= {
22
+ :test => !!(defined?(Rails) && !Rails.env.production?),
23
+ :production_url => "https://www.ups.com:443/ups.app/xml",
24
+ :test_url => "https://wwwcie.ups.com:443/ups.app/xml"
25
+ }
26
+ end
27
+
28
+ attr_accessor :key, :password, :account, :number, :options
29
+
30
+ # Before you can use the UPS web services you need to provide 4 credentials:
31
+ #
32
+ # 1. Your UPS access key
33
+ # 2. Your UPS password
34
+ # 3. Your UPS user ID
35
+ # 4. Your 6-character UPS account number
36
+ #
37
+ #TODO Explain how to acquire those 4 credentials.
38
+ #
39
+ # The last parameter allows you to modify the class options on an instance level. It accepts the
40
+ # same options that the class level method #options accepts. If you don't want to change any of
41
+ # them, don't supply this parameter.
42
+ def initialize(key, password, account, number, options = {})
43
+ self.key = key
44
+ self.password = password
45
+ self.account = account
46
+ self.number = number
47
+ self.options = self.class.options.merge(options)
48
+ end
49
+
50
+ # A convenience method for accessing the endpoint URL for the UPS API.
51
+ def url
52
+ options[:test] ? options[:test_url] : options[:production_url]
53
+ end
54
+
55
+ def cancel(attributes = {})
56
+ @cancel ||= Cancel.new(self, attributes)
57
+ end
58
+
59
+ def rate(attributes = {})
60
+ @rate ||= Rate.new(self, attributes)
61
+ end
62
+
63
+ def ship_confirm(attributes = {})
64
+ @ship_confirm ||= ShipConfirm.new(self, attributes)
65
+ end
66
+
67
+ def ship_accept(attributes = {})
68
+ @ship_accept ||= ShipAccept.new(self, attributes)
69
+ end
70
+
71
+ def ship(attributes = {})
72
+ @ship ||= ship_accept(:digest => ship_confirm(attributes).digest)
73
+ end
74
+
75
+ def track(attributes = {})
76
+ @track ||= Track.new(self, attributes)
77
+ end
78
+
79
+ def label(attributes = {})
80
+ @label ||= Label.new(self, attributes)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,52 @@
1
+ module Shippinglogic
2
+ class UPS
3
+ # An interface to the shipment canceling service provided by UPS. Allows you to cancel a shipment
4
+ #
5
+ # == Accessor methods / options
6
+ #
7
+ # * <tt>tracking_number</tt> - the tracking number
8
+ #
9
+ # === Simple Example
10
+ #
11
+ # ups = Shippinglogic::UPS.new(key, password, account)
12
+ # cancel = ups.cancel(:tracking_number => "my number")
13
+ # cancel.perform
14
+ # # => true
15
+ class Cancel < Service
16
+ def self.path
17
+ "/Void"
18
+ end
19
+
20
+ attribute :tracking_number, :string
21
+
22
+ # Our services are set up as a proxy. We need to access the underlying object, to trigger the request
23
+ # to UPS. 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
+ build_authentication(b)
39
+ b.instruct!
40
+
41
+ b.VoidShipmentRequest do
42
+ b.Request do
43
+ b.RequestAction "1"
44
+ end
45
+
46
+ #TODO Determine whether tracking numbers are valid shipment identification numbers.
47
+ b.ShipmentIdentificationNumber tracking_number
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,56 @@
1
+ module Shippinglogic
2
+ class UPS
3
+ # This module contains the various enumerations that UPS uses for its various options. When describing
4
+ # service options sometimes the docs will specify that the option must be an item in one of these arrays.
5
+ # You can also use these to build drop down options.
6
+ #
7
+ # Lastly, if you want to make these user friendly use a string inflector (humanize or titlize).
8
+ module Enumerations
9
+ # packaging options
10
+ PACKAGING_TYPES = {
11
+ "00" => "UNKNOWN",
12
+ "01" => "UPS Letter",
13
+ "02" => "Package",
14
+ "03" => "Tube",
15
+ "04" => "Pak",
16
+ "21" => "Express Box",
17
+ "24" => "25KG Box",
18
+ "25" => "10KG Box",
19
+ "30" => "Pallet",
20
+ "2a" => "Small Express Box",
21
+ "2b" => "Medium Express Box",
22
+ "2c" => "Large Express Box"
23
+ }
24
+
25
+ # delivery options
26
+ DROPOFF_TYPES = {
27
+ "01" => "Daily Pickup",
28
+ "03" => "Customer Counter",
29
+ "06" => "One Time Pickup",
30
+ "07" => "On Call Air",
31
+ "11" => "Suggested Retail Rates",
32
+ "19" => "Letter Center",
33
+ "20" => "Air Service Center"
34
+ }
35
+ SERVICE_TYPES = {
36
+ "01" => "Next Day Air",
37
+ "02" => "2nd Day Air",
38
+ "03" => "Ground",
39
+ "07" => "Worldwide Express",
40
+ "08" => "Worldwide Expedited",
41
+ "11" => "Standard",
42
+ "12" => "3 Day Select",
43
+ "13" => "Next Day Air Saver",
44
+ "14" => "Next Day Air Early AM",
45
+ "54" => "Worldwide Express Plus",
46
+ "59" => "2nd Day Air AM",
47
+ "65" => "Saver",
48
+ "82" => "UPS Today Standard",
49
+ "83" => "UPS Today Dedicated Courier",
50
+ "84" => "UPS Today Intercity",
51
+ "85" => "UPS Today Express",
52
+ "86" => "UPS Today Express Saver"
53
+ }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ module Shippinglogic
2
+ class UPS
3
+ # If UPS responds with an error, we try our best to pull the pertinent information out of that
4
+ # response and raise it with this object. Any time UPS says there is a problem an object of this
5
+ # class will be raised.
6
+ #
7
+ # === Tip
8
+ #
9
+ # If you want to see the raw request / respose catch the error object and call the request / response method. Ex:
10
+ #
11
+ # begin
12
+ # # my UPS code
13
+ # rescue Shippinglogic::UPS::Error => e
14
+ # # do whatever you want here, just do:
15
+ # # e.request
16
+ # # e.response
17
+ # # to get the raw response from UPS
18
+ # end
19
+ class Error < Shippinglogic::Error
20
+ def initialize(request, response)
21
+ super
22
+
23
+ if response.blank?
24
+ add_error("The response from UPS was blank.")
25
+ elsif !response.is_a?(Hash)
26
+ add_error("The response from UPS was malformed and was not in a valid XML format.")
27
+ elsif errors = response.fetch(:response, {})[:error]
28
+ errors = errors.is_a?(Array) ? errors : [errors]
29
+ errors.delete_if { |error| Response::SUCCESSFUL_SEVERITIES.include?(error[:error_severity]) }
30
+ errors.each { |error| add_error(error[:error_description], error[:error_code]) }
31
+ else
32
+ add_error(
33
+ "There was a problem with your UPS request, and we couldn't locate a specific error message. This means your response " +
34
+ "was in an unexpected format. You might try glancing at the raw response by using the 'response' method on this error object."
35
+ )
36
+ end
37
+
38
+ super(errors.collect { |error| error[:message] }.join(", "))
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ require "base64"
2
+
3
+ module Shippinglogic
4
+ class UPS
5
+ class Label < Service
6
+ def self.path
7
+ "/LabelRecovery"
8
+ end
9
+
10
+ attribute :tracking_number, :string
11
+
12
+ private
13
+ def target
14
+ @target ||= parse_response(request(build_request))
15
+ end
16
+
17
+ # Just building some XML to send off to USP using our various options
18
+ def build_request
19
+ b = builder
20
+ build_authentication(b)
21
+ b.instruct!
22
+
23
+ b.LabelRecoveryRequest do
24
+ b.Request do
25
+ b.RequestAction "LabelRecovery"
26
+ end
27
+
28
+ b.LabelSpecification do
29
+ b.LabelImageFormat do
30
+ b.Code "GIF"
31
+ end
32
+ end
33
+
34
+ b.Translate do
35
+ b.LanguageCode "eng"
36
+ b.DialectCode "US"
37
+ b.Code "01"
38
+ end
39
+
40
+ b.TrackingNumber tracking_number
41
+ end
42
+ end
43
+
44
+ def parse_response(response)
45
+ return unless details = response.fetch(:label_results, {})[:label_image]
46
+ Base64.decode64(details[:graphic_image])
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,228 @@
1
+ module Shippinglogic
2
+ class UPS
3
+ # An interface to the rate services provided by UPS. Allows you to get an array of rates from UPS for a shipment,
4
+ # or a single rate for a specific service.
5
+ #
6
+ # == Options
7
+ # === Shipper options
8
+ #
9
+ # * <tt>shipper_name</tt> - name of the shipper.
10
+ # * <tt>shipper_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
11
+ # * <tt>shipper_city</tt> - city part of the address.
12
+ # * <tt>shipper_state_</tt> - state part of the address, use state abreviations.
13
+ # * <tt>shipper_postal_code</tt> - postal code part of the address. Ex: zip for the US.
14
+ # * <tt>shipper_country</tt> - country code part of the address. UPS expects abbreviations, but Shippinglogic will convert full names to abbreviations for you.
15
+ #
16
+ # === Recipient options
17
+ #
18
+ # * <tt>recipient_name</tt> - name of the recipient.
19
+ # * <tt>recipient_streets</tt> - street part of the address, separate multiple streets with a new line, dont include blank lines.
20
+ # * <tt>recipient_city</tt> - city part of the address.
21
+ # * <tt>recipient_state</tt> - state part of the address, use state abreviations.
22
+ # * <tt>recipient_postal_code</tt> - postal code part of the address. Ex: zip for the US.
23
+ # * <tt>recipient_country</tt> - country code part of the address. UPS expects abbreviations, but Shippinglogic will convert full names to abbreviations for you.
24
+ # * <tt>recipient_residential</tt> - a boolean value representing if the address is redential or not (default: false)
25
+ #
26
+ # === Packaging options
27
+ #
28
+ # One thing to note is that UPS does support multiple package shipments. The problem is that all of the packages must be identical.
29
+ # UPS specifically notes in their documentation that mutiple package specifications are not allowed. So your only option for a
30
+ # multi package shipment is to increase the package_count option and keep the dimensions and weight the same for all packages. Then again,
31
+ # the documentation for the UPS web services is terrible, so I could be wrong. Any tests I tried resulted in an error though.
32
+ #
33
+ # * <tt>packaging_type</tt> - one of PACKAGE_TYPES. (default: YOUR_PACKAGING)
34
+ # * <tt>package_count</tt> - the number of packages in your shipment. (default: 1)
35
+ # * <tt>package_weight</tt> - a single packages weight.
36
+ # * <tt>package_weight_units</tt> - either LB or KG. (default: LB)
37
+ # * <tt>package_length</tt> - a single packages length, only required if using YOUR_PACKAGING for packaging_type.
38
+ # * <tt>package_width</tt> - a single packages width, only required if using YOUR_PACKAGING for packaging_type.
39
+ # * <tt>package_height</tt> - a single packages height, only required if using YOUR_PACKAGING for packaging_type.
40
+ # * <tt>package_dimension_units</tt> - either IN or CM. (default: IN)
41
+ #
42
+ # === Monetary options
43
+ #
44
+ # * <tt>currency_type</tt> - the type of currency. (default: nil, because UPS will default to your account preferences)
45
+ # * <tt>insured_value</tt> - the value you want to insure, if any. (default: nil)
46
+ # * <tt>payor_account_number</tt> - if the account paying for this ship is different than the account you specified then
47
+ # you can specify that here. (default: your account number)
48
+ #
49
+ # === Delivery options
50
+ #
51
+ # * <tt>service_type</tt> - one of SERVICE_TYPES, this is optional, leave this blank if you want a list of all
52
+ # available services. (default: nil)
53
+ # * <tt>delivery_deadline</tt> - whether or not to include estimated transit times. (default: true)
54
+ # * <tt>dropoff_type</tt> - one of DROP_OFF_TYPES. (default: REGULAR_PICKUP)
55
+ #
56
+ # === Misc options
57
+ #
58
+ # * <tt>documents_only</tt> - whether the package consists of only documents (default: false)
59
+ #
60
+ # == Simple Example
61
+ #
62
+ # Here is a very simple example. Mix and match the options above to get more accurate rates:
63
+ #
64
+ # ups = Shippinglogic::UPS.new(key, password, account)
65
+ # rates = ups.rate(
66
+ # :shipper_postal_code => "10007",
67
+ # :shipper_country => "US",
68
+ # :recipient_postal_code => "75201",
69
+ # :recipient_country_code => "US",
70
+ # :package_weight => 24,
71
+ # :package_length => 12,
72
+ # :package_width => 12,
73
+ # :package_height => 12
74
+ # )
75
+ #
76
+ # rates.first
77
+ # #<Shippinglogic::UPS::Rate::Service:0x10354d108 @currency="USD", @speed=nil,
78
+ # @rate=#<BigDecimal:10353ac10,'0.1885E2',18(18)>, @type="Ground", @name="Ground">
79
+ #
80
+ # # to show accessor methods
81
+ # rates.first.name
82
+ # # => "Ground"
83
+ class Rate < Service
84
+ def self.path
85
+ "/Rate"
86
+ end
87
+
88
+ # Each rate result is an object of this class
89
+ class Service; attr_accessor :name, :type, :speed, :rate, :currency; end
90
+
91
+ # shipper options
92
+ attribute :shipper_name, :string
93
+ attribute :shipper_streets, :string
94
+ attribute :shipper_city, :string
95
+ attribute :shipper_state, :string
96
+ attribute :shipper_postal_code, :string
97
+ attribute :shipper_country, :string, :modifier => :country_code
98
+
99
+ # recipient options
100
+ attribute :recipient_name, :string
101
+ attribute :recipient_streets, :string
102
+ attribute :recipient_city, :string
103
+ attribute :recipient_state, :string
104
+ attribute :recipient_postal_code, :string
105
+ attribute :recipient_country, :string, :modifier => :country_code
106
+ attribute :recipient_residential, :boolean, :default => false
107
+
108
+ # packaging options
109
+ attribute :packaging_type, :string, :default => "00"
110
+ attribute :package_count, :integer, :default => 1
111
+ attribute :package_weight, :float
112
+ attribute :package_weight_units, :string, :default => "LBS"
113
+ attribute :package_length, :float
114
+ attribute :package_width, :float
115
+ attribute :package_height, :float
116
+ attribute :package_dimension_units, :string, :default => "IN"
117
+
118
+ # monetary options
119
+ attribute :currency_type, :string
120
+ attribute :insured_value, :decimal
121
+ attribute :payor_account_number, :string, :default => lambda { |shipment| shipment.base.account }
122
+
123
+ # delivery options
124
+ attribute :service_type, :string
125
+ attribute :dropoff_type, :string, :default => "01"
126
+ attribute :saturday, :boolean, :default => false
127
+
128
+ # misc options
129
+ attribute :documents_only, :boolean, :default => false
130
+
131
+ private
132
+ def target
133
+ @target ||= parse_response(request(build_request))
134
+ end
135
+
136
+ def build_request
137
+ b = builder
138
+ build_authentication(b)
139
+ b.instruct!
140
+
141
+ b.RatingServiceSelectionRequest do
142
+ b.Request do
143
+ b.RequestAction "Rate"
144
+ b.RequestOption service_type ? "Rate" : "Shop"
145
+ end
146
+
147
+ b.PickupType do
148
+ b.Code dropoff_type
149
+ end
150
+
151
+ b.Shipment do
152
+ b.Shipper do
153
+ b.Name shipper_name
154
+ b.ShipperNumber payor_account_number
155
+ build_address(b, :shipper)
156
+ end
157
+
158
+ b.ShipTo do
159
+ b.CompanyName recipient_name
160
+ build_address(b, :recipient)
161
+ end
162
+
163
+ if service_type
164
+ b.Service do
165
+ b.Code service_type
166
+ end
167
+ end
168
+
169
+ b.DocumentsOnly if documents_only
170
+
171
+ package_count.times do |i|
172
+ b.Package do
173
+ b.PackagingType do
174
+ b.Code packaging_type
175
+ end
176
+
177
+ b.Dimensions do
178
+ b.UnitOfMeasurement do
179
+ b.Code package_dimension_units
180
+ end
181
+
182
+ b.Length "%.2f" % package_length
183
+ b.Width "%.2f" % package_width
184
+ b.Height "%.2f" % package_height
185
+ end
186
+
187
+ b.PackageWeight do
188
+ b.UnitOfMeasurement do
189
+ b.Code package_weight_units
190
+ end
191
+
192
+ b.Weight "%.1f" % package_weight
193
+ end
194
+
195
+ b.PackageServiceOptions do
196
+ if insured_value
197
+ b.InsuredValue do
198
+ b.MonetaryValue insured_value
199
+ b.CurrencyCode currency_type
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ b.ShipmentServiceOptions do
207
+ b.SaturdayDelivery if saturday
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ def parse_response(response)
214
+ return [] if !response[:rated_shipment]
215
+
216
+ response[:rated_shipment].collect do |details|
217
+ service = Service.new
218
+ service.name = Enumerations::SERVICE_TYPES[details[:service][:code]]
219
+ service.type = service.name
220
+ service.speed = (days = details[:guaranteed_days_to_delivery]) && (days.to_i * 86400)
221
+ service.rate = BigDecimal.new(details[:total_charges][:monetary_value])
222
+ service.currency = details[:total_charges][:currency_code]
223
+ service
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end