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