piggybak 0.1.1 → 0.2.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.
- data/Gemfile +2 -0
- data/Gemfile.lock +5 -0
- data/README.md +117 -0
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/app/assets/javascripts/piggybak.js +62 -2
- data/app/controllers/piggybak/orders_controller.rb +26 -5
- data/{lib/application_helper.rb → app/helpers/piggybak_helper.rb} +6 -1
- data/app/models/piggybak/address.rb +14 -1
- data/app/models/piggybak/cart.rb +19 -12
- data/app/models/piggybak/country.rb +5 -0
- data/app/models/piggybak/credit.rb +8 -0
- data/app/models/piggybak/line_item.rb +13 -6
- data/app/models/piggybak/order.rb +12 -6
- data/app/models/piggybak/payment.rb +5 -3
- data/app/models/piggybak/payment_calculator/authorize_net.rb +4 -0
- data/app/models/piggybak/payment_calculator/fake.rb +4 -0
- data/app/models/piggybak/payment_method.rb +8 -3
- data/app/models/piggybak/shipping_calculator/free.rb +13 -0
- data/app/models/piggybak/shipping_calculator/pickup.rb +1 -1
- data/app/models/piggybak/shipping_method.rb +9 -7
- data/app/models/piggybak/state.rb +1 -0
- data/app/models/piggybak/tax_calculator/{flat_rate.rb → percent.rb} +1 -1
- data/app/models/piggybak/tax_method.rb +8 -2
- data/app/models/piggybak/{product.rb → variant.rb} +5 -4
- data/app/views/piggybak/cart/_form.html.erb +7 -7
- data/app/views/piggybak/cart/_items.html.erb +9 -9
- data/app/views/piggybak/cart/show.html.erb +2 -0
- data/app/views/piggybak/notifier/order_notification.text.erb +1 -1
- data/app/views/piggybak/orders/_address_form.html.erb +18 -14
- data/app/views/piggybak/orders/_details.html.erb +48 -0
- data/app/views/piggybak/orders/download.text.erb +19 -0
- data/app/views/piggybak/orders/list.html.erb +12 -12
- data/app/views/piggybak/orders/no_access.text.erb +1 -0
- data/app/views/piggybak/orders/receipt.html.erb +1 -49
- data/app/views/piggybak/orders/show.html.erb +36 -31
- data/app/views/rails_admin/main/_actions.html.erb +9 -2
- data/config/routes.rb +12 -3
- data/db/migrate/20111227150106_create_orders.rb +4 -3
- data/db/migrate/20111227150322_create_addresses.rb +2 -1
- data/db/migrate/20111227150432_create_line_items.rb +2 -2
- data/db/migrate/{20111227213558_create_products.rb → 20111227213558_create_variants.rb} +3 -3
- data/db/migrate/20111228231829_create_payments.rb +3 -1
- data/db/migrate/20111228231838_create_shipments.rb +1 -1
- data/db/migrate/20120102162414_create_countries.rb +10 -0
- data/db/migrate/20120102162415_create_states.rb +1 -0
- data/db/migrate/20120104020930_populate_countries_and_states.rb +18 -0
- data/db/migrate/20120106010412_create_credits.rb +14 -0
- data/lib/{acts_as_product → acts_as_variant}/base.rb +6 -6
- data/lib/piggybak.rb +59 -23
- data/piggybak.gemspec +26 -11
- metadata +64 -62
- data/README.rdoc +0 -19
@@ -1,9 +1,9 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class LineItem < ActiveRecord::Base
|
3
3
|
belongs_to :order
|
4
|
-
belongs_to :
|
4
|
+
belongs_to :variant
|
5
5
|
|
6
|
-
validates_presence_of :
|
6
|
+
validates_presence_of :variant_id
|
7
7
|
validates_presence_of :total
|
8
8
|
validates_presence_of :quantity
|
9
9
|
validates_numericality_of :quantity, :only_integer => true, :greater_than_or_equal_to => 0
|
@@ -13,19 +13,26 @@ module Piggybak
|
|
13
13
|
after_update :update_inventory
|
14
14
|
|
15
15
|
def admin_label
|
16
|
-
"#{self.quantity} x #{self.
|
16
|
+
"#{self.quantity} x #{self.variant.description}"
|
17
17
|
end
|
18
18
|
|
19
19
|
def decrease_inventory
|
20
|
-
self.
|
20
|
+
self.variant.update_inventory(-1 * self.quantity)
|
21
21
|
end
|
22
22
|
|
23
23
|
def increase_inventory
|
24
|
-
self.
|
24
|
+
self.variant.update_inventory(self.quantity)
|
25
25
|
end
|
26
26
|
|
27
27
|
def update_inventory
|
28
|
-
|
28
|
+
if self.variant_id != self.variant_id_was
|
29
|
+
old_variant = Variant.find(self.variant_id_was)
|
30
|
+
old_variant.update_inventory(self.quantity_was)
|
31
|
+
self.variant.update_inventory(-1*self.quantity)
|
32
|
+
else
|
33
|
+
quantity_diff = self.quantity_was - self.quantity
|
34
|
+
self.variant.update_inventory(quantity_diff)
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|
31
38
|
end
|
@@ -3,6 +3,8 @@ module Piggybak
|
|
3
3
|
has_many :line_items, :inverse_of => :order
|
4
4
|
has_many :payments, :inverse_of => :order
|
5
5
|
has_many :shipments, :inverse_of => :order
|
6
|
+
has_many :credits, :inverse_of => :order
|
7
|
+
|
6
8
|
belongs_to :billing_address, :class_name => "Piggybak::Address"
|
7
9
|
belongs_to :shipping_address, :class_name => "Piggybak::Address"
|
8
10
|
belongs_to :user
|
@@ -11,7 +13,7 @@ module Piggybak
|
|
11
13
|
accepts_nested_attributes_for :shipping_address, :allow_destroy => true
|
12
14
|
accepts_nested_attributes_for :shipments, :allow_destroy => true
|
13
15
|
accepts_nested_attributes_for :line_items, :allow_destroy => true
|
14
|
-
accepts_nested_attributes_for :payments
|
16
|
+
accepts_nested_attributes_for :payments # test :allow_destroy
|
15
17
|
|
16
18
|
validates_presence_of :status
|
17
19
|
validates_presence_of :email
|
@@ -58,8 +60,8 @@ module Piggybak
|
|
58
60
|
def add_line_items(cart)
|
59
61
|
cart.update_quantities
|
60
62
|
cart.items.each do |item|
|
61
|
-
line_item = Piggybak::LineItem.new({ :
|
62
|
-
:total => item[:
|
63
|
+
line_item = Piggybak::LineItem.new({ :variant_id => item[:variant].id,
|
64
|
+
:total => item[:variant].price*item[:quantity],
|
63
65
|
:quantity => item[:quantity] })
|
64
66
|
self.line_items << line_item
|
65
67
|
end
|
@@ -73,7 +75,7 @@ module Piggybak
|
|
73
75
|
self.tax_charge = 0
|
74
76
|
|
75
77
|
self.line_items.each do |line_item|
|
76
|
-
line_item.total = line_item.
|
78
|
+
line_item.total = line_item.variant.price * line_item.quantity
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
@@ -92,10 +94,14 @@ module Piggybak
|
|
92
94
|
calculator = shipment.shipping_method.klass.constantize
|
93
95
|
shipment.total = calculator.rate(shipment.shipping_method, self)
|
94
96
|
end
|
95
|
-
logger.warn "steph shipment total is #{shipment.total}"
|
96
97
|
self.total += shipment.total
|
97
98
|
end
|
98
|
-
|
99
|
+
|
100
|
+
# Hook in credits, TBD
|
101
|
+
credits.each do |credit|
|
102
|
+
self.total -= credit.total
|
103
|
+
end
|
104
|
+
|
99
105
|
self.total = self.total.to_c
|
100
106
|
|
101
107
|
self.total_due = self.total
|
@@ -10,7 +10,7 @@ module Piggybak
|
|
10
10
|
validates_presence_of :year
|
11
11
|
|
12
12
|
def status_enum
|
13
|
-
["paid"]
|
13
|
+
["paid", "refunded"]
|
14
14
|
end
|
15
15
|
|
16
16
|
def month_enum
|
@@ -39,7 +39,8 @@ module Piggybak
|
|
39
39
|
if gateway_response.success?
|
40
40
|
self.attributes = { :total => self.order.total_due,
|
41
41
|
:number => 'hidden',
|
42
|
-
:verification_value => 'hidden'
|
42
|
+
:verification_value => 'hidden',
|
43
|
+
:transaction_id => payment_gateway.transaction_id(gateway_response) }
|
43
44
|
gateway.capture(1000, gateway_response.authorization)
|
44
45
|
return true
|
45
46
|
else
|
@@ -56,6 +57,7 @@ module Piggybak
|
|
56
57
|
return "Payment ##{self.id} (#{self.created_at.strftime("%m-%d-%Y")})<br />" +
|
57
58
|
"Payment Method: #{self.payment_method.description}<br />" +
|
58
59
|
"Status: #{self.status}<br />" +
|
60
|
+
"Transaction ID: #{self.transaction_id}<br />" +
|
59
61
|
"$#{"%.2f" % self.total}"
|
60
62
|
else
|
61
63
|
return ""
|
@@ -70,7 +72,7 @@ module Piggybak
|
|
70
72
|
if !credit_card.valid?
|
71
73
|
credit_card.errors.each do |key, value|
|
72
74
|
if value.any? && !["first_name", "last_name", "type"].include?(key)
|
73
|
-
record.errors.add key, value
|
75
|
+
record.errors.add key, (value.is_a?(Array) ? value.join(', ') : value)
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
@@ -2,5 +2,9 @@ module Piggybak
|
|
2
2
|
class PaymentCalculator::AuthorizeNet < PaymentCalculator
|
3
3
|
KEYS = ["login", "password"]
|
4
4
|
KLASS = ::ActiveMerchant::Billing::AuthorizeNetGateway
|
5
|
+
|
6
|
+
def self.transaction_id(gateway_response)
|
7
|
+
gateway_response.params["transaction_id"]
|
8
|
+
end
|
5
9
|
end
|
6
10
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class PaymentMethod < ActiveRecord::Base
|
3
|
+
|
4
|
+
# klass_enum requires the ShippingCalculator subclasses to be loaded
|
5
|
+
shipping_calcs_path = File.expand_path("../payment_calculator", __FILE__)
|
6
|
+
Dir.glob(shipping_calcs_path + "**/*.rb").each do |subclass|
|
7
|
+
ActiveSupport::Dependencies.require_or_load subclass
|
8
|
+
end
|
9
|
+
|
3
10
|
has_many :payment_method_values, :dependent => :destroy
|
4
11
|
alias :metadata :payment_method_values
|
5
12
|
|
@@ -9,9 +16,7 @@ module Piggybak
|
|
9
16
|
validates_presence_of :description
|
10
17
|
|
11
18
|
def klass_enum
|
12
|
-
|
13
|
-
[Piggybak::PaymentCalculator::AuthorizeNet,
|
14
|
-
Piggybak::PaymentCalculator::Fake]
|
19
|
+
Piggybak::PaymentCalculator.subclasses
|
15
20
|
end
|
16
21
|
|
17
22
|
validates_each :payment_method_values do |record, attr, value|
|
@@ -7,7 +7,7 @@ module Piggybak
|
|
7
7
|
|
8
8
|
if object.is_a?(Cart)
|
9
9
|
state = State.find(object.extra_data["state_id"])
|
10
|
-
return true if state.abbr == abbr
|
10
|
+
return true if state && state.abbr == abbr
|
11
11
|
else
|
12
12
|
if object.billing_address && object.billing_address.state
|
13
13
|
return object.billing_address.state.abbr == abbr
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class ShippingMethod < ActiveRecord::Base
|
3
|
+
|
4
|
+
# klass_enum requires the ShippingCalculator subclasses to be loaded
|
5
|
+
shipping_calcs_path = File.expand_path("../shipping_calculator", __FILE__)
|
6
|
+
Dir.glob(shipping_calcs_path + "**/*.rb").each do |subclass|
|
7
|
+
ActiveSupport::Dependencies.require_or_load subclass
|
8
|
+
end
|
9
|
+
|
3
10
|
has_many :shipping_method_values, :dependent => :destroy
|
4
11
|
alias :metadata :shipping_method_values
|
5
12
|
|
@@ -18,11 +25,8 @@ module Piggybak
|
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
|
-
def klass_enum
|
22
|
-
|
23
|
-
[Piggybak::ShippingCalculator::FlatRate,
|
24
|
-
Piggybak::ShippingCalculator::Range,
|
25
|
-
Piggybak::ShippingCalculator::Pickup]
|
28
|
+
def klass_enum
|
29
|
+
Piggybak::ShippingCalculator.subclasses
|
26
30
|
end
|
27
31
|
|
28
32
|
def self.available_methods(cart)
|
@@ -36,10 +40,8 @@ module Piggybak
|
|
36
40
|
|
37
41
|
active_methods.inject([]) do |arr, method|
|
38
42
|
klass = method.klass.constantize
|
39
|
-
logger.warn "steph: inside here!! #{method.inspect}"
|
40
43
|
if klass.available?(method, cart)
|
41
44
|
rate = klass.rate(method, cart)
|
42
|
-
logger.warn "steph rate is #{rate.inspect}"
|
43
45
|
arr << {
|
44
46
|
:label => "#{method.description} $#{"%.2f" % rate}",
|
45
47
|
:id => method.id,
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class TaxMethod < ActiveRecord::Base
|
3
|
+
|
4
|
+
# klass_enum requires the ShippingCalculator subclasses to be loaded
|
5
|
+
shipping_calcs_path = File.expand_path("../tax_calculator", __FILE__)
|
6
|
+
Dir.glob(shipping_calcs_path + "**/*.rb").each do |subclass|
|
7
|
+
ActiveSupport::Dependencies.require_or_load subclass
|
8
|
+
end
|
9
|
+
|
3
10
|
has_many :tax_method_values, :dependent => :destroy
|
4
11
|
alias :metadata :tax_method_values
|
5
12
|
|
@@ -19,8 +26,7 @@ module Piggybak
|
|
19
26
|
end
|
20
27
|
|
21
28
|
def klass_enum
|
22
|
-
|
23
|
-
[Piggybak::TaxCalculator::FlatRate]
|
29
|
+
Piggybak::TaxCalculator.subclasses
|
24
30
|
end
|
25
31
|
|
26
32
|
def self.calculate_tax(object)
|
@@ -1,6 +1,7 @@
|
|
1
|
-
class Piggybak::
|
2
|
-
belongs_to :item, :polymorphic => true, :inverse_of => :
|
3
|
-
attr_accessible :sku, :description, :price, :quantity, :active, :unlimited_inventory, :item_id, :item_type
|
1
|
+
class Piggybak::Variant < ActiveRecord::Base
|
2
|
+
belongs_to :item, :polymorphic => true, :inverse_of => :piggybak_variant
|
3
|
+
attr_accessible :sku, :description, :price, :quantity, :active, :unlimited_inventory, :item_id, :item_type
|
4
|
+
attr_accessible :item # to allow direct assignment from code or console
|
4
5
|
|
5
6
|
validates_presence_of :sku
|
6
7
|
validates_uniqueness_of :sku
|
@@ -10,7 +11,7 @@ class Piggybak::Product < ActiveRecord::Base
|
|
10
11
|
validates_numericality_of :quantity, :only_integer => true, :greater_than_or_equal_to => 0
|
11
12
|
|
12
13
|
def admin_label
|
13
|
-
"
|
14
|
+
"Variant: #{self.description}"
|
14
15
|
end
|
15
16
|
|
16
17
|
def update_inventory(purchased)
|
@@ -1,14 +1,14 @@
|
|
1
|
-
<% if object.reflections.keys.include?(:
|
2
|
-
<% if object.
|
3
|
-
<% if object.
|
1
|
+
<% if object.reflections.keys.include?(:piggybak_variant) -%>
|
2
|
+
<% if object.piggybak_variant && object.piggybak_variant.active -%>
|
3
|
+
<% if object.piggybak_variant.quantity > 0 || object.piggybak_variant.unlimited_inventory -%>
|
4
4
|
<%= form_tag piggybak.cart_add_url do -%>
|
5
|
-
<div id="
|
6
|
-
<span id="title"><%= object.
|
7
|
-
<span id="price"><%= number_to_currency object.
|
5
|
+
<div id="variant_details">
|
6
|
+
<span id="title"><%= object.piggybak_variant.description %></span>
|
7
|
+
<span id="price"><%= number_to_currency object.piggybak_variant.price %></span>
|
8
8
|
</div>
|
9
9
|
<label>Quantity</label>
|
10
10
|
<%= text_field_tag :quantity %>
|
11
|
-
<%= hidden_field_tag :
|
11
|
+
<%= hidden_field_tag :variant_id, object.piggybak_variant.id %>
|
12
12
|
<%= submit_tag "Add to Cart", :id => "submit" %>
|
13
13
|
<% end -%>
|
14
14
|
<% else -%>
|
@@ -2,29 +2,29 @@
|
|
2
2
|
<%= form_tag piggybak.cart_update_url do -%>
|
3
3
|
<table cellpadding="5" cellspacing="0" width="100%">
|
4
4
|
<tr>
|
5
|
-
<th>Item</th>
|
6
|
-
<th>Price</th>
|
7
|
-
<th>Quantity</th>
|
8
|
-
<th>Subtotal</th>
|
5
|
+
<th id="item_col">Item</th>
|
6
|
+
<th id="price_col">Price</th>
|
7
|
+
<th id="quantity_col">Quantity</th>
|
8
|
+
<th id="subtotal_col">Subtotal</th>
|
9
9
|
<% if page == "cart" -%>
|
10
10
|
<th></th>
|
11
11
|
<% end -%>
|
12
12
|
</tr>
|
13
13
|
<% @cart.items.each do |item| %>
|
14
14
|
<tr>
|
15
|
-
<td><%= item[:
|
16
|
-
<td><%= number_to_currency item[:
|
15
|
+
<td><%= item[:variant].description %></td>
|
16
|
+
<td><%= number_to_currency item[:variant].price %></td>
|
17
17
|
<td>
|
18
18
|
<% if page == "cart" -%>
|
19
|
-
<%= text_field_tag "quantity[#{item[:
|
19
|
+
<%= text_field_tag "quantity[#{item[:variant].id}]", item[:quantity] %>
|
20
20
|
<% else -%>
|
21
21
|
<%= item[:quantity] %>
|
22
22
|
<% end -%>
|
23
23
|
</td>
|
24
|
-
<td><%= number_to_currency item[:quantity]*item[:
|
24
|
+
<td><%= number_to_currency item[:quantity]*item[:variant].price %></td>
|
25
25
|
<% if page == "cart" -%>
|
26
26
|
<td>
|
27
|
-
<%= link_to "Remove", piggybak.remove_item_url(item[:
|
27
|
+
<%= link_to "Remove", piggybak.remove_item_url(item[:variant].id), :method => :delete %>
|
28
28
|
</td>
|
29
29
|
<% end -%>
|
30
30
|
</tr>
|
@@ -3,7 +3,7 @@ Thanks for your order!
|
|
3
3
|
Items
|
4
4
|
|
5
5
|
<% @order.line_items.each do |line_item| %>
|
6
|
-
<%= line_item.
|
6
|
+
<%= line_item.variant.description %> x <%= line_item.quantity %> = <%= number_to_currency line_item.total %>
|
7
7
|
<% end -%>
|
8
8
|
|
9
9
|
Total
|
@@ -1,24 +1,28 @@
|
|
1
|
-
<
|
1
|
+
<div class="item">
|
2
2
|
<%= address.label :firstname %>
|
3
3
|
<%= address.text_field :firstname %>
|
4
|
-
</
|
5
|
-
<
|
4
|
+
</div>
|
5
|
+
<div class="item">
|
6
6
|
<%= address.label :lastname %>
|
7
7
|
<%= address.text_field :lastname %>
|
8
|
-
</
|
9
|
-
<
|
8
|
+
</div>
|
9
|
+
<div class="item">
|
10
10
|
<%= address.label :address1 %>
|
11
11
|
<%= address.text_field :address1 %>
|
12
|
-
</
|
13
|
-
<
|
12
|
+
</div>
|
13
|
+
<div class="item">
|
14
14
|
<%= address.label :city %>
|
15
15
|
<%= address.text_field :city %>
|
16
|
-
</
|
17
|
-
<
|
18
|
-
|
19
|
-
|
20
|
-
</
|
21
|
-
<
|
16
|
+
</div>
|
17
|
+
<div class="item">
|
18
|
+
<%= address.label :country_id %>
|
19
|
+
<%= address.collection_select :country_id, Piggybak::Country.all, :id, :name, { :selected => address.object.country_id.to_s } %>
|
20
|
+
</div>
|
21
|
+
<div class="item">
|
22
|
+
<%= address.label :state_id %>
|
23
|
+
<%= address.collection_select :state_id, address.object.country.states, :id, :name, { :selected => address.object.state_id.to_s } %>
|
24
|
+
</div>
|
25
|
+
<div class="item">
|
22
26
|
<%= address.label :zip %>
|
23
27
|
<%= address.text_field :zip %>
|
24
|
-
</
|
28
|
+
</div>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<table id="items" cellpadding="5" cellspacing="0">
|
2
|
+
<tr>
|
3
|
+
<th>Item</th>
|
4
|
+
<th>Price</th>
|
5
|
+
<th>Quantity</th>
|
6
|
+
<th>Subtotal</th>
|
7
|
+
</tr>
|
8
|
+
<% order.line_items.each do |line_item| %>
|
9
|
+
<tr>
|
10
|
+
<td><%= line_item.variant.description %></td>
|
11
|
+
<td><%= line_item.variant.price %></td>
|
12
|
+
<td><%= line_item.quantity %></td>
|
13
|
+
<td><%= number_to_currency line_item.total %></td>
|
14
|
+
</tr>
|
15
|
+
<% end -%>
|
16
|
+
<tr>
|
17
|
+
<td colspan="4"></td>
|
18
|
+
</tr>
|
19
|
+
<tr>
|
20
|
+
<td colspan="2"></td>
|
21
|
+
<td>Subtotal</td>
|
22
|
+
<td><%= number_to_currency order.line_items.inject(0) { |subtotal, li| subtotal + li.total } %></td>
|
23
|
+
</tr>
|
24
|
+
<tr>
|
25
|
+
<td colspan="2"></td>
|
26
|
+
<td>Tax</td>
|
27
|
+
<td><%= number_to_currency order.tax_charge %></td>
|
28
|
+
</tr>
|
29
|
+
<tr>
|
30
|
+
<td colspan="2"></td>
|
31
|
+
<td>Shipping</td>
|
32
|
+
<td><%= number_to_currency order.shipments.inject(0) { |shipping, shipment| shipping + shipment.total } %></td>
|
33
|
+
</tr>
|
34
|
+
<tr>
|
35
|
+
<td colspan="2"></td>
|
36
|
+
<td>Total</td>
|
37
|
+
<td><%= number_to_currency order.total %></td>
|
38
|
+
</tr>
|
39
|
+
</table>
|
40
|
+
|
41
|
+
Email: <%= order.email %><br />
|
42
|
+
Phone: <%= order.phone %>
|
43
|
+
|
44
|
+
<h3>Billing Information</h3>
|
45
|
+
<%= raw order.billing_address.display %>
|
46
|
+
|
47
|
+
<h3>Shipping Information</h3>
|
48
|
+
<%= raw order.shipping_address.display %>
|