ACH payment consideration in teacher payments

This commit is contained in:
Seth Call 2016-05-28 18:48:40 -05:00
parent 5d68fe322a
commit ae9249532b
24 changed files with 856 additions and 371 deletions

View File

@ -1,6 +1,6 @@
ActiveAdmin.register JamRuby::LessonBooking, :as => 'LessonBookings' do
menu :label => 'LessonBooking', :parent => 'JamClass'
menu :label => 'Lesson Booking', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false

View File

@ -1,6 +1,6 @@
ActiveAdmin.register JamRuby::LessonSession, :as => 'LessonSessions' do
menu :label => 'LessonSession', :parent => 'JamClass'
menu :label => 'Lesson Session', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false

View File

@ -0,0 +1,50 @@
ActiveAdmin.register JamRuby::LessonSession, :as => 'SlowResponses' do
menu :label => 'Slow Responses', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = false
config.per_page = 100
config.paginate = true
config.filters = false
scope("Slow Responses", default: true) { |scope| scope.unscoped.slow_responses }
scope("Least Time Left") { |scope| scope.unscoped.least_time_left }
index do
column "Teacher" do |lesson_session|
teacher = lesson_session.teacher
span do
link_to "#{teacher.name} (#{teacher.email})", "#{Rails.application.config.external_root_url}/client#/profile/teacher/#{teacher.id}"
end
end
column "Student" do |lesson_session|
student = lesson_session.student
span do
link_to "#{student.name} (#{student.email})", "#{Rails.application.config.external_root_url}/client#/profile/#{student.id}"
end
end
column "Type" do |lesson_session|
link_to lesson_session.display_type
end
column "Start Time" do |lesson_session|
span do
if lesson_session.music_session.nil?
raise "Lessonsesison with no id #{lesson_session.id}"
else
lesson_session.music_session.pretty_scheduled_start(true)
end
end
end
column "Last student comms date" do |lesson_session|
lesson_session.last_student_comm_date
end
column "Days with no response" do |lesson_session|
"#{lesson_session.days_no_response} days"
end
end
end

View File

@ -354,4 +354,5 @@ lesson_booking_success.sql
user_origin.sql
remove_stripe_acct_id.sql
track_user_on_lesson.sql
audio_in_music_notations.sql
audio_in_music_notations.sql
lesson_time_tracking.sql

View File

@ -0,0 +1,6 @@
ALTER TABLE lesson_bookings ADD COLUMN sent_notices_at timestamp without time zone;
ALTER TABLE lesson_bookings ADD COLUMN countered_at timestamp without time zone;
ALTER TABLE lesson_sessions ADD COLUMN countered_at timestamp without time zone;
ALTER TABLE lesson_bookings ADD COLUMN counterer_id VARCHAR(64) REFERENCES users(id);
ALTER TABLE lesson_sessions ADD COLUMN counterer_id VARCHAR(64) REFERENCES users(id);
ALTER TABLE lesson_bookings ADD COLUMN sent_counter_reminder BOOLEAN NOT NULL DEFAULT FALSE;

View File

@ -1672,6 +1672,55 @@ module JamRuby
end
end
# please respond to outstanding counter!
def student_counter_reminder(lesson_session)
@student = lesson_session.student
@teacher = lesson_session.teacher
@session_url = lesson_session.web_url
@lesson_session = lesson_session
email = @student.email
@subject = "Instructor's time proposal is still awaiting your response"
unique_args = {:type => "student_counter_reminder"}
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
# please respond to outstanding counter!
def teacher_counter_reminder(lesson_session)
@student = lesson_session.student
@teacher = lesson_session.teacher
@session_name = lesson_session.music_session.name
@session_description = lesson_session.music_session.description
@session_date = lesson_session.slot.pretty_scheduled_start(true)
@session_url = lesson_session.web_url
@lesson_session = lesson_session
email = lesson_session.teacher.email
@subject = "Student #{@student.name}'s time proposal is still awaiting your response"
unique_args = {:type => "teacher_counter_reminder"}
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
def lesson_starting_soon_teacher(lesson_session)
@lesson_booking = lesson_booking = lesson_session.lesson_booking
@student = lesson_booking.student

View File

@ -0,0 +1,17 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @teacher.resolved_photo_url) %>
<% content_for :note do %>
<p>
<%= @teacher.name %> proposed a different time 24 hours ago.
<br/>
<br/>
Please click the button below to respond.
</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW
LESSON DETAILS</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @teacher.name %> has proposed a different time 24 hours ago. Please respond.
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -0,0 +1,17 @@
<% provide(:title, @subject) %>
<% provide(:photo_url, @student.resolved_photo_url) %>
<% content_for :note do %>
<p>
<%= @student.name %> has proposed a different time 24 hours ago.
<br/>
<br/>
Please click the button below to respond.
</p>
<p>
<a href="<%= @lesson_session.web_url %>" style="margin: 8px 0 0 0;background-color: #ed3618;border: solid 1px #F27861;padding: 3px 10px;font-size: 12px;font-weight: 300;cursor: pointer;color: #FC9;text-decoration: none;line-height: 12px;text-align: center;">VIEW
LESSON DETAILS</a>
</p>
<% end %>

View File

@ -0,0 +1,3 @@
<%= @student.name %> has proposed a different time 24 hours ago. Please respond.
To see this lesson, click here: <%= @lesson_session.web_url %>

View File

@ -9,7 +9,7 @@ module JamRuby
@@log = Logging.logger[LessonBooking]
attr_accessor :accepting, :countering, :canceling, :countered_slot, :countered_lesson, :current_purchase, :current_lesson
attr_accessor :accepting, :countering, :canceling, :autocanceling, :countered_slot, :countered_lesson, :current_purchase, :current_lesson
STATUS_REQUESTED = 'requested'
STATUS_CANCELED = 'canceled'
@ -17,8 +17,9 @@ module JamRuby
STATUS_SUSPENDED = 'suspended'
STATUS_COUNTERED = 'countered'
STATUS_COMPLETED = 'completed'
STATUS_UNCONFIRMED = 'unconfirmed'
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED, STATUS_COMPLETED]
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED, STATUS_COMPLETED, STATUS_UNCONFIRMED]
LESSON_TYPE_FREE = 'single-free'
LESSON_TYPE_TEST_DRIVE = 'test-drive'
@ -40,6 +41,7 @@ module JamRuby
belongs_to :teacher, class_name: "JamRuby::User"
belongs_to :accepter, class_name: "JamRuby::User"
belongs_to :canceler, class_name: "JamRuby::User"
belongs_to :counterer, class_name: "JamRuby::User", foreign_key: :counterer_id
belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking, :dependent => :destroy
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy
belongs_to :school, class_name: "JamRuby::School"
@ -188,6 +190,9 @@ module JamRuby
self.countering = true
self.lesson_booking_slots << slot
self.counter_slot = slot
self.counterer = proposer
self.countered_at = Time.now
self.sent_counter_reminder = false
#self.status = STATUS_COUNTERED
self.save
end
@ -432,6 +437,7 @@ module JamRuby
UserMailer.teacher_lesson_request(self).deliver
Notification.send_lesson_message('requested', lesson_sessions[0], false) # TODO: this isn't quite an 'accept'
self.sent_notices = true
self.sent_notices_at = Time.now
self.save
end
@ -566,6 +572,13 @@ module JamRuby
!self.accepter_id.nil?
end
def autocancel
self.autocanceling = true
self.active = false
self.status = STATUS_UNCONFIRMED
save
self
end
def cancel(canceler, other, message)
self.canceling = true

View File

@ -5,7 +5,7 @@ module JamRuby
include HtmlSanitize
html_sanitize strict: [:cancel_message]
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson, :canceling, :assigned_student
attr_accessor :accepting, :creating, :countering, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student
@@log = Logging.logger[LessonSession]
@ -22,27 +22,29 @@ module JamRuby
STATUS_APPROVED = 'approved'
STATUS_SUSPENDED = 'suspended'
STATUS_COUNTERED = 'countered'
STATUS_UNCONFIRMED = 'unconfirmed'
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED]
STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED, STATUS_UNCONFIRMED]
LESSON_TYPE_SINGLE = 'paid'
LESSON_TYPE_SINGLE_FREE = 'single-free'
LESSON_TYPE_TEST_DRIVE = 'test-drive'
LESSON_TYPES = [LESSON_TYPE_SINGLE, LESSON_TYPE_SINGLE_FREE, LESSON_TYPE_TEST_DRIVE]
has_one :music_session, class_name: "JamRuby::MusicSession", :dependent => :destroy
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id, inverse_of: :taught_lessons
belongs_to :canceler, class_name: "JamRuby::User", foreign_key: :canceler_id
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :user, class_name: "JamRuby::User"
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id, :dependent => :destroy
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id"
has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id"
has_one :music_session, class_name: "JamRuby::MusicSession", :dependent => :destroy
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id, inverse_of: :taught_lessons
belongs_to :canceler, class_name: "JamRuby::User", foreign_key: :canceler_id
belongs_to :counterer, class_name: "JamRuby::User", foreign_key: :counterer_id
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
belongs_to :user, class_name: "JamRuby::User"
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id, :dependent => :destroy
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id"
has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id"
validates :duration, presence: true, numericality: {only_integer: true}
@ -61,6 +63,7 @@ module JamRuby
validate :validate_creating, :if => :creating
validate :validate_accepted, :if => :accepting
validate :validate_canceled, :if => :canceling
validate :validate_autocancel, :if => :autocanceling
after_save :after_counter, :if => :countering
after_save :manage_slot_changes
@ -74,7 +77,13 @@ module JamRuby
scope :missed, -> { where(status: STATUS_MISSED) }
scope :upcoming, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', Time.now) }
scope :past_cancel_window, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', 24.hours.from_now) }
# show all requested/countered sessions where the student was the last to communicate
scope :slow_responses, -> {joins(:lesson_booking).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
.where('lesson_bookings.counterer_id IS NULL OR lesson_bookings.user_id = lesson_bookings.counterer_id')
.order('(COALESCE(lesson_bookings.countered_at, lesson_bookings.sent_notices_at)) ASC')}
scope :least_time_left, -> {joins(:lesson_booking, :music_session).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
.where('lesson_bookings.counterer_id IS NULL OR lesson_bookings.user_id = lesson_bookings.counterer_id')
.order('music_sessions.scheduled_start DESC')}
def create_charge
if !school_on_school? && !is_test_drive? && !is_monthly_payment?
self.lesson_payment_charge = LessonPaymentCharge.new
@ -107,30 +116,54 @@ module JamRuby
end
def self.hourly_check
auto_cancel
analyse_sessions
complete_sessions
remind_counters
end
def self.minutely_check
upcoming_sessions_reminder
end
def self.remind_counters
MusicSession.joins(lesson_session: :lesson_booking)
.where("lesson_bookings.sent_counter_reminder = false")
.where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
.where("? > (COALESCE(lesson_bookings.countered_at, lesson_bookings.sent_notices_at)) + (INTERVAL '24 hours')", Time.now).each do |music_session|
lesson_session = music_session.lesson_session
if lesson_session.student_last_proposed?
UserMailer.teacher_counter_reminder(lesson_session).deliver!
else
UserMailer.student_counter_reminder(lesson_session).deliver!
end
lesson_session.lesson_booking.sent_counter_reminder = true
lesson_session.lesson_booking.save(validate: false)
end
end
def self.auto_cancel
MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).each do |music_session|
lesson_session = music_session.lesson_session
lesson_session.autocancel
end
end
def self.analyse_sessions
MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where("NOW() > scheduled_start + (INTERVAL '1 minutes' * (duration))").where('analysed = false').each do |music_session|
lession_session = music_session.lesson_session
lession_session.analyse
MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).where('analysed = false').each do |music_session|
lesson_session = music_session.lesson_session
lesson_session.analyse
end
end
def self.complete_sessions
# this will find any paid session (recurring monthly paid, recurring single paid, single paid)
MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("NOW() > scheduled_start + (INTERVAL '1 minutes' * (duration))").where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session|
lession_session = music_session.lesson_session
lession_session.session_completed
MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session|
lesson_session = music_session.lesson_session
lesson_session.session_completed
end
# test drives don't have a lesson_payment_charge, so we don't join against them
MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR NOW() > scheduled_start + (INTERVAL '1 minutes' * duration)").where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session|
MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("session_removed_at IS NOT NULL OR ? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session|
lession_session = music_session.lesson_session
lession_session.session_completed
end
@ -150,6 +183,10 @@ module JamRuby
end
end
def student_last_proposed?
counterer_id.nil? || counterer_id == student_id
end
def analyse
if self.analysed
return
@ -291,7 +328,7 @@ module JamRuby
end
else
"because no error"
"because no error"
end
else
"not yet analysed"
@ -468,7 +505,7 @@ module JamRuby
end
# if this is a single lesson (testdrive, paid), but the scheduled time is before now, then it's in the past, and we have to reject
if !recurring && self.slot.scheduled_time(0) <= Time.now
if !recurring && self.slot.scheduled_time(0) <= Time.now
self.errors.add(:slot, "is in the past")
end
@ -482,6 +519,18 @@ module JamRuby
self.accepting = false
end
def validate_autocancel
if scheduled_start + (duration * 60) > Time.now
self.errors.add(:status, "This session is not in the past.")
end
if self.status_was != STATUS_REQUESTED
self.errors.add(:status, "This session is #{self.status_was} and can not be autocanceled")
end
self.autocanceling = false
end
def validate_canceled
if !is_canceled?
self.errors.add(:status, "This session is already #{self.status}.")
@ -714,6 +763,8 @@ module JamRuby
slot.proposer = proposer
slot.lesson_session = self
slot.message = message
self.counterer = proposer
self.countered_at = Time.now
self.lesson_booking_slots << slot
self.countered_slot = slot
self.countered_lesson = self
@ -759,6 +810,28 @@ module JamRuby
end
end
# canceled by the system because it is requested, and the end time has gone by
def autocancel
response = self
LessonSession.transaction do
self.autocanceling = true
self.status = LessonSession::STATUS_UNCONFIRMED
if self.save
if !lesson_booking.recurring
response = lesson_booking.autocancel
if response.errors.any?
raise ActiveRecord::Rollback
end
end
else
raise ActiveRecord::Rollback
end
msg = ChatMessage.create(teacher, nil, '', ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Timeout")
msg = ChatMessage.create(student, nil, '', ChatMessage::CHANNEL_LESSON, nil, teacher, self, "Lesson Timeout")
end
end
# teacher accepts the lesson
def cancel(params)
response = self
@ -825,6 +898,30 @@ module JamRuby
end
end
def display_type
if is_test_drive?
'TestDrive'
elsif recurring
'Recurring'
else
'Single'
end
end
def last_student_comm_date
counterer_id == user_id ? countered_at : sent_notices_at
end
def days_no_response
date = last_student_comm_date
if date.nil?
'?'
else
(Time.now - last_student_comm_date) / (60 * 60 * 24)
end
end
def stripe_description(lesson_booking)
description(lesson_booking)

View File

@ -396,7 +396,7 @@ module JamRuby
}
authorization = user.user_authorizations.build(user_auth_hash)
authorization.save
authorization.save!
end

View File

@ -91,14 +91,14 @@ module JamRuby
if payment.teacher_payment_charge.nil?
charge = TeacherPaymentCharge.new
charge.user = payment.payable_teacher
charge.amount_in_cents = payment.amount_in_cents
charge.amount_in_cents = (payment.amount_in_cents / (1 - APP_CONFIG.stripe[:ach_pct])).round
charge.fee_in_cents = payment.fee_in_cents
charge.teacher_payment = payment
payment.teacher_payment_charge = charge
# charge.save!
else
charge = payment.teacher_payment_charge
charge.amount_in_cents = payment.amount_in_cents
charge.amount_in_cents = (payment.amount_in_cents / (1 - APP_CONFIG.stripe[:ach_pct])).round
charge.fee_in_cents = payment.fee_in_cents
charge.save!
end

View File

@ -10,9 +10,130 @@ describe LessonSession do
let(:lesson_booking) {b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b}
let(:lesson_session) {lesson_booking.lesson_sessions[0]}
describe "accept" do
it "can accept" do
describe "autocancel" do
it "can't autocancel in the past" do
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.autocancel
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
lesson_session.lesson_booking.status.should eql LessonSession::STATUS_REQUESTED
end
it "can't autocancel approved" do
lesson_session = normal_lesson(user, teacher, {accept: true})
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.autocancel
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_APPROVED
lesson_session.lesson_booking.status.should eql LessonSession::STATUS_APPROVED
end
it "autocancel works" do
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
Timecop.travel(Date.today + 10)
lesson_session.autocancel
lesson_session.reload
lesson_session.status.should eql LessonSession::STATUS_UNCONFIRMED
lesson_session.lesson_booking.status.should eql LessonSession::STATUS_UNCONFIRMED
end
it "autocancel sweeper works" do
lesson_session.status.should eql LessonSession::STATUS_REQUESTED
Timecop.travel(Date.today + 10)
LessonSession.auto_cancel
lesson_session.reload
lesson_session.lesson_booking.active.should be_false
lesson_session.status.should eql LessonSession::STATUS_UNCONFIRMED
lesson_session.lesson_booking.status.should eql LessonSession::STATUS_UNCONFIRMED
end
end
describe "slow_responses" do
it "sorts correctly" do
lesson_session1 = normal_lesson(user, teacher, {counter: true, counterer: user})
lesson_session2 = normal_lesson(user, teacher, {counter: true, counterer: teacher}) # this shouldn't show up
Timecop.travel(Date.today - 5)
lesson_session3 = normal_lesson(user, teacher, {})
slow = LessonSession.unscoped.slow_responses
slow.count.should eql 2
slow[0].should eql lesson_session3
slow[1].should eql lesson_session1
end
end
describe "least_time_left" do
it "sorts correctly" do
lesson_session1 = normal_lesson(user, teacher, {counter: true, counterer: user})
lesson_session2 = normal_lesson(user, teacher, {counter: true, counterer: teacher}) # this shouldn't show up
Timecop.travel(Date.today - 5)
lesson_session3 = normal_lesson(user, teacher, {})
slow = LessonSession.unscoped.least_time_left
slow.count.should eql 2
slow[0].should eql lesson_session1
slow[1].should eql lesson_session3
end
end
describe "remind_counters" do
it "finds old requested and pokes teacher" do
lesson_session1 = normal_lesson(user, teacher, {})
mailer = mock
mailer.should_receive(:deliver!)
UserMailer.should_receive(:teacher_counter_reminder).and_return(mailer)
LessonSession.remind_counters
lesson_session1.reload
lesson_session1.lesson_booking.sent_counter_reminder.should be_false
Timecop.travel(Date.today + 10)
LessonSession.remind_counters
lesson_session1.reload
lesson_session1.lesson_booking.sent_counter_reminder.should be_true
end
it "finds old counter and pokes teacher" do
lesson_session1 = normal_lesson(user, teacher, {counter: true, counterer: user})
mailer = mock
mailer.should_receive(:deliver!)
UserMailer.should_receive(:teacher_counter_reminder).and_return(mailer)
LessonSession.remind_counters
lesson_session1.reload
lesson_session1.lesson_booking.sent_counter_reminder.should be_false
Timecop.travel(Date.today + 10)
LessonSession.remind_counters
lesson_session1.reload
lesson_session1.lesson_booking.sent_counter_reminder.should be_true
end
it "finds old counter and pokes teacher" do
lesson_session1 = normal_lesson(user, teacher, {counter: true, counterer: teacher})
mailer = mock
mailer.should_receive(:deliver!)
UserMailer.should_receive(:student_counter_reminder).and_return(mailer)
LessonSession.remind_counters
lesson_session1.reload
lesson_session1.lesson_booking.sent_counter_reminder.should be_false
Timecop.travel(Date.today + 10)
LessonSession.remind_counters
lesson_session1.reload
lesson_session1.lesson_booking.sent_counter_reminder.should be_true
end
end

View File

@ -4,235 +4,152 @@ describe TeacherPayment do
let(:user) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
let(:teacher_obj) {FactoryGirl.create(:teacher, stripe_account_id: stripe_account1_id)}
let(:teacher_obj2) {FactoryGirl.create(:teacher, stripe_account_id: stripe_account2_id)}
let(:school_owner_teacher) {FactoryGirl.create(:teacher, stripe_account_id: stripe_account2_id)}
let(:teacher) { FactoryGirl.create(:user, teacher: teacher_obj) }
let(:teacher2) { FactoryGirl.create(:user, teacher: teacher_obj2) }
let(:school_teacher) { FactoryGirl.create(:user, teacher: school_owner_teacher)}
let(:test_drive_lesson) {testdrive_lesson(user, teacher)}
let(:test_drive_lesson2) {testdrive_lesson(user2, teacher2)}
let(:test_drive_distribution) {FactoryGirl.create(:teacher_distribution, lesson_session: test_drive_lesson, teacher: teacher, teacher_payment: nil, ready:false)}
let(:test_drive_distribution2) {FactoryGirl.create(:teacher_distribution, lesson_session: test_drive_lesson2, teacher: teacher2, teacher_payment: nil, ready:false)}
let(:normal_lesson_session) {normal_lesson(user, teacher)}
let(:normal_distribution) {FactoryGirl.create(:teacher_distribution, lesson_session: normal_lesson_session, teacher: teacher, teacher_payment: nil, ready:false)}
let(:school) {FactoryGirl.create(:school, user: school_teacher)}
let(:teacher1_auth) { UserAuthorization.create(provider: 'stripe_connect', uid: stripe_account1_id, token: 'abc', refresh_token: 'abc', token_expiration: Date.today + 365, secret: 'secret') }
let(:teacher2_auth) { UserAuthorization.create(provider: 'stripe_connect', uid: stripe_account2_id, token: 'abc', refresh_token: 'abc', token_expiration: Date.today + 365, secret: 'secret') }
let(:teacher) { FactoryGirl.create(:user) }
let(:teacher2) { FactoryGirl.create(:user) }
let(:teacher_obj) { FactoryGirl.create(:teacher, user: teacher) }
let(:teacher_obj2) { FactoryGirl.create(:teacher, user: teacher2) }
let(:school_teacher) { FactoryGirl.create(:user) }
let(:school_owner_teacher) { FactoryGirl.create(:teacher, user: school_teacher) }
let(:test_drive_lesson) { testdrive_lesson(user, teacher) }
let(:test_drive_lesson2) { testdrive_lesson(user2, teacher2) }
let(:test_drive_distribution) { FactoryGirl.create(:teacher_distribution, lesson_session: test_drive_lesson, teacher: teacher, teacher_payment: nil, ready: false) }
let(:test_drive_distribution2) { FactoryGirl.create(:teacher_distribution, lesson_session: test_drive_lesson2, teacher: teacher2, teacher_payment: nil, ready: false) }
let(:normal_lesson_session) { normal_lesson(user, teacher) }
let(:normal_distribution) { FactoryGirl.create(:teacher_distribution, lesson_session: normal_lesson_session, teacher: teacher, teacher_payment: nil, ready: false) }
let(:school) { FactoryGirl.create(:school, user: school_teacher) }
describe "pending_teacher_payments" do
it "empty" do
TeacherPayment.pending_teacher_payments.count.should eql 0
describe "normal teachers" do
before(:each) do
teacher_obj.touch
teacher_obj2.touch
teacher.teacher.stripe_account_id = stripe_account1_id
teacher2.teacher.stripe_account_id = stripe_account2_id
end
it "empty" do
TeacherPayment.pending_teacher_payments.count.should eql 0
end
it "one distribution" do
teacher.should eql teacher_obj.user
test_drive_distribution.touch
teacher.user_authorizations.count.should eql 1
user_auth = teacher.user_authorizations[0]
user_auth.provider.should eql 'stripe_connect'
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
test_drive_distribution.ready = true
test_drive_distribution.save!
test_drive_distribution.distributed.should be_false
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 1
payments[0]['id'].should eql teacher.id
end
it "multiple teachers" do
test_drive_distribution.touch
test_drive_distribution2.touch
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
test_drive_distribution.ready = true
test_drive_distribution.save!
test_drive_distribution2.ready = true
test_drive_distribution2.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 2
payment_user_ids = payments.map(&:id)
payment_user_ids.include? teacher.id
payment_user_ids.include? teacher2.id
end
end
it "one distribution" do
test_drive_distribution.touch
describe "school teachers" do
before(:each) do
teacher_obj.touch
teacher.teacher.stripe_account_id = stripe_account1_id
end
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
it "school distribution" do
test_drive_distribution.ready = true
test_drive_distribution.save!
test_drive_distribution.school = school
test_drive_distribution.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 1
payments[0]['id'].should eql teacher.id
end
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
it "school distribution" do
test_drive_distribution.school = school
test_drive_distribution.save!
test_drive_distribution.ready = true
test_drive_distribution.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 1
payments[0]['id'].should eql teacher.id
end
test_drive_distribution.ready = true
test_drive_distribution.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 1
payments[0]['id'].should eql teacher.id
end
it "multiple teachers" do
test_drive_distribution.touch
test_drive_distribution2.touch
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 0
test_drive_distribution.ready = true
test_drive_distribution.save!
test_drive_distribution2.ready = true
test_drive_distribution2.save!
payments = TeacherPayment.pending_teacher_payments
payments.count.should eql 2
payment_user_ids = payments.map(&:id)
payment_user_ids.include? teacher.id
payment_user_ids.include? teacher2.id
end
end
describe "teacher_payments" do
it "empty" do
TeacherPayment.teacher_payments
end
it "charges test drive" do
test_drive_distribution.touch
test_drive_distribution.ready = true
test_drive_distribution.save!
TeacherPayment.teacher_payments
test_drive_distribution.reload
test_drive_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
payment = test_drive_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 0
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.application_fee.should eql nil
TeacherPayment.pending_teacher_payments.count.should eql 0
end
it "charges normal" do
normal_distribution.touch
normal_distribution.ready = true
normal_distribution.save!
UserMailer.deliveries.clear
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
describe "normal teachers" do
before(:each) do
teacher_obj.touch
teacher_obj2.touch
teacher.teacher.stripe_account_id = stripe_account1_id
teacher2.teacher.stripe_account_id = stripe_account2_id
end
# only one confirm email to teacher
UserMailer.deliveries.length.should eql 1
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
payment.teacher_payment_charge.teacher.should eql teacher
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.application_fee.should include("fee_")
end
it "charges school" do
teacher.touch
normal_distribution.school = school
normal_distribution.ready = true
normal_distribution.save!
UserMailer.deliveries.clear
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
normal_distribution.teacher_payment.school.should eql school
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
it "empty" do
TeacherPayment.teacher_payments
end
# one to school owner, one to teacher
UserMailer.deliveries.length.should eql 2
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
payment.teacher_payment_charge.user.should eql school.owner
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.destination.should eql school.owner.teacher.stripe_account_id
charge.amount.should eql 1000
charge.application_fee.should include("fee_")
end
it "charges multiple" do
test_drive_distribution.touch
test_drive_distribution.ready = true
test_drive_distribution.save!
normal_distribution.touch
normal_distribution.ready = true
normal_distribution.save!
it "charges test drive" do
test_drive_distribution.touch
TeacherPayment.teacher_payments
test_drive_distribution.ready = true
test_drive_distribution.save!
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 2
TeacherPayment.teacher_payments
payment = normal_distribution.teacher_payment
test_drive_distribution.reload
test_drive_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
payment = test_drive_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 0
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
charge.application_fee.should eql nil
TeacherPayment.pending_teacher_payments.count.should eql 0
end
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.application_fee.should include("fee_")
test_drive_distribution.reload
payment = test_drive_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 0
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.application_fee.should be_nil
end
describe "stripe mocked" do
before { StripeMock.start }
after { StripeMock.stop; Timecop.return }
it "failed payment, then success" do
StripeMock.prepare_card_error(:card_declined)
it "charges normal" do
normal_distribution.touch
normal_distribution.ready = true
normal_distribution.save!
UserMailer.deliveries.clear
TeacherPayment.teacher_payments
@ -242,134 +159,26 @@ describe TeacherPayment do
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billing_error_reason.should eql("card_declined")
payment.teacher_payment_charge.billing_error_detail.should include("declined")
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
payment.teacher_payment_charge.stripe_charge_id.should be_nil
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.reload
# only one confirm email to teacher
UserMailer.deliveries.length.should eql 1
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
payment.teacher_payment_charge.teacher.should eql teacher
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
charge.application_fee.should include("fee_")
end
it "failed payment, then success (school)" do
StripeMock.prepare_card_error(:card_declined)
normal_distribution.school = school
normal_distribution.ready = true
normal_distribution.save!
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billing_error_reason.should eql("card_declined")
payment.teacher_payment_charge.billing_error_detail.should include("declined")
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
payment.teacher_payment_charge.stripe_charge_id.should be_nil
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.reload
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
end
it "charges multiple (with initial failure)" do
StripeMock.prepare_card_error(:card_declined)
it "charges multiple" do
test_drive_distribution.touch
test_drive_distribution.ready = true
test_drive_distribution.save!
@ -379,40 +188,302 @@ describe TeacherPayment do
TeacherPayment.teacher_payments
TeacherPayment.count.should eql 1
payment = TeacherPayment.first
payment.teacher_payment_charge.billed.should be_false
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 2
payment = normal_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
charge.application_fee.should include("fee_")
test_drive_distribution.reload
payment = test_drive_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql 1000
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 0
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1000
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
charge.application_fee.should be_nil
end
end
describe "school teachers" do
before(:each) do
teacher_obj.touch
teacher.teacher.stripe_account_id = stripe_account1_id
school_owner_teacher.touch
school_teacher.teacher.stripe_account_id = stripe_account2_id
end
it "charges school" do
teacher.touch
normal_distribution.school = school
normal_distribution.ready = true
normal_distribution.save!
UserMailer.deliveries.clear
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
normal_distribution.teacher_payment.school.should eql school
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
if payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_reason
puts payment.teacher_payment_charge.billing_error_detail
end
# one to school owner, one to teacher
UserMailer.deliveries.length.should eql 2
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
payment.teacher_payment_charge.user.should eql school.owner
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.destination.should eql school.owner.teacher.stripe_account_id
charge.amount.should eql 1008
charge.application_fee.should include("fee_")
end
end
describe "stripe mocked" do
before {
StripeMock.start
}
after { StripeMock.stop }
describe "normal" do
before {
teacher_obj.touch
teacher_obj2.touch
teacher.teacher.stripe_account_id = stripe_account1_id
teacher2.teacher.stripe_account_id = stripe_account2_id
}
it "failed payment, then success" do
StripeMock.prepare_card_error(:card_declined)
normal_distribution.touch
normal_distribution.ready = true
normal_distribution.save!
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billing_error_reason.should eql("card_declined")
payment.teacher_payment_charge.billing_error_detail.should include("declined")
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
payment.teacher_payment_charge.stripe_charge_id.should be_nil
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.reload
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1008
end
it "charges multiple (with initial failure)" do
StripeMock.prepare_card_error(:card_declined)
test_drive_distribution.touch
test_drive_distribution.ready = true
test_drive_distribution.save!
normal_distribution.touch
normal_distribution.ready = true
normal_distribution.save!
TeacherPayment.teacher_payments
TeacherPayment.count.should eql 1
payment = TeacherPayment.first
payment.teacher_payment_charge.billed.should be_false
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 2
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
test_drive_distribution.reload
payment = test_drive_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 0
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
end
end
describe "school" do
before {
teacher_obj.touch
teacher.teacher.stripe_account_id = stripe_account1_id
school_owner_teacher.touch
school_teacher.teacher.stripe_account_id = stripe_account2_id
}
it "failed payment, then success (school)" do
StripeMock.prepare_card_error(:card_declined)
normal_distribution.school = school
normal_distribution.ready = true
normal_distribution.save!
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billing_error_reason.should eql("card_declined")
payment.teacher_payment_charge.billing_error_detail.should include("declined")
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
payment.teacher_payment_charge.stripe_charge_id.should be_nil
StripeMock.clear_errors
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.teacher_payment_charge.billed.should eql false
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
# advance one day so that a charge is attempted again
Timecop.freeze(Date.today + 2)
TeacherPayment.teacher_payments
normal_distribution.reload
normal_distribution.teacher_payment.should_not be_nil
TeacherPayment.count.should eql 1
# make sure the teacher_payment is reused, and charge is reused
normal_distribution.teacher_payment.should eql(payment)
normal_distribution.teacher_payment.teacher_payment_charge.should eql(payment.teacher_payment_charge)
# no attempt should be made because a day hasn't gone by
payment = normal_distribution.teacher_payment
payment.reload
payment.teacher_payment_charge.billed.should eql true
payment.teacher_payment_charge.amount_in_cents.should eql (1000 + 1000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql 280
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.amount.should eql 1008
end
end
end

View File

@ -51,6 +51,15 @@ describe "RenderMailers", :slow => true do
UserMailer.teacher_welcome_message(teacher).deliver
end
it "teacher_counter_reminder" do
@filename = "teacher_counter_reminder"
lesson_session = testdrive_lesson(user, teacher)
UserMailer.deliveries.clear
UserMailer.teacher_counter_reminder(lesson_session).deliver
end
it "teacher_lesson_request" do
@filename = "teacher_lesson_request"

View File

@ -66,6 +66,17 @@ def book_lesson(user, teacher, options)
end
end
if options[:counter]
if options[:monthly]
counter_slot = FactoryGirl.build(:lesson_booking_slot_recurring)
else
counter_slot = FactoryGirl.build(:lesson_booking_slot_single)
end
lesson.counter({proposer: options[:counterer] || user, message: "countered YEAH!", slot: counter_slot})
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
end
if options[:accept]
lesson.accept({message: 'Yeah I got this', slot: slots[0]})

View File

@ -301,7 +301,8 @@ def app_config
{
:publishable_key => 'pk_test_HLTvioRAxN3hr5fNfrztZeoX',
:secret_key => 'sk_test_OkjoIF7FmdjunyNsdVqJD02D',
:source_customer => 'cus_88Vp44SLnBWMXq' # seth@jamkazam.com in JamKazam-test account
:source_customer => 'cus_88Vp44SLnBWMXq', # seth@jamkazam.com in JamKazam-test account
:ach_pct => 0.008
}
end
private

View File

@ -44,6 +44,7 @@ ChatActions = @ChatActions
when 'Notation File' then 'attached a notation file'
when 'Audio File' then 'attached an audio file'
when 'JamKazam Recording' then 'attached a recording'
when 'Lesson Timeout' then 'canceled by system'
else purpose
notationClicked: (music_notation, e) ->

View File

@ -35,7 +35,9 @@ teacherActions = window.JK.Actions.Teacher
lesson.displayStatus = 'Scheduled'
if !lesson.displayStatus?
if lesson.status == 'canceled'
if lesson.status == 'unconfirmed'
lesson.displayStatus = 'Unconfirmed'
else if lesson.status == 'canceled'
lesson.displayStatus = 'Canceled'
if lesson.student_canceled
lesson.displayStatus = 'Canceled (Student)'

View File

@ -426,7 +426,8 @@ if defined?(Bundler)
config.stripe = {
:publishable_key => 'pk_test_9vO8ZnxBpb9Udb0paruV3qLv',
:secret_key => 'sk_test_cPVRbtr9xbMiqffV8jwibwLA',
:client_id => 'ca_8CgkjoHvfRMVqoQkcKdPt5Riy3dSPIlg'
:client_id => 'ca_8CgkjoHvfRMVqoQkcKdPt5Riy3dSPIlg',
:ach_pct => 0.008
}
config.test_drive_wait_period_year = 1

View File

@ -139,7 +139,8 @@ def web_config
{
:publishable_key => 'pk_test_HLTvioRAxN3hr5fNfrztZeoX',
:secret_key => 'sk_test_OkjoIF7FmdjunyNsdVqJD02D',
:source_customer => 'cus_88Vp44SLnBWMXq' # seth@jamkazam.com in JamKazam-test account
:source_customer => 'cus_88Vp44SLnBWMXq', # seth@jamkazam.com in JamKazam-test account
:ach_pct => 0.008
}
end

View File

@ -146,6 +146,17 @@ def book_lesson(user, teacher, options)
end
end
if options[:counter]
if options[:monthly]
counter_slot = FactoryGirl.build(:lesson_booking_slot_recurring)
else
counter_slot = FactoryGirl.build(:lesson_booking_slot_single)
end
lesson.counter({proposer: user, message: "countered YEAH!", slot: counter_slot})
lesson.reload
lesson.status.should eql LessonSession::STATUS_COUNTERED
end
if options[:accept]
lesson.accept({message: 'Yeah I got this', slot: slots[0]})