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.
- data/.gitignore +2 -0
- data/README.md +67 -0
- data/Rakefile +45 -0
- data/app/controllers/admin/shipments_controller_decorator.rb +11 -0
- data/app/controllers/admin/shipping_methods_controller_decorator.rb +15 -0
- data/app/controllers/checkout_controller_decorator.rb +11 -0
- data/app/models/calculator/active_shipping.rb +126 -0
- data/app/models/calculator/fedex/base.rb +8 -0
- data/app/models/calculator/fedex/base.rb~ +7 -0
- data/app/models/calculator/fedex/express_saver.rb +5 -0
- data/app/models/calculator/fedex/first_overnight.rb +5 -0
- data/app/models/calculator/fedex/ground.rb +5 -0
- data/app/models/calculator/fedex/ground_home_delivery.rb +5 -0
- data/app/models/calculator/fedex/international_economy.rb +5 -0
- data/app/models/calculator/fedex/international_economy_freight.rb +5 -0
- data/app/models/calculator/fedex/international_first.rb +5 -0
- data/app/models/calculator/fedex/international_ground.rb +5 -0
- data/app/models/calculator/fedex/international_priority.rb +5 -0
- data/app/models/calculator/fedex/international_priority_freight.rb +5 -0
- data/app/models/calculator/fedex/international_priority_saturday_delivery.rb +5 -0
- data/app/models/calculator/fedex/one_day_freight.rb +5 -0
- data/app/models/calculator/fedex/one_day_freight_saturday_delivery.rb +5 -0
- data/app/models/calculator/fedex/priority_overnight.rb +5 -0
- data/app/models/calculator/fedex/priority_overnight_saturday_delivery.rb +5 -0
- data/app/models/calculator/fedex/saver.rb +5 -0
- data/app/models/calculator/fedex/saver.rb~ +5 -0
- data/app/models/calculator/fedex/standard_overnight.rb +5 -0
- data/app/models/calculator/fedex/three_day_freight.rb +5 -0
- data/app/models/calculator/fedex/three_day_freight_saturday_delivery.rb +5 -0
- data/app/models/calculator/fedex/two_day.rb +5 -0
- data/app/models/calculator/fedex/two_day_freight.rb +5 -0
- data/app/models/calculator/fedex/two_day_freight_saturday_delivery.rb +5 -0
- data/app/models/calculator/fedex/two_day_saturday_delivery.rb +5 -0
- data/app/models/calculator/ups/base.rb +15 -0
- data/app/models/calculator/ups/ground.rb +5 -0
- data/app/models/calculator/ups/next_day_air.rb +5 -0
- data/app/models/calculator/ups/next_day_air_early_am.rb +5 -0
- data/app/models/calculator/ups/next_day_air_saver.rb +5 -0
- data/app/models/calculator/ups/saver.rb +5 -0
- data/app/models/calculator/ups/second_day_air.rb +5 -0
- data/app/models/calculator/ups/three_day_select.rb +5 -0
- data/app/models/calculator/ups/worldwide_expedited.rb +5 -0
- data/app/models/calculator/usps/base.rb +5 -0
- data/app/models/calculator/usps/express_mail.rb +5 -0
- data/app/models/calculator/usps/express_mail_international.rb +5 -0
- data/app/models/calculator/usps/media_mail.rb +5 -0
- data/app/models/calculator/usps/priority_mail.rb +5 -0
- data/app/models/calculator/usps/priority_mail_international.rb +5 -0
- data/app/models/calculator/usps/priority_mail_large_flat_rate_box.rb +5 -0
- data/app/models/calculator/usps/priority_mail_regular_medium_flat_rate_boxes.rb +5 -0
- data/app/models/calculator/usps/priority_mail_small_flat_rate_box.rb +5 -0
- data/app/views/admin/shipping_methods/_form.html.erb +42 -0
- data/config/locales/en.yml +3 -0
- data/lib/active_shipping_configuration.rb +25 -0
- data/lib/spree/active_shipping/config.rb +22 -0
- data/lib/spree/active_shipping/ups_override.rb +274 -0
- data/lib/spree/shipping_error.rb +3 -0
- data/lib/spree_active_shipping.rb +74 -0
- data/lib/tasks/active_shipping_extension_tasks.rake +29 -0
- data/spec/lib/spree/bogus_calculator.rb +11 -0
- data/spec/lib/spree/bogus_carrier.rb +15 -0
- data/spec/models/active_shipping_calculator_spec.rb +51 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +33 -0
- data/spree_active_shipping.gemspec +23 -0
- metadata +184 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|