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.
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