Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding shipping time estimates for UPS #4

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 46 additions & 8 deletions app/models/calculator/active_shipping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,77 @@ def compute(line_items)
:state => (addr.state ? addr.state.abbr : addr.state_name),
:city => addr.city,
:zip => addr.zipcode)

rates = Rails.cache.fetch(cache_key(line_items)) do
rates = retrieve_rates(origin, destination, packages(order))
end

return nil if rates.empty?
rate = rates[self.description].to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0)
return nil unless rate
# divide by 100 since active_shipping rates are expressed as cents

return rate/100.0
end



def timing(line_items, options={})
order = line_items.first.order
origin = Location.new(:country => Spree::ActiveShipping::Config[:origin_country],
:city => Spree::ActiveShipping::Config[:origin_city],
:state => Spree::ActiveShipping::Config[:origin_state],
:zip => Spree::ActiveShipping::Config[:origin_zip])
addr = order.ship_address
destination = Location.new(:country => addr.country.iso,
:state => (addr.state ? addr.state.abbr : addr.state_name),
:city => addr.city,
:zip => addr.zipcode)
timings = Rails.cache.fetch(cache_key(line_items)+"-timings") do
timings = retrieve_timings(origin, destination, packages(order), options)
end
return nil if timings.nil? || !timings.is_a?(Hash) || timings.empty?
return timings[self.description]
end

private

def retrieve_rates(origin, destination, packages)
begin
response = carrier.find_rates(origin, destination, packages)
# turn this beastly array into a nice little hash
Hash[*response.rates.collect { |rate| [rate.service_name, rate.price] }.flatten]
#rate_hash = Hash[*response.rates.collect { |rate| [rate.service_name, rate.price] }.flatten]
rate_hash = Hash[*response.rates.collect { |rate| [rate.service_name.gsub("<sup>®</sup>", ""), rate.price] }.flatten]
debugger
return rate_hash
rescue ActiveMerchant::Shipping::ResponseError => re
params = re.response.params
if params.has_key?("Response") && params["Response"].has_key?("Error") && params["Response"]["Error"].has_key?("ErrorDescription")
message = params["Response"]["Error"]["ErrorDescription"]
else
message = re.message
end

Rails.cache.write @cache_key, {} #write empty hash to cache to prevent constant re-lookups

raise Spree::ShippingError.new("#{I18n.t('shipping_error')}: #{message}")
end
end


def retrieve_timings(origin, destination, packages, options = {})
begin
if carrier.respond_to?(:find_time_in_transit)
response = carrier.find_time_in_transit(origin, destination, packages, options)
return response
end
rescue ActiveMerchant::Shipping::ResponseError => re
params = re.response.params
if params.has_key?("Response") && params["Response"].has_key?("Error") && params["Response"]["Error"].has_key?("ErrorDescription")
message = params["Response"]["Error"]["ErrorDescription"]
elsee
message = re.message
end
Rails.cache.write @cache_key+'-', {} #write empty hash to cache to prevent constant re-lookups
raise Spree::ShippingError.new("#{I18n.t('shipping_error')}: #{message}")
end
end


private

# Generates an array of Package objects based on the quantities and weights of the variants in the line items
def packages(order)
Expand Down
175 changes: 168 additions & 7 deletions lib/spree/active_shipping/ups_override.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ module Spree
module ActiveShipping
module UpsOverride
def self.included(base)



base.class_eval do

def build_rate_request(origin, destination, packages, options={})
packages = Array(packages)
xml_request = XmlNode.new('RatingServiceSelectionRequest') do |root_node|
Expand Down Expand Up @@ -99,6 +99,55 @@ def build_rate_request(origin, destination, packages, options={})
xml_request.to_s
end

def build_time_in_transit_request(origin, destination, packages, options={})
packages = Array(packages)
xml_request = XmlNode.new('TimeInTransitRequest') do |root_node|
root_node << XmlNode.new('Request') do |request|
request << XmlNode.new('TransactionReference') do |transaction_reference|
transaction_reference << XmlNode.new('CustomerContext', 'Time in Transit')
transaction_reference << XmlNode.new('XpciVersion', '1.0002')
end
request << XmlNode.new('RequestAction', 'TimeInTransit')
end
root_node << XmlNode.new('TransitFrom') do |transit_from|
transit_from << XmlNode.new('AddressArtifactFormat') do |address_artifact_format|
address_artifact_format << XmlNode.new('PoliticalDivision2',origin.city)
address_artifact_format << XmlNode.new('PoliticalDivision1',origin.state)
address_artifact_format << XmlNode.new('CountryCode',origin.country_code(:alpha2))
address_artifact_format << XmlNode.new('PostcodePrimaryLow',origin.postal_code)
end
end

root_node << XmlNode.new('TransitTo') do |transit_to|
transit_to << XmlNode.new('AddressArtifactFormat') do |address_artifact_format|
address_artifact_format << XmlNode.new('PoliticalDivision2',destination.city)
address_artifact_format << XmlNode.new('PoliticalDivision1',destination.state)
address_artifact_format << XmlNode.new('CountryCode',destination.country_code(:alpha2))
address_artifact_format << XmlNode.new('PostcodePrimaryLow',destination.postal_code)
end
end

root_node << XmlNode.new("ShipmentWeight") do |shipment_weight|
shipment_weight << XmlNode.new("UnitOfMeasurement") do |units|
units << XmlNode.new("Code", 'LBS')
end

value = ((packages[0].lbs).to_f*1000).round/1000.0 # 3 decimals
shipment_weight << XmlNode.new("Weight", [value,0.1].max)
end

root_node << XmlNode.new("InvoiceLineTotal") do |invoice_line_total|
invoice_line_total << XmlNode.new("CurrencyCode","USD")
invoice_line_total << XmlNode.new("MonetaryValue","50")
end
thedate = options[:date].nil? ? Time.now : options[:date]
root_node << XmlNode.new("PickupDate",thedate.strftime("%Y%m%d"))


end
xml_request.to_s
end

def build_location_node(name,location,options={})
# not implemented: * Shipment/Shipper/Name element
# * Shipment/(ShipTo|ShipFrom)/CompanyName element
Expand Down Expand Up @@ -129,33 +178,145 @@ def build_location_node(name,location,options={})
end
end

def parse_rate_response(origin, destination, packages, response, options={})
debugger
def find_time_in_transit(origin, destination, packages, options={})
origin, destination = upsified_location(origin), upsified_location(destination)
options = @options.merge(options)
packages = Array(packages)
access_request = build_access_request
rate_request = build_time_in_transit_request(origin, destination, packages, options)
response = ssl_post("https://www.ups.com/ups.app/xml/TimeInTransit", "<?xml version=\"1.0\"?>"+access_request+"<?xml version=\"1.0\"?>"+rate_request)
parse_time_in_transit_response(origin, destination, packages,response, options)
end

def find_rates(origin, destination, packages, options={})
origin, destination = upsified_location(origin), upsified_location(destination)
options = @options.merge(options)
packages = Array(packages)
access_request = build_access_request
rate_request = build_rate_request(origin, destination, packages, options)
response = commit(:rates, save_request( access_request + rate_request), (options[:test] || false))
parse_rate_response(origin, destination, packages, response, options)
end

def find_address_request(zip,options={})
options = @options.merge(options)
address_request = build_address_request(zip)
access_request = build_access_request
print "<?xml version=\"1.0\"?>"+access_request + "<?xml version=\"1.0\"?>" + address_request
response = ssl_post("https://www.ups.com/ups.app/xml/AV", "<?xml version=\"1.0\"?>"+ access_request + "<?xml version=\"1.0\"?>" + address_request)
parse_address_request(response)
end

def parse_address_request(response)
xml = REXML::Document.new(response)
success = response_success?(xml)
message = response_message(xml)
results = []
if success
city_options = {}
xml.elements.each('/*/AddressValidationResult') do |address_validation_result|
rank = address_validation_result.get_text('Rank').to_s
quality = address_validation_result.get_text('Quality').to_s
city = address_validation_result.get_text('Address/City').to_s
state = address_validation_result.get_text('Address/StateProvinceCode').to_s
zip_low = address_validation_result.get_text('PostalCodeLowEnd').to_s
zip_high = address_validation_result.get_text('PostalCodeHighEnd').to_s
results << {:rank => rank, :quality => quality, :city => city,:state => state, :zip_low => zip_low,:zip_high => zip_high}
end
end
return results
end


def build_address_request(zip)
xml_request = XmlNode.new('AddressValidationRequest') do |root_node|
root_node << XmlNode.new('Request') do |request|
request << XmlNode.new('TransactionReference') do |transaction_reference|
transaction_reference << XmlNode.new('CustomerContext', 'Customer Data')
transaction_reference << XmlNode.new('XpciVersion', '1.0001')
end
request << XmlNode.new('RequestAction', 'AV')
end

root_node << XmlNode.new('Address') do |address|
address << XmlNode.new('City',"")
address << XmlNode.new('StateProvinceCode',"")
address << XmlNode.new('PostalCode',zip)
end
end
xml_request.to_s
end






def parse_time_in_transit_response(origin, destination, packages, response, options={})

time_code_mapping = {
"1DA" => "01",
"2DA" => "02",
"GND" => "03",
"01" => "07",
"05" => "08",
"03" => "11",
"3DS" => "12",
"1DP" => "13",
"1DM" => "14",
"21" => "54",
"2DM" => "59"
}

rates = []
xml = REXML::Document.new(response)
success = response_success?(xml)
message = response_message(xml)
if success
rate_estimates = {}
xml.elements.each('/*/TransitResponse/ServiceSummary') do |service_summary|
service_code = service_summary.get_text('Service/Code').to_s
service_code_2 = time_code_mapping[service_code]
service_desc = service_summary.get_text('Service/Description').to_s
guaranteed_code = service_summary.get_text('Guaranteed/Code').to_s
business_transit_days = service_summary.get_text('EstimatedArrival/BusinessTransitDays').to_s
date = service_summary.get_text('EstimatedArrival/Date').to_s
rate_estimates[service_name_for(origin, service_code_2)] = {:service_code => service_code, :service_code_2 => service_code_2, :service_desc => service_desc,
:guaranteed_code => guaranteed_code, :business_transit_days => business_transit_days,
:date => date}
end
end
return rate_estimates
end


def parse_rate_response(origin, destination, packages, response, options={})
rates = []
xml = REXML::Document.new(response)
success = response_success?(xml)
message = response_message(xml)
transits = options[:transit]
if success
rate_estimates = []

xml.elements.each('/*/RatedShipment') do |rated_shipment|
service_code = rated_shipment.get_text('Service/Code').to_s
service = rated_shipment.get_text('Service/Code').to_s
negotiated_rate = rated_shipment.get_text('NegotiatedRates/NetSummaryCharges/GrandTotal/MonetaryValue').to_s
total_price = negotiated_rate.blank? ? rated_shipment.get_text('TotalCharges/MonetaryValue').to_s.to_f : negotiated_rate.to_f
currency = negotiated_rate.blank? ? rated_shipment.get_text('TotalCharges/CurrencyCode').to_s : rated_shipment.get_text('NegotiatedRates/NetSummaryCharges/GrandTotal/CurrencyCode').to_s

rate_estimates << ActiveMerchant::Shipping::RateEstimate.new(origin, destination, ActiveMerchant::Shipping::UPS.name,
service_name_for(origin, service_code),
:total_price => total_price,
:currency => currency,
:service_code => service_code,
:packages => packages)
:packages => packages
)
end
end
ActiveMerchant::Shipping::RateResponse.new(success, message, Hash.from_xml(response).values.first, :rates => rate_estimates, :xml => response, :request => last_request)
end

end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ def rates_from_response_node(response_node, packages)

package_node.each_element(service_node) do |service_response_node|
service_name = service_response_node.get_text(service_name_node).to_s

service_name.gsub!(/&amp;lt;sup&amp;gt;&amp;amp;reg;&amp;lt;\/sup&amp;gt;/, '')
# aggregate specific package rates into a service-centric RateEstimate
# first package with a given service name will initialize these;
# later packages with same service will add to them
Expand Down