spree_active_shipping 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +67 -0
  3. data/Rakefile +45 -0
  4. data/app/controllers/admin/shipments_controller_decorator.rb +11 -0
  5. data/app/controllers/admin/shipping_methods_controller_decorator.rb +15 -0
  6. data/app/controllers/checkout_controller_decorator.rb +11 -0
  7. data/app/models/calculator/active_shipping.rb +126 -0
  8. data/app/models/calculator/fedex/base.rb +8 -0
  9. data/app/models/calculator/fedex/base.rb~ +7 -0
  10. data/app/models/calculator/fedex/express_saver.rb +5 -0
  11. data/app/models/calculator/fedex/first_overnight.rb +5 -0
  12. data/app/models/calculator/fedex/ground.rb +5 -0
  13. data/app/models/calculator/fedex/ground_home_delivery.rb +5 -0
  14. data/app/models/calculator/fedex/international_economy.rb +5 -0
  15. data/app/models/calculator/fedex/international_economy_freight.rb +5 -0
  16. data/app/models/calculator/fedex/international_first.rb +5 -0
  17. data/app/models/calculator/fedex/international_ground.rb +5 -0
  18. data/app/models/calculator/fedex/international_priority.rb +5 -0
  19. data/app/models/calculator/fedex/international_priority_freight.rb +5 -0
  20. data/app/models/calculator/fedex/international_priority_saturday_delivery.rb +5 -0
  21. data/app/models/calculator/fedex/one_day_freight.rb +5 -0
  22. data/app/models/calculator/fedex/one_day_freight_saturday_delivery.rb +5 -0
  23. data/app/models/calculator/fedex/priority_overnight.rb +5 -0
  24. data/app/models/calculator/fedex/priority_overnight_saturday_delivery.rb +5 -0
  25. data/app/models/calculator/fedex/saver.rb +5 -0
  26. data/app/models/calculator/fedex/saver.rb~ +5 -0
  27. data/app/models/calculator/fedex/standard_overnight.rb +5 -0
  28. data/app/models/calculator/fedex/three_day_freight.rb +5 -0
  29. data/app/models/calculator/fedex/three_day_freight_saturday_delivery.rb +5 -0
  30. data/app/models/calculator/fedex/two_day.rb +5 -0
  31. data/app/models/calculator/fedex/two_day_freight.rb +5 -0
  32. data/app/models/calculator/fedex/two_day_freight_saturday_delivery.rb +5 -0
  33. data/app/models/calculator/fedex/two_day_saturday_delivery.rb +5 -0
  34. data/app/models/calculator/ups/base.rb +15 -0
  35. data/app/models/calculator/ups/ground.rb +5 -0
  36. data/app/models/calculator/ups/next_day_air.rb +5 -0
  37. data/app/models/calculator/ups/next_day_air_early_am.rb +5 -0
  38. data/app/models/calculator/ups/next_day_air_saver.rb +5 -0
  39. data/app/models/calculator/ups/saver.rb +5 -0
  40. data/app/models/calculator/ups/second_day_air.rb +5 -0
  41. data/app/models/calculator/ups/three_day_select.rb +5 -0
  42. data/app/models/calculator/ups/worldwide_expedited.rb +5 -0
  43. data/app/models/calculator/usps/base.rb +5 -0
  44. data/app/models/calculator/usps/express_mail.rb +5 -0
  45. data/app/models/calculator/usps/express_mail_international.rb +5 -0
  46. data/app/models/calculator/usps/media_mail.rb +5 -0
  47. data/app/models/calculator/usps/priority_mail.rb +5 -0
  48. data/app/models/calculator/usps/priority_mail_international.rb +5 -0
  49. data/app/models/calculator/usps/priority_mail_large_flat_rate_box.rb +5 -0
  50. data/app/models/calculator/usps/priority_mail_regular_medium_flat_rate_boxes.rb +5 -0
  51. data/app/models/calculator/usps/priority_mail_small_flat_rate_box.rb +5 -0
  52. data/app/views/admin/shipping_methods/_form.html.erb +42 -0
  53. data/config/locales/en.yml +3 -0
  54. data/lib/active_shipping_configuration.rb +25 -0
  55. data/lib/spree/active_shipping/config.rb +22 -0
  56. data/lib/spree/active_shipping/ups_override.rb +274 -0
  57. data/lib/spree/shipping_error.rb +3 -0
  58. data/lib/spree_active_shipping.rb +74 -0
  59. data/lib/tasks/active_shipping_extension_tasks.rake +29 -0
  60. data/spec/lib/spree/bogus_calculator.rb +11 -0
  61. data/spec/lib/spree/bogus_carrier.rb +15 -0
  62. data/spec/models/active_shipping_calculator_spec.rb +51 -0
  63. data/spec/spec.opts +6 -0
  64. data/spec/spec_helper.rb +33 -0
  65. data/spree_active_shipping.gemspec +23 -0
  66. metadata +184 -0
@@ -0,0 +1,2 @@
1
+ spec/test_app
2
+ *.swp
@@ -0,0 +1,67 @@
1
+ Active Shipping
2
+ ===============
3
+
4
+ This is a Spree extension that wraps the popular [active_shipping](http://github.com/Shopify/active_shipping/tree/master) plugin.
5
+
6
+ UPS
7
+ ---
8
+
9
+ You will need a UPS developer account to get rate quotes (even in development and test mode.) The UPS calculators also require that you set the following configuration properties. You will also need a USPS account to use USPS rate quote system. Note: acquiring the USPS developer account is easy but has a confusion activation step. Look for related sicussion in ActiveShipping's original discussion group as well as carefully read your USPS confirmation email.
10
+
11
+ <pre>
12
+ Spree::ActiveShipping::Config[:origin_country]
13
+ Spree::ActiveShipping::Config[:origin_city]
14
+ Spree::ActiveShipping::Config[:origin_state]
15
+ Spree::ActiveShipping::Config[:origin_zip]
16
+ Spree::ActiveShipping::Config[:ups_login]
17
+ Spree::ActiveShipping::Config[:ups_password]
18
+ Spree::ActiveShipping::Config[:ups_key]
19
+ Spree::ActiveShipping::Config[:usps_login]
20
+ </pre>
21
+
22
+ It will soon be possible to set these properties through a new admin configuration screen (even sooner if someone else writes the patch!)
23
+
24
+ Global Handling Fee
25
+ -------------------
26
+
27
+ <pre>
28
+ Spree::ActiveShipping::Config[:handling_fee]
29
+ </pre>
30
+
31
+ This property allows you to set a global handling fee that will be added to all calculated shipping rates. Specify the number of cents, not dollars.
32
+
33
+ Using Migrations
34
+ ----------------
35
+
36
+ If you'd like to set your shipping configuration as part of a migration you could add something like this to your site extension.
37
+
38
+ <pre>
39
+ class AddUpsConfiguration < ActiveRecord::Migration
40
+ def self.up
41
+ Spree::ActiveShipping::Config.set(:ups_login => "dpbrowning")
42
+ Spree::ActiveShipping::Config.set(:ups_password => "doublewide")
43
+ Spree::ActiveShipping::Config.set(:ups_key => "5B8DDE509EFDA5D6")
44
+ Spree::ActiveShipping::Config.set(:usps_login => "13658997AOE2568XOE")
45
+ end
46
+
47
+ def self.down
48
+ end
49
+ end
50
+ </pre>
51
+
52
+ Installation
53
+ ------------
54
+
55
+ 1. Add the following to your applications Gemfile
56
+
57
+ gem 'spree_active_shipping'
58
+
59
+ 2. Run bundler
60
+
61
+ bundle install
62
+
63
+
64
+ Further Reading
65
+ ---------------
66
+
67
+ Andrea Singh has also written an excellent [blog post](http://blog.madebydna.com/all/code/2010/05/26/setting-up-usps-shipping-with-spree.html) covering the use of this extension in detail.
@@ -0,0 +1,45 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new
6
+
7
+ require 'cucumber/rake/task'
8
+ Cucumber::Rake::Task.new do |t|
9
+ t.cucumber_opts = %w{--format pretty}
10
+ end
11
+
12
+ desc "Regenerates a rails 3 app for testing"
13
+ task :test_app do
14
+ SPREE_PATH = ENV['SPREE_PATH']
15
+ raise "SPREE_PATH should be specified" unless SPREE_PATH
16
+ require File.join(SPREE_PATH, 'lib/generators/spree/test_app_generator')
17
+ class SpreeActiveShippingTestAppGenerator < Spree::Generators::TestAppGenerator
18
+ def tweak_gemfile
19
+ append_file 'Gemfile' do
20
+ <<-gems
21
+ gem 'spree_core', :path => '#{File.join(SPREE_PATH, 'core')}'
22
+ gem 'spree_active_shipping', :path => '#{File.dirname(__FILE__)}'
23
+ gems
24
+ end
25
+ end
26
+
27
+ def install_gems
28
+ inside "test_app" do
29
+ run 'rake spree_core:install'
30
+ end
31
+ end
32
+
33
+ def migrate_db
34
+ run_migrations
35
+ end
36
+ end
37
+ SpreeActiveShippingTestAppGenerator.start
38
+ end
39
+
40
+ namespace :test_app do
41
+ desc 'Rebuild test and cucumber databases'
42
+ task :rebuild_dbs do
43
+ system("cd spec/test_app && rake db:drop db:migrate RAILS_ENV=test && rake db:drop db:migrate RAILS_ENV=cucumber")
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ # handle shipping errors gracefully on admin ui
2
+ Admin::ShipmentsController.class_eval do
3
+ rescue_from Spree::ShippingError, :with => :handle_shipping_error
4
+
5
+ private
6
+ def handle_shipping_error(e)
7
+ load_object
8
+ flash.now[:error] = e.message
9
+ render :action => "edit"
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ Admin::ShippingMethodsController.class_eval do
2
+
3
+ before_filter :load_data
4
+
5
+ private
6
+
7
+ # overriding to return an array of Calculator objects instead of a Set
8
+ def load_data
9
+ @available_zones = Zone.find :all, :order => :name
10
+ @calculators = []
11
+ ShippingMethod.calculators.each {|calc|
12
+ @calculators << eval(calc.name).new
13
+ }
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # handle shipping errors gracefully during checkout
2
+ CheckoutController.class_eval do
3
+
4
+ rescue_from Spree::ShippingError, :with => :handle_shipping_error
5
+
6
+ private
7
+ def handle_shipping_error(e)
8
+ flash[:error] = e.message
9
+ redirect_to checkout_state_path(:address)
10
+ end
11
+ end
@@ -0,0 +1,126 @@
1
+ # This is a base calculator for shipping calcualations using the ActiveShipping plugin. It is not intended to be
2
+ # instantiated directly. Create sublcass for each specific shipping method you wish to support instead.
3
+ class Calculator::ActiveShipping < Calculator
4
+
5
+ include ActiveMerchant::Shipping
6
+
7
+ def self.register
8
+ super
9
+ ShippingMethod.register_calculator(self)
10
+ end
11
+
12
+ def compute(object)
13
+ if object.is_a?(Array)
14
+ order = object.first.order
15
+ elsif object.is_a?(Shipment)
16
+ order = object.order
17
+ else
18
+ order = object
19
+ end
20
+ origin= Location.new(:country => Spree::ActiveShipping::Config[:origin_country],
21
+ :city => Spree::ActiveShipping::Config[:origin_city],
22
+ :state => Spree::ActiveShipping::Config[:origin_state],
23
+ :zip => Spree::ActiveShipping::Config[:origin_zip])
24
+
25
+ addr = order.ship_address
26
+
27
+ destination = Location.new(:country => addr.country.iso,
28
+ :state => (addr.state ? addr.state.abbr : addr.state_name),
29
+ :city => addr.city,
30
+ :zip => addr.zipcode)
31
+
32
+ rates = Rails.cache.fetch(cache_key(order)) do
33
+ rates = retrieve_rates(origin, destination, packages(order))
34
+ end
35
+
36
+ return nil if rates.empty?
37
+ rate = rates[self.class.description].to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0)
38
+ return nil unless rate
39
+ # divide by 100 since active_shipping rates are expressed as cents
40
+ return rate/100.0
41
+ end
42
+
43
+
44
+ def timing(line_items)
45
+ order = line_items.first.order
46
+ origin = Location.new(:country => Spree::ActiveShipping::Config[:origin_country],
47
+ :city => Spree::ActiveShipping::Config[:origin_city],
48
+ :state => Spree::ActiveShipping::Config[:origin_state],
49
+ :zip => Spree::ActiveShipping::Config[:origin_zip])
50
+ addr = order.ship_address
51
+ destination = Location.new(:country => addr.country.iso,
52
+ :state => (addr.state ? addr.state.abbr : addr.state_name),
53
+ :city => addr.city,
54
+ :zip => addr.zipcode)
55
+ timings = Rails.cache.fetch(cache_key(line_items)+"-timings") do
56
+ timings = retrieve_timings(origin, destination, packages(order))
57
+ end
58
+ return nil if timings.nil? || !timings.is_a?(Hash) || timings.empty?
59
+ return timings[self.description]
60
+
61
+ end
62
+
63
+ private
64
+ def retrieve_rates(origin, destination, packages)
65
+ begin
66
+ response = carrier.find_rates(origin, destination, packages)
67
+ # turn this beastly array into a nice little hash
68
+ rate_hash = Hash[*response.rates.collect { |rate| [rate.service_name, rate.price] }.flatten]
69
+ return rate_hash
70
+ rescue ActiveMerchant::ActiveMerchantError => e
71
+
72
+ if [ActiveMerchant::ResponseError, ActiveMerchant::Shipping::ResponseError].include? e.class
73
+ params = e.response.params
74
+ if params.has_key?("Response") && params["Response"].has_key?("Error") && params["Response"]["Error"].has_key?("ErrorDescription")
75
+ message = params["Response"]["Error"]["ErrorDescription"]
76
+ else
77
+ message = e.message
78
+ end
79
+ else
80
+ message = e.to_s
81
+ end
82
+
83
+ Rails.cache.write @cache_key, {} #write empty hash to cache to prevent constant re-lookups
84
+
85
+ raise Spree::ShippingError.new("#{I18n.t('shipping_error')}: #{message}")
86
+ end
87
+
88
+ end
89
+
90
+
91
+ def retrieve_timings(origin, destination, packages)
92
+ begin
93
+ if carrier.respond_to?(:find_time_in_transit)
94
+ response = carrier.find_time_in_transit(origin, destination, packages)
95
+ return response
96
+ end
97
+ rescue ActiveMerchant::Shipping::ResponseError => re
98
+ params = re.response.params
99
+ if params.has_key?("Response") && params["Response"].has_key?("Error") && params["Response"]["Error"].has_key?("ErrorDescription")
100
+ message = params["Response"]["Error"]["ErrorDescription"]
101
+ elsee
102
+ message = re.message
103
+ end
104
+ Rails.cache.write @cache_key+'-', {} #write empty hash to cache to prevent constant re-lookups
105
+ raise Spree::ShippingError.new("#{I18n.t('shipping_error')}: #{message}")
106
+ end
107
+ end
108
+
109
+
110
+ private
111
+
112
+ # Generates an array of Package objects based on the quantities and weights of the variants in the line items
113
+ def packages(order)
114
+ multiplier = Spree::ActiveShipping::Config[:unit_multiplier]
115
+ weight = order.line_items.inject(0) do |weight, line_item|
116
+ weight + (line_item.variant.weight ? (line_item.quantity * line_item.variant.weight * multiplier) : 0)
117
+ end
118
+ package = Package.new(weight, [], :units => Spree::ActiveShipping::Config[:units].to_sym)
119
+ [package]
120
+ end
121
+
122
+ def cache_key(order)
123
+ addr = order.ship_address
124
+ @cache_key = "#{carrier.name}-#{order.number}-#{addr.country.iso}-#{addr.state ? addr.state.abbr : addr.state_name}-#{addr.city}-#{addr.zipcode}-#{order.line_items.map {|li| li.variant_id.to_s + "_" + li.quantity.to_s }.join("|")}".gsub(" ","")
125
+ end
126
+ end
@@ -0,0 +1,8 @@
1
+ class Calculator::Fedex::Base < Calculator::ActiveShipping
2
+ def carrier
3
+ ActiveMerchant::Shipping::FedEx.new(:key => Spree::ActiveShipping::Config[:fedex_key],
4
+ :password => Spree::ActiveShipping::Config[:fedex_password],
5
+ :account => Spree::ActiveShipping::Config[:fedex_account],
6
+ :login => Spree::ActiveShipping::Config[:fedex_login])
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class Calculator::Fedex::Base < Calculator::ActiveShipping
2
+ def carrier
3
+ ActiveMerchant::Shipping::FEDEX.new(:login => Spree::ActiveShipping::Config[:fedex_login],
4
+ :password => Spree::ActiveShipping::Config[:fedex_password],
5
+ :key => Spree::ActiveShipping::Config[:fedex_key])
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::ExpressSaver < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Express Saver"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::FirstOvernight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx First Overnight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::Ground < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Ground"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::GroundHomeDelivery < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Ground Home Delivery"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalEconomy < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International Economy"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalEconomyFreight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International Economy Freight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalFirst < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International First"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalGround < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International Ground"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalPriority < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International Priority"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalPriorityFreight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International Priority Freight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::InternationalPrioritySaturdayDelivery < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx International Priority Saturday Delivery"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::OneDayFreight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx 1 Day Freight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::OneDayFreightSaturdayDelivery < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx 1 Day Freight Saturday Delivery"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::PriorityOvernight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Priority Overnight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::PriorityOvernightSaturdayDelivery < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Priority Overnight Saturday Delivery"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::Saver < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Saver"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Ups::Saver < Calculator::Ups::Base
2
+ def self.description
3
+ "UPS Saver"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::StandardOvernight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx Standard Overnight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::ThreeDayFreight < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx 3 Day Freight"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::ThreeDayFreightSaturdayDelivery < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx 3 Day Freight Saturday Delivery"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Calculator::Fedex::TwoDay < Calculator::Fedex::Base
2
+ def self.description
3
+ "FedEx 2 Day"
4
+ end
5
+ end