class ActiveShipping::CanadaPost

Constants

Box
DTD_NAME
DTD_URI
NON_ISO_COUNTRY_NAMES
PackedItem
PostalOutlet
RESPONSE_CODES
URL

Public Class Methods

default_location() click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 97
def self.default_location
  {
    :country     => 'CA',
    :province    => 'ON',
    :city        => 'Ottawa',
    :address1    => '61A York St',
    :postal_code => 'K1N5T2'
  }
end

Public Instance Methods

find_rates(origin, destination, line_items = [], options = {}) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 83
def find_rates(origin, destination, line_items = [], options = {})
  rate_request = build_rate_request(origin, destination, line_items, options)
  commit(rate_request, origin, destination, options)
end
maximum_address_field_length() click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 92
def maximum_address_field_length
  # https://www.canadapost.ca/cpo/mc/business/productsservices/developers/services/shippingmanifest/createshipment.jsf
  44
end
maximum_weight() click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 88
def maximum_weight
  Measured::Weight.new(30, :kg)
end
requirements() click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 79
def requirements
  [:login]
end

Protected Instance Methods

commit(request, origin, destination, options = {}) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 109
def commit(request, origin, destination, options = {})
  parse_rate_response(ssl_post(URL, request), origin, destination, options)
end

Private Instance Methods

build_line_items(xml, line_items) click to toggle source

<!– List of items in the shopping –> <!– cart –> <!– Each item is defined by : –> <!– - quantity (mandatory) –> <!– - size (mandatory) –> <!– - weight (mandatory) –> <!– - description (mandatory) –> <!– - ready to ship (optional) –>

# File lib/active_shipping/carriers/canada_post.rb, line 238
def build_line_items(xml, line_items)
  xml.lineItems do
    line_items.each do |line_item|
      xml.item do
        xml.quantity(1)
        xml.weight(line_item.kilograms)
        xml.length(line_item.cm(:length).to_s)
        xml.width(line_item.cm(:width).to_s)
        xml.height(line_item.cm(:height).to_s)
        xml.description(line_item.options[:description] || ' ')
        xml.readyToShip(line_item.options[:ready_to_ship] || nil)
        # By setting the 'readyToShip' tag to true, Sell Online will not pack this item in the boxes defined in the merchant profile.
      end
    end
  end
end
build_rate_request(origin, destination, line_items = [], options = {}) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 123
def build_rate_request(origin, destination, line_items = [], options = {})
  line_items  = [line_items] unless line_items.is_a?(Array)
  origin      = origin.is_a?(Location) ? origin : Location.new(origin)
  destination = destination.is_a?(Location) ? destination : Location.new(destination)

  generate_xml do |xml|
    xml.eparcel do
      xml.language(@options[:french] ? 'fr' : 'en')
      xml.ratesAndServicesRequest do
        xml.merchantCPCID(@options[:login])
        xml.fromPostalCode(origin.postal_code)
        xml.turnAroundTime(options[:turn_around_time]) if options[:turn_around_time]
        xml.itemsPrice(dollar_amount(line_items.map(&:value).compact.sum))

        build_line_items(xml, line_items)

        xml.city(destination.city)
        xml.provOrState(destination.province)
        xml.country(handle_non_iso_country_names(destination.country))
        xml.postalCode(destination.postal_code)
      end
    end
  end
end
dollar_amount(cents) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 255
def dollar_amount(cents)
  "%0.2f" % (cents / 100.0)
end
generate_xml() { |xml| ... } click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 115
def generate_xml(&block)
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.doc.create_internal_subset(DTD_NAME, nil, DTD_URI)
    yield(xml)
  end
  builder.to_xml
end
handle_non_iso_country_names(country) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 259
def handle_non_iso_country_names(country)
  NON_ISO_COUNTRY_NAMES[country.to_s] || country
end
parse_rate_response(response, origin, destination, options = {}) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 148
def parse_rate_response(response, origin, destination, options = {})
  xml = Nokogiri.XML(response)
  success = response_success?(xml)
  message = response_message(xml)

  rate_estimates = []
  boxes = []
  if success
    xml.xpath('eparcel/ratesAndServicesResponse/product').each do |product|
      service_name = (@options[:french] ? @@name_french : @@name) + " " + product.at('name').text
      service_code = product['id']

      rate_estimates << RateEstimate.new(origin, destination, @@name, service_name,
                                         :service_code => service_code,
                                         :total_price => product.at('rate').text,
                                         :currency => 'CAD',
                                         :shipping_date => product.at('shippingDate').text,
                                         :delivery_range => [product.at('deliveryDate').text] * 2
      )
    end

    boxes = xml.xpath('eparcel/ratesAndServicesResponse/packing/box').map do |box|
      b = Box.new
      b.packedItems = []
      b.name = box.at('name').text
      b.weight = box.at('weight').text.to_f
      b.expediter_weight = box.at('expediterWeight').text.to_f
      b.length = box.at('length').text.to_f
      b.width = box.at('width').text.to_f
      b.height = box.at('height').text.to_f
      b.packedItems = box.xpath('packedItem').map do |item|
        p = PackedItem.new
        p.quantity = item.at('quantity').text.to_i
        p.description = item.at('description').text
        p
      end
      b
    end

    postal_outlets = xml.xpath('eparcel/ratesAndServicesResponse/nearestPostalOutlet').map do |outlet|
      postal_outlet = PostalOutlet.new
      postal_outlet.sequence_no    = outlet.at('postalOutletSequenceNo').text
      postal_outlet.distance       = outlet.at('distance').text
      postal_outlet.name           = outlet.at('outletName').text
      postal_outlet.business_name  = outlet.at('businessName').text

      postal_outlet.postal_address = Location.new(
        :address1     => outlet.at('postalAddress/addressLine').text,
        :postal_code  => outlet.at('postalAddress/postal_code').text,
        :city         => outlet.at('postalAddress/municipality').text,
        :province     => outlet.at('postalAddress/province').text,
        :country      => 'Canada',
        :phone_number => outlet.at('phoneNumber').text
      )

      postal_outlet.business_hours = outlet.elements.collect('businessHours') do |hour|
        { :day_of_week => hour.at('dayOfWeek').text, :time => hour.at('time').text }
      end

      postal_outlet
    end
  end

  CanadaPostRateResponse.new(success, message, Hash.from_xml(response), :rates => rate_estimates, :xml => response, :boxes => boxes, :postal_outlets => postal_outlets)
end
response_message(xml) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 221
def response_message(xml)
  if response_success?(xml)
    xml.at('eparcel/ratesAndServicesResponse/statusMessage').text
  else
    xml.at('eparcel/error/statusMessage').text
  end
end
response_success?(xml) click to toggle source
# File lib/active_shipping/carriers/canada_post.rb, line 214
def response_success?(xml)
  return false unless xml.at('eparcel/error').nil?

  value = xml.at('eparcel/ratesAndServicesResponse/statusCode').text
  value == '1' || value == '2'
end