solidus_virtual_gift_card 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +26 -0
  6. data/README.md +52 -0
  7. data/Rakefile +21 -0
  8. data/app/assets/javascripts/spree/backend/solidus_virtual_gift_card.coffee +9 -0
  9. data/app/assets/stylesheets/spree/backend/solidus_virtual_gift_card.css +7 -0
  10. data/app/controllers/spree/admin/gift_cards_controller.rb +54 -0
  11. data/app/controllers/spree/api/gift_cards_controller.rb +23 -0
  12. data/app/mailers/spree/gift_card_mailer.rb +8 -0
  13. data/app/models/concerns/spree/gift_cards/line_item_concerns.rb +17 -0
  14. data/app/models/concerns/spree/gift_cards/order_concerns.rb +34 -0
  15. data/app/models/concerns/spree/redemption_code_generator.rb +10 -0
  16. data/app/models/spree/line_item_decorator.rb +1 -0
  17. data/app/models/spree/order_decorator.rb +1 -0
  18. data/app/models/spree/permission_sets/virtual_gift_card_display.rb +9 -0
  19. data/app/models/spree/permission_sets/virtual_gift_card_management.rb +9 -0
  20. data/app/models/spree/store_credit_category_decorator.rb +2 -0
  21. data/app/models/spree/virtual_gift_card.rb +75 -0
  22. data/app/overrides/admin_item_view_gift_card_codes.rb +13 -0
  23. data/app/overrides/admin_user_sidebar_store_credits.rb +6 -0
  24. data/app/overrides/admin_user_sub_menu.rb +6 -0
  25. data/app/views/spree/admin/gift_cards/_lookup_form.html.erb +8 -0
  26. data/app/views/spree/admin/gift_cards/_redemption_form.html.erb +8 -0
  27. data/app/views/spree/admin/gift_cards/index.html.erb +1 -0
  28. data/app/views/spree/admin/gift_cards/lookup.html.erb +16 -0
  29. data/app/views/spree/admin/gift_cards/show.html.erb +32 -0
  30. data/app/views/spree/admin/orders/_gift_card_redemption_codes.html.erb +3 -0
  31. data/app/views/spree/admin/users/_gift_card_sidebar.html.erb +3 -0
  32. data/app/views/spree/admin/users/_sub_menu.html.erb +10 -0
  33. data/app/views/spree/gift_card_mailer/gift_card_email.text.erb +7 -0
  34. data/bin/rails +7 -0
  35. data/circle.yml +6 -0
  36. data/config/initializers/gift_card_store_credit_event_originator.rb +7 -0
  37. data/config/locales/en.yml +21 -0
  38. data/config/routes.rb +23 -0
  39. data/db/migrate/20140623152903_create_virtual_gift_card.rb +16 -0
  40. data/db/migrate/20140624175113_seed_gift_card_category.rb +5 -0
  41. data/db/migrate/20140627185148_add_timestamps_to_gift_cards.rb +6 -0
  42. data/db/migrate/20140702153907_add_gift_card_flag_to_products.rb +5 -0
  43. data/db/migrate/20140707200431_add_line_item_to_gift_card.rb +7 -0
  44. data/db/seeds.rb +1 -0
  45. data/lib/generators/solidus_virtual_gift_card/install/install_generator.rb +36 -0
  46. data/lib/solidus_virtual_gift_card.rb +2 -0
  47. data/lib/spree_virtual_gift_card/engine.rb +20 -0
  48. data/lib/spree_virtual_gift_card/factories.rb +13 -0
  49. data/solidus_virtual_gift_card.gemspec +31 -0
  50. data/spec/controllers/spree/admin/gift_cards_controller_spec.rb +111 -0
  51. data/spec/controllers/spree/api/gift_cards_controller_spec.rb +88 -0
  52. data/spec/models/concerns/spree/redemption_code_generator_spec.rb +42 -0
  53. data/spec/models/spree/line_item_spec.rb +30 -0
  54. data/spec/models/spree/order_spec.rb +90 -0
  55. data/spec/models/spree/permission_sets/virtual_gift_card_display_spec.rb +22 -0
  56. data/spec/models/spree/permission_sets/virtual_gift_card_management_spec.rb +21 -0
  57. data/spec/models/spree/store_credit_category_spec.rb +23 -0
  58. data/spec/models/spree/virtual_gift_card_spec.rb +179 -0
  59. data/spec/spec_helper.rb +74 -0
  60. metadata +258 -0
@@ -0,0 +1,6 @@
1
+ class AddTimestampsToGiftCards < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_virtual_gift_cards, :created_at, :datetime
4
+ add_column :spree_virtual_gift_cards, :updated_at, :datetime
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddGiftCardFlagToProducts < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_products, :gift_card, :boolean, default: false
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class AddLineItemToGiftCard < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_virtual_gift_cards, :line_item_id, :integer
4
+
5
+ add_index :spree_virtual_gift_cards, :line_item_id
6
+ end
7
+ end
data/db/seeds.rb ADDED
@@ -0,0 +1 @@
1
+ Spree::StoreCreditCategory.find_or_create_by(name: 'Gift Card')
@@ -0,0 +1,36 @@
1
+ module SolidusVirtualGiftCard
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ class_option :auto_run_migrations, type: :boolean, default: false
6
+
7
+ def add_javascripts
8
+ append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_virtual_gift_card\n"
9
+ end
10
+
11
+ def add_stylesheets
12
+ inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_virtual_gift_card\n", before: /\*\//, verbose: true
13
+ end
14
+
15
+ def include_seed_data
16
+ append_file "db/seeds.rb", <<-SEEDS
17
+ \n
18
+ SpreeVirtualGiftCard::Engine.load_seed if defined?(SpreeVirtualGiftCard::Engine)
19
+ SEEDS
20
+ end
21
+
22
+ def add_migrations
23
+ run 'bundle exec rake railties:install:migrations FROM=solidus_virtual_gift_card'
24
+ end
25
+
26
+ def run_migrations
27
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
28
+ if run_migrations
29
+ run 'bundle exec rake db:migrate'
30
+ else
31
+ puts 'Skipping rake db:migrate, don\'t forget to run it!'
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,2 @@
1
+ require 'spree_core'
2
+ require 'spree_virtual_gift_card/engine'
@@ -0,0 +1,20 @@
1
+ module SpreeVirtualGiftCard
2
+ class Engine < Rails::Engine
3
+ require "solidus_core"
4
+
5
+ isolate_namespace Spree
6
+ engine_name "solidus_virtual_gift_card"
7
+
8
+ config.generators do |g|
9
+ g.test_framework :rspec
10
+ end
11
+
12
+ def self.activate
13
+ Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
14
+ Rails.configuration.cache_classes ? require(c) : load(c)
15
+ end
16
+ end
17
+
18
+ config.to_prepare &method(:activate).to_proc
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ FactoryGirl.define do
2
+ factory :store_credit_gift_card_category, class: Spree::StoreCreditCategory do
3
+ name Spree::StoreCreditCategory::GIFT_CARD_CATEGORY_NAME
4
+ end
5
+
6
+ factory :virtual_gift_card, class: Spree::VirtualGiftCard do
7
+ association :purchaser, factory: :user
8
+ association :line_item, factory: :line_item
9
+
10
+ amount 25.0
11
+ currency "USD"
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.platform = Gem::Platform::RUBY
5
+ s.name = "solidus_virtual_gift_card"
6
+ s.version = "1.0.0"
7
+ s.summary = "Virtual gift card for purchase, drops into the user's account as store credit"
8
+ s.required_ruby_version = ">= 2.1"
9
+
10
+ s.author = "Bonobos"
11
+ s.email = "engineering@bonobos.com"
12
+ s.homepage = "http://www.bonobos.com"
13
+ s.license = %q{BSD-3}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.require_path = "lib"
18
+ s.requirements << "none"
19
+
20
+ s.add_dependency "solidus", [">= 1.0.0", "< 1.2.0"]
21
+
22
+ s.add_development_dependency "rspec-rails", "~> 3.2"
23
+ s.add_development_dependency "simplecov"
24
+ s.add_development_dependency "sqlite3"
25
+ s.add_development_dependency "sass-rails"
26
+ s.add_development_dependency "coffee-rails"
27
+ s.add_development_dependency "factory_girl"
28
+ s.add_development_dependency "capybara"
29
+ s.add_development_dependency "database_cleaner"
30
+ s.add_development_dependency "ffaker"
31
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Admin::GiftCardsController do
4
+ stub_authorization!
5
+ let!(:gc_category) { create(:store_credit_gift_card_category) }
6
+ let!(:credit_type) { create(:secondary_credit_type, name: "Non-expiring") }
7
+
8
+ describe 'GET index' do
9
+ subject { spree_get :index }
10
+
11
+ it "returns a 200 status code" do
12
+ subject
13
+ expect(response.code).to eq "200"
14
+ end
15
+ end
16
+
17
+ describe 'GET show' do
18
+ let(:gift_card) { create(:virtual_gift_card) }
19
+ let(:redemption_code) { gift_card.redemption_code }
20
+
21
+ subject { spree_get :show, id: redemption_code }
22
+
23
+ context 'with a valid redemption code' do
24
+ it 'loads the gift cards' do
25
+ subject
26
+ expect(assigns(:gift_cards)).to eq [gift_card]
27
+ end
28
+
29
+ it 'returns a 200 status code' do
30
+ subject
31
+ expect(response.code).to eq '200'
32
+ end
33
+ end
34
+
35
+ context 'with an invalid redemption code' do
36
+ let(:redemption_code) { "DOES-NOT-EXIST" }
37
+
38
+ it "redirects to index" do
39
+ expect(subject).to redirect_to spree.admin_gift_cards_path
40
+ end
41
+
42
+ it "sets the flash error" do
43
+ subject
44
+ expect(flash[:error]).to eq Spree.t('admin.gift_cards.errors.not_found')
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'GET lookup' do
50
+ let(:user) { create :user }
51
+ subject { spree_get :lookup, user_id: user.id }
52
+
53
+ it "returns a 200 status code" do
54
+ subject
55
+ expect(response.code).to eq "200"
56
+ end
57
+ end
58
+
59
+ describe 'POST redeem' do
60
+ let(:user) { create :user }
61
+ let(:gift_card) { create(:virtual_gift_card) }
62
+ let(:redemption_code) { gift_card.redemption_code }
63
+
64
+ subject { spree_post :redeem, user_id: user.id, gift_card: { redemption_code: redemption_code } }
65
+
66
+ context "with a gift card that has not yet been redeemed" do
67
+
68
+ it "redirects to store credit index" do
69
+ expect(subject).to redirect_to spree.admin_user_store_credits_path(user)
70
+ end
71
+
72
+ it "redeems the gift card" do
73
+ subject
74
+ expect(gift_card.reload.redeemed?).to eq true
75
+ end
76
+
77
+ it "sets the redeemer to the correct user" do
78
+ subject
79
+ expect(gift_card.reload.redeemer).to eq user
80
+ end
81
+
82
+ it "creates store credit for the user" do
83
+ subject
84
+ expect(user.reload.store_credits.count).to eq 1
85
+ end
86
+
87
+ it "sets the store credit equal to the amount of the gift card" do
88
+ subject
89
+ expect(user.reload.store_credits.first.amount).to eq gift_card.amount
90
+ end
91
+ end
92
+
93
+ context "with a gift card that has already been redeemed" do
94
+ before(:each) { gift_card.update_attribute(:redeemed_at, Date.yesterday) }
95
+
96
+ it "renders the lookup page" do
97
+ subject
98
+ expect(response).to render_template(:lookup)
99
+ end
100
+ end
101
+
102
+ context "with a gift card code that does not exist" do
103
+ let(:redemption_code) { "INVALID-REDEMPTION-CODE" }
104
+
105
+ it "renders the lookup page" do
106
+ subject
107
+ expect(response).to render_template(:lookup)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Api::GiftCardsController do
4
+ render_views
5
+ let!(:credit_type) { create(:secondary_credit_type, name: "Non-expiring") }
6
+ let!(:gc_category) { create(:store_credit_gift_card_category) }
7
+
8
+ describe "POST redeem" do
9
+ let(:gift_card) { create(:virtual_gift_card) }
10
+
11
+ let(:parameters) do
12
+ {
13
+ redemption_code: gift_card.redemption_code
14
+ }
15
+ end
16
+
17
+ subject { spree_post :redeem, parameters, { format: :json } }
18
+
19
+ context "the user is not logged in" do
20
+
21
+ before { subject }
22
+
23
+ it "returns a 401" do
24
+ expect(response.status).to eq 401
25
+ end
26
+ end
27
+
28
+ context "the current api user is authenticated" do
29
+ let(:api_user) { create(:user) }
30
+
31
+ before do
32
+ allow(controller).to receive(:load_user)
33
+ controller.instance_variable_set(:@current_api_user, api_user)
34
+ end
35
+
36
+ let(:parsed_response) { HashWithIndifferentAccess.new(JSON.parse(response.body)) }
37
+
38
+ context "given an invalid gift card redemption code" do
39
+ before { subject }
40
+
41
+ let(:parameters) do
42
+ {
43
+ redemption_code: 'INVALID_CODE'
44
+ }
45
+ end
46
+
47
+ it 'does not find the gift card' do
48
+ expect(assigns(:gift_card)).to eq nil
49
+ end
50
+
51
+ it 'contains an error message' do
52
+ expect(parsed_response['error_message']).to be_present
53
+ end
54
+
55
+ it "returns a 404" do
56
+ expect(subject.status).to eq 404
57
+ end
58
+ end
59
+
60
+ context "there is no redemption code in the request body" do
61
+ let(:parameters) { {} }
62
+
63
+ it "returns a 404" do
64
+ expect(subject.status).to eq 404
65
+ end
66
+ end
67
+
68
+ context "given a valid gift card redemption code" do
69
+
70
+ it 'finds the gift card' do
71
+ subject
72
+ expect(assigns(:gift_card)).to eq gift_card
73
+ end
74
+
75
+ it 'redeems the gift card' do
76
+ allow(Spree::VirtualGiftCard).to receive(:active_by_redemption_code).and_return(gift_card)
77
+ expect(gift_card).to receive(:redeem).with(api_user)
78
+ subject
79
+ end
80
+
81
+ it "returns a 201" do
82
+ subject
83
+ expect(subject.status).to eq 201
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::RedemptionCodeGenerator do
4
+ describe '#generate_redemption_code' do
5
+ subject { Spree::RedemptionCodeGenerator.generate_redemption_code }
6
+
7
+ it 'generates a 16 character alpha-numeric code' do
8
+ code = subject
9
+ expect(code.match('^[a-zA-Z0-9]{16}')[0]).to eq code
10
+ end
11
+ end
12
+
13
+ describe '#format_redemption_code_for_lookup' do
14
+ subject { Spree::RedemptionCodeGenerator.format_redemption_code_for_lookup(redemption_code) }
15
+
16
+ context 'redemption code has no dashes' do
17
+ let(:redemption_code) { "1234ABCD1234ABCD" }
18
+
19
+ it 'does nothing to the code' do
20
+ expect(subject).to eq redemption_code
21
+ end
22
+ end
23
+
24
+ context 'redemption code 4 groups of 4 characters, separated by dashes' do
25
+ let(:redemption_code) { "1234-ABCD-1234-ABCD" }
26
+ let(:formatted_redemption_code) { "1234ABCD1234ABCD" }
27
+
28
+ it 'strips the dashes' do
29
+ expect(subject).to eq formatted_redemption_code
30
+ end
31
+ end
32
+
33
+ context 'redemption code is mixed-case' do
34
+ let(:redemption_code) { "1234-aBCd-1234-AbcD" }
35
+ let(:formatted_redemption_code) { "1234ABCD1234ABCD" }
36
+
37
+ it 'makes it all upcase' do
38
+ expect(subject).to eq formatted_redemption_code
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::LineItem do
4
+ describe "#redemption_codes" do
5
+ let(:line_item) { create(:line_item, quantity: 2) }
6
+ let!(:gift_card) { create(:virtual_gift_card, line_item: line_item) }
7
+ let!(:gift_card_2) { create(:virtual_gift_card, line_item: line_item) }
8
+ let(:subject) { line_item.redemption_codes }
9
+
10
+ it 'has the correct number of redemption codes and keys' do
11
+ expect(subject.size).to eq line_item.quantity
12
+ end
13
+
14
+ it 'contains a redemption_code' do
15
+ expect(subject.first).to have_key :redemption_code
16
+ end
17
+
18
+ it 'formats the redemption_code' do
19
+ expect(subject.first).to have_value gift_card.formatted_redemption_code
20
+ end
21
+
22
+ it 'contains an amount' do
23
+ expect(subject.first).to have_key :amount
24
+ end
25
+
26
+ it 'formats the amount' do
27
+ expect(subject.first).to have_value gift_card.formatted_amount
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Order do
4
+ describe "#create_gift_cards" do
5
+ let(:order) { create(:order_with_line_items) }
6
+ let(:line_item) { order.line_items.first }
7
+ subject { order.create_gift_cards }
8
+
9
+ context "the line item is a gift card" do
10
+ before do
11
+ allow(line_item).to receive(:gift_card?).and_return(true)
12
+ allow(line_item).to receive(:quantity).and_return(3)
13
+ end
14
+
15
+ it 'creates a gift card for each gift card in the line item' do
16
+ expect { subject }.to change { Spree::VirtualGiftCard.count }.by(line_item.quantity)
17
+ end
18
+
19
+ it 'sets the purchaser, amount, and currency' do
20
+ expect(Spree::VirtualGiftCard).to receive(:create!).exactly(3).times.with(amount: line_item.price, currency: line_item.currency, purchaser: order.user, line_item: line_item)
21
+ subject
22
+ end
23
+ end
24
+
25
+ context "the line item is not a gift card" do
26
+ before { allow(line_item).to receive(:gift_card?).and_return(false) }
27
+
28
+ it 'does not create a gift card' do
29
+ expect(Spree::VirtualGiftCard).not_to receive(:create!)
30
+ subject
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#finalize!" do
36
+ context "the order contains gift cards and transitions to complete" do
37
+ let(:order) { create(:order_with_line_items, state: 'complete') }
38
+
39
+ subject { order.finalize! }
40
+
41
+ it "calls #create_gift_cards" do
42
+ expect(order).to receive(:create_gift_cards)
43
+ subject
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#send_gift_card_emails" do
49
+
50
+ subject { order.send_gift_card_emails }
51
+
52
+ context "the order has gift cards" do
53
+ let(:gift_card) { create(:virtual_gift_card) }
54
+ let(:line_item) { gift_card.line_item }
55
+ let(:gift_card_2) { create(:virtual_gift_card, line_item: line_item) }
56
+ let(:order) { gift_card.line_item.order }
57
+
58
+ it "should call GiftCardMailer#send" do
59
+ expect(Spree::GiftCardMailer).to receive(:gift_card_email).with(gift_card).and_return(double(deliver: true))
60
+ expect(Spree::GiftCardMailer).to receive(:gift_card_email).with(gift_card_2).and_return(double(deliver: true))
61
+ subject
62
+ end
63
+ end
64
+
65
+ context "no gift cards" do
66
+ let(:order) { create(:order_with_line_items) }
67
+
68
+ it "should not call GiftCardMailer#send" do
69
+ expect(Spree::GiftCardMailer).to_not receive(:gift_card_email)
70
+ subject
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#display_total_applicable_store_credit" do
76
+ let(:total_applicable_store_credit) { 10.00 }
77
+
78
+ subject { create(:order) }
79
+
80
+ before { allow(subject).to receive_messages(total_applicable_store_credit: total_applicable_store_credit) }
81
+
82
+ it "returns a money instance" do
83
+ expect(subject.display_total_applicable_store_credit).to be_a(Spree::Money)
84
+ end
85
+
86
+ it "returns a negative amount" do
87
+ expect(subject.display_total_applicable_store_credit.money.cents).to eq (total_applicable_store_credit * -100.0)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::PermissionSets::VirtualGiftCardDisplay do
4
+ let(:ability) { Spree::Ability.new nil }
5
+
6
+ subject { ability }
7
+
8
+ context "when activated" do
9
+ before do
10
+ described_class.new(ability).activate!
11
+ end
12
+
13
+ it { should be_able_to(:admin, Spree::VirtualGiftCard) }
14
+ it { should be_able_to(:display, Spree::VirtualGiftCard) }
15
+ end
16
+
17
+ context "when not activated" do
18
+ it { should_not be_able_to(:admin, Spree::VirtualGiftCard) }
19
+ it { should_not be_able_to(:display, Spree::VirtualGiftCard) }
20
+ end
21
+ end
22
+
@@ -0,0 +1,21 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Spree::PermissionSets::VirtualGiftCardManagement do
5
+ let(:ability) { Spree::Ability.new nil }
6
+
7
+ subject { ability }
8
+
9
+ context "when activated" do
10
+ before do
11
+ described_class.new(ability).activate!
12
+ end
13
+
14
+ it { should be_able_to(:manage, Spree::VirtualGiftCard) }
15
+ end
16
+
17
+ context "when not activated" do
18
+ it { should_not be_able_to(:manage, Spree::VirtualGiftCard) }
19
+ end
20
+ end
21
+
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::StoreCreditCategory, type: :model do
4
+ describe "#non_expiring?" do
5
+ subject { build(:store_credit_category, name: category_name).non_expiring? }
6
+
7
+ context "non-expiring type store credit" do
8
+ let(:category_name) { Spree::StoreCreditCategory.non_expiring_credit_types.first }
9
+
10
+ it "returns true" do
11
+ expect(subject).to be true
12
+ end
13
+ end
14
+
15
+ context "expiring type store credit" do
16
+ let(:category_name) { "Expiring" }
17
+
18
+ it "returns false" do
19
+ expect(subject).to be false
20
+ end
21
+ end
22
+ end
23
+ end