Skip to content

Commit

Permalink
Initial commit of work in progress.
Browse files Browse the repository at this point in the history
  • Loading branch information
JDutil committed Oct 16, 2012
0 parents commit d523202
Show file tree
Hide file tree
Showing 42 changed files with 675 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
\#*
*~
.#*
.DS_Store
.idea
.project
tmp
nbproject
*.swp
spec/test_app
spec/dummy
Gemfile.lock
coverage
.sass-cache
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'http://rubygems.org'
gem 'spree_core', path: '~/spree/core'
gemspec
23 changes: 23 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Rails Dog LLC nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SpreeGiftCard
=============

This extension adds gift card functionality to spree. It is based off the original [spree_gift_cards](http://github.com/spree/spree_gift_cards)
extension, but differs in that it does not require a user to have an account. Gift cards may be redeemed by
entering a unique gift card code during checkout rather than applying store credits to the customers account.

Installation
============

1. Add `spree_gift_card` to Gemfile
1. Run `rails g spree_gift_card:install`
1. Run `rails g spree_gift_card:seed`

Testing
=======

1. bundle exec rake test_app
1. bundle exec rspec spec

Copyright (c) 2012 Jeff Dutil, released under the New BSD License
15 changes: 15 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'bundler'
Bundler::GemHelper.install_tasks

require 'rspec/core/rake_task'
require 'spree/core/testing_support/common_rake'

RSpec::Core::RakeTask.new

task :default => [:spec]

desc 'Generates a dummy app for testing'
task :test_app do
ENV['LIB_NAME'] = 'spree_gift_card'
Rake::Task['common:test_app'].invoke
end
1 change: 1 addition & 0 deletions Versionfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"1.1.x" => { :branch => "master" }
1 change: 1 addition & 0 deletions app/assets/javascripts/admin/spree_gift_card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//= require admin/spree_core
1 change: 1 addition & 0 deletions app/assets/javascripts/store/spree_gift_card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//= require store/spree_core
3 changes: 3 additions & 0 deletions app/assets/stylesheets/admin/spree_gift_card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/*
*= require admin/spree_core
*/
3 changes: 3 additions & 0 deletions app/assets/stylesheets/store/spree_gift_card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/*
*= require store/spree_core
*/
32 changes: 32 additions & 0 deletions app/controllers/spree/gift_cards_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Spree
class GiftCardsController < Spree::BaseController
helper 'spree/admin/base'

def new
find_gift_card_variants
@gift_card = GiftCard.new
end

def create
@gift_card = GiftCard.new(params[:gift_card])
if @gift_card.save
@order = current_order(true)
line_item = @order.add_variant(@gift_card.variant, 1)
@gift_card.line_item = line_item
@gift_card.save
redirect_to cart_path
else
find_gift_card_variants
render :action => :new
end
end

private

def find_gift_card_variants
gift_card_product_ids = Product.not_deleted.where(["is_gift_card = ?", true]).map(&:id)
@gift_card_variants = Variant.where(["price > 0 AND product_id IN (?)", gift_card_product_ids]).order("price")
end

end
end
43 changes: 43 additions & 0 deletions app/controllers/spree/orders_controller_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Spree::OrdersController.class_eval do

def update
@order = current_order
if @order.update_attributes(params[:order])
if defined?(Spree::Promo) and @order.coupon_code.present?
if apply_coupon_code
flash[:notice] = t(:coupon_code_applied)
else
flash[:error] = t(:promotion_not_found)
render :edit and return
end
end
if @order.gift_code.present?
if apply_gift_code
flash[:notice] = t(:gift_code_applied)
else
flash[:error] = t(:gift_code_not_found)
render :edit and return
end
end
@order.line_items = @order.line_items.select { |li| li.quantity > 0 }
fire_event('spree.order.contents_changed')
respond_with(@order) { |format| format.html { redirect_to cart_path } }
else
respond_with(@order)
end
end

private

def apply_gift_code
return if @order.gift_code.blank?
if gift = Spree::GiftCard.find_by_token(@order.gift_code)
if gift.order_activatable?(@order)
fire_event('spree.checkout.gift_code_added', :gift_code => @order.gift_code)
gift.apply(@order)
true
end
end
end

end
9 changes: 9 additions & 0 deletions app/mailers/spree/order_mailer_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Spree::OrderMailer.class_eval do
def gift_card_email(card, order)
@gift_card = card
@order = order
subject = "#{Spree::Config[:site_name]} Gift Card"
@gift_card.update_attribute(:sent_at, Time.now)
mail(:to => card.email, :subject => subject)
end
end
5 changes: 5 additions & 0 deletions app/models/spree/adjustment_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Spree::Adjustment.class_eval do

scope :gift_card, where(:originator_type => 'Spree::GiftCard')

end
63 changes: 63 additions & 0 deletions app/models/spree/gift_card.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require 'spree/core/validators/email'

module Spree
class GiftCard < ActiveRecord::Base

UNACTIVATABLE_ORDER_STATES = ["complete", "awaiting_return", "returned"]

belongs_to :variant
belongs_to :line_item

validates :current_value, presence: true
validates :email, email: true, presence: true
validates :original_value, presence: true
validates :name, presence: true
validates :token, presence: true, uniqueness: true

before_validation :generate_token, on: :create
before_validation :set_values, on: :create
before_validation :set_calculator # Goes after set_values to ensure current_value is set.

attr_accessible :email, :name, :note, :variant_id

calculated_adjustments

def apply(order)
# Nothing to do if the gift card is already associated with the order
return if order.gift_credit_exists?(self)
# order.adjustments.gift_card.reload.clear
order.update!
create_adjustment(I18n.t(:gift_card), order, order)
order.update!
# TODO: if successful we should update preferred amount or should that be done elsewhere? Might make sense to create a new calculator that does the updating
end

def price
self.line_item ? self.line_item.price * self.line_item.quantity : self.variant.price
end

def order_activatable?(order)
order &&
created_at.to_i < order.created_at.to_i &&
!UNACTIVATABLE_ORDER_STATES.include?(order.state)
end

private

def generate_token
until self.token.present? && self.class.where(token: self.token).count == 0
self.token = Digest::SHA1.hexdigest([Time.now, rand].join)
end
end

def set_calculator
self.calculator = Spree::Calculator::FlatRate.new({preferred_amount: -(self.current_value || 0)})
end

def set_values
self.current_value = self.variant.try(:price)
self.original_value = self.variant.try(:price)
end

end
end
40 changes: 40 additions & 0 deletions app/models/spree/order_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Spree::Order.class_eval do

attr_accessible :gift_code
attr_accessor :gift_code

# Tells us if there is the specified gift code already associated with the order
# regardless of whether or not its currently eligible.
def gift_credit_exists?(gift_card)
!! adjustments.gift_card.reload.detect { |credit| credit.originator_id == gift_card.id }
end

# unless self.method_defined?('update_adjustments_with_promotion_limiting')
# def update_adjustments_with_promotion_limiting
# update_adjustments_without_promotion_limiting
# return if adjustments.promotion.eligible.none?
# most_valuable_adjustment = adjustments.promotion.eligible.max{|a,b| a.amount.abs <=> b.amount.abs}
# current_adjustments = (adjustments.promotion.eligible - [most_valuable_adjustment])
# current_adjustments.each do |adjustment|
# adjustment.update_attribute_without_callbacks(:eligible, false)
# end
# end
# alias_method_chain :update_adjustments, :promotion_limiting
# end

# Finalizes an in progress order after checkout is complete.
# Called after transition to complete state when payments will have been processed
def finalize_with_gift_card!
finalize_without_gift_card!
self.line_items.each do |li|
Spree::OrderMailer.gift_card_email(li.gift_card, self).deliver if li.gift_card
end
end
alias_method_chain :finalize!, :gift_card

def contains?(variant)
return false if variant.product.is_gift_card?
line_items.detect{ |line_item| line_item.variant_id == variant.id }
end

end
8 changes: 8 additions & 0 deletions app/models/spree/product_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Spree::Product.class_eval do

attr_accessible :is_gift_card

scope :gift_cards, where(is_gift_card: true)
scope :not_gift_cards, where(is_gift_card: false)

end
5 changes: 5 additions & 0 deletions app/overrides/insert_bottom_admin_product_form_right.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Deface::Override.new(:virtual_path => "spree/admin/products/_form",
:name => "insert_bottom_admin_product_form_right",
:insert_bottom => "[data-hook='admin_product_form_right']",
:partial => %q{spree/admin/products/gift_card_fields},
:disabled => false)
5 changes: 5 additions & 0 deletions app/overrides/insert_bottom_order_item_description.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Deface::Override.new(:virtual_path => "spree/shared/_order_details",
:name => "insert_bottom_order_item_description",
:insert_bottom => "[data-hook='order_item_description']",
:partial => 'spree/orders/item_gift_certificate_info',
:disabled => false)
6 changes: 6 additions & 0 deletions app/overrides/insert_bottom_sidebar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Deface::Override.new(:virtual_path => "spree/layouts/spree_application",
:name => "insert_bottom_sidebar",
:insert_bottom => "#sidebar, [data-hook='sidebar']",
:text => %q{<%= link_to t("buy_gift_card"), new_gift_card_path, :class => 'button' %>},
:disabled => false,
:original => '2f11ce271ae3b346b9fc6a927598ad6d6d6a1885')
6 changes: 6 additions & 0 deletions app/overrides/replace_contents_cart_item_description.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Deface::Override.new(:virtual_path => "spree/orders/_line_item",
:name => "replace_contents_cart_item_description",
:insert_bottom => "[data-hook='cart_item_description']",
:partial => 'spree/orders/line_item_gift_certificate_info',
:disabled => false,
:original => '95a090c709b76195844a3e0019062916e7595109')
3 changes: 3 additions & 0 deletions app/views/spree/admin/products/_gift_card_fields.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p>
<%= f.check_box(:is_gift_card) %> <%= f.label :is_gift_card, t("is_gift_card")%>
</p>
22 changes: 22 additions & 0 deletions app/views/spree/gift_cards/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h1><%= t(:gift_cards) %></h1>
<%= render "spree/shared/error_messages", :target => @gift_card %>
<%= form_for @gift_card do |f| %>
<p>
<% @gift_card_variants.each do |card| %>
<%= f.radio_button :variant_id, card.id %> <%= number_to_currency(card.price) %>
<% end %>
</p>
<%= f.field_container :email do %>
<%= f.label :email, t("email") %> <span class="required">*</span><br />
<%= f.text_field :email %>
<% end %>
<%= f.field_container :name do %>
<%= f.label :name, t("recipient_name") %> <span class="required">*</span><br />
<%= f.text_field :name %>
<% end %>
<%= f.field_container :note do %>
<%= f.label :note, t("note") %><br />
<%= f.text_area :note, :rows => 10 %>
<% end %>
<%= f.submit t(:add_to_cart) %>
<% end %>
3 changes: 3 additions & 0 deletions app/views/spree/order_mailer/gift_card_email.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p>Hi <%= @gift_card.name %>,</p>
<p><%= @gift_card.note %></p>
<p>To use your <%= number_to_currency @gift_card.price %> Gift Card enter the following Gift Code during checkout: <%= @gift_card.token %></p>
8 changes: 8 additions & 0 deletions app/views/spree/orders/_gift_code_field.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<tbody>
<tr>
<td colspan='6'>
<%= order_form.label :gift_code, t(:gift_code) + ':' %>
<%= order_form.text_field :gift_code, :size => 10, :value => nil %>
</td>
</tr>
</tbody>
4 changes: 4 additions & 0 deletions app/views/spree/orders/_item_gift_certificate_info.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<% if item.gift_card %>
<br/><b><%= t(:for) %>:</b> <%= item.gift_card.name %> (<%= item.gift_card.email %>)
<br/><b><%= t(:note) %>:</b> <%= item.gift_card.note %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<% if line_item.gift_card %>
<br/><b><%= t(:for) %>:</b> <%= line_item.gift_card.name %> (<%= line_item.gift_card.email %>)
<br/><b><%= t(:note) %>:</b> <%= line_item.gift_card.note %>
<% end %>
9 changes: 9 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
en:
buy_gift_card: "Buy gift card"
gift_card: 'Gift Card'
gift_cards: "Gift cards"
gift_code: 'Gift Code'
gift_code_applied: 'Gift code has been successfully applied to your order.'
gift_code_not_found: "The gift code you entered doesn't exist. Please try again."
is_gift_card: "is gift card"
recipient_name: "Recipient name"
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Spree::Core::Engine.routes.draw do
resources :gift_cards
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddGiftCardAttrToSpreeProducts < ActiveRecord::Migration
def change
add_column :spree_products, :is_gift_card, :boolean, :default => false, :null => false
end
end
Loading

0 comments on commit d523202

Please sign in to comment.