stall 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -0
  3. data/app/assets/javascripts/stall.coffee +2 -0
  4. data/app/assets/javascripts/stall/cart-form.coffee +23 -0
  5. data/app/controllers/stall/application_controller.rb +10 -2
  6. data/app/controllers/stall/carts_controller.rb +31 -10
  7. data/app/controllers/stall/checkout/steps_controller.rb +36 -4
  8. data/app/controllers/stall/checkouts_controller.rb +12 -3
  9. data/app/controllers/stall/line_items_controller.rb +3 -3
  10. data/app/controllers/stall/payments_controller.rb +5 -3
  11. data/app/helpers/stall/add_to_cart_helper.rb +1 -1
  12. data/app/helpers/stall/checkout_helper.rb +22 -2
  13. data/app/helpers/stall/payments_helper.rb +8 -0
  14. data/app/mailers/stall/admin_mailer.rb +11 -0
  15. data/app/mailers/stall/base_mailer.rb +17 -0
  16. data/app/mailers/stall/customer_mailer.rb +13 -0
  17. data/app/models/address.rb +3 -0
  18. data/app/models/address_ownership.rb +3 -0
  19. data/app/models/adjustment.rb +3 -0
  20. data/app/models/cart.rb +3 -0
  21. data/app/models/customer.rb +3 -0
  22. data/app/models/line_item.rb +3 -0
  23. data/app/models/payment.rb +3 -0
  24. data/app/models/payment_method.rb +3 -0
  25. data/app/models/product_list.rb +3 -0
  26. data/app/models/shipment.rb +3 -0
  27. data/app/models/shipping_method.rb +3 -0
  28. data/app/models/stall/models.rb +4 -0
  29. data/app/models/stall/models/address.rb +45 -0
  30. data/app/models/stall/models/address_ownership.rb +26 -0
  31. data/app/models/stall/models/adjustment.rb +49 -0
  32. data/app/models/stall/models/cart.rb +37 -0
  33. data/app/models/stall/models/customer.rb +18 -0
  34. data/app/models/stall/models/line_item.rb +57 -0
  35. data/app/models/stall/models/payment.rb +33 -0
  36. data/app/models/stall/models/payment_method.rb +18 -0
  37. data/app/models/stall/models/product_list.rb +147 -0
  38. data/app/models/stall/models/shipment.rb +29 -0
  39. data/app/models/stall/models/shipping_method.rb +16 -0
  40. data/app/services/stall/add_to_cart_service.rb +11 -1
  41. data/app/services/stall/cart_update_service.rb +26 -0
  42. data/app/services/stall/payment_notification_service.rb +23 -9
  43. data/app/services/stall/shipping_fee_calculator_service.rb +13 -2
  44. data/app/views/checkout/steps/_informations.html.haml +3 -3
  45. data/app/views/checkout/steps/_payment.html.haml +25 -4
  46. data/app/views/checkout/steps/_payment_method.html.haml +1 -3
  47. data/app/views/checkout/steps/_payment_return.html.haml +6 -0
  48. data/app/views/checkout/steps/_shipping_method.html.haml +1 -1
  49. data/app/views/stall/admin_mailer/order_paid_email.html.haml +4 -0
  50. data/app/views/stall/carts/show.html.haml +36 -8
  51. data/app/views/stall/customer_mailer/order_paid_email.html.haml +8 -0
  52. data/app/views/stall/line_items/_added.html.haml +1 -1
  53. data/app/views/stall/shared/mailers/_address.html.haml +18 -0
  54. data/app/views/stall/shared/mailers/_cart.html.haml +114 -0
  55. data/config/locales/stall.fr.yml +90 -8
  56. data/db/migrate/20160304134849_change_all_json_columns_to_jsonb.rb +18 -0
  57. data/db/migrate/20160307142924_add_state_to_stall_addresses.rb +5 -0
  58. data/db/migrate/20160308142713_create_adjustments.rb +16 -0
  59. data/db/migrate/20160309165136_add_identifier_to_stall_product_lists.rb +5 -0
  60. data/db/migrate/20160316114649_add_data_to_stall_shipments.rb +5 -0
  61. data/db/migrate/20160317141632_add_state_to_stall_shipments.rb +5 -0
  62. data/db/migrate/20160629102943_add_active_to_stall_shipping_methods.rb +11 -0
  63. data/db/migrate/20160629104617_add_active_to_stall_payment_methods.rb +11 -0
  64. data/db/migrate/20160705110151_add_locale_to_stall_customers.rb +5 -0
  65. data/lib/generators/stall/checkout/step/templates/step.html.haml.erb +1 -1
  66. data/lib/generators/stall/checkout/step/templates/step.rb.erb +10 -2
  67. data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +1 -1
  68. data/lib/generators/stall/install/templates/initializer.rb +85 -1
  69. data/lib/generators/stall/model/model_generator.rb +27 -0
  70. data/lib/generators/stall/service/service_generator.rb +39 -0
  71. data/lib/generators/stall/service/templates/service.rb.erb +16 -0
  72. data/lib/generators/stall/shipping/calculator/calculator_generator.rb +17 -0
  73. data/lib/generators/stall/shipping/calculator/templates/calculator.rb.erb +29 -0
  74. data/lib/stall.rb +4 -0
  75. data/lib/stall/addressable.rb +28 -2
  76. data/lib/stall/cart_helper.rb +84 -0
  77. data/lib/stall/carts_cleaner.rb +46 -0
  78. data/lib/stall/checkout.rb +2 -0
  79. data/lib/stall/checkout/informations_checkout_step.rb +3 -5
  80. data/lib/stall/checkout/payment_checkout_step.rb +3 -0
  81. data/lib/stall/checkout/payment_return_checkout_step.rb +11 -0
  82. data/lib/stall/checkout/shipping_method_checkout_step.rb +2 -1
  83. data/lib/stall/checkout/step.rb +47 -11
  84. data/lib/stall/checkout/step_form.rb +71 -0
  85. data/lib/stall/checkout/wizard.rb +15 -5
  86. data/lib/stall/config.rb +51 -0
  87. data/lib/stall/engine.rb +24 -2
  88. data/lib/stall/payable.rb +26 -0
  89. data/lib/stall/payments.rb +7 -3
  90. data/lib/stall/payments/config.rb +37 -0
  91. data/lib/stall/payments/fake_gateway_payment_notification.rb +34 -0
  92. data/lib/stall/payments/gateway.rb +19 -12
  93. data/lib/stall/payments/urls_config.rb +40 -0
  94. data/lib/stall/priceable.rb +7 -0
  95. data/lib/stall/rails/currency_helper.rb +1 -1
  96. data/lib/stall/routes.rb +17 -4
  97. data/lib/stall/sellable.rb +1 -2
  98. data/lib/stall/shipping.rb +3 -1
  99. data/lib/stall/shipping/calculator.rb +36 -3
  100. data/lib/stall/shipping/config.rb +17 -3
  101. data/lib/stall/shipping/country_weight_table_calculator.rb +1 -1
  102. data/lib/stall/shipping/free_shipping_calculator.rb +1 -1
  103. data/lib/stall/utils.rb +2 -2
  104. data/lib/stall/utils/config_dsl.rb +5 -1
  105. data/lib/stall/version.rb +1 -1
  106. data/lib/tasks/stall_tasks.rake +11 -0
  107. metadata +73 -15
  108. data/app/assets/javascripts/stall/application.js +0 -13
  109. data/app/assets/javascripts/stall/carts.js +0 -2
  110. data/app/helpers/stall/cart_helper.rb +0 -28
  111. data/app/models/stall/address.rb +0 -5
  112. data/app/models/stall/address_ownership.rb +0 -8
  113. data/app/models/stall/cart.rb +0 -31
  114. data/app/models/stall/customer.rb +0 -8
  115. data/app/models/stall/line_item.rb +0 -49
  116. data/app/models/stall/payment.rb +0 -14
  117. data/app/models/stall/payment_method.rb +0 -7
  118. data/app/models/stall/product_list.rb +0 -68
  119. data/app/models/stall/shipment.rb +0 -15
  120. data/app/models/stall/shipping_method.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8be8383152aa8e6949b5ff132d32521541d15625
4
- data.tar.gz: b62bad7b2c986d09c579bee22d959c4984dda358
3
+ metadata.gz: 0524d2c5e775b699249a9e311d7eb05f2a28329a
4
+ data.tar.gz: f13ac7bc4efa4013a6cabf3d92214669832b2610
5
5
  SHA512:
6
- metadata.gz: 3931aee7e76b2e07d36b8f9f62e2e7fa89688195da6fd3fea8c5d1f0bd57f7b3e92343cea51ea4e734700dc0f81ba160c7117ed1fa1ce6928802d917cf7b26f6
7
- data.tar.gz: dd6a569d77cdd85bad7832ffe7aa9dbc21e6fd6450d4ec23e8399063c8c4c688147566cfec61faa5e19aa8977864d3e0fe22c858fb22a3518bb3e6689da7402c
6
+ metadata.gz: d25def30d139c976bbcc3c25635bcc4321dc152b008c62ea4b9d64798b6402b0d1715625e14763588ee3b0d23bef7d88c9591bbc230d9b31ad486d3c8d147544
7
+ data.tar.gz: c4a3e2c50f5770ad510c1cf8dce6329526e8dc3b5c16172960150976d0ea6ad75b3c87cf4f6735f1c345680a685ba6b6caf02c18352fe17610a0439d624fc337
data/README.md CHANGED
@@ -65,6 +65,51 @@ The checkout process is completely flexible and can be overriden easily.
65
65
  Please see the Wiki page :
66
66
  [The checkout process](https://github.com/rails-stall/stall/wiki/The-checkout-process)
67
67
 
68
+
69
+ ### Cleaning up aborted carts
70
+
71
+ A cart is created for each new visit on the app. You may want to clean
72
+ aborted carts to avoid the table to grow too big.
73
+
74
+ You can setup the provided `rake` task in a cron :
75
+
76
+ ```bash
77
+ rake stall:carts:clean
78
+ ```
79
+
80
+ #### Configuring what is cleaned in the task
81
+
82
+ Two types of aborted carts are cleaned :
83
+
84
+ - Empty carts older than 24h
85
+ - Aborted carts, with products added, older than 2 weeks
86
+
87
+ You can configure both delays with the following config in the stall initializer :
88
+
89
+ ```ruby
90
+ config.empty_carts_expires_after = 1.day
91
+ config.aborted_carts_expires_after = 14.days
92
+ ```
93
+
94
+ #### Cleaning other types of ProductList
95
+
96
+ You can create your own cart classes by extending the `Cart` or `ProductList`
97
+ model in your app.
98
+
99
+ To clean those models, you just need to pass the cart class name to the rake
100
+ task when calling it. For a `TestCart` model, you'd do :
101
+
102
+ ```bash
103
+ rake stall:carts:clean[TestCart]
104
+ ```
105
+
106
+ > **Note** : that double-quotes may be needed around the task name to make that
107
+ syntax work with some shells (e.g. zsh) : `rake "stall:carts:clean[TestCart]"`
108
+
109
+ You can also override in your subclass the way that the task retrieves aborted
110
+ carts by defining a scope or class method `.aborted(options)` where `options`
111
+ may contain a `:before` option.
112
+
68
113
  # Licence
69
114
 
70
115
  This project rocks and uses MIT-LICENSE.
@@ -1,6 +1,8 @@
1
1
  #= require vertebra
2
+ #= require cocoon
2
3
  #= require_self
3
4
  #= require stall/add-to-cart-form
5
+ #= require stall/cart-form
4
6
 
5
7
  @Stall =
6
8
  onDomReady: (callback) ->
@@ -0,0 +1,23 @@
1
+ class Stall.CartForm extends Vertebra.View
2
+ events:
3
+ 'change [data-quantity-field]': 'formUpdated'
4
+ 'cocoon:after-remove': 'formUpdated'
5
+ 'ajax:success': 'updateSuccess'
6
+
7
+ initialize: ->
8
+ @clean()
9
+
10
+ clean: ->
11
+ @$('[data-cart-update-button]').hide(0)
12
+
13
+ formUpdated: (e) ->
14
+ @$el.submit()
15
+
16
+ updateSuccess: (e, resp) ->
17
+ $form = $(resp).find('[data-cart-form]')
18
+ @$el.html($form.html())
19
+ @clean()
20
+
21
+ Stall.onDomReady ->
22
+ if ($cartForm = $('[data-cart-form]')).length
23
+ new Stall.CartForm(el: $cartForm)
@@ -1,13 +1,21 @@
1
1
  module Stall
2
2
  class ApplicationController < Stall.config.application_controller_ancestor.constantize
3
- layout :_set_stall_layout
3
+ before_action :set_request_variant
4
+ layout :set_stall_layout
4
5
 
5
6
  private
6
7
 
8
+ def set_request_variant
9
+ request.variant = :xhr if request.xhr?
10
+ end
11
+
7
12
  # Enforce app-level layout by defaulting to the "application" layout if
8
13
  # no parent layout was set before, thus avoiding the "stall/application"
9
14
  # layout to be rendered
10
- def _set_stall_layout
15
+ def set_stall_layout
16
+ return false if request.xhr?
17
+ return Stall.config.default_layout if Stall.config.default_layout
18
+
11
19
  parent_controller = self.class.ancestors.find do |ancestor|
12
20
  !ancestor.name.match(/^Stall::/)
13
21
  end
@@ -1,28 +1,49 @@
1
1
  module Stall
2
- class CartsController < ApplicationController
3
- before_action :load_cart
2
+ class CartsController < Stall::ApplicationController
3
+ before_action :load_cart, except: :show
4
4
 
5
5
  def show
6
+ @cart = ProductList.find_by_token(params[:id]) || current_cart
6
7
  end
7
8
 
8
9
  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'
10
+ respond_to do |format|
11
+ service = Stall::CartUpdateService.new(@cart, cart_params)
12
+
13
+ if service.call
14
+ format.html.xhr do
15
+ render 'show'
16
+ end
17
+
18
+ format.html.any do
19
+ flash[:success] = t('stall.carts.flashes.update.success')
20
+ redirect_to cart_path(@cart)
21
+ end
22
+ else
23
+ format.html.xhr do
24
+ render 'show'
25
+ end
26
+
27
+ format.html.any do
28
+ flash[:error] = t('stall.carts.flashes.update.error')
29
+ render 'show'
30
+ end
31
+ end
15
32
  end
16
33
  end
17
34
 
18
35
  private
19
36
 
20
37
  def load_cart
21
- @cart = Cart.find_by_token(params[:id])
38
+ @cart = ProductList.find_by_token!(params[:id])
22
39
  end
23
40
 
24
41
  def cart_params
25
- params.require(:cart).permit(line_items_attributes: [:id, :quantity])
42
+ params.require(:cart).permit(
43
+ line_items_attributes: [
44
+ :id, :quantity, :_destroy
45
+ ]
46
+ )
26
47
  end
27
48
  end
28
49
  end
@@ -3,6 +3,8 @@ module Stall
3
3
  class StepsController < Stall::ApplicationController
4
4
  include Stall::CheckoutHelper
5
5
 
6
+ before_action :load_cart
7
+ before_action :ensure_cart_checkoutable
6
8
  before_action :load_step
7
9
 
8
10
  def show
@@ -12,21 +14,51 @@ module Stall
12
14
  def update
13
15
  if @step.process
14
16
  @wizard.validate_current_step!
15
- redirect_to step_path(@cart)
17
+ redirect_to step_path
16
18
  else
17
19
  @step.prepare
18
- flash[:error] = t("stall.checkout.#{ @wizard.current_step_name }.error")
20
+ flash[:error] ||= t("stall.checkout.#{ @wizard.current_step_name }.error")
21
+ render 'show'
22
+ end
23
+ end
24
+
25
+ def change
26
+ target_step = params[:step]
27
+
28
+ if @wizard.step_complete?(target_step)
29
+ @wizard.move_to_step!(target_step)
30
+ redirect_to step_path
31
+ else
32
+ @step.prepare
33
+ flash[:error] ||= t("stall.checkout.#{ target_step }.error")
19
34
  render 'show'
20
35
  end
21
36
  end
22
37
 
23
38
  private
24
39
 
40
+ def load_cart
41
+ @cart = current_cart
42
+ end
43
+
44
+ def ensure_cart_checkoutable
45
+ unless @cart.checkoutable?
46
+ flash[:error] = t('stall.checkout.shared.not_checkoutable')
47
+ redirect_to request.referrer || root_path
48
+ end
49
+ end
50
+
25
51
  def load_step
26
- @cart = Stall::Cart.find_by_token(params[:cart_id])
27
52
  @wizard = @cart.wizard.new(@cart)
28
53
 
29
- @step = @wizard.initialize_current_step(params) do |step|
54
+ @step = @wizard.initialize_current_step do |step|
55
+ # Inject request dependent data
56
+ step.inject(:params, params)
57
+ step.inject(:session, session)
58
+ step.inject(:cookies, cookies)
59
+ step.inject(:request, request)
60
+ step.inject(:flash, flash)
61
+
30
62
  if Stall.config.steps_initialization
31
63
  instance_exec(step, &Stall.config.steps_initialization)
32
64
  end
@@ -1,9 +1,18 @@
1
1
  module Stall
2
- class CheckoutsController < ApplicationController
2
+ class CheckoutsController < Stall::ApplicationController
3
+ include CheckoutHelper
4
+
5
+ before_action :load_cart
6
+
3
7
  def show
4
- @cart = Stall::Cart.find_by_token(params[:id])
5
8
  @cart.reset_state!
6
- redirect_to checkout_step_path(@cart.wizard.route_key, @cart)
9
+ redirect_to step_path
10
+ end
11
+
12
+ private
13
+
14
+ def load_cart
15
+ @cart = current_cart
7
16
  end
8
17
  end
9
18
  end
@@ -1,7 +1,7 @@
1
1
  module Stall
2
- class LineItemsController < ApplicationController
2
+ class LineItemsController < Stall::ApplicationController
3
3
  def create
4
- service = Stall::AddToCartService.new(cart, line_item_params)
4
+ service = Stall.config.service_for(:add_to_cart).new(cart, line_item_params)
5
5
 
6
6
  if service.call
7
7
  @quantity = params[:line_item][:quantity].to_i
@@ -20,7 +20,7 @@ module Stall
20
20
  end
21
21
 
22
22
  def cart
23
- @cart ||= Stall::Cart.find_by_token(params[:cart_id])
23
+ @cart ||= ProductList.find_by_token(params[:cart_id]) || current_cart
24
24
  end
25
25
  end
26
26
  end
@@ -1,7 +1,9 @@
1
1
  module Stall
2
- class PaymentsController < ApplicationController
3
- def process
4
- service = Stall::PaymentNotificationService.new(request)
2
+ class PaymentsController < Stall::ApplicationController
3
+ skip_before_action :verify_authenticity_token, only: [:notify]
4
+
5
+ def notify
6
+ service = Stall.config.service_for(:payment_notification).new(params[:gateway], request)
5
7
  service.call
6
8
  render service.rendering_options
7
9
  end
@@ -3,7 +3,7 @@ module Stall
3
3
  def add_to_cart_form_for(sellable, cart: nil)
4
4
  render partial: 'stall/line_items/form', locals: {
5
5
  cart: (cart || current_cart),
6
- line_item: Stall::LineItem.new(sellable: sellable)
6
+ line_item: LineItem.new(sellable: sellable)
7
7
  }
8
8
  end
9
9
  end
@@ -1,7 +1,27 @@
1
1
  module Stall
2
2
  module CheckoutHelper
3
- def step_path(cart)
4
- checkout_step_path(cart.wizard.route_key, cart)
3
+ def step_path(*args)
4
+ checkout_step_path(*([current_cart_key] + args))
5
+ end
6
+
7
+ def available_shipping_methods_for(cart)
8
+ ShippingMethod.active.ordered.select do |shipping_method|
9
+ calculator_class = Stall::Shipping::Calculator.for(shipping_method)
10
+
11
+ unless calculator_class
12
+ raise Stall::Shipping::CalculatorNotFound,
13
+ "No calculator found for the shipping method : " +
14
+ "#{ shipping_method.name } " +
15
+ "(#{ shipping_method.identifier }). " +
16
+ "Please remove the Stall::ShippingMethod with id " +
17
+ "#{ shipping_method.id } or create the associated " +
18
+ "calculator with `rails g stall:shipping:calculator " +
19
+ "#{ shipping_method.identifier }`"
20
+ end
21
+
22
+ calculator = calculator_class.new(cart, shipping_method)
23
+ calculator.available?
24
+ end
5
25
  end
6
26
  end
7
27
  end
@@ -0,0 +1,8 @@
1
+ module Stall
2
+ module PaymentsHelper
3
+ def payment_button_for(cart)
4
+ request = Stall::Payments::Gateway.for(cart.payment.payment_method).request(cart)
5
+ render partial: request.payment_form_partial_path, locals: { request: request }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module Stall
2
+ class AdminMailer < Stall::BaseMailer
3
+ def order_paid_email(cart)
4
+ @cart = cart
5
+
6
+ mail from: sender_email_for(cart),
7
+ to: admin_email_for(cart),
8
+ subject: I18n.t('stall.mailers.admin.order_paid_email.subject', ref: cart.reference)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Stall
2
+ class BaseMailer < Stall.config.mailers_parent_class.constantize
3
+ private
4
+
5
+ def sender_email_for(cart)
6
+ value_for(Stall.config.sender_email, cart)
7
+ end
8
+
9
+ def admin_email_for(cart)
10
+ value_for(Stall.config.admin_email, cart)
11
+ end
12
+
13
+ def value_for(config, cart)
14
+ Proc === config ? config.call(cart) : config
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Stall
2
+ class CustomerMailer < Stall::BaseMailer
3
+ def order_paid_email(cart)
4
+ I18n.with_locale(cart.customer.locale) do
5
+ @cart = cart
6
+
7
+ mail from: sender_email_for(cart),
8
+ to: cart.customer.email,
9
+ subject: I18n.t('stall.mailers.customer.order_paid_email.subject', ref: cart.reference)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ class Address < ActiveRecord::Base
2
+ include Stall::Models::Address
3
+ end
@@ -0,0 +1,3 @@
1
+ class AddressOwnership < ActiveRecord::Base
2
+ include Stall::Models::AddressOwnership
3
+ end
@@ -0,0 +1,3 @@
1
+ class Adjustment < ActiveRecord::Base
2
+ include Stall::Models::Adjustment
3
+ end
@@ -0,0 +1,3 @@
1
+ class Cart < ::ProductList
2
+ include Stall::Models::Cart
3
+ end
@@ -0,0 +1,3 @@
1
+ class Customer < ActiveRecord::Base
2
+ include Stall::Models::Customer
3
+ end
@@ -0,0 +1,3 @@
1
+ class LineItem < ActiveRecord::Base
2
+ include Stall::Models::LineItem
3
+ end
@@ -0,0 +1,3 @@
1
+ class Payment < ActiveRecord::Base
2
+ include Stall::Models::Payment
3
+ end
@@ -0,0 +1,3 @@
1
+ class PaymentMethod < ActiveRecord::Base
2
+ include Stall::Models::PaymentMethod
3
+ end
@@ -0,0 +1,3 @@
1
+ class ProductList < ActiveRecord::Base
2
+ include Stall::Models::ProductList
3
+ end
@@ -0,0 +1,3 @@
1
+ class Shipment < ActiveRecord::Base
2
+ include Stall::Models::Shipment
3
+ end