stall 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +7 -0
  4. data/Rakefile +22 -0
  5. data/app/assets/javascripts/stall.coffee +12 -0
  6. data/app/assets/javascripts/stall/add-to-cart-form.coffee +22 -0
  7. data/app/assets/javascripts/stall/application.js +13 -0
  8. data/app/assets/javascripts/stall/carts.js +2 -0
  9. data/app/assets/stylesheets/stall/application.css +15 -0
  10. data/app/assets/stylesheets/stall/carts.css +4 -0
  11. data/app/controllers/stall/application_controller.rb +18 -0
  12. data/app/controllers/stall/carts_controller.rb +28 -0
  13. data/app/controllers/stall/checkout/steps_controller.rb +32 -0
  14. data/app/controllers/stall/checkouts_controller.rb +9 -0
  15. data/app/controllers/stall/line_items_controller.rb +26 -0
  16. data/app/helpers/stall/add_to_cart_helper.rb +10 -0
  17. data/app/helpers/stall/application_helper.rb +4 -0
  18. data/app/helpers/stall/cart_helper.rb +28 -0
  19. data/app/helpers/stall/checkout_helper.rb +7 -0
  20. data/app/models/stall.rb +5 -0
  21. data/app/models/stall/address.rb +5 -0
  22. data/app/models/stall/address_ownership.rb +15 -0
  23. data/app/models/stall/cart.rb +14 -0
  24. data/app/models/stall/customer.rb +5 -0
  25. data/app/models/stall/line_item.rb +45 -0
  26. data/app/models/stall/product_list.rb +56 -0
  27. data/app/models/stall/shipment.rb +15 -0
  28. data/app/models/stall/shipping_method.rb +5 -0
  29. data/app/services/stall/add_to_cart_service.rb +52 -0
  30. data/app/services/stall/base_service.rb +4 -0
  31. data/app/services/stall/shipping_fee_calculator_service.rb +21 -0
  32. data/app/views/checkout/steps/_informations.html.haml +41 -0
  33. data/app/views/checkout/steps/_payment_method.html.haml +1 -0
  34. data/app/views/checkout/steps/_shipping_method.html.haml +10 -0
  35. data/app/views/layouts/stall/application.html.erb +15 -0
  36. data/app/views/stall/carts/show.html.haml +23 -0
  37. data/app/views/stall/checkout/steps/show.html.haml +1 -0
  38. data/app/views/stall/line_items/_add_error.html.haml +17 -0
  39. data/app/views/stall/line_items/_added.html.haml +23 -0
  40. data/app/views/stall/line_items/_form.html.haml +12 -0
  41. data/config/locales/stall.en.yml +60 -0
  42. data/config/locales/stall.fr.yml +60 -0
  43. data/db/migrate/20151103154313_create_stall_line_items.rb +19 -0
  44. data/db/migrate/20160118121116_create_stall_product_lists.rb +15 -0
  45. data/db/migrate/20160118124016_create_customers.rb +11 -0
  46. data/db/migrate/20160122143746_create_stall_addresses.rb +17 -0
  47. data/db/migrate/20160122143748_create_stall_address_ownerships.rb +17 -0
  48. data/db/migrate/20160124014144_create_stall_shipping_methods.rb +10 -0
  49. data/db/migrate/20160124020313_create_stall_shipments.rb +17 -0
  50. data/lib/generators/stall/checkout/step/step_generator.rb +20 -0
  51. data/lib/generators/stall/checkout/step/templates/step.rb.erb +2 -0
  52. data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +3 -0
  53. data/lib/generators/stall/checkout/wizard/wizard_generator.rb +20 -0
  54. data/lib/generators/stall/install/install_generator.rb +17 -0
  55. data/lib/generators/stall/install/templates/initializer.rb +47 -0
  56. data/lib/stall.rb +38 -0
  57. data/lib/stall/addressable.rb +44 -0
  58. data/lib/stall/checkout.rb +15 -0
  59. data/lib/stall/checkout/informations_checkout_step.rb +41 -0
  60. data/lib/stall/checkout/payment_checkout_step.rb +6 -0
  61. data/lib/stall/checkout/payment_method_checkout_step.rb +6 -0
  62. data/lib/stall/checkout/shipping_method_checkout_step.rb +20 -0
  63. data/lib/stall/checkout/step.rb +47 -0
  64. data/lib/stall/checkout/wizard.rb +85 -0
  65. data/lib/stall/config.rb +27 -0
  66. data/lib/stall/engine.rb +30 -0
  67. data/lib/stall/rails/currency_helper.rb +26 -0
  68. data/lib/stall/rails/routing_mapper.rb +13 -0
  69. data/lib/stall/routes.rb +27 -0
  70. data/lib/stall/sellable.rb +8 -0
  71. data/lib/stall/sellable/mixin.rb +17 -0
  72. data/lib/stall/sellable/model.rb +46 -0
  73. data/lib/stall/shipping.rb +16 -0
  74. data/lib/stall/shipping/calculator.rb +28 -0
  75. data/lib/stall/shipping/config.rb +17 -0
  76. data/lib/stall/shipping/country_weight_table_calculator.rb +69 -0
  77. data/lib/stall/shipping/free_shipping_calculator.rb +23 -0
  78. data/lib/stall/utils.rb +22 -0
  79. data/lib/stall/utils/config_dsl.rb +19 -0
  80. data/lib/stall/version.rb +3 -0
  81. data/lib/tasks/stall_tasks.rake +4 -0
  82. 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,10 @@
1
+ class CreateStallShippingMethods < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_shipping_methods do |t|
4
+ t.string :name
5
+ t.string :identifier
6
+
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ 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,2 @@
1
+ class <%= class_name %> < Stall::Checkout::Step
2
+ end
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < Stall::Checkout::Wizard
2
+ steps :informations, :shipping_method, :payment_method, :payment
3
+ 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
@@ -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,6 @@
1
+ module Stall
2
+ module Checkout
3
+ class PaymentCheckoutStep < Stall::Checkout::Step
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Stall
2
+ module Checkout
3
+ class PaymentMethodCheckoutStep < Stall::Checkout::Step
4
+ end
5
+ end
6
+ 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