stall 0.1.0 → 0.1.1

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -0
  3. data/app/controllers/stall/checkout/steps_controller.rb +6 -1
  4. data/app/controllers/stall/payments_controller.rb +9 -0
  5. data/app/helpers/stall/cart_helper.rb +2 -2
  6. data/app/models/stall/address_ownership.rb +0 -7
  7. data/app/models/stall/cart.rb +17 -0
  8. data/app/models/stall/customer.rb +3 -0
  9. data/app/models/stall/line_item.rb +4 -0
  10. data/app/models/stall/payment.rb +14 -0
  11. data/app/models/stall/payment_method.rb +7 -0
  12. data/app/models/stall/product_list.rb +12 -0
  13. data/app/models/stall/shipment.rb +1 -1
  14. data/app/services/stall/payment_notification_service.rb +31 -0
  15. data/app/views/checkout/steps/_payment.html.haml +28 -0
  16. data/app/views/checkout/steps/_payment_method.html.haml +10 -0
  17. data/app/views/stall/carts/show.html.haml +12 -0
  18. data/app/views/stall/line_items/_form.html.haml +1 -1
  19. data/config/locales/stall.fr.yml +9 -0
  20. data/db/migrate/20160118121116_create_stall_product_lists.rb +1 -0
  21. data/db/migrate/20160125100733_create_stall_payment_methods.rb +10 -0
  22. data/db/migrate/20160125100734_create_stall_payments.rb +15 -0
  23. data/db/migrate/20160127113619_add_user_to_customer.rb +5 -0
  24. data/lib/generators/stall/checkout/step/step_generator.rb +9 -1
  25. data/lib/generators/stall/checkout/step/templates/step.html.haml.erb +7 -0
  26. data/lib/generators/stall/checkout/step/templates/step.rb.erb +17 -0
  27. data/lib/generators/stall/checkout/wizard/wizard_generator.rb +1 -1
  28. data/lib/generators/stall/install/install_generator.rb +1 -1
  29. data/lib/generators/stall/install/templates/initializer.rb +10 -0
  30. data/lib/stall.rb +1 -0
  31. data/lib/stall/checkout/informations_checkout_step.rb +7 -4
  32. data/lib/stall/checkout/payment_method_checkout_step.rb +3 -0
  33. data/lib/stall/checkout/step.rb +11 -1
  34. data/lib/stall/checkout/wizard.rb +15 -3
  35. data/lib/stall/config.rb +11 -0
  36. data/lib/stall/engine.rb +0 -6
  37. data/lib/stall/payments.rb +13 -0
  38. data/lib/stall/payments/gateway.rb +73 -0
  39. data/lib/stall/routes.rb +10 -10
  40. data/lib/stall/sellable.rb +38 -3
  41. data/lib/stall/shipping/calculator.rb +1 -1
  42. data/lib/stall/utils.rb +1 -1
  43. data/lib/stall/version.rb +1 -1
  44. metadata +27 -5
  45. data/app/models/stall.rb +0 -5
  46. data/lib/stall/sellable/mixin.rb +0 -17
  47. data/lib/stall/sellable/model.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9caa6a04c98d0ebd94ac128e27de7e7ea7797a55
4
- data.tar.gz: 024289eded82201eaf2a5c0dcc3c1cde5f071011
3
+ metadata.gz: 8be8383152aa8e6949b5ff132d32521541d15625
4
+ data.tar.gz: b62bad7b2c986d09c579bee22d959c4984dda358
5
5
  SHA512:
6
- metadata.gz: 76d45836e3b79f5ab7bde8809ff4ed35779d1bfb5dc28aeb99985b8b6b31ad9f1f3aa82b0870b32a770c293322ede4ccdad39fcfaa24d1ebce474f9cc541b9b1
7
- data.tar.gz: ea9f53aca3a34913111afc301c48616912fc9ff39831fe2679fa0dcfb77123316236fbafdc87a980cd9d4adb5a5164680ff8c8f3e94314f78f9d80fcb4f1e9c0
6
+ metadata.gz: 3931aee7e76b2e07d36b8f9f62e2e7fa89688195da6fd3fea8c5d1f0bd57f7b3e92343cea51ea4e734700dc0f81ba160c7117ed1fa1ce6928802d917cf7b26f6
7
+ data.tar.gz: dd6a569d77cdd85bad7832ffe7aa9dbc21e6fd6450d4ec23e8399063c8c4c688147566cfec61faa5e19aa8977864d3e0fe22c858fb22a3518bb3e6689da7402c
data/README.md CHANGED
@@ -1,6 +1,69 @@
1
1
  # Stall
2
2
 
3
+ [![Build Status](https://api.travis-ci.org/rails-stall/stall.svg?branch=master)](http://travis-ci.org/rails-stall/stall)
4
+ [![Code Climate](https://codeclimate.com/github/rails-stall/stall.svg)](https://codeclimate.com/github/rails-stall/stall)
3
5
 
6
+ Stall is a flexible e-commerce framework for Rails with some specific concerns
7
+ in mind :
8
+
9
+ - Product models and categorization handling is flexible and done in the app
10
+ - The checkout process is easily configurable, overridable but has a standard working default
11
+ - The whole system is modular and extensible
12
+ - The core should stay small and vendor-specific code (payment gateways, shipping carriers) is done in separate gems
13
+ - Authentication and admin is better handled by existing gems
14
+
15
+ **Note :** This gem is under active development, and is a complete rewrite of
16
+ the [glysellin](https://github.com/glysellin/glysellin) gem, but with a more
17
+ flexible structure, and with a complete test coverage
18
+
19
+ ## Installation
20
+
21
+ Add to your Gemfile and `bundle install` :
22
+
23
+ ```ruby
24
+ gem 'stall'
25
+ ```
26
+
27
+ Then run the install generator :
28
+
29
+ ```bash
30
+ rails generate stall:install
31
+ ```
32
+
33
+ This will generate :
34
+
35
+ - The stall models migrations
36
+ - A default initializer file to configure the system
37
+ - A default checkout wizard class making it easy to configure your checkout process
38
+
39
+ ## Usage
40
+
41
+ ### Making a model sellable
42
+
43
+ Stall allows you to make any model sellable by including the `Stall::Sellable`
44
+ mixin into your model :
45
+
46
+ ```ruby
47
+ class Book < ActiveRecord::Base
48
+ include Stall::Sellable
49
+ end
50
+ ```
51
+
52
+ You can now add the "Add to cart" button to your templates :
53
+
54
+ ```ruby
55
+ = add_to_cart_form_for(@book)
56
+ ```
57
+
58
+ For more informations see the Wiki page :
59
+ [Allowing customers to add products to cart](https://github.com/rails-stall/stall/wiki/Allowing-customers-to-add-products-to-cart)
60
+
61
+ ### Configuring the checkout flow
62
+
63
+ The checkout process is completely flexible and can be overriden easily.
64
+
65
+ Please see the Wiki page :
66
+ [The checkout process](https://github.com/rails-stall/stall/wiki/The-checkout-process)
4
67
 
5
68
  # Licence
6
69
 
@@ -25,7 +25,12 @@ module Stall
25
25
  def load_step
26
26
  @cart = Stall::Cart.find_by_token(params[:cart_id])
27
27
  @wizard = @cart.wizard.new(@cart)
28
- @step = @wizard.current_step.new(@cart, params)
28
+
29
+ @step = @wizard.initialize_current_step(params) do |step|
30
+ if Stall.config.steps_initialization
31
+ instance_exec(step, &Stall.config.steps_initialization)
32
+ end
33
+ end
29
34
  end
30
35
  end
31
36
  end
@@ -0,0 +1,9 @@
1
+ module Stall
2
+ class PaymentsController < ApplicationController
3
+ def process
4
+ service = Stall::PaymentNotificationService.new(request)
5
+ service.call
6
+ render service.rendering_options
7
+ end
8
+ end
9
+ end
@@ -8,8 +8,8 @@ module Stall
8
8
 
9
9
  def load_current_cart
10
10
  if (cart_token = session[cart_store_key_for(:default)])
11
- if (cart = Stall::Cart.find_by_token(cart_token))
12
- return cart
11
+ if (current_cart = Stall::Cart.find_by_token(cart_token))
12
+ return current_cart
13
13
  end
14
14
  end
15
15
 
@@ -4,12 +4,5 @@ module Stall
4
4
  accepts_nested_attributes_for :address
5
5
 
6
6
  belongs_to :addressable, polymorphic: true
7
-
8
- def type_name
9
- types = []
10
- types << :billing if billing
11
- types << :shipping if shipping
12
- types.join('-')
13
- end
14
7
  end
15
8
  end
@@ -2,13 +2,30 @@ module Stall
2
2
  class Cart < Stall::ProductList
3
3
  include Stall::Addressable
4
4
 
5
+ store_accessor :data, :reference
6
+
5
7
  has_one :shipment, dependent: :destroy, inverse_of: :cart
6
8
  accepts_nested_attributes_for :shipment
7
9
 
10
+ has_one :payment, dependent: :destroy, inverse_of: :cart
11
+ accepts_nested_attributes_for :payment
12
+
13
+ after_save :ensure_reference, on: :create
14
+
8
15
  def total_weight
9
16
  line_items.reduce(0) do |total, line_item|
10
17
  total + (line_item.weight || Stall.config.default_product_weight)
11
18
  end
12
19
  end
20
+
21
+ private
22
+
23
+ def ensure_reference
24
+ unless reference.present?
25
+ reference = [Time.now.strftime('%Y%m%d'), ('%05d' % id)].join('-')
26
+ self.reference = reference
27
+ save(validate: false)
28
+ end
29
+ end
13
30
  end
14
31
  end
@@ -1,5 +1,8 @@
1
1
  module Stall
2
2
  class Customer < ActiveRecord::Base
3
3
  include Stall::Addressable
4
+
5
+ belongs_to :user, polymorphic: true, inverse_of: :customer
6
+ accepts_nested_attributes_for :user
4
7
  end
5
8
  end
@@ -31,6 +31,10 @@ module Stall
31
31
  product_list.try(:currency) || Money.default_currency
32
32
  end
33
33
 
34
+ def vat
35
+ price - eot_price
36
+ end
37
+
34
38
  private
35
39
 
36
40
  # TODO : Stocks availibility handling
@@ -0,0 +1,14 @@
1
+ module Stall
2
+ class Payment < ActiveRecord::Base
3
+ store_accessor :data, :transaction_id
4
+
5
+ belongs_to :payment_method
6
+ belongs_to :cart
7
+
8
+ validates :cart, :payment_method, presence: true
9
+
10
+ def pay!
11
+ update_attributes!(paid_at: Time.now)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module Stall
2
+ class PaymentMethod < ActiveRecord::Base
3
+ has_many :payments, dependent: :nullify
4
+
5
+ validates :name, :identifier, presence: true
6
+ end
7
+ end
@@ -25,6 +25,18 @@ module Stall
25
25
  token
26
26
  end
27
27
 
28
+ def total_price
29
+ line_items.map(&:price).sum
30
+ end
31
+
32
+ def total_eot_price
33
+ line_items.map(&:eot_price).sum
34
+ end
35
+
36
+ def total_vat
37
+ line_items.map(&:vat).sum
38
+ end
39
+
28
40
  def total_quantity
29
41
  line_items.map(&:quantity).sum
30
42
  end
@@ -6,7 +6,7 @@ module Stall
6
6
  monetize :eot_price_cents, :price_cents,
7
7
  with_model_currency: :currency, allow_nil: true
8
8
 
9
- validates :cart, presence: true
9
+ validates :cart, :shipping_method, presence: true
10
10
 
11
11
  def currency
12
12
  cart.try(:currency) || Money.default_currency
@@ -0,0 +1,31 @@
1
+ module Stall
2
+ class PaymentNotificationService < Stall::BaseService
3
+ attr_reader :request
4
+
5
+ def initialize(request)
6
+ @request = request
7
+ end
8
+
9
+ def call
10
+ gateway.process_payment_for(request)
11
+ end
12
+
13
+ def rendering_options
14
+ gateway.rendering_options
15
+ end
16
+
17
+ private
18
+
19
+ def gateway
20
+ @gateway ||= gateway_class.new(cart)
21
+ end
22
+
23
+ def gateway_class
24
+ @gateway_class ||= Stall::Payments.gateways[request.params[:gateway]]
25
+ end
26
+
27
+ def cart
28
+ @cart ||= Stall::Cart.find(gateway_class.cart_id_from(request))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ %h1= t('stall.checkout.payment.title')
2
+
3
+ %table.table.table-striped
4
+ %thead
5
+ %tr
6
+ %th= Stall::LineItem.human_attribute_name(:name)
7
+ %th= Stall::LineItem.human_attribute_name(:unit_price)
8
+ %th= Stall::LineItem.human_attribute_name(:quantity)
9
+ %th= Stall::LineItem.human_attribute_name(:price)
10
+ %tbody
11
+ - cart.line_items.each do |line_item|
12
+ %tr{ data: { :'line-item-id' => line_item.id } }
13
+ %td= line_item.name
14
+ %td= number_to_currency(line_item.unit_price)
15
+ %td= line_item.quantity
16
+ %td= number_to_currency(line_item.price)
17
+
18
+ %tr
19
+ %td{ colspan: 3 }= t('stall.carts.recap.total_eot_price')
20
+ %td= number_to_currency(cart.total_eot_price)
21
+
22
+ %tr
23
+ %td{ colspan: 3 }= t('stall.carts.recap.total_vat')
24
+ %td= number_to_currency(cart.total_vat)
25
+
26
+ %tr
27
+ %td{ colspan: 3 }= t('stall.carts.recap.total_price')
28
+ %td= number_to_currency(cart.total_price)
@@ -1 +1,11 @@
1
1
  %h1= t('stall.checkout.payment_method.title')
2
+
3
+ = simple_form_for cart, as: :cart, url: step_path(cart) do |form|
4
+ = form.fields_for :payment do |payment_fields|
5
+ = payment_fields.association :payment_method, as: :radio_buttons
6
+
7
+ .form-actions
8
+ %button.btn.btn-primary{ type: 'submit' }
9
+ = t('stall.checkout.payment_method.validate')
10
+
11
+
@@ -15,6 +15,18 @@
15
15
  %td= line_item_fields.input_field :quantity, class: 'form-control', data: { :'quantity-field' => true }
16
16
  %td= line_item_fields.object.price
17
17
 
18
+ %tr
19
+ %td{ colspan: 3 }= t('stall.carts.recap.total_eot_price')
20
+ %td= number_to_currency(@cart.total_eot_price)
21
+
22
+ %tr
23
+ %td{ colspan: 3 }= t('stall.carts.recap.total_vat')
24
+ %td= number_to_currency(@cart.total_vat)
25
+
26
+ %tr
27
+ %td{ colspan: 3 }= t('stall.carts.recap.total_price')
28
+ %td= number_to_currency(@cart.total_price)
29
+
18
30
  .form-actions
19
31
  %button.btn.btn-default{ type: 'submit' }
20
32
  = t('stall.carts.recap.update')
@@ -3,7 +3,7 @@
3
3
  = form.hidden_field :sellable_id
4
4
 
5
5
  .input-group
6
- = form.input_field :quantity, spinner: false, value: 1, class: 'form-group'
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
9
  %button.btn.btn-primary{ type: 'submit', style: 'font-size: 12px' }
@@ -4,11 +4,16 @@ fr:
4
4
  close: "Fermer"
5
5
 
6
6
  carts:
7
+ formats:
8
+ name: "Commande n°%{ref}"
7
9
  flashes:
8
10
  update:
9
11
  success: "Votre panier a bien été mis à jour."
10
12
  error: "Votre panier n'a pu être mis à jour, merci de vérifier les champs."
11
13
  recap:
14
+ total_eot_price: "Prix total HT"
15
+ total_vat: "Total TVA"
16
+ total_price: "Prix total"
12
17
  update: "Mettre à jour le panier"
13
18
  validate: "Passer la commande"
14
19
 
@@ -37,6 +42,10 @@ fr:
37
42
  payment:
38
43
  title: "Paiement"
39
44
 
45
+ payments:
46
+ gateway:
47
+ pay: "Payer ma commande"
48
+
40
49
  activerecord:
41
50
  attributes:
42
51
  stall/line_item:
@@ -6,6 +6,7 @@ class CreateStallProductLists < ActiveRecord::Migration
6
6
  t.string :currency, null: false
7
7
  t.references :customer, index: true
8
8
  t.string :token, null: false
9
+ t.json :data
9
10
 
10
11
  t.timestamps null: false
11
12
  end
@@ -0,0 +1,10 @@
1
+ class CreateStallPaymentMethods < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_payment_methods do |t|
4
+ t.string :name
5
+ t.string :identifier
6
+
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ class CreateStallPayments < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_payments do |t|
4
+ t.references :payment_method, index: true
5
+ t.references :cart, index: true
6
+ t.datetime :paid_at
7
+ t.json :data
8
+
9
+ t.timestamps null: false
10
+ end
11
+
12
+ add_foreign_key :stall_payments, :stall_product_lists, column: :cart_id
13
+ add_foreign_key :stall_payments, :stall_payment_methods, column: :payment_method_id
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ class AddUserToCustomer < ActiveRecord::Migration
2
+ def change
3
+ add_reference :stall_customers, :user, polymorphic: true, index: true
4
+ end
5
+ end
@@ -3,10 +3,14 @@ module Stall
3
3
  class StepGenerator < ::Rails::Generators::NamedBase
4
4
  source_root File.expand_path('../templates', __FILE__)
5
5
 
6
- def copy_template
6
+ def copy_step_template
7
7
  template 'step.rb.erb', "lib/#{ file_path }.rb"
8
8
  end
9
9
 
10
+ def create_view_template
11
+ template 'step.html.haml.erb', "app/views/checkout/steps/_#{ base_file_name }.html.haml"
12
+ end
13
+
10
14
  private
11
15
 
12
16
  # Override provided file name to include checkout_wizard at the end and
@@ -15,6 +19,10 @@ module Stall
15
19
  def file_name
16
20
  @_file_name ||= [@file_name, 'checkout_step'].join('_')
17
21
  end
22
+
23
+ def base_file_name
24
+ @file_name
25
+ end
18
26
  end
19
27
  end
20
28
  end
@@ -0,0 +1,7 @@
1
+ %h1= t('stall.checkout.<%= base_file_name %>.title')
2
+
3
+ = simple_form_for cart, as: :cart, url: step_path(cart) do |form|
4
+
5
+ %button.btn.btn-primary{ type: 'submit' }
6
+ = t('stall.checkout.<%= base_file_name %>.validate')
7
+
@@ -1,2 +1,19 @@
1
1
  class <%= class_name %> < Stall::Checkout::Step
2
+ # Prepare the `cart` before showing your view
3
+ #
4
+ # def prepare
5
+ # end
6
+
7
+ # Process `params` here, defaults to updating the cart with `cart_params`
8
+ # (params[:cart])
9
+ #
10
+ # def process
11
+ # end
12
+
13
+ # Handle the case where the step should be skipped by returning true from
14
+ # this method
15
+ #
16
+ # def skip?
17
+ # false
18
+ # end
2
19
  end
@@ -3,7 +3,7 @@ module Stall
3
3
  class WizardGenerator < ::Rails::Generators::NamedBase
4
4
  source_root File.expand_path('../templates', __FILE__)
5
5
 
6
- def copy_template
6
+ def copy_wizard_template
7
7
  template 'wizard.rb.erb', "lib/#{ file_path }.rb"
8
8
  end
9
9
 
@@ -11,7 +11,7 @@ module Stall
11
11
  end
12
12
 
13
13
  def copy_default_checkout_wizard
14
- generate 'stall:wizard', 'default'
14
+ generate 'stall:checkout:wizard', 'default'
15
15
  end
16
16
  end
17
17
  end
@@ -44,4 +44,14 @@ Stall.configure do |config|
44
44
  # Defaults to nil, which means all countries are available
45
45
  #
46
46
  # config.shipping.free_shipping.available = nil
47
+
48
+ # Allows hooking into checkout steps initialization in the steps controller
49
+ # to inject dependencies to all or a specific step
50
+ #
51
+ # config.steps_initialization do |step|
52
+ # # Add the `current_user` in all steps
53
+ # step.inject(:current_user, current_user)
54
+ # # Only inject in the :some step :
55
+ # step.inject(:ip, request.remote_ip) if SomeCheckoutStep === step
56
+ # end
47
57
  end
@@ -18,6 +18,7 @@ module Stall
18
18
 
19
19
  autoload :Checkout
20
20
  autoload :Shipping
21
+ autoload :Payments
21
22
 
22
23
  autoload :Routes
23
24
  autoload :Config
@@ -29,11 +29,14 @@ module Stall
29
29
  def process_addresses
30
30
  unless params[:use_another_address_for_billing]
31
31
  # Remove submitted billing address
32
- billing_ownership = cart.address_ownership_for(:billing)
33
- cart.address_ownerships.destroy(billing_ownership)
32
+ if (billing_ownership = cart.address_ownership_for(:billing))
33
+ cart.address_ownerships.destroy(billing_ownership)
34
+ end
35
+
34
36
  # Set shipping address as the billing one
35
- shipping_ownership = cart.address_ownership_for(:shipping)
36
- shipping_ownership.billing = true
37
+ if (shipping_ownership = cart.address_ownership_for(:shipping))
38
+ shipping_ownership.billing = true
39
+ end
37
40
  end
38
41
  end
39
42
  end
@@ -1,6 +1,9 @@
1
1
  module Stall
2
2
  module Checkout
3
3
  class PaymentMethodCheckoutStep < Stall::Checkout::Step
4
+ def prepare
5
+ cart.build_payment unless cart.payment
6
+ end
4
7
  end
5
8
  end
6
9
  end
@@ -10,6 +10,12 @@ module Stall
10
10
  @params = params
11
11
  end
12
12
 
13
+ # Allow injecting dependencies on step initialization and accessing
14
+ # them as instance method in subclasses
15
+ def inject(method, content)
16
+ define_singleton_method(method, -> { content })
17
+ end
18
+
13
19
  # Allows for preparing to the cart for the current step before rendering
14
20
  # the step's view
15
21
  #
@@ -23,7 +29,11 @@ module Stall
23
29
  end
24
30
 
25
31
  def cart_params
26
- params.require(:cart).permit!
32
+ @cart_params ||= params.require(:cart).permit!
33
+ end
34
+
35
+ def skip?
36
+ false
27
37
  end
28
38
 
29
39
  # Handles conversion from an identifier to a checkout step class, allowing
@@ -19,12 +19,24 @@ module Stall
19
19
  @cart = cart
20
20
  end
21
21
 
22
- def current_step
23
- if (step_name = current_step_name)
24
- Stall::Checkout::Step.for(step_name)
22
+ def initialize_current_step(params, &block)
23
+ step = current_step.new(cart, params)
24
+ # This block allows us to let the config inject controller-bound
25
+ # dependencies to the step just after it is initialized
26
+ block.call(step) if block
27
+
28
+ if step.skip?
29
+ validate_current_step!
30
+ initialize_current_step(params, &block)
31
+ else
32
+ step
25
33
  end
26
34
  end
27
35
 
36
+ def current_step
37
+ Stall::Checkout::Step.for(current_step_name)
38
+ end
39
+
28
40
  def current_step_name
29
41
  if step?(cart.state)
30
42
  cart.state
@@ -20,8 +20,19 @@ module Stall
20
20
  # Default product weight if no weight is found
21
21
  param :default_product_weight, 0
22
22
 
23
+ # Default step initialization hook
24
+ param :_steps_initialization_callback
25
+
23
26
  def shipping
24
27
  @shipping ||= Stall::Shipping::Config.new
25
28
  end
29
+
30
+ def steps_initialization(value = nil, &block)
31
+ if (value ||= block)
32
+ @_steps_initialization_callback = value
33
+ else
34
+ @_steps_initialization_callback
35
+ end
36
+ end
26
37
  end
27
38
  end
@@ -1,11 +1,5 @@
1
1
  module Stall
2
2
  class Engine < ::Rails::Engine
3
- initializer 'include sellable mixin into models' do
4
- ActiveSupport.on_load(:active_record) do
5
- include Stall::Sellable::Mixin
6
- end
7
- end
8
-
9
3
  initializer 'set money gem default currency' do
10
4
  Money.default_currency = Stall.config.default_currency
11
5
  end
@@ -0,0 +1,13 @@
1
+ module Stall
2
+ module Payments
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Gateway
6
+
7
+ # autoload :Config
8
+
9
+ mattr_reader :gateways
10
+ @@gateways = {}
11
+ end
12
+ end
13
+
@@ -0,0 +1,73 @@
1
+ module Stall
2
+ module Payments
3
+ class Gateway
4
+ TRANSACTION_ID_FORMAT = 'ESHOP-%{cart_id}-%{transaction_index}'
5
+
6
+ attr_reader :cart
7
+
8
+ def initialize(cart)
9
+ @cart = cart
10
+ end
11
+
12
+ def self.register(name)
13
+ Stall::Payments.gateways[name] = self
14
+ end
15
+
16
+ def self.cart_id_from(_request)
17
+ raise NoMethodError,
18
+ 'Subclasses must implement the .cart_id_from(request) class method ' \
19
+ 'to allow retrieving the cart from the remote gateway notification ' \
20
+ 'request object'
21
+ end
22
+
23
+ def self.cart_id_from_transaction_id(transaction_id)
24
+ transaction_id && transaction_id.split('-')[2].to_i
25
+ end
26
+
27
+ def process_payment_for(_request)
28
+ raise NoMethodError,
29
+ 'Subclasses must implement the #process_payment_for(request) ' \
30
+ 'method to handle payment verifications and cart payment validation'
31
+ end
32
+
33
+ def transaction_id
34
+ @transaction_id ||= begin
35
+ unless (id = cart.payment.transaction_id)
36
+ id = next_transaction_id
37
+ cart.payment.update_attributes(transaction_id: id)
38
+ end
39
+
40
+ id
41
+ end
42
+ end
43
+
44
+ # Defines the arguments passed to the render call in response to the
45
+ # automatic gateway response notification
46
+ #
47
+ # Most of the gateways expect some specific return, so this is to be
48
+ # overriden by subclasses
49
+ def rendering_options
50
+ { text: nil }
51
+ end
52
+
53
+ private
54
+
55
+ def next_transaction_id
56
+ if (last_transaction = Stall::Payment.order("data->>'transaction_id' DESC").select(:data).first)
57
+ if (id = last_transaction.transaction_id)
58
+ index = id.split('-').pop.to_i + 1
59
+ return transaction_id_for(index)
60
+ end
61
+ end
62
+
63
+ transaction_id_for(1)
64
+ end
65
+
66
+ def transaction_id_for(index)
67
+ TRANSACTION_ID_FORMAT
68
+ .gsub('%{cart_id}', cart.reference)
69
+ .gsub('%{transaction_index}', ('%05d' % index))
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,20 +6,20 @@ module Stall
6
6
  @router = router
7
7
  end
8
8
 
9
- def draw(mount_location, options = {}, &block)
9
+ def draw(mount_location)
10
10
  router.instance_eval do
11
- scope mount_location do
12
- scope module: :stall do
13
- resources :carts do
14
- resources :line_items
15
- end
11
+ scope mount_location, module: :stall do
12
+ resources :carts do
13
+ resources :line_items
14
+ end
16
15
 
17
- resources :checkouts, only: [:show]
16
+ resources :checkouts, only: [:show]
18
17
 
19
- scope '/checkout/:type/:cart_id', module: 'checkout', as: :checkout do
20
- resource :step, only: [:show, :update]
21
- end
18
+ scope '/checkout/:type/:cart_id', module: 'checkout', as: :checkout do
19
+ resource :step, only: [:show, :update]
22
20
  end
21
+
22
+ match '/payments/:gateway/process' => 'payments#process', via: [:get, :post]
23
23
  end
24
24
  end
25
25
  end
@@ -1,8 +1,43 @@
1
1
  module Stall
2
2
  module Sellable
3
- extend ActiveSupport::Autoload
3
+ extend ActiveSupport::Concern
4
4
 
5
- autoload :Mixin
6
- autoload :Model
5
+ included do
6
+ has_many :line_items, class_name: 'Stall::LineItem',
7
+ dependent: :nullify,
8
+ as: :sellable,
9
+ inverse_of: :sellable
10
+ end
11
+
12
+ def to_line_item
13
+ line_items.build(
14
+ name: (respond_to?(:name) && name) || (respond_to?(:title) && title),
15
+ unit_price: (respond_to?(:price) && price),
16
+ unit_eot_price: eot_price,
17
+ vat_rate: vat_rate,
18
+ )
19
+ end
20
+
21
+ def vat_ratio
22
+ (vat_rate / 100.0) + 1
23
+ end
24
+
25
+ private
26
+
27
+ def default_eot_price
28
+ price && (price / vat_ratio)
29
+ end
30
+
31
+ def default_vat_rate
32
+ @default_vat_rate ||= Stall.config.vat_rate
33
+ end
34
+
35
+ def method_missing(name, *args, &block)
36
+ if [:eot_price, :vat_rate].include?(name)
37
+ send(:"default_#{ name }")
38
+ else
39
+ super
40
+ end
41
+ end
7
42
  end
8
43
  end
@@ -8,7 +8,7 @@ module Stall
8
8
  @config = config
9
9
  end
10
10
 
11
- def available_for?(address)
11
+ def available_for?(_address)
12
12
  raise NoMethodError,
13
13
  'Shipping calculators must implement the #available_for? method ' \
14
14
  'to allow filtering available shipping methods'
@@ -12,7 +12,7 @@ module Stall
12
12
  begin
13
13
  require name.underscore
14
14
  name.constantize
15
- rescue LoadError, NameError => e
15
+ rescue LoadError, NameError
16
16
  nil
17
17
  end
18
18
  end
@@ -1,3 +1,3 @@
1
1
  module Stall
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - vala
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-25 00:00:00.000000000 Z
11
+ date: 2016-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -248,6 +248,20 @@ dependencies:
248
248
  - - "~>"
249
249
  - !ruby/object:Gem::Version
250
250
  version: 0.36.0
251
+ - !ruby/object:Gem::Dependency
252
+ name: codeclimate-test-reporter
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
251
265
  description: Rails e-commerce framework
252
266
  email:
253
267
  - vala@glyph.fr
@@ -269,23 +283,27 @@ files:
269
283
  - app/controllers/stall/checkout/steps_controller.rb
270
284
  - app/controllers/stall/checkouts_controller.rb
271
285
  - app/controllers/stall/line_items_controller.rb
286
+ - app/controllers/stall/payments_controller.rb
272
287
  - app/helpers/stall/add_to_cart_helper.rb
273
288
  - app/helpers/stall/application_helper.rb
274
289
  - app/helpers/stall/cart_helper.rb
275
290
  - app/helpers/stall/checkout_helper.rb
276
- - app/models/stall.rb
277
291
  - app/models/stall/address.rb
278
292
  - app/models/stall/address_ownership.rb
279
293
  - app/models/stall/cart.rb
280
294
  - app/models/stall/customer.rb
281
295
  - app/models/stall/line_item.rb
296
+ - app/models/stall/payment.rb
297
+ - app/models/stall/payment_method.rb
282
298
  - app/models/stall/product_list.rb
283
299
  - app/models/stall/shipment.rb
284
300
  - app/models/stall/shipping_method.rb
285
301
  - app/services/stall/add_to_cart_service.rb
286
302
  - app/services/stall/base_service.rb
303
+ - app/services/stall/payment_notification_service.rb
287
304
  - app/services/stall/shipping_fee_calculator_service.rb
288
305
  - app/views/checkout/steps/_informations.html.haml
306
+ - app/views/checkout/steps/_payment.html.haml
289
307
  - app/views/checkout/steps/_payment_method.html.haml
290
308
  - app/views/checkout/steps/_shipping_method.html.haml
291
309
  - app/views/layouts/stall/application.html.erb
@@ -303,7 +321,11 @@ files:
303
321
  - db/migrate/20160122143748_create_stall_address_ownerships.rb
304
322
  - db/migrate/20160124014144_create_stall_shipping_methods.rb
305
323
  - db/migrate/20160124020313_create_stall_shipments.rb
324
+ - db/migrate/20160125100733_create_stall_payment_methods.rb
325
+ - db/migrate/20160125100734_create_stall_payments.rb
326
+ - db/migrate/20160127113619_add_user_to_customer.rb
306
327
  - lib/generators/stall/checkout/step/step_generator.rb
328
+ - lib/generators/stall/checkout/step/templates/step.html.haml.erb
307
329
  - lib/generators/stall/checkout/step/templates/step.rb.erb
308
330
  - lib/generators/stall/checkout/wizard/templates/wizard.rb.erb
309
331
  - lib/generators/stall/checkout/wizard/wizard_generator.rb
@@ -320,12 +342,12 @@ files:
320
342
  - lib/stall/checkout/wizard.rb
321
343
  - lib/stall/config.rb
322
344
  - lib/stall/engine.rb
345
+ - lib/stall/payments.rb
346
+ - lib/stall/payments/gateway.rb
323
347
  - lib/stall/rails/currency_helper.rb
324
348
  - lib/stall/rails/routing_mapper.rb
325
349
  - lib/stall/routes.rb
326
350
  - lib/stall/sellable.rb
327
- - lib/stall/sellable/mixin.rb
328
- - lib/stall/sellable/model.rb
329
351
  - lib/stall/shipping.rb
330
352
  - lib/stall/shipping/calculator.rb
331
353
  - lib/stall/shipping/config.rb
@@ -1,5 +0,0 @@
1
- module Stall
2
- def self.table_name_prefix
3
- 'stall_'
4
- end
5
- end
@@ -1,17 +0,0 @@
1
- module Stall
2
- module Sellable
3
- module Mixin
4
- extend ActiveSupport::Concern
5
-
6
- def sellable?
7
- false
8
- end
9
-
10
- module ClassMethods
11
- def acts_as_sellable
12
- include Stall::Sellable::Model
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,46 +0,0 @@
1
- module Stall
2
- module Sellable
3
- module Model
4
- extend ActiveSupport::Concern
5
-
6
- def sellable?
7
- true
8
- end
9
-
10
- def to_line_item
11
- value_if_method = -> name { send(name) if respond_to?(name) }
12
-
13
- Stall::LineItem.new(
14
- sellable: self,
15
- quantity: 1,
16
- name: (value_if_method.(:name) || value_if_method.(:title)),
17
- unit_price: value_if_method.(:price),
18
- unit_eot_price: eot_price,
19
- vat_rate: vat_rate,
20
- )
21
- end
22
-
23
- def vat_ratio
24
- (vat_rate / 100.0) + 1
25
- end
26
-
27
- private
28
-
29
- def default_eot_price
30
- price && (price / vat_ratio)
31
- end
32
-
33
- def default_vat_rate
34
- @default_vat_rate ||= Stall.config.vat_rate
35
- end
36
-
37
- def method_missing(name, *args, &block)
38
- if [:eot_price, :vat_rate].include?(name)
39
- send(:"default_#{ name }")
40
- else
41
- super
42
- end
43
- end
44
- end
45
- end
46
- end