2016-04-06 02:23:15 +00:00
|
|
|
|
module JamRuby
|
2016-08-03 01:46:15 +00:00
|
|
|
|
class Charge < ActiveRecord::Base
|
2016-04-06 02:23:15 +00:00
|
|
|
|
|
2016-07-10 01:48:22 +00:00
|
|
|
|
attr_accessor :stripe_charge
|
|
|
|
|
|
|
2016-05-11 21:03:55 +00:00
|
|
|
|
belongs_to :user, class_name: "JamRuby::User"
|
|
|
|
|
|
|
2016-04-06 02:23:15 +00:00
|
|
|
|
validates :sent_billing_notices, inclusion: {in: [true, false]}
|
|
|
|
|
|
|
|
|
|
|
|
def max_retries
|
|
|
|
|
|
raise "not implemented"
|
|
|
|
|
|
end
|
|
|
|
|
|
def do_charge(force)
|
|
|
|
|
|
raise "not implemented"
|
|
|
|
|
|
end
|
|
|
|
|
|
def do_send_notices
|
|
|
|
|
|
raise "not implemented"
|
|
|
|
|
|
end
|
|
|
|
|
|
def do_send_unable_charge
|
|
|
|
|
|
raise "not implemented"
|
|
|
|
|
|
end
|
|
|
|
|
|
def charge_retry_hours
|
|
|
|
|
|
24
|
|
|
|
|
|
end
|
|
|
|
|
|
def charged_user
|
|
|
|
|
|
raise "not implemented"
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2016-07-10 01:48:22 +00:00
|
|
|
|
def record_charge(stripe_charge)
|
|
|
|
|
|
self.stripe_charge_id = stripe_charge.id
|
|
|
|
|
|
self.billed = true
|
|
|
|
|
|
self.billed_at = Time.now
|
|
|
|
|
|
self.save(validate: false)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2018-03-13 02:53:24 +00:00
|
|
|
|
def admin_url
|
|
|
|
|
|
APP_CONFIG.admin_root_url + "/admin/charges/" + id
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2016-04-06 02:23:15 +00:00
|
|
|
|
def charge(force = false)
|
|
|
|
|
|
|
2016-07-10 01:48:22 +00:00
|
|
|
|
@stripe_charge = nil
|
2016-04-06 02:23:15 +00:00
|
|
|
|
|
|
|
|
|
|
if !self.billed
|
|
|
|
|
|
|
|
|
|
|
|
# check if we can bill at the moment
|
|
|
|
|
|
if !force && last_billing_attempt_at && (charge_retry_hours.hours.ago < last_billing_attempt_at)
|
|
|
|
|
|
return false
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
if !force && !billing_should_retry
|
|
|
|
|
|
return false
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# bill the user right now. if it fails, move on; will be tried again
|
|
|
|
|
|
self.billing_attempts = self.billing_attempts + 1
|
|
|
|
|
|
self.billing_should_retry = self.billing_attempts < max_retries
|
|
|
|
|
|
self.last_billing_attempt_at = Time.now
|
|
|
|
|
|
self.save(validate: false)
|
|
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
|
|
|
|
|
|
|
stripe_charge = do_charge(force)
|
2016-07-10 01:48:22 +00:00
|
|
|
|
|
|
|
|
|
|
# record the charge in this context (meaning, in our transaction)
|
|
|
|
|
|
record_charge(@stripe_charge) if @stripe_charge
|
|
|
|
|
|
|
2016-04-06 02:23:15 +00:00
|
|
|
|
rescue Stripe::StripeError => e
|
|
|
|
|
|
|
|
|
|
|
|
stripe_handler(e)
|
|
|
|
|
|
|
|
|
|
|
|
subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (stripe)"
|
|
|
|
|
|
body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
2016-08-03 01:46:15 +00:00
|
|
|
|
AdminMailer.alerts({subject: subject, body: body}).deliver_now
|
2016-04-06 02:23:15 +00:00
|
|
|
|
do_send_unable_charge
|
|
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
rescue Exception => e
|
2016-07-10 01:48:22 +00:00
|
|
|
|
|
|
|
|
|
|
# record the charge even if there was an unhandled exception at some point
|
|
|
|
|
|
record_charge(@stripe_charge) if @stripe_charge
|
|
|
|
|
|
|
|
|
|
|
|
unhandled_handler(e)
|
|
|
|
|
|
|
2016-04-06 02:23:15 +00:00
|
|
|
|
subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (unhandled)"
|
|
|
|
|
|
body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
2016-09-08 10:59:58 +00:00
|
|
|
|
AdminMailer.alerts({subject: subject, body: body}).deliver_now
|
2016-04-06 02:23:15 +00:00
|
|
|
|
return false
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
if !self.sent_billing_notices
|
|
|
|
|
|
# If the charge is successful, then we post the charge to the student’s payment history,
|
|
|
|
|
|
# and associate the charge with the lesson, so that everyone knows the student has paid, and we send an email
|
|
|
|
|
|
|
|
|
|
|
|
do_send_notices
|
|
|
|
|
|
|
|
|
|
|
|
self.sent_billing_notices = true
|
|
|
|
|
|
self.sent_billing_notices_at = Time.now
|
|
|
|
|
|
self.post_processed = true
|
|
|
|
|
|
self.post_processed_at = Time.now
|
|
|
|
|
|
self.save(validate: false)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
return stripe_charge
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2016-07-10 01:48:22 +00:00
|
|
|
|
def unhandled_handler(e)
|
|
|
|
|
|
self.billing_error_reason = e.to_s
|
2016-04-06 02:23:15 +00:00
|
|
|
|
if e.cause
|
|
|
|
|
|
self.billing_error_detail = e.cause.to_s + "\n" + e.cause.backtrace.join("\n\t") if e.cause.backtrace
|
|
|
|
|
|
self.billing_error_detail << "\n\n"
|
|
|
|
|
|
self.billing_error_detail << e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
|
|
|
|
|
|
else
|
|
|
|
|
|
self.billing_error_detail = e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
|
|
|
|
|
|
end
|
2016-07-10 01:48:22 +00:00
|
|
|
|
#puts "Charge: unhandled exception #{billing_error_reason}, #{billing_error_detail}"
|
2016-04-06 02:23:15 +00:00
|
|
|
|
self.save(validate: false)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def is_card_declined?
|
|
|
|
|
|
billed == false && billing_error_reason == 'card_declined'
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def is_card_expired?
|
|
|
|
|
|
billed == false && billing_error_reason == 'card_expired'
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def last_billed_at_date
|
|
|
|
|
|
last_billing_attempt_at.strftime("%B %d, %Y") if last_billing_attempt_at
|
|
|
|
|
|
end
|
|
|
|
|
|
def stripe_handler(e)
|
|
|
|
|
|
|
|
|
|
|
|
msg = e.to_s
|
|
|
|
|
|
|
|
|
|
|
|
if msg.include?('declined')
|
|
|
|
|
|
self.billing_error_reason = 'card_declined'
|
|
|
|
|
|
self.billing_error_detail = msg
|
|
|
|
|
|
elsif msg.include?('expired')
|
|
|
|
|
|
self.billing_error_reason = 'card_expired'
|
|
|
|
|
|
self.billing_error_detail = msg
|
|
|
|
|
|
elsif msg.include?('processing')
|
|
|
|
|
|
self.billing_error_reason = 'processing_error'
|
|
|
|
|
|
self.billing_error_detail = msg
|
|
|
|
|
|
else
|
|
|
|
|
|
self.billing_error_reason = 'stripe'
|
|
|
|
|
|
self.billing_error_detail = msg
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
self.save(validate: false)
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|