bodega 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +3 -0
- data/Gemfile +10 -6
- data/Gemfile.lock +50 -19
- data/VERSION +1 -1
- data/app/controllers/bodega/orders_controller.rb +21 -30
- data/app/helpers/bodega/application_helper.rb +1 -0
- data/app/helpers/bodega/cart_helper.rb +9 -25
- data/app/models/bodega/order.rb +113 -11
- data/app/models/bodega/order_product.rb +20 -26
- data/app/models/bodega/product.rb +9 -12
- data/app/views/bodega/orders/_cart.html.erb +26 -0
- data/app/views/bodega/orders/_cart_row.html.erb +24 -0
- data/app/views/bodega/orders/_shipping_row.html.erb +13 -0
- data/app/views/bodega/orders/edit.html.erb +1 -0
- data/app/views/bodega/orders/new.html.erb +2 -38
- data/bodega.gemspec +30 -12
- data/config/locales/en.yml +13 -0
- data/config/routes.rb +9 -9
- data/db/migrate/20121111170337_create_bodega_orders.rb +12 -0
- data/lib/bodega/engine.rb +0 -1
- data/lib/bodega/optional.rb +12 -0
- data/lib/bodega/payment_method/base.rb +5 -13
- data/lib/bodega/payment_method/paypal.rb +12 -4
- data/lib/bodega/payment_method.rb +0 -4
- data/lib/bodega/shipping_method/base.rb +71 -0
- data/lib/bodega/shipping_method/ups.rb +18 -0
- data/lib/bodega/shipping_method.rb +5 -0
- data/lib/bodega.rb +19 -1
- data/spec/lib/bodega/payment_method/base_spec.rb +12 -0
- data/spec/lib/bodega/shipping_method/base_spec.rb +12 -0
- data/spec/lib/bodega_spec.rb +18 -0
- data/spec/models/order_product_spec.rb +72 -0
- data/spec/models/order_spec.rb +93 -0
- data/spec/models/product_spec.rb +78 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/active_record.rb +43 -0
- data/spec/support/rails.rb +7 -0
- data/spec/support/vcr.rb +0 -0
- metadata +36 -18
@@ -1,41 +1,5 @@
|
|
1
|
-
<% if
|
1
|
+
<% if current_order.empty? -%>
|
2
2
|
<h3><%= t 'bodega.empty_cart' %></h3>
|
3
3
|
<% else -%>
|
4
|
-
<%=
|
5
|
-
<table id="bodega-cart">
|
6
|
-
<thead>
|
7
|
-
<tr><th class="product-name" colspan="2"><%= t 'bodega.product' %></th><th class="price"><%= t 'bodega.price' %></th><th class="total" colspan="2"><%= t 'bodega.total' %></th></tr>
|
8
|
-
</thead>
|
9
|
-
<tbody>
|
10
|
-
<% current_order.order_products.each do |order_product| -%>
|
11
|
-
<tr>
|
12
|
-
<td class="quantity-field">
|
13
|
-
<%= number_field_tag 'products[][quantity]', order_product.quantity, class: 'quantity', max: order_product.product.max_for_sale, min: 1 %>
|
14
|
-
</td>
|
15
|
-
<td class="product-name">
|
16
|
-
<%= order_product.name %>
|
17
|
-
<%= hidden_field_tag 'products[][type]', order_product.product_type %>
|
18
|
-
<%= hidden_field_tag 'products[][id]', order_product.product_id %>
|
19
|
-
</td>
|
20
|
-
<td class="price">
|
21
|
-
<%= humanized_money_with_symbol order_product.price %>
|
22
|
-
</td>
|
23
|
-
<td class="subtotal">
|
24
|
-
<%= humanized_money_with_symbol order_product.subtotal %>
|
25
|
-
</td>
|
26
|
-
<td class="remove">
|
27
|
-
<%= link_to t('bodega.remove'), bodega.remove_path(product_id: order_product.identifier) %>
|
28
|
-
</td>
|
29
|
-
</tr>
|
30
|
-
<% end -%>
|
31
|
-
<tr>
|
32
|
-
<td colspan="3"></td>
|
33
|
-
<td><%= humanized_money_with_symbol current_order.subtotal %></td>
|
34
|
-
<td></td>
|
35
|
-
</tr>
|
36
|
-
</tbody>
|
37
|
-
</table>
|
38
|
-
<%= button_tag t('bodega.update_cart'), id: 'bodega-update', name: :update, value: 1 %>
|
39
|
-
<%= button_tag t('bodega.checkout'), id: 'bodega-checkout', name: :checkout, value: 1 %>
|
40
|
-
<% end =%>
|
4
|
+
<%= render :partial => 'cart' %>
|
41
5
|
<% end -%>
|
data/bodega.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "bodega"
|
8
|
-
s.version = "0.3.
|
8
|
+
s.version = "0.3.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Flip Sasser"]
|
12
|
-
s.date = "2013-
|
12
|
+
s.date = "2013-02-04"
|
13
13
|
s.description = "Bodega adds checkout logic to any model in your app!"
|
14
14
|
s.email = "flip@x451.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -33,6 +33,10 @@ Gem::Specification.new do |s|
|
|
33
33
|
"app/models/bodega/order.rb",
|
34
34
|
"app/models/bodega/order_product.rb",
|
35
35
|
"app/models/bodega/product.rb",
|
36
|
+
"app/views/bodega/orders/_cart.html.erb",
|
37
|
+
"app/views/bodega/orders/_cart_row.html.erb",
|
38
|
+
"app/views/bodega/orders/_shipping_row.html.erb",
|
39
|
+
"app/views/bodega/orders/edit.html.erb",
|
36
40
|
"app/views/bodega/orders/new.html.erb",
|
37
41
|
"app/views/bodega/orders/show.html.erb",
|
38
42
|
"bodega.gemspec",
|
@@ -42,9 +46,13 @@ Gem::Specification.new do |s|
|
|
42
46
|
"db/migrate/20121111170420_create_bodega_order_products.rb",
|
43
47
|
"lib/bodega.rb",
|
44
48
|
"lib/bodega/engine.rb",
|
49
|
+
"lib/bodega/optional.rb",
|
45
50
|
"lib/bodega/payment_method.rb",
|
46
51
|
"lib/bodega/payment_method/base.rb",
|
47
52
|
"lib/bodega/payment_method/paypal.rb",
|
53
|
+
"lib/bodega/shipping_method.rb",
|
54
|
+
"lib/bodega/shipping_method/base.rb",
|
55
|
+
"lib/bodega/shipping_method/ups.rb",
|
48
56
|
"lib/bodega/version.rb",
|
49
57
|
"lib/generators/bodega/install/install_generator.rb",
|
50
58
|
"lib/generators/bodega/product/USAGE",
|
@@ -55,7 +63,17 @@ Gem::Specification.new do |s|
|
|
55
63
|
"lib/generators/bodega/productize/productize_generator.rb",
|
56
64
|
"lib/generators/bodega/productize/templates/migration.rb",
|
57
65
|
"lib/tasks/bodega_tasks.rake",
|
58
|
-
"script/rails"
|
66
|
+
"script/rails",
|
67
|
+
"spec/lib/bodega/payment_method/base_spec.rb",
|
68
|
+
"spec/lib/bodega/shipping_method/base_spec.rb",
|
69
|
+
"spec/lib/bodega_spec.rb",
|
70
|
+
"spec/models/order_product_spec.rb",
|
71
|
+
"spec/models/order_spec.rb",
|
72
|
+
"spec/models/product_spec.rb",
|
73
|
+
"spec/spec_helper.rb",
|
74
|
+
"spec/support/active_record.rb",
|
75
|
+
"spec/support/rails.rb",
|
76
|
+
"spec/support/vcr.rb"
|
59
77
|
]
|
60
78
|
s.homepage = "http://github.com/flipsasser/bodega"
|
61
79
|
s.licenses = ["MIT"]
|
@@ -67,24 +85,24 @@ Gem::Specification.new do |s|
|
|
67
85
|
s.specification_version = 3
|
68
86
|
|
69
87
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
70
|
-
s.add_runtime_dependency(%q<
|
88
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 3.2.11"])
|
89
|
+
s.add_runtime_dependency(%q<configurator2>, [">= 0.1.3"])
|
71
90
|
s.add_runtime_dependency(%q<i18n>, [">= 0"])
|
91
|
+
s.add_runtime_dependency(%q<maintain>, [">= 0"])
|
72
92
|
s.add_runtime_dependency(%q<money-rails>, [">= 0"])
|
73
|
-
s.add_development_dependency(%q<jeweler>, ["= 1.8.4"])
|
74
|
-
s.add_development_dependency(%q<pry>, [">= 0"])
|
75
93
|
else
|
76
|
-
s.add_dependency(%q<
|
94
|
+
s.add_dependency(%q<activerecord>, [">= 3.2.11"])
|
95
|
+
s.add_dependency(%q<configurator2>, [">= 0.1.3"])
|
77
96
|
s.add_dependency(%q<i18n>, [">= 0"])
|
97
|
+
s.add_dependency(%q<maintain>, [">= 0"])
|
78
98
|
s.add_dependency(%q<money-rails>, [">= 0"])
|
79
|
-
s.add_dependency(%q<jeweler>, ["= 1.8.4"])
|
80
|
-
s.add_dependency(%q<pry>, [">= 0"])
|
81
99
|
end
|
82
100
|
else
|
83
|
-
s.add_dependency(%q<
|
101
|
+
s.add_dependency(%q<activerecord>, [">= 3.2.11"])
|
102
|
+
s.add_dependency(%q<configurator2>, [">= 0.1.3"])
|
84
103
|
s.add_dependency(%q<i18n>, [">= 0"])
|
104
|
+
s.add_dependency(%q<maintain>, [">= 0"])
|
85
105
|
s.add_dependency(%q<money-rails>, [">= 0"])
|
86
|
-
s.add_dependency(%q<jeweler>, ["= 1.8.4"])
|
87
|
-
s.add_dependency(%q<pry>, [">= 0"])
|
88
106
|
end
|
89
107
|
end
|
90
108
|
|
data/config/locales/en.yml
CHANGED
@@ -1,3 +1,16 @@
|
|
1
1
|
en:
|
2
2
|
bodega:
|
3
|
+
total: "Total"
|
4
|
+
grand_total: "Grand total:"
|
5
|
+
check_out: "Check Out"
|
6
|
+
update_cart: "Update Cart"
|
3
7
|
empty_cart: "Your cart is currently empty."
|
8
|
+
sold_out: "Sorry, this product is sold out."
|
9
|
+
one_in_stock: "There is only one in stock!"
|
10
|
+
x_in_stock: "There are only x in stock!"
|
11
|
+
street_1: "123 Main Street"
|
12
|
+
street_2: "Apartment #1B"
|
13
|
+
city: "City"
|
14
|
+
postal_code: "Zip"
|
15
|
+
order_processed: "Your order has been processed! Thank you!"
|
16
|
+
order_failed: "There was a problem processing this order. Your account has not been charged."
|
data/config/routes.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
Bodega::Engine.routes.draw do
|
2
2
|
# Building orders
|
3
|
-
get '', as: :
|
4
|
-
|
3
|
+
get '', as: :new_order, to: 'orders#new'
|
4
|
+
resource :order, only: :create, path: '' do
|
5
|
+
# Add products to an order
|
6
|
+
post :add, as: :add_to, to: 'orders#add'
|
7
|
+
get 'remove/:product_id', as: :remove_from, constraints: {product_id: /.+\.\d+/}, to: 'orders#remove'
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# Processing orders
|
11
|
-
get :complete, to: 'orders#complete'
|
12
|
-
post :complete, to: 'orders#complete'
|
9
|
+
# Processing orders
|
10
|
+
get :complete, to: 'orders#complete'
|
11
|
+
post :complete, to: 'orders#complete'
|
12
|
+
end
|
13
13
|
|
14
14
|
# Existing orders
|
15
15
|
get ':id', as: :order, to: 'orders#show'
|
@@ -4,8 +4,20 @@ class CreateBodegaOrders < ActiveRecord::Migration
|
|
4
4
|
def change
|
5
5
|
create_table :bodega_orders do |t|
|
6
6
|
t.belongs_to :customer, polymorphic: true
|
7
|
+
t.integer :status
|
7
8
|
t.string :identifier, limit: 20
|
8
9
|
t.string :payment_id
|
10
|
+
t.string :shipping_rate_code
|
11
|
+
t.string :shipping_rate_name, limit: 50
|
12
|
+
t.text :shipping_rates
|
13
|
+
t.string :tracking_number
|
14
|
+
t.string :street_1, limit: 60
|
15
|
+
t.string :street_2, limit: 60
|
16
|
+
t.string :city, limit: 60
|
17
|
+
t.string :state, limit: 3
|
18
|
+
t.string :postal_code, limit: 11
|
19
|
+
t.string :country, limit: 3
|
20
|
+
t.money :shipping
|
9
21
|
t.money :tax
|
10
22
|
t.money :total
|
11
23
|
t.timestamps
|
data/lib/bodega/engine.rb
CHANGED
@@ -1,16 +1,9 @@
|
|
1
|
+
require 'bodega/optional'
|
2
|
+
|
1
3
|
module Bodega
|
2
4
|
module PaymentMethod
|
3
5
|
class Base
|
4
|
-
|
5
|
-
def options(*new_options)
|
6
|
-
option_namespace = self.name.split('::').pop.underscore
|
7
|
-
Bodega.class_eval do
|
8
|
-
option option_namespace do
|
9
|
-
options(*new_options.flatten)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
6
|
+
extend Bodega::Optional
|
14
7
|
|
15
8
|
attr_accessor :options, :order
|
16
9
|
|
@@ -18,13 +11,12 @@ module Bodega
|
|
18
11
|
raise "Implement #{self.class.name}#checkout_url"
|
19
12
|
end
|
20
13
|
|
21
|
-
def complete!
|
14
|
+
def complete!(options = {})
|
22
15
|
raise "Implement #{self.class.name}#complete!"
|
23
16
|
end
|
24
17
|
|
25
|
-
def initialize(order
|
18
|
+
def initialize(order)
|
26
19
|
self.order = order
|
27
|
-
self.options = options
|
28
20
|
end
|
29
21
|
end
|
30
22
|
end
|
@@ -10,13 +10,12 @@ module Bodega
|
|
10
10
|
response.redirect_uri
|
11
11
|
end
|
12
12
|
|
13
|
-
def complete!
|
13
|
+
def complete!(options = {})
|
14
14
|
response = client.checkout!(
|
15
15
|
options[:token],
|
16
16
|
options[:PayerID],
|
17
17
|
request
|
18
18
|
)
|
19
|
-
require 'pry'; binding.pry
|
20
19
|
response.payment_info.last.transaction_id
|
21
20
|
end
|
22
21
|
|
@@ -32,8 +31,17 @@ module Bodega
|
|
32
31
|
|
33
32
|
def request
|
34
33
|
@request ||= ::Paypal::Payment::Request.new(
|
35
|
-
amount: order.
|
36
|
-
description: order.order_products.map(&:quantity_and_name).to_sentence
|
34
|
+
amount: order.total.to_f,
|
35
|
+
description: order.order_products.map(&:quantity_and_name).to_sentence,
|
36
|
+
items: order.order_products.map {|order_product|
|
37
|
+
{
|
38
|
+
name: order_product.name,
|
39
|
+
amount: order_product.price.to_f,
|
40
|
+
quantity: order_product.quantity
|
41
|
+
}
|
42
|
+
},
|
43
|
+
shipping_amount: order.shipping.to_f,
|
44
|
+
tax_amount: order.tax.to_f
|
37
45
|
)
|
38
46
|
end
|
39
47
|
end
|
@@ -2,9 +2,5 @@ module Bodega
|
|
2
2
|
module PaymentMethod
|
3
3
|
autoload :Paypal, 'bodega/payment_method/paypal'
|
4
4
|
autoload :Plinq, 'bodega/payment_method/plinq'
|
5
|
-
|
6
|
-
def payment_method
|
7
|
-
@payment_method ||= "Bodega::PaymentMethod::#{Bodega.config.payment_method.to_s.classify}".constantize.new(current_order, params)
|
8
|
-
end
|
9
5
|
end
|
10
6
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'bodega/optional'
|
2
|
+
|
3
|
+
module Bodega
|
4
|
+
module ShippingMethod
|
5
|
+
class Base
|
6
|
+
extend Bodega::Optional
|
7
|
+
include ActiveMerchant::Shipping if defined?(ActiveMerchant)
|
8
|
+
|
9
|
+
attr_accessor :order
|
10
|
+
|
11
|
+
def initialize(order)
|
12
|
+
self.order = order
|
13
|
+
end
|
14
|
+
|
15
|
+
def rates
|
16
|
+
return {} unless packages.any?
|
17
|
+
@rates ||= {}.tap do |rates|
|
18
|
+
response = client.find_rates(origin, destination, packages)
|
19
|
+
response.rates.sort_by(&:price).each do |rate|
|
20
|
+
rates[rate.service_code] = {
|
21
|
+
name: rate.service_name,
|
22
|
+
price: rate.price
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def client
|
30
|
+
raise "Implement #{self.class}#client to return an instance of an ActiveMerchant::Shipping method"
|
31
|
+
end
|
32
|
+
|
33
|
+
def destination
|
34
|
+
@destination ||= location_for(order)
|
35
|
+
end
|
36
|
+
|
37
|
+
def location_for(location_object)
|
38
|
+
Location.new(
|
39
|
+
city: location_object.city,
|
40
|
+
state: location_object.state,
|
41
|
+
zip: location_object.postal_code,
|
42
|
+
country: location_object.country
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def origin
|
47
|
+
@origin ||= location_for(Bodega.config.shipping.origin)
|
48
|
+
end
|
49
|
+
|
50
|
+
def packages
|
51
|
+
@packages ||= [].tap do |packages|
|
52
|
+
order.products.each do |product|
|
53
|
+
packages.push(package_for(product)) if shippable?(product)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def package_for(product)
|
59
|
+
Package.new(
|
60
|
+
product.weight,
|
61
|
+
product.dimensions,
|
62
|
+
units: Bodega.config.shipping.units
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def shippable?(product)
|
67
|
+
product.respond_to?(:weight) && product.respond_to?(:dimensions)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bodega/shipping_method/base'
|
2
|
+
|
3
|
+
module Bodega
|
4
|
+
module ShippingMethod
|
5
|
+
class UPS < Base
|
6
|
+
options :login, :password, :api_key
|
7
|
+
|
8
|
+
protected
|
9
|
+
def client
|
10
|
+
@client ||= ActiveMerchant::Shipping::UPS.new(
|
11
|
+
login: Bodega.config.ups.login,
|
12
|
+
password: Bodega.config.ups.password,
|
13
|
+
key: Bodega.config.ups.api_key
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/bodega.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
require 'bodega/engine'
|
1
|
+
require 'bodega/engine' if defined?(Rails)
|
2
2
|
require 'configurator'
|
3
3
|
require 'i18n'
|
4
4
|
require 'money-rails'
|
5
5
|
|
6
6
|
module Bodega
|
7
7
|
autoload :PaymentMethod, 'bodega/payment_method'
|
8
|
+
autoload :ShippingMethod, 'bodega/shipping_method'
|
8
9
|
|
9
10
|
extend Configurator
|
10
11
|
option :customer_method, :current_user
|
12
|
+
option :max_quantity, 1000
|
13
|
+
|
11
14
|
# Auto-detect payment method. If a user has the Paypal gem installed,
|
12
15
|
# it'll use that. If a user has the Plinq gem installed, it'll use that.
|
13
16
|
# Otherwise, it'll be all, "HEY I NEED A PAYMENT METHOD" when checkout
|
@@ -16,6 +19,21 @@ module Bodega
|
|
16
19
|
defined?(::Plinq) ? :plinq : defined?(::Paypal) ? :paypal : raise("No payment method detected. Please set one using `Bodega.config.payment_method=`")
|
17
20
|
}
|
18
21
|
|
22
|
+
# Defaults to no shipping. Change to :fedex, :ups, or :usps and add
|
23
|
+
# `gem "active_shipping"` to gain access to various shipping calculations
|
24
|
+
# in the checkout process.
|
25
|
+
option :shipping_method, nil
|
26
|
+
option :shipping do
|
27
|
+
origin do
|
28
|
+
city nil
|
29
|
+
state nil
|
30
|
+
postal_code nil
|
31
|
+
country nil
|
32
|
+
end
|
33
|
+
states []
|
34
|
+
units :metric
|
35
|
+
end
|
36
|
+
|
19
37
|
# Auto-detect test mode. Defaults to true if running in development or test
|
20
38
|
# mode.
|
21
39
|
option :test_mode, lambda { Rails.env.development? || Rails.env.test? }
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bodega'
|
2
|
+
require 'bodega/payment_method/base'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
describe Bodega::PaymentMethod::Base do
|
6
|
+
describe ".options" do
|
7
|
+
it "defines options on the Bodega configuration instance" do
|
8
|
+
described_class.options(:a, :b, :z)
|
9
|
+
Bodega.config.base.should_not be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bodega'
|
2
|
+
require 'bodega/shipping_method/base'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
describe Bodega::PaymentMethod::Base do
|
6
|
+
describe ".options" do
|
7
|
+
it "defines options on the Bodega configuration instance" do
|
8
|
+
described_class.options(:a, :b, :z)
|
9
|
+
Bodega.config.base.should_not be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bodega do
|
4
|
+
describe ".config" do
|
5
|
+
describe "#payment_method" do
|
6
|
+
it "auto-detects Paypal" do
|
7
|
+
class Paypal; end
|
8
|
+
Bodega.config.payment_method.should == :paypal
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "test_mode" do
|
13
|
+
it "auto-detects test mode" do
|
14
|
+
Bodega.config.test_mode.should be_true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bodega/order_product'
|
3
|
+
|
4
|
+
describe Bodega::OrderProduct do
|
5
|
+
let(:product) { TestProduct.create!(product_attrs) }
|
6
|
+
let(:product_attrs) { {price: 49.95} }
|
7
|
+
let(:order_product) { described_class.new(product: product, quantity: 1) }
|
8
|
+
|
9
|
+
describe "#identifier" do
|
10
|
+
it "returns a friendly identifier" do
|
11
|
+
order_product.identifier.should == "TestProduct.1"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#name" do
|
16
|
+
it "defaults to a human-readable name" do
|
17
|
+
order_product.name.should == "Test Product #1"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "delegates to the product" do
|
21
|
+
def product.name
|
22
|
+
"Ohai"
|
23
|
+
end
|
24
|
+
order_product.name.should == "Ohai"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#quantity_and_name" do
|
29
|
+
it "returns the quantity and the name" do
|
30
|
+
order_product.quantity_and_name.should == "1 x Test Product #1"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#subtotal" do
|
35
|
+
it "returns the subtotal for the product quantity" do
|
36
|
+
product.stub(:price) { 25.0 }
|
37
|
+
order_product.quantity = 2
|
38
|
+
order_product.subtotal.should == 50.0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "for stock-kept products" do
|
43
|
+
let(:product_attrs) { {price: 49.95, keep_stock: true, number_in_stock: 1} }
|
44
|
+
|
45
|
+
it "can't be saved if the product is out-of-stock" do
|
46
|
+
product.stub(:in_stock?) { false }
|
47
|
+
product.save!
|
48
|
+
order_product.save
|
49
|
+
order_product.errors[:quantity].first.should == "Sorry, this product is sold out."
|
50
|
+
end
|
51
|
+
|
52
|
+
it "notifies me if my quantity is higher than the number left" do
|
53
|
+
order_product.quantity = 2
|
54
|
+
order_product.save
|
55
|
+
order_product.errors[:quantity].first.should == "There is only one in stock!"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can't be saved if the quantity is too high" do
|
59
|
+
product.stub(:number_in_stock) { 2 }
|
60
|
+
order_product.quantity = 3
|
61
|
+
order_product.save
|
62
|
+
order_product.errors[:quantity].first.should == "There are only 2 in stock!"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "reduces Product#number_in_stock when #update_stock is called" do
|
66
|
+
order_product.stub(:order) { OpenStruct.new }
|
67
|
+
order_product.save!
|
68
|
+
order_product.update_stock
|
69
|
+
product.reload.number_in_stock.should == 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bodega/order'
|
3
|
+
require 'bodega/order_product'
|
4
|
+
require 'bodega/product'
|
5
|
+
|
6
|
+
describe Bodega::Order do
|
7
|
+
before do
|
8
|
+
TestProduct.send :include, Bodega::Product
|
9
|
+
end
|
10
|
+
|
11
|
+
let!(:product_1) { TestProduct.create!(price: 30) }
|
12
|
+
let!(:product_2) { TestProduct.create!(price: 25) }
|
13
|
+
let(:order) { Bodega::Order.new }
|
14
|
+
|
15
|
+
describe "#payment_method" do
|
16
|
+
require 'bodega'
|
17
|
+
require 'bodega/payment_method'
|
18
|
+
class Paypal; end
|
19
|
+
|
20
|
+
it "returns an instance of Bodega::PaymentMethod::Base" do
|
21
|
+
order.payment_method.should be_instance_of(Bodega::PaymentMethod::Paypal)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns an instance of Bodega::PaymentMethod::Base with a reference to the order" do
|
25
|
+
order.payment_method.order.should == order
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#shipping_method" do
|
30
|
+
require 'bodega'
|
31
|
+
require 'bodega/shipping_method'
|
32
|
+
|
33
|
+
before do
|
34
|
+
Bodega.config { shipping_method :ups }
|
35
|
+
module ActiveMerchant; module Shipping; class UPS; end; end; end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns an instance of Bodega::ShippingMethod::Base" do
|
39
|
+
order.shipping_method.should be_instance_of(Bodega::ShippingMethod::UPS)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#subtotal" do
|
44
|
+
let(:cart) do
|
45
|
+
{
|
46
|
+
"TestProduct.1" => {
|
47
|
+
product_type: "TestProduct",
|
48
|
+
product_id: "1",
|
49
|
+
quantity: "1"
|
50
|
+
},
|
51
|
+
"TestProduct.2" => {
|
52
|
+
product_type: "TestProduct",
|
53
|
+
product_id: "2",
|
54
|
+
quantity: "2"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
it "adds up the #order_products subtotals" do
|
60
|
+
cart.each do |identifier, item|
|
61
|
+
order.update_product(item)
|
62
|
+
end
|
63
|
+
order.save!
|
64
|
+
order.subtotal.should == 80
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "cart management" do
|
69
|
+
before { order.save! }
|
70
|
+
|
71
|
+
it "defaults to quantity 1 when none given" do
|
72
|
+
order.update_product(product_type: "TestProduct", product_id: 1)
|
73
|
+
order.send(:order_product, "TestProduct.1").quantity.should == 1
|
74
|
+
end
|
75
|
+
|
76
|
+
it "accepts new quantities" do
|
77
|
+
order.update_product(product_type: "TestProduct", product_id: 1, quantity: 10)
|
78
|
+
order.send(:order_product, "TestProduct.1").quantity.should == 10
|
79
|
+
end
|
80
|
+
|
81
|
+
it "updates old quantities when no new quantity is given" do
|
82
|
+
order.update_product(product_type: "TestProduct", product_id: 1, quantity: 10)
|
83
|
+
order.update_product(product_type: "TestProduct", product_id: 1)
|
84
|
+
order.send(:order_product, "TestProduct.1").quantity.should == 11
|
85
|
+
end
|
86
|
+
|
87
|
+
it "removes products when told to" do
|
88
|
+
order.update_product(product_type: "TestProduct", product_id: 1, quantity: 10)
|
89
|
+
order.update_product(product_type: "TestProduct", product_id: 1, quantity: 30, remove: "1")
|
90
|
+
order.send(:order_product, "TestProduct.1").should be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|