This commit is contained in:
Seth Call 2016-03-14 11:56:16 -05:00
parent f587eb7260
commit e2b7eec291
9 changed files with 161 additions and 12 deletions

View File

@ -41,13 +41,14 @@ CREATE TABLE lesson_package_purchases (
sent_billing_notices BOOLEAN NOT NULL DEFAULT FALSE,
sent_billing_notices_at TIMESTAMP,
last_billing_attempt_at TIMESTAMP,
success BOOLEAN NOT NULL DEFAULT FALSE,
billed BOOLEAN NOT NULL DEFAULT FALSE,
billed_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,
post_processed BOOLEAN NOT NULL DEFAULT FALSE,
post_processed_at TIMESTAMP,
lesson_booking_id VARCHAR(64) REFERENCES lesson_bookings(id) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@ -1166,5 +1166,65 @@
format.html { render :layout => "from_user_mailer" }
end
end
def student_lesson_monthly_charged(lesson_package_purchase)
lesson_booking = lesson_package_purchase.lesson_booking
@student = lesson_booking.student
@teacher = lesson_booking.teacher
@lesson_package_purchase = lesson_package_purchase
@card_declined = lesson_package_purchase.is_card_declined?
@card_expired = lesson_package_purchase.is_card_expired?
@bill_date = lesson_package_purchase.last_billed_at_date
@lesson_booking = lesson_booking
@month_name = lesson_package_purchase.month_name
email = @student.email
@subject = "Your JamClass lessons with #{teacher.first_name} for #{@month_name}"
unique_args = {:type => "student_lesson_monthly_charged"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@student.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
def teacher_lesson_monthly_charged(lesson_package_purchase)
lesson_booking = lesson_package_purchase.lesson_booking
@student = lesson_booking.student
@teacher = lesson_booking.teacher
@lesson_package_purchase = lesson_package_purchase
@card_declined = lesson_package_purchase.is_card_declined?
@card_expired = lesson_package_purchase.is_card_expired?
@bill_date = lesson_package_purchase.last_billed_at_date
@lesson_booking = lesson_booking
@month_name = lesson_package_purchase.month_name
email = @teacher.email
if lesson_booking.is_suspended?
@subject = "Your weekly lessons with #{@student.name} has been suspended."
else
@subject = "The student #{@student.name} had a failed credit card charge for #{@month_name}."
end
unique_args = {:type => "student_lesson_monthly_charged"}
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
sendgrid_recipients([email])
sendgrid_substitute('@USERID', [@teacher.id])
mail(:to => email, :subject => @subject) do |format|
format.text
format.html { render :layout => "from_user_mailer" }
end
end
end
end

View File

@ -0,0 +1,28 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>
<p>
Hello <%= @student.name %>,
</p>
<p>
You have been billed $<%= @lesson_package_purchase.amount_charged %> for this month's lessons with <%= @teacher.name %>.
</p>
<p>
<% if !@student.has_rated_teacher(@teacher) %>
If you haven't already done so, please <a href="<%= @teacher.ratings_url %>" style="color:#fc0">rate your teacher</a> now to help other students in the community find the best
instructors.
<% end %>
If you had technical problems during your lesson, or have questions, or would like to make suggestions
on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
</p>
<br/>
<p>
Best Regards,<br>Team JamKazam
</p>
<% end %>

View File

@ -8,7 +8,7 @@
<p>
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have been
billed <%= @lesson_session.amount_charged %> for today's lesson.
billed $<%= @lesson_session.amount_charged %> for today's lesson.
</p>
<p>

View File

@ -1,6 +1,6 @@
Hello <%= @student.name %>,
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have been billed <%= @lesson_session.amount_charged %> for today's lesson.
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. You have been billed $<%= @lesson_session.amount_charged %> for today's lesson.
<% if !@student.has_rated_teacher(@teacher) %>
If you haven't already done so, please rate your teacher now to help other students in the community find the best

View File

@ -87,6 +87,8 @@ module JamRuby
sync_remaining_test_drives
@default_slot_did_change = nil
@accepting = nil
@countering = nil
end
def student
@ -306,6 +308,10 @@ module JamRuby
status == STATUS_APPROVED
end
def is_suspended?
status == STATUS_SUSPENDED
end
def validate_accepted
if !is_requested?
self.errors.add(:status, "This lesson is already #{self.status}.")
@ -393,7 +399,7 @@ module JamRuby
# errors.add(:user, 'has no credit card stored')
#end
elsif is_test_drive?
if !user.has_test_drives? && !user.can_buy_test_drive?
if !user.has_test_drives? || !user.can_buy_test_drive?
errors.add(:user, "have no remaining test drives")
end
elsif is_normal?
@ -513,7 +519,7 @@ module JamRuby
LessonBooking
.joins(:lesson_sessions)
.joins("LEFT JOIN lesson_package_purchases ON (lesson_package_purchases.lesson_booking_id = lesson_bookings.id AND (lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}) OR (lesson_package_purchases.year = #{next_month_last_day.year} AND lesson_package_purchases.month = #{next_month_last_day.month}))")
.where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.success == false)")
.where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.id IS NOT NULL AND lesson_package_purchases.post_processed = false)")
.where(payment_style: PAYMENT_STYLE_MONTHLY)
.where(status: STATUS_APPROVED)
.where('lesson_sessions.created_at >= ?', current_month_first_day)
@ -543,8 +549,17 @@ module JamRuby
end
def suspend!
# when this is called, the calling code sends out a email to let the student and teacher know (it feels unnatural it's not here, though)
self.status = STATUS_SUSPENDED
self.save
if self.save
future_sessions.each do |lesson_session|
lesson_session.suspend!
end
end
end
def future_sessions
lesson_sessions.joins(:music_session).where('scheduled_start > ?', Time.now).order(:created_at)
end
def self.schedule_upcoming_lessons

View File

@ -32,6 +32,10 @@ module JamRuby
end
def amount_charged
sale_line_item.sale.recurly_total_in_cents / 100.0
end
def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil)
purchase = LessonPackagePurchase.new
purchase.user = user
@ -118,11 +122,15 @@ module JamRuby
lesson_booking.suspend!
end
subject = "Unable to charge user #{student.email} for lesson #{self.id} (stripe)"
subject = "Unable to charge user #{student.email} for lesson #{self.id} (stripe=#{billing_error_reason})"
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_monthly(self)
if lesson_booking.is_suspended?
UserMailer.teacher_unable_charge_monthly(self)
end
return false
rescue Exception => e
@ -139,11 +147,13 @@ module JamRuby
# 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
UserMailer.student_lesson_monthly_charged(self).deliver
UserMailer.teacher_lesson_monthly_charged(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

View File

@ -12,8 +12,9 @@ module JamRuby
STATUS_MISSED = 'missed'
STATUS_COMPLETED = 'completed'
STATUS_APPROVED = 'approved'
STATUS_SUSPENDED = 'suspended'
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED]
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED]
LESSON_TYPE_SINGLE = 'paid'
LESSON_TYPE_SINGLE_FREE = 'single-free'
@ -52,6 +53,11 @@ module JamRuby
end
def suspend!
self.status = STATUS_SUSPENDED
self.save
end
def self.hourly_check
analyse_sessions
complete_sessions
@ -100,7 +106,6 @@ module JamRuby
else
nil
end
end
def analysis_to_json(analysis)
@ -217,6 +222,8 @@ module JamRuby
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
@ -384,6 +391,9 @@ module JamRuby
status == STATUS_APPROVED
end
def is_suspended?
status == STATUS_SUSPENDED
end
def validate_creating
if !is_requested? && !is_approved?

View File

@ -13,6 +13,22 @@ describe LessonBooking do
let(:valid_single_slots) {[lesson_booking_slot_single1, lesson_booking_slot_single2]}
let(:valid_recurring_slots) {[lesson_booking_slot_recurring1, lesson_booking_slot_recurring2]}
describe "suspend!" do
it "should set status as well as update status of all associated lesson_sessions" do
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
booking.lesson_sessions[0].accept({message: "got it", slot: booking.lesson_booking_slots[0].id})
booking.suspend!
booking.errors.any?.should be false
booking.reload
booking.status.should eql LessonBooking::STATUS_SUSPENDED
booking.lesson_sessions.count.should eql 2
booking.lesson_sessions.each do |lesson_session|
lesson_session.status.should eql LessonBooking::STATUS_SUSPENDED
end
end
end
describe "bill_monthlies" do
it "empty" do
@ -38,9 +54,17 @@ describe LessonBooking do
now = Time.now
billables = LessonBooking.billable_monthlies(now)
billables.all.should eql [booking]
LessonPackagePurchase.where(lesson_booking_id: booking.id).count.should eql 0
# to make this billable monthly go away, we will need to create one LessonPackagePurchase; one for this month (because it's only one we have lessons in)
# and further, mark them both as post_processed
package = LessonPackagePurchase.create(user, booking, LessonPackageType.single, 2016, 1)
LessonBooking.billable_monthlies(now).count.should eql 1
package.post_processed = true
package.save!
LessonBooking.billable_monthlies(now).count.should eql 0
end
end
@ -235,8 +259,9 @@ describe LessonBooking do
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be false
ChatMessage.count.should eq 1
user.reload
user.remaining_test_drives.should eql 0
booking = LessonBooking.book_test_drive(user, teacher_user, valid_single_slots, "Hey I've heard of you before.")
booking.errors.any?.should be true