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
@@ -0,0 +1,3 @@
1
+ class ShippingMethod < ActiveRecord::Base
2
+ include Stall::Models::ShippingMethod
3
+ end
@@ -0,0 +1,4 @@
1
+ module Stall
2
+ module Models
3
+ end
4
+ end
@@ -0,0 +1,45 @@
1
+ module Stall
2
+ module Models
3
+ module Address
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_addresses'
8
+
9
+ has_one :address_ownership, dependent: :destroy
10
+
11
+ enum civility: { :m => 1, :mme => 2 }
12
+
13
+ def self.civility_enum
14
+ civilities.each_with_object({}) do |(key, value), hash|
15
+ hash[I18n.t("stall.addresses.civilities.#{ key }")] = key
16
+ end
17
+ end
18
+
19
+ def civility_name
20
+ I18n.t("stall.addresses.civilities.#{ civility }")
21
+ end
22
+
23
+ def country_name
24
+ if iso_country
25
+ iso_country.translations[I18n.locale.to_s] || iso_country.name
26
+ end
27
+ end
28
+
29
+ def state_name
30
+ iso_state['name'] if iso_state
31
+ end
32
+
33
+ private
34
+
35
+ def iso_country
36
+ @iso_country ||= country && ISO3166::Country[country]
37
+ end
38
+
39
+ def iso_state
40
+ @iso_state ||= iso_country.subdivisions[state] if iso_country && state
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ module Stall
2
+ module Models
3
+ module AddressOwnership
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_address_ownerships'
8
+
9
+ belongs_to :address, inverse_of: :address_ownership
10
+ accepts_nested_attributes_for :address
11
+
12
+ belongs_to :addressable, polymorphic: true
13
+ end
14
+
15
+ [:shipping, :billing].each do |type|
16
+ define_method(:"mark_as_#{ type }") do
17
+ if (ownership = addressable.address_ownership_for(type)) && ownership != self
18
+ ownership.send(:"#{ type }=", false)
19
+ end
20
+
21
+ send(:"#{ type }=", true)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ module Stall
2
+ module Models
3
+ module Adjustment
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_adjustments'
8
+
9
+ monetize :eot_price_cents, :price_cents,
10
+ with_model_currency: :currency, allow_nil: true
11
+
12
+ belongs_to :cart
13
+
14
+ delegate :currency, to: :cart, allow_nil: true
15
+
16
+ validates :name, :price, :eot_price, :vat_rate, :cart, presence: true
17
+
18
+ before_validation :fill_eot_price_and_vat_rate
19
+
20
+ define_method(:eot_price) do
21
+ if (eot_price = read_attribute(:eot_price)) && eot_price.to_f != 0
22
+ eot_price
23
+ else
24
+ default_eot_price
25
+ end
26
+ end
27
+ end
28
+
29
+ def vat_rate
30
+ read_attribute(:vat_rate) || Stall.config.vat_rate
31
+ end
32
+
33
+ def vat
34
+ price - eot_price
35
+ end
36
+
37
+ private
38
+
39
+ def fill_eot_price_and_vat_rate
40
+ self.vat_rate = vat_rate
41
+ self.eot_price = eot_price
42
+ end
43
+
44
+ def default_eot_price
45
+ (price / (1.0 + (vat_rate / 100.0))) if price && vat_rate
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ module Stall
2
+ module Models
3
+ module Cart
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Stall::Addressable
8
+ include Stall::Payable
9
+
10
+ has_one :shipment, dependent: :destroy, inverse_of: :cart
11
+ accepts_nested_attributes_for :shipment
12
+
13
+ has_many :adjustments, dependent: :destroy, inverse_of: :cart
14
+ accepts_nested_attributes_for :adjustments
15
+ end
16
+
17
+ def total_weight
18
+ line_items.reduce(0) do |total, line_item|
19
+ total + (line_item.weight || Stall.config.default_product_weight)
20
+ end
21
+ end
22
+
23
+ def active?
24
+ !paid?
25
+ end
26
+
27
+ private
28
+
29
+ def items
30
+ items = line_items.to_a
31
+ items << shipment if shipment
32
+ items += adjustments.to_a
33
+ items
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ module Stall
2
+ module Models
3
+ module Customer
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_customers'
8
+
9
+ include Stall::Addressable
10
+
11
+ belongs_to :user, polymorphic: true, inverse_of: :customer
12
+ accepts_nested_attributes_for :user
13
+
14
+ has_many :product_lists, dependent: :destroy
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ module Stall
2
+ module Models
3
+ module LineItem
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_line_items'
8
+
9
+ include Stall::Priceable
10
+
11
+ store_accessor :data, :weight
12
+
13
+ monetize :unit_eot_price_cents, :unit_price_cents,
14
+ :eot_price_cents, :price_cents,
15
+ with_model_currency: :currency, allow_nil: true
16
+
17
+ belongs_to :sellable, polymorphic: true
18
+ belongs_to :product_list
19
+
20
+ validates :name, :unit_price, :unit_eot_price, :vat_rate, :price, :quantity,
21
+ :eot_price, :sellable, presence: true
22
+
23
+ validates :unit_price, :unit_eot_price, :vat_rate, :price, :eot_price,
24
+ numericality: true
25
+
26
+ validates :quantity, numericality: { greater_than: 0 }
27
+
28
+ validate :stock_availability
29
+
30
+ before_validation :refresh_total_prices
31
+
32
+ scope :ordered, -> { order(created_at: :asc) }
33
+ end
34
+
35
+ def like?(other)
36
+ [:sellable_id, :sellable_type].all? do |property|
37
+ public_send(property) == other.public_send(property)
38
+ end
39
+ end
40
+
41
+ def currency
42
+ product_list.try(:currency) || Money.default_currency
43
+ end
44
+
45
+ private
46
+
47
+ # TODO : Stocks availibility handling
48
+ def stock_availability
49
+ end
50
+
51
+ def refresh_total_prices
52
+ self.eot_price = unit_eot_price * quantity if unit_eot_price && quantity
53
+ self.price = unit_price * quantity if unit_price && quantity
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,33 @@
1
+ module Stall
2
+ module Models
3
+ module Payment
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_payments'
8
+
9
+ store_accessor :data, :transaction_id
10
+
11
+ belongs_to :payment_method
12
+ belongs_to :cart
13
+
14
+ validates :cart, :payment_method, presence: true
15
+
16
+ scope :pending, -> { where(paid_at: nil) }
17
+ scope :paid, -> { where.not(paid_at: nil) }
18
+ end
19
+
20
+ def pay!
21
+ update_attributes!(paid_at: Time.now)
22
+ end
23
+
24
+ def paid?
25
+ paid_at != nil
26
+ end
27
+
28
+ def state
29
+ paid? ? :paid : :pending
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ module Stall
2
+ module Models
3
+ module PaymentMethod
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_payment_methods'
8
+
9
+ has_many :payments, dependent: :nullify
10
+
11
+ validates :name, :identifier, presence: true
12
+
13
+ scope :ordered, -> { order('stall_payment_methods.name ASC') }
14
+ scope :active, -> { where(active: true) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,147 @@
1
+ module Stall
2
+ module Models
3
+ module ProductList
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ self.table_name = 'stall_product_lists'
8
+
9
+ store_accessor :data, :reference
10
+
11
+ has_secure_token
12
+
13
+ has_many :line_items, -> { ordered }, dependent: :destroy
14
+ accepts_nested_attributes_for :line_items, allow_destroy: true
15
+
16
+ belongs_to :customer
17
+ accepts_nested_attributes_for :customer
18
+
19
+ validates :type, presence: true
20
+
21
+ after_initialize :ensure_currency
22
+ after_initialize :ensure_state
23
+
24
+ after_save :ensure_reference, on: :create
25
+
26
+ scope :empty, -> {
27
+ joins(
28
+ 'LEFT JOIN stall_line_items ' \
29
+ 'ON stall_product_lists.id = stall_line_items.product_list_id'
30
+ ).where(stall_line_items: { id: nil })
31
+ }
32
+
33
+ scope :filled, -> { joins(:line_items) }
34
+
35
+ scope :older_than, ->(date) {
36
+ where('stall_product_lists.updated_at < ?', date)
37
+ }
38
+ end
39
+
40
+ def state
41
+ read_attribute(:state).try(:to_sym)
42
+ end
43
+
44
+ def reset_state!
45
+ update_column(:state, wizard.steps.first)
46
+ end
47
+
48
+ def to_param
49
+ persisted? ? token : 'empty-cart'
50
+ end
51
+
52
+ def subtotal
53
+ price = line_items.map(&:price).sum
54
+ price = Money.new(price, currency) unless Money === price
55
+ price
56
+ end
57
+
58
+ def eot_subtotal
59
+ line_items.map(&:eot_price).sum
60
+ end
61
+
62
+ def total_price
63
+ price = items.map(&:price).sum
64
+ price = Money.new(price, currency) unless Money === price
65
+ price
66
+ end
67
+
68
+ def total_eot_price
69
+ items.map(&:eot_price).sum
70
+ end
71
+
72
+ def total_vat
73
+ items.map(&:vat).sum
74
+ end
75
+
76
+ def total_quantity
77
+ line_items.map(&:quantity).sum
78
+ end
79
+
80
+ def wizard
81
+ @wizard ||= self.class.wizard
82
+ end
83
+
84
+ def checkoutable?
85
+ line_items.length > 0
86
+ end
87
+
88
+ def active?
89
+ true
90
+ end
91
+
92
+ private
93
+
94
+ def ensure_currency
95
+ self.currency ||= Money.default_currency
96
+ end
97
+
98
+ def ensure_state
99
+ self.state ||= (wizard.try(:steps).try(:first) || 'pending')
100
+ end
101
+
102
+ def items
103
+ line_items.to_a
104
+ end
105
+
106
+ def ensure_reference
107
+ unless reference.present?
108
+ reference = [Time.now.strftime('%Y%m%d'), ('%05d' % id)].join('-')
109
+ self.reference = reference
110
+ save(validate: false)
111
+ end
112
+ end
113
+
114
+ module ClassMethods
115
+ def find_by_reference(reference)
116
+ where("data->>'reference' = ?", reference).first
117
+ end
118
+
119
+ # The .aborted and .finalized scopes cannot be declared as actual rails
120
+ # scopes since subclasses that override the .wizard method wouldn't
121
+ # be taken into account, scopes being executed in the context of the
122
+ # declaring class
123
+ #
124
+ def aborted(options = {})
125
+ where.not(state: wizard.steps.last)
126
+ .older_than(options.fetch(:before, 1.day.ago))
127
+ end
128
+
129
+ def finalized
130
+ where(state: wizard.steps.last)
131
+ end
132
+
133
+ def wizard
134
+ wizard_name = Stall.config.default_checkout_wizard
135
+
136
+ if (wizard = Stall::Utils.try_load_constant(wizard_name))
137
+ wizard
138
+ else
139
+ raise Stall::Checkout::WizardNotFoundError.new,
140
+ "The checkout wizard #{ wizard_name } was not found. You must generate it " +
141
+ "with `rails g stall:wizard #{ wizard_name.underscore.gsub('_checkout_wizard', '') }`"
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end