stall 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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