refinerycms-stores 0.0.1
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 +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 -%>
|