locomotive_ecommerce_plugin 1.0.0

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 (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