757 lines
35 KiB
Ruby
757 lines
35 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe "Monthly Recurring Lesson Flow" do
|
|
|
|
let(:user) { FactoryGirl.create(:user, remaining_test_drives: 0) }
|
|
|
|
let(:teacher_user) { FactoryGirl.create(:teacher_user) }
|
|
let(:teacher) { teacher_user.teacher }
|
|
let(:lesson_booking_slot_single1) { FactoryGirl.build(:lesson_booking_slot_single) }
|
|
let(:lesson_booking_slot_single2) { FactoryGirl.build(:lesson_booking_slot_single) }
|
|
let(:lesson_booking_slot_recurring1) { FactoryGirl.build(:lesson_booking_slot_recurring) }
|
|
let(:lesson_booking_slot_recurring2) { FactoryGirl.build(:lesson_booking_slot_recurring) }
|
|
let(:valid_single_slots) { [lesson_booking_slot_single1, lesson_booking_slot_single2] }
|
|
let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] }
|
|
let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) }
|
|
let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) }
|
|
let(:school) {FactoryGirl.create(:school)}
|
|
|
|
|
|
|
|
after {Timecop.return}
|
|
|
|
before {
|
|
teacher.stripe_account_id = stripe_account1_id
|
|
teacher.save!
|
|
}
|
|
it "works normal" do
|
|
|
|
# if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on
|
|
Timecop.travel(Date.new(2016, 3, 20))
|
|
|
|
# user has no test drives, no credit card on file, but attempts to book a lesson
|
|
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
|
|
booking.errors.any?.should be_false
|
|
booking.card_presumed_ok.should be_false
|
|
booking.user.should eql user
|
|
booking.card_presumed_ok.should be_false
|
|
booking.same_school.should be_false
|
|
booking.same_school_free.should be_false
|
|
booking.should eql user.unprocessed_normal_lesson
|
|
booking.sent_notices.should be_false
|
|
booking.booked_price.should eql 30.00
|
|
|
|
########## Need validate their credit card
|
|
token = create_stripe_token
|
|
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
|
|
booking.reload
|
|
booking.card_presumed_ok.should be_true
|
|
booking.errors.any?.should be_false
|
|
booking = result[:lesson]
|
|
lesson = booking.lesson_sessions[0]
|
|
lesson.errors.any?.should be_false
|
|
|
|
booking.sent_notices.should be_true
|
|
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
|
lesson.amount_charged.should be 0.0
|
|
lesson.reload
|
|
|
|
user.reload
|
|
user.stripe_customer_id.should_not be nil
|
|
user.remaining_test_drives.should eql 0
|
|
user.lesson_purchases.length.should eql 0
|
|
|
|
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
|
|
customer.email.should eql user.email
|
|
|
|
booking.lesson_sessions.length.should eql 1
|
|
lesson_session = booking.lesson_sessions[0]
|
|
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
|
|
booking.status.should eql LessonBooking::STATUS_REQUESTED
|
|
|
|
######### Teacher counters with new slot
|
|
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true)
|
|
UserMailer.deliveries.clear
|
|
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
|
|
booking.reload
|
|
booking.errors.any?.should be false
|
|
lesson_session.lesson_booking.errors.any?.should be false
|
|
lesson_session.lesson_booking_slots.length.should eql 1
|
|
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
|
|
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
|
|
teacher_counter.should eql teacher_countered_slot
|
|
teacher_counter.proposer.should eql teacher_user
|
|
booking.lesson_booking_slots.length.should eql 3
|
|
UserMailer.deliveries.length.should eql 1
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.message.should eql 'Does this work?'
|
|
chat.user.should eql teacher_user
|
|
chat.target_user.should eql user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql true
|
|
notification.purpose.should eql 'counter'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
######### Student counters with new slot
|
|
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true)
|
|
UserMailer.deliveries.clear
|
|
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
|
|
lesson_session.errors.any?.should be false
|
|
lesson_session.lesson_booking.errors.any?.should be false
|
|
lesson_session.lesson_booking_slots.length.should eql 2
|
|
student_counter = booking.lesson_booking_slots.order(:created_at).last
|
|
student_counter.proposer.should eql user
|
|
booking.reload
|
|
booking.lesson_booking_slots.length.should eql 4
|
|
UserMailer.deliveries.length.should eql 1
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.message.should eql 'Does this work better?'
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.user.should eql user
|
|
chat.target_user.should eql teacher_user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql false
|
|
notification.purpose.should eql 'counter'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
######## Teacher accepts slot
|
|
UserMailer.deliveries.clear
|
|
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
|
|
UserMailer.deliveries.each do |del|
|
|
# puts del.inspect
|
|
end
|
|
# get acceptance emails, as well as 'your stuff is accepted'
|
|
UserMailer.deliveries.length.should eql 2
|
|
lesson_session.errors.any?.should be_false
|
|
lesson_session.reload
|
|
lesson_session.slot.should eql student_counter
|
|
lesson_session.status.should eql LessonSession::STATUS_APPROVED
|
|
booking.reload
|
|
booking.default_slot.should eql student_counter
|
|
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
|
booking.status.should eql LessonBooking::STATUS_APPROVED
|
|
|
|
UserMailer.deliveries.length.should eql 2
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.message.should eql 'Yeah I got this'
|
|
chat.purpose.should eql 'Lesson Approved'
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.user.should eql teacher_user
|
|
chat.target_user.should eql user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql true
|
|
notification.purpose.should eql 'accept'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
|
|
# teacher & student get into session
|
|
start = lesson_session.scheduled_start
|
|
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
|
|
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
|
|
# artificially end the session, which is covered by other background jobs
|
|
lesson_session.music_session.session_removed_at = end_time
|
|
lesson_session.music_session.save!
|
|
|
|
Timecop.travel(end_time + 1)
|
|
|
|
LessonSession.hourly_check
|
|
lesson_session.reload
|
|
lesson_session.analysed.should be_true
|
|
analysis = lesson_session.analysis
|
|
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
|
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
|
if lesson_session.billing_error_detail
|
|
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
|
|
end
|
|
|
|
# let user pay for it
|
|
LessonBooking.hourly_check
|
|
|
|
booked_price = booking.booked_price
|
|
prorated = booked_price / 2
|
|
prorated_cents = (prorated * 100).to_i
|
|
user.reload
|
|
user.lesson_purchases.length.should eql 1
|
|
lesson_purchase = user.lesson_purchases[0]
|
|
lesson_purchase.price.should eql prorated
|
|
lesson_purchase.lesson_package_type.is_normal?.should eql true
|
|
lesson_purchase.price_in_cents.should eql prorated_cents
|
|
teacher_distribution = lesson_purchase.teacher_distribution
|
|
teacher_distribution.amount_in_cents.should eql prorated_cents
|
|
teacher_distribution.ready.should be_true
|
|
teacher_distribution.distributed.should be_false
|
|
user.sales.length.should eql 1
|
|
sale = user.sales.first
|
|
sale.stripe_charge_id.should_not be_nil
|
|
sale.recurly_tax_in_cents.should eql (100 * prorated * 0.0825).round.to_i
|
|
sale.recurly_total_in_cents.should eql ((prorated * 100 * 0.0825).round + 100 * prorated).to_i
|
|
sale.recurly_subtotal_in_cents.should eql prorated_cents
|
|
sale.recurly_currency.should eql 'USD'
|
|
sale.stripe_charge_id.should_not be_nil
|
|
line_item = sale.sale_line_items[0]
|
|
line_item.quantity.should eql 1
|
|
line_item.product_type.should eql SaleLineItem::LESSON
|
|
line_item.product_id.should eq LessonPackageType.single.id
|
|
line_item.lesson_package_purchase.should eql lesson_purchase
|
|
lesson_purchase.sale_line_item.should eql line_item
|
|
|
|
TeacherPayment.count.should eql 0
|
|
TeacherPayment.hourly_check
|
|
teacher_distribution.reload
|
|
teacher_distribution.distributed.should be_true
|
|
TeacherPayment.count.should eql 1
|
|
payment = TeacherPayment.first
|
|
payment.amount_in_cents.should eql prorated_cents
|
|
payment.fee_in_cents.should eql (prorated_cents * 0.28).round
|
|
payment.teacher_payment_charge.amount_in_cents.should eql (payment.real_distribution_in_cents + payment.real_distribution_in_cents * APP_CONFIG.stripe[:ach_pct]).round
|
|
payment.teacher_payment_charge.fee_in_cents.should eql (prorated_cents * 0.28).round
|
|
payment.teacher.should eql teacher_user
|
|
payment.teacher_distribution.should eql teacher_distribution
|
|
|
|
|
|
|
|
# teacher & student get into session
|
|
start = lesson_session.scheduled_start
|
|
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
|
|
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
|
|
# artificially end the session, which is covered by other background jobs
|
|
lesson_session.music_session.session_removed_at = end_time
|
|
lesson_session.music_session.save!
|
|
|
|
|
|
UserMailer.deliveries.clear
|
|
# background code comes around and analyses the session
|
|
LessonSession.hourly_check
|
|
lesson_session.reload
|
|
lesson_session.analysed.should be_true
|
|
analysis = lesson_session.analysis
|
|
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
|
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
|
if lesson_session.billing_error_detail
|
|
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
|
|
end
|
|
|
|
lesson.amount_charged.should eql 0.0
|
|
lesson_session.billing_error_reason.should be_nil
|
|
lesson_session.sent_billing_notices.should be nil
|
|
user.reload
|
|
user.remaining_test_drives.should eql 0
|
|
UserMailer.deliveries.length.should eql 0 # one for student
|
|
end
|
|
|
|
it "works (school on school education)" do
|
|
|
|
# make sure teacher can get payments
|
|
teacher.stripe_account_id = stripe_account1_id
|
|
school.user.stripe_account_id = stripe_account2_id
|
|
|
|
# get user and teacher into same school
|
|
school.education = true
|
|
school.save!
|
|
user.school = school
|
|
user.save!
|
|
teacher.school = school
|
|
teacher.save!
|
|
|
|
|
|
# if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on
|
|
Timecop.travel(Date.new(2016, 3, 20))
|
|
|
|
# user has no test drives, no credit card on file, but attempts to book a lesson
|
|
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
|
|
booking.errors.any?.should be_false
|
|
booking.card_presumed_ok.should be_false
|
|
booking.user.should eql user
|
|
booking.card_presumed_ok.should be_false
|
|
booking.same_school.should be_true
|
|
booking.same_school_free.should be_false
|
|
booking.should eql user.unprocessed_normal_lesson
|
|
booking.sent_notices.should be_false
|
|
booking.booked_price.should eql 30.00
|
|
|
|
########## Need validate their credit card
|
|
token = create_stripe_token
|
|
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
|
|
booking.reload
|
|
booking.card_presumed_ok.should be_true
|
|
booking.errors.any?.should be_false
|
|
booking = result[:lesson]
|
|
lesson = booking.lesson_sessions[0]
|
|
lesson.errors.any?.should be_false
|
|
|
|
booking.sent_notices.should be_true
|
|
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
|
lesson.amount_charged.should be 0.0
|
|
lesson.reload
|
|
|
|
user.reload
|
|
user.stripe_customer_id.should_not be nil
|
|
user.remaining_test_drives.should eql 0
|
|
user.lesson_purchases.length.should eql 0
|
|
|
|
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
|
|
customer.email.should eql user.email
|
|
|
|
booking.lesson_sessions.length.should eql 1
|
|
lesson_session = booking.lesson_sessions[0]
|
|
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
|
|
booking.status.should eql LessonBooking::STATUS_REQUESTED
|
|
|
|
######### Teacher counters with new slot
|
|
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true)
|
|
UserMailer.deliveries.clear
|
|
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
|
|
booking.reload
|
|
booking.errors.any?.should be false
|
|
lesson_session.lesson_booking.errors.any?.should be false
|
|
lesson_session.lesson_booking_slots.length.should eql 1
|
|
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
|
|
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
|
|
teacher_counter.should eql teacher_countered_slot
|
|
teacher_counter.proposer.should eql teacher_user
|
|
booking.lesson_booking_slots.length.should eql 3
|
|
UserMailer.deliveries.length.should eql 1
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.message.should eql 'Does this work?'
|
|
chat.user.should eql teacher_user
|
|
chat.target_user.should eql user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql true
|
|
notification.purpose.should eql 'counter'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
######### Student counters with new slot
|
|
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true)
|
|
UserMailer.deliveries.clear
|
|
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
|
|
lesson_session.errors.any?.should be false
|
|
lesson_session.lesson_booking.errors.any?.should be false
|
|
lesson_session.lesson_booking_slots.length.should eql 2
|
|
student_counter = booking.lesson_booking_slots.order(:created_at).last
|
|
student_counter.proposer.should eql user
|
|
booking.reload
|
|
booking.lesson_booking_slots.length.should eql 4
|
|
UserMailer.deliveries.length.should eql 1
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.message.should eql 'Does this work better?'
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.user.should eql user
|
|
chat.target_user.should eql teacher_user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql false
|
|
notification.purpose.should eql 'counter'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
######## Teacher accepts slot
|
|
UserMailer.deliveries.clear
|
|
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
|
|
UserMailer.deliveries.each do |del|
|
|
# puts del.inspect
|
|
end
|
|
# get acceptance emails, as well as 'your stuff is accepted'
|
|
UserMailer.deliveries.length.should eql 2
|
|
lesson_session.errors.any?.should be_false
|
|
lesson_session.reload
|
|
lesson_session.slot.should eql student_counter
|
|
lesson_session.status.should eql LessonSession::STATUS_APPROVED
|
|
booking.reload
|
|
booking.default_slot.should eql student_counter
|
|
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
|
booking.status.should eql LessonBooking::STATUS_APPROVED
|
|
|
|
UserMailer.deliveries.length.should eql 2
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.message.should eql 'Yeah I got this'
|
|
chat.purpose.should eql 'Lesson Approved'
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.user.should eql teacher_user
|
|
chat.target_user.should eql user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql true
|
|
notification.purpose.should eql 'accept'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
|
|
# teacher & student get into session
|
|
start = lesson_session.scheduled_start
|
|
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
|
|
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
|
|
# artificially end the session, which is covered by other background jobs
|
|
lesson_session.music_session.session_removed_at = end_time
|
|
lesson_session.music_session.save!
|
|
|
|
Timecop.travel(end_time + 1)
|
|
|
|
LessonSession.hourly_check
|
|
lesson_session.reload
|
|
lesson_session.analysed.should be_true
|
|
analysis = lesson_session.analysis
|
|
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
|
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
|
if lesson_session.billing_error_detail
|
|
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
|
|
end
|
|
|
|
# let user pay for it
|
|
LessonBooking.hourly_check
|
|
|
|
booked_price = booking.booked_price
|
|
prorated = booked_price / 2
|
|
prorated_cents = (prorated * 100).to_i
|
|
user.reload
|
|
user.lesson_purchases.length.should eql 1
|
|
lesson_purchase = user.lesson_purchases[0]
|
|
lesson_purchase.price.should eql prorated
|
|
lesson_purchase.lesson_package_type.is_normal?.should eql true
|
|
lesson_purchase.price_in_cents.should eql prorated_cents
|
|
teacher_distribution = lesson_purchase.teacher_distribution
|
|
teacher_distribution.amount_in_cents.should eql prorated_cents
|
|
teacher_distribution.ready.should be_true
|
|
teacher_distribution.distributed.should be_false
|
|
education_distribution = lesson_purchase.education_distribution
|
|
education_distribution.amount_in_cents.should eql (prorated_cents * 0.0625).round
|
|
education_distribution.ready.should be_true
|
|
education_distribution.distributed.should be_false
|
|
user.sales.length.should eql 1
|
|
sale = user.sales.first
|
|
sale.stripe_charge_id.should_not be_nil
|
|
sale.recurly_tax_in_cents.should eql (100 * prorated * 0.0825).round.to_i
|
|
sale.recurly_total_in_cents.should eql ((prorated * 100 * 0.0825).round + 100 * prorated).to_i
|
|
sale.recurly_subtotal_in_cents.should eql prorated_cents
|
|
sale.recurly_currency.should eql 'USD'
|
|
sale.stripe_charge_id.should_not be_nil
|
|
line_item = sale.sale_line_items[0]
|
|
line_item.quantity.should eql 1
|
|
line_item.product_type.should eql SaleLineItem::LESSON
|
|
line_item.product_id.should eq LessonPackageType.single.id
|
|
line_item.lesson_package_purchase.should eql lesson_purchase
|
|
lesson_purchase.sale_line_item.should eql line_item
|
|
|
|
TeacherPayment.count.should eql 0
|
|
TeacherPayment.hourly_check
|
|
teacher_distribution.reload
|
|
teacher_distribution.distributed.should be_true
|
|
TeacherPayment.count.should eql 2
|
|
payment = teacher_distribution.teacher_payment
|
|
payment.amount_in_cents.should eql prorated_cents
|
|
payment.fee_in_cents.should eql (prorated_cents * (school.base_rate + APP_CONFIG.stripe[:charge_fee])).round
|
|
payment.teacher_payment_charge.amount_in_cents.should eql (payment.real_distribution_in_cents + payment.real_distribution_in_cents * APP_CONFIG.stripe[:ach_pct]).round
|
|
payment.teacher_payment_charge.fee_in_cents.should eql (prorated_cents * (school.base_rate + 0.03)).round
|
|
payment.teacher.should eql teacher_user
|
|
payment.teacher_distribution.should eql teacher_distribution
|
|
education_distribution.reload
|
|
education_distribution.distributed.should be_true
|
|
|
|
education_amt = (prorated_cents * 0.0625).round
|
|
payment = education_distribution.teacher_payment
|
|
payment.amount_in_cents.should eql education_amt
|
|
payment.fee_in_cents.should eql 0
|
|
payment.teacher_payment_charge.amount_in_cents.should eql (education_amt + education_amt * APP_CONFIG.stripe[:ach_pct]).round
|
|
payment.teacher_payment_charge.fee_in_cents.should eql 0
|
|
payment.teacher.should eql teacher_user
|
|
payment.teacher_distribution.should eql education_distribution
|
|
|
|
|
|
|
|
# teacher & student get into session
|
|
start = lesson_session.scheduled_start
|
|
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
|
|
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
|
|
# artificially end the session, which is covered by other background jobs
|
|
lesson_session.music_session.session_removed_at = end_time
|
|
lesson_session.music_session.save!
|
|
|
|
|
|
UserMailer.deliveries.clear
|
|
# background code comes around and analyses the session
|
|
LessonSession.hourly_check
|
|
lesson_session.reload
|
|
lesson_session.analysed.should be_true
|
|
analysis = lesson_session.analysis
|
|
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
|
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
|
if lesson_session.billing_error_detail
|
|
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
|
|
end
|
|
|
|
lesson.amount_charged.should eql 0.0
|
|
lesson_session.billing_error_reason.should be_nil
|
|
lesson_session.sent_billing_notices.should be nil
|
|
user.reload
|
|
user.remaining_test_drives.should eql 0
|
|
UserMailer.deliveries.length.should eql 0 # one for student
|
|
end
|
|
|
|
it "works (school on school normal)" do
|
|
|
|
# get user and teacher into same school
|
|
user.school = school
|
|
user.save!
|
|
teacher.school = school
|
|
teacher.save!
|
|
|
|
# if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on
|
|
Timecop.travel(Date.new(2016, 3, 20))
|
|
|
|
# user has no test drives, no credit card on file, but attempts to book a lesson
|
|
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60)
|
|
booking.errors.any?.should be_false
|
|
booking.card_presumed_ok.should be_false
|
|
booking.user.should eql user
|
|
booking.sent_notices.should be_false
|
|
|
|
token = create_stripe_token
|
|
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
|
|
booking.reload
|
|
booking.card_presumed_ok.should be_true
|
|
booking.errors.any?.should be_false
|
|
|
|
booking.sent_notices.should be_true
|
|
booking.booked_price.should eql 30.00
|
|
|
|
|
|
user.reload
|
|
user.stripe_customer_id.should_not be nil
|
|
user.remaining_test_drives.should eql 0
|
|
user.lesson_purchases.length.should eql 0
|
|
|
|
booking.lesson_sessions.length.should eql 1
|
|
lesson_session = booking.lesson_sessions[0]
|
|
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
|
|
booking.status.should eql LessonBooking::STATUS_REQUESTED
|
|
|
|
######### Teacher counters with new slot
|
|
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true)
|
|
UserMailer.deliveries.clear
|
|
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
|
|
lesson_session.errors.any?.should be_false
|
|
booking.reload
|
|
booking.errors.any?.should be false
|
|
lesson_session.lesson_booking.errors.any?.should be false
|
|
lesson_session.lesson_booking_slots.length.should eql 1
|
|
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
|
|
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
|
|
teacher_counter.should eql teacher_countered_slot
|
|
teacher_counter.proposer.should eql teacher_user
|
|
booking.lesson_booking_slots.length.should eql 3
|
|
UserMailer.deliveries.length.should eql 1
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.message.should eql 'Does this work?'
|
|
chat.user.should eql teacher_user
|
|
chat.target_user.should eql user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql true
|
|
notification.purpose.should eql 'counter'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
######### Student counters with new slot
|
|
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true)
|
|
UserMailer.deliveries.clear
|
|
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
|
|
lesson_session.errors.any?.should be false
|
|
lesson_session.lesson_booking.errors.any?.should be false
|
|
lesson_session.lesson_booking_slots.length.should eql 2
|
|
student_counter = booking.lesson_booking_slots.order(:created_at).last
|
|
student_counter.proposer.should eql user
|
|
booking.reload
|
|
booking.lesson_booking_slots.length.should eql 4
|
|
UserMailer.deliveries.length.should eql 1
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.message.should eql 'Does this work better?'
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.user.should eql user
|
|
chat.target_user.should eql teacher_user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql false
|
|
notification.purpose.should eql 'counter'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
######## Teacher accepts slot
|
|
UserMailer.deliveries.clear
|
|
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
|
|
UserMailer.deliveries.each do |del|
|
|
# puts del.inspect
|
|
end
|
|
# get acceptance emails, as well as 'your stuff is accepted'
|
|
UserMailer.deliveries.length.should eql 2
|
|
lesson_session.errors.any?.should be_false
|
|
lesson_session.reload
|
|
lesson_session.slot.should eql student_counter
|
|
lesson_session.status.should eql LessonSession::STATUS_APPROVED
|
|
booking.reload
|
|
booking.default_slot.should eql student_counter
|
|
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
|
booking.status.should eql LessonBooking::STATUS_APPROVED
|
|
|
|
UserMailer.deliveries.length.should eql 2
|
|
chat = ChatMessage.unscoped.order(:created_at).last
|
|
chat.message.should eql 'Yeah I got this'
|
|
chat.purpose.should eql 'Lesson Approved'
|
|
chat.channel.should eql ChatMessage::CHANNEL_LESSON
|
|
chat.user.should eql teacher_user
|
|
chat.target_user.should eql user
|
|
notification = Notification.unscoped.order(:created_at).last
|
|
notification.session_id.should eql lesson_session.music_session.id
|
|
notification.student_directed.should eql true
|
|
notification.purpose.should eql 'accept'
|
|
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
|
|
|
|
|
# teacher & student get into session
|
|
start = lesson_session.scheduled_start
|
|
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
|
|
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
|
|
# artificially end the session, which is covered by other background jobs
|
|
lesson_session.music_session.session_removed_at = end_time
|
|
lesson_session.music_session.save!
|
|
|
|
Timecop.travel(end_time + 1)
|
|
|
|
LessonSession.hourly_check
|
|
lesson_session.reload
|
|
lesson_session.analysed.should be_true
|
|
analysis = lesson_session.analysis
|
|
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
|
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
|
if lesson_session.billing_error_detail
|
|
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
|
|
end
|
|
|
|
# let user pay for it
|
|
LessonBooking.hourly_check
|
|
|
|
LessonPaymentCharge.count.should eql 0
|
|
TeacherDistribution.count.should eql 0
|
|
|
|
user.reload
|
|
user.lesson_purchases.length.should eql 0
|
|
user.sales.length.should eql 0
|
|
|
|
|
|
TeacherPayment.count.should eql 0
|
|
TeacherPayment.hourly_check
|
|
TeacherPayment.count.should eql 0
|
|
|
|
|
|
# teacher & student get into session
|
|
start = lesson_session.scheduled_start
|
|
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
|
|
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
|
|
# artificially end the session, which is covered by other background jobs
|
|
lesson_session.music_session.session_removed_at = end_time
|
|
lesson_session.music_session.save!
|
|
|
|
|
|
UserMailer.deliveries.clear
|
|
# background code comes around and analyses the session
|
|
LessonSession.hourly_check
|
|
lesson_session.reload
|
|
lesson_session.analysed.should be_true
|
|
analysis = lesson_session.analysis
|
|
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
|
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
|
if lesson_session.billing_error_detail
|
|
puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
|
|
end
|
|
|
|
lesson_session.amount_charged.should eql 0.0
|
|
lesson_session.billing_error_reason.should be_nil
|
|
lesson_session.sent_billing_notices.should be_nil
|
|
user.reload
|
|
user.remaining_test_drives.should eql 0
|
|
UserMailer.deliveries.length.should eql 0 # one for student
|
|
|
|
LessonPaymentCharge.count.should eql 0
|
|
TeacherDistribution.count.should eql 0
|
|
end
|
|
|
|
|
|
|
|
it "affiliate gets their cut" do
|
|
Timecop.travel(2016, 05, 15)
|
|
user.affiliate_referral = affiliate_partner
|
|
user.save!
|
|
teacher_user.affiliate_referral = affiliate_partner2
|
|
teacher_user.save!
|
|
|
|
user.lesson_purchases.count.should eql 0
|
|
lesson = monthly_lesson(user, teacher_user, {finish:true, accept:true})
|
|
|
|
user.reload
|
|
|
|
user.lesson_purchases.count.should eql 1
|
|
lesson_package_purchase = user.lesson_purchases.first
|
|
teacher_distribution = lesson_package_purchase.teacher_distribution
|
|
|
|
|
|
user.sales.count.should eql 1
|
|
user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2
|
|
affiliate_partner.affiliate_distributions.count.should eql 1
|
|
affiliate_partner2.affiliate_distributions.count.should eql 1
|
|
partner1_distribution = affiliate_partner.affiliate_distributions.first
|
|
partner2_distribution = affiliate_partner2.affiliate_distributions.first
|
|
partner1_distribution.sale_line_item.should eql partner2_distribution.sale_line_item
|
|
partner1_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner.lesson_rate).round
|
|
partner2_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner2.lesson_rate).round
|
|
end
|
|
|
|
it "school affiliate gets nothing when teacher school is involved" do
|
|
Timecop.travel(2016, 05, 15)
|
|
teacher.school = school
|
|
teacher.save!
|
|
|
|
user.affiliate_referral = affiliate_partner
|
|
user.save!
|
|
|
|
lesson = monthly_lesson(user, teacher_user, {finish:true, accept:true })
|
|
|
|
user.sales.count.should eql 1
|
|
user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 1
|
|
user.lesson_purchases.count.should eql 1
|
|
lesson_package_purchase = user.lesson_purchases.first
|
|
teacher_distribution = lesson_package_purchase.teacher_distribution
|
|
|
|
|
|
affiliate_partner.affiliate_distributions.count.should eql 1
|
|
partner1_distribution = affiliate_partner.affiliate_distributions.first
|
|
partner1_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner.lesson_rate).round
|
|
school.affiliate_partner.affiliate_distributions.count.should eql 0
|
|
end
|
|
|
|
it "student school affiliates gets cut when student school is involved. so does teacher's" do
|
|
# in the middle of the month so that we don't get the next month's in-advance purchase put on us
|
|
Timecop.travel(2016, 05, 15)
|
|
user.affiliate_referral = school.affiliate_partner
|
|
user.save!
|
|
|
|
teacher_user.affiliate_referral = affiliate_partner
|
|
teacher_user.save!
|
|
|
|
lesson = monthly_lesson(user, teacher_user, {finish: true, accept: true})
|
|
|
|
user.sales.count.should eql 1
|
|
user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2
|
|
user.lesson_purchases.count.should eql 1
|
|
lesson_package_purchase = user.lesson_purchases.first
|
|
teacher_distribution = lesson_package_purchase.teacher_distribution
|
|
|
|
|
|
affiliate_partner.affiliate_distributions.count.should eql 1
|
|
partner1_distribution = affiliate_partner.affiliate_distributions.count.should eql 1
|
|
school.affiliate_partner.affiliate_distributions.count.should eql 1
|
|
school_partner_distribution = school.affiliate_partner.affiliate_distributions.first
|
|
school_partner_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * school.affiliate_partner.lesson_rate).round
|
|
end
|
|
end
|