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
@@ -1,20 +1,65 @@
1
1
  module Stall
2
2
  module Checkout
3
3
  class InformationsCheckoutStep < Stall::Checkout::Step
4
+ validations do
5
+ validate :customer_accepts_terms
6
+ validates :customer, :shipping_address, presence: true
7
+
8
+ validates :billing_address, presence: true,
9
+ if: :use_another_address_for_billing?
10
+
11
+ nested :shipping_address do
12
+ validates :civility, :first_name, :last_name, :address, :country,
13
+ :zip, :city, presence: true
14
+ end
15
+
16
+ nested :billing_address do
17
+ validates :civility, :first_name, :last_name, :address, :country,
18
+ :zip, :city, presence: true
19
+ end
20
+ end
21
+
4
22
  def prepare
5
23
  ensure_customer
6
- ensure_address(:billing)
7
- ensure_address(:shipping)
24
+ prefill_addresses_from_customer
25
+ ensure_shipment
26
+ ensure_payment
8
27
  end
9
28
 
10
29
  def process
30
+ prepare_user_attributes
11
31
  cart.assign_attributes(cart_params)
12
32
  process_addresses
13
- cart.save if valid?
33
+
34
+ return unless valid?
35
+
36
+ cart.save.tap do |valid|
37
+ assign_addresses_to_customer!
38
+ calculate_shipping_fee!
39
+ end
14
40
  end
15
41
 
16
42
  private
17
43
 
44
+ def cart_params
45
+ @cart_params ||= params.require(:cart).permit(
46
+ :use_another_address_for_billing, :terms,
47
+ :payment_method_id, :shipping_method_id,
48
+ customer_attributes: [
49
+ :email, user_attributes: [
50
+ :password, :password_confirmation
51
+ ]
52
+ ],
53
+ address_ownerships_attributes: [
54
+ :id, :shipping, :billing,
55
+ address_attributes: [
56
+ :id, :civility, :first_name, :last_name, :address,
57
+ :address_details, :country, :zip, :city, :phone
58
+ ]
59
+ ]
60
+ )
61
+ end
62
+
18
63
  def ensure_customer
19
64
  cart.build_customer unless cart.customer
20
65
  end
@@ -24,18 +69,92 @@ module Stall
24
69
  ownership.address || ownership.build_address
25
70
  end
26
71
 
27
- def process_addresses
28
- unless params[:use_another_address_for_billing]
29
- # Remove submitted billing address
30
- if (billing_ownership = cart.address_ownership_for(:billing))
31
- cart.address_ownerships.destroy(billing_ownership)
32
- end
33
-
34
- # Set shipping address as the billing one
35
- if (shipping_ownership = cart.address_ownership_for(:shipping))
36
- shipping_ownership.billing = true
37
- end
72
+ def ensure_billing_address
73
+ return ensure_address(:billing) unless cart_params[:use_another_address_for_billing] == '0'
74
+
75
+ # If the form was submitted and we merged both shipping and billing
76
+ # addresses together, we must separate them back to present them to
77
+ # the user form, avoiding to render errors in a form that the user
78
+ # didn't even see before
79
+ ownership = cart.address_ownership_for(:billing)
80
+ ownership.billing = false if ownership.shipping
81
+ ensure_address(:billing)
82
+ end
83
+
84
+ def ensure_shipment
85
+ cart.build_shipment unless cart.shipment
86
+ end
87
+
88
+ def ensure_payment
89
+ cart.build_payment unless cart.payment
90
+ end
91
+
92
+ # Remvove user attributes when no account should be created, for an
93
+ # "anonymous" order creation.
94
+ #
95
+ def prepare_user_attributes
96
+ return if params[:create_account] == '1'
97
+
98
+ if cart_params[:customer_attributes] && cart_params[:customer_attributes][:user_attributes]
99
+ cart_params[:customer_attributes].delete(:user_attributes)
38
100
  end
101
+
102
+ # Remove user from customer to avoid automatic validation of the user
103
+ # if no user should be saved with the customer
104
+ unless stall_user_signed_in? || cart.customer.try(:user).try(:persisted?) ||
105
+ !cart.customer
106
+ then
107
+ cart.customer.user = nil
108
+ end
109
+ end
110
+
111
+ # Merges shipping and billing addresses into one address when the visitor
112
+ # has chosen to use the shipping address for both.
113
+ #
114
+ def process_addresses
115
+ return if use_another_address_for_billing?
116
+
117
+ shipping_ownership = cart.address_ownership_for(:shipping)
118
+ billing_ownership = cart.address_ownership_for(:billing)
119
+
120
+ return if billing_ownership == shipping_ownership
121
+
122
+ # If the user choosed to receive his order by shipping and that he
123
+ # choosed not to fill a billing address, we remove the billing address
124
+ # hidden form that was submitted in the step, and make the shipping
125
+ # address be used as billing address too
126
+ cart.address_ownerships.destroy(billing_ownership) if billing_ownership
127
+ cart.mark_address_ownership_as_billing(shipping_ownership) if shipping_ownership
128
+ end
129
+
130
+ # Assigns the shipping fees to the cart based on the selected shipping
131
+ # method
132
+ #
133
+ def calculate_shipping_fee!
134
+ service_class = Stall.config.service_for(:shipping_fee_calculator)
135
+ service_class.new(cart).call
136
+ end
137
+
138
+ # Fetches addresses from the customer account and copy them to the
139
+ # cart to pre-fill the fields for the user
140
+ #
141
+ def prefill_addresses_from_customer
142
+ Stall::Addresses::PrefillTargetFromSource.new(cart.customer, cart).copy
143
+ end
144
+
145
+ # Copies the addresses filled in the cart to the customer account for
146
+ # next orders informations pre-filling
147
+ #
148
+ def assign_addresses_to_customer!
149
+ Stall::Addresses::CopySourceToTarget.new(cart, cart.customer).copy!
150
+ end
151
+
152
+ def use_another_address_for_billing?
153
+ @use_another_address_for_billing ||= cart_params[:use_another_address_for_billing] == '1'
154
+ end
155
+
156
+ def customer_accepts_terms
157
+ cart.errors.add(:terms, :accepted) unless cart.terms == '1'
39
158
  end
40
159
  end
41
160
  end
@@ -1,8 +1,22 @@
1
1
  module Stall
2
2
  module Checkout
3
3
  class PaymentCheckoutStep < Stall::Checkout::Step
4
+ # Determine wether the customer's payment has been validated or not.
5
+ #
6
+ # By default, the payment processing occurs in the background and, for
7
+ # some of the payment gateways, can be run asynchronously. In this case,
8
+ # the gateway should redirect here with the `:succeeded` param in the URL.
9
+ #
10
+ # If the payment processing occurs synchronously, the gateway overrides
11
+ # the #synchronous_payment_notification? method, using the cart payment
12
+ # state to determine this parameter.
13
+ #
4
14
  def process
5
- return true if params[:succeeded]
15
+ if gateway.synchronous_payment_notification?
16
+ cart.paid?
17
+ elsif params[:succeeded]
18
+ true
19
+ end
6
20
  end
7
21
 
8
22
  # When we access this step after a payment to validate the step, the cart
@@ -11,6 +25,12 @@ module Stall
11
25
  def allow_inactive_carts?
12
26
  !!params[:succeeded]
13
27
  end
28
+
29
+ private
30
+
31
+ def gateway
32
+ @gateway ||= Stall::Payments::Gateway.for(cart.payment.payment_method).new(cart)
33
+ end
14
34
  end
15
35
  end
16
36
  end
@@ -3,6 +3,8 @@ module Stall
3
3
  class StepNotFoundError < StandardError; end
4
4
 
5
5
  class Step
6
+ class_attribute :_validations
7
+
6
8
  attr_reader :cart
7
9
 
8
10
  def initialize(cart)
@@ -27,14 +29,6 @@ module Stall
27
29
  save
28
30
  end
29
31
 
30
- def cart_params
31
- @cart_params ||= if params[:cart]
32
- params.require(:cart).permit!
33
- else
34
- {}.with_indifferent_access
35
- end
36
- end
37
-
38
32
  def skip?
39
33
  false
40
34
  end
@@ -62,6 +56,16 @@ module Stall
62
56
  false
63
57
  end
64
58
 
59
+ # Run cart validations then step validations, and cart validations, returning wether they're both valid or
60
+ # not, allowing to display all involved errors to the visitor in one time
61
+ #
62
+ def valid?
63
+ cart.validate
64
+ run_step_validations!(clear: false)
65
+
66
+ cart.errors.empty?
67
+ end
68
+
65
69
  # Abstracts the simple case of assigning the submitted parameters to the
66
70
  # cart object, running the step validations and saving the cart
67
71
  def save
@@ -69,6 +73,14 @@ module Stall
69
73
  cart.save if valid?
70
74
  end
71
75
 
76
+ private
77
+
78
+ def run_step_validations!(clear: true)
79
+ if (validations = self.class.validations)
80
+ validations.new(cart, self, clear: clear).validate
81
+ end
82
+ end
83
+
72
84
  # Handles conversion from an identifier to a checkout step class, allowing
73
85
  # us to specify a list of symbols in our wizard's .step macro
74
86
  #
@@ -92,13 +104,8 @@ module Stall
92
104
  end
93
105
 
94
106
  def self.validations(&block)
95
- return @validations unless block
96
- @validations = Stall::Checkout::StepForm.build(&block)
97
- end
98
-
99
- def valid?
100
- return true unless (validations = self.class.validations)
101
- validations.new(cart, self).validate
107
+ return _validations unless block
108
+ self._validations = Stall::Checkout::StepForm.build(&block)
102
109
  end
103
110
  end
104
111
  end
@@ -5,17 +5,28 @@ module Stall
5
5
 
6
6
  class_attribute :nested_forms
7
7
 
8
- attr_reader :object, :step
8
+ attr_reader :object, :step, :clear_cart_errors_before_validation
9
9
 
10
10
  delegate :errors, to: :object
11
11
 
12
- def initialize(object, step)
12
+ def initialize(object, step, clear: true)
13
13
  @object = object
14
14
  @step = step
15
+ @clear_cart_errors_before_validation = clear
15
16
  end
16
17
 
18
+ # Runs form and nested forms validations and returns wether they all
19
+ # passed or not
20
+ #
21
+ # Only clear validation errors on the cart if needed, allowing to run
22
+ # cart validations before the step ones, passing clear: false in the
23
+ # form constructor, aggregating both validation sources' errors
24
+ #
17
25
  def validate
18
- super && validate_nested_forms
26
+ errors.clear if clear_cart_errors_before_validation
27
+ run_validations!
28
+ validate_nested_forms
29
+ !errors.any?
19
30
  end
20
31
 
21
32
  def self.nested(type, &block)
@@ -23,6 +34,9 @@ module Stall
23
34
  nested_forms[type] = build(&block)
24
35
  end
25
36
 
37
+ # Build an dynamic StepForm subclass with the given block as the body
38
+ # of the class
39
+ #
26
40
  def self.build(&block)
27
41
  Class.new(StepForm, &block)
28
42
  end
@@ -40,7 +54,7 @@ module Stall
40
54
  # Override model name instanciation to add a name, since the form classes
41
55
  # are anonymous, and ActiveModel::Name does not support unnamed classes
42
56
  def model_name
43
- @model_name ||= ActiveModel::Name.new(self, nil, object.class.name)
57
+ @model_name ||= ActiveModel::Name.new(self, nil, self.class.name)
44
58
  end
45
59
 
46
60
  private
@@ -54,12 +68,12 @@ module Stall
54
68
  def validate_nested_forms
55
69
  # If no nested forms are present in the class, just return true since
56
70
  # no validation should be tested
57
- return true unless self.class.nested_forms
71
+ return true unless nested_forms
58
72
 
59
73
  # Run all validations on all nested forms and ensure they're all valid
60
- self.class.nested_forms.map do |name, form|
61
- if object.respond_to?(name) && (model = object.send(name))
62
- Array.wrap(model).map { |m| form.new(m, step).validate }.all?
74
+ nested_forms.map do |name, form|
75
+ if object.respond_to?(name) && (resource = object.send(name))
76
+ Array.wrap(resource).map { |m| form.new(m, step).validate }.all?
63
77
  else
64
78
  # Nested validations shouldn't be run on undefined relations
65
79
  true
data/lib/stall/config.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  module Stall
2
2
  class Config
3
3
  extend Stall::Utils::ConfigDSL
4
+ # Store name used in e-mails and other interfaces duisplaying such an
5
+ # information
4
6
  param :store_name
5
7
 
6
8
  # Admin e-mail address to which order notifications will be sent
@@ -9,6 +11,9 @@ module Stall
9
11
  # E-mail address used to send e-mails to customers
10
12
  param :sender_email, -> { ENV['STALL_SENDER_EMAIL'] || 'shop.change_me_in.stall.rb@example.com' }
11
13
 
14
+ # Email regex validation. Taken from Devise
15
+ param :email_regexp, -> { (defined?(Devise) && Devise.email_regexp) || /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/ }
16
+
12
17
  # Default VAT rate
13
18
  param :vat_rate, BigDecimal.new('20.0')
14
19
 
@@ -27,7 +32,7 @@ module Stall
27
32
  param :default_currency, 'EUR'
28
33
 
29
34
  # Default app domain for building routes
30
- param :_default_app_domain
35
+ param :default_app_domain
31
36
 
32
37
  # Default checkout wizard used
33
38
  param :default_checkout_wizard, 'DefaultCheckoutWizard'
@@ -43,9 +48,14 @@ module Stall
43
48
  # Duration after which an empty cart is cleaned out by the rake task
44
49
  param :empty_carts_expires_after, 1.day
45
50
 
46
- # Duration after which an aborted is cleaned out by the rake task
51
+ # Duration after which an aborted cart is cleaned out by the rake task
47
52
  param :aborted_carts_expires_after, 14.days
48
53
 
54
+ param :default_user_model_name, 'User'
55
+ param :default_user_helper_method, :current_user
56
+
57
+ # Configure the terms of service page path
58
+ param :terms_path, 'about:blank'
49
59
 
50
60
  def shipping
51
61
  @shipping ||= Stall::Shipping::Config.new
@@ -64,7 +74,7 @@ module Stall
64
74
  end
65
75
 
66
76
  def default_app_domain
67
- _default_app_domain || ENV['APP_DOMAIN']
77
+ @default_app_domain || ENV['APP_DOMAIN']
68
78
  end
69
79
 
70
80
  # Fetch user config and add top-namespace lookup to avoid collision
@@ -85,5 +95,9 @@ module Stall
85
95
  def services=(value)
86
96
  self.services.merge!(value)
87
97
  end
98
+
99
+ def default_user_model
100
+ default_user_model_name.try(:constantize)
101
+ end
88
102
  end
89
103
  end
data/lib/stall/engine.rb CHANGED
@@ -4,6 +4,12 @@ module Stall
4
4
  Money.default_currency = Stall.config.default_currency
5
5
  end
6
6
 
7
+ initializer 'stall.add_routing_mapper_extension' do
8
+ ActiveSupport.on_load(:action_controller) do
9
+ ActionDispatch::Routing::Mapper.send(:include, Stall::RoutingMapper)
10
+ end
11
+ end
12
+
7
13
  initializer 'stall.override_actionview_number_helpers' do
8
14
  ActiveSupport.on_load(:action_view) do
9
15
  include Stall::CurrencyHelper
data/lib/stall/payable.rb CHANGED
@@ -22,5 +22,14 @@ module Stall
22
22
 
23
23
  delegate :paid?, to: :payment, allow_nil: true
24
24
  end
25
+
26
+ module ClassMethods
27
+ def find_by_payment_transaction_id(transaction_id)
28
+ joins(:payment).where(
29
+ "stall_payments.data->>'transaction_id' = ?",
30
+ transaction_id
31
+ ).first
32
+ end
33
+ end
25
34
  end
26
35
  end
@@ -1,8 +1,6 @@
1
1
  module Stall
2
2
  module Payments
3
3
  class Gateway
4
- TRANSACTION_ID_FORMAT = 'ESHOP-%{cart_id}-%{transaction_index}'
5
-
6
4
  attr_reader :cart
7
5
 
8
6
  def initialize(cart)
@@ -33,9 +31,9 @@ module Stall
33
31
  'Subclasses must implement the .response(request) class method '
34
32
  end
35
33
 
36
- def transaction_id
34
+ def transaction_id(refresh: false)
37
35
  @transaction_id ||= begin
38
- unless (id = cart.payment.transaction_id)
36
+ if refresh || !(id = cart.payment.transaction_id)
39
37
  id = next_transaction_id
40
38
  cart.payment.update_attributes(transaction_id: id)
41
39
  end
@@ -50,13 +48,21 @@ module Stall
50
48
  # Most of the gateways expect some specific return, so this is to be
51
49
  # overriden by subclasses
52
50
  def rendering_options
53
- { text: nil }
51
+ { nothing: false }
54
52
  end
55
53
 
56
54
  def payment_urls
57
55
  @payment_urls ||= Stall::Payments::UrlsConfig.new(cart)
58
56
  end
59
57
 
58
+ # Override this method and retrun true if the gateway payment
59
+ # notification should be taken into account to determine wether the
60
+ # payment has been successful or not when returning from the gateway.
61
+ #
62
+ def synchronous_payment_notification?
63
+ false
64
+ end
65
+
60
66
  private
61
67
 
62
68
  def next_transaction_id
@@ -71,10 +77,14 @@ module Stall
71
77
  end
72
78
 
73
79
  def transaction_id_for(index)
74
- TRANSACTION_ID_FORMAT
80
+ transaction_id_format
75
81
  .gsub('%{cart_id}', cart.reference)
76
82
  .gsub('%{transaction_index}', ('%05d' % index))
77
83
  end
84
+
85
+ def transaction_id_format
86
+ 'ESHOP-%{cart_id}-%{transaction_index}'
87
+ end
78
88
  end
79
89
  end
80
90
  end
@@ -27,8 +27,8 @@ module Stall
27
27
  def default_config
28
28
  ->(urls) {
29
29
  urls.payment_notification_url = notify_payment_url(gateway: gateway_identifier, host: Stall.config.default_app_domain)
30
- urls.payment_success_return_url = process_checkout_step_url(cart.identifier, host: Stall.config.default_app_domain)
31
- urls.payment_failure_return_url = process_checkout_step_url(cart.identifier, host: Stall.config.default_app_domain)
30
+ urls.payment_success_return_url = process_checkout_step_url(cart.identifier, host: Stall.config.default_app_domain, succeeded: true)
31
+ urls.payment_failure_return_url = process_checkout_step_url(cart.identifier, host: Stall.config.default_app_domain, aborted: true)
32
32
  }
33
33
  end
34
34
 
@@ -1,12 +1,10 @@
1
1
  # Routing mapper override to allow mounting the engine as non-isolated, avoiding
2
2
  # issues with routes in templates when switching from the app to the engine
3
3
  #
4
- module ActionDispatch
5
- module Routing
6
- class Mapper
7
- def mount_stall(mount_location)
8
- Stall::Routes.new(self).draw(mount_location)
9
- end
4
+ module Stall
5
+ module RoutingMapper
6
+ def mount_stall(mount_location)
7
+ Stall::Routes.new(self).draw(mount_location)
10
8
  end
11
9
  end
12
10
  end
data/lib/stall/routes.rb CHANGED
@@ -19,7 +19,10 @@ module Stall
19
19
  scope '(:cart_key)' do
20
20
  resource :step, only: [:show, :update] do
21
21
  post '/', action: :update, as: :update
22
- get '/process', action: :update, as: :process
22
+ get '/process', action: :update, as: :process
23
+ # Allow external URLs process steps, allowing some payment
24
+ # gateways to return the user through a POST request
25
+ post '/process', action: :foreign_update
23
26
  get 'change/:step', action: :change, as: :change
24
27
  end
25
28
  end
@@ -10,8 +10,8 @@ module Stall
10
10
 
11
11
  def to_line_item
12
12
  line_items.build(
13
- name: (respond_to?(:name) && name) || (respond_to?(:title) && title),
14
- unit_price: (respond_to?(:price) && price),
13
+ name: (try(:name) || try(:title)),
14
+ unit_price: try(:price),
15
15
  unit_eot_price: eot_price,
16
16
  vat_rate: vat_rate,
17
17
  )
@@ -21,16 +21,26 @@ module Stall
21
21
  (vat_rate / 100.0) + 1
22
22
  end
23
23
 
24
+ def currency
25
+ Money.default_currency
26
+ end
27
+
24
28
  private
25
29
 
26
30
  def default_eot_price
27
- price && (price / vat_ratio)
31
+ if (price = try(:price))
32
+ price / vat_ratio
33
+ end
28
34
  end
29
35
 
30
36
  def default_vat_rate
31
37
  @default_vat_rate ||= Stall.config.vat_rate
32
38
  end
33
39
 
40
+ # Create default handlers for the `#eot_price` and `#vat_rate` methods that
41
+ # don't need to be explictly defined if the whole shop has a single VAT rate
42
+ # for all products
43
+ #
34
44
  def method_missing(name, *args, &block)
35
45
  if [:eot_price, :vat_rate].include?(name)
36
46
  send(:"default_#{ name }")
data/lib/stall/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stall
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/stall.rb CHANGED
@@ -16,6 +16,7 @@ module Stall
16
16
 
17
17
  autoload :Sellable
18
18
  autoload :Addressable
19
+ autoload :Addresses
19
20
  autoload :Priceable
20
21
  autoload :Payable
21
22