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
@@ -0,0 +1,11 @@
1
+ module Piggybak
2
+ class ShippingMethodValue < ActiveRecord::Base
3
+ belongs_to :shipping_method
4
+ validates_presence_of :key
5
+ validates_presence_of :value
6
+
7
+ def admin_label
8
+ "#{self.key} - #{self.value}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Piggybak
2
+ class State < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Piggybak
2
+ class TaxCalculator
3
+ def self.available?(*args)
4
+ false
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Piggybak
2
+ class TaxCalculator::FlatRate < TaxCalculator
3
+ KEYS = ["state_abbr", "rate"]
4
+
5
+ def self.available?(method, object)
6
+ abbr = method.metadata.detect { |t| t.key == "state_abbr" }.value
7
+
8
+ if object.is_a?(Cart)
9
+ state = State.find(object.extra_data["state_id"])
10
+ return state.abbr == abbr
11
+ else
12
+ if object.billing_address && object.billing_address.state
13
+ return object.billing_address.state.abbr == abbr
14
+ end
15
+ end
16
+ return false
17
+ end
18
+
19
+ def self.rate(method, object)
20
+ taxable_total = 0
21
+
22
+ if object.is_a?(Cart)
23
+ taxable_total = object.total
24
+ else
25
+ object.line_items.each do |line_item|
26
+ taxable_total = line_item.total
27
+ end
28
+ end
29
+
30
+ (method.metadata.detect { |m| m.key == "rate" }.value.to_f * taxable_total).to_c
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ module Piggybak
2
+ class TaxMethod < ActiveRecord::Base
3
+ has_many :tax_method_values, :dependent => :destroy
4
+ alias :metadata :tax_method_values
5
+
6
+ validates_presence_of :description
7
+ validates_presence_of :klass
8
+
9
+ accepts_nested_attributes_for :tax_method_values, :allow_destroy => true
10
+
11
+ validates_each :tax_method_values do |record, attr, value|
12
+ if record.klass
13
+ calculator = record.klass.constantize
14
+ metadata_keys = value.collect { |v| v.key }.sort
15
+ if calculator::KEYS.sort != metadata_keys
16
+ record.errors.add attr, "You must define key values for #{calculator::KEYS.join(', ')} for this tax method."
17
+ end
18
+ end
19
+ end
20
+
21
+ def klass_enum
22
+ #TODO: Troubleshoot use of subclasses here instead
23
+ [Piggybak::TaxCalculator::FlatRate]
24
+ end
25
+
26
+ def self.calculate_tax(object)
27
+ total_tax = 0
28
+
29
+ TaxMethod.all.each do |tax_method|
30
+ calculator = tax_method.klass.constantize
31
+ if calculator.available?(tax_method, object)
32
+ total_tax += calculator.rate(tax_method, object)
33
+ end
34
+ end
35
+
36
+ total_tax
37
+ end
38
+
39
+ def admin_label
40
+ self.description
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ module Piggybak
2
+ class TaxMethodValue < ActiveRecord::Base
3
+ belongs_to :tax_method
4
+ validates_presence_of :key
5
+ validates_presence_of :value
6
+
7
+ def admin_label
8
+ "#{self.key} - #{self.value}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ <% if object.reflections.keys.include?(:piggybak_product) -%>
2
+ <% if object.piggybak_product && object.piggybak_product.active -%>
3
+ <% if object.piggybak_product.quantity > 0 -%>
4
+ <%= form_tag piggybak.cart_add_url do -%>
5
+ <div id="product_details">
6
+ <span id="title"><%= object.piggybak_product.description %></span>
7
+ <span id="price"><%= number_to_currency object.piggybak_product.price %></span>
8
+ </div>
9
+ <label>Quantity</label>
10
+ <%= text_field_tag :quantity %>
11
+ <%= hidden_field_tag :product_id, object.piggybak_product.id %>
12
+ <%= submit_tag "Add to Cart", :id => "submit" %>
13
+ <% end -%>
14
+ <% else -%>
15
+ Sorry, out of stock.
16
+ <% end -%>
17
+ <% end -%>
18
+ <% end -%>
@@ -0,0 +1,68 @@
1
+ <% if @cart.items.any? -%>
2
+ <%= form_tag piggybak.cart_update_url do -%>
3
+ <table cellpadding="5" cellspacing="0" width="100%">
4
+ <tr>
5
+ <th>Item</th>
6
+ <th>Price</th>
7
+ <th>Quantity</th>
8
+ <th>Subtotal</th>
9
+ <% if page == "cart" -%>
10
+ <th></th>
11
+ <% end -%>
12
+ </tr>
13
+ <% @cart.items.each do |item| %>
14
+ <tr>
15
+ <td><%= item[:product].description %></td>
16
+ <td><%= number_to_currency item[:product].price %></td>
17
+ <td>
18
+ <% if page == "cart" -%>
19
+ <%= text_field_tag "quantity[#{item[:product].id}]", item[:quantity] %>
20
+ <% else -%>
21
+ <%= item[:quantity] %>
22
+ <% end -%>
23
+ </td>
24
+ <td><%= number_to_currency item[:quantity]*item[:product].price %></td>
25
+ <% if page == "cart" -%>
26
+ <td>
27
+ <%= link_to "Remove", piggybak.remove_item_url(item[:product].id), :method => :delete %>
28
+ </td>
29
+ <% end -%>
30
+ </tr>
31
+ <% end -%>
32
+ <tr>
33
+ <td colspan="<%= page == "cart" ? "5" : "4" %>"></td>
34
+ </tr>
35
+ <tr>
36
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
37
+ <td>Subtotal</td>
38
+ <td id="subtotal_total" data-total="<%= @cart.total %>"><%= number_to_currency @cart.total %></td>
39
+ </tr>
40
+ <% if page != "cart" -%>
41
+ <tr>
42
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
43
+ <td>Tax</td>
44
+ <td id="tax_total"></td>
45
+ </tr>
46
+ <tr>
47
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
48
+ <td>Shipping</td>
49
+ <td id="shipping_total"></td>
50
+ </tr>
51
+ <tr>
52
+ <td colspan="<%= page == "cart" ? "3" : "2" %>"></td>
53
+ <td>Total</td>
54
+ <td id="order_total"></td>
55
+ </tr>
56
+ <% end -%>
57
+ </table>
58
+
59
+ <% if page == "cart" %>
60
+ <%= submit_tag "Update", :id => "update" %>
61
+ <% end -%>
62
+
63
+ <% end -%>
64
+ <% else -%>
65
+ <p id="emtpy">
66
+ Your cart is empty.
67
+ </p>
68
+ <% end -%>
@@ -0,0 +1,13 @@
1
+ <div id="cart">
2
+
3
+ <h1>Shopping Cart</h1>
4
+ <% if @cart.errors.any? -%>
5
+ <%= raw @cart.errors.join('<br />') %>
6
+ <% end -%>
7
+
8
+ <%= render "items", :page => "cart" %>
9
+
10
+ <% if @cart.items.any? -%>
11
+ <%= link_to "Proceed to Checkout", piggybak.orders_url, :id => "checkout" %>
12
+ <% end -%>
13
+ </div>
@@ -0,0 +1,11 @@
1
+ Thanks for your order!
2
+
3
+ Items
4
+
5
+ <% @order.line_items.each do |line_item| %>
6
+ <%= line_item.product.description %> x <%= line_item.quantity %> = <%= number_to_currency line_item.total %>
7
+ <% end -%>
8
+
9
+ Total
10
+
11
+ <%= number_to_currency @order.total %>
@@ -0,0 +1,24 @@
1
+ <p>
2
+ <%= address.label :firstname %>
3
+ <%= address.text_field :firstname %>
4
+ </p>
5
+ <p>
6
+ <%= address.label :lastname %>
7
+ <%= address.text_field :lastname %>
8
+ </p>
9
+ <p>
10
+ <%= address.label :address1 %>
11
+ <%= address.text_field :address1 %>
12
+ </p>
13
+ <p>
14
+ <%= address.label :city %>
15
+ <%= address.text_field :city %>
16
+ </p>
17
+ <p>
18
+ <%= address.label :state %>
19
+ <%= address.select :state_id, ::Piggybak::State.all.collect {|p| [ p.name, p.id ] }, {:include_blank => false} %>
20
+ </p>
21
+ <p>
22
+ <%= address.label :zip %>
23
+ <%= address.text_field :zip %>
24
+ </p>
@@ -0,0 +1,12 @@
1
+ <h1>Your Orders</h1>
2
+
3
+ <% @user.piggybak_orders.each do |order| -%>
4
+ <h2>Order #<%= order.id %></h2>
5
+
6
+ <p><%= number_to_currency order.total %></p>
7
+
8
+ <% order.line_items.each do |line_item| -%>
9
+ <%= debug(line_item) %>
10
+ <% end -%>
11
+
12
+ <% end -%>
@@ -0,0 +1,53 @@
1
+ <div id="receipt">
2
+ <h1>Thanks for your Order</h1>
3
+
4
+ <table id="items" cellpadding="5" cellspacing="0">
5
+ <tr>
6
+ <th>Item</th>
7
+ <th>Price</th>
8
+ <th>Quantity</th>
9
+ <th>Subtotal</th>
10
+ </tr>
11
+ <% @order.line_items.each do |line_item| %>
12
+ <tr>
13
+ <td><%= line_item.product.description %></td>
14
+ <td><%= line_item.product.price %></td>
15
+ <td><%= line_item.quantity %></td>
16
+ <td><%= number_to_currency line_item.total %></td>
17
+ </tr>
18
+ <% end -%>
19
+ <tr>
20
+ <td colspan="4"></td>
21
+ </tr>
22
+ <tr>
23
+ <td colspan="2"></td>
24
+ <td>Subtotal</td>
25
+ <td><%= number_to_currency @order.line_items.inject(0) { |subtotal, li| subtotal + li.total } %></td>
26
+ </tr>
27
+ <tr>
28
+ <td colspan="2"></td>
29
+ <td>Tax</td>
30
+ <td><%= number_to_currency @order.tax_charge %></td>
31
+ </tr>
32
+ <tr>
33
+ <td colspan="2"></td>
34
+ <td>Shipping</td>
35
+ <td><%= number_to_currency @order.shipments.inject(0) { |shipping, shipment| shipping + shipment.total } %></td>
36
+ </tr>
37
+ <tr>
38
+ <td colspan="2"></td>
39
+ <td>Total</td>
40
+ <td><%= number_to_currency @order.total %></td>
41
+ </tr>
42
+ </table>
43
+
44
+ Email: <%= @order.email %><br />
45
+ Phone: <%= @order.phone %>
46
+
47
+ <h3>Billing Information</h3>
48
+ <%= raw @order.billing_address.display %>
49
+
50
+ <h3>Shipping Information</h3>
51
+ <%= raw @order.shipping_address.display %>
52
+
53
+ </div>
@@ -0,0 +1,112 @@
1
+ <div id="checkout">
2
+
3
+ <h1>Checkout</h1>
4
+ <%= form_for @order, :url => piggybak.order_submit_url, :method => "POST" do |f| %>
5
+
6
+ <% if @order.errors.any? -%>
7
+ <b>You have errors with your submission:</b><br />
8
+ <%= raw @order.errors.full_messages.join("<br />") %>
9
+ <% end -%>
10
+ <div class="clear"></div>
11
+
12
+ <div id="billing_address">
13
+ <h3>Billing Address</h3>
14
+ <%= f.fields_for :billing_address do |billing_address| %>
15
+ <%= render "address_form", :address => billing_address %>
16
+ <% end -%>
17
+ </div>
18
+ <div id="shipping_address">
19
+ <h3>Shipping Address</h3>
20
+ <%= f.fields_for :shipping_address do |shipping_address| %>
21
+ <%= render "address_form", :address => shipping_address %>
22
+ <% end -%>
23
+ </div>
24
+
25
+ <div id="add_details">
26
+ <div id="user_details">
27
+ <h3>User Details</h3>
28
+ <p>
29
+ <% if current_user -%>
30
+ <%= f.label :email %>
31
+ <%= f.text_field :email, { :readonly => true, :class => "readonly" } %>
32
+ <% if false -%>
33
+ <span>or <%= link_to 'LOGOUT', destroy_user_session_path, :method => :delete, :class => "last" %></span>
34
+ <% end -%>
35
+ <% else -%>
36
+ <%= f.label :email %>
37
+ <%= f.text_field :email %>
38
+ <% if false -%>
39
+ <span>or <%= link_to 'LOG IN', new_user_session_path %></span>
40
+ <% end -%>
41
+ <% end -%>
42
+ </p>
43
+ <p>
44
+ <%= f.label :phone %>
45
+ <%= f.text_field :phone %>
46
+ </p>
47
+ </div>
48
+ <div id="shipping">
49
+ <h3>Shipping Option</h3>
50
+ <%= f.fields_for :shipments, f.object.shipments.build do |shipment| %>
51
+ <p>
52
+ <%= shipment.label :shipping_method_id %>
53
+ <%= shipment.select :shipping_method_id, [] %>
54
+ </p>
55
+ <% end -%>
56
+ </div>
57
+ <div id="payment">
58
+ <h3>Payment</h3>
59
+ <%= f.fields_for :payments, f.object.payments.build do |payment| %>
60
+ <p>
61
+ <%= payment.label :number %>
62
+ <% if @order.errors.keys.include?("payments.number".to_sym) %>
63
+ <span class="field_with_errors">
64
+ <%= payment.text_field :number %>
65
+ </span>
66
+ <% else -%>
67
+ <%= payment.text_field :number %>
68
+ <% end -%>
69
+ </p>
70
+
71
+ <p>
72
+ <%= payment.label :verification_value %>
73
+ <% if @order.errors.keys.include?("payments.verification_value".to_sym) %>
74
+ <span class="field_with_errors">
75
+ <%= payment.text_field :verification_value %>
76
+ </span>
77
+ <% else -%>
78
+ <%= payment.text_field :verification_value %>
79
+ <% end -%>
80
+ </p>
81
+
82
+ <p>
83
+ <%= payment.label :month %>
84
+ <% if @order.errors.keys.include?("payments.verification_value".to_sym) %>
85
+ <span class="field_with_errors">
86
+ <%= payment.select :month, 1.upto(12) %> /
87
+ <%= payment.select :year, Time.now.year.upto(Time.now.year + 10) %>
88
+ </span>
89
+ <% else -%>
90
+ <%= payment.select :month, 1.upto(12) %> /
91
+ <%= payment.select :year, Time.now.year.upto(Time.now.year + 10) %>
92
+ <% end -%>
93
+ </p>
94
+ <% end -%>
95
+ </div>
96
+ <div id="submit">
97
+ <%= f.submit %>
98
+ </div>
99
+ </div>
100
+ <% end -%>
101
+
102
+ <div id="totals_section">
103
+ <h3>Totals</h3>
104
+ <%= render "piggybak/cart/items", :page => "checkout" %>
105
+ </div>
106
+
107
+ </div>
108
+
109
+ <script type="text/javascript">
110
+ var shipping_lookup = "<%= piggybak.orders_shipping_url %>";
111
+ var tax_lookup = "<%= piggybak.orders_tax_url %>";
112
+ </script>
@@ -0,0 +1,5 @@
1
+ <% if params[:action] == 'edit' -%>
2
+ <%= link_to "Send Email Confirmation", piggybak.orders_email_url(params[:id]) %>
3
+ <% else -%>
4
+ <b>You may not send a confirmation email until you have a saved order.</b>
5
+ <% end -%>