151 lines
4.5 KiB
Ruby
151 lines
4.5 KiB
Ruby
module JamRuby
|
||
class Charge < ActiveRecord::Base
|
||
|
||
attr_accessor :stripe_charge
|
||
|
||
belongs_to :user, class_name: "JamRuby::User"
|
||
|
||
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
|
||
|
||
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
|
||
|
||
def charge(force = false)
|
||
|
||
@stripe_charge = nil
|
||
|
||
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)
|
||
|
||
# record the charge in this context (meaning, in our transaction)
|
||
record_charge(@stripe_charge) if @stripe_charge
|
||
|
||
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}"
|
||
AdminMailer.alerts({subject: subject, body: body}).deliver
|
||
do_send_unable_charge
|
||
|
||
return false
|
||
rescue Exception => e
|
||
|
||
# record the charge even if there was an unhandled exception at some point
|
||
record_charge(@stripe_charge) if @stripe_charge
|
||
|
||
unhandled_handler(e)
|
||
|
||
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}"
|
||
AdminMailer.alerts({subject: subject, body: body}).deliver
|
||
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
|
||
|
||
def unhandled_handler(e)
|
||
self.billing_error_reason = e.to_s
|
||
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
|
||
#puts "Charge: unhandled exception #{billing_error_reason}, #{billing_error_detail}"
|
||
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 |