spree_store_credits 1.0.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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ spec/test_app
2
+ *.swp
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
@@ -0,0 +1,13 @@
1
+ StoreCredits
2
+ ============
3
+
4
+ Introduction goes here.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ Example goes here.
11
+
12
+
13
+ Copyright (c) 2010 Roman Smirnov, released under the New BSD License
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,7 @@
1
+ class StoreCredit < ActiveRecord::Base
2
+ validates :amount, :presence => true, :numericality => true
3
+ validates :reason, :presence => true
4
+ validates :user, :presence => true
5
+
6
+ belongs_to :user
7
+ end
@@ -0,0 +1,3 @@
1
+ class StoreCreditAdjustment < Adjustment
2
+
3
+ end
@@ -0,0 +1,7 @@
1
+ User.class_eval do
2
+ has_many :store_credits
3
+
4
+ def store_credits_total
5
+ store_credits.sum(:remaining_amount)
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ <%= f.field_container :amount do %>
2
+ <%= f.label :amount, t("amount")%><br />
3
+ <%= f.text_field :amount %>
4
+ <% end %>
5
+ <%= f.field_container :reason do %>
6
+ <%= f.label :reason, t("reason")%><br />
7
+ <%= f.text_field :reason %>
8
+ <% 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 %> &nbsp;
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,10 @@
1
+ Rails.application.routes.draw do
2
+
3
+ namespace :admin do
4
+ resources :store_credits
5
+ resources :users do
6
+ resources :store_credits
7
+ end
8
+ end
9
+
10
+ end
@@ -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
+ %(&nbsp;
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
@@ -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