Refactoring digital shipping calculation and display
* removed the deface override that modified the checkout/_delivery partial * created custom digital delivery calculator * removed digital_shipping_method from Spree::Order. Moved logic to available_shipping_methods monkey patch The main goal was to eliminate the shipping method detection logic from the _delivery partial. This was a bit of a hack all along and didn't really present the correct information to the underlying order logic (i.e. rate_hash.blank? would return true when there was no shipping options available). This should be more future proof and make overriding another aspect of the delivery process easier.
This commit is contained in:
@@ -2,19 +2,17 @@ h1. Spree Digital
|
|||||||
|
|
||||||
This is a spree extension to enable downloadable products. The design goal is to keep it robust. It should survive future spree versions out of the box.
|
This is a spree extension to enable downloadable products. The design goal is to keep it robust. It should survive future spree versions out of the box.
|
||||||
|
|
||||||
NOTE: This is still under development, but since things were falling into place, I thought I'd publish it.
|
The master branch is compatible with Spree 1.1.x, <= 1.0 versions are referenced in the Versionfile.
|
||||||
|
|
||||||
The master branch may work with spree 1.0, but the latest confirmed working version if referenced in the Versionfile.
|
The idea is simple. You attach a file to a Product (or a Variant of this Product) and when people buy it, they will receive a link via email where they can download it once. There are a few assumptions that spree_digital (currently) makes and it's important to be aware of them.
|
||||||
|
|
||||||
The idea is simple. You attach a file to a Product (or a Variant of this Product) and when people buy it, they will receive a link via email where they can download it once. There are a few assumptions that spree_digital (currently) makes and it's important to be aware of them. These might change, but since I programmed digital_spree over night, this is what you get ;)
|
|
||||||
|
|
||||||
* The table structure of spree_core is not touched. Spree_digital lives parallel to spree_core and does not do any changes to your existing database, except adding two new tables.
|
* The table structure of spree_core is not touched. Spree_digital lives parallel to spree_core and does not do any changes to your existing database, except adding two new tables.
|
||||||
* The download links will be sent via email in the order confirmation (or "resend" from the admin section). The links do *not* appear in the order "overview" that the customer sees.
|
* The download links will be sent via email in the order confirmation (or "resend" from the admin section). The links do *not* appear in the order "overview" that the customer sees.
|
||||||
* There should only be "live" payments. Once the order is checked-out, the download links will immediately be sent (i.e. in the order confirmation).
|
* There should only be "live" payments. Once the order is checked-out, the download links will immediately be sent (i.e. in the order confirmation).
|
||||||
* You should define a shipping method called "download" and it should be cost-free. Spree_digital will use that one when all products in the cart are digital
|
* You should create a ShippingMethod based on the Digital Delivery calculator type. The default cost for digital delivery is 0, but you can define a flat rate (creating a per-item digital delivery fee would be possible as well).
|
||||||
* One may buy several items of the same digital product in one cart. The customer will simply receive several links by doing so. This allows customer's to legally purchase multiple copies of the same product and maybe give one away to a friend.
|
* One may buy several items of the same digital product in one cart. The customer will simply receive several links by doing so. This allows customer's to legally purchase multiple copies of the same product and maybe give one away to a friend.
|
||||||
* The links will only work 3 times (but we tell the customer that it works only once). After 24 hours the links are deactivated. This should keep you reasonably free from complaints of people who are not capable of appropriately clicking on a link. They can try a whole day long, 3 times per link. The file should really be downloadable for weird customers. I understand that this is a little bit security by obscurity, but it's better than other solutions that I've seen.
|
* The links will only work 3 times (but we tell the customer that it works only once). After 24 hours the links are deactivated. This should keep you reasonably free from complaints of people who are not capable of appropriately clicking on a link. They can try a whole day long, 3 times per link. The file should really be downloadable for weird customers. I understand that this is a little bit security by obscurity, but it's better than other solutions that I've seen. The number of clicks and days the links are valid for is customizable via preferences.
|
||||||
* The file @views/order_mailer/confirm_email.text.erb@ is the only thing that should need customization. But since I assume you do that anyway, it doesn't hurt to do it when you're using spree_digital. The reason is that the download links are added to the confirmation email to the customer.
|
* The file @views/order_mailer/confirm_email.text.erb@ is the only thing that should need customization. But since I assume you do that anyway, it doesn't hurt to do it when you're using spree_digital. The reason is that the download links are added to the confirmation email to the customer. "http://github.com/iloveitaly/spree-html-email":http://github.com/iloveitaly/spree-html-email also supports spree_digital.
|
||||||
* A purchased product can be downloaded even if you disable the product immediately. You would have to remove the attached file in your admin section to prevent people from downloading purchased products.
|
* A purchased product can be downloaded even if you disable the product immediately. You would have to remove the attached file in your admin section to prevent people from downloading purchased products.
|
||||||
* File are uploaded to @rails_root/private@. Make sure it's symlinked in case you're using Capistrano.
|
* File are uploaded to @rails_root/private@. Make sure it's symlinked in case you're using Capistrano.
|
||||||
* We use send_file to send the files on download. Yes, it goes through the entire stack right now.
|
* We use send_file to send the files on download. Yes, it goes through the entire stack right now.
|
||||||
@@ -40,7 +38,7 @@ This should be all there is to do.
|
|||||||
|
|
||||||
h3. Important!
|
h3. Important!
|
||||||
|
|
||||||
You should go to the spree admin section and create a shipping method that has the word @download@ somehow in its name (it should be cost-free, but it doesn't have to). It will be detected by spree_digital. Otherwise your customer will be forced to choose something like "UPS" even if they purchase only downloadable products.
|
You should create a ShippingMethod based on the Digital Delivery calculator type. It will be detected by spree_digital. Otherwise your customer will be forced to choose something like "UPS" even if they purchase only downloadable products.
|
||||||
|
|
||||||
h2. Usage
|
h2. Usage
|
||||||
|
|
||||||
@@ -87,6 +85,11 @@ Bring up the test application (you only need to do this whenever you fiddle arou
|
|||||||
|
|
||||||
This link may be very helpful to you: "http://github.com/spree/spree":http://github.com/spree/spree
|
This link may be very helpful to you: "http://github.com/spree/spree":http://github.com/spree/spree
|
||||||
|
|
||||||
|
h3. Authors
|
||||||
|
|
||||||
|
* "iloveitaly":http://github.com/iloveitaly/
|
||||||
|
* "funkensturm":http://github.com/funkensturm
|
||||||
|
|
||||||
h3. License
|
h3. License
|
||||||
|
|
||||||
Copyright (c) 2011 funkensturm.
|
Copyright (c) 2011 funkensturm.
|
||||||
|
|||||||
18
app/models/spree/calculator/digital_delivery.rb
Normal file
18
app/models/spree/calculator/digital_delivery.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# https://github.com/spree/spree/issues/1439
|
||||||
|
require_dependency 'spree/calculator'
|
||||||
|
|
||||||
|
module Spree
|
||||||
|
class Calculator::DigitalDelivery < Calculator::FlatRate
|
||||||
|
def self.description
|
||||||
|
I18n.t(:digital_delivery)
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute(object=nil)
|
||||||
|
self.preferred_amount
|
||||||
|
end
|
||||||
|
|
||||||
|
def available?(order)
|
||||||
|
order.digital?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,26 +1,25 @@
|
|||||||
Spree::Order.class_eval do
|
Spree::Order.class_eval do
|
||||||
|
# all products are digital
|
||||||
# Are all products/variants of this Order to be downloaded by the customer?
|
|
||||||
def digital?
|
def digital?
|
||||||
line_items.map { |item| return false unless item.digital? }
|
line_items.map { |item| return false unless item.digital? }
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Is at least one product/variant digital?
|
|
||||||
def some_digital?
|
def some_digital?
|
||||||
line_items.map { |item| return true if item.digital? }
|
line_items.map { |item| return true if item.digital? }
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determine which method to use for shipping of digital products.
|
|
||||||
def digital_shipping_method
|
|
||||||
rates = rate_hash
|
|
||||||
# If there is a shipping method has "Download" in its name then we take that one.
|
|
||||||
rates.each { |rate| return rate if rate[:name].downcase.include?('download') }
|
|
||||||
# Other than that, we take the first one that we find that doesn't cost anything.
|
|
||||||
rates.each { |rate| return rate if rate[:cost] == 0 }
|
|
||||||
# Well, at this point we have a problem. No shipping method is cost-free or called "download".
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# TODO this works as of spree 1.1.1; make sure to check the original function on upgrade
|
||||||
|
|
||||||
|
def available_shipping_methods(display_on = nil)
|
||||||
|
return [] unless ship_address
|
||||||
|
all_methods = Spree::ShippingMethod.all_available(self, display_on)
|
||||||
|
|
||||||
|
if self.digital?
|
||||||
|
all_methods.detect { |m| m.calculator.class == Spree::Calculator::DigitalDelivery }.try { |d| [d] } || all_methods
|
||||||
|
else
|
||||||
|
all_methods
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
Spree::Product.class_eval do
|
Spree::Product.class_eval do
|
||||||
has_many :digitals, :through => :variants_including_master
|
has_many :digitals, :through => :variants_including_master
|
||||||
end
|
end
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
Deface::Override.new(:virtual_path => "spree/checkout/_delivery",
|
|
||||||
:name => "modify_shipping_options_display",
|
|
||||||
:replace_contents => "#shipping_method #methods p.radios",
|
|
||||||
:original => %q{
|
|
||||||
<% @order.rate_hash.each do |shipping_method| %>
|
|
||||||
<label>
|
|
||||||
<%= radio_button(:order, :shipping_method_id, shipping_method[:id]) %>
|
|
||||||
<% if Spree::Config[:shipment_inc_vat] %>
|
|
||||||
<%= shipping_method[:name] %> <%= format_price (1 + Spree::TaxRate.default) * shipping_method[:cost] %>
|
|
||||||
<% else %>
|
|
||||||
<%= shipping_method[:name] %> <%= number_to_currency shipping_method[:cost] %>
|
|
||||||
<% end %>
|
|
||||||
</label>
|
|
||||||
<% end %>
|
|
||||||
},
|
|
||||||
:text => %q{
|
|
||||||
<% if @order.digital? && @order.digital_shipping_method.present? %>
|
|
||||||
<label>
|
|
||||||
<%= radio_button :order, :shipping_method_id, @order.digital_shipping_method[:id] %>
|
|
||||||
<%== t 'digital_shipping', :email => @order.email %> (<%= number_to_currency @order.digital_shipping_method[:cost] %>)
|
|
||||||
</label>
|
|
||||||
<% else %>
|
|
||||||
<% filtered_rate_hash = @order.rate_hash.select { |m| !(@order.digital_shipping_method && m[:id] == @order.digital_shipping_method[:id]) } %>
|
|
||||||
<% if filtered_rate_hash.count > 0 %>
|
|
||||||
<% filtered_rate_hash.each do |shipping_method| %>
|
|
||||||
<label>
|
|
||||||
<%= radio_button(:order, :shipping_method_id, shipping_method[:id]) %>
|
|
||||||
<% if Spree::Config[:shipment_inc_vat] %>
|
|
||||||
<%= shipping_method[:name] %> <%= format_price (1 + Spree::TaxRate.default) * shipping_method[:cost] %>
|
|
||||||
<% else %>
|
|
||||||
<%= shipping_method[:name] %> <%= number_to_currency shipping_method[:cost] %>
|
|
||||||
<% end %>
|
|
||||||
</label>
|
|
||||||
<% end %>
|
|
||||||
<% else %>
|
|
||||||
<%= t :no_shipping_methods %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
})
|
|
||||||
@@ -5,7 +5,6 @@ en:
|
|||||||
delete_file: Delete this file
|
delete_file: Delete this file
|
||||||
broken_file: Warning! this file is broken
|
broken_file: Warning! this file is broken
|
||||||
delete_file_cofirmation: Are you sure you want to delete the file %{filename}?
|
delete_file_cofirmation: Are you sure you want to delete the file %{filename}?
|
||||||
digital_shipping: Delivery per email to <strong>%{email}</strong>
|
digital_delivery: Digital Delivery
|
||||||
|
|
||||||
spree_digital:
|
spree_digital:
|
||||||
upload: Upload
|
upload: Upload
|
||||||
@@ -4,14 +4,13 @@ module SpreeDigital
|
|||||||
|
|
||||||
config.autoload_paths += %W(#{config.root}/lib)
|
config.autoload_paths += %W(#{config.root}/lib)
|
||||||
|
|
||||||
# Use RSpec for tests
|
|
||||||
config.generators do |g|
|
|
||||||
g.test_framework :rspec
|
|
||||||
end
|
|
||||||
|
|
||||||
initializer "spree.spree_digital.preferences", :after => "spree.environment" do |app|
|
initializer "spree.spree_digital.preferences", :after => "spree.environment" do |app|
|
||||||
Spree::DigitalConfiguration = Spree::SpreeDigitalConfiguration.new
|
Spree::DigitalConfiguration = Spree::SpreeDigitalConfiguration.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
initializer "spree.register.digital_shipping", :after => 'spree.register.calculators' do |app|
|
||||||
|
app.config.spree.calculators.shipping_methods << Spree::Calculator::DigitalDelivery
|
||||||
|
end
|
||||||
|
|
||||||
def self.activate
|
def self.activate
|
||||||
Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
|
Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
|
||||||
|
|||||||
@@ -49,6 +49,20 @@ describe Spree::Order do
|
|||||||
order.digital?.should be_false
|
order.digital?.should be_false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "digital shipping" do
|
||||||
|
before do
|
||||||
|
# TODO create digital shipping factory
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should only offer digital shipping if all items are digital" do
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not offer digital shipping if only some items are digital" do
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.platform = Gem::Platform::RUBY
|
s.platform = Gem::Platform::RUBY
|
||||||
s.name = 'spree_digital'
|
s.name = 'spree_digital'
|
||||||
s.version = '1.0.0'
|
s.version = '1.1.0'
|
||||||
s.summary = ''
|
s.summary = ''
|
||||||
s.description = 'This gem is supposed to be used in connection with spree_core. It was tested with spree_core 0.66.99 but it might work with newer versions as well.'
|
s.description = 'Add digital download functionality to spree'
|
||||||
s.author = 'funkensturm.'
|
s.author = 'funkensturm.'
|
||||||
s.homepage = 'http://www.funkensturm.com'
|
s.homepage = 'http://www.funkensturm.com'
|
||||||
s.files = `git ls-files`.split("\n")
|
s.files = `git ls-files`.split("\n")
|
||||||
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
|
|||||||
s.require_path = 'lib'
|
s.require_path = 'lib'
|
||||||
s.requirements << 'none'
|
s.requirements << 'none'
|
||||||
s.required_ruby_version = '>= 1.8.7'
|
s.required_ruby_version = '>= 1.8.7'
|
||||||
s.add_dependency('spree_core', '>= 1.0.0')
|
s.add_dependency('spree_core', '>= 1.1.0')
|
||||||
|
|
||||||
# test suite
|
# test suite
|
||||||
s.add_development_dependency 'shoulda-matchers'
|
s.add_development_dependency 'shoulda-matchers'
|
||||||
|
|||||||
Reference in New Issue
Block a user