2015-04-03 20:34:12 +00:00
module JamRuby
# a sale is created every time someone tries to buy something
class Sale < ActiveRecord :: Base
2015-04-10 20:19:08 +00:00
JAMTRACK_SALE = 'jamtrack'
2016-04-06 02:23:15 +00:00
LESSON_SALE = 'lesson'
2015-04-10 20:19:08 +00:00
2016-02-09 15:12:10 +00:00
SOURCE_RECURLY = 'recurly'
SOURCE_IOS = 'ios'
2015-04-03 20:34:12 +00:00
belongs_to :user , class_name : 'JamRuby::User'
has_many :sale_line_items , class_name : 'JamRuby::SaleLineItem'
2015-04-12 18:45:26 +00:00
has_many :recurly_transactions , class_name : 'JamRuby::RecurlyTransactionWebHook' , inverse_of : :sale , foreign_key : 'invoice_id' , primary_key : 'recurly_invoice_id'
2015-04-10 20:19:08 +00:00
validates :order_total , numericality : { only_integer : false }
2015-04-03 20:34:12 +00:00
validates :user , presence : true
2015-04-12 18:45:26 +00:00
@@log = Logging . logger [ Sale ]
def self . index ( user , params = { } )
limit = params [ :per_page ]
limit || = 20
limit = limit . to_i
query = Sale . limit ( limit )
. includes ( [ :recurly_transactions , :sale_line_items ] )
. where ( 'sales.user_id' = > user . id )
. order ( 'sales.created_at DESC' )
current_page = params [ :page ] . nil? ? 1 : params [ :page ] . to_i
next_page = current_page + 1
# will_paginate gem
query = query . paginate ( :page = > current_page , :per_page = > limit )
2016-04-06 02:23:15 +00:00
if query . length == 0 # no more results
{ query : query , next_page : nil }
elsif query . length < limit # no more results
{ query : query , next_page : nil }
2015-04-12 18:45:26 +00:00
else
2016-04-06 02:23:15 +00:00
{ query : query , next_page : next_page }
2015-04-12 18:45:26 +00:00
end
end
def state
original_total = self . recurly_total_in_cents
is_voided = false
refund_total = 0
recurly_transactions . each do | transaction |
if transaction . is_voided?
is_voided = true
else
end
if transaction . is_refund?
refund_total = refund_total + transaction . amount_in_cents
end
end
# if refund_total is > 0, then you have a refund.
# if voided is true, then in theory the whole thing has been refunded
{
voided : is_voided ,
original_total : original_total ,
refund_total : refund_total
}
end
2015-04-10 20:19:08 +00:00
2016-01-28 17:31:57 +00:00
# The expectation is that this code would throw an exception (breaking the transaction that encompasses it),
# if it can't validate the receipt, or communicate with Apple at all, etc
#
# So, if this raises exceptions, you can handle them in the stubbed out begin/rescue in ApiJamTracksController#ios_order_placed
2016-02-09 12:46:18 +00:00
def self . validateIOSReceipt ( receipt , price_data , user , sale )
2016-01-28 17:31:57 +00:00
# these are all 'in cents' (as painfully named to be very clear), and all expected to be integers
2016-02-07 14:54:49 +00:00
price = price_data [ 'product_price' ] . to_f * 100 . 0
price_info = {
2016-07-10 01:48:22 +00:00
subtotal_in_cents : price ,
total_in_cents : price ,
tax_in_cents : nil ,
currency : price_data [ 'product_currency' ]
2016-02-07 14:54:49 +00:00
}
2016-07-10 01:48:22 +00:00
response = IosReceiptValidator . post ( '/verifyReceipt' ,
body : { 'receipt-data' = > receipt } . to_json ,
headers : { 'Content-Type' = > 'application/json' } )
2016-02-07 14:54:49 +00:00
json_resp = JSON . parse ( response . body )
# https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1
2016-02-09 11:00:39 +00:00
if 0 != json_resp [ 'status' ]
2016-02-07 14:54:49 +00:00
err_msgs = {
2016-07-10 01:48:22 +00:00
21000 = > 'The App Store could not read the JSON object you provided.' ,
21002 = > 'The data in the receipt-data property was malformed or missing.' ,
21003 = > 'The receipt could not be authenticated.' ,
21005 = > 'The receipt server is not currently available.' ,
21007 = > 'This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.' ,
21008 = > 'This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.'
2016-02-07 14:54:49 +00:00
}
2016-02-09 11:00:39 +00:00
raise err_msgs [ json_resp [ 'status' ] ]
2016-02-09 12:46:18 +00:00
else
receiptJson = SaleReceiptIOS . new
receiptJson . user = user
receiptJson . sale = sale
receiptJson . data_blob = json_resp
receiptJson . save!
2016-02-07 14:54:49 +00:00
end
2016-01-28 17:31:57 +00:00
price_info
end
2015-04-10 20:19:08 +00:00
2016-02-07 14:54:49 +00:00
def self . ios_purchase ( current_user , jam_track , receipt , price_data )
2016-01-28 17:31:57 +00:00
jam_track_right = nil
# everything needs to go into a transaction! If anything goes wrong, we need to raise an exception to break it
Sale . transaction do
using_free_credit = current_user . redeem_free_credit
2016-02-09 15:12:10 +00:00
sale = create_jam_track_sale ( current_user , SOURCE_IOS )
2016-01-28 17:31:57 +00:00
if sale . valid?
if using_free_credit
SaleLineItem . create_from_jam_track ( current_user , sale , jam_track , using_free_credit )
sale . recurly_subtotal_in_cents = 0
sale . recurly_tax_in_cents = 0
sale . recurly_total_in_cents = 0
sale . recurly_currency = 'USD'
sale . save!
else
2016-02-09 15:12:10 +00:00
2016-02-09 12:46:18 +00:00
price_info = validateIOSReceipt ( receipt , price_data , current_user , sale )
2016-01-28 17:31:57 +00:00
SaleLineItem . create_from_jam_track ( current_user , sale , jam_track , using_free_credit )
sale . recurly_subtotal_in_cents = price_info [ :subtotal_in_cents ]
sale . recurly_tax_in_cents = price_info [ :tax_in_cents ]
sale . recurly_total_in_cents = price_info [ :total_in_cents ]
sale . recurly_currency = price_info [ :currency ]
sale . save!
end
else
# should not get out of testing. This would be very rare (i.e., from a big regression). Sale is always valid at this point.
raise " invalid sale object "
end
# if we make it this far, all is well!
jam_track_right = JamRuby :: JamTrackRight . find_or_create_by_user_id_and_jam_track_id ( current_user . id , jam_track . id ) do | jam_track_right |
jam_track_right . redeemed = using_free_credit
jam_track_right . version = jam_track . version
end
2015-11-29 01:38:39 +00:00
end
2016-01-28 17:31:57 +00:00
jam_track_right
2015-11-29 01:38:39 +00:00
end
2015-04-10 20:19:08 +00:00
# place_order will create one or more sales based on the contents of shopping_carts for the current user
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
def self . place_order ( current_user , shopping_carts )
sales = [ ]
2015-11-29 19:58:10 +00:00
if Sale . is_mixed ( shopping_carts )
# the controller checks this too; this is just an extra-level of sanity checking
return sales
2015-04-10 20:19:08 +00:00
end
2015-11-29 19:58:10 +00:00
jam_track_sale = order_jam_tracks ( current_user , shopping_carts )
2015-04-10 20:19:08 +00:00
sales << jam_track_sale if jam_track_sale
# TODO: process shopping_carts_subscriptions
sales
end
2015-11-29 19:58:10 +00:00
def self . is_only_freebie ( shopping_carts )
2015-11-13 13:12:58 +00:00
free = true
2015-11-29 19:58:10 +00:00
shopping_carts . each do | cart |
2015-11-13 13:12:58 +00:00
free = cart . product_info [ :free ]
if ! free
break
end
end
free
end
# we don't allow mixed shopping carts :/
def self . is_mixed ( shopping_carts )
free = false
non_free = false
shopping_carts . each do | cart |
if cart . product_info [ :free ]
free = true
else
non_free = true
end
end
free && non_free
2015-05-15 17:34:35 +00:00
end
2016-05-05 02:20:38 +00:00
def self . purchase_test_drive ( current_user , lesson_package_type , booking = nil )
2016-07-10 01:48:22 +00:00
self . purchase_lesson ( nil , current_user , booking , lesson_package_type )
2016-04-06 02:23:15 +00:00
end
2016-07-10 01:48:22 +00:00
def self . post_sale_test_failure
return true
2016-04-06 02:23:15 +00:00
end
# this is easy to make generic, but right now, it just purchases lessons
2016-07-10 01:48:22 +00:00
def self . purchase_lesson ( charge , current_user , lesson_booking , lesson_package_type , lesson_session = nil , lesson_package_purchase = nil , force = false )
2016-04-06 02:23:15 +00:00
stripe_charge = nil
sale = nil
purchase = nil
# everything needs to go into a transaction! If anything goes wrong, we need to raise an exception to break it
Sale . transaction ( :requires_new = > true ) do
2016-07-10 01:48:22 +00:00
sale = create_lesson_sale ( current_user )
2016-04-06 02:23:15 +00:00
2016-07-10 01:48:22 +00:00
if sale . valid?
2016-04-06 02:23:15 +00:00
2016-07-10 01:48:22 +00:00
if lesson_booking
lesson_booking . current_lesson = lesson_session
lesson_booking . current_purchase = lesson_package_purchase
end
2016-05-16 16:39:20 +00:00
2016-07-10 01:48:22 +00:00
sale_line_item = SaleLineItem . create_from_lesson_package ( current_user , sale , lesson_package_type , lesson_booking )
2016-04-06 02:23:15 +00:00
2016-07-10 01:48:22 +00:00
price_info = charge_stripe_for_lesson ( charge , current_user , lesson_booking , lesson_package_type , sale_line_item , lesson_session , lesson_package_purchase , force )
2016-04-06 02:23:15 +00:00
2016-07-10 01:48:22 +00:00
post_stripe_test_failure
2016-04-06 02:23:15 +00:00
2016-07-10 01:48:22 +00:00
if price_info [ :purchase ] && price_info [ :purchase ] . errors . any?
purchase = price_info [ :purchase ]
raise ActiveRecord :: Rollback
end
2016-04-06 02:23:15 +00:00
2016-07-10 01:48:22 +00:00
if ! sale_line_item . valid?
raise " invalid sale_line_item object for user #{ current_user . email } and lesson_booking #{ lesson_booking . id } "
end
# sale.source = 'stripe'
sale . recurly_subtotal_in_cents = price_info [ :subtotal_in_cents ]
sale . recurly_tax_in_cents = price_info [ :tax_in_cents ]
sale . recurly_total_in_cents = price_info [ :total_in_cents ]
sale . recurly_currency = price_info [ :currency ]
sale . stripe_charge_id = price_info [ :charge_id ]
sale . save
stripe_charge = price_info [ :charge ]
purchase = price_info [ :purchase ]
else
# should not get out of testing. This would be very rare (i.e., from a big regression). Sale is always valid at this point.
puts " invalid sale object "
raise " invalid sale object "
end
2016-04-06 02:23:15 +00:00
end
2016-05-16 16:39:20 +00:00
2016-04-06 02:23:15 +00:00
{ sale : sale , stripe_charge : stripe_charge , purchase : purchase }
end
2016-07-10 01:48:22 +00:00
def self . charge_stripe_for_lesson ( charge , current_user , lesson_booking , lesson_package_type , sale_line_item , lesson_session = nil , lesson_package_purchase = nil , force = false )
2016-04-06 02:23:15 +00:00
if lesson_package_purchase
target = lesson_package_purchase
elsif lesson_session
target = lesson_session
else
target = lesson_package_type
end
current_user . sync_stripe_customer
purchase = lesson_package_purchase
purchase = LessonPackagePurchase . create ( current_user , lesson_booking , lesson_package_type ) if purchase . nil?
if purchase . errors . any?
2016-05-05 02:20:38 +00:00
puts " purchase errors #{ purchase . errors . inspect } "
2016-04-06 02:23:15 +00:00
price_info = { }
price_info [ :purchase ] = purchase
return price_info
end
if lesson_session
lesson_session . lesson_package_purchase_id = purchase . id
lesson_session . save!
end
subtotal_in_cents = purchase . price_in_cents
tax_percent = 0
if current_user . stripe_zip_code
lookup = ZipCodes . identify ( current_user . stripe_zip_code )
if lookup && lookup [ :state_code ] == 'TX'
tax_percent = 0 . 0825
end
end
tax_in_cents = ( subtotal_in_cents * tax_percent ) . round
total_in_cents = subtotal_in_cents + tax_in_cents
2016-07-10 01:48:22 +00:00
lesson_id = lesson_session . id if lesson_session # not set if test drive
charge_id = charge . id if charge # not set if test drive
2016-04-06 02:23:15 +00:00
stripe_charge = Stripe :: Charge . create (
:amount = > total_in_cents ,
:currency = > " usd " ,
:customer = > current_user . stripe_customer_id ,
2016-07-10 01:48:22 +00:00
:description = > target . stripe_description ( lesson_booking ) ,
:metadata = > {
lesson_package : purchase . id ,
lesson_session : lesson_id ,
charge : charge_id ,
user : current_user . id
}
2016-04-06 02:23:15 +00:00
)
2016-07-10 01:48:22 +00:00
if charge
charge . stripe_charge = stripe_charge
end
2016-04-06 02:23:15 +00:00
sale_line_item . lesson_package_purchase = purchase
sale_line_item . save
price_info = { }
price_info [ :subtotal_in_cents ] = subtotal_in_cents
price_info [ :tax_in_cents ] = tax_in_cents
price_info [ :total_in_cents ] = total_in_cents
price_info [ :currency ] = 'USD'
price_info [ :charge_id ] = stripe_charge . id
price_info [ :charge ] = stripe_charge
price_info [ :purchase ] = purchase
price_info
end
2015-04-10 20:19:08 +00:00
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
2015-11-29 19:58:10 +00:00
def self . order_jam_tracks ( current_user , shopping_carts )
shopping_carts_jam_tracks = [ ]
shopping_carts_subscriptions = [ ]
shopping_carts_gift_cards = [ ]
shopping_carts . each do | shopping_cart |
if shopping_cart . is_jam_track?
shopping_carts_jam_tracks << shopping_cart
elsif shopping_cart . is_gift_card?
shopping_carts_gift_cards << shopping_cart
else
# XXX: this may have to be revisited when we actually have something other than JamTracks for puchase
raise " unknown shopping cart type #{ shopping_cart . cart_type } "
shopping_carts_subscriptions << shopping_cart
end
end
2015-04-10 20:19:08 +00:00
client = RecurlyClient . new
sale = nil
Sale . transaction do
2016-02-09 15:12:10 +00:00
sale = create_jam_track_sale ( current_user , SOURCE_RECURLY )
2015-04-10 20:19:08 +00:00
if sale . valid?
2015-11-29 19:58:10 +00:00
if is_only_freebie ( shopping_carts )
sale . process_shopping_carts ( current_user , shopping_carts , nil )
2015-05-15 17:34:35 +00:00
sale . recurly_subtotal_in_cents = 0
sale . recurly_tax_in_cents = 0
sale . recurly_total_in_cents = 0
sale . recurly_currency = 'USD'
2015-08-19 14:24:14 +00:00
if sale . sale_line_items . count == 0
@@log . info ( " no sale line items associated with sale " )
# we must have ditched some of the sale items. let's just abort this sale
sale . destroy
sale = nil
return sale
end
2015-11-29 19:58:10 +00:00
sale . sale_line_items . each do | sale_line_item |
sale_line_item = sale . sale_line_items [ 0 ]
sale_line_item . recurly_tax_in_cents = 0
sale_line_item . recurly_total_in_cents = 0
sale_line_item . recurly_currency = 'USD'
sale_line_item . recurly_discount_in_cents = 0
end
2015-05-15 17:34:35 +00:00
sale . save
else
account = client . get_account ( current_user )
if account . present?
purge_pending_adjustments ( account )
2015-11-29 19:58:10 +00:00
created_adjustments = sale . process_shopping_carts ( current_user , shopping_carts , account )
2015-05-15 17:34:35 +00:00
# now invoice the sale ... almost done
begin
invoice = account . invoice!
sale . recurly_invoice_id = invoice . uuid
sale . recurly_invoice_number = invoice . invoice_number
# now slap in all the real tax/purchase totals
sale . recurly_subtotal_in_cents = invoice . subtotal_in_cents
sale . recurly_tax_in_cents = invoice . tax_in_cents
sale . recurly_total_in_cents = invoice . total_in_cents
sale . recurly_currency = invoice . currency
# and resolve against sale_line_items
sale . sale_line_items . each do | sale_line_item |
found_line_item = false
invoice . line_items . each do | line_item |
if line_item . uuid == sale_line_item . recurly_adjustment_uuid
sale_line_item . recurly_tax_in_cents = line_item . tax_in_cents
sale_line_item . recurly_total_in_cents = line_item . total_in_cents
sale_line_item . recurly_currency = line_item . currency
sale_line_item . recurly_discount_in_cents = line_item . discount_in_cents
found_line_item = true
break
end
2015-04-10 20:19:08 +00:00
end
2015-05-15 17:34:35 +00:00
if ! found_line_item
@@log . error ( " can't find line item #{ sale_line_item . recurly_adjustment_uuid } " )
puts " CANT FIND LINE ITEM "
end
2015-04-13 20:54:23 +00:00
end
2015-04-10 20:19:08 +00:00
2015-05-15 17:34:35 +00:00
unless sale . save
raise RecurlyClientError , " Invalid sale (at end). "
2015-04-10 20:19:08 +00:00
end
2015-05-15 17:34:35 +00:00
rescue Recurly :: Resource :: Invalid = > e
# this exception is thrown by invoice! if the invoice is invalid
sale . rollback_adjustments ( current_user , created_adjustments )
sale = nil
raise ActiveRecord :: Rollback # kill all db activity, but don't break outside logic
2015-04-10 20:19:08 +00:00
end
2015-05-15 17:34:35 +00:00
else
raise RecurlyClientError , " Could not find account to place order. "
2015-04-10 20:19:08 +00:00
end
end
else
raise RecurlyClientError , " Invalid sale. "
end
end
sale
end
2015-11-29 19:58:10 +00:00
def process_shopping_carts ( current_user , shopping_carts , account )
2015-04-10 20:19:08 +00:00
created_adjustments = [ ]
begin
2015-11-29 19:58:10 +00:00
shopping_carts . each do | shopping_cart |
process_shopping_cart ( current_user , shopping_cart , account , created_adjustments )
2015-04-10 20:19:08 +00:00
end
rescue Recurly :: Error , NoMethodError = > x
# rollback any adjustments created if error
rollback_adjustments ( user , created_adjustments )
raise RecurlyClientError , x . to_s
rescue Exception = > e
# rollback any adjustments created if error
rollback_adjustments ( user , created_adjustments )
raise e
end
created_adjustments
end
2015-11-29 19:58:10 +00:00
def process_shopping_cart ( current_user , shopping_cart , account , created_adjustments )
2015-04-10 20:19:08 +00:00
recurly_adjustment_uuid = nil
recurly_adjustment_credit_uuid = nil
# we do this because of ShoppingCart.remove_jam_track_from_cart; if it occurs, which should be rare, we need fresh shopping cart info
shopping_cart . reload
# get the JamTrack in this shopping cart
2015-11-29 19:58:10 +00:00
cart_product = shopping_cart . cart_product
if shopping_cart . is_jam_track?
jam_track = cart_product
if jam_track . right_for_user ( current_user )
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
ShoppingCart . remove_jam_track_from_cart ( current_user , shopping_cart )
return
end
2015-04-10 20:19:08 +00:00
end
2015-11-29 19:58:10 +00:00
2015-05-15 17:34:35 +00:00
if account
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
adjustments = shopping_cart . create_adjustment_attributes ( current_user )
2015-04-10 20:19:08 +00:00
2015-05-15 17:34:35 +00:00
adjustments . each do | adjustment |
2015-04-10 20:19:08 +00:00
2015-05-15 17:34:35 +00:00
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
created_adjustment = account . adjustments . new ( adjustment )
created_adjustment . save
2015-04-10 20:19:08 +00:00
2015-05-15 17:34:35 +00:00
# if the adjustment could not be made, bail
raise RecurlyClientError . new ( created_adjustment . errors ) if created_adjustment . errors . any?
2015-04-10 20:19:08 +00:00
2015-05-15 17:34:35 +00:00
# keep track of adjustments we created for this order, in case we have to roll them back
created_adjustments << created_adjustment
2015-04-10 20:19:08 +00:00
2015-05-15 17:34:35 +00:00
if ShoppingCart . is_product_purchase? ( adjustment )
# this was a normal product adjustment, so track it as such
recurly_adjustment_uuid = created_adjustment . uuid
else
# this was a 'credit' adjustment, so track it as such
recurly_adjustment_credit_uuid = created_adjustment . uuid
end
2015-04-10 20:19:08 +00:00
end
end
2015-05-15 17:34:35 +00:00
2015-04-10 20:19:08 +00:00
# create one sale line item for every jam track
sale_line_item = SaleLineItem . create_from_shopping_cart ( self , shopping_cart , nil , recurly_adjustment_uuid , recurly_adjustment_credit_uuid )
# if the sale line item is invalid, blow up the transaction
unless sale_line_item . valid?
2015-11-29 19:58:10 +00:00
@@log . error ( " sale item invalid! #{ sale_line_item . errors . inspect } " )
2015-04-10 20:19:08 +00:00
puts ( " sale item invalid! #{ sale_line_item . errors . inspect } " )
Stats . write ( 'web.recurly.purchase.sale_invalid' , { message : sale_line_item . errors . to_s , value : 1 } )
raise RecurlyClientError . new ( sale_line_item . errors )
end
2015-11-29 19:58:10 +00:00
if shopping_cart . is_jam_track?
jam_track = cart_product
2015-04-10 20:19:08 +00:00
2015-11-29 19:58:10 +00:00
# create a JamTrackRight (this needs to be in a transaction too to make sure we don't make these by accident)
jam_track_right = JamRuby :: JamTrackRight . find_or_create_by_user_id_and_jam_track_id ( current_user . id , jam_track . id ) do | jam_track_right |
jam_track_right . redeemed = shopping_cart . free?
2015-11-30 23:54:17 +00:00
jam_track_right . version = jam_track . version
2015-11-13 13:12:58 +00:00
end
2015-05-15 17:34:35 +00:00
2015-11-29 19:58:10 +00:00
# also if the purchase was a free one, then:
# first, mark the free has_redeemable_jamtrack field if that's still true
# and if still they have more free things, then redeem the giftable_jamtracks
if shopping_cart . free?
2016-01-03 17:38:30 +00:00
current_user . redeem_free_credit
2015-11-29 19:58:10 +00:00
end
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
if jam_track_right . recurly_adjustment_uuid != recurly_adjustment_uuid
jam_track_right . recurly_adjustment_uuid = recurly_adjustment_uuid
jam_track_right . recurly_adjustment_credit_uuid = recurly_adjustment_credit_uuid
unless jam_track_right . save
raise RecurlyClientError . new ( jam_track_right . errors )
end
end
# blow up the transaction if the JamTrackRight did not get created
raise RecurlyClientError . new ( jam_track_right . errors ) if jam_track_right . errors . any?
elsif shopping_cart . is_gift_card?
gift_card_type = cart_product
raise " gift card is null " if gift_card_type . nil?
raise if current_user . nil?
shopping_cart . quantity . times do | item |
gift_card_purchase = GiftCardPurchase . new (
{
user : current_user ,
gift_card_type : gift_card_type
} )
2015-04-10 20:19:08 +00:00
2015-11-29 19:58:10 +00:00
unless gift_card_purchase . save
raise RecurlyClientError . new ( gift_card_purchase . errors )
end
2015-04-10 20:19:08 +00:00
end
2015-11-29 19:58:10 +00:00
else
raise 'unknown shopping cart type: ' + shopping_cart . cart_type
2015-04-10 20:19:08 +00:00
end
# delete the shopping cart; it's been dealt with
shopping_cart . destroy if shopping_cart
end
def rollback_adjustments ( current_user , adjustments )
begin
adjustments . each { | adjustment | adjustment . destroy }
rescue Exception = > e
AdminMailer . alerts ( {
subject : " ACTION REQUIRED: #{ current_user . email } did not have all of his adjustments destroyed in rollback " ,
body : " go delete any adjustments on the account that don't belong. error: #{ e } \n \n Adjustments: #{ adjustments . inspect } "
} ) . deliver
end
end
def self . purge_pending_adjustments ( account )
account . adjustments . pending . find_each do | adjustment |
# we only pre-emptively destroy pending adjustments if they appear to be created by the server
adjustment . destroy if ShoppingCart . is_server_pending_adjustment? ( adjustment )
end
end
def is_jam_track_sale?
sale_type == JAMTRACK_SALE
end
2016-04-06 02:23:15 +00:00
def is_lesson_sale?
sale_type == LESSON_SALE
end
2016-02-07 14:54:49 +00:00
def self . create_jam_track_sale ( user , sale_source = nil )
2015-04-03 20:34:12 +00:00
sale = Sale . new
sale . user = user
2015-11-29 19:58:10 +00:00
sale . sale_type = JAMTRACK_SALE # gift cards and jam tracks are sold with this type of sale
2015-04-03 20:34:12 +00:00
sale . order_total = 0
2016-02-07 14:54:49 +00:00
sale . source = sale_source if sale_source
2015-04-03 20:34:12 +00:00
sale . save
sale
end
2016-04-06 02:23:15 +00:00
def self . create_lesson_sale ( user )
sale = Sale . new
sale . user = user
sale . sale_type = LESSON_SALE # gift cards and jam tracks are sold with this type of sale
sale . order_total = 0
sale . save
sale
end
2015-04-10 20:19:08 +00:00
# this checks just jamtrack sales appropriately
def self . check_integrity_of_jam_track_sales
Sale . select ( [ :total , :voided ] ) . find_by_sql (
" SELECT COUNT(sales.id) AS total,
2015-04-03 20:34:12 +00:00
COUNT ( CASE WHEN transactions . transaction_type = '#{RecurlyTransactionWebHook::VOID}' THEN 1 ELSE null END ) voided
2015-04-10 20:19:08 +00:00
FROM sales
LEFT OUTER JOIN recurly_transaction_web_hooks as transactions ON invoice_id = sales . recurly_invoice_id
WHERE sale_type = '#{JAMTRACK_SALE}' " )
2015-04-03 20:34:12 +00:00
end
end
2016-01-03 17:38:30 +00:00
end