stall 0.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +7 -0
- data/Rakefile +22 -0
- data/app/assets/javascripts/stall.coffee +12 -0
- data/app/assets/javascripts/stall/add-to-cart-form.coffee +22 -0
- data/app/assets/javascripts/stall/application.js +13 -0
- data/app/assets/javascripts/stall/carts.js +2 -0
- data/app/assets/stylesheets/stall/application.css +15 -0
- data/app/assets/stylesheets/stall/carts.css +4 -0
- data/app/controllers/stall/application_controller.rb +18 -0
- data/app/controllers/stall/carts_controller.rb +28 -0
- data/app/controllers/stall/checkout/steps_controller.rb +32 -0
- data/app/controllers/stall/checkouts_controller.rb +9 -0
- data/app/controllers/stall/line_items_controller.rb +26 -0
- data/app/helpers/stall/add_to_cart_helper.rb +10 -0
- data/app/helpers/stall/application_helper.rb +4 -0
- data/app/helpers/stall/cart_helper.rb +28 -0
- data/app/helpers/stall/checkout_helper.rb +7 -0
- data/app/models/stall.rb +5 -0
- data/app/models/stall/address.rb +5 -0
- data/app/models/stall/address_ownership.rb +15 -0
- data/app/models/stall/cart.rb +14 -0
- data/app/models/stall/customer.rb +5 -0
- data/app/models/stall/line_item.rb +45 -0
- data/app/models/stall/product_list.rb +56 -0
- data/app/models/stall/shipment.rb +15 -0
- data/app/models/stall/shipping_method.rb +5 -0
- data/app/services/stall/add_to_cart_service.rb +52 -0
- data/app/services/stall/base_service.rb +4 -0
- data/app/services/stall/shipping_fee_calculator_service.rb +21 -0
- data/app/views/checkout/steps/_informations.html.haml +41 -0
- data/app/views/checkout/steps/_payment_method.html.haml +1 -0
- data/app/views/checkout/steps/_shipping_method.html.haml +10 -0
- data/app/views/layouts/stall/application.html.erb +15 -0
- data/app/views/stall/carts/show.html.haml +23 -0
- data/app/views/stall/checkout/steps/show.html.haml +1 -0
- data/app/views/stall/line_items/_add_error.html.haml +17 -0
- data/app/views/stall/line_items/_added.html.haml +23 -0
- data/app/views/stall/line_items/_form.html.haml +12 -0
- data/config/locales/stall.en.yml +60 -0
- data/config/locales/stall.fr.yml +60 -0
- data/db/migrate/20151103154313_create_stall_line_items.rb +19 -0
- data/db/migrate/20160118121116_create_stall_product_lists.rb +15 -0
- data/db/migrate/20160118124016_create_customers.rb +11 -0
- data/db/migrate/20160122143746_create_stall_addresses.rb +17 -0
- data/db/migrate/20160122143748_create_stall_address_ownerships.rb +17 -0
- data/db/migrate/20160124014144_create_stall_shipping_methods.rb +10 -0
- data/db/migrate/20160124020313_create_stall_shipments.rb +17 -0
- data/lib/generators/stall/checkout/step/step_generator.rb +20 -0
- data/lib/generators/stall/checkout/step/templates/step.rb.erb +2 -0
- data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +3 -0
- data/lib/generators/stall/checkout/wizard/wizard_generator.rb +20 -0
- data/lib/generators/stall/install/install_generator.rb +17 -0
- data/lib/generators/stall/install/templates/initializer.rb +47 -0
- data/lib/stall.rb +38 -0
- data/lib/stall/addressable.rb +44 -0
- data/lib/stall/checkout.rb +15 -0
- data/lib/stall/checkout/informations_checkout_step.rb +41 -0
- data/lib/stall/checkout/payment_checkout_step.rb +6 -0
- data/lib/stall/checkout/payment_method_checkout_step.rb +6 -0
- data/lib/stall/checkout/shipping_method_checkout_step.rb +20 -0
- data/lib/stall/checkout/step.rb +47 -0
- data/lib/stall/checkout/wizard.rb +85 -0
- data/lib/stall/config.rb +27 -0
- data/lib/stall/engine.rb +30 -0
- data/lib/stall/rails/currency_helper.rb +26 -0
- data/lib/stall/rails/routing_mapper.rb +13 -0
- data/lib/stall/routes.rb +27 -0
- data/lib/stall/sellable.rb +8 -0
- data/lib/stall/sellable/mixin.rb +17 -0
- data/lib/stall/sellable/model.rb +46 -0
- data/lib/stall/shipping.rb +16 -0
- data/lib/stall/shipping/calculator.rb +28 -0
- data/lib/stall/shipping/config.rb +17 -0
- data/lib/stall/shipping/country_weight_table_calculator.rb +69 -0
- data/lib/stall/shipping/free_shipping_calculator.rb +23 -0
- data/lib/stall/utils.rb +22 -0
- data/lib/stall/utils/config_dsl.rb +19 -0
- data/lib/stall/version.rb +3 -0
- data/lib/tasks/stall_tasks.rake +4 -0
- metadata +362 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateStallAddressOwnerships < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :stall_address_ownerships do |t|
|
4
|
+
t.references :address, index: true
|
5
|
+
t.references :addressable, polymorphic: true
|
6
|
+
t.boolean :billing, default: false
|
7
|
+
t.boolean :shipping, default: false
|
8
|
+
|
9
|
+
t.timestamps null: false
|
10
|
+
|
11
|
+
t.index [:addressable_id, :addressable_type],
|
12
|
+
name: :index_address_ownerships_on_addressable_type_and_id
|
13
|
+
|
14
|
+
t.foreign_key :stall_addresses, column: :address_id
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateStallShipments < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :stall_shipments do |t|
|
4
|
+
t.references :cart, index: true
|
5
|
+
t.references :shipping_method, index: true
|
6
|
+
t.monetize :price, currency: { present: false }
|
7
|
+
t.monetize :eot_price, currency: { present: false }
|
8
|
+
t.decimal :vat_rate
|
9
|
+
t.datetime :sent_at
|
10
|
+
|
11
|
+
t.timestamps null: false
|
12
|
+
end
|
13
|
+
|
14
|
+
add_foreign_key :stall_shipments, :stall_product_lists, column: :cart_id
|
15
|
+
add_foreign_key :stall_shipments, :stall_shipping_methods, column: :shipping_method_id
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
class StepGenerator < ::Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
def copy_template
|
7
|
+
template 'step.rb.erb', "lib/#{ file_path }.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Override provided file name to include checkout_wizard at the end and
|
13
|
+
# allow every NamedBase helpers to use that name
|
14
|
+
#
|
15
|
+
def file_name
|
16
|
+
@_file_name ||= [@file_name, 'checkout_step'].join('_')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
class WizardGenerator < ::Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
def copy_template
|
7
|
+
template 'wizard.rb.erb', "lib/#{ file_path }.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Override provided file name to include checkout_wizard at the end and
|
13
|
+
# allow every NamedBase helpers to use that name
|
14
|
+
#
|
15
|
+
def file_name
|
16
|
+
@_file_name ||= [@file_name, 'checkout_wizard'].join('_')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Stall
|
2
|
+
class InstallGenerator < ::Rails::Generators::Base
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def copy_migrations
|
6
|
+
rake 'stall_engine:install:migrations'
|
7
|
+
end
|
8
|
+
|
9
|
+
def copy_initializer
|
10
|
+
copy_file 'initializer.rb', 'config/initializers/stall.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
def copy_default_checkout_wizard
|
14
|
+
generate 'stall:wizard', 'default'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Stall.configure do |config|
|
2
|
+
# Global default VAT rate, can be overrided by products
|
3
|
+
#
|
4
|
+
# config.vat_rate = BigDecimal.new('20.0')
|
5
|
+
|
6
|
+
# Defines the default number of decimals precision used in prices
|
7
|
+
#
|
8
|
+
# config.prices_precision = 2
|
9
|
+
|
10
|
+
# Default engine's controllers ancestor class
|
11
|
+
#
|
12
|
+
# config.application_controller_ancestor = '::ApplicationController'
|
13
|
+
|
14
|
+
# Default currency used in the app for money objects.
|
15
|
+
#
|
16
|
+
# Note : If you need multi currency support in your app, you'll have to ensure
|
17
|
+
# that you create your line items, carts and orders with the right currency
|
18
|
+
# set.
|
19
|
+
#
|
20
|
+
# param :default_currency, 'EUR'
|
21
|
+
|
22
|
+
# Defined the default checkout wizard used for checking carts out in the
|
23
|
+
# shop process.
|
24
|
+
#
|
25
|
+
# Checkout wizards are used to customize the checkout steps.
|
26
|
+
#
|
27
|
+
# A default one is generated on stall's installation and can be found in your
|
28
|
+
# app at : lib/default_checkout_wizard.rb
|
29
|
+
#
|
30
|
+
# config.default_checkout_wizard = 'DefaultCheckoutWizard'
|
31
|
+
|
32
|
+
# Default line items weight when added to cart and no weight is found on
|
33
|
+
# the sellable
|
34
|
+
#
|
35
|
+
# This allows for simply setting a weight here and not in each product
|
36
|
+
# to calculate shipping fees depending on the total cart weight
|
37
|
+
#
|
38
|
+
# config.default_product_weight = 0
|
39
|
+
|
40
|
+
# Allows configuring which countries are available for free shipping offers
|
41
|
+
#
|
42
|
+
# Accepts an array of country codes or a proc with a Stall::Address argument
|
43
|
+
#
|
44
|
+
# Defaults to nil, which means all countries are available
|
45
|
+
#
|
46
|
+
# config.shipping.free_shipping.available = nil
|
47
|
+
end
|
data/lib/stall.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'has_secure_token'
|
2
|
+
require 'vertebra'
|
3
|
+
require 'money-rails'
|
4
|
+
require 'request_store'
|
5
|
+
require 'haml-rails'
|
6
|
+
require 'simple_form'
|
7
|
+
require 'country_select'
|
8
|
+
|
9
|
+
require 'stall/rails/routing_mapper'
|
10
|
+
require 'stall/rails/currency_helper'
|
11
|
+
require 'stall/engine'
|
12
|
+
|
13
|
+
module Stall
|
14
|
+
extend ActiveSupport::Autoload
|
15
|
+
|
16
|
+
autoload :Sellable
|
17
|
+
autoload :Addressable
|
18
|
+
|
19
|
+
autoload :Checkout
|
20
|
+
autoload :Shipping
|
21
|
+
|
22
|
+
autoload :Routes
|
23
|
+
autoload :Config
|
24
|
+
autoload :Utils
|
25
|
+
|
26
|
+
def self.table_name_prefix
|
27
|
+
'stall_'
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.config
|
31
|
+
@config ||= Stall::Config.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.configure
|
35
|
+
yield config
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Provide the common behavior for all models that need to own addresses,
|
2
|
+
# for billing and shipping
|
3
|
+
#
|
4
|
+
module Stall
|
5
|
+
module Addressable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
has_many :address_ownerships, as: :addressable, dependent: :destroy
|
10
|
+
has_many :addresses, through: :address_ownerships
|
11
|
+
accepts_nested_attributes_for :address_ownerships, allow_destroy: true
|
12
|
+
end
|
13
|
+
|
14
|
+
def address_ownership_for(type)
|
15
|
+
address_ownerships.find do |ownership|
|
16
|
+
ownership.public_send(type)
|
17
|
+
end if [:shipping, :billing].include?(type.to_sym)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Provide billing and shipping address fetching and assigning shortcuts by
|
21
|
+
# using the address ownerships
|
22
|
+
#
|
23
|
+
[:shipping, :billing].each do |type|
|
24
|
+
instance_variable_name = :"@#{ type }_address"
|
25
|
+
|
26
|
+
define_method(:"#{ type }_address=") do |address|
|
27
|
+
ownership = address_ownership_for(type) || address_ownerships.build(type => true)
|
28
|
+
ownership.address = address
|
29
|
+
ownership.save if persisted?
|
30
|
+
instance_variable_set(instance_variable_name, address)
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method(:"#{ type }_address") do
|
34
|
+
if (address = instance_variable_get(instance_variable_name))
|
35
|
+
return address
|
36
|
+
end
|
37
|
+
|
38
|
+
if (ownership = address_ownership_for(type))
|
39
|
+
instance_variable_set(instance_variable_name, ownership.address)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Wizard
|
6
|
+
autoload :Step
|
7
|
+
|
8
|
+
autoload :InformationsCheckoutStep
|
9
|
+
autoload :ShippingMethodCheckoutStep
|
10
|
+
autoload :PaymentMethodCheckoutStep
|
11
|
+
autoload :PaymentCheckoutStep
|
12
|
+
|
13
|
+
class WizardNotFoundError < StandardError; end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
class InformationsCheckoutStep < Stall::Checkout::Step
|
4
|
+
def prepare
|
5
|
+
ensure_customer
|
6
|
+
ensure_address(:billing)
|
7
|
+
ensure_address(:shipping)
|
8
|
+
end
|
9
|
+
|
10
|
+
def process
|
11
|
+
cart.assign_attributes(cart_params)
|
12
|
+
process_addresses
|
13
|
+
cart.save!
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def ensure_customer
|
19
|
+
cart.build_customer unless cart.customer
|
20
|
+
end
|
21
|
+
|
22
|
+
def ensure_address(type)
|
23
|
+
unless cart.address_ownership_for(type)
|
24
|
+
ownership = cart.address_ownerships.build(type => true)
|
25
|
+
ownership.build_address
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_addresses
|
30
|
+
unless params[:use_another_address_for_billing]
|
31
|
+
# Remove submitted billing address
|
32
|
+
billing_ownership = cart.address_ownership_for(:billing)
|
33
|
+
cart.address_ownerships.destroy(billing_ownership)
|
34
|
+
# Set shipping address as the billing one
|
35
|
+
shipping_ownership = cart.address_ownership_for(:shipping)
|
36
|
+
shipping_ownership.billing = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
class ShippingMethodCheckoutStep < Stall::Checkout::Step
|
4
|
+
def prepare
|
5
|
+
cart.build_shipment unless cart.shipment
|
6
|
+
end
|
7
|
+
|
8
|
+
def process
|
9
|
+
super
|
10
|
+
calculate_shipping_fee!
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def calculate_shipping_fee!
|
16
|
+
Stall::ShippingFeeCalculatorService.new(cart).call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
class StepNotFoundError < StandardError; end
|
4
|
+
|
5
|
+
class Step
|
6
|
+
attr_reader :cart, :params
|
7
|
+
|
8
|
+
def initialize(cart, params)
|
9
|
+
@cart = cart
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
# Allows for preparing to the cart for the current step before rendering
|
14
|
+
# the step's view
|
15
|
+
#
|
16
|
+
# Note : Meant to be overriden by subclasses
|
17
|
+
#
|
18
|
+
def prepare
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
cart.update_attributes(cart_params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def cart_params
|
26
|
+
params.require(:cart).permit!
|
27
|
+
end
|
28
|
+
|
29
|
+
# Handles conversion from an identifier to a checkout step class, allowing
|
30
|
+
# us to specify a list of symbols in our wizard's .step macro
|
31
|
+
#
|
32
|
+
def self.for(identifier)
|
33
|
+
name = identifier.to_s.camelize
|
34
|
+
step_name = [name, 'CheckoutStep'].join
|
35
|
+
|
36
|
+
step = Stall::Utils.try_load_constant(step_name) ||
|
37
|
+
Stall::Utils.try_load_constant(['Stall', 'Checkout', step_name.demodulize].join('::'))
|
38
|
+
|
39
|
+
return step if step
|
40
|
+
|
41
|
+
raise StepNotFoundError,
|
42
|
+
"No checkout step was found for #{ identifier }. You can generate " +
|
43
|
+
"it with `rails g stall:checkout:step #{ identifier }`"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Stall
|
2
|
+
module Checkout
|
3
|
+
class Wizard
|
4
|
+
class StepUndefinedError < StandardError; end
|
5
|
+
|
6
|
+
attr_reader :cart
|
7
|
+
|
8
|
+
class_attribute :_steps
|
9
|
+
|
10
|
+
def self.steps(*identifiers)
|
11
|
+
if identifiers.length > 0
|
12
|
+
self._steps = identifiers
|
13
|
+
else
|
14
|
+
_steps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(cart)
|
19
|
+
@cart = cart
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_step
|
23
|
+
if (step_name = current_step_name)
|
24
|
+
Stall::Checkout::Step.for(step_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_step_name
|
29
|
+
if step?(cart.state)
|
30
|
+
cart.state
|
31
|
+
else
|
32
|
+
raise StepUndefinedError,
|
33
|
+
"The current carte state #{ cart.state } does not exist " +
|
34
|
+
"for the current #{ self.class.name } wizard, which has the " +
|
35
|
+
"following steps : #{ steps.join(', ') }."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_step_name
|
40
|
+
step_name_for(step_index + 1) if step_index && step_index < steps_count
|
41
|
+
end
|
42
|
+
|
43
|
+
def complete?
|
44
|
+
step_index && step_index >= steps_count - 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def steps_count
|
48
|
+
@steps_count ||= steps.length
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate_current_step!
|
52
|
+
cart.state = next_step_name
|
53
|
+
cart.save!
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.route_key
|
57
|
+
name.gsub(/CheckoutWizard/, '').underscore.gsub('_', ' ').parameterize
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.from_route_key(key)
|
61
|
+
Stall::Utils.try_load_constant(
|
62
|
+
[key.underscore.camelize, 'CheckoutWizard'].join
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def steps
|
69
|
+
@steps ||= self.class.steps
|
70
|
+
end
|
71
|
+
|
72
|
+
def step_name_for(index)
|
73
|
+
steps[index]
|
74
|
+
end
|
75
|
+
|
76
|
+
def step_index
|
77
|
+
steps.index(cart.state)
|
78
|
+
end
|
79
|
+
|
80
|
+
def step?(state)
|
81
|
+
steps.include?(state)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|