stall 0.1.1 → 0.1.2

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