From e2b7eec291f4152901ddcbcd72e4d6eaa481be84 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 14 Mar 2016 11:56:16 -0500 Subject: [PATCH] * wip --- db/up/lessons.sql | 3 +- ruby/lib/jam_ruby/app/mailers/user_mailer.rb | 60 +++++++++++++++++++ .../student_lesson_monthly_done.html.erb | 28 +++++++++ .../student_lesson_normal_done.html.erb | 2 +- .../student_lesson_normal_done.text.erb | 2 +- ruby/lib/jam_ruby/models/lesson_booking.rb | 21 ++++++- .../models/lesson_package_purchase.rb | 16 ++++- ruby/lib/jam_ruby/models/lesson_session.rb | 14 ++++- .../jam_ruby/models/lesson_booking_spec.rb | 27 ++++++++- 9 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_monthly_done.html.erb diff --git a/db/up/lessons.sql b/db/up/lessons.sql index 0ebdda8eb..5619980ec 100644 --- a/db/up/lessons.sql +++ b/db/up/lessons.sql @@ -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, diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 4318a748a..8c9ac40be 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -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 diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_monthly_done.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_monthly_done.html.erb new file mode 100644 index 000000000..d795f64b4 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_monthly_done.html.erb @@ -0,0 +1,28 @@ +<% provide(:title, @subject) %> +<% provide(:photo_url, @teacher.resolved_photo_url) %> + +<% content_for :note do %> +

+ Hello <%= @student.name %>, +

+ +

+ You have been billed $<%= @lesson_package_purchase.amount_charged %> for this month's lessons with <%= @teacher.name %>. +

+ +

+ <% 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 + 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 support@jamkazam.com. +

+
+

+ Best Regards,
Team JamKazam +

+<% end %> + + diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.html.erb index 61900abf4..1a3c7e42a 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.html.erb @@ -8,7 +8,7 @@

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.

diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.text.erb index 43d2985c5..efdccf098 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.text.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/student_lesson_normal_done.text.erb @@ -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 diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index 733fc1b87..20ae47e15 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/lesson_package_purchase.rb b/ruby/lib/jam_ruby/models/lesson_package_purchase.rb index 90662e790..f3eba5465 100644 --- a/ruby/lib/jam_ruby/models/lesson_package_purchase.rb +++ b/ruby/lib/jam_ruby/models/lesson_package_purchase.rb @@ -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 student’s 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 diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index d1e1d7388..df90bc095 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -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? diff --git a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb index f66a8043a..ae1e5e58a 100644 --- a/ruby/spec/jam_ruby/models/lesson_booking_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_booking_spec.rb @@ -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