stall 0.1.3 → 0.2.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -3
  3. data/app/assets/javascripts/stall/add-to-cart-form.coffee +55 -3
  4. data/app/assets/javascripts/stall/addresses-fields.coffee +17 -0
  5. data/app/assets/javascripts/stall/remote-sign-in-form.coffee +55 -0
  6. data/app/assets/javascripts/stall.coffee +3 -1
  7. data/app/controllers/stall/application_controller.rb +1 -1
  8. data/app/controllers/stall/carts_controller.rb +1 -3
  9. data/app/controllers/stall/checkout/steps_controller.rb +18 -1
  10. data/app/helpers/stall/checkout_helper.rb +7 -8
  11. data/app/helpers/stall/customers_helper.rb +25 -0
  12. data/app/models/stall/models/cart.rb +2 -0
  13. data/app/models/stall/models/customer.rb +20 -0
  14. data/app/models/stall/models/product_list.rb +13 -0
  15. data/app/views/checkout/steps/_informations.html.haml +24 -35
  16. data/app/views/checkout/steps/_payment.html.haml +1 -45
  17. data/app/views/checkout/steps/_shipping_method.html.haml +0 -1
  18. data/app/views/stall/addresses/_fields.html.haml +16 -0
  19. data/app/views/stall/addresses/_nested_fields.html.haml +14 -0
  20. data/app/views/stall/carts/_cart.html.haml +45 -0
  21. data/app/views/stall/carts/show.html.haml +2 -2
  22. data/app/views/stall/customers/_fields.html.haml +32 -0
  23. data/app/views/stall/customers/_sign_in.html.haml +20 -0
  24. data/app/views/stall/line_items/_form.html.haml +2 -2
  25. data/app/views/stall/payments/_fields.html.haml +2 -0
  26. data/app/views/stall/shared/mailers/_cart.html.haml +31 -29
  27. data/app/views/stall/shipments/_fields.html.haml +2 -0
  28. data/config/locales/stall.fr.yml +35 -8
  29. data/lib/generators/stall/checkout/step/templates/step.rb.erb +9 -0
  30. data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +1 -1
  31. data/lib/generators/stall/install/install_generator.rb +6 -0
  32. data/lib/generators/stall/install/templates/initializer.rb +10 -1
  33. data/lib/generators/stall/view/view_generator.rb +38 -0
  34. data/lib/stall/addressable.rb +2 -0
  35. data/lib/stall/addresses/copier_base.rb +24 -0
  36. data/lib/stall/addresses/copy_source_to_target.rb +45 -0
  37. data/lib/stall/addresses/prefill_target_from_source.rb +29 -0
  38. data/lib/stall/addresses.rb +9 -0
  39. data/lib/stall/cart_helper.rb +14 -1
  40. data/lib/stall/checkout/informations_checkout_step.rb +133 -14
  41. data/lib/stall/checkout/payment_checkout_step.rb +21 -1
  42. data/lib/stall/checkout/step.rb +22 -15
  43. data/lib/stall/checkout/step_form.rb +22 -8
  44. data/lib/stall/config.rb +17 -3
  45. data/lib/stall/engine.rb +6 -0
  46. data/lib/stall/payable.rb +9 -0
  47. data/lib/stall/payments/gateway.rb +16 -6
  48. data/lib/stall/payments/urls_config.rb +2 -2
  49. data/lib/stall/rails/routing_mapper.rb +4 -6
  50. data/lib/stall/routes.rb +4 -1
  51. data/lib/stall/sellable.rb +13 -3
  52. data/lib/stall/version.rb +1 -1
  53. data/lib/stall.rb +1 -0
  54. metadata +25 -6
  55. data/lib/stall/checkout/payment_method_checkout_step.rb +0 -9
  56. data/lib/stall/checkout/shipping_method_checkout_step.rb +0 -21
@@ -0,0 +1,32 @@
1
+ -# When stall a user is signed in, a customer autoatically exist and is assigned
2
+ -# to the cart, by default, we notify the visitor that he's signed in and will
3
+ -# order with that account, making customer and user related fields unnecessary
4
+ - if stall_user_signed_in?
5
+ .alert.alert-info
6
+ %i.fa.fa-info-circle
7
+ = t('stall.checkout.informations.signed_in_as', email: current_stall_user.email)
8
+
9
+ - else
10
+ -# The `#with_errors_from_user` helper ensures that user e-mail errors, like
11
+ -# format or uniqueness, are actually bubbled up to the customer e-mail field.
12
+ = form.fields_for :customer, with_errors_from_user(form.object.customer) do |customer_form|
13
+ = customer_form.input :email
14
+
15
+ = form.fields_for :customer do |customer_form|
16
+ = customer_form.fields_for :user, customer_form.object.user_or_default do |user_form|
17
+ %fieldset.collapse{ class: ('in' if params[:create_account] == '1'), data: { :'user-fields' => true } }
18
+ = user_form.input :password
19
+ = user_form.input :password_confirmation
20
+
21
+ %p.help-block
22
+ %label
23
+ -# This field allows the step to determine wether an account should be
24
+ -# created or not while creating the customer, allowing it to clean
25
+ -# user related fields if it's not needed.
26
+ -#
27
+ -# The `autocomplete="off"` clears the Firefox cache that autofills the
28
+ -# checked attribute and breaks the unerlying basic bootstrap collapse
29
+ -# behavior
30
+ = check_box_tag :create_account, '1', (params[:create_account] == '1'), class: 'collapsed', autocomplete: 'off', data: { target: '[data-user-fields]', toggle: 'collapse' }
31
+ = t('stall.checkout.informations.create_an_account')
32
+ %hr
@@ -0,0 +1,20 @@
1
+ .panel.panel-info
2
+ .panel-heading
3
+ %i.fa.fa-user
4
+ = t('stall.checkout.sign_in.already_have_an_account')
5
+ = link_to '#sign-in-form', data: { toggle: 'collapse' } do
6
+ = t('stall.checkout.sign_in.click_to_sign_in')
7
+
8
+ #sign-in-form.panel-body.collapse{ data: { :'sign-in-form' => true } }
9
+ %p.help-block
10
+ = t('stall.checkout.sign_in.sign_in_instructions', label: t('stall.checkout.sign_in.button_label'))
11
+
12
+ = simple_form_for resource, as: resource_name, url: session_path(resource_name), remote: true, html: { data: { :'stall-remote-sign-in-form' => true } } do |form|
13
+ .row
14
+ .col-md-6
15
+ = form.input :email
16
+ .col-md-6
17
+ = form.input :password
18
+
19
+ %button.btn.btn-primary{ type: 'submit', autocomplete: 'off', data: { :'sign-in-submit-btn' => true, :'loading-text' => t('stall.shared.sending') } }
20
+ = t('stall.checkout.sign_in.button_label')
@@ -1,4 +1,4 @@
1
- = simple_form_for line_item, as: :line_item, url: cart_line_items_path(cart), remote: true, data: { :'add-to-cart-form' => true } do |form|
1
+ = simple_form_for line_item, as: :line_item, url: cart_line_items_path(cart), remote: true, data: { :'add-to-cart-form' => true, :'error-messages' => { choose: t('stall.line_items.errors.choose'), quantity: t('stall.line_items.errors.quantity') } } do |form|
2
2
  = form.hidden_field :sellable_type
3
3
  = form.hidden_field :sellable_id
4
4
 
@@ -6,7 +6,7 @@
6
6
  = form.input_field :quantity, spinner: false, value: 1, class: 'form-group', data: { :'quantity-field' => true }
7
7
 
8
8
  %span.input-group-btn
9
- %button.btn.btn-primary{ type: 'submit', style: 'font-size: 12px' }
9
+ %button.btn.btn-primary{ type: 'submit', data: { :'loading-text' => t('stall.shared.sending') } }
10
10
  %i.fa.fa-shopping-cart
11
11
  = t('stall.line_items.form.add_to_cart')
12
12
 
@@ -0,0 +1,2 @@
1
+ = form.fields_for :payment do |payment_fields|
2
+ = payment_fields.association :payment_method, as: :radio_buttons, collection: PaymentMethod.active.ordered
@@ -78,37 +78,39 @@
78
78
  %td
79
79
  %table
80
80
  %tbody
81
- %tr
82
- %td{ colspan: 2 }
83
- %strong= Cart.human_attribute_name(:payment)
81
+ - if cart.payment && cart.payment.payment_method
82
+ %tr
83
+ %td{ colspan: 2 }
84
+ %strong= Cart.human_attribute_name(:payment)
84
85
 
85
- %tr
86
- %td
87
- = Payment.human_attribute_name(:payment_method)
88
- %td
89
- = cart.payment.payment_method.name
86
+ %tr
87
+ %td
88
+ = Payment.human_attribute_name(:payment_method)
89
+ %td
90
+ = cart.payment.payment_method.name
90
91
 
91
- %tr
92
- %td
93
- = Payment.human_attribute_name(:state)
94
- %td
95
- - if cart.payment.paid?
96
- = t("stall.carts.recap.paid_at", at: cart.payment.paid_at)
97
- - else
98
- = t("activerecord.enums.payment.state.#{ cart.payment.state }")
92
+ %tr
93
+ %td
94
+ = Payment.human_attribute_name(:state)
95
+ %td
96
+ - if cart.payment.paid?
97
+ = t("stall.carts.recap.paid_at", at: cart.payment.paid_at)
98
+ - else
99
+ = t("activerecord.enums.payment.state.#{ cart.payment.state }")
99
100
 
100
- %tr
101
- %td{ colspan: 2 }
102
- %strong= Cart.human_attribute_name(:shipment)
101
+ - if cart.shipment && cart.shipment.shipping_method
102
+ %tr
103
+ %td{ colspan: 2 }
104
+ %strong= Cart.human_attribute_name(:shipment)
103
105
 
104
- %tr
105
- %td
106
- = Shipment.human_attribute_name(:shipping_method)
107
- %td
108
- = cart.shipment.shipping_method.name
106
+ %tr
107
+ %td
108
+ = Shipment.human_attribute_name(:shipping_method)
109
+ %td
110
+ = cart.shipment.shipping_method.name
109
111
 
110
- %tr
111
- %td
112
- = Shipment.human_attribute_name(:state)
113
- %td
114
- = t("activerecord.enums.payment.state.#{ cart.shipment.state }")
112
+ %tr
113
+ %td
114
+ = Shipment.human_attribute_name(:state)
115
+ %td
116
+ = t("activerecord.enums.payment.state.#{ cart.shipment.state }")
@@ -0,0 +1,2 @@
1
+ = form.fields_for :shipment do |shipment_fields|
2
+ = shipment_fields.association :shipping_method, as: :radio_buttons
@@ -2,14 +2,17 @@ fr:
2
2
  stall:
3
3
  shared:
4
4
  close: "Fermer"
5
+ sending: "Envoi en cours ..."
5
6
 
6
7
  carts:
7
8
  formats:
8
9
  name: "Commande n°%{ref}"
10
+
9
11
  flashes:
10
12
  update:
11
13
  success: "Votre panier a bien été mis à jour."
12
14
  error: "Votre panier n'a pu être mis à jour, merci de vérifier les champs."
15
+
13
16
  recap:
14
17
  order_ref: "Commande n°%{ref}"
15
18
  ordered_at: "Passée le %{at}"
@@ -32,20 +35,28 @@ fr:
32
35
  add_error:
33
36
  title: "Votre produit n'a pu être ajouté"
34
37
  description: "Merci de vérifier que vous avez bien rempli la quantité avant d'ajouter le produit au panier."
38
+ errors:
39
+ choose: "Merci de choisir un produit afin de l'ajouter au panier"
40
+ quantity: "Merci de choisir une quantité à ajouter au panier"
35
41
 
36
42
  checkout:
37
43
  shared:
38
- not_checkoutable: "Aucun produit dans le panier, impossible de passer la commande."
44
+ not_checkoutable: "Le panier a expiré, celui-ci a probablement déjà été payé."
39
45
  informations:
40
46
  title: "Vos informations"
41
- validate: "Valider mes adresses"
47
+ validate: "Valider la commande et passer au paiement"
42
48
  error: "Vos informations n'ont pu être enregistrées, merci de vérifier les champs"
43
- shipping_method:
44
- title: "Mode de livraison"
45
- validate: "Valider mon mode de livraison"
46
- payment_method:
47
- title: "Mode de paiement"
48
- validate: "Valider mon mode de paiement"
49
+ signed_in_as: "Vous êtes actuellement connectés en tant que %{email}"
50
+ create_an_account: "Me créer un compte"
51
+ accept_the: "J'accepte les "
52
+ sign_in:
53
+ already_have_an_account: "J'ai déjà un compte :"
54
+ click_to_sign_in: "Cliquez-ici pour vous connecter"
55
+ sign_in_instructions: |
56
+ Renseigez dans les champs suivant vos identifiants de connexion puis
57
+ cliquez sur le bouton "%{label}"
58
+ button_label: "Me connecter"
59
+
49
60
  payment:
50
61
  title: "Paiement"
51
62
  error: |
@@ -91,6 +102,7 @@ fr:
91
102
  state:
92
103
  pending: "En attente"
93
104
  shipped: "Payé"
105
+
94
106
  models:
95
107
  line_item: "Produit"
96
108
  customer: "Client"
@@ -98,6 +110,7 @@ fr:
98
110
  cart: "Panier"
99
111
  shipment: "Livraison"
100
112
  payment: "Paiement"
113
+
101
114
  attributes:
102
115
  line_item:
103
116
  sellable: "Produit source"
@@ -129,6 +142,7 @@ fr:
129
142
  customer: "Client"
130
143
  created_at: "Date"
131
144
  line_items: "Produits"
145
+ use_another_address_for_billing: "Utiliser une autre adresse pour la facturation"
132
146
  cart:
133
147
  state: "Étape du panier"
134
148
  total_price: "Montant total"
@@ -136,6 +150,7 @@ fr:
136
150
  shipment: "Livraison"
137
151
  shipping_address: "Adresse de livraison"
138
152
  billing_address: "Adresse de facturation"
153
+ terms: "Conditions générales de vente"
139
154
  shipment:
140
155
  price: "Prix"
141
156
  eot_price: "Prix HT"
@@ -149,3 +164,15 @@ fr:
149
164
  paid_at: "Payé le"
150
165
  transaction_id: "Numéro de transaction"
151
166
  state: "État"
167
+
168
+ simple_form:
169
+ placeholders:
170
+ address:
171
+ first_name: "Votre prénom"
172
+ last_name: "Votre nom"
173
+ address: "Numéro et rue"
174
+ address_details: "Autres informations : bâtiment, escalier, interphone etc."
175
+ country: "Votre pays"
176
+ zip: "Votre code postal"
177
+ city: "Votre ville"
178
+ phone: "Votre numéro de téléphone (portable / fixe)"
@@ -24,4 +24,13 @@ class <%= class_name %> < Stall::Checkout::Step
24
24
  # def skip?
25
25
  # false
26
26
  # end
27
+
28
+ private
29
+
30
+ # Process `params` here, defaults to assigning the cart with `cart_params`
31
+ # (params[:cart]) to the cart, running the step validations
32
+ #
33
+ def cart_params
34
+ params.require(:cart).permit({})
35
+ end
27
36
  end
@@ -1,3 +1,3 @@
1
1
  class <%= class_name %> < Stall::Checkout::Wizard
2
- steps :informations, :shipping_method, :payment_method, :payment, :payment_return
2
+ steps :informations, :payment, :payment_return
3
3
  end
@@ -13,5 +13,11 @@ module Stall
13
13
  def copy_default_checkout_wizard
14
14
  generate 'stall:checkout:wizard', 'default'
15
15
  end
16
+
17
+ def mount_engine_in_routes
18
+ say "Mounting Stall engine in routes"
19
+ gsub_file 'config/routes.rb', /mount_stall.+\n/, ''
20
+ route "mount_stall '/'"
21
+ end
16
22
  end
17
23
  end
@@ -56,7 +56,7 @@ Stall.configure do |config|
56
56
  # that you create your line items, carts and orders with the right currency
57
57
  # set.
58
58
  #
59
- # param :default_currency, 'EUR'
59
+ # config.default_currency = 'EUR'
60
60
 
61
61
  # Default app domain use for building URLs in payment gateway forms and in
62
62
  # e-mails.
@@ -75,6 +75,15 @@ Stall.configure do |config|
75
75
  # A default one is generated on stall's installation and can be found in your
76
76
  # app at : lib/default_checkout_wizard.rb
77
77
  #
78
+ # Note :
79
+ #
80
+ # If you do not autoload files in your app's lib folder, please require your
81
+ # checkout wizard with the following command here
82
+ #
83
+ # require 'default_checkout_wizard'
84
+ #
85
+ #
86
+ #
78
87
  # config.default_checkout_wizard = 'DefaultCheckoutWizard'
79
88
 
80
89
  # Default line items weight when added to cart and no weight is found on
@@ -0,0 +1,38 @@
1
+ module Stall
2
+ class ViewGenerator < ::Rails::Generators::Base
3
+ class ViewNotFound < StandardError; end
4
+
5
+ VIEWS_DIR = File.expand_path('../../../../../app/views', __FILE__)
6
+ source_root VIEWS_DIR
7
+
8
+ argument :file_paths, type: :array, required: true
9
+
10
+ def copy_view_template
11
+ paths = file_paths.map do |file_path|
12
+ source_path = source_path_for(file_path)
13
+ raise ViewNotFound, "Could not find any stall view for #{ file_path }" unless File.exist?(source_path)
14
+ target_path = "app/views/#{ file_path_with_ext_for(file_path) }"
15
+
16
+ [source_path, target_path]
17
+ end
18
+
19
+ paths.each do |source_path, target_path|
20
+ template(source_path, target_path)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def source_path_for(file_path)
27
+ File.join(VIEWS_DIR, file_path_with_ext_for(file_path))
28
+ end
29
+
30
+ def file_path_with_ext_for(file_path)
31
+ if file_path.match(/\.html\.haml\z/)
32
+ file_path
33
+ else
34
+ "#{ file_path }.html.haml"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -11,6 +11,8 @@ module Stall
11
11
 
12
12
  has_many :addresses, through: :address_ownerships
13
13
  accepts_nested_attributes_for :address_ownerships, allow_destroy: true
14
+
15
+ attr_accessor :use_another_address_for_billing
14
16
  end
15
17
 
16
18
  def address_ownership_for(type)
@@ -0,0 +1,24 @@
1
+ module Stall
2
+ module Addresses
3
+ class CopierBase
4
+ attr_reader :source, :target
5
+
6
+ def initialize(source, target)
7
+ @source = source
8
+ @target = target
9
+ end
10
+
11
+ def copy
12
+ fail NotImplementedError
13
+ end
14
+
15
+ private
16
+
17
+ def duplicate_attributes(model)
18
+ model.attributes.dup.tap do |attributes|
19
+ attributes.delete('id')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ # Allows copying
2
+ module Stall
3
+ module Addresses
4
+ class CopySourceToTarget < Stall::Addresses::CopierBase
5
+ def copy!
6
+ copy
7
+ target.save!
8
+ end
9
+
10
+ def copy
11
+ copy_shipping_address
12
+ copy_billing_address
13
+ end
14
+
15
+ private
16
+
17
+ def copy_shipping_address
18
+ # Update or create target's shipping address with source's shipping
19
+ # address attributes
20
+ if target.shipping_address
21
+ target.shipping_address.assign_attributes(duplicate_attributes(source.shipping_address))
22
+ else
23
+ target.build_shipping_address(duplicate_attributes(source.shipping_address))
24
+ end
25
+ end
26
+
27
+ def copy_billing_address
28
+ # If the source uses the same address for shipping and billing, we
29
+ # reproduce this behavior ont the target
30
+ if source.shipping_address == source.billing_address
31
+ shipping_ownership = target.address_ownership_for(:shipping)
32
+ target.mark_address_ownership_as_billing(shipping_ownership)
33
+ # If the source has a separate billing address, we update or create the
34
+ # target's billing address with the source's one attributes
35
+ else
36
+ if target.billing_address
37
+ target.billing_address.assign_attributes(duplicate_attributes(source.billing_address))
38
+ else
39
+ target.build_billing_address(duplicate_attributes(source.billing_address))
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ module Stall
2
+ module Addresses
3
+ class PrefillTargetFromSource < Stall::Addresses::CopierBase
4
+ def copy
5
+ prefill_shipping_address
6
+ prefill_billing_address
7
+ end
8
+
9
+ private
10
+
11
+ def prefill_shipping_address
12
+ if source.shipping_address && !target.shipping_address
13
+ target.build_shipping_address(duplicate_attributes(source.shipping_address))
14
+ elsif !target.shipping_address
15
+ target.build_shipping_address
16
+ end
17
+ end
18
+
19
+ def prefill_billing_address
20
+ if source.billing_address && !target.billing_address
21
+ target.build_billing_address(duplicate_attributes(source.billing_address))
22
+ elsif !target.billing_address || target.billing_address == target.shipping_address
23
+ target.address_ownership_for(:shipping).billing = false
24
+ target.build_billing_address
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module Stall
2
+ module Addresses
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :CopierBase
6
+ autoload :CopySourceToTarget
7
+ autoload :PrefillTargetFromSource
8
+ end
9
+ end
@@ -3,8 +3,10 @@ module Stall
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ include Stall::CustomersHelper
7
+
6
8
  if respond_to?(:helper_method)
7
- helper_method :current_cart, :current_cart_key
9
+ helper_method :current_cart, :current_cart_key, :current_customer
8
10
  end
9
11
 
10
12
  if respond_to?(:after_action)
@@ -16,6 +18,16 @@ module Stall
16
18
  RequestStore.store[cart_key] ||= load_current_cart
17
19
  end
18
20
 
21
+ def current_customer
22
+ @current_customer ||= if stall_user_signed_in?
23
+ if (customer = current_stall_user.customer)
24
+ customer
25
+ else
26
+ current_stall_user.create_customer(email: current_stall_user.email)
27
+ end
28
+ end
29
+ end
30
+
19
31
  protected
20
32
 
21
33
  def current_cart_key
@@ -35,6 +47,7 @@ module Stall
35
47
 
36
48
  def prepare_cart(cart)
37
49
  cart.tap do |cart|
50
+ cart.customer = current_customer if current_customer
38
51
  # Keep track of potential customer locale switching to allow e-mailing
39
52
  # him in his last used locale
40
53
  cart.customer.locale = I18n.locale if cart.customer