Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add support for Canada Post #43

Merged
merged 8 commits into from

3 participants

@j15e

*Revision of #41

Support for active_shipping Canada Post.

Includes national & worldwide rates, but not Canada-US rates, not really difficult to add if you want to.

Also minor change to ActiveShipping::Base :

  • Added the I18n.locale to the cache key as the API of CP is localized
  • Handle CP eParcel specific errors

This is the "old" API, but I think it is still the most widely used as it is more simple to user.

Canada Post old API documentation is available here (yeah, it is terrible) : http://sellonline.canadapost.ca/DevelopersResources/

jbourassa and others added some commits
@jbourassa jbourassa Add Canada Post national & international services
6e85e03
@jbourassa jbourassa Add specific error handling for Canada Post
36f1298
@jbourassa jbourassa Add french flag for Canada Post API
c70efbf
@jbourassa jbourassa Decode HTML Entities in rate hash.
Handy for bilingual XML, Canada Post returns some fancy xml such as
"Postes Canada Colis accélérés"
2ec0b19
@jbourassa jbourassa Fix for "Ready To Ship" in Canada Post API
be56eae
@jbourassa jbourassa Add locale to cache key
Allows Canada Post to recalculate values when the lang switches (service name and errors are localized)
6bcf653
@jbourassa jbourassa Make sure we return nil when there's no package.
Will prevent CanadaPost from firing a request with no lineitem that will crash.
8692710
@j15e j15e Fix for compatibility with 1.8.7 using iconv fallback 1a6946d
@j15e

20 to 8 more meaningful commits, I think its much better!

@j15e

Funny, just saw that right now the top news on Hacker News is Linus story about clean git history : http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html

@jumph4x

Good work.

@jumph4x

This is very interesting. Mind sharing why this exists?

Right on!

@jumph4x jumph4x merged commit e797dc2 into from
@j15e

Thanks for merging, it will sure helps lots of canadians to use Spree!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 4, 2012
  1. @jbourassa @j15e

    Add Canada Post national & international services

    jbourassa authored j15e committed
  2. @jbourassa @j15e

    Add specific error handling for Canada Post

    jbourassa authored j15e committed
  3. @jbourassa @j15e

    Add french flag for Canada Post API

    jbourassa authored j15e committed
  4. @jbourassa @j15e

    Decode HTML Entities in rate hash.

    jbourassa authored j15e committed
    Handy for bilingual XML, Canada Post returns some fancy xml such as
    "Postes Canada Colis accélérés"
  5. @jbourassa @j15e

    Fix for "Ready To Ship" in Canada Post API

    jbourassa authored j15e committed
  6. @jbourassa @j15e

    Add locale to cache key

    jbourassa authored j15e committed
    Allows Canada Post to recalculate values when the lang switches (service name and errors are localized)
  7. @jbourassa @j15e

    Make sure we return nil when there's no package.

    jbourassa authored j15e committed
    Will prevent CanadaPost from firing a request with no lineitem that will crash.
  8. @j15e
This page is out of date. Refresh to see the latest.
View
25 app/models/spree/calculator/active_shipping/base.rb
@@ -3,6 +3,7 @@
#
# Digest::MD5 is used for cache_key generation.
require 'digest/md5'
+require 'iconv' if RUBY_VERSION.to_f < 1.9
require_dependency 'spree/calculator'
module Spree
@@ -36,10 +37,14 @@ def compute(object)
:zip => addr.zipcode)
rates = Rails.cache.fetch(cache_key(order)) do
- rates = retrieve_rates(origin, destination, packages(order))
+ order_packages = packages(order)
+ if order_packages.empty?
+ {}
+ else
+ retrieve_rates(origin, destination, order_packages)
+ end
end
- return nil if rates.empty?
rate = rates[self.class.description]
return nil unless rate
rate = rate.to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0)
@@ -79,7 +84,16 @@ def retrieve_rates(origin, destination, packages)
begin
response = carrier.find_rates(origin, destination, packages)
# turn this beastly array into a nice little hash
- rate_hash = Hash[*response.rates.collect { |rate| [rate.service_name, rate.price] }.flatten]
+ rates = response.rates.collect do |rate|
+ # decode html entities for xml-based APIs, ie Canada Post
+ if RUBY_VERSION.to_f < 1.9
+ service_name = Iconv.iconv('UTF-8//IGNORE', 'UTF-8', rate.service_name).first
+ else
+ service_name = rate.service_name.encode("UTF-8")
+ end
+ [CGI.unescapeHTML(service_name), rate.price]
+ end
+ rate_hash = Hash[*rates.flatten]
return rate_hash
rescue ActiveMerchant::ActiveMerchantError => e
@@ -87,6 +101,9 @@ def retrieve_rates(origin, destination, packages)
params = e.response.params
if params.has_key?("Response") && params["Response"].has_key?("Error") && params["Response"]["Error"].has_key?("ErrorDescription")
message = params["Response"]["Error"]["ErrorDescription"]
+ # Canada Post specific error message
+ elsif params.has_key?("eparcel") && params["eparcel"].has_key?("error") && params["eparcel"]["error"].has_key?("statusMessage")
+ message = e.response.params["eparcel"]["error"]["statusMessage"]
else
message = e.message
end
@@ -185,7 +202,7 @@ def packages(order)
def cache_key(order)
addr = order.ship_address
line_items_hash = Digest::MD5.hexdigest(order.line_items.map {|li| li.variant_id.to_s + "_" + li.quantity.to_s }.join("|"))
- @cache_key = "#{carrier.name}-#{order.number}-#{addr.country.iso}-#{addr.state ? addr.state.abbr : addr.state_name}-#{addr.city}-#{addr.zipcode}-#{line_items_hash}".gsub(" ","")
+ @cache_key = "#{carrier.name}-#{order.number}-#{addr.country.iso}-#{addr.state ? addr.state.abbr : addr.state_name}-#{addr.city}-#{addr.zipcode}-#{line_items_hash}-#{I18n.locale}".gsub(" ","")
end
end
end
View
17 app/models/spree/calculator/canada_post/base.rb
@@ -0,0 +1,17 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class Base < Spree::Calculator::ActiveShipping::Base
+ def carrier
+ canada_post_options = {
+ :login => Spree::ActiveShipping::Config[:canada_post_login],
+ :french => (I18n.locale.to_sym == :fr)
+ }
+ ActiveMerchant::Shipping::CanadaPost.new(canada_post_options)
+ end
+ end
+ end
+ end
+end
View
13 app/models/spree/calculator/canada_post/expedited.rb
@@ -0,0 +1,13 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class Expedited < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.expedited")
+ end
+ end
+ end
+ end
+end
View
14 app/models/spree/calculator/canada_post/parcel_surface.rb
@@ -0,0 +1,14 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class ParcelSurface < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.parcel_surface")
+ end
+ end
+ end
+ end
+end
+
View
14 app/models/spree/calculator/canada_post/priority_worldwide_intl.rb
@@ -0,0 +1,14 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class PriorityWorldwideIntl < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.priority_worldwide_intl")
+ end
+ end
+ end
+ end
+end
+
View
13 app/models/spree/calculator/canada_post/regular.rb
@@ -0,0 +1,13 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class Regular < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.regular")
+ end
+ end
+ end
+ end
+end
View
14 app/models/spree/calculator/canada_post/small_packets_air.rb
@@ -0,0 +1,14 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class SmallPacketsAir < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.small_packets_air")
+ end
+ end
+ end
+ end
+end
+
View
14 app/models/spree/calculator/canada_post/small_packets_surface.rb
@@ -0,0 +1,14 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class SmallPacketsSurface < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.small_packets_surface")
+ end
+ end
+ end
+ end
+end
+
View
14 app/models/spree/calculator/canada_post/xpresspost.rb
@@ -0,0 +1,14 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class Xpresspost < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.xpresspost")
+ end
+ end
+ end
+ end
+end
+
View
14 app/models/spree/calculator/canada_post/xpresspost_international.rb
@@ -0,0 +1,14 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator < ActiveRecord::Base
+ module CanadaPost
+ class XpresspostInternational < Calculator::CanadaPost::Base
+ def self.description
+ I18n.t("canada_post.xpresspost_international")
+ end
+ end
+ end
+ end
+end
+
View
9 config/locales/en.yml
@@ -48,4 +48,13 @@ en:
priority_mail_international_small_flat_rate_box: "USPS Priority Mail International Small Flat Rate Box"
priority_mail_international_medium_flat_rate_box: "USPS Priority Mail International Medium Flat Rate Box"
priority_mail_international_large_flat_rate_box: "USPS Priority Mail International Large Flat Rate Box"
+ canada_post:
+ regular: "Canada Post Regular"
+ expedited: "Canada Post Expedited"
+ xpresspost: "Canada Post Xpresspost"
+ priority_worldwide_intl: "Canada Post Priority Worldwide INTL"
+ xpresspost_international: "Canada Post XPressPost International"
+ small_packets_air: "Canada Post Small Packets Air"
+ parcel_surface: "Canada Post Parcel Surface"
+ small_packets_surface: "Canada Post Small Packets Surface"
shipping_error: "Shipping Error"
View
12 config/locales/fr.yml
@@ -0,0 +1,12 @@
+---
+fr:
+ canada_post:
+ regular: "Postes Canada Colis standard"
+ expedited: "Postes Canada Colis accélérés"
+ xpresspost: "Postes Canada Xpresspost"
+ priority_worldwide_intl: "Postes Canada Priorité Mondial INTL"
+ xpresspost_international: "Postes Canada XPressPost International"
+ small_packets_air: "Postes Canada Petits Colis - Air"
+ parcel_surface: "Postes Canada Colis Postaux - Surface"
+ small_packets_surface: "Postes Canada Petits Colis - Surface"
+ shipping_error: "Erreur de livraison"
View
47 lib/spree/active_shipping/canada_post_override.rb
@@ -0,0 +1,47 @@
+module Spree
+ module ActiveShipping
+ # Override to fix a bug with readToShip : sending ready_to_ship: nil will
+ # add a <readyToShip/> tag, which means true. The solution is to not
+ # include the tag.
+ module CanadaPostOverride
+ def self.included(base)
+
+ base.class_eval do
+
+ # <!-- List of items in the shopping -->
+ # <!-- cart -->
+ # <!-- Each item is defined by : -->
+ # <!-- - quantity (mandatory) -->
+ # <!-- - size (mandatory) -->
+ # <!-- - weight (mandatory) -->
+ # <!-- - description (mandatory) -->
+ # <!-- - ready to ship (optional) -->
+
+ def build_line_items(line_items)
+ xml_line_items = XmlNode.new('lineItems') do |line_items_node|
+
+ line_items.each do |line_item|
+
+ line_items_node << XmlNode.new('item') do |item|
+ item << XmlNode.new('quantity', 1)
+ item << XmlNode.new('weight', line_item.kilograms)
+ item << XmlNode.new('length', line_item.cm(:length).to_s)
+ item << XmlNode.new('width', line_item.cm(:width).to_s)
+ item << XmlNode.new('height', line_item.cm(:height).to_s)
+ item << XmlNode.new('description', line_item.options[:description] || ' ')
+ # Only this line got changed
+ item << XmlNode.new('readyToShip') if line_item.options[:ready_to_ship]
+
+ # 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
+
+ xml_line_items
+ end
+ end
+ end
+ end
+ end
+end
+
View
2  lib/spree/active_shipping_configuration.rb
@@ -12,6 +12,8 @@ class Spree::ActiveShippingConfiguration < Spree::Preferences::Configuration
preference :usps_login, :string, :default => "aunt_judy"
+ preference :canada_post_login, :string, :default => "canada_post_login"
+
preference :origin_country, :string, :default => "US"
preference :origin_state, :string, :default => "PA"
preference :origin_city, :string, :default => "University Park"
View
6 lib/spree_active_shipping/engine.rb
@@ -19,6 +19,9 @@ def self.activate
#Only required until following active_shipping commit is merged (add negotiated rates).
#http://github.com/BDQ/active_shipping/commit/2f2560d53aa7264383e5a35deb7264db60eb405a
ActiveMerchant::Shipping::UPS.send(:include, Spree::ActiveShipping::UpsOverride)
+
+ # Fix Canada Post "Ready to ship" package
+ ActiveMerchant::Shipping::CanadaPost.send(:include, Spree::ActiveShipping::CanadaPostOverride)
end
config.autoload_paths += %W(#{config.root}/lib)
@@ -32,7 +35,8 @@ def self.activate
app.config.spree.calculators.shipping_methods.concat(
Spree::Calculator::Fedex::Base.descendants +
Spree::Calculator::Ups::Base.descendants +
- Spree::Calculator::Usps::Base.descendants
+ Spree::Calculator::Usps::Base.descendants +
+ Spree::Calculator::CanadaPost::Base.descendants
)
end
end
Something went wrong with that request. Please try again.