lib/active_shipping/carriers/ups.rb in active_shipping-1.9.2 vs lib/active_shipping/carriers/ups.rb in active_shipping-1.10.1
- old
+ new
@@ -16,11 +16,12 @@
:rates => 'ups.app/xml/Rate',
:track => 'ups.app/xml/Track',
:ship_confirm => 'ups.app/xml/ShipConfirm',
:ship_accept => 'ups.app/xml/ShipAccept',
:delivery_dates => 'ups.app/xml/TimeInTransit',
- :void => 'ups.app/xml/Void'
+ :void => 'ups.app/xml/Void',
+ :validate_address => 'ups.app/xml/XAV'
}
PICKUP_CODES = HashWithIndifferentAccess.new(
:daily_pickup => "01",
:customer_counter => "03",
@@ -230,10 +231,25 @@
def maximum_address_field_length
# http://www.ups.com/worldshiphelp/WS12/ENU/AppHelp/CONNECT/Shipment_Data_Field_Descriptions.htm
35
end
+ # Validates a location with the Street Level Validation service
+ #
+ # @param location [Location] The Location to validate
+ # @return [ActiveShipping::AddressValidationResponse] The response from the validation endpoint. This
+ # response will determine if the given address is valid or not, its commercial/residential classification,
+ # and the cleaned-up address and/or potential candidate addresses if the passed location can't be found
+ def validate_address(location, options = {})
+ location = upsified_location(location)
+ options = @options.merge(options)
+ access_request = build_access_request
+ address_validation_request = build_address_validation_request(location, options)
+ response = commit(:validate_address, save_request(access_request + address_validation_request), options[:test])
+ parse_address_validation_response(location, response, options)
+ end
+
protected
def upsified_location(location)
if location.country_code == 'US' && US_TERRITORIES_TREATED_AS_COUNTRIES.include?(location.state)
atts = {:country => location.state}
@@ -959,9 +975,100 @@
if success
true
else
raise ResponseError.new("Void shipment failed with message: #{message}")
end
+ end
+
+ def build_address_validation_request(location, options = {})
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
+ xml.AddressValidationRequest do
+ xml.Request do
+ xml.RequestAction('XAV')
+ xml.RequestOption('3')
+
+ if options[:customer_context]
+ xml.TransactionReference do
+ xml.CustomerContext(options[:customer_context])
+ xml.XpciVersion("1.0")
+ end
+ end
+ end
+
+ xml.AddressKeyFormat do
+ xml.AddressLine(location.address1)
+ if location.address2.present?
+ xml.AddressLine(location.address2)
+ end
+ xml.PoliticalDivision2(location.city)
+ xml.PoliticalDivision1(location.state)
+ xml.PostcodePrimaryLow(location.postal_code)
+ xml.CountryCode(location.country_code)
+ end
+ end
+ end
+
+ xml_builder.to_xml
+ end
+
+ def parse_address_validation_response(address, response, options={})
+ xml = build_document(response, 'AddressValidationResponse')
+ success = response_success?(xml)
+ message = response_message(xml)
+
+ validity = nil
+ classification_code = nil
+ classification_description = nil
+ addresses = []
+
+ if success
+ if xml.at('AddressClassification/Code').present?
+ classification_code = xml.at('AddressClassification/Code').text
+ end
+
+ classification = case classification_code
+ when "1"
+ :commercial
+ when "2"
+ :residential
+ else
+ :unknown
+ end
+
+ validity = if xml.at("ValidAddressIndicator").present?
+ :valid
+ elsif xml.at("AmbiguousAddressIndicator").present?
+ :ambiguous
+ elsif xml.at("NoCandidatesIndicator").present?
+ :invalid
+ else
+ :unknown
+ end
+
+ addresses = xml.css('AddressKeyFormat').collect { |node| location_from_address_key_format_node(node) }
+ end
+
+ params = Hash.from_xml(response).values.first
+ response = AddressValidationResponse.new(success, message, params, :validity => validity, :classification => classification, :candidate_addresses => addresses, :xml => response, :request => last_request)
+ end
+
+ # Converts from a AddressKeyFormat XML node to a Location
+ def location_from_address_key_format_node(address)
+ return nil unless address
+ country = address.at('CountryCode').try(:text)
+ country = 'US' if country == 'ZZ' # Sometimes returned by SUREPOST in the US
+
+ address_lines = address.css('AddressLine')
+
+ Location.new(
+ :country => country,
+ :postal_code => address.at('PostcodePrimaryLow').try(:text),
+ :province => address.at('PoliticalDivision1').try(:text),
+ :city => address.at('PoliticalDivision2').try(:text),
+ :address1 => address_lines[0].try(:text),
+ :address2 => address_lines[1].try(:text),
+ :address3 => address_lines[2].try(:text),
+ )
end
def location_from_address_node(address)
return nil unless address
country = address.at('CountryCode').try(:text)