class ReactiveShipping::CanadaPostPWS

Constants

ENDPOINT
LANGUAGE
MAX_WEIGHT
RATES_OPTIONS
RATE_MIMETYPE
REGISTER_MIMETYPE
SHIPMENT_MIMETYPE
SHIPPING_OPTIONS
SHIPPING_SERVICES
TRACK_MIMETYPE

Attributes

customer_number[RW]
endpoint[RW]
language[RW]
logger[RW]
platform_id[RW]

Public Class Methods

default_location() click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 67
def self.default_location
  {
    :country     => 'CA',
    :province    => 'ON',
    :city        => 'Ottawa',
    :address1    => '61A York St',
    :postal_code => 'K1N5T2'
  }
end
new(options = {}) click to toggle source
Calls superclass method ReactiveShipping::Carrier::new
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 55
def initialize(options = {})
  @language = LANGUAGE[options[:language]] || LANGUAGE['en']
  @endpoint = options[:endpoint] || ENDPOINT
  @platform_id = options[:platform_id]
  @customer_number = options[:customer_number]
  super(options)
end

Public Instance Methods

build_rates_request(origin, destination, line_items = [], options = {}, package = nil, services = []) click to toggle source

rating

# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 257
def build_rates_request(origin, destination, line_items = [], options = {}, package = nil, services = [])
  line_items = Array(line_items)

  builder = Nokogiri::XML::Builder.new do |xml|
    xml.public_send('mailing-scenario', :xmlns => "http://www.canadapost.ca/ws/ship/rate") do
      customer_number_node(xml, options)
      contract_id_node(xml, options)
      quote_type_node(xml, options)
      expected_mailing_date_node(xml, shipping_date(options)) if options[:shipping_date]
      shipping_options_node(xml, RATES_OPTIONS, options)
      parcel_node(xml, line_items, package)
      origin_node(xml, origin)
      destination_node(xml, destination)
      services_node(xml, services) unless services.blank?
    end
  end
  builder.to_xml
end
build_shipment_request(origin, destination, package, line_items = [], options = {}) click to toggle source

options :service => 'DOM.EP' :notification_email :packing_instructions :show_postage_rate :cod, :cod_amount, :insurance, :insurance_amount, :signature_required, :pa18, :pa19, :hfp, :dns, :lad

# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 361
def build_shipment_request(origin, destination, package, line_items = [], options = {})
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.public_send('non-contract-shipment', :xmlns => "http://www.canadapost.ca/ws/ncshipment") do
      xml.public_send('delivery-spec') do
        shipment_service_code_node(xml, options)
        shipment_sender_node(xml, origin, options)
        shipment_destination_node(xml, destination, options)
        shipment_options_node(xml, options)
        shipment_parcel_node(xml, package)
        shipment_notification_node(xml, options)
        shipment_preferences_node(xml, options)
        references_node(xml, options)             # optional > user defined custom notes
        shipment_customs_node(xml, destination, line_items, options)
        # COD Remittance defaults to sender
      end
    end
  end
  builder.to_xml
end
build_tracking_events(events) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 337
def build_tracking_events(events)
  events.map do |event|
    date      = event.at('event-date').text
    time      = event.at('event-time').text
    zone      = event.at('event-time-zone').text
    timestamp = DateTime.parse("#{date} #{time} #{zone}")
    time      = Time.utc(timestamp.utc.year, timestamp.utc.month, timestamp.utc.day, timestamp.utc.hour, timestamp.utc.min, timestamp.utc.sec)
    message   = event.at('event-description').text
    location  = [event.at('event-retail-name'), event.at('event-site'), event.at('event-province')].
                  reject { |e| e.nil? || e.text.empty? }.join(", ")
    name      = event.at('event-identifier').text
    ShipmentEvent.new(name, time, location, message)
  end
end
create_shipment(origin, destination, package, line_items = [], options = {}) click to toggle source

line_items should be a list of PackageItem's

# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 100
def create_shipment(origin, destination, package, line_items = [], options = {})
  request_body = build_shipment_request(origin, destination, package, line_items, options)
  response = ssl_post(create_shipment_url(options), request_body, headers(options, SHIPMENT_MIMETYPE, SHIPMENT_MIMETYPE))
  parse_shipment_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSShippingResponse)
rescue MissingCustomerNumberError
  CPPWSShippingResponse.new(false, "Missing Customer Number", {}, :carrier => @@name)
end
error_response(response, response_klass) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 572
def error_response(response, response_klass)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  messages = doc.xpath('messages/message')
  message = messages.map { |m| m.at('description').text }.join(", ")
  code = messages.map { |m| m.at('code').text }.join(", ")
  response_klass.new(false, message, {}, :carrier => @@name, :code => code)
end
find_option_details(option_code, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 158
def find_option_details(option_code, options = {})
  url = endpoint + "rs/ship/option/#{option_code}"
  response = ssl_get(url, headers(options, RATE_MIMETYPE))
  parse_option_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSRateResponse)
end
find_rates(origin, destination, line_items = [], options = {}, package = nil, services = []) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 77
def find_rates(origin, destination, line_items = [], options = {}, package = nil, services = [])
  url = endpoint + "rs/ship/price"
  request  = build_rates_request(origin, destination, line_items, options, package, services)
  response = ssl_post(url, request, headers(options, RATE_MIMETYPE, RATE_MIMETYPE))
  parse_rates_response(response, origin, destination, !!options[:exclude_tax])
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSRateResponse)
end
find_service_options(service_code, country, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 151
def find_service_options(service_code, country, options = {})
  response = ssl_get(services_url(country, service_code), headers(options, RATE_MIMETYPE))
  parse_service_options_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSRateResponse)
end
find_services(country = nil, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 144
def find_services(country = nil, options = {})
  response = ssl_get(services_url(country), headers(options, RATE_MIMETYPE))
  parse_services_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSRateResponse)
end
find_shipment_receipt(shipping_id, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 115
def find_shipment_receipt(shipping_id, options = {})
  response = ssl_get(shipment_receipt_url(shipping_id, options), headers(options, SHIPMENT_MIMETYPE, SHIPMENT_MIMETYPE))
  parse_shipment_receipt_response(response)
end
find_tracking_info(pin, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 86
def find_tracking_info(pin, options = {})
  response = ssl_get(tracking_url(pin), headers(options, TRACK_MIMETYPE))
  parse_tracking_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  if e.response
    error_response(e.response.body, CPPWSTrackingResponse)
  else
    CPPWSTrackingResponse.new(false, e.message, {}, :carrier => @@name)
  end
rescue InvalidPinFormatError
  CPPWSTrackingResponse.new(false, "Invalid Pin Format", {}, :carrier => @@name)
end
log(msg) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 581
def log(msg)
  logger.debug(msg) if logger
end
maximum_address_field_length() click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 170
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/reactive_shipping/carriers/canada_post_pws.rb, line 166
def maximum_weight
  Measured::Weight.new(MAX_WEIGHT, :kg)
end
parse_merchant_details_response(response) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 522
def parse_merchant_details_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  raise "No Merchant Info" unless doc.at('merchant-info')
  raise "No Merchant Info" if doc.root.at('customer-number').blank?
  options = {
    :customer_number => doc.root.at('customer-number').text,
    :contract_number => doc.root.at('contract-number').text,
    :username => doc.root.at('merchant-username').text,
    :password => doc.root.at('merchant-password').text,
    :has_default_credit_card => doc.root.at('has-default-credit-card').text == 'true'
  }
  CPPWSMerchantDetailsResponse.new(true, "", {}, options)
end
parse_option_response(response) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 235
def parse_option_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!

  conflicts = doc.root.xpath('conflicting-options/option-code').map(&:text)
  prereqs = doc.root.xpath('prerequisite-options/option-code').map(&:text)
  option = {
    :code => doc.root.at('option-code').text,
    :name => doc.root.at('option-name').text,
    :class => doc.root.at('option-class').text,
    :prints_on_label => doc.root.at('prints-on-label').text != "false",
    :qualifier_required => doc.root.at('qualifier-required').text != "false",
  }
  option[:conflicting_options] = conflicts if conflicts
  option[:prerequisite_options] = prereqs if prereqs

  option[:qualifier_max] = doc.root.at("qualifier-max").text.to_i if doc.root.at("qualifier-max")
  option
end
parse_rates_response(response, origin, destination, exclude_tax) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 276
def parse_rates_response(response, origin, destination, exclude_tax)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  raise ReactiveShipping::ResponseError, "No Quotes" unless doc.at('price-quotes')

  rates = doc.root.xpath('price-quote').map do |node|
    service_name  = node.at("service-name").text
    service_code  = node.at("service-code").text
    total_price   = price_from_node(node, exclude_tax)
    expected_date = expected_date_from_node(node)
    options = {
      :service_code   => service_code,
      :total_price    => total_price,
      :currency       => 'CAD',
      :delivery_range => [expected_date, expected_date]
    }
    RateEstimate.new(origin, destination, @@name, service_name, options)
  end
  CPPWSRateResponse.new(true, "", {}, :rates => rates)
end
parse_register_token_response(response) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 512
def parse_register_token_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  raise ReactiveShipping::ResponseError, "No Registration Token" unless doc.at('token')
  options = {
    :token_id => doc.root.at('token-id').text
  }
  CPPWSRegisterResponse.new(true, "", {}, options)
end
parse_service_options_response(response) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 195
def parse_service_options_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!

  service_code = doc.root.at("service-code").text
  service_name = doc.root.at("service-name").text

  option_nodes = doc.root.xpath('options/option')
  options = option_nodes.map do |node|
    option = {
      :code => node.at("option-code").text,
      :name => node.at("option-name").text,
      :required => node.at("mandatory").text != "false",
      :qualifier_required => node.at("qualifier-required").text != "false",
    }
    option[:qualifier_max] = node.at("qualifier-max").text.to_i if node.at("qualifier-max")
    option
  end

  restrictions_node = doc.root.at('restrictions')
  dimensions_node = restrictions_node.at('dimensional-restrictions')
  restrictions = {
    :min_weight => restrictions_node.at("weight-restriction").attributes['min'].value.to_i,
    :max_weight => restrictions_node.at("weight-restriction").attributes['max'].value.to_i,
    :min_length => dimensions_node.at("length").attributes['min'].value.to_f,
    :max_length => dimensions_node.at("length").attributes['max'].value.to_f,
    :min_height => dimensions_node.at("height").attributes['min'].value.to_f,
    :max_height => dimensions_node.at("height").attributes['max'].value.to_f,
    :min_width => dimensions_node.at("width").attributes['min'].value.to_f,
    :max_width => dimensions_node.at("width").attributes['max'].value.to_f
  }

  {
    :service_code => service_code,
    :service_name => service_name,
    :options => options,
    :restrictions => restrictions
  }
end
parse_services_response(response) click to toggle source

service discovery

# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 177
def parse_services_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  service_nodes = doc.xpath('services/service')
  service_nodes.inject({}) do |result, node|
    service_code = node.at("service-code").text
    service_name = node.at("service-name").text
    service_link = node.at("link").attributes['href'].value
    service_link_media_type = node.at("link").attributes['media-type'].value
    result[service_code] = {
      :name => service_name,
      :link => service_link,
      :link_media_type => service_link_media_type
    }
    result
  end
end
parse_shipment_receipt_response(response) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 537
def parse_shipment_receipt_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  root = doc.at('non-contract-shipment-receipt')
  cc_details_node = root.at('cc-receipt-details')
  service_standard_node = root.at('service-standard')
  receipt = {
    :final_shipping_point => root.at("final-shipping-point").text,
    :shipping_point_name => root.at("shipping-point-name").text,
    :service_code => root.at("service-code").text,
    :rated_weight => root.at("rated-weight").text.to_f,
    :base_amount => root.at("base-amount").text.to_f,
    :pre_tax_amount => root.at("pre-tax-amount").text.to_f,
    :gst_amount => root.at("gst-amount").text.to_f,
    :pst_amount => root.at("pst-amount").text.to_f,
    :hst_amount => root.at("hst-amount").text.to_f,
    :charge_amount => cc_details_node.at("charge-amount").text.to_f,
    :currency => cc_details_node.at("currency").text,
    :expected_transit_days => service_standard_node.at("expected-transit-time").text.to_i,
    :expected_delivery_date => service_standard_node.at("expected-delivery-date").text
  }
  option_nodes = root.xpath('priced-options/priced-option')

  receipt[:priced_options] = if option_nodes.length > 0
    option_nodes.inject({}) do |result, node|
      result[node.at("option-code").text] = node.at("option-price").text.to_f
      result
      end
  else
    {}
  end

  receipt
end
parse_shipment_response(response) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 497
def parse_shipment_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  raise ReactiveShipping::ResponseError, "No Shipping" unless doc.at('non-contract-shipment-info')
  options = {
    :shipping_id      => doc.root.at('shipment-id').text,
    :details_url      => doc.root.at_xpath("links/link[@rel='details']")['href'],
    :label_url        => doc.root.at_xpath("links/link[@rel='label']")['href'],
    :receipt_url      => doc.root.at_xpath("links/link[@rel='receipt']")['href'],
  }
  options[:tracking_number] = doc.root.at('tracking-pin').text if doc.root.at('tracking-pin')

  CPPWSShippingResponse.new(true, "", {}, options)
end
parse_tracking_response(response) click to toggle source

tracking

# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 307
def parse_tracking_response(response)
  doc = Nokogiri.XML(response)
  doc.remove_namespaces!
  raise ReactiveShipping::ResponseError, "No Tracking" unless doc.at('tracking-detail')

  events = doc.root.xpath('significant-events/occurrence')

  shipment_events  = build_tracking_events(events)
  change_date      = doc.root.at('changed-expected-date').text
  expected_date    = doc.root.at('expected-delivery-date').text
  dest_postal_code = doc.root.at('destination-postal-id').text
  destination      = Location.new(:postal_code => dest_postal_code)
  origin           = Location.new(origin_hash_for(doc.root))
  options = {
    :carrier                 => @@name,
    :service_name            => doc.root.at('service-name').text,
    :expected_date           => expected_date.blank? ? nil : Date.parse(expected_date),
    :changed_date            => change_date.blank? ? nil : Date.parse(change_date),
    :change_reason           => doc.root.at('changed-expected-delivery-reason').text.strip,
    :destination_postal_code => doc.root.at('destination-postal-id').text,
    :shipment_events         => shipment_events,
    :tracking_number         => doc.root.at('pin').text,
    :origin                  => origin,
    :destination             => destination,
    :customer_number         => doc.root.at('mailed-by-customer-number').text
  }

  CPPWSTrackingResponse.new(true, "", {}, options)
end
price_from_node(node, exclude_tax) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 297
def price_from_node(node, exclude_tax)
  price = node.at('price-details/due').text
  return price unless exclude_tax
  children = node.at('price-details/taxes').children
  tax_total_cents = children.sum { |node| node.elem? ? Package.cents_from(node.text) : 0 }
  Package.cents_from(price) - tax_total_cents
end
references_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 441
def references_node(xml, options)
  # custom values
  # xml.public_send('references') do
  # end
end
register_merchant(options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 125
def register_merchant(options = {})
  url = endpoint + "ot/token"
  response = ssl_post(url, nil, headers({}, REGISTER_MIMETYPE, REGISTER_MIMETYPE).merge("Content-Length" => "0"))
  parse_register_token_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSRegisterResponse)
end
requirements() click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 63
def requirements
  [:api_key, :secret]
end
retrieve_merchant_details(options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 133
def retrieve_merchant_details(options = {})
  raise MissingTokenIdError unless token_id = options[:token_id]
  url = endpoint + "ot/token/#{token_id}"
  response = ssl_get(url, headers({}, REGISTER_MIMETYPE, REGISTER_MIMETYPE))
  parse_merchant_details_response(response)
rescue ActiveUtils::ResponseError, ReactiveShipping::ResponseError => e
  error_response(e.response.body, CPPWSMerchantDetailsResponse)
rescue Exception => e
  raise ResponseError.new(e.message)
end
retrieve_shipment(shipping_id, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 110
def retrieve_shipment(shipping_id, options = {})
  response = ssl_post(shipment_url(shipping_id, options), nil, headers(options, SHIPMENT_MIMETYPE, SHIPMENT_MIMETYPE))
  parse_shipment_response(response)
end
retrieve_shipping_label(shipping_response, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 120
def retrieve_shipping_label(shipping_response, options = {})
  raise MissingShippingNumberError unless shipping_response && shipping_response.shipping_id
  ssl_get(shipping_response.label_url, headers(options, "application/pdf"))
end
shipment_customs_node(xml, destination, line_items, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 447
def shipment_customs_node(xml, destination, line_items, options)
  return unless location_from_hash(destination).country_code != 'CA'

  xml.public_send('customs') do
    currency = options[:currency] || "CAD"
    xml.public_send('currency', currency)
    xml.public_send('conversion-from-cad', options[:conversion_from_cad].to_s) if currency != 'CAD' && options[:conversion_from_cad]
    xml.public_send('reason-for-export', 'SOG') # SOG - Sale of Goods
    xml.public_send('other-reason', options[:customs_other_reason]) if options[:customs_reason_for_export] && options[:customs_other_reason]
    xml.public_send('additional-customs-info', options[:customs_addition_info]) if options[:customs_addition_info]
    xml.public_send('sku-list') do
      line_items.each do |line_item|
        kg = '%#2.3f' % [sanitize_weight_kg(line_item.kg)]
        xml.public_send('item') do
          xml.public_send('hs-tariff-code', line_item.hs_code) if line_item.hs_code && !line_item.hs_code.empty?
          xml.public_send('sku', line_item.sku) if line_item.sku && !line_item.sku.empty?
          xml.public_send('customs-description', line_item.name.slice(0, 44))
          xml.public_send('unit-weight', kg)
          xml.public_send('customs-value-per-unit', '%.2f' % sanitize_price_from_cents(line_item.value))
          xml.public_send('customs-number-of-units', line_item.quantity)
          xml.public_send('country-of-origin', line_item.options[:country_of_origin]) if line_item.options && line_item.options[:country_of_origin] && !line_item.options[:country_of_origin].empty?
          xml.public_send('province-of-origin', line_item.options[:province_of_origin]) if line_item.options && line_item.options[:province_of_origin] && !line_item.options[:province_of_origin].empty?
        end
      end
    end

  end
end
shipment_destination_node(xml, destination, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 402
def shipment_destination_node(xml, destination, options)
  location = location_from_hash(destination)
  xml.public_send('destination') do
    xml.public_send('name', location.name)
    xml.public_send('company', location.company) if location.company.present?
    xml.public_send('client-voice-number', location.phone)
    xml.public_send('address-details') do
      xml.public_send('address-line-1', location.address1)
      xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
      xml.public_send('city', location.city)
      xml.public_send('prov-state', location.province) unless location.province.blank?
      xml.public_send('country-code', location.country_code)
      xml.public_send('postal-zip-code', get_sanitized_postal_code(location))
    end
  end
end
shipment_notification_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 423
def shipment_notification_node(xml, options)
  return unless options[:notification_email]
  xml.public_send('notification') do
    xml.public_send('email', options[:notification_email])
    xml.public_send('on-shipment', true)
    xml.public_send('on-exception', true)
    xml.public_send('on-delivery', true)
  end
end
shipment_options_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 419
def shipment_options_node(xml, options)
  shipping_options_node(xml, SHIPPING_OPTIONS, options)
end
shipment_parcel_node(xml, package, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 476
def shipment_parcel_node(xml, package, options = {})
  weight = sanitize_weight_kg(package.kilograms.to_f)
  xml.public_send('parcel-characteristics') do
    xml.public_send('weight', "%#2.3f" % weight)
    pkg_dim = package.cm
    if pkg_dim && !pkg_dim.select { |x| x != 0 }.empty?
      xml.public_send('dimensions') do
        xml.public_send('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
        xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
        xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
      end
      xml.public_send('document', false)
    else
      xml.public_send('document', true)
    end

    xml.public_send('mailing-tube', package.tube?)
    xml.public_send('unpackaged', package.unpackaged?)
  end
end
shipment_preferences_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 433
def shipment_preferences_node(xml, options)
  xml.public_send('preferences') do
    xml.public_send('show-packing-instructions', options[:packing_instructions] || true)
    xml.public_send('show-postage-rate', options[:show_postage_rate] || false)
    xml.public_send('show-insured-value', true)
  end
end
shipment_sender_node(xml, sender, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 385
def shipment_sender_node(xml, sender, options)
  location = location_from_hash(sender)
  xml.public_send('sender') do
    xml.public_send('name', location.name)
    xml.public_send('company', location.company) if location.company.present?
    xml.public_send('contact-phone', location.phone)
    xml.public_send('address-details') do
      xml.public_send('address-line-1', location.address1)
      xml.public_send('address-line-2', location.address2_and_3) unless location.address2_and_3.blank?
      xml.public_send('city', location.city)
      xml.public_send('prov-state', location.province)
      # xml.public_send('country-code', location.country_code)
      xml.public_send('postal-zip-code', get_sanitized_postal_code(location))
    end
  end
end
shipment_service_code_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 381
def shipment_service_code_node(xml, options)
  xml.public_send('service-code', options[:service])
end

Private Instance Methods

contract_id_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 659
def contract_id_node(xml, options)
  xml.public_send("contract-id", options[:contract_id]) if options[:contract_id]
end
create_shipment_url(options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 598
def create_shipment_url(options)
  raise MissingCustomerNumberError unless customer_number = options[:customer_number]
  if @platform_id.present?
    endpoint + "rs/#{customer_number}-#{@platform_id}/ncshipment"
  else
    endpoint + "rs/#{customer_number}/ncshipment"
  end
end
customer_credentials_valid?(credentials) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 632
def customer_credentials_valid?(credentials)
  (credentials.keys & [:customer_api_key, :customer_secret]).any?
end
customer_number_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 655
def customer_number_node(xml, options)
  xml.public_send("customer-number", options[:customer_number] || customer_number)
end
destination_node(xml, location) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 696
def destination_node(xml, location)
  destination = location_from_hash(location)
  postal_code = get_sanitized_postal_code(destination)
  case destination.country_code
    when 'CA'
      xml.public_send('destination') do
        xml.public_send('domestic') do
          xml.public_send('postal-code', postal_code)
        end
      end

    when 'US'
      xml.public_send('destination') do
        xml.public_send('united-states') do
          xml.public_send('zip-code', postal_code)
        end
      end

    else
      xml.public_send('destination') do
        xml.public_send('international') do
          xml.public_send('country-code', destination.country_code)
        end
      end
  end
end
encoded_authorization(customer_credentials = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 636
def encoded_authorization(customer_credentials = {})
  if customer_credentials_valid?(customer_credentials)
    "Basic %s" % Base64.encode64("#{customer_credentials[:customer_api_key]}:#{customer_credentials[:customer_secret]}")
  else
    "Basic %s" % Base64.encode64("#{@options[:api_key]}:#{@options[:secret]}")
  end
end
expected_date_from_node(node) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 766
def expected_date_from_node(node)
  if service = node.at('service-standard/expected-delivery-date')
    expected_date = service.text
  else
    expected_date = nil
  end
  expected_date
end
expected_mailing_date_node(xml, date_as_string) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 667
def expected_mailing_date_node(xml, date_as_string)
  xml.public_send("expected-mailing-date", date_as_string)
end
get_sanitized_postal_code(location) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 784
def get_sanitized_postal_code(location)
  return nil if location.nil? || location.postal_code.nil?
  location.postal_code.gsub(/\s+/, '').upcase
end
headers(customer_credentials, accept = nil, content_type = nil) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 644
def headers(customer_credentials, accept = nil, content_type = nil)
  headers = {
    'Authorization'   => encoded_authorization(customer_credentials),
    'Accept-Language' => language
  }
  headers['Accept'] = accept if accept
  headers['Content-Type'] = content_type if content_type
  headers['Platform-ID'] = platform_id if platform_id && customer_credentials_valid?(customer_credentials)
  headers
end
location_from_hash(location) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 779
def location_from_hash(location)
  return location if location.is_a?(Location)
  return Location.new(location)
end
origin_hash_for(root) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 797
def origin_hash_for(root)
  occurrences = root.xpath('significant-events/occurrence')
  earliest = occurrences.sort_by { |occurrence| time_of_occurrence(occurrence) }.first

  {
    city: earliest.at('event-site').text,
    province: earliest.at('event-province').text,
    address_1: earliest.at('event-retail-location-id').text,
    country: 'Canada'
  }
end
origin_node(xml, location) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 691
def origin_node(xml, location)
  origin = location_from_hash(location)
  xml.public_send("origin-postal-code", get_sanitized_postal_code(origin))
end
parcel_node(xml, line_items, package = nil, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 671
def parcel_node(xml, line_items, package = nil, options = {})
  weight = sanitize_weight_kg(package && !package.kilograms.zero? ? package.kilograms.to_f : line_items.sum(&:kilograms).to_f)
  xml.public_send('parcel-characteristics') do
    xml.public_send('weight', "%#2.3f" % weight)
    if package
      pkg_dim = package.cm
      if pkg_dim && !pkg_dim.select { |x| x != 0 }.empty?
        xml.public_send('dimensions') do
          xml.public_send('length', '%.1f' % ((pkg_dim[2] * 10).round / 10.0)) if pkg_dim.size >= 3
          xml.public_send('width', '%.1f' % ((pkg_dim[1] * 10).round / 10.0)) if pkg_dim.size >= 2
          xml.public_send('height', '%.1f' % ((pkg_dim[0] * 10).round / 10.0)) if pkg_dim.size >= 1
        end
      end
    end
    xml.public_send('mailing-tube', line_items.any?(&:tube?))
    xml.public_send('oversized', true) if line_items.any?(&:oversized?)
    xml.public_send('unpackaged', line_items.any?(&:unpackaged?))
  end
end
quote_type_node(xml, options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 663
def quote_type_node(xml, options)
  xml.public_send("quote-type", 'commercial')
end
sanitize_price_from_cents(value) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 793
def sanitize_price_from_cents(value)
  value == 0 ? 0.01 : value.round / 100.0
end
sanitize_weight_kg(kg) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 789
def sanitize_weight_kg(kg)
  kg == 0 ? 0.001 : kg
end
services_node(xml, services) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 723
def services_node(xml, services)
  xml.public_send('services') do
    services.each { |code| xml.public_send('service-code', code) }
  end
end
services_url(country = nil, service_code = nil) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 625
def services_url(country = nil, service_code = nil)
  url = endpoint + "rs/ship/service"
  url += "/#{service_code}" if service_code
  url += "?country=#{country}" if country
  url
end
shipment_receipt_url(shipping_id, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 616
def shipment_receipt_url(shipping_id, options = {})
  raise MissingCustomerNumberError unless customer_number = options[:customer_number]
  if @platform_id.present?
    endpoint + "rs/#{customer_number}-#{@platform_id}/ncshipment/#{shipping_id}/receipt"
  else
    endpoint + "rs/#{customer_number}/ncshipment/#{shipping_id}/receipt"
  end
end
shipment_url(shipping_id, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 607
def shipment_url(shipping_id, options = {})
  raise MissingCustomerNumberError unless customer_number = options[:customer_number]
  if @platform_id.present?
    endpoint + "rs/#{customer_number}-#{@platform_id}/ncshipment/#{shipping_id}"
  else
    endpoint + "rs/#{customer_number}/ncshipment/#{shipping_id}"
  end
end
shipping_date(options) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 775
def shipping_date(options)
  DateTime.strptime((options[:shipping_date] || Time.now).to_s, "%Y-%m-%d")
end
shipping_options_node(xml, available_options, options = {}) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 729
def shipping_options_node(xml, available_options, options = {})
  return if (options.symbolize_keys.keys & available_options).empty?
  xml.public_send('options') do

    if options[:cod] && options[:cod_amount]
      xml.public_send('option') do
        xml.public_send('option-code', 'COD')
        xml.public_send('option-amount', options[:cod_amount])
        xml.public_send('option-qualifier-1', options[:cod_includes_shipping]) unless options[:cod_includes_shipping].blank?
        xml.public_send('option-qualifier-2', options[:cod_method_of_payment]) unless options[:cod_method_of_payment].blank?
      end
    end

    if options[:cov]
      xml.public_send('option') do
        xml.public_send('option-code', 'COV')
        xml.public_send('option-amount', options[:cov_amount]) unless options[:cov_amount].blank?
      end
    end

    if options[:d2po]
      xml.public_send('option') do
        xml.public_send('option-code', 'D2PO')
        xml.public_send('option-qualifier-2'. options[:d2po_office_id]) unless options[:d2po_office_id].blank?
      end
    end

    [:so, :dc, :pa18, :pa19, :hfp, :dns, :lad, :rase, :rts, :aban].each do |code|
      if options[code]
        xml.public_send('option') do
          xml.public_send('option-code', code.to_s.upcase)
        end
      end
    end
  end
end
time_of_occurrence(occurrence) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 809
def time_of_occurrence(occurrence)
  time = occurrence.at('event-time').text
  date = occurrence.at('event-date').text
  time_zone = occurrence.at('event-time-zone').text
  DateTime.parse "#{date} #{time} #{time_zone}"
end
tracking_url(pin) click to toggle source
# File lib/reactive_shipping/carriers/canada_post_pws.rb, line 587
def tracking_url(pin)
  case pin.length
  when 12, 13, 16
    "#{endpoint}vis/track/pin/#{pin}/detail"
  when 15
    "#{endpoint}vis/track/dnc/#{pin}/detail"
  else
    raise InvalidPinFormatError
  end
end