refinerycms-stores 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +26 -0
- data/Gemfile.lock +306 -0
- data/Guardfile +27 -0
- data/LICENSE.txt +20 -0
- data/Rakefile +65 -0
- data/VERSION +1 -0
- data/app/assets/stylesheets/refinery/refinerycms-stores.sass +76 -0
- data/app/controllers/refinery/addresses/admin/addresses_controller.rb +12 -0
- data/app/controllers/refinery/orders/admin/orders_controller.rb +12 -0
- data/app/controllers/refinery/orders/orders_controller.rb +96 -0
- data/app/controllers/refinery/products/admin/products_controller.rb +12 -0
- data/app/controllers/refinery/products/products_controller.rb +17 -0
- data/app/controllers/refinery/stores/admin/stores_controller.rb +12 -0
- data/app/controllers/refinery/stores/stores_controller.rb +80 -0
- data/app/helpers/refinery/orders/orders_helper.rb +11 -0
- data/app/models/refinery/addresses/address.rb +12 -0
- data/app/models/refinery/orders/line_item.rb +24 -0
- data/app/models/refinery/orders/order.rb +364 -0
- data/app/models/refinery/products/product.rb +17 -0
- data/app/models/refinery/stores/store.rb +33 -0
- data/app/views/refinery/addresses/admin/addresses/_actions.html.erb +25 -0
- data/app/views/refinery/addresses/admin/addresses/_address.html.erb +16 -0
- data/app/views/refinery/addresses/admin/addresses/_addresses.html.erb +2 -0
- data/app/views/refinery/addresses/admin/addresses/_form.html.erb +89 -0
- data/app/views/refinery/addresses/admin/addresses/_records.html.erb +18 -0
- data/app/views/refinery/addresses/admin/addresses/_sortable_list.html.erb +5 -0
- data/app/views/refinery/addresses/admin/addresses/edit.html.erb +1 -0
- data/app/views/refinery/addresses/admin/addresses/index.html.erb +7 -0
- data/app/views/refinery/addresses/admin/addresses/new.html.erb +1 -0
- data/app/views/refinery/orders/admin/orders/_actions.html.erb +25 -0
- data/app/views/refinery/orders/admin/orders/_form.html.erb +122 -0
- data/app/views/refinery/orders/admin/orders/_order.html.erb +20 -0
- data/app/views/refinery/orders/admin/orders/_orders.html.erb +2 -0
- data/app/views/refinery/orders/admin/orders/_records.html.erb +18 -0
- data/app/views/refinery/orders/admin/orders/_sortable_list.html.erb +5 -0
- data/app/views/refinery/orders/admin/orders/edit.html.erb +1 -0
- data/app/views/refinery/orders/admin/orders/index.html.erb +7 -0
- data/app/views/refinery/orders/admin/orders/new.html.erb +1 -0
- data/app/views/refinery/orders/orders/_address_form.html.haml +35 -0
- data/app/views/refinery/orders/orders/_card_form.html.haml +63 -0
- data/app/views/refinery/orders/orders/_order_snapshot.html.haml +44 -0
- data/app/views/refinery/orders/orders/confirmation.html.haml +26 -0
- data/app/views/refinery/orders/orders/edit.html.haml +36 -0
- data/app/views/refinery/orders/orders/index.html.haml +6 -0
- data/app/views/refinery/orders/orders/purchase.html.haml +25 -0
- data/app/views/refinery/orders/orders/show.html.haml +36 -0
- data/app/views/refinery/products/admin/products/_actions.html.erb +25 -0
- data/app/views/refinery/products/admin/products/_form.html.erb +127 -0
- data/app/views/refinery/products/admin/products/_product.html.erb +20 -0
- data/app/views/refinery/products/admin/products/_products.html.erb +2 -0
- data/app/views/refinery/products/admin/products/_records.html.erb +18 -0
- data/app/views/refinery/products/admin/products/_sortable_list.html.erb +5 -0
- data/app/views/refinery/products/admin/products/edit.html.erb +4 -0
- data/app/views/refinery/products/admin/products/index.html.erb +10 -0
- data/app/views/refinery/products/admin/products/new.html.erb +4 -0
- data/app/views/refinery/products/products/show.html.erb +105 -0
- data/app/views/refinery/stores/admin/stores/_actions.html.erb +57 -0
- data/app/views/refinery/stores/admin/stores/_form.html.erb +56 -0
- data/app/views/refinery/stores/admin/stores/_records.html.erb +18 -0
- data/app/views/refinery/stores/admin/stores/_sortable_list.html.erb +5 -0
- data/app/views/refinery/stores/admin/stores/_store.html.erb +20 -0
- data/app/views/refinery/stores/admin/stores/_stores.html.erb +2 -0
- data/app/views/refinery/stores/admin/stores/edit.html.erb +4 -0
- data/app/views/refinery/stores/admin/stores/index.html.erb +13 -0
- data/app/views/refinery/stores/admin/stores/new.html.erb +4 -0
- data/app/views/refinery/stores/stores/_cart.html.erb +22 -0
- data/app/views/refinery/stores/stores/_cart_item.html.erb +9 -0
- data/app/views/refinery/stores/stores/_list_product.html.erb +16 -0
- data/app/views/refinery/stores/stores/index.html.erb +11 -0
- data/app/views/refinery/stores/stores/show.html.erb +28 -0
- data/config/locales/en.yml +143 -0
- data/config/locales/es.yml +143 -0
- data/config/locales/fr.yml +139 -0
- data/config/locales/nb.yml +143 -0
- data/config/locales/nl.yml +143 -0
- data/config/routes.rb +81 -0
- data/db/migrate/1_create_stores_stores.rb +29 -0
- data/db/migrate/2_create_products_products.rb +44 -0
- data/db/migrate/3_create_orders_orders.rb +44 -0
- data/db/migrate/4_create_orders_line_items.rb +20 -0
- data/db/migrate/5_create_addresses_addresses.rb +39 -0
- data/db/seeds.rb +23 -0
- data/lib/generators/refinery/stores_generator.rb +17 -0
- data/lib/refinery/addresses/engine.rb +31 -0
- data/lib/refinery/addresses.rb +21 -0
- data/lib/refinery/cart.rb +36 -0
- data/lib/refinery/cart_item.rb +28 -0
- data/lib/refinery/orders/engine.rb +31 -0
- data/lib/refinery/orders.rb +21 -0
- data/lib/refinery/products/engine.rb +31 -0
- data/lib/refinery/products.rb +21 -0
- data/lib/refinery/store_gateway.rb +106 -0
- data/lib/refinery/stores/engine.rb +31 -0
- data/lib/refinery/stores.rb +21 -0
- data/lib/refinerycms-stores.rb +7 -0
- data/lib/tasks/refinery/stores.rake +13 -0
- data/readme.md +10 -0
- data/refinerycms-stores.gemspec +261 -0
- data/spec/models/refinery/addresses/address_spec.rb +18 -0
- data/spec/models/refinery/orders/order_spec.rb +18 -0
- data/spec/models/refinery/products/product_spec.rb +18 -0
- data/spec/models/refinery/stores/store_spec.rb +18 -0
- data/spec/requests/refinery/addresses/admin/addresses_spec.rb +101 -0
- data/spec/requests/refinery/orders/admin/orders_spec.rb +101 -0
- data/spec/requests/refinery/products/admin/products_spec.rb +101 -0
- data/spec/requests/refinery/stores/admin/stores_spec.rb +101 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/factories/refinery/addresses.rb +7 -0
- data/spec/support/factories/refinery/orders.rb +7 -0
- data/spec/support/factories/refinery/products.rb +7 -0
- data/spec/support/factories/refinery/stores.rb +7 -0
- data/tasks/rspec.rake +6 -0
- data/tasks/testing.rake +8 -0
- data/test/helper.rb +17 -0
- data/test/test_refinerycms-cart.rb +7 -0
- metadata +571 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Refinery
|
2
|
+
module Stores
|
3
|
+
class StoresController < ::ApplicationController
|
4
|
+
|
5
|
+
before_filter :find_all_stores, :only => :index
|
6
|
+
before_filter :find_page, :except => [:index, :add_to_cart, :empty_cart, :checkout]
|
7
|
+
before_filter :find_cart, :except => :empty_cart
|
8
|
+
|
9
|
+
def index
|
10
|
+
# you can use meta fields from your model instead (e.g. browser_title)
|
11
|
+
# by swapping @page for @store in the line below:
|
12
|
+
present(@page)
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
@store = Store.find(params[:id])
|
17
|
+
|
18
|
+
# you can use meta fields from your model instead (e.g. browser_title)
|
19
|
+
# by swapping @page for @store in the line below:
|
20
|
+
present(@page)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_to_cart
|
24
|
+
begin
|
25
|
+
product = ::Refinery::Products::Product.find(params[:id])
|
26
|
+
rescue ActiveRecord::RecordNotFound
|
27
|
+
logger.error("Attempt to access invalid product #{params[:id]}")
|
28
|
+
redirect_to_index("Invalid product")
|
29
|
+
else
|
30
|
+
@current_item = @cart.add_product(product)
|
31
|
+
redirect_to_index unless request.xhr?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty_cart
|
36
|
+
session[:cart] = nil
|
37
|
+
redirect_to_index
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def checkout
|
42
|
+
if @cart.items.empty?
|
43
|
+
redirect_to_index("Your cart is empty")
|
44
|
+
else
|
45
|
+
@order = ::Refinery::Orders::Order.checkout!( @cart )
|
46
|
+
session[:cart] = nil
|
47
|
+
redirect_to refinery.edit_orders_order_path( @order )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def redirect_to_index(msg = nil)
|
54
|
+
flash[:notice] = msg if msg
|
55
|
+
redirect_to refinery.stores_store_path( find_first_store )
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def find_cart
|
60
|
+
@cart = (session[:cart] ||= Cart.new)
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def find_first_store
|
66
|
+
@store = Store.order('position ASC').first
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def find_all_stores
|
71
|
+
@stores = Store.order('position ASC')
|
72
|
+
end
|
73
|
+
|
74
|
+
def find_page
|
75
|
+
@page = ::Refinery::Page.where(:link_url => "/stores").first
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Refinery
|
2
|
+
module Addresses
|
3
|
+
class Address < Refinery::Core::BaseModel
|
4
|
+
self.table_name = :refinery_addresses
|
5
|
+
# acts_as_indexed :fields => [:first_name, :last_name, :phone, :email, :address1, :address2, :city, :state, :zip, :country]
|
6
|
+
|
7
|
+
validates :first_name, :presence => true
|
8
|
+
validates :last_name, :presence => true
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Refinery
|
2
|
+
module Orders
|
3
|
+
class LineItem < Refinery::Core::BaseModel
|
4
|
+
self.table_name = :refinery_line_items
|
5
|
+
|
6
|
+
belongs_to :order
|
7
|
+
belongs_to :product, :class_name => ::Refinery::Products::Product
|
8
|
+
|
9
|
+
# validates :order_status, :presence => true, :uniqueness => true
|
10
|
+
|
11
|
+
def self.from_cart_item( item )
|
12
|
+
return self.create(
|
13
|
+
:product => item.product,
|
14
|
+
:quantity => item.quantity,
|
15
|
+
:unit_price => item.price
|
16
|
+
)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class LineItem
|
21
|
+
|
22
|
+
end # module Orders
|
23
|
+
|
24
|
+
end # module Refinery
|
@@ -0,0 +1,364 @@
|
|
1
|
+
module Refinery
|
2
|
+
module Orders
|
3
|
+
# #########################################################################
|
4
|
+
class OrderProcessingException < SecurityError; end
|
5
|
+
# #########################################################################
|
6
|
+
# #########################################################################
|
7
|
+
# #########################################################################
|
8
|
+
class Order < Refinery::Core::BaseModel
|
9
|
+
|
10
|
+
require 'aasm'
|
11
|
+
|
12
|
+
self.table_name = :refinery_orders
|
13
|
+
|
14
|
+
has_many :addresses, :class_name => ::Refinery::Addresses::Address
|
15
|
+
has_many :line_items
|
16
|
+
|
17
|
+
# belongs_to :customer
|
18
|
+
# belongs_to :shipping_type
|
19
|
+
# belongs_to :discount
|
20
|
+
# belongs_to :shipping_vendor
|
21
|
+
|
22
|
+
before_create :generate_order_number
|
23
|
+
|
24
|
+
has_one :billing_address, :class_name => ::Refinery::Addresses::Address,
|
25
|
+
:conditions => { :is_billing => true }
|
26
|
+
|
27
|
+
has_one :shipping_address, :class_name => ::Refinery::Addresses::Address,
|
28
|
+
:conditions => { :is_billing => false }
|
29
|
+
|
30
|
+
# #########################################################################
|
31
|
+
# attr_protected :order_number
|
32
|
+
attr_accessible :order_notes, :shipping_type
|
33
|
+
# #########################################################################
|
34
|
+
|
35
|
+
# t.integer :order_number, :null => false, :unique => true
|
36
|
+
# t.references :order_customer
|
37
|
+
# t.string :order_status, :default => '', :null => false
|
38
|
+
# t.text :order_notes
|
39
|
+
# t.references :shipping_type
|
40
|
+
# t.datetime :shipped_on
|
41
|
+
# t.float :product_cost, :default => 0.0, :limit => 10
|
42
|
+
# t.float :shipping_cost, :default => 0.0, :limit => 10
|
43
|
+
# t.float :tax_cost, :default => 0.0, :limit => 5
|
44
|
+
|
45
|
+
# #########################################################################
|
46
|
+
# AASM STATE MACHINE AREA ... try to keep generic!
|
47
|
+
# #########################################################################
|
48
|
+
|
49
|
+
include AASM
|
50
|
+
|
51
|
+
# #########################################################################
|
52
|
+
# STATE MACHINE STATES
|
53
|
+
# #########################################################################
|
54
|
+
aasm_column :order_status # expects this field for persistance of state
|
55
|
+
|
56
|
+
# default starting state for order objects
|
57
|
+
aasm_initial_state :checkout_started
|
58
|
+
|
59
|
+
# initial state: cart becomes an "empty" order (nothing else filled in)
|
60
|
+
aasm_state :checkout_started
|
61
|
+
|
62
|
+
# 1 order processing states
|
63
|
+
aasm_state :edit_stage_completed, :enter => :edit_done
|
64
|
+
|
65
|
+
# 2 automatic states for purchased
|
66
|
+
aasm_state :purchase_confirmed, :enter => :order_confirmed
|
67
|
+
aasm_state :order_completed, :enter => :order_completed
|
68
|
+
|
69
|
+
# 2 defunct or cancelled states
|
70
|
+
aasm_state :refunded_defunct, :enter => :refund2defunct
|
71
|
+
|
72
|
+
# #########################################################################
|
73
|
+
# STATE MACHINE EVENT HANDLING
|
74
|
+
# #########################################################################
|
75
|
+
#
|
76
|
+
# Further note:
|
77
|
+
# if all digital download, skip delivery method
|
78
|
+
# if all digital download && gateway_handles_billing, skip address
|
79
|
+
# if gateway_does_everything, skip payment selection
|
80
|
+
#
|
81
|
+
# ---------------------------------------------------------------------------
|
82
|
+
# next_in_process -- advances order thru order wizard stages
|
83
|
+
# ---------------------------------------------------------------------------
|
84
|
+
aasm_event :next_in_process do
|
85
|
+
transitions :to => :edit_stage_completed, :from => :checkout_started
|
86
|
+
end
|
87
|
+
|
88
|
+
# ---------------------------------------------------------------------------
|
89
|
+
# confirm_purchase -- user definitely confirms purchase intent
|
90
|
+
# ---------------------------------------------------------------------------
|
91
|
+
aasm_event :confirm_purchase do
|
92
|
+
transitions :to => :purchase_confirmed, :from => :edit_stage_completed
|
93
|
+
end
|
94
|
+
|
95
|
+
# ---------------------------------------------------------------------------
|
96
|
+
# payment_verfified -- payment gateway verifies funds transfer
|
97
|
+
# ---------------------------------------------------------------------------
|
98
|
+
aasm_event :payment_verified do
|
99
|
+
transitions :to => :order_completed, :from => :purchase_confirmed
|
100
|
+
end
|
101
|
+
|
102
|
+
# ---------------------------------------------------------------------------
|
103
|
+
# purchase_failed -- payment gateway failed
|
104
|
+
# backs state to select a payment method
|
105
|
+
# ---------------------------------------------------------------------------
|
106
|
+
aasm_event( :purchase_failed, :success => :void_payment ) do
|
107
|
+
transitions :to => :delivery_stage_completed, :from => :purchase_confirmed
|
108
|
+
end
|
109
|
+
|
110
|
+
# ---------------------------------------------------------------------------
|
111
|
+
# refund_order -- order fully refunded making it defunct
|
112
|
+
# ---------------------------------------------------------------------------
|
113
|
+
aasm_event :refund_order do
|
114
|
+
transitions :to => :refunded_defunct, :from => :order_completed
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# ---------------------------------------------------------------------------
|
119
|
+
# restart_checkout -- return to the edit stage
|
120
|
+
# ---------------------------------------------------------------------------
|
121
|
+
aasm_event :restart_checkout do
|
122
|
+
transitions :to => :checkout_started,
|
123
|
+
:from => [:checkout_started, :edit_stage_completed,
|
124
|
+
:purchase_confirmed ]
|
125
|
+
end
|
126
|
+
|
127
|
+
# ---------------------------------------------------------------------------
|
128
|
+
# cancel_process -- cancel the order wizard process, void any payt tokens
|
129
|
+
# ---------------------------------------------------------------------------
|
130
|
+
aasm_event( :cancel_process, :success => :void_payment ) do
|
131
|
+
transitions :to => :checkout_started,
|
132
|
+
:from => [:checkout_started, :edit_stage_completed ]
|
133
|
+
end
|
134
|
+
|
135
|
+
# #########################################################################
|
136
|
+
# #########################################################################
|
137
|
+
# END OF AASM AREA
|
138
|
+
# #########################################################################
|
139
|
+
# #########################################################################
|
140
|
+
|
141
|
+
# ---------------------------------------------------------------------------
|
142
|
+
# get_render_template -- returns template name for next edit render
|
143
|
+
# returns nil if an invalid state
|
144
|
+
# ---------------------------------------------------------------------------
|
145
|
+
def get_render_template
|
146
|
+
action = {
|
147
|
+
:checkout_started => :edit,
|
148
|
+
:edit_stage_completed => :confirmation
|
149
|
+
}[ self.order_status.to_sym ]
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
# ---------------------------------------------------------------------------
|
154
|
+
# handle_update -- handle user params for updating order
|
155
|
+
# returns bill_address, ship_address objects (or nil for both)
|
156
|
+
# which might have errors
|
157
|
+
# ---------------------------------------------------------------------------
|
158
|
+
def handle_update( params )
|
159
|
+
if checkout_started?
|
160
|
+
bill_address, ship_address = update_addresses( params )
|
161
|
+
next_in_process! if bill_address.errors.empty? && ship_address.errors.empty?
|
162
|
+
end
|
163
|
+
|
164
|
+
self.cc_token = params[:stripeToken]
|
165
|
+
self.cc_last4 = params[:last4]
|
166
|
+
self.cc_card_type = params[:card_type]
|
167
|
+
self.cc_expiry_month = params[:expiry_month]
|
168
|
+
self.cc_expiry_year = params[:expiry_year]
|
169
|
+
self.save!
|
170
|
+
|
171
|
+
|
172
|
+
return bill_address, ship_address
|
173
|
+
end
|
174
|
+
|
175
|
+
# ---------------------------------------------------------------------------
|
176
|
+
# checkout! -- checkout converts the cart into a fledgling order
|
177
|
+
# ---------------------------------------------------------------------------
|
178
|
+
def self.checkout!( cart )
|
179
|
+
order = Order.new
|
180
|
+
|
181
|
+
# convert cart.items to order.line_items
|
182
|
+
cart.items.each do | item |
|
183
|
+
order.line_items.build(
|
184
|
+
:product => item.product,
|
185
|
+
:quantity => item.quantity,
|
186
|
+
:unit_price => item.price
|
187
|
+
)
|
188
|
+
end # do
|
189
|
+
|
190
|
+
order.save!
|
191
|
+
|
192
|
+
return order
|
193
|
+
end
|
194
|
+
|
195
|
+
# TODO: convert customer's billing/shipping addresses to order addresses
|
196
|
+
|
197
|
+
# ---------------------------------------------------------------------------
|
198
|
+
# ---------------------------------------------------------------------------
|
199
|
+
def get_billship_addresses()
|
200
|
+
bill_address = self.billing_address || ::Refinery::Addresses::Address.new( :is_billing => true )
|
201
|
+
|
202
|
+
# ship needs to be populated from bill if no ship already present
|
203
|
+
ship_address = self.shipping_address ||
|
204
|
+
::Refinery::Addresses::Address.new(
|
205
|
+
:first_name => bill_address.first_name ,
|
206
|
+
:last_name => bill_address.last_name ,
|
207
|
+
:phone => bill_address.phone ,
|
208
|
+
:email => bill_address.email ,
|
209
|
+
:address1 => bill_address.address1 ,
|
210
|
+
:address2 => bill_address.address2 ,
|
211
|
+
:city => bill_address.city ,
|
212
|
+
:state => bill_address.state ,
|
213
|
+
:zip => bill_address.zip ,
|
214
|
+
:country => bill_address.country ,
|
215
|
+
:is_billing => false
|
216
|
+
)
|
217
|
+
|
218
|
+
return bill_address, ship_address
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# ---------------------------------------------------------------------------
|
223
|
+
# update_addresses -- complex logic for dealing with 2 order addresses
|
224
|
+
# return bill_address, ship_address objects
|
225
|
+
# ---------------------------------------------------------------------------
|
226
|
+
def update_addresses( params )
|
227
|
+
# if bill exists; update with parameters
|
228
|
+
# else create bill
|
229
|
+
if (bill_address = self.billing_address)
|
230
|
+
bill_address.update_attributes( params[:billing_address] )
|
231
|
+
else
|
232
|
+
bill_address = self.addresses.create(
|
233
|
+
params[:billing_address].merge( { :is_billing => true } )
|
234
|
+
)
|
235
|
+
end # if..then..else billing address setup
|
236
|
+
|
237
|
+
# continue if error free
|
238
|
+
if bill_address.errors.empty?
|
239
|
+
|
240
|
+
# does customer wants same address for both ?
|
241
|
+
ship_params = ( params[:use_billing] ?
|
242
|
+
params[:billing_address] :
|
243
|
+
params[:shipping_address]
|
244
|
+
).merge( { :is_billing => false } )
|
245
|
+
|
246
|
+
if (ship_address = self.shipping_address)
|
247
|
+
ship_address.update_attributes( ship_params )
|
248
|
+
else
|
249
|
+
ship_address = self.addresses.create( ship_params )
|
250
|
+
end # if..then..else shiping address setup
|
251
|
+
|
252
|
+
else # need placeholder for re-editing the order
|
253
|
+
ship_address = self.addresses.build( params[:shipping_address] )
|
254
|
+
end # if no bill address errors
|
255
|
+
|
256
|
+
return bill_address, ship_address
|
257
|
+
end
|
258
|
+
|
259
|
+
# ---------------------------------------------------------------------------
|
260
|
+
# ---------------------------------------------------------------------------
|
261
|
+
|
262
|
+
def total_items
|
263
|
+
line_items.sum { |item| item.quantity }
|
264
|
+
end
|
265
|
+
|
266
|
+
|
267
|
+
def total_price
|
268
|
+
line_items.sum { |item| item.unit_price * item.quantity }
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
# ---------------------------------------------------------------------------
|
273
|
+
# process_purchase -- charges CC for the purchase
|
274
|
+
# returns nil if error (self.errors captures), or result JSON obj
|
275
|
+
# ---------------------------------------------------------------------------
|
276
|
+
def process_purchase( store_gateway )
|
277
|
+
raise OrderProcessingException unless self.purchase_confirmed?
|
278
|
+
|
279
|
+
result = store_gateway.charge_purchase(
|
280
|
+
self.cc_token,
|
281
|
+
self.product_total + self.shipping_charges + self.tax_charges,
|
282
|
+
self,
|
283
|
+
self.billing_address.email
|
284
|
+
)
|
285
|
+
|
286
|
+
unless result.nil? # unless there were errors ...
|
287
|
+
self.cc_purchased_on = Time.now # date of purchase
|
288
|
+
self.cc_confirmation_id = result.id # confirmation code
|
289
|
+
# next step saves above changes IFF paid is true
|
290
|
+
self.payment_verified! if result.paid # state becomes paid & final
|
291
|
+
end # unless there were errors
|
292
|
+
|
293
|
+
return result
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
# ---------------------------------------------------------------------------
|
298
|
+
# ---------------------------------------------------------------------------
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
# ---------------------------------------------------------------------------
|
303
|
+
# edit_done -- things to do after edit input & verified
|
304
|
+
# ---------------------------------------------------------------------------
|
305
|
+
def edit_done()
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
# ---------------------------------------------------------------------------
|
310
|
+
# order_confirmed -- things to do after user confirmed purchase decision
|
311
|
+
# ---------------------------------------------------------------------------
|
312
|
+
def order_confirmed()
|
313
|
+
self.product_total = total_price
|
314
|
+
# shipping_charges
|
315
|
+
# tax_charges
|
316
|
+
save!
|
317
|
+
end
|
318
|
+
|
319
|
+
# ---------------------------------------------------------------------------
|
320
|
+
# order_completed -- things to do after purchase verified by gateway
|
321
|
+
# ---------------------------------------------------------------------------
|
322
|
+
def order_completed()
|
323
|
+
# create a confirmation code
|
324
|
+
# kick off shipping process
|
325
|
+
end
|
326
|
+
|
327
|
+
# ---------------------------------------------------------------------------
|
328
|
+
# refund2defunct -- things to do after refunding an order making it defunct
|
329
|
+
# ---------------------------------------------------------------------------
|
330
|
+
def refund2defunct()
|
331
|
+
end
|
332
|
+
|
333
|
+
# ---------------------------------------------------------------------------
|
334
|
+
# ---------------------------------------------------------------------------
|
335
|
+
def void_payment
|
336
|
+
|
337
|
+
end
|
338
|
+
|
339
|
+
# ---------------------------------------------------------------------------
|
340
|
+
# ---------------------------------------------------------------------------
|
341
|
+
def generate_order_number
|
342
|
+
nbr = 0
|
343
|
+
max = 0xfffffffe
|
344
|
+
tries = max # deadman counter to prevent infinite loop
|
345
|
+
|
346
|
+
while ( tries > 0 && Order.find_by_order_number(nbr = rand( max ) + 1) ) do
|
347
|
+
tries = tries - 1 # decrement deadman counter
|
348
|
+
end # while
|
349
|
+
raise ArgumentError, "maxed out store order numbers?!" if tries.zero?
|
350
|
+
|
351
|
+
self.order_number = nbr
|
352
|
+
end
|
353
|
+
|
354
|
+
# ---------------------------------------------------------------------------
|
355
|
+
# ---------------------------------------------------------------------------
|
356
|
+
|
357
|
+
# ---------------------------------------------------------------------------
|
358
|
+
# ---------------------------------------------------------------------------
|
359
|
+
|
360
|
+
# #########################################################################
|
361
|
+
end # class Order
|
362
|
+
# #########################################################################
|
363
|
+
end # module Orders
|
364
|
+
end # module Refinery
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Refinery
|
2
|
+
module Products
|
3
|
+
class Product < Refinery::Core::BaseModel
|
4
|
+
self.table_name = :refinery_products
|
5
|
+
acts_as_indexed :fields => [:name, :code, :description]
|
6
|
+
|
7
|
+
validates :name, :presence => true, :uniqueness => true
|
8
|
+
|
9
|
+
belongs_to :main_pic, :class_name => '::Refinery::Image'
|
10
|
+
belongs_to :store, :class_name => '::Refinery::Stores::Store'
|
11
|
+
has_many :line_items, :class_name => '::Refinery::Orders::LineItem'
|
12
|
+
|
13
|
+
scope :active, lambda { |i| where(["date_available <= ? ", Time.now ]) }
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Refinery
|
2
|
+
module Stores
|
3
|
+
class Store < Refinery::Core::BaseModel
|
4
|
+
self.table_name = :refinery_stores
|
5
|
+
|
6
|
+
acts_as_indexed :fields => [:name, :meta_keywords, :description]
|
7
|
+
|
8
|
+
validates :name, :presence => true, :uniqueness => true
|
9
|
+
|
10
|
+
has_many :products
|
11
|
+
# we may want to expand the concept of active in the future
|
12
|
+
has_many :active_products, :class_name => '::Refinery::Products::Product', :foreign_key => 'store_id'
|
13
|
+
|
14
|
+
# -----------------------------------------------------------------------
|
15
|
+
# select_list -- returns an html select list suitable for selecting a store
|
16
|
+
# -----------------------------------------------------------------------
|
17
|
+
def self.select_list()
|
18
|
+
all.map{ |x| [x.name, x.id] }
|
19
|
+
end
|
20
|
+
|
21
|
+
# -----------------------------------------------------------------------
|
22
|
+
# -----------------------------------------------------------------------
|
23
|
+
|
24
|
+
# -----------------------------------------------------------------------
|
25
|
+
# -----------------------------------------------------------------------
|
26
|
+
|
27
|
+
# -----------------------------------------------------------------------
|
28
|
+
# -----------------------------------------------------------------------
|
29
|
+
|
30
|
+
end # class Store
|
31
|
+
|
32
|
+
end # module Stores
|
33
|
+
end # module Refineryd
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<ul>
|
2
|
+
<% if ::Refinery::Addresses::Admin::AddressesController.searchable? %>
|
3
|
+
<li>
|
4
|
+
<%= render '/refinery/admin/search', :url => refinery.addresses_admin_addresses_path %>
|
5
|
+
</li>
|
6
|
+
<% end %>
|
7
|
+
<li>
|
8
|
+
<%= link_to t('.create_new'), refinery.new_addresses_admin_address_path,
|
9
|
+
:class => "add_icon" %>
|
10
|
+
</li>
|
11
|
+
<% if !searching? && ::Refinery::Addresses::Admin::AddressesController.sortable? && ::Refinery::Addresses::Address.any? %>
|
12
|
+
<li>
|
13
|
+
<%= link_to t('.reorder', :what => "Addresses"),
|
14
|
+
refinery.addresses_admin_addresses_path,
|
15
|
+
:id => "reorder_action",
|
16
|
+
:class => "reorder_icon" %>
|
17
|
+
|
18
|
+
<%= link_to t('.reorder_done', :what => "Addresses"),
|
19
|
+
refinery.addresses_admin_addresses_path,
|
20
|
+
:id => "reorder_action_done",
|
21
|
+
:style => "display: none;",
|
22
|
+
:class => "reorder_icon" %>
|
23
|
+
</li>
|
24
|
+
<% end %>
|
25
|
+
</ul>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<li class='clearfix record <%= cycle("on", "on-hover") %>' id="<%= dom_id(address) -%>">
|
2
|
+
<span class='title'>
|
3
|
+
<%= address.first_name %>
|
4
|
+
|
5
|
+
</span>
|
6
|
+
<span class='actions'>
|
7
|
+
|
8
|
+
<%= link_to refinery_icon_tag("application_edit.png"), refinery.edit_addresses_admin_address_path(address),
|
9
|
+
:title => t('.edit') %>
|
10
|
+
<%= link_to refinery_icon_tag("delete.png"), refinery.addresses_admin_address_path(address),
|
11
|
+
:class => "cancel confirm-delete",
|
12
|
+
:title => t('.delete'),
|
13
|
+
:confirm => t('message', :scope => 'refinery.admin.delete', :title => address.first_name),
|
14
|
+
:method => :delete %>
|
15
|
+
</span>
|
16
|
+
</li>
|
@@ -0,0 +1,89 @@
|
|
1
|
+
<%= form_for [refinery, :addresses_admin, @address] do |f| -%>
|
2
|
+
<%= render '/refinery/admin/error_messages',
|
3
|
+
:object => @address,
|
4
|
+
:include_object_name => true %>
|
5
|
+
|
6
|
+
|
7
|
+
<div class='field'>
|
8
|
+
<%= f.label :customer -%>
|
9
|
+
<%= f.text_field :customer, :class => 'larger widest' -%>
|
10
|
+
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class='field'>
|
14
|
+
<%= f.label :order -%>
|
15
|
+
<%= f.text_field :order -%>
|
16
|
+
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class='field'>
|
20
|
+
<%= f.label :is_billing -%>
|
21
|
+
<%= f.check_box :is_billing, :checked => @address[:is_billing] -%>
|
22
|
+
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class='field'>
|
26
|
+
<%= f.label :first_name -%>
|
27
|
+
<%= f.text_field :first_name -%>
|
28
|
+
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<div class='field'>
|
32
|
+
<%= f.label :last_name -%>
|
33
|
+
<%= f.text_field :last_name -%>
|
34
|
+
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<div class='field'>
|
38
|
+
<%= f.label :phone -%>
|
39
|
+
<%= f.text_field :phone -%>
|
40
|
+
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div class='field'>
|
44
|
+
<%= f.label :email -%>
|
45
|
+
<%= f.text_field :email -%>
|
46
|
+
|
47
|
+
</div>
|
48
|
+
|
49
|
+
<div class='field'>
|
50
|
+
<%= f.label :address1 -%>
|
51
|
+
<%= f.text_field :address1 -%>
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<div class='field'>
|
56
|
+
<%= f.label :address2 -%>
|
57
|
+
<%= f.text_field :address2 -%>
|
58
|
+
|
59
|
+
</div>
|
60
|
+
|
61
|
+
<div class='field'>
|
62
|
+
<%= f.label :city -%>
|
63
|
+
<%= f.text_field :city -%>
|
64
|
+
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div class='field'>
|
68
|
+
<%= f.label :state -%>
|
69
|
+
<%= f.text_field :state -%>
|
70
|
+
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<div class='field'>
|
74
|
+
<%= f.label :zip -%>
|
75
|
+
<%= f.text_field :zip -%>
|
76
|
+
|
77
|
+
</div>
|
78
|
+
|
79
|
+
<div class='field'>
|
80
|
+
<%= f.label :country -%>
|
81
|
+
<%= f.text_field :country -%>
|
82
|
+
|
83
|
+
</div>
|
84
|
+
|
85
|
+
<%= render '/refinery/admin/form_actions', :f => f,
|
86
|
+
:continue_editing => false,
|
87
|
+
:delete_title => t('delete', :scope => 'refinery.addresses.admin.addresses.address'),
|
88
|
+
:delete_confirmation => t('message', :scope => 'refinery.admin.delete', :title => @address.first_name) %>
|
89
|
+
<% end -%>
|