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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9caa6a04c98d0ebd94ac128e27de7e7ea7797a55
4
+ data.tar.gz: 024289eded82201eaf2a5c0dcc3c1cde5f071011
5
+ SHA512:
6
+ metadata.gz: 76d45836e3b79f5ab7bde8809ff4ed35779d1bfb5dc28aeb99985b8b6b31ad9f1f3aa82b0870b32a770c293322ede4ccdad39fcfaa24d1ebce474f9cc541b9b1
7
+ data.tar.gz: ea9f53aca3a34913111afc301c48616912fc9ff39831fe2679fa0dcfb77123316236fbafdc87a980cd9d4adb5a5164680ff8c8f3e94314f78f9d80fcb4f1e9c0
@@ -0,0 +1,20 @@
1
+ Copyright 2015 vala
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ # Stall
2
+
3
+
4
+
5
+ # Licence
6
+
7
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Stall'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+ load 'rails/tasks/statistics.rake'
20
+
21
+ Bundler::GemHelper.install_tasks
22
+
@@ -0,0 +1,12 @@
1
+ #= require vertebra
2
+ #= require_self
3
+ #= require stall/add-to-cart-form
4
+
5
+ @Stall =
6
+ onDomReady: (callback) ->
7
+ event = if window.Turbolinks && window.Turbolinks.supported
8
+ 'page:change'
9
+ else
10
+ 'ready'
11
+
12
+ $(document).on(event, callback)
@@ -0,0 +1,22 @@
1
+ class Stall.AddToCartForm extends Vertebra.View
2
+ @create = ($el) ->
3
+ return if $el.data('stall.add-to-cart-form')
4
+ instance = new Stall.AddToCartForm(el: $el)
5
+ $el.data('stall.add-to-cart-form', instance)
6
+
7
+ events:
8
+ 'ajax:success': 'onSuccess'
9
+
10
+ onSuccess: (e, resp) ->
11
+ @$modal = $(resp).appendTo('body').modal()
12
+ @updateTotalQuantityCounter()
13
+
14
+ updateTotalQuantityCounter: ->
15
+ quantity = @$modal.data('cart-total-quantity')
16
+
17
+ if ($counter = $('[data-cart-quantity-counter]')).length
18
+ $counter.text(quantity)
19
+
20
+ Stall.onDomReady ->
21
+ $('body').on 'ajax:beforeSend', '[data-add-to-cart-form]', (e) ->
22
+ Stall.AddToCartForm.create($(e.currentTarget))
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,18 @@
1
+ module Stall
2
+ class ApplicationController < Stall.config.application_controller_ancestor.constantize
3
+ layout :_set_stall_layout
4
+
5
+ private
6
+
7
+ # Enforce app-level layout by defaulting to the "application" layout if
8
+ # no parent layout was set before, thus avoiding the "stall/application"
9
+ # layout to be rendered
10
+ def _set_stall_layout
11
+ parent_controller = self.class.ancestors.find do |ancestor|
12
+ !ancestor.name.match(/^Stall::/)
13
+ end
14
+
15
+ parent_controller._layout ||= 'application'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Stall
2
+ class CartsController < ApplicationController
3
+ before_action :load_cart
4
+
5
+ def show
6
+ end
7
+
8
+ def update
9
+ if @cart.update_attributes(cart_params)
10
+ flash[:success] = t('stall.carts.flashes.update.success')
11
+ redirect_to cart_path(@cart)
12
+ else
13
+ flash[:error] = t('stall.carts.flashes.update.error')
14
+ render 'show'
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def load_cart
21
+ @cart = Cart.find_by_token(params[:id])
22
+ end
23
+
24
+ def cart_params
25
+ params.require(:cart).permit(line_items_attributes: [:id, :quantity])
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ module Stall
2
+ module Checkout
3
+ class StepsController < Stall::ApplicationController
4
+ include Stall::CheckoutHelper
5
+
6
+ before_action :load_step
7
+
8
+ def show
9
+ @step.prepare
10
+ end
11
+
12
+ def update
13
+ if @step.process
14
+ @wizard.validate_current_step!
15
+ redirect_to step_path(@cart)
16
+ else
17
+ @step.prepare
18
+ flash[:error] = t("stall.checkout.#{ @wizard.current_step_name }.error")
19
+ render 'show'
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def load_step
26
+ @cart = Stall::Cart.find_by_token(params[:cart_id])
27
+ @wizard = @cart.wizard.new(@cart)
28
+ @step = @wizard.current_step.new(@cart, params)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Stall
2
+ class CheckoutsController < ApplicationController
3
+ def show
4
+ @cart = Stall::Cart.find_by_token(params[:id])
5
+ @cart.reset_state!
6
+ redirect_to checkout_step_path(@cart.wizard.route_key, @cart)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module Stall
2
+ class LineItemsController < ApplicationController
3
+ def create
4
+ service = Stall::AddToCartService.new(cart, line_item_params)
5
+
6
+ if service.call
7
+ @quantity = params[:line_item][:quantity].to_i
8
+ @line_item = service.line_item
9
+ render partial: 'added'
10
+ else
11
+ @line_item = service.line_item
12
+ render partial: 'add_error'
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def line_item_params
19
+ params.require(:line_item).permit(:sellable_type, :sellable_id, :quantity)
20
+ end
21
+
22
+ def cart
23
+ @cart ||= Stall::Cart.find_by_token(params[:cart_id])
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ module Stall
2
+ module AddToCartHelper
3
+ def add_to_cart_form_for(sellable, cart: nil)
4
+ render partial: 'stall/line_items/form', locals: {
5
+ cart: (cart || current_cart),
6
+ line_item: Stall::LineItem.new(sellable: sellable)
7
+ }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module Stall
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,28 @@
1
+ module Stall
2
+ module CartHelper
3
+ def current_cart
4
+ RequestStore.store[cart_store_key_for(:default)] ||= load_current_cart
5
+ end
6
+
7
+ private
8
+
9
+ def load_current_cart
10
+ if (cart_token = session[cart_store_key_for(:default)])
11
+ if (cart = Stall::Cart.find_by_token(cart_token))
12
+ return cart
13
+ end
14
+ end
15
+
16
+ # If no token was stored or the token does not exist anymore, create a
17
+ # new cart and store the new token
18
+ #
19
+ Stall::Cart.create!.tap do |cart|
20
+ session[cart_store_key_for(:default)] = cart.token
21
+ end
22
+ end
23
+
24
+ def cart_store_key_for(identifier)
25
+ ['stall', 'cart', identifier.to_s].join('.')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module Stall
2
+ module CheckoutHelper
3
+ def step_path(cart)
4
+ checkout_step_path(cart.wizard.route_key, cart)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Stall
2
+ def self.table_name_prefix
3
+ 'stall_'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Stall
2
+ class Address < ActiveRecord::Base
3
+ has_one :addressable_ownership, dependent: :destroy
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module Stall
2
+ class AddressOwnership < ActiveRecord::Base
3
+ belongs_to :address
4
+ accepts_nested_attributes_for :address
5
+
6
+ belongs_to :addressable, polymorphic: true
7
+
8
+ def type_name
9
+ types = []
10
+ types << :billing if billing
11
+ types << :shipping if shipping
12
+ types.join('-')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Stall
2
+ class Cart < Stall::ProductList
3
+ include Stall::Addressable
4
+
5
+ has_one :shipment, dependent: :destroy, inverse_of: :cart
6
+ accepts_nested_attributes_for :shipment
7
+
8
+ def total_weight
9
+ line_items.reduce(0) do |total, line_item|
10
+ total + (line_item.weight || Stall.config.default_product_weight)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Stall
2
+ class Customer < ActiveRecord::Base
3
+ include Stall::Addressable
4
+ end
5
+ end
@@ -0,0 +1,45 @@
1
+ module Stall
2
+ class LineItem < ActiveRecord::Base
3
+ store_accessor :data, :weight
4
+
5
+ monetize :unit_eot_price_cents, :unit_price_cents,
6
+ :eot_price_cents, :price_cents,
7
+ with_model_currency: :currency, allow_nil: true
8
+
9
+ belongs_to :sellable, polymorphic: true
10
+ belongs_to :product_list
11
+
12
+ validates :name, :unit_price, :unit_eot_price, :vat_rate, :price, :quantity,
13
+ :eot_price, :sellable, presence: true
14
+
15
+ validates :unit_price, :unit_eot_price, :vat_rate, :price, :eot_price,
16
+ numericality: true
17
+
18
+ validates :quantity, numericality: { greater_than: 0 }
19
+
20
+ validate :stock_availability
21
+
22
+ before_validation :refresh_total_prices
23
+
24
+ def like?(other)
25
+ [:sellable_id, :sellable_type].all? do |property|
26
+ public_send(property) == other.public_send(property)
27
+ end
28
+ end
29
+
30
+ def currency
31
+ product_list.try(:currency) || Money.default_currency
32
+ end
33
+
34
+ private
35
+
36
+ # TODO : Stocks availibility handling
37
+ def stock_availability
38
+ end
39
+
40
+ def refresh_total_prices
41
+ self.eot_price = unit_eot_price * quantity if unit_eot_price && quantity
42
+ self.price = unit_price * quantity if unit_price && quantity
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,56 @@
1
+ module Stall
2
+ class ProductList < ActiveRecord::Base
3
+ has_secure_token
4
+
5
+ has_many :line_items, dependent: :destroy
6
+ accepts_nested_attributes_for :line_items
7
+
8
+ belongs_to :customer, class_name: 'Stall::Customer'
9
+ accepts_nested_attributes_for :customer
10
+
11
+ validates :type, presence: true
12
+
13
+ after_initialize :ensure_currency
14
+ after_initialize :ensure_state
15
+
16
+ def state
17
+ read_attribute(:state).try(:to_sym)
18
+ end
19
+
20
+ def reset_state!
21
+ update_column(:state, wizard.steps.first)
22
+ end
23
+
24
+ def to_param
25
+ token
26
+ end
27
+
28
+ def total_quantity
29
+ line_items.map(&:quantity).sum
30
+ end
31
+
32
+ def wizard
33
+ @wizard ||= begin
34
+ wizard_name = Stall.config.default_checkout_wizard
35
+
36
+ if (wizard = Stall::Utils.try_load_constant(wizard_name))
37
+ wizard
38
+ else
39
+ raise Stall::Checkout::WizardNotFoundError.new,
40
+ "The checkout wizard #{ wizard_name } was not found. You must generate it " +
41
+ "with `rails g stall:wizard #{ wizard_name.underscore.gsub('_checkout_wizard', '') }`"
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def ensure_currency
49
+ self.currency ||= Money.default_currency
50
+ end
51
+
52
+ def ensure_state
53
+ self.state ||= wizard.steps.first
54
+ end
55
+ end
56
+ end