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,100 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class Order
4
+ include Mongoid::Document
5
+ include ::Mongoid::Timestamps
6
+ include InventoryInterface
7
+ field :quantity, :type => Integer, :default => 1
8
+ field :sku, :type => String
9
+ belongs_to :cart, :class_name => "::Locomotive::Ecommerce::Cart"
10
+
11
+ def quantity=(value)
12
+ begin
13
+ new_quantity = Integer(value)
14
+ rescue
15
+ return false
16
+ end
17
+ self[:quantity] = new_quantity
18
+ cart.remove_product_by_sku(sku) if new_quantity < 1
19
+ end
20
+
21
+ def quantity
22
+ self[:quantity]
23
+ end
24
+
25
+ def price
26
+ quantity * product_price
27
+ end
28
+
29
+ def out_of_stock?
30
+ quantity > product_quantity
31
+ end
32
+
33
+ def self.id_to_sku(id)
34
+ ret = product_class.where(:_id => id).first
35
+ ret ? ret.sku : nil
36
+ end
37
+
38
+ def product
39
+ self.class.product_class.find_by_sku(sku)
40
+ end
41
+
42
+ [:size, :color, :quantity, :price, :name].each do |method|
43
+ defined = "product_#{method}".to_sym
44
+ default = [:quantity, :price].include?(method)? 0 : ''
45
+ method = :description if method == :name
46
+ define_method(defined) do |sku|
47
+ i = product
48
+ if i == nil
49
+ default
50
+ else
51
+ if i.instance_method(method).arity == 0
52
+ i.send(method)
53
+ elsif i.instance_method(method).arity == 0
54
+ i.send(method, sku)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def product_quantity=(value)
61
+ i = product
62
+ if i.respond_to?(:set_quantity)
63
+ i.set_quantity(value, sku)
64
+ else
65
+ i.quantity = value
66
+ end
67
+ i.save!
68
+ end
69
+
70
+ def to_liquid
71
+ OrderDrop.new(self)
72
+ end
73
+
74
+ private
75
+
76
+ def self.product_class
77
+ inventory_items_class
78
+ end
79
+ end
80
+ class OrderDrop < ::Liquid::Drop
81
+ def initialize(source)
82
+ @source = source
83
+ end
84
+
85
+ def id
86
+ @source.id.to_s
87
+ end
88
+
89
+ def price
90
+ "%0.2f" % @source.price.round(2)
91
+ end
92
+
93
+ delegate :size, :color, :sku, :product, :out_of_stock?, :quantity,
94
+ :product_quantity, :cart, to: :@source
95
+
96
+ protected
97
+ attr_accessor :source
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,148 @@
1
+ module Locomotive
2
+ module Ecommerce
3
+ class Purchase
4
+ include Mongoid::Document
5
+ include ::Mongoid::Timestamps
6
+ field :shipping_info, :type => Hash, :default => {}
7
+ field :shipping_method, :type => String
8
+ field :completed, :type => Boolean, :default => false
9
+ field :user_id, :type => ::BSON::ObjectId
10
+ field :transmitted, :type => Boolean, :default => false
11
+ field :stripe_token, :type => String
12
+ has_one :cart, :class_name => "::Locomotive::Ecommerce::Cart"
13
+
14
+ def complete
15
+ cart.orders.each { |order| order.product_quantity -= order.quantity }
16
+ end
17
+
18
+ def to_liquid
19
+ PurchaseDrop.new(self)
20
+ end
21
+
22
+ delegate :subtotal_est_tax, to: :cart
23
+
24
+ def shipping_estimate
25
+ unless @shipping_estimate
26
+ @shipping_estimate = 0.0
27
+ ct = Thread.current[:site].content_types.where(slug: Engine.config_or_default('shipping_model')).first
28
+ price_break = Engine.config_or_default('price_break').to_f
29
+ over_field = Engine.config_or_default('shipping_over_slug').to_sym
30
+ under_field = Engine.config_or_default('shipping_under_slug').to_sym
31
+ method = ct.ordered_entries.first
32
+ if self.cart.purchase_total > price_break
33
+ @shipping_estimate = method.send(over_field).to_f
34
+ else
35
+ @shipping_estimate = method.send(under_field).to_f
36
+ end
37
+ end
38
+ @shipping_estimate
39
+ end
40
+
41
+ def subtotal_est_shipping
42
+ subtotal_est_tax + shipping_estimate
43
+ end
44
+
45
+ def tax
46
+ if precent = tax_precentage
47
+ cart.purchase_total * (precent.to_f/100)
48
+ else
49
+ -1
50
+ end
51
+ end
52
+
53
+ def tax_precentage
54
+ unless @tax_precentage
55
+ ct = Thread.current[:site].content_types.where(slug: Engine.config_or_default('tax_model')).first
56
+ if ct
57
+ query_hash = {}
58
+ country_field = Engine.config_or_default('country_slug').to_sym
59
+ province_field = Engine.config_or_default('province_slug').to_sym
60
+ precentage_field = Engine.config_or_default('precentage_slug').to_sym
61
+ query_hash[country_field] = /#{shipping_info[country_field.to_s]}/i
62
+ query = ct.entries.where(query_hash)
63
+ if query.count == 1
64
+ @tax_precentage = query.first.send(precentage_field)
65
+ elsif query.count > 0
66
+ query_hash = {}
67
+ query_hash[province_field] = /#{shipping_info[province_field.to_s]}/i
68
+ query = query.and(query_hash)
69
+ if query.count > 0
70
+ @tax_precentage = query.first.send(precentage_field)
71
+ else
72
+ @tax_precentage = nil
73
+ end
74
+ else
75
+ @tax_precentage = nil
76
+ end
77
+ else
78
+ @tax_precentage = nil
79
+ end
80
+ end
81
+ @tax_precentage
82
+ end
83
+
84
+ def shipping
85
+ unless @shipping
86
+ @shipping = 0.0
87
+ ct = Thread.current[:site].content_types.where(slug: Engine.config_or_default('shipping_model')).first
88
+ price_break = Engine.config_or_default('price_break').to_f
89
+ name_field = Engine.config_or_default('shipping_name_slug').to_sym
90
+ over_field = Engine.config_or_default('shipping_over_slug').to_sym
91
+ under_field = Engine.config_or_default('shipping_under_slug').to_sym
92
+ query_hash = {}
93
+ query_hash[name_field] = self.shipping_method
94
+ method = ct.entries.where(query_hash).first
95
+ if self.cart.purchase_total > price_break
96
+ @shipping = method.send(over_field).to_f
97
+ else
98
+ @shipping = method.send(under_field).to_f
99
+ end
100
+ end
101
+ @shipping
102
+ end
103
+
104
+
105
+ def total
106
+ if tax >= 0
107
+ cart.purchase_total + tax + shipping + cart.extras_total
108
+ else
109
+ cart.purchase_total + shipping + cart.extras_total
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ class PurchaseDrop < ::Liquid::Drop
116
+ def initialize(source)
117
+ @source = source
118
+ end
119
+
120
+ def id
121
+ @source.id.to_s
122
+ end
123
+
124
+ def date
125
+ @source.updated_at
126
+ end
127
+
128
+ [:subtotal_est_tax, :shipping_estimate, :subtotal_est_shipping,
129
+ :shipping, :tax, :tax_precentage, :total].each do |method|
130
+ define_method("#{method.to_s}_value".to_sym) {@source.send(method).round(2)}
131
+ define_method(method) {"%0.2f" % @source.send(method).round(2)}
132
+ end
133
+
134
+ delegate :cart, :stripe_token, :completed, :shipping_info, to: :@source
135
+
136
+ def method_missing(meth, *args, &block)
137
+ if @source.shipping_info.key?(meth)
138
+ @source.shipping_info[meth]
139
+ else
140
+ super
141
+ end
142
+ end
143
+
144
+ protected
145
+ attr_accessor :source
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,4 @@
1
+ <div class="alert alert-error">
2
+ <button type="button" class="close" data-dismiss="alert">x</button>
3
+ &nbsp;<%= message %>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="alert alert-error">
2
+ <button type="button" class="close" data-dismiss="alert">x</button>
3
+ &nbsp;<%= message %>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="alert alert-info">
2
+ <button type="button" class="close" data-dismiss="alert">x</button>
3
+ &nbsp;<%= message %>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="alert alert-success">
2
+ <button type="button" class="close" data-dismiss="alert">x</button>
3
+ &nbsp;<%= message %>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="alert alert-success">
2
+ <button type="button" class="close" data-dismiss="alert">x</button>
3
+ &nbsp;<%= message %>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="alert alert-warning">
2
+ <button type="button" class="close" data-dismiss="alert">x</button>
3
+ &nbsp;<%= message %>
4
+ </div>
@@ -0,0 +1,11 @@
1
+ <%# Link to the "First" page
2
+ - available local variables
3
+ url: url to the first page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <span class="first">
10
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote %>
11
+ </span>
@@ -0,0 +1,8 @@
1
+ <%# Non-link tag that stands for skipped pages...
2
+ - available local variables
3
+ current_page: a page object for the currently displayed page
4
+ total_pages: total number of pages
5
+ per_page: number of items to fetch per page
6
+ remote: data-remote
7
+ -%>
8
+ <span class="page gap"><%= raw(t 'views.pagination.truncate') %></span>
@@ -0,0 +1,11 @@
1
+ <%# Link to the "Last" page
2
+ - available local variables
3
+ url: url to the last page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <span class="last">
10
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
11
+ </span>
@@ -0,0 +1,11 @@
1
+ <%# Link to the "Next" page
2
+ - available local variables
3
+ url: url to the next page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <span class="next">
10
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote %>
11
+ </span>
@@ -0,0 +1,12 @@
1
+ <%# Link showing page number
2
+ - available local variables
3
+ page: a page object for "this" page
4
+ url: url to this page
5
+ current_page: a page object for the currently displayed page
6
+ total_pages: total number of pages
7
+ per_page: number of items to fetch per page
8
+ remote: data-remote
9
+ -%>
10
+ <span class="page<%= ' current' if page.current? %>">
11
+ <%= link_to_unless page.current?, page, url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil} %>
12
+ </span>
@@ -0,0 +1,23 @@
1
+ <%# The container tag
2
+ - available local variables
3
+ current_page: a page object for the currently displayed page
4
+ total_pages: total number of pages
5
+ per_page: number of items to fetch per page
6
+ remote: data-remote
7
+ paginator: the paginator that renders the pagination tags inside
8
+ -%>
9
+ <%= paginator.render do -%>
10
+ <nav class="pagination">
11
+ <%= first_page_tag unless current_page.first? %>
12
+ <%= prev_page_tag unless current_page.first? %>
13
+ <% each_page do |page| -%>
14
+ <% if page.left_outer? || page.right_outer? || page.inside_window? -%>
15
+ <%= page_tag page %>
16
+ <% elsif !page.was_truncated? -%>
17
+ <%= gap_tag %>
18
+ <% end -%>
19
+ <% end -%>
20
+ <%= next_page_tag unless current_page.last? %>
21
+ <%= last_page_tag unless current_page.last? %>
22
+ </nav>
23
+ <% end -%>
@@ -0,0 +1,11 @@
1
+ <%# Link to the "Previous" page
2
+ - available local variables
3
+ url: url to the previous page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <span class="prev">
10
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote %>
11
+ </span>
@@ -0,0 +1,11 @@
1
+ Hello,
2
+
3
+ Thank you for placing an order at <%= @shop_name %>.
4
+
5
+ Here is a summary of the order you placed:
6
+
7
+ Qty. Product<% for item in @purchase.cart.orders %>
8
+ <%= item.quantity%> <%= item.product.description %>
9
+ <% end%>
10
+
11
+ If you have any questions please contact <%= @contact %>
@@ -0,0 +1 @@
1
+ ActiveResource::Base.logger = Rails.logger
@@ -0,0 +1,16 @@
1
+ Kaminari::Helpers::Tag.class_eval do
2
+ def to_s(locals = {}) #:nodoc:
3
+ @template.render :partial => "../views/kaminari/#{@theme}#{self.class.name.demodulize.underscore}", :locals => @options.merge(locals)
4
+ end
5
+ end
6
+
7
+ Kaminari.configure do |config|
8
+ # config.default_per_page = 25
9
+ # config.max_per_page = nil
10
+ # config.window = 4
11
+ # config.outer_window = 0
12
+ # config.left = 0
13
+ # config.right = 0
14
+ # config.page_method_name = :page
15
+ # config.param_name = :page
16
+ end
@@ -0,0 +1,7 @@
1
+ module Liquid
2
+ class Context
3
+ def self.new(e = {}, s = {}, r = {}, err = true)
4
+ super(e, s, r, true)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ require 'stripe'
2
+ require 'stripe_helper'
3
+
4
+ module Locomotive
5
+ module Ecommerce
6
+ class StripeConfigurationHelper
7
+ include EcommerceHelper
8
+ end
9
+
10
+ ::StripeHelper.configure do |config|
11
+ helper = StripeConfigurationHelper.new
12
+
13
+ amt_proc = lambda do |controller, token|
14
+ purchase = Purchase.where(_id: token).first
15
+ return nil if !purchase
16
+ (purchase.total.round(2)*100).to_i
17
+ end
18
+
19
+ failure_proc = lambda do |controller, token, msg|
20
+ purchase = Purchase.where(_id: token).first
21
+ controller.flash[:error] = msg
22
+ helper.checkout_path
23
+ end
24
+
25
+ success_proc = lambda do |controller, token, stripe|
26
+ purchase = Purchase.where(_id: token).first
27
+ PurchaseController.complete(token,
28
+ helper.current_user(controller),
29
+ helper.current_user_cart(controller),
30
+ stripe)
31
+ controller.flash[:notice] = "Thank you for your purchase."
32
+ helper.post_checkout_path
33
+ end
34
+
35
+ config.charge_amount = amt_proc
36
+ config.charge_failure = failure_proc
37
+ config.charge_success = success_proc
38
+ end
39
+ end
40
+ end