active_shipping 1.0.0.pre4 → 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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +17 -0
- data/CONTRIBUTING.md +2 -2
- data/Gemfile.activesupport32 +1 -0
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/active_shipping.gemspec +2 -2
- data/lib/active_shipping.rb +2 -3
- data/lib/active_shipping/carriers.rb +1 -0
- data/lib/active_shipping/carriers/canada_post_pws.rb +281 -266
- data/lib/active_shipping/carriers/correios.rb +285 -0
- data/lib/active_shipping/carriers/fedex.rb +205 -199
- data/lib/active_shipping/carriers/stamps.rb +218 -219
- data/lib/active_shipping/carriers/ups.rb +287 -48
- data/lib/active_shipping/carriers/usps.rb +3 -3
- data/lib/active_shipping/delivery_date_estimate.rb +21 -0
- data/lib/active_shipping/delivery_date_estimates_response.rb +11 -0
- data/lib/active_shipping/errors.rb +20 -1
- data/lib/active_shipping/location.rb +0 -5
- data/lib/active_shipping/response.rb +0 -15
- data/lib/active_shipping/version.rb +1 -1
- data/test/credentials.yml +11 -3
- data/test/fixtures/xml/correios/book_response.xml +13 -0
- data/test/fixtures/xml/correios/book_response_invalid.xml +13 -0
- data/test/fixtures/xml/correios/clothes_response.xml +43 -0
- data/test/fixtures/xml/correios/poster_response.xml +23 -0
- data/test/fixtures/xml/correios/shoes_response.xml +43 -0
- data/test/fixtures/xml/fedex/tracking_request.xml +13 -11
- data/test/fixtures/xml/fedex/tracking_response_bad_tracking_number.xml +20 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_door.xml +254 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_at_facility.xml +403 -0
- data/test/fixtures/xml/fedex/tracking_response_delivered_with_signature.xml +269 -0
- data/test/fixtures/xml/fedex/tracking_response_in_transit.xml +127 -0
- data/test/fixtures/xml/fedex/tracking_response_multiple_results.xml +100 -0
- data/test/fixtures/xml/fedex/tracking_response_not_found.xml +52 -0
- data/test/fixtures/xml/fedex/tracking_response_shipment_exception.xml +209 -0
- data/test/fixtures/xml/ups/delivery_dates_response.xml +140 -0
- data/test/fixtures/xml/ups/package_exceeds_maximum_length.xml +12 -0
- data/test/fixtures/xml/ups/rate_single_service.xml +54 -0
- data/test/fixtures/xml/ups/rescheduled_shipment.xml +204 -0
- data/test/fixtures/xml/ups/test_real_home_as_residential_destination_response.xml +290 -1
- data/test/fixtures/xml/usps/delivered_extended_tracking_response.xml +11 -0
- data/test/remote/canada_post_pws_platform_test.rb +35 -22
- data/test/remote/canada_post_pws_test.rb +32 -40
- data/test/remote/correios_test.rb +83 -0
- data/test/remote/fedex_test.rb +95 -13
- data/test/remote/stamps_test.rb +1 -0
- data/test/remote/ups_test.rb +77 -40
- data/test/remote/usps_test.rb +13 -1
- data/test/test_helper.rb +12 -2
- data/test/unit/carriers/canada_post_pws_rating_test.rb +66 -59
- data/test/unit/carriers/canada_post_pws_shipping_test.rb +34 -23
- data/test/unit/carriers/correios_test.rb +244 -0
- data/test/unit/carriers/fedex_test.rb +161 -156
- data/test/unit/carriers/ups_test.rb +193 -1
- data/test/unit/carriers/usps_test.rb +14 -0
- data/test/unit/location_test.rb +0 -10
- metadata +63 -46
- metadata.gz.sig +0 -0
- data/lib/vendor/test_helper.rb +0 -6
- data/lib/vendor/xml_node/README +0 -36
- data/lib/vendor/xml_node/Rakefile +0 -21
- data/lib/vendor/xml_node/benchmark/bench_generation.rb +0 -30
- data/lib/vendor/xml_node/init.rb +0 -1
- data/lib/vendor/xml_node/lib/xml_node.rb +0 -221
- data/lib/vendor/xml_node/test/test_generating.rb +0 -89
- data/lib/vendor/xml_node/test/test_parsing.rb +0 -40
- data/test/fixtures/xml/fedex/tracking_response.xml +0 -151
- data/test/fixtures/xml/fedex/tracking_response_empty_destination.xml +0 -76
- data/test/fixtures/xml/fedex/tracking_response_no_destination.xml +0 -139
- data/test/fixtures/xml/fedex/tracking_response_no_ship_time.xml +0 -150
- data/test/fixtures/xml/fedex/tracking_response_with_estimated_delivery_date.xml +0 -95
- data/test/fixtures/xml/fedex/tracking_response_with_shipper_address.xml +0 -71
| @@ -15,7 +15,8 @@ module ActiveShipping | |
| 15 15 | 
             
                  :rates => 'ups.app/xml/Rate',
         | 
| 16 16 | 
             
                  :track => 'ups.app/xml/Track',
         | 
| 17 17 | 
             
                  :ship_confirm => 'ups.app/xml/ShipConfirm',
         | 
| 18 | 
            -
                  :ship_accept => 'ups.app/xml/ShipAccept'
         | 
| 18 | 
            +
                  :ship_accept => 'ups.app/xml/ShipAccept',
         | 
| 19 | 
            +
                  :delivery_dates =>  'ups.app/xml/TimeInTransit'
         | 
| 19 20 | 
             
                }
         | 
| 20 21 |  | 
| 21 22 | 
             
                PICKUP_CODES = HashWithIndifferentAccess.new(
         | 
| @@ -102,6 +103,22 @@ module ActiveShipping | |
| 102 103 |  | 
| 103 104 | 
             
                IMPERIAL_COUNTRIES = %w(US LR MM)
         | 
| 104 105 |  | 
| 106 | 
            +
                DEFAULT_SERVICE_NAME_TO_CODE = Hash[UPS::DEFAULT_SERVICES.to_a.map(&:reverse)]
         | 
| 107 | 
            +
                DEFAULT_SERVICE_NAME_TO_CODE['UPS 2nd Day Air'] = "02"
         | 
| 108 | 
            +
                DEFAULT_SERVICE_NAME_TO_CODE['UPS 3 Day Select'] = "12"
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                SHIPMENT_DELIVERY_CONFIRMATION_CODES = {
         | 
| 111 | 
            +
                  delivery_confirmation_signature_required: 1,
         | 
| 112 | 
            +
                  delivery_confirmation_adult_signature_required: 2
         | 
| 113 | 
            +
                }
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                PACKAGE_DELIVERY_CONFIRMATION_CODES = {
         | 
| 116 | 
            +
                  delivery_confirmation: 1,
         | 
| 117 | 
            +
                  delivery_confirmation_signature_required: 2,
         | 
| 118 | 
            +
                  delivery_confirmation_adult_signature_required: 3,
         | 
| 119 | 
            +
                  usps_delivery_confirmation: 4
         | 
| 120 | 
            +
                }
         | 
| 121 | 
            +
             | 
| 105 122 | 
             
                def requirements
         | 
| 106 123 | 
             
                  [:key, :login, :password]
         | 
| 107 124 | 
             
                end
         | 
| @@ -144,8 +161,8 @@ module ActiveShipping | |
| 144 161 | 
             
                    xml = parse_ship_confirm(confirm_response)
         | 
| 145 162 | 
             
                    success = response_success?(xml)
         | 
| 146 163 | 
             
                    message = response_message(xml)
         | 
| 147 | 
            -
                    digest  = response_digest(xml)
         | 
| 148 164 | 
             
                    raise message unless success
         | 
| 165 | 
            +
                    digest  = response_digest(xml)
         | 
| 149 166 |  | 
| 150 167 | 
             
                    # STEP 2: Accept. Use shipment digest in first response to get the actual label.
         | 
| 151 168 | 
             
                    accept_request = build_accept_request(digest, options)
         | 
| @@ -164,6 +181,16 @@ module ActiveShipping | |
| 164 181 | 
             
                  end
         | 
| 165 182 | 
             
                end
         | 
| 166 183 |  | 
| 184 | 
            +
                def get_delivery_date_estimates(origin, destination, packages, pickup_date=Date.current, options = {})
         | 
| 185 | 
            +
                  origin, destination = upsified_location(origin), upsified_location(destination)
         | 
| 186 | 
            +
                  options = @options.merge(options)
         | 
| 187 | 
            +
                  packages = Array(packages)
         | 
| 188 | 
            +
                  access_request = build_access_request
         | 
| 189 | 
            +
                  dates_request = build_delivery_dates_request(origin, destination, packages, pickup_date, options)
         | 
| 190 | 
            +
                  response = commit(:delivery_dates, save_request(access_request + dates_request), (options[:test] || false))
         | 
| 191 | 
            +
                  parse_delivery_dates_response(origin, destination, packages, response, options)
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 167 194 | 
             
                protected
         | 
| 168 195 |  | 
| 169 196 | 
             
                def upsified_location(location)
         | 
| @@ -182,7 +209,7 @@ module ActiveShipping | |
| 182 209 | 
             
                  xml_builder = Nokogiri::XML::Builder.new do |xml|
         | 
| 183 210 | 
             
                    xml.AccessRequest do
         | 
| 184 211 | 
             
                      xml.AccessLicenseNumber(@options[:key])
         | 
| 185 | 
            -
                      xml.UserId(@options[: | 
| 212 | 
            +
                      xml.UserId(@options[:login])
         | 
| 186 213 | 
             
                      xml.Password(@options[:password])
         | 
| 187 214 | 
             
                    end
         | 
| 188 215 | 
             
                  end
         | 
| @@ -194,9 +221,7 @@ module ActiveShipping | |
| 194 221 | 
             
                    xml.RatingServiceSelectionRequest do
         | 
| 195 222 | 
             
                      xml.Request do
         | 
| 196 223 | 
             
                        xml.RequestAction('Rate')
         | 
| 197 | 
            -
                        xml.RequestOption('Shop')
         | 
| 198 | 
            -
                        # not implemented: 'Rate' RequestOption to specify a single service query
         | 
| 199 | 
            -
                        # xml.RequestOption((options[:service].nil? or options[:service] == :all) ? 'Shop' : 'Rate')
         | 
| 224 | 
            +
                        xml.RequestOption((options[:service].nil?) ? 'Shop' : 'Rate')
         | 
| 200 225 | 
             
                      end
         | 
| 201 226 |  | 
| 202 227 | 
             
                      pickup_type = options[:pickup_type] || :daily_pickup
         | 
| @@ -226,13 +251,19 @@ module ActiveShipping | |
| 226 251 | 
             
                        #                   * Shipment/AlternateDeliveryTime element
         | 
| 227 252 | 
             
                        #                   * Shipment/DocumentsOnly element
         | 
| 228 253 |  | 
| 254 | 
            +
                        unless options[:service].nil?
         | 
| 255 | 
            +
                          xml.Service do
         | 
| 256 | 
            +
                            xml.Code(options[:service])
         | 
| 257 | 
            +
                          end
         | 
| 258 | 
            +
                        end
         | 
| 259 | 
            +
             | 
| 229 260 | 
             
                        Array(packages).each do |package|
         | 
| 230 261 | 
             
                          options[:imperial] ||= IMPERIAL_COUNTRIES.include?(origin.country_code(:alpha2))
         | 
| 231 262 | 
             
                          build_package_node(xml, package, options)
         | 
| 232 263 | 
             
                        end
         | 
| 233 264 |  | 
| 234 265 | 
             
                        # not implemented:  * Shipment/ShipmentServiceOptions element
         | 
| 235 | 
            -
                        if options[: | 
| 266 | 
            +
                        if options[:negotiated_rates]
         | 
| 236 267 | 
             
                          xml.RateInformation do
         | 
| 237 268 | 
             
                            xml.NegotiatedRatesIndicator
         | 
| 238 269 | 
             
                          end
         | 
| @@ -251,18 +282,34 @@ module ActiveShipping | |
| 251 282 | 
             
                # * shipper: who is sending the package and where it should be returned
         | 
| 252 283 | 
             
                #     if it is undeliverable.
         | 
| 253 284 | 
             
                # * ship_from: where the package is picked up.
         | 
| 254 | 
            -
                # * service_code: default to ' | 
| 255 | 
            -
                # * service_descriptor: default to 'Next Day Air Early AM'
         | 
| 285 | 
            +
                # * service_code: default to '03'
         | 
| 256 286 | 
             
                # * saturday_delivery: any truthy value causes this element to exist
         | 
| 257 287 | 
             
                # * optional_processing: 'validate' (blank) or 'nonvalidate' or blank
         | 
| 258 | 
            -
                #
         | 
| 259 | 
            -
                 | 
| 260 | 
            -
             | 
| 261 | 
            -
             | 
| 288 | 
            +
                # * paperless_invoice: set to truthy if using paperless invoice to ship internationally
         | 
| 289 | 
            +
                # * terms_of_shipment: used with paperless invoice to specify who pays duties and taxes
         | 
| 290 | 
            +
                # * reference_numbers: Array of hashes with :value => a reference number value and optionally :code => reference number type
         | 
| 291 | 
            +
                # * prepay: if truthy the shipper will be bill immediatly. Otherwise the shipper is billed when the label is used.
         | 
| 292 | 
            +
                # * negotiated_rates: if truthy negotiated rates will be requested from ups. Only valid if shipper account has negotiated rates.
         | 
| 293 | 
            +
                # * delivery_confirmation: Can be set to any key from SHIPMENT_DELIVERY_CONFIRMATION_CODES. Can also be set on package level via package.options
         | 
| 294 | 
            +
                def build_shipment_request(origin, destination, packages, options={})
         | 
| 295 | 
            +
                  packages = Array(packages)
         | 
| 296 | 
            +
                  options[:international] = origin.country.name != destination.country.name
         | 
| 297 | 
            +
                  options[:imperial] ||= IMPERIAL_COUNTRIES.include?(origin.country_code(:alpha2))
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                  if allow_package_level_reference_numbers(origin, destination)
         | 
| 300 | 
            +
                    if options[:reference_numbers]
         | 
| 301 | 
            +
                      packages.each do |package|
         | 
| 302 | 
            +
                        package.options[:reference_numbers] = options[:reference_numbers]
         | 
| 303 | 
            +
                      end
         | 
| 304 | 
            +
                    end
         | 
| 305 | 
            +
                    options[:reference_numbers] = []
         | 
| 306 | 
            +
                  end
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                  handle_delivery_confirmation_options(origin, destination, packages, options)
         | 
| 309 | 
            +
             | 
| 262 310 | 
             
                  xml_builder = Nokogiri::XML::Builder.new do |xml|
         | 
| 263 311 | 
             
                    xml.ShipmentConfirmRequest do
         | 
| 264 312 | 
             
                      xml.Request do
         | 
| 265 | 
            -
                        # Required element and the text must be "ShipConfirm"
         | 
| 266 313 | 
             
                        xml.RequestAction('ShipConfirm')
         | 
| 267 314 | 
             
                        # Required element cotnrols level of address validation.
         | 
| 268 315 | 
             
                        xml.RequestOption(options[:optional_processing] || 'validate')
         | 
| @@ -275,54 +322,96 @@ module ActiveShipping | |
| 275 322 | 
             
                      end
         | 
| 276 323 |  | 
| 277 324 | 
             
                      xml.Shipment do
         | 
| 278 | 
            -
                        # Required element.
         | 
| 279 325 | 
             
                        xml.Service do
         | 
| 280 | 
            -
                          xml.Code(options[:service_code] || ' | 
| 281 | 
            -
                          xml.Description(options[:service_description] || 'Next Day Air Early AM')
         | 
| 326 | 
            +
                          xml.Code(options[:service_code] || '03')
         | 
| 282 327 | 
             
                        end
         | 
| 283 328 |  | 
| 284 | 
            -
                         | 
| 285 | 
            -
                        build_location_node(xml, ' | 
| 329 | 
            +
                        build_location_node(xml, 'ShipTo', destination, options)
         | 
| 330 | 
            +
                        build_location_node(xml, 'ShipFrom', origin, options)
         | 
| 286 331 | 
             
                        # Required element. The company whose account is responsible for the label(s).
         | 
| 287 | 
            -
                        build_location_node(xml, 'Shipper', options[:shipper] || origin,  | 
| 288 | 
            -
                        # Required if pickup is different different from shipper's address.
         | 
| 289 | 
            -
                        build_location_node(xml, 'ShipFrom', options[:ship_from], {}) if options[:ship_from]
         | 
| 332 | 
            +
                        build_location_node(xml, 'Shipper', options[:shipper] || origin, options)
         | 
| 290 333 |  | 
| 291 | 
            -
                        # Optional.
         | 
| 292 334 | 
             
                        if options[:saturday_delivery]
         | 
| 293 335 | 
             
                          xml.ShipmentServiceOptions do
         | 
| 294 336 | 
             
                            xml.SaturdayDelivery
         | 
| 295 337 | 
             
                          end
         | 
| 296 338 | 
             
                        end
         | 
| 297 339 |  | 
| 298 | 
            -
                        # Optional.
         | 
| 299 340 | 
             
                        if options[:origin_account]
         | 
| 300 341 | 
             
                          xml.RateInformation do
         | 
| 301 342 | 
             
                            xml.NegotiatedRatesIndicator
         | 
| 302 343 | 
             
                          end
         | 
| 303 344 | 
             
                        end
         | 
| 304 345 |  | 
| 305 | 
            -
                         | 
| 306 | 
            -
                        if options[:shipment] && options[:shipment][:reference_number]
         | 
| 346 | 
            +
                        Array(options[:reference_numbers]).each do |reference_num_info|
         | 
| 307 347 | 
             
                          xml.ReferenceNumber do
         | 
| 308 | 
            -
                            xml.Code( | 
| 309 | 
            -
                            xml.Value( | 
| 348 | 
            +
                            xml.Code(reference_num_info[:code] || "")
         | 
| 349 | 
            +
                            xml.Value(reference_num_info[:value])
         | 
| 310 350 | 
             
                          end
         | 
| 311 351 | 
             
                        end
         | 
| 312 352 |  | 
| 313 | 
            -
                         | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
                               | 
| 353 | 
            +
                        if options[:prepay]
         | 
| 354 | 
            +
                          xml.PaymentInformation do
         | 
| 355 | 
            +
                            xml.Prepaid do
         | 
| 356 | 
            +
                              xml.BillShipper do
         | 
| 357 | 
            +
                                xml.AccountNumber(options[:origin_account])
         | 
| 358 | 
            +
                              end
         | 
| 319 359 | 
             
                            end
         | 
| 320 360 | 
             
                          end
         | 
| 361 | 
            +
                        else
         | 
| 362 | 
            +
                          xml.ItemizedPaymentInformation do
         | 
| 363 | 
            +
                            xml.ShipmentCharge do
         | 
| 364 | 
            +
                              # Type '01' means 'Transportation'
         | 
| 365 | 
            +
                              # This node specifies who will be billed for transportation.
         | 
| 366 | 
            +
                              xml.Type('01')
         | 
| 367 | 
            +
                              xml.BillShipper do
         | 
| 368 | 
            +
                                xml.AccountNumber(options[:origin_account])
         | 
| 369 | 
            +
                              end
         | 
| 370 | 
            +
                            end
         | 
| 371 | 
            +
                            if options[:terms_of_shipment] == 'DDP'
         | 
| 372 | 
            +
                              # DDP stands for delivery duty paid and means the shipper will cover duties and taxes
         | 
| 373 | 
            +
                              # Otherwise UPS will charge the receiver
         | 
| 374 | 
            +
                              xml.ShipmentCharge do
         | 
| 375 | 
            +
                                xml.Type('02') # Type '02' means 'Duties and Taxes'
         | 
| 376 | 
            +
                                xml.BillShipper do
         | 
| 377 | 
            +
                                  xml.AccountNumber(options[:origin_account])
         | 
| 378 | 
            +
                                end
         | 
| 379 | 
            +
                              end
         | 
| 380 | 
            +
                            end
         | 
| 381 | 
            +
                          end
         | 
| 382 | 
            +
                        end
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                        if options[:international]
         | 
| 385 | 
            +
                          build_location_node(xml, 'SoldTo', options[:sold_to] || destination, options)
         | 
| 386 | 
            +
             | 
| 387 | 
            +
                          if origin.country_code(:alpha2) == 'US' && ['CA', 'PR'].include?(destination.country_code(:alpha2))
         | 
| 388 | 
            +
                            # Required for shipments from the US to Puerto Rico or Canada
         | 
| 389 | 
            +
                            xml.InvoiceLineTotal do
         | 
| 390 | 
            +
                              total_value = packages.inject(0) {|sum, package| sum + (package.value || 0)}
         | 
| 391 | 
            +
                              xml.MonetaryValue(total_value)
         | 
| 392 | 
            +
                            end
         | 
| 393 | 
            +
                          end
         | 
| 394 | 
            +
             | 
| 395 | 
            +
                          contents_description = packages.map {|p| p.options[:description]}.compact.join(',')
         | 
| 396 | 
            +
                          unless contents_description.empty?
         | 
| 397 | 
            +
                            xml.Description(contents_description)
         | 
| 398 | 
            +
                          end
         | 
| 399 | 
            +
                        end
         | 
| 400 | 
            +
             | 
| 401 | 
            +
                        xml.ShipmentServiceOptions do
         | 
| 402 | 
            +
                          if delivery_confirmation = options[:delivery_confirmation]
         | 
| 403 | 
            +
                            xml.DeliveryConfirmation do
         | 
| 404 | 
            +
                              xml.DCISType(SHIPMENT_DELIVERY_CONFIRMATION_CODES[delivery_confirmation])
         | 
| 405 | 
            +
                            end
         | 
| 406 | 
            +
                          end
         | 
| 407 | 
            +
             | 
| 408 | 
            +
                          if options[:international]
         | 
| 409 | 
            +
                            build_international_forms(xml, origin, destination, packages, options)
         | 
| 410 | 
            +
                          end
         | 
| 321 411 | 
             
                        end
         | 
| 322 412 |  | 
| 323 413 | 
             
                        # A request may specify multiple packages.
         | 
| 324 | 
            -
                         | 
| 325 | 
            -
                        Array(packages).each do |package|
         | 
| 414 | 
            +
                        packages.each do |package|
         | 
| 326 415 | 
             
                          build_package_node(xml, package, options)
         | 
| 327 416 | 
             
                        end
         | 
| 328 417 | 
             
                      end
         | 
| @@ -343,6 +432,61 @@ module ActiveShipping | |
| 343 432 | 
             
                  xml_builder.to_xml
         | 
| 344 433 | 
             
                end
         | 
| 345 434 |  | 
| 435 | 
            +
                def build_delivery_dates_request(origin, destination, packages, pickup_date, options={})
         | 
| 436 | 
            +
                  xml_builder = Nokogiri::XML::Builder.new do |xml|
         | 
| 437 | 
            +
             | 
| 438 | 
            +
                    xml.TimeInTransitRequest do
         | 
| 439 | 
            +
                      xml.Request do
         | 
| 440 | 
            +
                        xml.RequestAction('TimeInTransit')
         | 
| 441 | 
            +
                      end
         | 
| 442 | 
            +
             | 
| 443 | 
            +
                      build_address_artifact_format_location(xml, 'TransitFrom', origin)
         | 
| 444 | 
            +
                      build_address_artifact_format_location(xml, 'TransitTo', destination)
         | 
| 445 | 
            +
             | 
| 446 | 
            +
                      xml.InvoiceLineTotal do
         | 
| 447 | 
            +
                        xml.CurrencyCode('USD')
         | 
| 448 | 
            +
                        total_value = packages.inject(0) {|sum, package| sum + package.value}
         | 
| 449 | 
            +
                        xml.MonetaryValue(total_value)
         | 
| 450 | 
            +
                      end
         | 
| 451 | 
            +
             | 
| 452 | 
            +
                      xml.PickupDate(pickup_date.strftime('%Y%m%d'))
         | 
| 453 | 
            +
                    end
         | 
| 454 | 
            +
                  end
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                  xml_builder.to_xml
         | 
| 457 | 
            +
                end
         | 
| 458 | 
            +
             | 
| 459 | 
            +
                def build_international_forms(xml, origin, destination, packages, options)
         | 
| 460 | 
            +
                  if options[:paperless_invoice]
         | 
| 461 | 
            +
                    xml.InternationalForms do
         | 
| 462 | 
            +
                      xml.FormType('01') # 01 is "Invoice"
         | 
| 463 | 
            +
                      xml.InvoiceDate(options[:invoice_date] || Date.today.strftime('%Y%m%d'))
         | 
| 464 | 
            +
                      xml.ReasonForExport(options[:reason_for_export] || 'SALE')
         | 
| 465 | 
            +
                      xml.CurrencyCode(options[:currency_code] || 'USD')
         | 
| 466 | 
            +
             | 
| 467 | 
            +
                      if options[:terms_of_shipment]
         | 
| 468 | 
            +
                        xml.TermsOfShipment(options[:terms_of_shipment])
         | 
| 469 | 
            +
                      end
         | 
| 470 | 
            +
             | 
| 471 | 
            +
                      packages.each do |package|
         | 
| 472 | 
            +
                        xml.Product do |xml|
         | 
| 473 | 
            +
                          xml.Description(package.options[:description])
         | 
| 474 | 
            +
                          xml.CommodityCode(package.options[:commodity_code])
         | 
| 475 | 
            +
                          xml.OriginCountryCode(origin.country_code(:alpha2))
         | 
| 476 | 
            +
                          xml.Unit do |xml|
         | 
| 477 | 
            +
                            xml.Value(package.value / (package.options[:item_count] || 1))
         | 
| 478 | 
            +
                            xml.Number((package.options[:item_count] || 1))
         | 
| 479 | 
            +
                            xml.UnitOfMeasurement do |xml|
         | 
| 480 | 
            +
                              # NMB = number. You can specify units in barrels, boxes, etc. Codes are in the api docs.
         | 
| 481 | 
            +
                              xml.Code(package.options[:unit_of_item_count] || 'NMB')
         | 
| 482 | 
            +
                            end
         | 
| 483 | 
            +
                          end
         | 
| 484 | 
            +
                        end
         | 
| 485 | 
            +
                      end
         | 
| 486 | 
            +
                    end
         | 
| 487 | 
            +
                  end
         | 
| 488 | 
            +
                end
         | 
| 489 | 
            +
             | 
| 346 490 | 
             
                def build_accept_request(digest, options = {})
         | 
| 347 491 | 
             
                  xml_builder = Nokogiri::XML::Builder.new do |xml|
         | 
| 348 492 | 
             
                    xml.ShipmentAcceptRequest do
         | 
| @@ -374,8 +518,7 @@ module ActiveShipping | |
| 374 518 | 
             
                  #                   * Shipment/(Shipper|ShipTo|ShipFrom)/AttentionName element
         | 
| 375 519 | 
             
                  #                   * Shipment/(Shipper|ShipTo|ShipFrom)/TaxIdentificationNumber element
         | 
| 376 520 | 
             
                  xml.public_send(name) do
         | 
| 377 | 
            -
                     | 
| 378 | 
            -
                    if shipper_name = (options[:origin_name] || @options[:origin_name])
         | 
| 521 | 
            +
                    if shipper_name = (location.name || location.company_name || options[:origin_name])
         | 
| 379 522 | 
             
                      xml.Name(shipper_name)
         | 
| 380 523 | 
             
                    end
         | 
| 381 524 | 
             
                    xml.PhoneNumber(location.phone.gsub(/[^\d]/, '')) unless location.phone.blank?
         | 
| @@ -387,7 +530,7 @@ module ActiveShipping | |
| 387 530 | 
             
                      xml.ShipperAssignedIdentificationNumber(destination_account)
         | 
| 388 531 | 
             
                    end
         | 
| 389 532 |  | 
| 390 | 
            -
                    if name = location.company_name || location.name
         | 
| 533 | 
            +
                    if name = (location.company_name || location.name || options[:origin_name])
         | 
| 391 534 | 
             
                      xml.CompanyName(name)
         | 
| 392 535 | 
             
                    end
         | 
| 393 536 |  | 
| @@ -414,6 +557,18 @@ module ActiveShipping | |
| 414 557 | 
             
                  end
         | 
| 415 558 | 
             
                end
         | 
| 416 559 |  | 
| 560 | 
            +
                def build_address_artifact_format_location(xml, name, location)
         | 
| 561 | 
            +
                  xml.public_send(name) do
         | 
| 562 | 
            +
                    xml.AddressArtifactFormat do
         | 
| 563 | 
            +
                      xml.PoliticalDivision2(location.city)
         | 
| 564 | 
            +
                      xml.PoliticalDivision1(location.province)
         | 
| 565 | 
            +
                      xml.CountryCode(location.country_code(:alpha2))
         | 
| 566 | 
            +
                      xml.PostcodePrimaryLow(location.postal_code)
         | 
| 567 | 
            +
                      xml.ResidentialAddressIndicator(true) unless location.commercial?
         | 
| 568 | 
            +
                    end
         | 
| 569 | 
            +
                  end
         | 
| 570 | 
            +
                end
         | 
| 571 | 
            +
             | 
| 417 572 | 
             
                def build_package_node(xml, package, options = {})
         | 
| 418 573 | 
             
                  xml.Package do
         | 
| 419 574 |  | 
| @@ -443,15 +598,23 @@ module ActiveShipping | |
| 443 598 | 
             
                      xml.Weight([value, 0.1].max)
         | 
| 444 599 | 
             
                    end
         | 
| 445 600 |  | 
| 446 | 
            -
             | 
| 601 | 
            +
             | 
| 602 | 
            +
                    Array(package.options[:reference_numbers]).each do |reference_number_info|
         | 
| 447 603 | 
             
                      xml.ReferenceNumber do
         | 
| 448 | 
            -
                        xml.Code( | 
| 449 | 
            -
                        xml.Value( | 
| 604 | 
            +
                        xml.Code(reference_number_info[:code] || "")
         | 
| 605 | 
            +
                        xml.Value(reference_number_info[:value])
         | 
| 606 | 
            +
                      end
         | 
| 607 | 
            +
                    end
         | 
| 608 | 
            +
             | 
| 609 | 
            +
                    xml.PackageServiceOptions do
         | 
| 610 | 
            +
                      if delivery_confirmation = package.options[:delivery_confirmation]
         | 
| 611 | 
            +
                        xml.DeliveryConfirmation do
         | 
| 612 | 
            +
                          xml.DCISType(PACKAGE_DELIVERY_CONFIRMATION_CODES[delivery_confirmation])
         | 
| 613 | 
            +
                        end
         | 
| 450 614 | 
             
                      end
         | 
| 451 615 | 
             
                    end
         | 
| 452 616 |  | 
| 453 617 | 
             
                    # not implemented:  * Shipment/Package/LargePackageIndicator element
         | 
| 454 | 
            -
                    #                   * Shipment/Package/PackageServiceOptions element
         | 
| 455 618 | 
             
                    #                   * Shipment/Package/AdditionalHandling element
         | 
| 456 619 | 
             
                  end
         | 
| 457 620 | 
             
                end
         | 
| @@ -526,10 +689,15 @@ module ActiveShipping | |
| 526 689 |  | 
| 527 690 | 
             
                    # Get scheduled delivery date
         | 
| 528 691 | 
             
                    unless status == :delivered
         | 
| 529 | 
            -
                       | 
| 530 | 
            -
             | 
| 531 | 
            -
             | 
| 532 | 
            -
             | 
| 692 | 
            +
                      scheduled_delivery_date_node = first_shipment.at('ScheduledDeliveryDate')
         | 
| 693 | 
            +
                      scheduled_delivery_date_node ||= first_shipment.at('RescheduledDeliveryDate')
         | 
| 694 | 
            +
             | 
| 695 | 
            +
                      if scheduled_delivery_date_node
         | 
| 696 | 
            +
                        scheduled_delivery_date = parse_ups_datetime(
         | 
| 697 | 
            +
                          :date => scheduled_delivery_date_node,
         | 
| 698 | 
            +
                          :time => nil
         | 
| 699 | 
            +
                          )
         | 
| 700 | 
            +
                      end
         | 
| 533 701 | 
             
                    end
         | 
| 534 702 |  | 
| 535 703 | 
             
                    activities = first_package.css('> Activity')
         | 
| @@ -591,6 +759,30 @@ module ActiveShipping | |
| 591 759 | 
             
                                       :tracking_number => tracking_number)
         | 
| 592 760 | 
             
                end
         | 
| 593 761 |  | 
| 762 | 
            +
                def parse_delivery_dates_response(origin, destination, packages, response, options={})
         | 
| 763 | 
            +
                  xml     = build_document(response, 'TimeInTransitResponse')
         | 
| 764 | 
            +
                  success = response_success?(xml)
         | 
| 765 | 
            +
                  message = response_message(xml)
         | 
| 766 | 
            +
                  delivery_estimates = []
         | 
| 767 | 
            +
             | 
| 768 | 
            +
                  if success
         | 
| 769 | 
            +
                    xml.css('ServiceSummary').each do |service_summary|
         | 
| 770 | 
            +
                      # Translate the Time in Transit Codes to the service codes used elsewhere
         | 
| 771 | 
            +
                      service_name = service_summary.at('Service/Description').text
         | 
| 772 | 
            +
                      service_code = UPS::DEFAULT_SERVICE_NAME_TO_CODE[service_name]
         | 
| 773 | 
            +
                      date = Date.strptime(service_summary.at('EstimatedArrival/Date').text, '%Y-%m-%d')
         | 
| 774 | 
            +
                      business_transit_days = service_summary.at('EstimatedArrival/BusinessTransitDays').text.to_i
         | 
| 775 | 
            +
                      delivery_estimates << DeliveryDateEstimate.new(origin, destination, self.class.class_variable_get(:@@name),
         | 
| 776 | 
            +
                                                service_name,
         | 
| 777 | 
            +
                                                :service_code => service_code,
         | 
| 778 | 
            +
                                                :guaranteed => service_summary.at('Guaranteed/Code').text == 'Y',
         | 
| 779 | 
            +
                                                :date =>  date,
         | 
| 780 | 
            +
                                                :business_transit_days => business_transit_days)
         | 
| 781 | 
            +
                    end
         | 
| 782 | 
            +
                  end
         | 
| 783 | 
            +
                  response = DeliveryDateEstimatesResponse.new(success, message, Hash.from_xml(response).values.first, :delivery_estimates => delivery_estimates, :xml => response, :request => last_request)
         | 
| 784 | 
            +
                end
         | 
| 785 | 
            +
             | 
| 594 786 | 
             
                def location_from_address_node(address)
         | 
| 595 787 | 
             
                  return nil unless address
         | 
| 596 788 | 
             
                  Location.new(
         | 
| @@ -621,7 +813,9 @@ module ActiveShipping | |
| 621 813 | 
             
                end
         | 
| 622 814 |  | 
| 623 815 | 
             
                def response_message(document)
         | 
| 624 | 
            -
                  document.root.at_xpath('Response/ | 
| 816 | 
            +
                  status = document.root.at_xpath('Response/ResponseStatusDescription').try(:text)
         | 
| 817 | 
            +
                  desc = document.root.at_xpath('Response/Error/ErrorDescription').try(:text)
         | 
| 818 | 
            +
                  [status, desc].select(&:present?).join(": ").presence || "UPS could not process the request."
         | 
| 625 819 | 
             
                end
         | 
| 626 820 |  | 
| 627 821 | 
             
                def response_digest(xml)
         | 
| @@ -663,5 +857,50 @@ module ActiveShipping | |
| 663 857 | 
             
                  name ||= OTHER_NON_US_ORIGIN_SERVICES[code] unless name == 'US'
         | 
| 664 858 | 
             
                  name || DEFAULT_SERVICES[code]
         | 
| 665 859 | 
             
                end
         | 
| 860 | 
            +
             | 
| 861 | 
            +
                def allow_package_level_reference_numbers(origin, destination)
         | 
| 862 | 
            +
                  # if the package is US -> US or PR -> PR the only type of reference numbers that are allowed are package-level
         | 
| 863 | 
            +
                  # Otherwise the only type of reference numbers that are allowed are shipment-level
         | 
| 864 | 
            +
                  [['US','US'],['PR', 'PR']].include?([origin,destination].map(&:country_code))
         | 
| 865 | 
            +
                end
         | 
| 866 | 
            +
             | 
| 867 | 
            +
                def handle_delivery_confirmation_options(origin, destination, packages, options)
         | 
| 868 | 
            +
                  if package_level_delivery_confirmation?(origin, destination)
         | 
| 869 | 
            +
                    handle_package_level_delivery_confirmation(origin, destination, packages, options)
         | 
| 870 | 
            +
                  else
         | 
| 871 | 
            +
                    handle_shipment_level_delivery_confirmation(origin, destination, packages, options)
         | 
| 872 | 
            +
                  end
         | 
| 873 | 
            +
                end
         | 
| 874 | 
            +
             | 
| 875 | 
            +
                def handle_package_level_delivery_confirmation(origin, destination, packages, options)
         | 
| 876 | 
            +
                  packages.each do |package|
         | 
| 877 | 
            +
                    # Transfer shipment-level option to package with no specified delivery_confirmation
         | 
| 878 | 
            +
                    package.options[:delivery_confirmation] = options[:delivery_confirmation] unless package.options[:delivery_confirmation]
         | 
| 879 | 
            +
             | 
| 880 | 
            +
                    # Assert that option is valid
         | 
| 881 | 
            +
                    if package.options[:delivery_confirmation] && !PACKAGE_DELIVERY_CONFIRMATION_CODES[package.options[:delivery_confirmation]]
         | 
| 882 | 
            +
                      raise "Invalid delivery_confirmation option on package: '#{package.options[:delivery_confirmation]}'. Use a key from PACKAGE_DELIVERY_CONFIRMATION_CODES"
         | 
| 883 | 
            +
                    end
         | 
| 884 | 
            +
                  end
         | 
| 885 | 
            +
                  options.delete(:delivery_confirmation)
         | 
| 886 | 
            +
                end
         | 
| 887 | 
            +
             | 
| 888 | 
            +
                def handle_shipment_level_delivery_confirmation(origin, destination, packages, options)
         | 
| 889 | 
            +
                  if packages.any? { |p| p.options[:delivery_confirmation] }
         | 
| 890 | 
            +
                    raise "origin/destination pair does not support package level delivery_confirmation options"
         | 
| 891 | 
            +
                  end
         | 
| 892 | 
            +
             | 
| 893 | 
            +
                  if options[:delivery_confirmation] && !SHIPMENT_DELIVERY_CONFIRMATION_CODES[options[:delivery_confirmation]]
         | 
| 894 | 
            +
                    raise "Invalid delivery_confirmation option: '#{options[:delivery_confirmation]}'. Use a key from SHIPMENT_DELIVERY_CONFIRMATION_CODES"
         | 
| 895 | 
            +
                  end
         | 
| 896 | 
            +
                end
         | 
| 897 | 
            +
             | 
| 898 | 
            +
                # For certain origin/destination pairs, UPS allows each package in a shipment to have a specified delivery_confirmation option
         | 
| 899 | 
            +
                # otherwise the delivery_confirmation option must be specified on the entire shipment.
         | 
| 900 | 
            +
                # See Appendix P of UPS Shipping Package XML Developers Guide for the rules on which the logic below is based.
         | 
| 901 | 
            +
                def package_level_delivery_confirmation?(origin, destination)
         | 
| 902 | 
            +
                  origin.country_code == destination.country_code ||
         | 
| 903 | 
            +
                  [['US','PR'], ['PR','US']].include?([origin,destination].map(&:country_code))
         | 
| 904 | 
            +
                end
         | 
| 666 905 | 
             
              end
         | 
| 667 906 | 
             
            end
         |