piggybak 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.document +5 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +18 -0
  4. data/README.rdoc +19 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/app/assets/javascripts/piggybak.js +72 -0
  8. data/app/controllers/piggybak/cart_controller.rb +29 -0
  9. data/app/controllers/piggybak/orders_controller.rb +86 -0
  10. data/app/mailers/piggybak/notifier.rb +10 -0
  11. data/app/models/piggybak/address.rb +23 -0
  12. data/app/models/piggybak/cart.rb +77 -0
  13. data/app/models/piggybak/line_item.rb +31 -0
  14. data/app/models/piggybak/order.rb +141 -0
  15. data/app/models/piggybak/payment.rb +80 -0
  16. data/app/models/piggybak/payment_calculator.rb +7 -0
  17. data/app/models/piggybak/payment_calculator/authorize_net.rb +6 -0
  18. data/app/models/piggybak/payment_calculator/fake.rb +24 -0
  19. data/app/models/piggybak/payment_method.rb +40 -0
  20. data/app/models/piggybak/payment_method_value.rb +11 -0
  21. data/app/models/piggybak/product.rb +19 -0
  22. data/app/models/piggybak/shipment.rb +21 -0
  23. data/app/models/piggybak/shipping_calculator.rb +12 -0
  24. data/app/models/piggybak/shipping_calculator/flat_rate.rb +13 -0
  25. data/app/models/piggybak/shipping_calculator/pickup.rb +23 -0
  26. data/app/models/piggybak/shipping_calculator/range.rb +16 -0
  27. data/app/models/piggybak/shipping_method.rb +55 -0
  28. data/app/models/piggybak/shipping_method_value.rb +11 -0
  29. data/app/models/piggybak/state.rb +4 -0
  30. data/app/models/piggybak/tax_calculator.rb +7 -0
  31. data/app/models/piggybak/tax_calculator/flat_rate.rb +33 -0
  32. data/app/models/piggybak/tax_method.rb +43 -0
  33. data/app/models/piggybak/tax_method_value.rb +11 -0
  34. data/app/views/piggybak/cart/_form.html.erb +18 -0
  35. data/app/views/piggybak/cart/_items.html.erb +68 -0
  36. data/app/views/piggybak/cart/show.html.erb +13 -0
  37. data/app/views/piggybak/notifier/order_notification.text.erb +11 -0
  38. data/app/views/piggybak/orders/_address_form.html.erb +24 -0
  39. data/app/views/piggybak/orders/list.html.erb +12 -0
  40. data/app/views/piggybak/orders/receipt.html.erb +53 -0
  41. data/app/views/piggybak/orders/show.html.erb +112 -0
  42. data/app/views/rails_admin/main/_actions.html.erb +5 -0
  43. data/config/routes.rb +15 -0
  44. data/db/migrate/20111227150106_create_orders.rb +21 -0
  45. data/db/migrate/20111227150322_create_addresses.rb +15 -0
  46. data/db/migrate/20111227150432_create_line_items.rb +10 -0
  47. data/db/migrate/20111227213558_create_products.rb +14 -0
  48. data/db/migrate/20111228231756_create_shipping_methods.rb +9 -0
  49. data/db/migrate/20111228231806_create_payment_methods.rb +11 -0
  50. data/db/migrate/20111228231829_create_payments.rb +17 -0
  51. data/db/migrate/20111228231838_create_shipments.rb +12 -0
  52. data/db/migrate/20111228235852_create_shipping_method_values.rb +9 -0
  53. data/db/migrate/20111228235853_create_payment_method_values.rb +9 -0
  54. data/db/migrate/20120102154050_create_tax_methods.rb +9 -0
  55. data/db/migrate/20120102162415_create_states.rb +8 -0
  56. data/db/migrate/20120102162703_create_tax_method_values.rb +9 -0
  57. data/lib/acts_as_orderer/base.rb +29 -0
  58. data/lib/acts_as_product/base.rb +32 -0
  59. data/lib/application_helper.rb +12 -0
  60. data/lib/currency.rb +5 -0
  61. data/lib/piggybak.rb +295 -0
  62. data/piggybak.gemspec +115 -0
  63. metadata +213 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ # gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,18 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.4)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.9.2.2)
10
+ rcov (0.9.11)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ bundler (~> 1.0.0)
17
+ jeweler (~> 1.6.4)
18
+ rcov
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = piggybak
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to piggybak
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Steph Skardal. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "piggybak"
18
+ gem.homepage = "http://github.com/stephskardal/piggybak"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Mountable ecommerce}
21
+ gem.description = %Q{Mountable ecommerce}
22
+ gem.email = "steph@endpoint.com"
23
+ gem.authors = ["Steph Skardal"]
24
+
25
+ gem.add_dependency "rails_admin"
26
+ gem.add_dependency "devise"
27
+ gem.add_dependency "activemerchant"
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ require 'rcov/rcovtask'
39
+ Rcov::RcovTask.new do |test|
40
+ test.libs << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ test.rcov_opts << '--exclude "gems/*"'
44
+ end
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "piggybak #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,72 @@
1
+ var tax_total = 0;
2
+
3
+ $(function() {
4
+ piggybak.update_shipping_options($('#piggybak_order_shipping_address_attributes_state_id'));
5
+ piggybak.initialize_listeners();
6
+ piggybak.update_tax();
7
+ });
8
+
9
+ var piggybak = {
10
+ initialize_listeners: function() {
11
+ $('#piggybak_order_shipping_address_attributes_state_id').change(function() {
12
+ piggybak.update_shipping_options($(this));
13
+ });
14
+ $('#piggybak_order_billing_address_attributes_state_id').change(function() {
15
+ piggybak.update_tax();
16
+ });
17
+ $('#shipping select').change(function() {
18
+ piggybak.update_totals();
19
+ });
20
+ return;
21
+ },
22
+ update_shipping_options: function(field) {
23
+ var shipping_field = $('#piggybak_order_shipments_attributes_0_shipping_method_id');
24
+ shipping_field.hide();
25
+ var shipping_data = {};
26
+ $('#shipping_address input, #shipping_address select').each(function(i, j) {
27
+ var id = $(j).attr('id');
28
+ id = id.replace("piggybak_order_shipping_address_attributes_", '');
29
+ shipping_data[id] = $(j).val();
30
+ });
31
+ $.ajax({
32
+ url: shipping_lookup,
33
+ cached: false,
34
+ data: shipping_data,
35
+ dataType: "JSON",
36
+ success: function(data) {
37
+ shipping_field.find('option').remove();
38
+ $.each(data, function(i, j) {
39
+ shipping_field.append($('<option>').html(j.label).val(j.id).data('rate', j.rate));
40
+ });
41
+ shipping_field.show();
42
+ piggybak.update_totals();
43
+ }
44
+ });
45
+ },
46
+ update_tax: function(field) {
47
+ var billing_data = {};
48
+ $('#billing_address input, #billing_address select').each(function(i, j) {
49
+ var id = $(j).attr('id');
50
+ id = id.replace("piggybak_order_billing_address_attributes_", '');
51
+ billing_data[id] = $(j).val();
52
+ });
53
+ $.ajax({
54
+ url: tax_lookup,
55
+ cached: false,
56
+ data: billing_data,
57
+ dataType: "JSON",
58
+ success: function(data) {
59
+ tax_total = data.tax;
60
+ piggybak.update_totals();
61
+ }
62
+ });
63
+ },
64
+ update_totals: function() {
65
+ var subtotal = $('#subtotal_total').data('total');
66
+ $('#tax_total').html('$' + tax_total.toFixed(2));
67
+ var shipping_total = $('#shipping select option:selected').data('rate');
68
+ $('#shipping_total').html('$' + shipping_total.toFixed(2));
69
+ var order_total = subtotal + tax_total + shipping_total;
70
+ $('#order_total').html('$' + order_total.toFixed(2));
71
+ }
72
+ };
@@ -0,0 +1,29 @@
1
+ module Piggybak
2
+ class CartController < ApplicationController
3
+ def show
4
+ @cart = Cart.new(cookies["cart"])
5
+ @cart.update_quantities
6
+ cookies["cart"] = { :value => @cart.to_cookie, :path => '/' }
7
+ end
8
+
9
+ def add
10
+ cookies["cart"] = { :value => Cart.add(cookies["cart"], params), :path => '/' }
11
+ redirect_to piggybak.cart_url
12
+ end
13
+
14
+ def remove
15
+ response.set_cookie("cart", { :value => Cart.remove(cookies["cart"], params[:item]), :path => '/' })
16
+ redirect_to piggybak.cart_url
17
+ end
18
+
19
+ def clear
20
+ cookies["cart"] = { :value => '', :path => '/' }
21
+ redirect_to piggybak.cart_url
22
+ end
23
+
24
+ def update
25
+ cookies["cart"] = { :value => Cart.update(cookies["cart"], params), :path => '/' }
26
+ redirect_to piggybak.cart_url
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,86 @@
1
+ module Piggybak
2
+ class OrdersController < ApplicationController
3
+ def show
4
+ @cart = Piggybak::Cart.new(request.cookies["cart"])
5
+ @order = Piggybak::Order.new
6
+
7
+ if current_user
8
+ @order.user = current_user
9
+ @order.email = current_user.email
10
+ end
11
+
12
+ @order.billing_address ||= Piggybak::Address.new
13
+ @order.shipping_address ||= Piggybak::Address.new
14
+
15
+ @order.shipments ||= [Piggybak::Shipment.new]
16
+
17
+ @order.payments ||= [Piggybak::Payment.new]
18
+ end
19
+
20
+ def submit
21
+ begin
22
+ ActiveRecord::Base.transaction do
23
+ @order = Piggybak::Order.new(params[:piggybak_order])
24
+ @order.user = current_user if current_user
25
+ @order.payments.first.payment_method_id = Piggybak::PaymentMethod.find_by_active(true).id
26
+
27
+ cart = Piggybak::Cart.new(request.cookies["cart"])
28
+ @order.add_line_items(cart)
29
+
30
+ if @order.save
31
+ Piggybak::Notifier.order_notification(@order)
32
+
33
+ cookies["cart"] = { :value => '', :path => '/' }
34
+ session[:last_order] = @order.id
35
+ redirect_to piggybak.receipt_url
36
+ else
37
+ raise Exception, @order.errors.full_messages
38
+ end
39
+ end
40
+ rescue Exception => e
41
+ @message = e.message
42
+ @cart = Piggybak::Cart.new(request.cookies["cart"])
43
+
44
+ if current_user
45
+ @order.user = current_user
46
+ @order.email = current_user.email
47
+ end
48
+
49
+ @user = current_user
50
+
51
+ render "piggybak/orders/show"
52
+ end
53
+ end
54
+
55
+ def receipt
56
+ @order = Piggybak::Order.find(session[:last_order])
57
+ end
58
+
59
+ def list
60
+ @user = current_user
61
+ redirect_to root if @user.nil?
62
+ end
63
+
64
+ def email
65
+ order = Order.find(params[:id])
66
+ Piggybak::Notifier.order_notification(order)
67
+ flash[:notice] = "Email notification sent."
68
+
69
+ redirect_to rails_admin.edit_path('Piggybak::Order', order.id)
70
+ end
71
+
72
+ def shipping
73
+ cart = Piggybak::Cart.new(request.cookies["cart"])
74
+ cart.extra_data = params
75
+ shipping_methods = Piggybak::ShippingMethod.lookup_methods(cart)
76
+ render :json => shipping_methods
77
+ end
78
+
79
+ def tax
80
+ cart = Piggybak::Cart.new(request.cookies["cart"])
81
+ cart.extra_data = params
82
+ total_tax = Piggybak::TaxMethod.calculate_tax(cart)
83
+ render :json => { :tax => total_tax }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,10 @@
1
+ module Piggybak
2
+ class Notifier < ActionMailer::Base
3
+ def order_notification(order)
4
+ @order = order
5
+
6
+ mail(:to => order.email,
7
+ :subject => "Order ##{@order.id}")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ module Piggybak
2
+ class Address < ActiveRecord::Base
3
+ belongs_to :state
4
+
5
+ validates_presence_of :firstname
6
+ validates_presence_of :lastname
7
+ validates_presence_of :address1
8
+ validates_presence_of :city
9
+ validates_presence_of :state_id
10
+ validates_presence_of :zip
11
+
12
+ def admin_label
13
+ address = "#{self.firstname} #{self.lastname}<br />"
14
+ address += "#{self.address1}<br />"
15
+ if self.address2 && self.address2 != ''
16
+ address += "#{self.address2}<br />"
17
+ end
18
+ address += "#{self.city}, #{self.state.abbr} #{self.zip}"
19
+ address
20
+ end
21
+ alias :display :admin_label
22
+ end
23
+ end
@@ -0,0 +1,77 @@
1
+ module Piggybak
2
+ class Cart
3
+ attr_accessor :items
4
+ attr_accessor :total
5
+ attr_accessor :errors
6
+ attr_accessor :extra_data
7
+
8
+ def initialize(cookie='')
9
+ self.items = []
10
+ self.errors = []
11
+ cookie ||= ''
12
+ cookie.split(';').each do |item|
13
+ self.items << { :product => Piggybak::Product.find(item.split(':')[0]), :quantity => (item.split(':')[1]).to_i }
14
+ end
15
+ self.total = self.items.sum { |item| item[:quantity]*item[:product].price }
16
+ end
17
+
18
+ def self.to_hash(cookie)
19
+ cookie ||= ''
20
+ cookie.split(';').inject({}) do |hash, item|
21
+ hash[item.split(':')[0]] = (item.split(':')[1]).to_i
22
+ hash
23
+ end
24
+ end
25
+
26
+ def self.to_string(cart)
27
+ cookie = ''
28
+ cart.each do |k, v|
29
+ cookie += "#{k.to_s}:#{v.to_s};" if v.to_i > 0
30
+ end
31
+ cookie
32
+ end
33
+
34
+ def self.add(cookie, params)
35
+ cart = to_hash(cookie)
36
+ cart["#{params[:product_id]}"] ||= 0
37
+ cart["#{params[:product_id]}"] += params[:quantity].to_i
38
+ to_string(cart)
39
+ end
40
+
41
+ def self.remove(cookie, product_id)
42
+ cart = to_hash(cookie)
43
+ cart[product_id] = 0
44
+ to_string(cart)
45
+ end
46
+
47
+ def self.update(cookie, params)
48
+ cart = to_hash(cookie)
49
+ cart.each { |k, v| cart[k] = params[:quantity][k].to_i }
50
+ to_string(cart)
51
+ end
52
+
53
+ def to_cookie
54
+ cookie = ''
55
+ self.items.each do |item|
56
+ cookie += "#{item[:product].id.to_s}:#{item[:quantity].to_s};" if item[:quantity].to_i > 0
57
+ end
58
+ cookie
59
+ end
60
+
61
+ def update_quantities
62
+ self.errors = []
63
+ new_items = []
64
+ self.items.each do |item|
65
+ if item[:product].unlimited_inventory || item[:product].quantity >= item[:quantity]
66
+ new_items << item
67
+ else
68
+ self.errors << ["Adjusting quantity for #{item[:product].description}"]
69
+ item[:quantity] = item[:product].quantity
70
+ new_items << item
71
+ end
72
+ end
73
+ self.items = new_items
74
+ self.total = self.items.sum { |item| item[:quantity]*item[:product].price }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,31 @@
1
+ module Piggybak
2
+ class LineItem < ActiveRecord::Base
3
+ belongs_to :order
4
+ belongs_to :product
5
+
6
+ validates_presence_of :product_id
7
+ validates_presence_of :total
8
+ validates_presence_of :quantity
9
+ validates_numericality_of :quantity, :only_integer => true, :greater_than_or_equal_to => 0
10
+
11
+ after_create :decrease_inventory
12
+ after_destroy :increase_inventory
13
+ after_update :update_inventory
14
+
15
+ def admin_label
16
+ "#{self.quantity} x #{self.product.description}"
17
+ end
18
+
19
+ def decrease_inventory
20
+ self.product.update_inventory(-1 * self.quantity)
21
+ end
22
+
23
+ def increase_inventory
24
+ self.product.update_inventory(self.quantity)
25
+ end
26
+
27
+ def update_inventory
28
+ Rails.logger.warn "steph inside update inventory: #{self.inspect}"
29
+ end
30
+ end
31
+ end