This commit is contained in:
Seth Call 2016-03-21 21:42:14 -05:00
parent af2e681916
commit 5b578389e8
7 changed files with 238 additions and 3 deletions

View File

@ -8,14 +8,14 @@ ActiveAdmin.register JamRuby::User, :as => 'Students' do
config.paginate = true
def booked_anything(scope)
scope.joins(:student_lesson_bookings).uniq
scope.joins(:student_lesson_bookings).where('lesson_bookings.status = ?' > LessonBooking::STATUS_APPROVED).uniq
end
scope("Default", default: true) { |scope| booked_anything(scope).order('ready_for_session_at IS NULL DESC') }
index do
column "Name" do |user|
link_to teacher.user.name, "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
link_to user.name, "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
end
column "Email" do |user|
user.email

View File

@ -27,6 +27,24 @@ CREATE TABLE lesson_bookings (
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE charges (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
amount NUMERIC(8,2) NOT NULL,
type VARCHAR(64) NOT NULL,
sent_billing_notices BOOLEAN NOT NULL DEFAULT FALSE,
sent_billing_notices_at TIMESTAMP,
last_billing_attempt_at TIMESTAMP,
billed BOOLEAN NOT NULL DEFAULT FALSE,
billed_at TIMESTAMP,
post_processed BOOLEAN NOT NULL DEFAULT FALSE,
post_processed_at TIMESTAMP,
billing_error_reason VARCHAR,
billing_error_detail VARCHAR,
billing_should_retry BOOLEAN NOT NULL DEFAULT TRUE ,
billing_attempts INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE lesson_package_purchases (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
@ -132,4 +150,25 @@ ALTER TABLE users ADD COLUMN stripe_token VARCHAR(200);
ALTER TABLE users ADD COLUMN stripe_customer_id VARCHAR(200);
ALTER TABLE users ADD COLUMN stripe_zip_code VARCHAR(200);
ALTER TABLE sales ADD COLUMN stripe_charge_id VARCHAR(200);
ALTER TABLE sale_line_items ADD COLUMN lesson_package_purchase_id VARCHAR(64) REFERENCES lesson_package_purchases(id);
ALTER TABLE sale_line_items ADD COLUMN lesson_package_purchase_id VARCHAR(64) REFERENCES lesson_package_purchases(id);
CREATE TABLE teacher_payments (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
teacher_id VARCHAR(64) REFERENCES users(id) NOT NULL,
charge_id VARCHAR(64) REFERENCES charges(id) NOT NULL,
amount NUMERIC(8,2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE teacher_distributions (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
teacher_id VARCHAR(64) REFERENCES users(id) NOT NULL,
lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id),
teacher_payment_id VARCHAR(64) REFERENCES teacher_payments(id),
lesson_payment_purchase_id VARCHAR(64) REFERENCES lesson_package_purchases(id),
amount_in_cents INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -281,6 +281,8 @@ require "jam_ruby/models/jamblaster_pairing_request"
require "jam_ruby/models/sale_receipt_ios"
require "jam_ruby/models/lesson_session_analyser"
require "jam_ruby/models/lesson_session_monthly_price"
require "jam_ruby/models/teacher_distribution"
require "jam_ruby/models/charge"
include Jampb

View File

@ -0,0 +1,131 @@
module JamRuby
class Charge
validates :sent_notices, inclusion: {in: [true, false]}
def max_retries
raise "not implemented"
end
def do_charge
raise "not implemented"
end
def bill_lesson
if !self.billed
# check if we can bill at the moment
if last_billing_attempt_at && (24.hours.ago < last_billing_attempt_at)
return
end
if !billing_should_retry
return
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
do_charge
if sale.errors.any?
self.billing_error_reason = 'sale_error'
self.billing_error_detail = sale.errors.inspect
line_item = sale.sale_line_items[0]
if line_item && line_item.errors.any?
self.billing_error_detail = "#{self.billing_error_detail}\n\n#{line_item.errors.inspect}"
end
self.save(validate: false)
return false
else
self.billed = true
self.billed_at = Time.now
self.save(validate: false)
end
rescue Stripe::StripeError => e
stripe_handler(e)
subject = "Unable to charge user #{student.email} for lesson #{self.id} (stripe)"
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
AdminMailer.alerts({subject: subject, body: body})
UserMailer.student_unable_charge(self)
return false
rescue Exception => e
subject = "Unable to charge user #{student.email} for lesson #{self.id} (unhandled)"
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
AdminMailer.alerts({subject: subject, body: body})
unhandled_handler(e)
return false
end
end
if !self.sent_billing_notices
# If the charge is successful, then we post the charge to the students payment history,
# and associate the charge with the lesson, so that everyone knows the student has paid, and we send an email
UserMailer.student_lesson_normal_done(self).deliver
UserMailer.teacher_lesson_normal_done(self).deliver
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 true
end
def unhandled_handler(e, reason = 'unhandled_exception')
self.billing_error_reason = reason
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
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

View File

@ -0,0 +1,14 @@
module JamRuby
class TeacherPayment
belongs_to :teacher, class: "JamRuby::User"
belongs_to :charge, class: "JamRuby::Charge"
validates :teacher, presence: true
validates :charge, presence: true
validates :amount, presence: true
end
end

View File

@ -0,0 +1,48 @@
module JamRuby
class TeacherPaymentCharge < Charge
has_one :teacher_payment, class: "JamRuby::TeacherDistribution"
def max_retries
1
end
def teacher
@teacher ||= teacher_distribution.teacher
end
def amount_in_cents
amount * 100
end
def uncollected_charges
T.where(teacher_id: teacher.id).where(distributed: false).where(billed:true).where(type: 'JamRuby::StudentLessonCharge')
end
def do_charge
@uncollected_charges = uncollected_charges
charge = Stripe::Charge.create(
:amount => amount_in_cents,
:currency => "usd",
:source => APP_CONFIG.stripe_charge_token,
:description => construct_description,
:destination => teacher.stripe_account_id
)
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] = charge.id
price_info[:change] = charge
price_info[:purchase] = purchase
price_info
end
def construct_description
teacher_distribution.
end
end
end

View File

@ -166,6 +166,7 @@ if defined?(Bundler)
config.stripe_secret_key = 'sk_test_cPVRbtr9xbMiqffV8jwibwLA'
config.stripe_publishable_key = 'pk_test_9vO8ZnxBpb9Udb0paruV3qLv'
config.stripe_charge_token = '#XXX#'
if Rails.env == 'production'
config.desk_url = 'https://jamkazam.desk.com'