piggybak 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +18 -0
- data/README.rdoc +19 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/app/assets/javascripts/piggybak.js +72 -0
- data/app/controllers/piggybak/cart_controller.rb +29 -0
- data/app/controllers/piggybak/orders_controller.rb +86 -0
- data/app/mailers/piggybak/notifier.rb +10 -0
- data/app/models/piggybak/address.rb +23 -0
- data/app/models/piggybak/cart.rb +77 -0
- data/app/models/piggybak/line_item.rb +31 -0
- data/app/models/piggybak/order.rb +141 -0
- data/app/models/piggybak/payment.rb +80 -0
- data/app/models/piggybak/payment_calculator.rb +7 -0
- data/app/models/piggybak/payment_calculator/authorize_net.rb +6 -0
- data/app/models/piggybak/payment_calculator/fake.rb +24 -0
- data/app/models/piggybak/payment_method.rb +40 -0
- data/app/models/piggybak/payment_method_value.rb +11 -0
- data/app/models/piggybak/product.rb +19 -0
- data/app/models/piggybak/shipment.rb +21 -0
- data/app/models/piggybak/shipping_calculator.rb +12 -0
- data/app/models/piggybak/shipping_calculator/flat_rate.rb +13 -0
- data/app/models/piggybak/shipping_calculator/pickup.rb +23 -0
- data/app/models/piggybak/shipping_calculator/range.rb +16 -0
- data/app/models/piggybak/shipping_method.rb +55 -0
- data/app/models/piggybak/shipping_method_value.rb +11 -0
- data/app/models/piggybak/state.rb +4 -0
- data/app/models/piggybak/tax_calculator.rb +7 -0
- data/app/models/piggybak/tax_calculator/flat_rate.rb +33 -0
- data/app/models/piggybak/tax_method.rb +43 -0
- data/app/models/piggybak/tax_method_value.rb +11 -0
- data/app/views/piggybak/cart/_form.html.erb +18 -0
- data/app/views/piggybak/cart/_items.html.erb +68 -0
- data/app/views/piggybak/cart/show.html.erb +13 -0
- data/app/views/piggybak/notifier/order_notification.text.erb +11 -0
- data/app/views/piggybak/orders/_address_form.html.erb +24 -0
- data/app/views/piggybak/orders/list.html.erb +12 -0
- data/app/views/piggybak/orders/receipt.html.erb +53 -0
- data/app/views/piggybak/orders/show.html.erb +112 -0
- data/app/views/rails_admin/main/_actions.html.erb +5 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20111227150106_create_orders.rb +21 -0
- data/db/migrate/20111227150322_create_addresses.rb +15 -0
- data/db/migrate/20111227150432_create_line_items.rb +10 -0
- data/db/migrate/20111227213558_create_products.rb +14 -0
- data/db/migrate/20111228231756_create_shipping_methods.rb +9 -0
- data/db/migrate/20111228231806_create_payment_methods.rb +11 -0
- data/db/migrate/20111228231829_create_payments.rb +17 -0
- data/db/migrate/20111228231838_create_shipments.rb +12 -0
- data/db/migrate/20111228235852_create_shipping_method_values.rb +9 -0
- data/db/migrate/20111228235853_create_payment_method_values.rb +9 -0
- data/db/migrate/20120102154050_create_tax_methods.rb +9 -0
- data/db/migrate/20120102162415_create_states.rb +8 -0
- data/db/migrate/20120102162703_create_tax_method_values.rb +9 -0
- data/lib/acts_as_orderer/base.rb +29 -0
- data/lib/acts_as_product/base.rb +32 -0
- data/lib/application_helper.rb +12 -0
- data/lib/currency.rb +5 -0
- data/lib/piggybak.rb +295 -0
- data/piggybak.gemspec +115 -0
- metadata +213 -0
data/.document
ADDED
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,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
|