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 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