piggybak 0.5.5 → 0.6.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/README.md +32 -25
- data/VERSION +1 -1
- data/app/assets/javascripts/piggybak.js +11 -1
- data/app/assets/javascripts/piggybak.states.js +6 -5
- data/app/controllers/piggybak/orders_controller.rb +14 -22
- data/app/models/piggybak/address.rb +4 -1
- data/app/models/piggybak/adjustment.rb +1 -25
- data/app/models/piggybak/cart.rb +18 -17
- data/app/models/piggybak/line_item.rb +106 -19
- data/app/models/piggybak/order.rb +94 -108
- data/app/models/piggybak/payment.rb +25 -27
- data/app/models/piggybak/payment_method.rb +3 -1
- data/app/models/piggybak/payment_method_value.rb +3 -1
- data/app/models/piggybak/{variant.rb → sellable.rb} +6 -4
- data/app/models/piggybak/shipment.rb +4 -10
- data/app/models/piggybak/shipping_calculator/flat_rate.rb +4 -0
- data/app/models/piggybak/shipping_calculator/free.rb +4 -0
- data/app/models/piggybak/shipping_calculator/range.rb +4 -0
- data/app/models/piggybak/shipping_method.rb +1 -1
- data/app/models/piggybak/tax_method.rb +1 -1
- data/app/views/piggybak/cart/_form.html.erb +7 -7
- data/app/views/piggybak/cart/_items.html.erb +14 -5
- data/app/views/piggybak/notifier/order_notification.text.erb +10 -2
- data/app/views/piggybak/orders/_details.html.erb +14 -5
- data/app/views/piggybak/orders/_google_analytics.html.erb +3 -3
- data/app/views/piggybak/orders/download.text.erb +6 -6
- data/app/views/piggybak/orders/submit.html.erb +55 -49
- data/app/views/rails_admin/main/_location_select.html.haml +3 -3
- data/app/views/rails_admin/main/_order_details.html.erb +12 -24
- data/app/views/rails_admin/main/_polymorphic_nested.html.haml +29 -0
- data/bin/piggybak +12 -0
- data/config/routes.rb +0 -2
- data/db/migrate/20121008160425_rename_variants_to_sellables.rb +11 -0
- data/db/migrate/20121008175144_line_item_rearchitecture.rb +96 -0
- data/lib/acts_as_sellable.rb +21 -0
- data/lib/formatted_changes.rb +2 -2
- data/lib/piggybak.rb +80 -126
- data/lib/piggybak/cli.rb +81 -0
- data/lib/piggybak/config.rb +21 -0
- data/piggybak.gemspec +10 -7
- data/spec/dummy_app/app/models/image.rb +1 -1
- data/spec/factories.rb +1 -1
- metadata +52 -49
- data/app/controllers/piggybak/payments_controller.rb +0 -14
- data/app/views/rails_admin/main/_order_notes.html.erb +0 -1
- data/app/views/rails_admin/main/_payment_refund.html.haml +0 -6
- data/lib/acts_as_variant.rb +0 -15
@@ -1,9 +1,6 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class Order < ActiveRecord::Base
|
3
3
|
has_many :line_items, :inverse_of => :order
|
4
|
-
has_many :payments, :inverse_of => :order
|
5
|
-
has_many :shipments, :inverse_of => :order
|
6
|
-
has_many :adjustments, :inverse_of => :order
|
7
4
|
has_many :order_notes, :inverse_of => :order
|
8
5
|
|
9
6
|
belongs_to :billing_address, :class_name => "Piggybak::Address"
|
@@ -12,42 +9,38 @@ module Piggybak
|
|
12
9
|
|
13
10
|
accepts_nested_attributes_for :billing_address, :allow_destroy => true
|
14
11
|
accepts_nested_attributes_for :shipping_address, :allow_destroy => true
|
15
|
-
accepts_nested_attributes_for :shipments, :allow_destroy => true
|
16
12
|
accepts_nested_attributes_for :line_items, :allow_destroy => true
|
17
|
-
accepts_nested_attributes_for :payments
|
18
|
-
accepts_nested_attributes_for :adjustments, :allow_destroy => true
|
19
13
|
accepts_nested_attributes_for :order_notes
|
20
14
|
|
21
|
-
attr_accessor :recorded_changes
|
22
|
-
|
23
|
-
attr_accessor :was_new_record
|
24
|
-
attr_accessor :disable_order_notes
|
15
|
+
attr_accessor :recorded_changes, :recorded_changer,
|
16
|
+
:was_new_record, :disable_order_notes
|
25
17
|
|
26
|
-
validates_presence_of :status, :email, :phone, :total, :total_due, :
|
18
|
+
validates_presence_of :status, :email, :phone, :total, :total_due, :created_at, :ip_address, :user_agent
|
27
19
|
|
28
|
-
after_initialize :
|
29
|
-
|
30
|
-
after_validation :update_totals
|
31
|
-
before_save :process_payments, :update_status, :set_new_record
|
20
|
+
after_initialize :initialize_defaults
|
21
|
+
before_save :postprocess_order, :update_status, :set_new_record
|
32
22
|
after_save :record_order_note
|
33
23
|
|
34
24
|
default_scope :order => 'created_at DESC'
|
35
25
|
|
36
|
-
|
26
|
+
attr_accessible :user_id, :email, :phone, :billing_address_attributes,
|
27
|
+
:shipping_address_attributes, :line_items_attributes,
|
28
|
+
:order_notes_attributes, :details, :recorded_changer, :ip_address
|
29
|
+
|
30
|
+
def initialize_defaults
|
37
31
|
self.recorded_changes ||= []
|
38
32
|
|
39
33
|
self.billing_address ||= Piggybak::Address.new
|
40
34
|
self.shipping_address ||= Piggybak::Address.new
|
41
|
-
self.shipments ||= [Piggybak::Shipment.new]
|
42
|
-
self.payments ||= [Piggybak::Payment.new]
|
43
|
-
if self.payments.any?
|
44
|
-
self.payments.first.payment_method_id = Piggybak::PaymentMethod.find_by_active(true).id
|
45
|
-
end
|
46
|
-
end
|
47
35
|
|
48
|
-
def initialize_request
|
49
36
|
self.ip_address ||= 'admin'
|
50
37
|
self.user_agent ||= 'admin'
|
38
|
+
|
39
|
+
self.created_at ||= Time.now
|
40
|
+
self.status ||= "new"
|
41
|
+
self.total ||= 0
|
42
|
+
self.total_due ||= 0
|
43
|
+
self.disable_order_notes = false
|
51
44
|
end
|
52
45
|
|
53
46
|
def initialize_user(user, on_post)
|
@@ -57,26 +50,62 @@ module Piggybak
|
|
57
50
|
end
|
58
51
|
end
|
59
52
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
has_errors = true
|
53
|
+
def postprocess_order
|
54
|
+
# Mark line items for destruction if quantity == 0
|
55
|
+
self.line_items.each do |line_item|
|
56
|
+
if line_item.quantity == 0
|
57
|
+
line_item.mark_for_destruction
|
66
58
|
end
|
67
59
|
end
|
68
60
|
|
69
|
-
|
61
|
+
# Recalculate and create line item for tax
|
62
|
+
# If a tax line item already exists, reset price
|
63
|
+
# If a tax line item doesn't, create
|
64
|
+
# If tax is 0, destroy tax line item
|
65
|
+
tax = TaxMethod.calculate_tax(self)
|
66
|
+
tax_line_item = self.line_items.detect { |line_item| line_item.line_item_type == "tax" }
|
67
|
+
if tax > 0
|
68
|
+
if tax_line_item
|
69
|
+
tax_line_item.price = tax
|
70
|
+
else
|
71
|
+
self.line_items << LineItem.new({ :line_item_type => "tax", :description => "Tax Charge", :price => tax })
|
72
|
+
end
|
73
|
+
elsif tax_line_item
|
74
|
+
tax_line_item.mark_for_destruction
|
75
|
+
end
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
# Postprocess everything but payments first
|
78
|
+
self.line_items.each do |line_item|
|
79
|
+
next if line_item.line_item_type == "payment"
|
80
|
+
method = "postprocess_#{line_item.line_item_type}"
|
81
|
+
if line_item.respond_to?(method)
|
82
|
+
if !line_item.send(method)
|
83
|
+
return false
|
84
|
+
end
|
74
85
|
end
|
75
86
|
end
|
87
|
+
|
88
|
+
# Recalculating total and total due, in case post process changed totals
|
89
|
+
self.total = 0
|
90
|
+
self.line_items.each do |line_item|
|
91
|
+
if !line_item._destroy
|
92
|
+
self.total += line_item.price
|
93
|
+
end
|
94
|
+
end
|
95
|
+
self.total_due = self.total
|
76
96
|
|
77
|
-
|
97
|
+
# Postprocess payment last
|
98
|
+
self.line_items.each do |line_item|
|
99
|
+
next if line_item.line_item_type != "payment"
|
100
|
+
method = "postprocess_#{line_item.line_item_type}"
|
101
|
+
if line_item.respond_to?(method)
|
102
|
+
if !line_item.send(method)
|
103
|
+
return false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
78
107
|
|
79
|
-
|
108
|
+
true
|
80
109
|
end
|
81
110
|
|
82
111
|
def record_order_note
|
@@ -89,82 +118,38 @@ module Piggybak
|
|
89
118
|
end
|
90
119
|
end
|
91
120
|
|
92
|
-
def
|
93
|
-
|
94
|
-
cart.items.each do |item|
|
95
|
-
line_item = Piggybak::LineItem.new({ :variant_id => item[:variant].id,
|
96
|
-
:price => item[:variant].price,
|
97
|
-
:total => item[:variant].price*item[:quantity],
|
98
|
-
:description => item[:variant].description,
|
99
|
-
:quantity => item[:quantity] })
|
100
|
-
self.line_items << line_item
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def set_defaults
|
105
|
-
self.created_at ||= Time.now
|
106
|
-
self.status ||= "new"
|
107
|
-
self.total = 0
|
108
|
-
self.total_due = 0
|
109
|
-
self.tax_charge = 0
|
110
|
-
self.disable_order_notes = false
|
111
|
-
|
112
|
-
return if self.to_be_cancelled
|
121
|
+
def create_payment_shipment
|
122
|
+
shipment_line_item = self.line_items.detect { |li| li.line_item_type == "shipment" }
|
113
123
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
+
if shipment_line_item.nil?
|
125
|
+
new_shipment_line_item = Piggybak::LineItem.new({ :line_item_type => "shipment" })
|
126
|
+
new_shipment_line_item.build_shipment
|
127
|
+
self.line_items << new_shipment_line_item
|
128
|
+
elsif shipment_line_item.shipment.nil?
|
129
|
+
shipment_line_item.build_shipment
|
130
|
+
else
|
131
|
+
previous_method = shipment_line_item.shipment.shipping_method_id
|
132
|
+
shipment_line_item.build_shipment
|
133
|
+
shipment_line_item.shipment.shipping_method_id = previous_method
|
124
134
|
end
|
125
|
-
end
|
126
135
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
136
|
+
if !self.line_items.detect { |li| li.line_item_type == "payment" }
|
137
|
+
payment_line_item = Piggybak::LineItem.new({ :line_item_type => "payment" })
|
138
|
+
payment_line_item.build_payment
|
139
|
+
self.line_items << payment_line_item
|
132
140
|
end
|
133
141
|
end
|
134
142
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
self.line_items.each do |line_item|
|
139
|
-
if !line_item._destroy
|
140
|
-
self.total += line_item.total
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
self.tax_charge = TaxMethod.calculate_tax(self)
|
145
|
-
self.total += self.tax_charge
|
146
|
-
|
147
|
-
shipments.each do |shipment|
|
148
|
-
if !shipment._destroy
|
149
|
-
if (shipment.new_record? || shipment.status != 'shipped') && shipment.shipping_method
|
150
|
-
calculator = shipment.shipping_method.klass.constantize
|
151
|
-
shipment.total = calculator.rate(shipment.shipping_method, self)
|
152
|
-
end
|
153
|
-
|
154
|
-
shipping_cast = ((shipment.total*100).to_i).to_f/100
|
155
|
-
self.total += shipping_cast
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
payments_total = self.payments.inject(0) { |s, payment| s + payment.total }
|
143
|
+
def add_line_items(cart)
|
144
|
+
cart.update_quantities
|
160
145
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
146
|
+
cart.items.each do |item|
|
147
|
+
self.line_items << Piggybak::LineItem.new({ :sellable_id => item[:sellable].id,
|
148
|
+
:unit_price => item[:sellable].price,
|
149
|
+
:price => item[:sellable].price*item[:quantity],
|
150
|
+
:description => item[:sellable].description,
|
151
|
+
:quantity => item[:quantity] })
|
165
152
|
end
|
166
|
-
|
167
|
-
self.total_due = (self.total - payments_total).round(2)
|
168
153
|
end
|
169
154
|
|
170
155
|
def update_status
|
@@ -175,18 +160,19 @@ module Piggybak
|
|
175
160
|
else
|
176
161
|
if self.to_be_cancelled
|
177
162
|
self.status = "cancelled"
|
178
|
-
|
163
|
+
# TODO: line items scope doesn't work here, maybe on new items? Fix if possible.
|
164
|
+
elsif line_items.select { |li| li.line_item_type == "shipment" }.any? && line_items.select { |li| li.line_item_type == "shipment" }.all? { |s| s.shipment.status == "shipped" }
|
179
165
|
self.status = "shipped"
|
180
|
-
elsif
|
166
|
+
elsif line_items.select { |li| li.line_item_type == "shipment" }.any? && line_items.select { |li| li.line_item_type == "shipment" }.all? { |s| s.shipment.status == "processing" }
|
181
167
|
self.status = "processing"
|
182
168
|
else
|
183
169
|
self.status = "new"
|
184
170
|
end
|
185
171
|
end
|
186
172
|
end
|
173
|
+
|
187
174
|
def set_new_record
|
188
175
|
self.was_new_record = self.new_record?
|
189
|
-
|
190
176
|
true
|
191
177
|
end
|
192
178
|
|
@@ -211,9 +197,9 @@ module Piggybak
|
|
211
197
|
def subtotal
|
212
198
|
v = 0
|
213
199
|
|
214
|
-
self.line_items.each do |line_item|
|
200
|
+
self.line_items.select { |li| li.line_item_type == "sellable" }.each do |line_item|
|
215
201
|
if !line_item._destroy
|
216
|
-
v += line_item.
|
202
|
+
v += line_item.price
|
217
203
|
end
|
218
204
|
end
|
219
205
|
|
@@ -1,11 +1,10 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class Payment < ActiveRecord::Base
|
3
3
|
belongs_to :order
|
4
|
-
acts_as_changer
|
5
4
|
belongs_to :payment_method
|
5
|
+
belongs_to :line_item
|
6
6
|
|
7
7
|
validates_presence_of :status
|
8
|
-
validates_presence_of :total
|
9
8
|
validates_presence_of :payment_method_id
|
10
9
|
validates_presence_of :month
|
11
10
|
validates_presence_of :year
|
@@ -13,6 +12,9 @@ module Piggybak
|
|
13
12
|
attr_accessor :number
|
14
13
|
attr_accessor :verification_value
|
15
14
|
|
15
|
+
attr_accessible :number, :verification_value, :month, :year,
|
16
|
+
:transaction_id, :masked_number, :payment_method_id
|
17
|
+
|
16
18
|
def status_enum
|
17
19
|
["paid"]
|
18
20
|
end
|
@@ -30,30 +32,27 @@ module Piggybak
|
|
30
32
|
"month" => self.month,
|
31
33
|
"year" => self.year,
|
32
34
|
"verification_value" => self.verification_value,
|
33
|
-
"first_name" => self.order.billing_address.firstname,
|
34
|
-
"last_name" => self.order.billing_address.lastname }
|
35
|
+
"first_name" => self.line_item ? self.line_item.order.billing_address.firstname : nil,
|
36
|
+
"last_name" => self.line_item ? self.line_item.order.billing_address.lastname : nil }
|
35
37
|
end
|
36
38
|
|
37
|
-
def process
|
39
|
+
def process(order)
|
40
|
+
return true if !self.new_record?
|
41
|
+
|
38
42
|
ActiveMerchant::Billing::Base.mode = Piggybak.config.activemerchant_mode
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:masked_number => self.number.mask_cc_number }
|
49
|
-
gateway.capture(self.order.total_due*100, gateway_response.authorization, { :credit_card => p_credit_card } )
|
50
|
-
return true
|
51
|
-
else
|
52
|
-
self.errors.add :payment_method_id, gateway_response.message
|
53
|
-
return false
|
54
|
-
end
|
55
|
-
else
|
44
|
+
payment_gateway = self.payment_method.klass.constantize
|
45
|
+
gateway = payment_gateway::KLASS.new(self.payment_method.key_values)
|
46
|
+
p_credit_card = ActiveMerchant::Billing::CreditCard.new(self.credit_card)
|
47
|
+
gateway_response = gateway.authorize(order.total_due*100, p_credit_card, :address => order.avs_address)
|
48
|
+
if gateway_response.success?
|
49
|
+
self.attributes = { :transaction_id => payment_gateway.transaction_id(gateway_response),
|
50
|
+
:masked_number => self.number.mask_cc_number }
|
51
|
+
gateway.capture(order.total_due*100, gateway_response.authorization, { :credit_card => p_credit_card } )
|
56
52
|
return true
|
53
|
+
else
|
54
|
+
self.errors.add :payment_method_id, gateway_response.message
|
55
|
+
return false
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
@@ -66,20 +65,19 @@ module Piggybak
|
|
66
65
|
return
|
67
66
|
end
|
68
67
|
|
69
|
-
def
|
68
|
+
def details
|
70
69
|
if !self.new_record?
|
71
|
-
return "Payment ##{self.id} (#{self.created_at.strftime("%m-%d-%Y")}): "
|
72
|
-
"$#{"%.2f" % self.total}"
|
70
|
+
return "Payment ##{self.id} (#{self.created_at.strftime("%m-%d-%Y")}): " #+
|
71
|
+
#"$#{"%.2f" % self.total}" reference line item total here instead
|
73
72
|
else
|
74
73
|
return ""
|
75
74
|
end
|
76
75
|
end
|
77
|
-
alias :details :admin_label
|
78
76
|
|
79
77
|
validates_each :payment_method_id do |record, attr, value|
|
80
78
|
if record.new_record?
|
81
|
-
|
82
|
-
|
79
|
+
credit_card = ActiveMerchant::Billing::CreditCard.new(record.credit_card)
|
80
|
+
|
83
81
|
if !credit_card.valid?
|
84
82
|
credit_card.errors.each do |key, value|
|
85
83
|
if value.any? && !["first_name", "last_name", "type"].include?(key)
|
@@ -1,5 +1,5 @@
|
|
1
|
-
class Piggybak::
|
2
|
-
belongs_to :item, :polymorphic => true, :inverse_of => :
|
1
|
+
class Piggybak::Sellable < ActiveRecord::Base
|
2
|
+
belongs_to :item, :polymorphic => true, :inverse_of => :piggybak_sellable
|
3
3
|
attr_accessible :sku, :description, :price, :quantity, :active, :unlimited_inventory, :item_id, :item_type
|
4
4
|
attr_accessible :item # to allow direct assignment from code or console
|
5
5
|
|
@@ -9,9 +9,11 @@ class Piggybak::Variant < ActiveRecord::Base
|
|
9
9
|
validates_presence_of :price
|
10
10
|
validates_presence_of :item_type
|
11
11
|
validates_numericality_of :quantity, :only_integer => true, :greater_than_or_equal_to => 0
|
12
|
-
|
12
|
+
|
13
|
+
has_many :line_items, :as => :reference, :inverse_of => :reference
|
14
|
+
|
13
15
|
def admin_label
|
14
|
-
"
|
16
|
+
"Sellable: #{self.description}"
|
15
17
|
end
|
16
18
|
|
17
19
|
def update_inventory(purchased)
|
@@ -1,22 +1,16 @@
|
|
1
1
|
module Piggybak
|
2
2
|
class Shipment < ActiveRecord::Base
|
3
3
|
belongs_to :order
|
4
|
-
acts_as_changer
|
5
4
|
belongs_to :shipping_method
|
5
|
+
belongs_to :line_item
|
6
6
|
|
7
7
|
validates_presence_of :status
|
8
|
-
validates_presence_of :total
|
9
8
|
validates_presence_of :shipping_method_id
|
10
|
-
|
9
|
+
|
10
|
+
attr_accessible :shipping_method_id, :status
|
11
|
+
|
11
12
|
def status_enum
|
12
13
|
["new", "processing", "shipped"]
|
13
14
|
end
|
14
|
-
|
15
|
-
def admin_label
|
16
|
-
"Shipment ##{self.id}<br />" +
|
17
|
-
"#{self.shipping_method.description}<br />" +
|
18
|
-
"Status: #{self.status}<br />" +
|
19
|
-
"$#{"%.2f" % self.total}"
|
20
|
-
end
|
21
15
|
end
|
22
16
|
end
|