bodega 0.3.0 → 0.4.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/.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
|