spree_store_credits 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/LICENSE +23 -0
- data/README.md +13 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/app/controllers/admin/store_credits_controller.rb +27 -0
- data/app/controllers/checkout_controller_decorator.rb +14 -0
- data/app/controllers/users_controller_decorator.rb +9 -0
- data/app/models/order_decorator.rb +92 -0
- data/app/models/store_credit.rb +7 -0
- data/app/models/store_credit_adjustment.rb +3 -0
- data/app/models/user_decorator.rb +7 -0
- data/app/views/admin/store_credits/_form.html.erb +8 -0
- data/app/views/admin/store_credits/edit.html.erb +10 -0
- data/app/views/admin/store_credits/index.html.erb +39 -0
- data/app/views/admin/store_credits/new.html.erb +11 -0
- data/app/views/admin/store_credits/show.html.erb +1 -0
- data/app/views/checkout/_store_credits.html.erb +10 -0
- data/app/views/users/_store_credits.html.erb +29 -0
- data/config/locales/en.yml +16 -0
- data/config/routes.rb +10 -0
- data/lib/generators/spree_store_credits/install_generator.rb +19 -0
- data/lib/generators/templates/db/migrate/20100928140217_create_store_credits.rb +15 -0
- data/lib/spree_store_credits.rb +15 -0
- data/lib/spree_store_credits_hooks.rb +26 -0
- data/spec/models/order_spec.rb +198 -0
- data/spec/models/user_spec.rb +16 -0
- data/spec/spec_helper.rb +27 -0
- data/spree_store_credits.gemspec +22 -0
- metadata +115 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Redistribution and use in source and binary forms, with or without modification,
|
2
|
+
are permitted provided that the following conditions are met:
|
3
|
+
|
4
|
+
* Redistributions of source code must retain the above copyright notice,
|
5
|
+
this list of conditions and the following disclaimer.
|
6
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
7
|
+
this list of conditions and the following disclaimer in the documentation
|
8
|
+
and/or other materials provided with the distribution.
|
9
|
+
* Neither the name of the Rails Dog LLC nor the names of its
|
10
|
+
contributors may be used to endorse or promote products derived from this
|
11
|
+
software without specific prior written permission.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
14
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
15
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
16
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
17
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
18
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
19
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
20
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
21
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
22
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
23
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
desc "Default Task"
|
7
|
+
task :default => [ :spec ]
|
8
|
+
|
9
|
+
require 'rspec/core/rake_task'
|
10
|
+
RSpec::Core::RakeTask.new
|
11
|
+
|
12
|
+
require 'cucumber/rake/task'
|
13
|
+
Cucumber::Rake::Task.new do |t|
|
14
|
+
t.cucumber_opts = %w{--format pretty}
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Regenerates a rails 3 app for testing"
|
18
|
+
task :test_app do
|
19
|
+
SPREE_PATH = ENV['SPREE_PATH']
|
20
|
+
raise "SPREE_PATH should be specified" unless SPREE_PATH
|
21
|
+
require File.join(SPREE_PATH, 'lib/generators/spree/test_app_generator')
|
22
|
+
class AuthTestAppGenerator < Spree::Generators::TestAppGenerator
|
23
|
+
def tweak_gemfile
|
24
|
+
append_file 'Gemfile' do
|
25
|
+
<<-gems
|
26
|
+
gem 'spree_core', :path => '#{File.join(SPREE_PATH, 'core')}'
|
27
|
+
gem 'spree_auth', :path => '#{File.join(SPREE_PATH, 'auth')}'
|
28
|
+
gem 'spree_store_credits', :path => '#{File.dirname(__FILE__)}'
|
29
|
+
gems
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def install_gems
|
34
|
+
inside "test_app" do
|
35
|
+
run 'rake spree_core:install'
|
36
|
+
run 'rake spree_auth:install'
|
37
|
+
end
|
38
|
+
|
39
|
+
generate 'spree_store_credits:install -f'
|
40
|
+
end
|
41
|
+
|
42
|
+
def migrate_db
|
43
|
+
run_migrations
|
44
|
+
end
|
45
|
+
end
|
46
|
+
AuthTestAppGenerator.start
|
47
|
+
end
|
48
|
+
|
49
|
+
namespace :test_app do
|
50
|
+
desc 'Rebuild test and cucumber databases'
|
51
|
+
task :rebuild_dbs do
|
52
|
+
system("cd spec/test_app && rake db:drop db:migrate RAILS_ENV=test && rake db:drop db:migrate RAILS_ENV=cucumber")
|
53
|
+
end
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.30.0.beta2
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Admin::StoreCreditsController < Admin::BaseController
|
2
|
+
resource_controller
|
3
|
+
before_filter :check_amounts, :only => [:edit, :update]
|
4
|
+
before_filter :set_remaining_amount, :only => [:create, :update]
|
5
|
+
|
6
|
+
create.response do |wants|
|
7
|
+
wants.html { redirect_to collection_url }
|
8
|
+
end
|
9
|
+
|
10
|
+
update.response do |wants|
|
11
|
+
wants.html { redirect_to collection_url }
|
12
|
+
end
|
13
|
+
|
14
|
+
destroy.success.wants.js { render_js_for_destroy }
|
15
|
+
|
16
|
+
private
|
17
|
+
def check_amounts
|
18
|
+
if (object.remaining_amount < object.amount)
|
19
|
+
flash[:error] = "Can't be edit, b/c already was used."
|
20
|
+
redirect_to collection_url
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_remaining_amount
|
25
|
+
params[:store_credit][:remaining_amount] = params[:store_credit][:amount]
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
CheckoutController.class_eval do
|
2
|
+
before_filter :remove_payments_attributes_if_total_is_zero
|
3
|
+
|
4
|
+
private
|
5
|
+
def remove_payments_attributes_if_total_is_zero
|
6
|
+
return unless params[:order] && params[:order][:store_credit_amount]
|
7
|
+
store_credit_amount = [BigDecimal.new(params[:order][:store_credit_amount]), current_user.store_credits_total].min
|
8
|
+
if store_credit_amount >= current_order.total
|
9
|
+
params[:order].delete(:source_attributes)
|
10
|
+
params.delete(:payment_source)
|
11
|
+
params[:order].delete(:payments_attributes)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
UsersController.class_eval do
|
2
|
+
before_filter :find_orders_with_store_credit, :only => :show
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def find_orders_with_store_credit
|
7
|
+
@orders_with_store_credit = object.orders.joins(:adjustments).where(:adjustments => {:source_type => 'StoreCredit'})
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
Order.class_eval do
|
2
|
+
attr_accessible :store_credit_amount, :remove_store_credits
|
3
|
+
attr_accessor :store_credit_amount, :remove_store_credits
|
4
|
+
before_save :process_store_credit, :if => "@store_credit_amount"
|
5
|
+
before_save :remove_store_credits
|
6
|
+
after_save :ensure_sufficient_credit
|
7
|
+
|
8
|
+
has_many :store_credits, :class_name => 'StoreCreditAdjustment', :conditions => "source_type='StoreCredit'"
|
9
|
+
|
10
|
+
def store_credit_amount
|
11
|
+
store_credits.sum(:amount).abs
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# override core process payments to force payment present
|
16
|
+
# in case store credits were destroyed by ensure_sufficient_credit
|
17
|
+
def process_payments!
|
18
|
+
if total > 0 && payment.nil?
|
19
|
+
false
|
20
|
+
else
|
21
|
+
ret = payments.each(&:process!)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# credit or update store credit adjustment to correct value if amount specified
|
29
|
+
#
|
30
|
+
def process_store_credit
|
31
|
+
@store_credit_amount = BigDecimal.new(@store_credit_amount.to_s).round(2)
|
32
|
+
|
33
|
+
# store credit can't be greater than order total (not including existing credit), or the users available credit
|
34
|
+
@store_credit_amount = [@store_credit_amount, user.store_credits_total, (total + store_credit_amount.abs)].min
|
35
|
+
|
36
|
+
if @store_credit_amount <= 0
|
37
|
+
if sca = adjustments.detect {|adjustment| adjustment.source_type == "StoreCredit" }
|
38
|
+
sca.destroy
|
39
|
+
end
|
40
|
+
else
|
41
|
+
if sca = adjustments.detect {|adjustment| adjustment.source_type == "StoreCredit" }
|
42
|
+
sca.update_attributes({:amount => -(@store_credit_amount)})
|
43
|
+
else
|
44
|
+
#create adjustment off association to prevent reload
|
45
|
+
sca = adjustments.create(:source_type => "StoreCredit", :label => I18n.t(:store_credit) , :amount => -(@store_credit_amount))
|
46
|
+
end
|
47
|
+
|
48
|
+
#recalc totals and ensure payment is set to new amount
|
49
|
+
update_totals
|
50
|
+
payment.amount = total if payment
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# consume users store credit once the order has completed.
|
55
|
+
fsm = self.state_machines[:state]
|
56
|
+
fsm.after_transition :to => 'complete', :do => :consume_users_credit
|
57
|
+
|
58
|
+
def consume_users_credit
|
59
|
+
return unless completed?
|
60
|
+
credit_used = self.store_credit_amount
|
61
|
+
|
62
|
+
user.store_credits.each do |store_credit|
|
63
|
+
break if credit_used == 0
|
64
|
+
if store_credit.remaining_amount > 0
|
65
|
+
if store_credit.remaining_amount > credit_used
|
66
|
+
store_credit.remaining_amount -= credit_used
|
67
|
+
store_credit.save
|
68
|
+
credit_used = 0
|
69
|
+
else
|
70
|
+
credit_used -= store_credit.remaining_amount
|
71
|
+
store_credit.update_attribute(:remaining_amount, 0)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# ensure that user has sufficient credits to cover adjustments
|
79
|
+
#
|
80
|
+
def ensure_sufficient_credit
|
81
|
+
if user.store_credits_total < store_credit_amount
|
82
|
+
#user's credit does not cover all adjustments.
|
83
|
+
store_credits.destroy_all
|
84
|
+
|
85
|
+
update!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def remove_store_credits
|
90
|
+
store_credits.clear if @remove_store_credits == '1'
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= render :partial => 'admin/shared/configuration_menu' %>
|
2
|
+
|
3
|
+
<h1><%=t("editing_store_credit")%></h1>
|
4
|
+
|
5
|
+
<%= render 'shared/error_messages', :target => @store_credit %>
|
6
|
+
|
7
|
+
<%= form_for(:store_credit, :url => object_url, :html => { :method => :put }) do |f| %>
|
8
|
+
<%= render :partial => "form", :locals => { :f => f } %>
|
9
|
+
<%= render :partial => "admin/shared/edit_resource_links" %>
|
10
|
+
<% end %>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<%= render :partial => 'admin/shared/configuration_menu' %>
|
2
|
+
|
3
|
+
<h1><%= t("listing_store_credits") %></h1>
|
4
|
+
<table class="index">
|
5
|
+
<thead>
|
6
|
+
<tr>
|
7
|
+
<th><%= t("user") %></th>
|
8
|
+
<th><%= t("amount") %></th>
|
9
|
+
<th><%= t("remaining_amount") %></th>
|
10
|
+
<th><%= t("reason") %></th>
|
11
|
+
<th></th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<% @store_credits.each do |store_credit|%>
|
16
|
+
<tr id="<%= dom_id store_credit %>">
|
17
|
+
<td><%= link_to store_credit.user.email, admin_user_url(store_credit.user) %></td>
|
18
|
+
<td><%= number_to_currency store_credit.amount %></td>
|
19
|
+
<td><%= number_to_currency store_credit.remaining_amount %></td>
|
20
|
+
<td><%= store_credit.reason %></td>
|
21
|
+
<td class="actions">
|
22
|
+
<% if store_credit.remaining_amount > 0 %>
|
23
|
+
<% if store_credit.amount == store_credit.remaining_amount %>
|
24
|
+
<%= link_to_edit store_credit %>
|
25
|
+
<% else %>
|
26
|
+
<%= t(:was_partially_used) %>
|
27
|
+
<% end %>
|
28
|
+
<%= link_to_delete store_credit %>
|
29
|
+
<% else %>
|
30
|
+
<%= t(:was_fully_used) %>
|
31
|
+
<% end %>
|
32
|
+
</tr>
|
33
|
+
<% end %>
|
34
|
+
<% if @store_credits.empty? %>
|
35
|
+
<tr><td colspan="4"><%= t(:none) %></td></tr>
|
36
|
+
<% end %>
|
37
|
+
</tbody>
|
38
|
+
</table>
|
39
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= render :partial => 'admin/shared/configuration_menu' %>
|
2
|
+
|
3
|
+
<h1><%=t("new_store_credit")%></h1>
|
4
|
+
|
5
|
+
<%= render 'shared/error_messages', :target => @store_credit %>
|
6
|
+
|
7
|
+
<%= form_for(:store_credit, :url => admin_user_store_credits_url(params[:user_id])) do |f| %>
|
8
|
+
<%= f.hidden_field :user_id, :value => params[:user_id] %>
|
9
|
+
<%= render :partial => "form", :locals => { :f => f } %>
|
10
|
+
<%= render :partial => "admin/shared/new_resource_links" %>
|
11
|
+
<% end %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render :partial => 'admin/shared/configuration_menu' %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<% if (current_user && current_user.store_credits_total > 0) %>
|
2
|
+
<br style='clear:both;' />
|
3
|
+
<p><%= t('you_have_store_credit',
|
4
|
+
:amount => number_to_currency(current_user.store_credits_total))%>
|
5
|
+
</p>
|
6
|
+
<p>
|
7
|
+
<label><%= t('enter_desired_amount_of_store_credit') %></label><br />
|
8
|
+
<%= form.text_field :store_credit_amount, :size => 19 %>
|
9
|
+
</p>
|
10
|
+
<% end %>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<h2><%= t("store_credits") %></h2>
|
2
|
+
<p>
|
3
|
+
<%= t('current_store_credit') %>: <strong><%= number_to_currency @user.store_credits_total %></strong>
|
4
|
+
</p>
|
5
|
+
<h3><%= t('orders_with_store_credit') %></h3>
|
6
|
+
<table class="order-summary" width="545">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th><%= t("order_number") %></th>
|
10
|
+
<th><%= t("order_date") %></th>
|
11
|
+
<th><%= t("status") %></th>
|
12
|
+
<th><%= t("customer") %></th>
|
13
|
+
<th><%= t("total") %></th>
|
14
|
+
<th><%= t("store_credit") %></th>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
<% @orders_with_store_credit.each do |order| %>
|
19
|
+
<tr class="<%= cycle('even', 'odd') %>">
|
20
|
+
<td><%= link_to order.number, order_url(order) %></td>
|
21
|
+
<td><%=order.created_at.to_date%></td>
|
22
|
+
<td><%= t(order.state).titleize %></td>
|
23
|
+
<td><%= order.user.email if order.user %></td>
|
24
|
+
<td><%= number_to_currency order.total %></td>
|
25
|
+
<td><%= number_to_currency order.store_credits.sum(:amount) %></td>
|
26
|
+
</tr>
|
27
|
+
<% end %>
|
28
|
+
</tbody>
|
29
|
+
</table>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
add_store_credit: "Add store credit"
|
4
|
+
current_store_credit: "Current store credit"
|
5
|
+
editing_store_credit: "Editing store credit"
|
6
|
+
enter_desired_amount_of_store_credit: "Enter amount, which you want to use"
|
7
|
+
listing_store_credits: "Store credits"
|
8
|
+
new_store_credit: "New store credit"
|
9
|
+
manage_store_credits: "Manage store credits"
|
10
|
+
orders_with_store_credit: "Orders with store credit"
|
11
|
+
remaining_amount: "Remaining amount"
|
12
|
+
store_credit: "Store Credit"
|
13
|
+
store_credits: "Store Credits"
|
14
|
+
you_have_store_credit: "You have %{amount} of store credits"
|
15
|
+
was_fully_used: "was fully used"
|
16
|
+
was_partially_used: "was partially used"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module SpreeStoreCredits
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../templates", __FILE__)
|
5
|
+
|
6
|
+
desc "Configures your Rails application for use with spree_store_credits"
|
7
|
+
|
8
|
+
def copy_migrations
|
9
|
+
directory "db"
|
10
|
+
end
|
11
|
+
|
12
|
+
# def copy_public
|
13
|
+
# directory "public"
|
14
|
+
# end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateStoreCredits < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :store_credits do |t|
|
4
|
+
t.references :user
|
5
|
+
t.decimal :amount, :precision => 8, :scale => 2, :default => 0.0, :null => false
|
6
|
+
t.decimal :remaining_amount, :precision => 8, :scale => 2, :default => 0.0, :null => false
|
7
|
+
t.string :reason
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :store_credits
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spree_core'
|
2
|
+
require 'spree_store_credits_hooks'
|
3
|
+
|
4
|
+
module SpreeStoreCredits
|
5
|
+
class Engine < Rails::Engine
|
6
|
+
def self.activate
|
7
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
|
8
|
+
Rails.env == "production" ? require(c) : load(c)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
config.to_prepare &method(:activate).to_proc
|
12
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class SpreeStaticContentHooks < Spree::ThemeSupport::HookListener
|
2
|
+
insert_after :admin_configurations_menu do
|
3
|
+
"<%= configurations_menu_item(I18n.t('store_credits'), admin_store_credits_url, I18n.t('manage_store_credits')) %>"
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
insert_after :admin_users_index_row_actions do
|
8
|
+
%(
|
9
|
+
<%= link_to_with_icon('add', t('add_store_credit'), new_admin_user_store_credit_url(user)) %>
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
insert_after :checkout_payment_step, :partial => 'checkout/store_credits'
|
14
|
+
|
15
|
+
insert_after :account_my_orders, :partial => 'users/store_credits'
|
16
|
+
|
17
|
+
insert_after :order_details_adjustments do
|
18
|
+
%(
|
19
|
+
<% if @order.store_credits.present? && !@order.completed? %>
|
20
|
+
<tr><td colspan="4">
|
21
|
+
<%= check_box :order, :remove_store_credits %> <%= label :order, :remove_store_credits %>
|
22
|
+
</td></tr>
|
23
|
+
<% end %>
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Order do
|
4
|
+
let(:user) { mock_model User, :email => 'spree@example.com', :store_credits_total => 45.00 }
|
5
|
+
let(:line_item) { mock_model(LineItem, :variant => mock('variant'), :quantity => 5, :price => 10) }
|
6
|
+
let(:order) { Order.create() }
|
7
|
+
|
8
|
+
before { order.stub(:user => user, :total => 50 ) }
|
9
|
+
|
10
|
+
context "process_store_credit" do
|
11
|
+
it "should create store credit adjustment when user has sufficient credit" do
|
12
|
+
order.store_credit_amount = 5.0
|
13
|
+
order.save
|
14
|
+
order.store_credits.size.should == 1
|
15
|
+
order.store_credit_amount.should == 5.0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should only create adjustment with amount equal to users total credit" do
|
19
|
+
order.store_credit_amount = 50.0
|
20
|
+
order.save
|
21
|
+
order.store_credit_amount.should == 45.00
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should only create adjusment with amount equal to order total" do
|
25
|
+
user.stub(:store_credits_total => 100.0)
|
26
|
+
order.store_credit_amount = 90.0
|
27
|
+
order.save
|
28
|
+
order.store_credit_amount.should == 50.00
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not create adjustment when you does not have any credit" do
|
32
|
+
user.stub(:store_credits_total => 0.0)
|
33
|
+
order.store_credit_amount = 5.0
|
34
|
+
order.save
|
35
|
+
order.store_credits.size.should == 0
|
36
|
+
order.store_credit_amount.should == 0.0
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should update order totals if credit is applied" do
|
40
|
+
order.should_receive(:update_totals)
|
41
|
+
order.store_credit_amount = 5.0
|
42
|
+
order.save
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should update payment amount if credit is applied" do
|
46
|
+
order.stub(:payment => mock('payment'))
|
47
|
+
order.payment.should_receive(:amount=)
|
48
|
+
order.store_credit_amount = 5.0
|
49
|
+
order.save
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should create negative adjustment" do
|
53
|
+
order.store_credit_amount = 5.0
|
54
|
+
order.save
|
55
|
+
order.adjustments[0].amount.should == -5.0
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should process credits if order total is already zero" do
|
59
|
+
order.stub(:total => 0)
|
60
|
+
order.store_credit_amount = 5.0
|
61
|
+
order.should_receive(:process_store_credit)
|
62
|
+
order.save
|
63
|
+
order.store_credits.size.should == 0
|
64
|
+
order.store_credit_amount.should == 0.0
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with an existing adjustment" do
|
68
|
+
before { order.adjustments.create(:source_type => "StoreCredit", :label => I18n.t(:store_credit) , :amount => -10) }
|
69
|
+
|
70
|
+
it "should decrease existing adjustment if specific amount is less than adjustment amount" do
|
71
|
+
order.store_credit_amount = 5.0
|
72
|
+
order.save
|
73
|
+
order.store_credits.size.should == 1
|
74
|
+
order.store_credit_amount.should == 5.0
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should increase existing adjustment if specified amount is greater than adjustment amount" do
|
78
|
+
order.store_credit_amount = 25.0
|
79
|
+
order.save
|
80
|
+
order.store_credits.size.should == 1
|
81
|
+
order.store_credit_amount.should == 25.0
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should destroy the adjustment if specified amount is zero" do
|
85
|
+
order.store_credit_amount = 0.0
|
86
|
+
order.save
|
87
|
+
order.store_credits.size.should == 0
|
88
|
+
order.store_credit_amount.should == 0.0
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should decrease existing adjustment when existing credit amount is equal to the order total" do
|
92
|
+
order.stub(:total => 10)
|
93
|
+
order.store_credit_amount = 5.0
|
94
|
+
order.save
|
95
|
+
order.store_credits.size.should == 1
|
96
|
+
order.store_credit_amount.should == 5.0
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
context "store_credit_amount" do
|
103
|
+
it "should return total for all store credit adjustments applied to order" do
|
104
|
+
order.adjustments.create(:source_type => "StoreCredit", :label => I18n.t(:store_credit) , :amount => -10)
|
105
|
+
order.adjustments.create(:source_type => "StoreCredit", :label => I18n.t(:store_credit) , :amount => -5)
|
106
|
+
|
107
|
+
order.store_credit_amount.should == 15
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "consume_users_credit" do
|
112
|
+
let(:store_credit_1) { mock_model(StoreCredit, :amount => 100, :remaining_amount => 100) }
|
113
|
+
let(:store_credit_2) { mock_model(StoreCredit, :amount => 10, :remaining_amount => 5) }
|
114
|
+
let(:store_credit_3) { mock_model(StoreCredit, :amount => 60, :remaining_amount => 50 ) }
|
115
|
+
before { order.stub(:completed? => true, :store_credit_amount => 35) }
|
116
|
+
|
117
|
+
it "should reduce remaining amount on a single credit when that credit satisfies the entire amount" do
|
118
|
+
user.stub(:store_credits => [store_credit_1])
|
119
|
+
store_credit_1.should_receive(:remaining_amount=).with(65)
|
120
|
+
store_credit_1.should_receive(:save)
|
121
|
+
order.send(:consume_users_credit)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should reduce remaining amount on a mutliple creidts when a single credit does not satify the entire amount" do
|
125
|
+
order.stub(:store_credit_amount => 55)
|
126
|
+
user.stub(:store_credits => [store_credit_2, store_credit_3])
|
127
|
+
store_credit_2.should_receive(:update_attribute).with(:remaining_amount, 0)
|
128
|
+
store_credit_3.should_receive(:update_attribute).with(:remaining_amount, 0)
|
129
|
+
order.send(:consume_users_credit)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should call consume_users_credit after transition to complete" do
|
133
|
+
order = Order.new()
|
134
|
+
order.state = "confirm"
|
135
|
+
order.should_receive(:consume_users_credit).at_least(1).times
|
136
|
+
order.next!
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
context "ensure_sufficient_credit" do
|
143
|
+
let(:payment) { mock_model(Payment, :checkout? => true, :amount => 50)}
|
144
|
+
before do
|
145
|
+
order.adjustments.create(:source_type => "StoreCredit", :label => I18n.t(:store_credit) , :amount => -10)
|
146
|
+
order.stub(:completed? => true, :store_credit_amount => 35, :payment => payment )
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should do nothing when user has credits" do
|
151
|
+
order.store_credits.should_not_receive(:destroy_all)
|
152
|
+
order.payment.should_not_receive(:update_attributes_without_callbacks)
|
153
|
+
order.send(:ensure_sufficient_credit)
|
154
|
+
end
|
155
|
+
|
156
|
+
context "when user no longer has sufficient credit to cover entire credit amount" do
|
157
|
+
before do
|
158
|
+
payment.stub(:amount => 40)
|
159
|
+
user.stub(:store_credits_total => 0.0)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should destory all store credit adjustments" do
|
163
|
+
order.payment.stub(:update_attributes_without_callbacks)
|
164
|
+
order.store_credits.should_receive(:destroy_all)
|
165
|
+
order.send(:ensure_sufficient_credit)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should update payment" do
|
169
|
+
order.payment.should_receive(:update_attributes_without_callbacks).with(:amount => 50)
|
170
|
+
order.send(:ensure_sufficient_credit)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
context "process_payments!" do
|
177
|
+
it "should return false when total is greater than zero and payment is nil" do
|
178
|
+
order.process_payments!.should be_false
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should return true when total is zero and payment is nil" do
|
182
|
+
order.stub(:total => 0.0)
|
183
|
+
order.process_payments!.should be_true
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should return true when total is zero and payment is not nil" do
|
187
|
+
order.stub(:payment => mock_model(Payment, :process! => true))
|
188
|
+
order.process_payments!.should be_true
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should process payment when total is zero and payment is not nil" do
|
192
|
+
order.stub(:payments => [mock_model(Payment)])
|
193
|
+
order.payment.should_receive(:process!)
|
194
|
+
order.process_payments!
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe User do
|
4
|
+
let(:user) { User.create(:email => "foo@bar.com", :password => "secret", :password_confirmation => "secret") }
|
5
|
+
|
6
|
+
context "store_credits_total" do
|
7
|
+
before do
|
8
|
+
user.store_credits.create(:amount => 100, :remaining_amount => 100, :reason => "A")
|
9
|
+
user.store_credits.create(:amount => 60, :remaining_amount => 55, :reason => "B")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return the total remaining amount for users store credits" do
|
13
|
+
user.store_credits_total.should == 155.00
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
2
|
+
# from the project root directory.
|
3
|
+
ENV["RAILS_ENV"] ||= 'test'
|
4
|
+
require File.expand_path("../test_app/config/environment", __FILE__)
|
5
|
+
require 'rspec/rails'
|
6
|
+
|
7
|
+
# Requires supporting files with custom matchers and macros, etc,
|
8
|
+
# in ./support/ and its subdirectories.
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
# == Mock Framework
|
13
|
+
#
|
14
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
15
|
+
#
|
16
|
+
# config.mock_with :mocha
|
17
|
+
# config.mock_with :flexmock
|
18
|
+
# config.mock_with :rr
|
19
|
+
config.mock_with :rspec
|
20
|
+
|
21
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
22
|
+
|
23
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
24
|
+
# examples within a transaction, comment the following line or assign false
|
25
|
+
# instead of true.
|
26
|
+
config.use_transactional_fixtures = true
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
|
6
|
+
s.name = 'spree_store_credits'
|
7
|
+
s.version = '1.0.0'
|
8
|
+
s.authors = ["Roman Smirnov", "Brian Quinn"]
|
9
|
+
s.email = 'roman@railsdog.com'
|
10
|
+
s.homepage = 'http://github.com/spree/spree-store-credits'
|
11
|
+
s.summary = 'Provides store credits for a Spree store.'
|
12
|
+
s.description = 'Provides store credits for a Spree store.'
|
13
|
+
s.required_ruby_version = '>= 1.8.7'
|
14
|
+
s.rubygems_version = '1.3.6'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency('spree_core', '>= 0.30.1')
|
21
|
+
end
|
22
|
+
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spree_store_credits
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Roman Smirnov
|
14
|
+
- Brian Quinn
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-12-21 00:00:00 +00:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: spree_core
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 101
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
- 30
|
34
|
+
- 1
|
35
|
+
version: 0.30.1
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
description: Provides store credits for a Spree store.
|
39
|
+
email: roman@railsdog.com
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- VERSION
|
52
|
+
- app/controllers/admin/store_credits_controller.rb
|
53
|
+
- app/controllers/checkout_controller_decorator.rb
|
54
|
+
- app/controllers/users_controller_decorator.rb
|
55
|
+
- app/models/order_decorator.rb
|
56
|
+
- app/models/store_credit.rb
|
57
|
+
- app/models/store_credit_adjustment.rb
|
58
|
+
- app/models/user_decorator.rb
|
59
|
+
- app/views/admin/store_credits/_form.html.erb
|
60
|
+
- app/views/admin/store_credits/edit.html.erb
|
61
|
+
- app/views/admin/store_credits/index.html.erb
|
62
|
+
- app/views/admin/store_credits/new.html.erb
|
63
|
+
- app/views/admin/store_credits/show.html.erb
|
64
|
+
- app/views/checkout/_store_credits.html.erb
|
65
|
+
- app/views/users/_store_credits.html.erb
|
66
|
+
- config/locales/en.yml
|
67
|
+
- config/routes.rb
|
68
|
+
- lib/generators/spree_store_credits/install_generator.rb
|
69
|
+
- lib/generators/templates/db/migrate/20100928140217_create_store_credits.rb
|
70
|
+
- lib/spree_store_credits.rb
|
71
|
+
- lib/spree_store_credits_hooks.rb
|
72
|
+
- spec/models/order_spec.rb
|
73
|
+
- spec/models/user_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
- spree_store_credits.gemspec
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/spree/spree-store-credits
|
78
|
+
licenses: []
|
79
|
+
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 57
|
91
|
+
segments:
|
92
|
+
- 1
|
93
|
+
- 8
|
94
|
+
- 7
|
95
|
+
version: 1.8.7
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.3.7
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Provides store credits for a Spree store.
|
112
|
+
test_files:
|
113
|
+
- spec/models/order_spec.rb
|
114
|
+
- spec/models/user_spec.rb
|
115
|
+
- spec/spec_helper.rb
|