stall 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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