locomotive_ecommerce_plugin 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +40 -0
  3. data/app/assets/javascripts/locomotive/ecommerce/application.js +15 -0
  4. data/app/assets/stylesheets/locomotive/ecommerce/application.css +13 -0
  5. data/app/assets/stylesheets/locomotive/ecommerce/flash_dance.alerts.bootstrap.css +107 -0
  6. data/app/controllers/locomotive/ecommerce/application_controller.rb +35 -0
  7. data/app/controllers/locomotive/ecommerce/cart_controller.rb +12 -0
  8. data/app/controllers/locomotive/ecommerce/order_controller.rb +18 -0
  9. data/app/controllers/locomotive/ecommerce/purchase_controller.rb +56 -0
  10. data/app/helpers/locomotive/ecommerce/ecommerce_cart_helper.rb +11 -0
  11. data/app/helpers/locomotive/ecommerce/ecommerce_helper.rb +25 -0
  12. data/app/helpers/locomotive/ecommerce/ecommerce_url_helper.rb +52 -0
  13. data/app/mailers/locomotive/ecommerce/purchase_mailer.rb +19 -0
  14. data/app/models/locomotive/ecommerce/cart.rb +162 -0
  15. data/app/models/locomotive/ecommerce/order.rb +100 -0
  16. data/app/models/locomotive/ecommerce/purchase.rb +148 -0
  17. data/app/views/flash_dance/_alert.html.erb +4 -0
  18. data/app/views/flash_dance/_error.html.erb +4 -0
  19. data/app/views/flash_dance/_info.html.erb +4 -0
  20. data/app/views/flash_dance/_notice.html.erb +4 -0
  21. data/app/views/flash_dance/_success.html.erb +4 -0
  22. data/app/views/flash_dance/_warning.html.erb +4 -0
  23. data/app/views/kaminari/_first_page.html.erb +11 -0
  24. data/app/views/kaminari/_gap.html.erb +8 -0
  25. data/app/views/kaminari/_last_page.html.erb +11 -0
  26. data/app/views/kaminari/_next_page.html.erb +11 -0
  27. data/app/views/kaminari/_page.html.erb +12 -0
  28. data/app/views/kaminari/_paginator.html.erb +23 -0
  29. data/app/views/kaminari/_prev_page.html.erb +11 -0
  30. data/app/views/locomotive/ecommerce/purchase_mailer/purchase_confirmation.text.erb +11 -0
  31. data/config/initializers/active_resource.rb +1 -0
  32. data/config/initializers/kaminari_config.rb +16 -0
  33. data/config/initializers/liquid_stack_trace.rb +7 -0
  34. data/config/initializers/stripe_setup.rb +40 -0
  35. data/config/routes.rb +14 -0
  36. data/lib/locomotive/ecommerce/plugin.rb +74 -0
  37. data/lib/locomotive/ecommerce/plugin/config.html +111 -0
  38. data/lib/locomotive/ecommerce/plugin/ecommerce_drop.rb +106 -0
  39. data/lib/locomotive/ecommerce/plugin/ecommerce_filters.rb +35 -0
  40. data/lib/locomotive/ecommerce/plugin/ecommerce_tags.rb +31 -0
  41. data/lib/locomotive/ecommerce/plugin/engine.rb +42 -0
  42. data/lib/locomotive/ecommerce/plugin/inventory_interface.rb +15 -0
  43. data/lib/locomotive/ecommerce/plugin/version.rb +5 -0
  44. data/lib/locomotive_ecommerce_plugin.rb +1 -0
  45. metadata +184 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWE5M2U2MDk3N2QxMGEwZTJjZTlkMTdiYTkyYTY1MGZiYzJhNWYwNg==
5
+ data.tar.gz: !binary |-
6
+ YmY0ZDQ1YzA2Y2E5NDU3MzhhNzBlNzliOThjNWQ5NzliN2VmMTRlMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YTRmZTY0YTM1YjZhYjVkMTQ3Mzc0OGNlN2ZhOWZhNGI0NmY1NGZjOWY2YWYz
10
+ OThlNTY3ZTI3ZDgyZmE4Y2Q4NzkxNTdjYjVkZDA5NDBlMTg5ZWY0ZDgwM2E0
11
+ OTkxMGE1MTM4YzQ1YTlhYTJkNTM4MWMxMjIwM2U1MmRiZTNjOWY=
12
+ data.tar.gz: !binary |-
13
+ ZjlkNDAwMTdkYTdkYWU0NTdmZjZlOWQ4OWJhYjM0MjEwOWRmNDJmNzc0NWQ0
14
+ OTFjMzg1YTNkZDBjNjMxNjNkOTdlMjc2NzlkZGUzNWJiMTJiMTgzMTBlMjdl
15
+ OWI3OWU1M2YxZWM4ZmJkNWYxYzc0OTJlYTkxNzJiMGVkOWZmMDI=
data/Gemfile ADDED
@@ -0,0 +1,40 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Declare your gem's dependencies in hbird_ecommerce.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Stripe helper
9
+ gem 'stripe_helper', path: '../../stripe_helper'
10
+
11
+ # jquery-rails is used by the dummy application
12
+ group :test do
13
+ gem 'rspec-rails'
14
+ gem 'capybara'
15
+ gem "factory_girl"
16
+ gem "mocha"
17
+ gem "database_cleaner"
18
+ gem "factory_girl_rails"
19
+ gem "simplecov", require: false
20
+ gem 'shoulda-matchers', require: false
21
+ gem "debugger"
22
+ gem "poltergeist"
23
+ end
24
+
25
+ # gem "locomotive_cms", path: '../../locomotive_engine', require: 'locomotive/engine'
26
+ # gem "locomotive_plugins", path: '../../locomotive_plugins'
27
+
28
+ group :assets do
29
+ gem 'compass-rails', '~> 1.1.7'
30
+ gem 'sass-rails', '~> 3.2.4'
31
+ gem 'coffee-rails', '~> 3.2.2'
32
+ gem 'uglifier', '~> 1.2.4'
33
+ end
34
+
35
+ group :locomotive_plugins do
36
+ gem "locomotive_ecommerce_plugin", path: '.'
37
+ end
38
+
39
+ # TEMP
40
+ gem 'flash-dance', :git => 'https://github.com/MunkiPhD/flash-dance.git' # It may be not being used.
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,107 @@
1
+ /*!
2
+ * Bootstrap v2.2.1
3
+ *
4
+ * Copyright 2012 Twitter, Inc
5
+ * Licensed under the Apache License v2.0
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
9
+ */
10
+ .clearfix {
11
+ *zoom: 1;
12
+ }
13
+ .clearfix:before,
14
+ .clearfix:after {
15
+ display: table;
16
+ content: "";
17
+ line-height: 0;
18
+ }
19
+ .clearfix:after {
20
+ clear: both;
21
+ }
22
+ .hide-text {
23
+ font: 0/0 a;
24
+ color: transparent;
25
+ text-shadow: none;
26
+ background-color: transparent;
27
+ border: 0;
28
+ }
29
+ .input-block-level {
30
+ display: block;
31
+ width: 100%;
32
+ min-height: 30px;
33
+ -webkit-box-sizing: border-box;
34
+ -moz-box-sizing: border-box;
35
+ box-sizing: border-box;
36
+ }
37
+ .alert {
38
+ padding: 8px 35px 8px 14px;
39
+ margin-bottom: 20px;
40
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
41
+ background-color: #fcf8e3;
42
+ border: 1px solid #fbeed5;
43
+ -webkit-border-radius: 4px;
44
+ -moz-border-radius: 4px;
45
+ border-radius: 4px;
46
+ color: #c09853;
47
+ }
48
+ .alert h4 {
49
+ margin: 0;
50
+ }
51
+ .alert .close {
52
+ position: relative;
53
+ top: -2px;
54
+ right: -21px;
55
+ line-height: 20px;
56
+ }
57
+ .alert-success {
58
+ background-color: #dff0d8;
59
+ border-color: #d6e9c6;
60
+ color: #468847;
61
+ }
62
+ .alert-danger,
63
+ .alert-error {
64
+ background-color: #f2dede;
65
+ border-color: #eed3d7;
66
+ color: #b94a48;
67
+ }
68
+ .alert-info {
69
+ background-color: #d9edf7;
70
+ border-color: #bce8f1;
71
+ color: #3a87ad;
72
+ }
73
+ .alert-block {
74
+ padding-top: 14px;
75
+ padding-bottom: 14px;
76
+ }
77
+ .alert-block > p,
78
+ .alert-block > ul {
79
+ margin-bottom: 0;
80
+ }
81
+ .alert-block p + p {
82
+ margin-top: 5px;
83
+ }
84
+ .close {
85
+ float: right;
86
+ font-size: 20px;
87
+ font-weight: bold;
88
+ line-height: 20px;
89
+ color: #000000;
90
+ text-shadow: 0 1px 0 #ffffff;
91
+ opacity: 0.2;
92
+ filter: alpha(opacity=20);
93
+ }
94
+ .close:hover {
95
+ color: #000000;
96
+ text-decoration: none;
97
+ cursor: pointer;
98
+ opacity: 0.4;
99
+ filter: alpha(opacity=40);
100
+ }
101
+ button.close {
102
+ padding: 0;
103
+ cursor: pointer;
104
+ background: transparent;
105
+ border: 0;
106
+ -webkit-appearance: none;
107
+ }
@@ -0,0 +1,35 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class ApplicationController < ::ActionController::Base
4
+ include ::Locomotive::Ecommerce::EcommerceHelper
5
+
6
+ before_filter :set_current_site
7
+
8
+ def authenticate_user!
9
+ if current_user(self) == nil
10
+ flash[:error] = "Authentication needed. Please log in to continue."
11
+ redirect_to cart_path
12
+ end
13
+ end
14
+
15
+ def locomotive_user?
16
+ locomotive_account_signed_in?
17
+ end
18
+
19
+ private
20
+
21
+ def fetch_site
22
+ if Locomotive.config.multi_sites?
23
+ @current_site ||= ::Locomotive::Site.match_domain(request.host).first
24
+ else
25
+ @current_site ||= ::Locomotive::Site.first
26
+ end
27
+ end
28
+
29
+ def set_current_site
30
+ Thread.current[:site] = fetch_site
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class CartController < ::Locomotive::Ecommerce::ApplicationController
4
+ def update
5
+ @cart = Cart.find(params[:id])
6
+ @cart.update_from_params(params)
7
+ flash[:success] = 'Updated cart'
8
+ redirect_to cart_path
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class OrderController < ::Locomotive::Ecommerce::ApplicationController
4
+ def create
5
+ @order = current_user_cart(self).add_product_by_sku(params[:item_sku])
6
+ flash[:success] = 'Added product to cart'
7
+ redirect_to cart_path
8
+ end
9
+
10
+ def destroy
11
+ @order = current_user_cart(self).remove_product_by_sku(
12
+ params[:product_id])
13
+ flash[:success] = 'Removed product from cart'
14
+ redirect_to cart_path
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ require 'stripe'
2
+
3
+ module Locomotive
4
+ module Ecommerce
5
+ class PurchaseController < ::Locomotive::Ecommerce::ApplicationController
6
+ before_filter :authenticate_user!, except: [:do_new_purchase]
7
+
8
+ def create
9
+ @purchase = current_user_cart(self).purchase
10
+ @purchase.shipping_info = params[:shipping_info]
11
+ @purchase.shipping_method = params[:shipping_method]
12
+ if @purchase.save
13
+ redirect_to confirm_order_path
14
+ return
15
+ elsif @purchase.errors.any?
16
+ flash_ar = []
17
+ @purchase.errors.full_messages.each { |msg| flash_ar << msg }
18
+ flash[:error] = flash_ar.join(', ')
19
+ end
20
+
21
+ redirect_to checkout_path
22
+ end
23
+
24
+ def self.complete(purchase_id, user, cart, stripeToken)
25
+ purchase = Purchase.where(_id: purchase_id).first
26
+
27
+ #Reset user cart
28
+ purchase.cart.user_id = nil
29
+ purchase.cart.save!
30
+ new_cart = Cart.create
31
+ new_cart.user_id = user.id
32
+ new_cart.save!
33
+
34
+ #complete purchase
35
+ purchase.stripe_token = stripeToken
36
+ purchase.complete
37
+ purchase.completed = true
38
+ purchase.user_id = user.id
39
+ purchase.save!
40
+ PurchaseMailer.purchase_confirmation(user, purchase).deliver
41
+ after_purchase_hook(purchase, user)
42
+ end
43
+
44
+ private
45
+
46
+ def self.after_purchase_hook(purchase, user)
47
+
48
+ site = Thread.current[:site]
49
+ cxt = site.plugin_object_for_id('ecommerce').js3_context
50
+ cxt['user'] = user
51
+ cxt['purchase'] = purchase
52
+ last = cxt.eval(Engine.config_or_default('after_purchase_hook'))
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ module EcommerceCartHelper
4
+ def current_user_cart(controller)
5
+ user = current_user(controller)
6
+ id = user == nil ? nil : user.id
7
+ Cart.find_or_create(id, controller.session)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ require 'locomotive/ecommerce/plugin/inventory_interface'
2
+
3
+ module Locomotive
4
+ module Ecommerce
5
+ module EcommerceHelper
6
+ include ::Locomotive::Ecommerce::EcommerceCartHelper
7
+ include ::Locomotive::Ecommerce::EcommerceUrlHelper
8
+ include ::Locomotive::Ecommerce::InventoryInterface
9
+
10
+ # User
11
+ def current_user(controller)
12
+ if controller.session[:user_id]
13
+ site = Thread.current[:site]
14
+ user_from_plugin = site.plugin_object_for_id('identity_plugin').js3_context['identity_plugin_users']
15
+ @current_user ||= user_from_plugin.find(controller.session[:user_id])
16
+ end
17
+ end
18
+
19
+ # View Helper
20
+ def as_currency(val)
21
+ "$#{'%.2f' % val}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ module EcommerceUrlHelper
4
+ ##################
5
+ # configured urls
6
+ ##################
7
+ def cart_path(*args)
8
+ Engine.config_or_default('cart_url')
9
+ end
10
+
11
+ def checkout_path
12
+ Engine.config_or_default('checkout_url')
13
+ end
14
+
15
+ def confirm_order_path
16
+ Engine.config_or_default('confirm_order_url')
17
+ end
18
+
19
+ def purchases_path
20
+ Engine.config_or_default('purchases_url')
21
+ end
22
+
23
+ def post_checkout_path
24
+ Engine.config_or_default('post_checkout_url')
25
+ end
26
+
27
+ ###########################
28
+ # non-configured urls
29
+ # (provided by the engine)
30
+ # #########################
31
+ def cart_update_path(stem, cart)
32
+ "#{stem}cart/#{cart.id}"
33
+ end
34
+
35
+ def add_to_cart_path(stem, product)
36
+ "#{stem}add_to_cart/#{product.id}"
37
+ end
38
+
39
+ def remove_from_cart_path(stem, product_id)
40
+ "#{stem}remove_from_cart/#{product_id}"
41
+ end
42
+
43
+ def checkout_update_path(stem, purchase)
44
+ "#{stem}checkout/#{purchase.id}"
45
+ end
46
+
47
+ def checkout_index_path(stem)
48
+ "#{stem}checkout"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class PurchaseMailer < ActionMailer::Base
4
+ default from: Engine.config_or_default('contact')
5
+
6
+ # Subject can be set in your I18n file at config/locales/en.yml
7
+ # with the following lookup:
8
+ #
9
+ # en.purchase_mailer.purchase_confirmation.subject
10
+ #
11
+ def purchase_confirmation(user, purchase)
12
+ @purchase = purchase
13
+ @contact = Engine.config_or_default('contact')
14
+ @shop_name = Engine.config_or_default('shop_name')
15
+ mail to: user.email, subject: "Purchase Confirmation"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,162 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class Cart
4
+ include ::Locomotive::Ecommerce::EcommerceHelper
5
+ include Mongoid::Document
6
+ field :user_id, :type => ::BSON::ObjectId
7
+ belongs_to :purchase, :class_name => "::Locomotive::Ecommerce::Purchase"
8
+ has_many :orders, :class_name => "::Locomotive::Ecommerce::Order"
9
+
10
+ def add_product_by_sku(sku)
11
+ already_existing_order = orders.where(:sku => sku)
12
+ if already_existing_order.count > 0
13
+ order = already_existing_order.first
14
+ order.quantity += 1
15
+ else
16
+ order = Order.new
17
+ order.sku = sku
18
+ order.cart = self
19
+ end
20
+ order.save!
21
+ return order
22
+ end
23
+
24
+ def remove_product_by_sku(sku)
25
+ order = orders.where(:sku => sku)
26
+ order.destroy if order
27
+ end
28
+
29
+ def purchase_total
30
+ total = 0
31
+ orders.each { |order| total += order.price }
32
+ return total
33
+ end
34
+
35
+ def estimated_tax
36
+ purchase_total * ((Engine.config_or_default('estimated_tax_rate').to_f/100))
37
+ end
38
+
39
+ def subtotal_est_tax
40
+ purchase_total + estimated_tax + extras_total
41
+ end
42
+
43
+ def add_extras_js
44
+ ext = Engine.config_or_default('edit_extra')
45
+ return nil unless ext
46
+ site = Thread.current[:site]
47
+ cxt = site.plugin_object_for_id('inventory').js3_context
48
+ cxt['purchase_total'] = purchase_total
49
+ cxt['orders'] = orders
50
+ js = cxt.eval(ext)
51
+
52
+ return js
53
+ end
54
+
55
+ def extras_total
56
+ ext = 0
57
+ arr = add_extras_js
58
+ # Adding all the values returned by the javascript function
59
+ if arr
60
+ arr.values.each do |value|
61
+ ext += value
62
+ end
63
+ end
64
+
65
+ return ext
66
+ end
67
+
68
+ def extras
69
+ orders ? arr = add_extras_js : ''
70
+ # Converting the prices to currency
71
+ extras_hash = Hash[arr.map {|k,v| [k,as_currency(v) ]}]
72
+ extras_hash.to_liquid
73
+ end
74
+
75
+ def update_from_params(params)
76
+ to_update = Order.find(params[:order_ids])
77
+ count = 0
78
+ to_update.each do |order|
79
+ order.quantity = params[:quantity_ids][count] if order.cart_id == id
80
+ count += 1
81
+ order.save!
82
+ end
83
+ end
84
+
85
+ # merge in contents of another cart
86
+ def merge(cart)
87
+ cart.orders.each do |order|
88
+ (1..order.quantity).each { |c| add_product_by_sku(order.sku) }
89
+ cart.remove_product_by_sku(order.sku)
90
+ end
91
+ end
92
+
93
+ def valid_stock?
94
+ orders.each { |order| return false if order.out_of_stock? }
95
+ return true
96
+ end
97
+
98
+ def to_liquid
99
+ CartDrop.new(self)
100
+ end
101
+
102
+ # self methods
103
+ def self.for_user(id)
104
+ cart = where(:user_id => id).first || create(:user_id => id)
105
+ end
106
+
107
+ def self.find_or_create(id, session)
108
+ cart_id = session[:cart_id]
109
+ if cart_id != nil
110
+ cart = where(:_id => cart_id).first
111
+
112
+ if cart == nil
113
+ session[:cart_id] = nil
114
+ return find_or_create(id, session)
115
+ end
116
+
117
+ return cart if id == nil
118
+
119
+ session[:cart_id] = nil
120
+ user_cart = Cart.for_user(id)
121
+ user_cart.merge(cart)
122
+ cart.destroy
123
+ return user_cart
124
+ end
125
+
126
+ return Cart.for_user(id) if id != nil
127
+
128
+ cart = create
129
+ cart.save!
130
+ session[:cart_id] = cart.id.to_s
131
+ return cart
132
+ end
133
+ end
134
+
135
+ class CartDrop < ::Liquid::Drop
136
+ def initialize(source)
137
+ @source = source
138
+ end
139
+
140
+ def line_items
141
+ @source.orders
142
+ end
143
+
144
+ def id
145
+ @source.id.to_s
146
+ end
147
+
148
+ def extras
149
+ @source.extras
150
+ end
151
+
152
+ [:purchase_total, :estimated_tax, :subtotal_est_tax, :extras_total].each do |method|
153
+ define_method(method) {"%0.2f" % @source.send(method).round(2)}
154
+ end
155
+
156
+ delegate :valid_stock?, to: :@source
157
+
158
+ protected
159
+ attr_accessor :source
160
+ end
161
+ end
162
+ end