2014-11-04 20:55:12 +00:00
module JamRuby
class ShoppingCart < ActiveRecord :: Base
2015-04-10 20:19:08 +00:00
# just a normal purchase; used on the description field of a recurly adjustment
PURCHASE_NORMAL = 'purchase-normal'
# a free purchase; used on the description field of a recurly adjustment
PURCHASE_FREE = 'purchase-free'
# a techinicality of Recurly; we create a free-credit adjustment to balance out the free purchase adjustment
PURCHASE_FREE_CREDIT = 'purchase-free-credit'
PURCHASE_REASONS = [ PURCHASE_NORMAL , PURCHASE_FREE , PURCHASE_FREE_CREDIT ]
2017-02-05 20:42:51 +00:00
JAMTRACK_FULL = 'full'
JAMTRACK_STREAM = 'stream'
JAMTRACK_DOWNLOAD = 'download'
JAMTRACK_VARIANTS = [ 'full' , 'stream' , 'download' ]
2014-11-04 20:55:12 +00:00
attr_accessible :quantity , :cart_type , :product_info
2015-11-13 13:12:58 +00:00
attr_accessor :skip_mix_check
2015-04-19 16:43:54 +00:00
validates_uniqueness_of :cart_id , scope : [ :cart_type , :user_id , :anonymous_user_id ]
2015-04-10 20:19:08 +00:00
2014-11-04 20:55:12 +00:00
belongs_to :user , :inverse_of = > :shopping_carts , :class_name = > " JamRuby::User " , :foreign_key = > " user_id "
validates :cart_id , presence : true
validates :cart_type , presence : true
validates :cart_class_name , presence : true
2015-03-23 13:52:52 +00:00
validates :marked_for_redeem , numericality : { only_integer : true }
2017-02-05 20:42:51 +00:00
validates :variant , inclusion : { in : [ nil , JAMTRACK_FULL , JAMTRACK_STREAM , JAMTRACK_DOWNLOAD ] }
2016-08-03 01:46:15 +00:00
#validate :not_mixed
2014-11-04 20:55:12 +00:00
2016-07-17 15:16:27 +00:00
default_scope { order ( 'created_at DESC' ) }
2014-11-04 20:55:12 +00:00
2016-04-06 02:23:15 +00:00
def product_info ( instance = nil )
2014-11-04 20:55:12 +00:00
product = self . cart_product
2017-02-05 20:42:51 +00:00
data = { type : cart_type , name : product . name , price : product . variant_price ( variant ) , product_id : cart_id , plan_code : product . plan_code , real_price : real_price ( product ) , total_price : total_price ( product ) , quantity : quantity , marked_for_redeem : marked_for_redeem , free : free? , sales_region : product . sales_region , sale_display : product . sale_display ( variant ) , allow_free : allow_free ( product ) } unless product . nil?
2016-04-06 02:23:15 +00:00
if data && instance
data . merge! ( instance . product_info )
end
data
2014-11-04 20:55:12 +00:00
end
2015-04-10 20:19:08 +00:00
# multiply quantity by price
2015-03-20 13:48:00 +00:00
def total_price ( product )
2017-02-05 20:42:51 +00:00
quantity * product . variant_price ( variant )
end
def purchasing_downloadable_rights?
is_jam_track? && ( variant == ShoppingCart :: JAMTRACK_DOWNLOAD || variant == ShoppingCart :: JAMTRACK_FULL )
2015-04-10 20:19:08 +00:00
end
# multiply (quantity - redeemable) by price
def real_price ( product )
2017-02-05 20:42:51 +00:00
( quantity - marked_for_redeem ) * product . variant_price ( variant )
2015-03-20 13:48:00 +00:00
end
2015-03-23 13:52:52 +00:00
2016-08-03 01:46:15 +00:00
def allow_free ( product )
if ( product . is_a? ( JamTrack ) )
product . allow_free
else
false
end
end
2015-11-13 13:12:58 +00:00
def not_mixed
return if @skip_mix_check
existing_carts = [ ]
this_user = any_user ( )
if this_user
existing_carts = this_user . shopping_carts
end
existing_carts = existing_carts . to_a
existing_carts << self
if Sale . is_mixed ( existing_carts )
if free?
errors . add ( :base , " You can not add a free JamTrack to a cart with non-free items. Please clear out your cart. " )
return false
else
errors . add ( :base , " You can not add a non-free JamTrack to a cart containing free items. Please clear out your cart. " )
return false
end
end
false
end
2015-04-10 20:19:08 +00:00
2014-11-04 20:55:12 +00:00
def cart_product
2015-03-24 15:19:21 +00:00
self . cart_class_name . classify . constantize . find_by_id ( self . cart_id ) unless self . cart_class_name . blank?
2014-11-04 20:55:12 +00:00
end
2015-03-20 13:48:00 +00:00
def redeem ( mark_redeem )
self . marked_for_redeem = mark_redeem ? 1 : 0
end
def free?
marked_for_redeem == quantity
end
2015-11-13 13:12:58 +00:00
def any_user
if user
user
elsif anonymous_user_id
AnonymousUser . new ( anonymous_user_id , nil )
else
nil
end
end
2017-02-05 20:42:51 +00:00
def self . create ( user , product , quantity = 1 , mark_redeem = false , variant = nil )
2015-11-29 19:58:10 +00:00
2014-11-04 20:55:12 +00:00
cart = ShoppingCart . new
2015-03-12 01:55:11 +00:00
if user . is_a? ( User )
cart . user = user
else
cart . anonymous_user_id = user . id
end
2014-11-04 20:55:12 +00:00
cart . cart_type = product . class :: PRODUCT_TYPE
2017-02-05 20:42:51 +00:00
if cart . cart_type == JamTrack :: PRODUCT_TYPE && variant . nil?
cart . variant = JAMTRACK_STREAM # default to jamtrack 'stream'
else
cart . variant = variant
end
2014-11-04 20:55:12 +00:00
cart . cart_class_name = product . class . name
cart . cart_id = product . id
cart . quantity = quantity
2015-03-20 13:48:00 +00:00
cart . redeem ( mark_redeem )
2014-11-04 20:55:12 +00:00
cart . save
cart
end
2015-03-20 13:48:00 +00:00
2015-04-10 20:19:08 +00:00
def is_jam_track?
cart_type == JamTrack :: PRODUCT_TYPE
end
2015-11-29 19:58:10 +00:00
def is_gift_card?
cart_type == GiftCardType :: PRODUCT_TYPE
end
2015-04-10 20:19:08 +00:00
2016-04-06 02:23:15 +00:00
def is_lesson?
cart_type == LessonPackageType :: PRODUCT_TYPE
end
2015-04-10 20:19:08 +00:00
# returns an array of adjustments for the shopping cart
def create_adjustment_attributes ( current_user )
2015-11-29 19:58:10 +00:00
raise " not a jam track or gift card " unless is_jam_track? || is_gift_card?
2015-04-10 20:19:08 +00:00
info = self . product_info
if free?
# create the credit, then the pseudo charge
2016-08-03 01:46:15 +00:00
[ ]
2015-04-10 20:19:08 +00:00
else
2015-11-29 19:58:10 +00:00
2015-04-10 20:19:08 +00:00
[
{
accounting_code : PURCHASE_NORMAL ,
currency : 'USD' ,
unit_amount_in_cents : ( info [ :total_price ] * 100 ) . to_i ,
2015-11-29 19:58:10 +00:00
description : info [ :sale_display ] ,
2015-04-10 20:19:08 +00:00
tax_exempt : false
}
]
end
end
2015-04-19 21:40:35 +00:00
def self . move_to_user ( user , anonymous_user , shopping_carts )
shopping_carts . each do | shopping_cart |
2015-11-29 19:58:10 +00:00
if shopping_cart . is_jam_track?
mark_redeem = ShoppingCart . user_has_redeemable_jam_track? ( user )
2017-02-05 20:42:51 +00:00
cart = ShoppingCart . create ( user , shopping_cart . cart_product , shopping_cart . quantity , mark_redeem , shopping_cart . variant )
2015-11-29 19:58:10 +00:00
else
2017-02-05 20:42:51 +00:00
cart = ShoppingCart . create ( user , shopping_cart . cart_product , shopping_cart . quantity , false , shopping_cart . variant )
2015-11-29 19:58:10 +00:00
end
2015-04-19 21:40:35 +00:00
end
anonymous_user . destroy_all_shopping_carts
end
2015-04-10 20:19:08 +00:00
def self . is_product_purchase? ( adjustment )
( adjustment [ :accounting_code ] . include? ( PURCHASE_FREE ) || adjustment [ :accounting_code ] . include? ( PURCHASE_NORMAL ) ) && ! adjustment [ :accounting_code ] . include? ( PURCHASE_FREE_CREDIT )
end
# recurly_adjustment is a Recurly::Adjustment (http://www.rubydoc.info/gems/recurly/Recurly/Adjustment)
# this asks, 'is this a pending adjustment?' AND 'was this adjustment created by the server (vs manually by someone -- we should leave those alone).'
def self . is_server_pending_adjustment? ( recurly_adjustment )
recurly_adjustment . state == 'pending' && ( recurly_adjustment . accounting_code . include? ( PURCHASE_FREE ) || recurly_adjustment . accounting_code . include? ( PURCHASE_NORMAL ) || recurly_adjustment . accounting_code . include? ( PURCHASE_FREE_CREDIT ) )
end
2015-03-20 13:48:00 +00:00
# if the user has a redeemable jam_track still on their account, then also check if any shopping carts have already been marked.
# if no shpping carts have been marked, then mark it redeemable
# should be wrapped in a TRANSACTION
def self . user_has_redeemable_jam_track? ( any_user )
2015-11-13 13:12:58 +00:00
if any_user . has_redeemable_jamtrack || any_user . gifted_jamtracks > 0
free_in_cart = 0
2016-08-03 01:46:15 +00:00
2015-03-23 13:52:52 +00:00
any_user . shopping_carts . each do | shopping_cart |
2015-03-20 13:48:00 +00:00
# but if we find any shopping cart item already marked for redeem, then back out of mark_redeem=true
2015-11-13 13:12:58 +00:00
if shopping_cart . cart_type == JamTrack :: PRODUCT_TYPE
free_in_cart += shopping_cart . marked_for_redeem
2015-03-20 13:48:00 +00:00
end
end
2015-11-13 13:12:58 +00:00
any_user . free_jamtracks > free_in_cart
else
false
2015-03-20 13:48:00 +00:00
end
end
2015-03-23 13:52:52 +00:00
# adds a jam_track to cart, checking for promotions
2017-02-05 20:42:51 +00:00
def self . add_jam_track_to_cart ( any_user , jam_track , variant = JAMTRACK_FULL )
2015-03-24 15:19:21 +00:00
cart = nil
2017-02-05 20:42:51 +00:00
if variant . nil?
variant = JAMTRACK_FULL
end
2015-03-23 13:52:52 +00:00
ShoppingCart . transaction do
2015-05-15 17:34:35 +00:00
2016-08-03 01:46:15 +00:00
# if clear
if any_user . shopping_carts . length == 1 && any_user . shopping_carts [ 0 ] . product_info [ :allow_free ] && ( any_user . has_redeemable_jamtrack && any_user . gifted_jamtracks == 0 ) && jam_track . allow_free && any_user . free_jamtracks > 0 # clear
2015-11-13 13:12:58 +00:00
# if you are an anonymous user, we make sure there is nothing else in your shopping cart ... keep it clean for the 'new user rummaging around for a freebie scenario'
2015-11-29 19:58:10 +00:00
any_user . destroy_jam_track_shopping_carts
2015-11-13 13:12:58 +00:00
any_user . reload
2015-05-15 17:34:35 +00:00
end
2016-08-03 01:46:15 +00:00
mark_redeem = jam_track . allow_free ? ShoppingCart . user_has_redeemable_jam_track? ( any_user ) : false
2017-02-05 20:42:51 +00:00
cart = ShoppingCart . create ( any_user , jam_track , 1 , mark_redeem , variant )
2015-03-23 13:52:52 +00:00
end
2016-08-03 01:46:15 +00:00
any_user . reload
2015-03-24 15:19:21 +00:00
cart
2015-03-23 13:52:52 +00:00
end
2015-11-29 19:58:10 +00:00
def self . add_item_to_cart ( any_user , item )
cart = nil
ShoppingCart . transaction do
cart = ShoppingCart . create ( any_user , item , 1 , false )
end
cart
end
2015-03-23 13:52:52 +00:00
# deletes a jam track from the shopping cart, updating redeem flag as necessary
def self . remove_jam_track_from_cart ( any_user , cart )
ShoppingCart . transaction do
cart . destroy
2015-11-13 13:12:58 +00:00
# so that user.shopping_carts reflects truth
any_user . reload
# check if we should move the redemption around automatically
2015-03-23 13:52:52 +00:00
mark_redeem = ShoppingCart . user_has_redeemable_jam_track? ( any_user )
carts = any_user . shopping_carts
2015-11-13 13:12:58 +00:00
# if we find any carts on the account that are not redeemed, mark first one redeemable
2015-03-23 13:52:52 +00:00
if mark_redeem && carts . length > 0
2015-11-13 13:12:58 +00:00
carts . each do | cart |
if cart . marked_for_redeem == 0
if cart . quantity > 1
raise 'unknown situation for redeemption juggling'
end
cart . redeem ( mark_redeem )
cart . save
break
end
end
2015-03-23 13:52:52 +00:00
end
end
2015-05-15 17:34:35 +00:00
end
2015-03-23 13:52:52 +00:00
2015-11-29 19:58:10 +00:00
def self . remove_item_from_cart ( any_user , cart )
ShoppingCart . transaction do
cart . destroy
end
end
2015-11-13 13:12:58 +00:00
# if the number of items in the shopping cart is less than gifted_jamtracks on the user, then fix them all up
def self . apply_gifted_jamtracks ( user )
jam_track_carts = user . shopping_carts . where ( cart_type : JamTrack :: PRODUCT_TYPE )
if jam_track_carts . count > user . gifted_jamtracks
# just whack everything in their shopping cart
user . destroy_all_shopping_carts
return
end
jam_track_carts . each do | cart |
cart . skip_mix_check = true
cart . marked_for_redeem = 1
cart . save!
end
end
2015-05-15 17:34:35 +00:00
def port ( user , anonymous_user )
ShoppingCart . transaction do
move_to_user ( user , anonymous_user , anonymous_user . shopping_carts )
end
2015-03-23 13:52:52 +00:00
end
2014-11-04 20:55:12 +00:00
end
end