stall 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +45 -0
- data/app/assets/javascripts/stall.coffee +2 -0
- data/app/assets/javascripts/stall/cart-form.coffee +23 -0
- data/app/controllers/stall/application_controller.rb +10 -2
- data/app/controllers/stall/carts_controller.rb +31 -10
- data/app/controllers/stall/checkout/steps_controller.rb +36 -4
- data/app/controllers/stall/checkouts_controller.rb +12 -3
- data/app/controllers/stall/line_items_controller.rb +3 -3
- data/app/controllers/stall/payments_controller.rb +5 -3
- data/app/helpers/stall/add_to_cart_helper.rb +1 -1
- data/app/helpers/stall/checkout_helper.rb +22 -2
- data/app/helpers/stall/payments_helper.rb +8 -0
- data/app/mailers/stall/admin_mailer.rb +11 -0
- data/app/mailers/stall/base_mailer.rb +17 -0
- data/app/mailers/stall/customer_mailer.rb +13 -0
- data/app/models/address.rb +3 -0
- data/app/models/address_ownership.rb +3 -0
- data/app/models/adjustment.rb +3 -0
- data/app/models/cart.rb +3 -0
- data/app/models/customer.rb +3 -0
- data/app/models/line_item.rb +3 -0
- data/app/models/payment.rb +3 -0
- data/app/models/payment_method.rb +3 -0
- data/app/models/product_list.rb +3 -0
- data/app/models/shipment.rb +3 -0
- data/app/models/shipping_method.rb +3 -0
- data/app/models/stall/models.rb +4 -0
- data/app/models/stall/models/address.rb +45 -0
- data/app/models/stall/models/address_ownership.rb +26 -0
- data/app/models/stall/models/adjustment.rb +49 -0
- data/app/models/stall/models/cart.rb +37 -0
- data/app/models/stall/models/customer.rb +18 -0
- data/app/models/stall/models/line_item.rb +57 -0
- data/app/models/stall/models/payment.rb +33 -0
- data/app/models/stall/models/payment_method.rb +18 -0
- data/app/models/stall/models/product_list.rb +147 -0
- data/app/models/stall/models/shipment.rb +29 -0
- data/app/models/stall/models/shipping_method.rb +16 -0
- data/app/services/stall/add_to_cart_service.rb +11 -1
- data/app/services/stall/cart_update_service.rb +26 -0
- data/app/services/stall/payment_notification_service.rb +23 -9
- data/app/services/stall/shipping_fee_calculator_service.rb +13 -2
- data/app/views/checkout/steps/_informations.html.haml +3 -3
- data/app/views/checkout/steps/_payment.html.haml +25 -4
- data/app/views/checkout/steps/_payment_method.html.haml +1 -3
- data/app/views/checkout/steps/_payment_return.html.haml +6 -0
- data/app/views/checkout/steps/_shipping_method.html.haml +1 -1
- data/app/views/stall/admin_mailer/order_paid_email.html.haml +4 -0
- data/app/views/stall/carts/show.html.haml +36 -8
- data/app/views/stall/customer_mailer/order_paid_email.html.haml +8 -0
- data/app/views/stall/line_items/_added.html.haml +1 -1
- data/app/views/stall/shared/mailers/_address.html.haml +18 -0
- data/app/views/stall/shared/mailers/_cart.html.haml +114 -0
- data/config/locales/stall.fr.yml +90 -8
- data/db/migrate/20160304134849_change_all_json_columns_to_jsonb.rb +18 -0
- data/db/migrate/20160307142924_add_state_to_stall_addresses.rb +5 -0
- data/db/migrate/20160308142713_create_adjustments.rb +16 -0
- data/db/migrate/20160309165136_add_identifier_to_stall_product_lists.rb +5 -0
- data/db/migrate/20160316114649_add_data_to_stall_shipments.rb +5 -0
- data/db/migrate/20160317141632_add_state_to_stall_shipments.rb +5 -0
- data/db/migrate/20160629102943_add_active_to_stall_shipping_methods.rb +11 -0
- data/db/migrate/20160629104617_add_active_to_stall_payment_methods.rb +11 -0
- data/db/migrate/20160705110151_add_locale_to_stall_customers.rb +5 -0
- data/lib/generators/stall/checkout/step/templates/step.html.haml.erb +1 -1
- data/lib/generators/stall/checkout/step/templates/step.rb.erb +10 -2
- data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +1 -1
- data/lib/generators/stall/install/templates/initializer.rb +85 -1
- data/lib/generators/stall/model/model_generator.rb +27 -0
- data/lib/generators/stall/service/service_generator.rb +39 -0
- data/lib/generators/stall/service/templates/service.rb.erb +16 -0
- data/lib/generators/stall/shipping/calculator/calculator_generator.rb +17 -0
- data/lib/generators/stall/shipping/calculator/templates/calculator.rb.erb +29 -0
- data/lib/stall.rb +4 -0
- data/lib/stall/addressable.rb +28 -2
- data/lib/stall/cart_helper.rb +84 -0
- data/lib/stall/carts_cleaner.rb +46 -0
- data/lib/stall/checkout.rb +2 -0
- data/lib/stall/checkout/informations_checkout_step.rb +3 -5
- data/lib/stall/checkout/payment_checkout_step.rb +3 -0
- data/lib/stall/checkout/payment_return_checkout_step.rb +11 -0
- data/lib/stall/checkout/shipping_method_checkout_step.rb +2 -1
- data/lib/stall/checkout/step.rb +47 -11
- data/lib/stall/checkout/step_form.rb +71 -0
- data/lib/stall/checkout/wizard.rb +15 -5
- data/lib/stall/config.rb +51 -0
- data/lib/stall/engine.rb +24 -2
- data/lib/stall/payable.rb +26 -0
- data/lib/stall/payments.rb +7 -3
- data/lib/stall/payments/config.rb +37 -0
- data/lib/stall/payments/fake_gateway_payment_notification.rb +34 -0
- data/lib/stall/payments/gateway.rb +19 -12
- data/lib/stall/payments/urls_config.rb +40 -0
- data/lib/stall/priceable.rb +7 -0
- data/lib/stall/rails/currency_helper.rb +1 -1
- data/lib/stall/routes.rb +17 -4
- data/lib/stall/sellable.rb +1 -2
- data/lib/stall/shipping.rb +3 -1
- data/lib/stall/shipping/calculator.rb +36 -3
- data/lib/stall/shipping/config.rb +17 -3
- data/lib/stall/shipping/country_weight_table_calculator.rb +1 -1
- data/lib/stall/shipping/free_shipping_calculator.rb +1 -1
- data/lib/stall/utils.rb +2 -2
- data/lib/stall/utils/config_dsl.rb +5 -1
- data/lib/stall/version.rb +1 -1
- data/lib/tasks/stall_tasks.rake +11 -0
- metadata +73 -15
- data/app/assets/javascripts/stall/application.js +0 -13
- data/app/assets/javascripts/stall/carts.js +0 -2
- data/app/helpers/stall/cart_helper.rb +0 -28
- data/app/models/stall/address.rb +0 -5
- data/app/models/stall/address_ownership.rb +0 -8
- data/app/models/stall/cart.rb +0 -31
- data/app/models/stall/customer.rb +0 -8
- data/app/models/stall/line_item.rb +0 -49
- data/app/models/stall/payment.rb +0 -14
- data/app/models/stall/payment_method.rb +0 -7
- data/app/models/stall/product_list.rb +0 -68
- data/app/models/stall/shipment.rb +0 -15
- data/app/models/stall/shipping_method.rb +0 -5
@@ -19,15 +19,16 @@ module Stall
|
|
19
19
|
@cart = cart
|
20
20
|
end
|
21
21
|
|
22
|
-
def initialize_current_step(
|
23
|
-
step = current_step.new(cart
|
24
|
-
|
25
|
-
#
|
22
|
+
def initialize_current_step(&block)
|
23
|
+
step = current_step.new(cart)
|
24
|
+
|
25
|
+
# This block allows us to let inject controller-bound dependencies
|
26
|
+
# into the step just after it's initialized
|
26
27
|
block.call(step) if block
|
27
28
|
|
28
29
|
if step.skip?
|
29
30
|
validate_current_step!
|
30
|
-
initialize_current_step(
|
31
|
+
initialize_current_step(&block)
|
31
32
|
else
|
32
33
|
step
|
33
34
|
end
|
@@ -56,6 +57,10 @@ module Stall
|
|
56
57
|
step_index && step_index >= steps_count - 1
|
57
58
|
end
|
58
59
|
|
60
|
+
def step_complete?(step_identifier)
|
61
|
+
steps.index(cart.state) > steps.index(step_identifier.to_sym)
|
62
|
+
end
|
63
|
+
|
59
64
|
def steps_count
|
60
65
|
@steps_count ||= steps.length
|
61
66
|
end
|
@@ -65,6 +70,11 @@ module Stall
|
|
65
70
|
cart.save!
|
66
71
|
end
|
67
72
|
|
73
|
+
def move_to_step!(target_step)
|
74
|
+
cart.state = target_step
|
75
|
+
cart.save!
|
76
|
+
end
|
77
|
+
|
68
78
|
def self.route_key
|
69
79
|
name.gsub(/CheckoutWizard/, '').underscore.gsub('_', ' ').parameterize
|
70
80
|
end
|
data/lib/stall/config.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
module Stall
|
2
2
|
class Config
|
3
3
|
extend Stall::Utils::ConfigDSL
|
4
|
+
param :store_name
|
5
|
+
|
6
|
+
# Admin e-mail address to which order notifications will be sent
|
7
|
+
param :admin_email, -> { ENV['STALL_ADMIN_EMAIL'] || 'admin.change_me_in.stall.rb@example.com' }
|
8
|
+
|
9
|
+
# E-mail address used to send e-mails to customers
|
10
|
+
param :sender_email, -> { ENV['STALL_SENDER_EMAIL'] || 'shop.change_me_in.stall.rb@example.com' }
|
4
11
|
|
5
12
|
# Default VAT rate
|
6
13
|
param :vat_rate, BigDecimal.new('20.0')
|
@@ -11,9 +18,17 @@ module Stall
|
|
11
18
|
# Engine's ApplicationController parent
|
12
19
|
param :application_controller_ancestor, '::ApplicationController'
|
13
20
|
|
21
|
+
param :mailers_parent_class, 'ActionMailer::Base'
|
22
|
+
|
23
|
+
# Default layout used for the checkout
|
24
|
+
param :default_layout
|
25
|
+
|
14
26
|
# Default currency for money objects
|
15
27
|
param :default_currency, 'EUR'
|
16
28
|
|
29
|
+
# Default app domain for building routes
|
30
|
+
param :_default_app_domain
|
31
|
+
|
17
32
|
# Default checkout wizard used
|
18
33
|
param :default_checkout_wizard, 'DefaultCheckoutWizard'
|
19
34
|
|
@@ -23,10 +38,23 @@ module Stall
|
|
23
38
|
# Default step initialization hook
|
24
39
|
param :_steps_initialization_callback
|
25
40
|
|
41
|
+
param :services, {}
|
42
|
+
|
43
|
+
# Duration after which an empty cart is cleaned out by the rake task
|
44
|
+
param :empty_carts_expires_after, 1.day
|
45
|
+
|
46
|
+
# Duration after which an aborted is cleaned out by the rake task
|
47
|
+
param :aborted_carts_expires_after, 14.days
|
48
|
+
|
49
|
+
|
26
50
|
def shipping
|
27
51
|
@shipping ||= Stall::Shipping::Config.new
|
28
52
|
end
|
29
53
|
|
54
|
+
def payment
|
55
|
+
@payment ||= Stall::Payments::Config.new
|
56
|
+
end
|
57
|
+
|
30
58
|
def steps_initialization(value = nil, &block)
|
31
59
|
if (value ||= block)
|
32
60
|
@_steps_initialization_callback = value
|
@@ -34,5 +62,28 @@ module Stall
|
|
34
62
|
@_steps_initialization_callback
|
35
63
|
end
|
36
64
|
end
|
65
|
+
|
66
|
+
def default_app_domain
|
67
|
+
_default_app_domain || ENV['APP_DOMAIN']
|
68
|
+
end
|
69
|
+
|
70
|
+
# Fetch user config and add top-namespace lookup to avoid collision
|
71
|
+
# with Stall module services
|
72
|
+
#
|
73
|
+
# Default allows looking up Stall namespace automatically, when no
|
74
|
+
# config has been given
|
75
|
+
def service_for(identifier)
|
76
|
+
class_name = if (service_name = services[identifier])
|
77
|
+
"::#{ services[identifier].gsub(/^::/, '') }"
|
78
|
+
else
|
79
|
+
"Stall::#{ identifier.to_s.camelize }Service"
|
80
|
+
end
|
81
|
+
|
82
|
+
class_name.constantize
|
83
|
+
end
|
84
|
+
|
85
|
+
def services=(value)
|
86
|
+
self.services.merge!(value)
|
87
|
+
end
|
37
88
|
end
|
38
89
|
end
|
data/lib/stall/engine.rb
CHANGED
@@ -1,15 +1,37 @@
|
|
1
1
|
module Stall
|
2
2
|
class Engine < ::Rails::Engine
|
3
|
-
initializer '
|
3
|
+
initializer 'stall.set_money_gem_default_currency' do
|
4
4
|
Money.default_currency = Stall.config.default_currency
|
5
5
|
end
|
6
6
|
|
7
|
-
initializer '
|
7
|
+
initializer 'stall.override_actionview_number_helpers' do
|
8
8
|
ActiveSupport.on_load(:action_view) do
|
9
9
|
include Stall::CurrencyHelper
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
initializer 'stall.include_cart_helper' do
|
14
|
+
ActiveSupport.on_load(:action_controller) do
|
15
|
+
include Stall::CartHelper
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer 'stall.ensure_shipping_method_for_all_calculators' do
|
20
|
+
Stall::Shipping.calculators.each_key do |name|
|
21
|
+
ShippingMethod.where(identifier: name).first_or_create do |method|
|
22
|
+
method.name = name.to_s.humanize
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
initializer 'stall.ensure_payment_method_for_all_gateways' do
|
28
|
+
Stall::Payments.gateways.each_key do |name|
|
29
|
+
PaymentMethod.where(identifier: name).first_or_create do |method|
|
30
|
+
method.name = name.to_s.humanize
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
13
35
|
# Development : Configure rails generators to only generate the target
|
14
36
|
# files and not try to generate useless complementary files
|
15
37
|
#
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Stall
|
2
|
+
module Payable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_one :payment, dependent: :destroy,
|
7
|
+
inverse_of: :cart,
|
8
|
+
foreign_key: :cart_id
|
9
|
+
accepts_nested_attributes_for :payment
|
10
|
+
|
11
|
+
scope :paid, -> {
|
12
|
+
joins(:payment).where.not(stall_payments: { paid_at: nil })
|
13
|
+
}
|
14
|
+
|
15
|
+
scope :finalized, -> { paid }
|
16
|
+
|
17
|
+
scope :aborted, ->(options = {}) {
|
18
|
+
joins('LEFT JOIN stall_payments ON stall_payments.cart_id = stall_product_lists.id')
|
19
|
+
.where(stall_payments: { paid_at: nil })
|
20
|
+
.older_than(options.fetch(:before, 1.day.ago))
|
21
|
+
}
|
22
|
+
|
23
|
+
delegate :paid?, to: :payment, allow_nil: true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/stall/payments.rb
CHANGED
@@ -3,11 +3,15 @@ module Stall
|
|
3
3
|
extend ActiveSupport::Autoload
|
4
4
|
|
5
5
|
autoload :Gateway
|
6
|
-
|
7
|
-
|
6
|
+
autoload :Config
|
7
|
+
autoload :UrlsConfig
|
8
|
+
autoload :FakeGatewayPaymentNotification
|
8
9
|
|
9
10
|
mattr_reader :gateways
|
10
|
-
@@gateways = {}
|
11
|
+
@@gateways = {}.with_indifferent_access
|
12
|
+
|
13
|
+
mattr_reader :payment_urls
|
14
|
+
@@payment_urls = {}.with_indifferent_access
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Stall
|
2
|
+
module Payments
|
3
|
+
class Config
|
4
|
+
extend Stall::Utils::ConfigDSL
|
5
|
+
|
6
|
+
# This mehtod allows for registering an in-app gateway that can be
|
7
|
+
# auto loaded without having to explicitly require it
|
8
|
+
#
|
9
|
+
# Use a string representing the gateway name if the target class is
|
10
|
+
# auto-loaded by Rails
|
11
|
+
#
|
12
|
+
def register_gateway(name, gateway)
|
13
|
+
Stall::Payments.gateways[name] = gateway
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure
|
17
|
+
yield self
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure_urls(&block)
|
21
|
+
Stall::Payments::UrlsConfig.config_block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(name, *args)
|
25
|
+
if (gateway = Stall::Payments::Gateway.for(name))
|
26
|
+
if block_given?
|
27
|
+
yield gateway
|
28
|
+
else
|
29
|
+
gateway
|
30
|
+
end
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Stall
|
2
|
+
module Payments
|
3
|
+
class FakeGatewayPaymentNotification
|
4
|
+
attr_reader :cart
|
5
|
+
|
6
|
+
def initialize(cart)
|
7
|
+
@cart = cart
|
8
|
+
end
|
9
|
+
|
10
|
+
def params
|
11
|
+
raise 'FakeGatewayPaymentNotification subclasses must define the ' \
|
12
|
+
'#params method to return a valid notification params hash.'
|
13
|
+
end
|
14
|
+
|
15
|
+
def raw_post
|
16
|
+
params.to_query
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def transaction_id
|
22
|
+
['FAKE', cart.reference, 0].join('-')
|
23
|
+
end
|
24
|
+
|
25
|
+
def gateway
|
26
|
+
@gateway ||= gateway_class.new(cart)
|
27
|
+
end
|
28
|
+
|
29
|
+
def gateway_class
|
30
|
+
@gateway_class ||= Stall::Payments::Gateway.for(cart.payment.payment_method)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -10,24 +10,27 @@ module Stall
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.register(name)
|
13
|
-
Stall
|
13
|
+
Stall.config.payment.register_gateway(name, self)
|
14
14
|
end
|
15
15
|
|
16
|
-
def self.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def self.for(payment_method)
|
17
|
+
identifier = case payment_method
|
18
|
+
when String, Symbol then payment_method.to_s
|
19
|
+
else payment_method.identifier
|
20
|
+
end
|
21
|
+
|
22
|
+
gateway = Stall::Payments.gateways[identifier]
|
23
|
+
String === gateway ? gateway.constantize : gateway
|
21
24
|
end
|
22
25
|
|
23
|
-
def self.
|
24
|
-
|
26
|
+
def self.request(cart)
|
27
|
+
raise NoMethodError,
|
28
|
+
'Subclasses must implement the .request(cart) class method '
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
31
|
+
def self.response(_request)
|
28
32
|
raise NoMethodError,
|
29
|
-
'Subclasses must implement the
|
30
|
-
'method to handle payment verifications and cart payment validation'
|
33
|
+
'Subclasses must implement the .response(request) class method '
|
31
34
|
end
|
32
35
|
|
33
36
|
def transaction_id
|
@@ -50,10 +53,14 @@ module Stall
|
|
50
53
|
{ text: nil }
|
51
54
|
end
|
52
55
|
|
56
|
+
def payment_urls
|
57
|
+
@payment_urls ||= Stall::Payments::UrlsConfig.new(cart)
|
58
|
+
end
|
59
|
+
|
53
60
|
private
|
54
61
|
|
55
62
|
def next_transaction_id
|
56
|
-
if (last_transaction =
|
63
|
+
if (last_transaction = Payment.order("data->>'transaction_id' DESC").select(:data).first)
|
57
64
|
if (id = last_transaction.transaction_id)
|
58
65
|
index = id.split('-').pop.to_i + 1
|
59
66
|
return transaction_id_for(index)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Stall
|
2
|
+
module Payments
|
3
|
+
class UrlsConfig
|
4
|
+
include Rails.application.routes.url_helpers
|
5
|
+
|
6
|
+
class_attribute :config_block
|
7
|
+
|
8
|
+
attr_reader :cart
|
9
|
+
|
10
|
+
attr_accessor :payment_notification_url,
|
11
|
+
:payment_success_return_url,
|
12
|
+
:payment_failure_return_url
|
13
|
+
|
14
|
+
def initialize(cart)
|
15
|
+
@cart = cart
|
16
|
+
|
17
|
+
# Parse URLs
|
18
|
+
instance_exec(self, &config_block)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def config_block
|
24
|
+
self.class.config_block || default_config
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_config
|
28
|
+
->(urls) {
|
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)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def gateway_identifier
|
36
|
+
@gateway_identifier ||= cart.payment.payment_method.identifier
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/stall/routes.rb
CHANGED
@@ -13,13 +13,26 @@ module Stall
|
|
13
13
|
resources :line_items
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
get 'checkout/:cart_key' => 'checkouts#show', as: :checkout
|
17
17
|
|
18
|
-
scope '
|
19
|
-
|
18
|
+
scope 'checkout', module: 'checkout', as: :checkout do
|
19
|
+
scope '(:cart_key)' do
|
20
|
+
resource :step, only: [:show, :update] do
|
21
|
+
post '/', action: :update, as: :update
|
22
|
+
get '/process', action: :update, as: :process
|
23
|
+
get 'change/:step', action: :change, as: :change
|
24
|
+
end
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
|
-
|
28
|
+
|
29
|
+
scope '/:gateway' do
|
30
|
+
resource :payment, only: [] do
|
31
|
+
member do
|
32
|
+
match 'notify', action: 'notify', via: [:get, :post]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
23
36
|
end
|
24
37
|
end
|
25
38
|
end
|
data/lib/stall/sellable.rb
CHANGED