* wip
This commit is contained in:
parent
cf3d7cddd3
commit
4b4b50cb86
|
|
@ -29,7 +29,8 @@ CREATE TABLE lesson_bookings (
|
|||
|
||||
CREATE TABLE charges (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
amount NUMERIC(8,2) NOT NULL,
|
||||
amount_in_cents INTEGER NOT NULL,
|
||||
fee_in_cents INTEGER NOT NULL DEFAULT 0,
|
||||
type VARCHAR(64) NOT NULL,
|
||||
sent_billing_notices BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sent_billing_notices_at TIMESTAMP,
|
||||
|
|
@ -42,6 +43,7 @@ CREATE TABLE charges (
|
|||
billing_error_detail VARCHAR,
|
||||
billing_should_retry BOOLEAN NOT NULL DEFAULT TRUE ,
|
||||
billing_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
stripe_charge_id VARCHAR(200),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -52,23 +54,16 @@ CREATE TABLE lesson_package_purchases (
|
|||
user_id VARCHAR(64) REFERENCES users(id) NOT NULL,
|
||||
teacher_id VARCHAR(64) REFERENCES users(id),
|
||||
price NUMERIC(8,2),
|
||||
|
||||
recurring BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
year INTEGER,
|
||||
month INTEGER,
|
||||
sent_billing_notices BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sent_billing_notices_at TIMESTAMP,
|
||||
last_billing_attempt_at TIMESTAMP,
|
||||
billed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
billed_at TIMESTAMP,
|
||||
billing_error_reason VARCHAR,
|
||||
billing_error_detail VARCHAR,
|
||||
billing_should_retry BOOLEAN NOT NULL DEFAULT TRUE ,
|
||||
billing_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
charge_id VARCHAR(64) REFERENCES charges(id),
|
||||
affiliate_partner_id INTEGER REFERENCES affiliate_partners(id),
|
||||
lesson_booking_id VARCHAR(64) REFERENCES lesson_bookings(id) NOT NULL,
|
||||
sent_notices BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sent_notices_at TIMESTAMP,
|
||||
post_processed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
post_processed_at TIMESTAMP,
|
||||
|
||||
lesson_booking_id VARCHAR(64) REFERENCES lesson_bookings(id) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -94,19 +89,15 @@ CREATE TABLE lesson_sessions (
|
|||
analysed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
analysis JSON,
|
||||
analysed_at TIMESTAMP,
|
||||
sent_billing_notices BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sent_billing_notices_at TIMESTAMP,
|
||||
last_billing_attempt_at TIMESTAMP,
|
||||
charge_id VARCHAR(64) REFERENCES charges(id),
|
||||
success BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
bill BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
billed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
billed_at TIMESTAMP,
|
||||
sent_notices BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sent_notices_at TIMESTAMP,
|
||||
post_processed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
post_processed_at TIMESTAMP,
|
||||
billing_error_reason VARCHAR,
|
||||
billing_error_detail VARCHAR,
|
||||
billing_should_retry BOOLEAN NOT NULL DEFAULT TRUE ,
|
||||
billing_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
|
||||
affiliate_partner_id INTEGER REFERENCES affiliate_partners(id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -150,25 +141,31 @@ ALTER TABLE users ADD COLUMN stripe_token VARCHAR(200);
|
|||
ALTER TABLE users ADD COLUMN stripe_customer_id VARCHAR(200);
|
||||
ALTER TABLE users ADD COLUMN stripe_zip_code VARCHAR(200);
|
||||
ALTER TABLE sales ADD COLUMN stripe_charge_id VARCHAR(200);
|
||||
ALTER TABLE teachers ADD COLUMN stripe_account_id VARCHAR(200);
|
||||
ALTER TABLE sale_line_items ADD COLUMN lesson_package_purchase_id VARCHAR(64) REFERENCES lesson_package_purchases(id);
|
||||
|
||||
|
||||
-- one is created every time the teacher is paid. N teacher_distributions point to this
|
||||
CREATE TABLE teacher_payments (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
teacher_id VARCHAR(64) REFERENCES users(id) NOT NULL,
|
||||
charge_id VARCHAR(64) REFERENCES charges(id) NOT NULL,
|
||||
amount NUMERIC(8,2) NOT NULL,
|
||||
amount_in_cents INTEGER NOT NULL,
|
||||
fee_in_cents INTEGER NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- one is created for every bit of money the teacher is due
|
||||
CREATE TABLE teacher_distributions (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
teacher_id VARCHAR(64) REFERENCES users(id) NOT NULL,
|
||||
lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id),
|
||||
teacher_payment_id VARCHAR(64) REFERENCES teacher_payments(id),
|
||||
lesson_session_id VARCHAR(64) REFERENCES lesson_sessions(id),
|
||||
lesson_payment_purchase_id VARCHAR(64) REFERENCES lesson_package_purchases(id),
|
||||
amount_in_cents INTEGER NOT NULL,
|
||||
ready BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
distributed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
);
|
||||
|
|
|
|||
|
|
@ -282,7 +282,11 @@ require "jam_ruby/models/sale_receipt_ios"
|
|||
require "jam_ruby/models/lesson_session_analyser"
|
||||
require "jam_ruby/models/lesson_session_monthly_price"
|
||||
require "jam_ruby/models/teacher_distribution"
|
||||
require "jam_ruby/models/teacher_payment"
|
||||
require "jam_ruby/models/charge"
|
||||
require "jam_ruby/models/teacher_payment_charge"
|
||||
require "jam_ruby/models/affiliate_payment_charge"
|
||||
require "jam_ruby/models/lesson_payment_charge"
|
||||
|
||||
include Jampb
|
||||
|
||||
|
|
|
|||
|
|
@ -1226,5 +1226,103 @@
|
|||
format.html { render :layout => "from_user_mailer" }
|
||||
end
|
||||
end
|
||||
|
||||
def teacher_distribution_done(teacher_payment)
|
||||
@teacher_payment = teacher_payment
|
||||
@teacher = teacher_payment.teacher
|
||||
email = @teacher.email
|
||||
|
||||
@subject = "You have received payment for your participation in JamClass"
|
||||
unique_args = {:type => "teacher_distribution_done"}
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
def teacher_distribution_fail(teacher_payment)
|
||||
@teacher_payment = teacher_payment
|
||||
@teacher = teacher_payment.teacher
|
||||
email = @teacher.email
|
||||
|
||||
@card_declined = teacher_payment.is_card_declined?
|
||||
@card_expired = teacher_payment.is_card_expired?
|
||||
@bill_date = teacher_payment.last_billed_at_date
|
||||
|
||||
|
||||
@subject = "We were unable to pay you today"
|
||||
unique_args = {:type => "teacher_distribution_fail"}
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
def monthly_recurring_done(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 = @student.email
|
||||
subject = "Your JamClass lesson today with #{@teacher.first_name}"
|
||||
unique_args = {:type => "student_lesson_normal_no_bill"}
|
||||
|
||||
sendgrid_category "Notification"
|
||||
sendgrid_unique_args :type => unique_args[:type]
|
||||
|
||||
sendgrid_recipients([email])
|
||||
sendgrid_substitute('@USERID', [@student.id])
|
||||
|
||||
mail(:to => email, :subject => subject) do |format|
|
||||
format.text
|
||||
format.html { render :layout => "from_user_mailer" }
|
||||
end
|
||||
end
|
||||
|
||||
def monthly_recurring_no_bill(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 = @student.email
|
||||
subject = "Your lesson with #{@teacher.name} will not be billed"
|
||||
unique_args = {:type => "student_lesson_normal_done"}
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<% provide(:title, "Your JamClass lesson today with #{@teacher.first_name}") %>
|
||||
<% provide(:photo_url, @teacher.resolved_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
<p>
|
||||
Hello <%= @student.name %>,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. As just a reminder, you already paid for this lesson in advance.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<% if !@student.has_rated_teacher(@teacher) %>
|
||||
If you haven't already done so, please <a href="<%= @teacher.ratings_url %>" style="color:#fc0">rate your teacher</a> now to help other students in the community find the best
|
||||
instructors.
|
||||
<% end %>
|
||||
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions
|
||||
on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
</p>
|
||||
<br/>
|
||||
<p>
|
||||
Best Regards,<br>Team JamKazam
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<% provide(:title, "Your JamClass lesson today with #{@teacher.first_name}") %>
|
||||
<% provide(:photo_url, @teacher.resolved_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
<p>
|
||||
Hello <%= @student.name %>,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We hope you enjoyed your JamClass lesson today with <%= @teacher.name %>. As just a reminder, you already paid for this lesson in advance.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<% if !@student.has_rated_teacher(@teacher) %>
|
||||
If you haven't already done so, please <a href="<%= @teacher.ratings_url %>" style="color:#fc0">rate your teacher</a> now to help other students in the community find the best
|
||||
instructors.
|
||||
<% end %>
|
||||
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions
|
||||
on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
</p>
|
||||
<br/>
|
||||
<p>
|
||||
Best Regards,<br>Team JamKazam
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<% provide(:title, "Your lesson with #{@teacher.name} will not be billed") %>
|
||||
<% provide(:photo_url, @teacher.resolved_photo_url) %>
|
||||
|
||||
<% content_for :note do %>
|
||||
<p>
|
||||
Hello <%= @student.name %>,
|
||||
</p>
|
||||
|
||||
<p>You will not be billed for today's session with <%= @teacher.name %>. However, you already paid for the lesson in advance, so next month's bill will be lower than usual.
|
||||
<br/>
|
||||
<br/>
|
||||
Click the button below to see more information about this session.
|
||||
</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>
|
||||
<p>
|
||||
Best Regards,<br>Team JamKazam
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Hello <%= @student.name %>,
|
||||
|
||||
You will not be billed for today's session with <%= @teacher.name %>. However, you already paid for the lesson in advance, so next month's bill will be lower than usual.
|
||||
|
||||
To see this lesson, click here: <%= @lesson_session.web_url %>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
<p>You were paid a total of $<%= @teacher_payment.amount %> for your participation in JamClass. Below are more details:</p>
|
||||
<br/>
|
||||
|
||||
<% @teacher_payment.teacher_distributions.each do |distribution| %>
|
||||
|
||||
<% if distribution.is_test_drive? %>
|
||||
<h3>You have earned $<%= distribution.amount %> for your TestDrive lesson with <%= distribution.student.name %></h3>
|
||||
<p>
|
||||
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
|
||||
If you haven't already done so, please <a href="<%= distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
|
||||
<% end %>
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
</p>
|
||||
<% elsif distribution.is_normal? %>
|
||||
<h3>You have earned $<%= distribution.amount %> for your lesson with <%= distribution.student.name %></h3>
|
||||
<p>
|
||||
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
|
||||
If you haven't already done so, please <a href="<%= distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
|
||||
<% end %>
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
</p>
|
||||
<% elsif distribution.is_monthly? %>
|
||||
<h3>You have earned $<%= distribution.amount %> for your <%= distribution.month_name%> lesson with <%= distribution.student.name %></h3>
|
||||
<p>
|
||||
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
|
||||
If you haven't already done so, please <a href="<%= distribution.student.student_ratings_url %>" style="color:#fc0">rate your student</a> now to help us monitor for any issues with students who may cause issues for our instructor community.
|
||||
<% end %>
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at <a href="mailto:support@jamkazam.com" style="color:#fc0">support@jamkazam.com</a>.
|
||||
</p>
|
||||
<% else %>
|
||||
Unknown payment type.
|
||||
<% end %>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<% end %>
|
||||
|
||||
Best Regards,<br/>
|
||||
JamKazam
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
You were paid a total of $<%= @teacher_payment.amount %> for your participation in JamClass. Below are more details:
|
||||
|
||||
<% @teacher_payment.teacher_distributions.each do |distribution| %>
|
||||
|
||||
<% if distribution.is_test_drive? %>
|
||||
You have earned $<%= distribution.amount %> for your TestDrive lesson with <%= distribution.student.name %>.
|
||||
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
|
||||
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= distribution.student.student_ratings_url %>
|
||||
<% end%>
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at support@jamkazam.com.
|
||||
<% elsif distribution.is_normal? %>
|
||||
You have earned $<%= distribution.amount %> for your lesson with <%= distribution.student.name %>.
|
||||
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
|
||||
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= distribution.student.student_ratings_url %>
|
||||
<% end%>
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at support@jamkazam.com.
|
||||
<% elsif distribution.is_monthly? %>
|
||||
You have earned $<%= distribution.amount %> for your <%= distribution.month_name%> lesson with <%= distribution.student.name %>.
|
||||
<% if !@teacher_payment.teacher.has_rated_student(distribution.student) %>
|
||||
If you haven't already done so, please rate your student now to help us monitor for any issues with students who may cause issues for our instructor community. <%= distribution.student.student_ratings_url %>
|
||||
<% end%>
|
||||
If you had technical problems during your lesson, or have questions, or would like to make suggestions on how to improve JamClass, please email us at support@jamkazam.com.
|
||||
<% else %>
|
||||
Unknown payment type.
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
Best Regards,
|
||||
JamKazam
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
<p>
|
||||
<% if @card_declined %>
|
||||
When we tried to distribute a payment to you on <%= @bill_date %>, the charge was declined by stripe. Can you please check your stripe account status? Thank you!
|
||||
<% elsif @card_expired %>
|
||||
When we tried to distribute a payment to you on <%= @bill_date %>, the charge was declined by stripe due to a card expiration. Can you please check your stripe account status? Thank you!
|
||||
<% else %>
|
||||
For some reason, when we tried to distribute a payment to you on <%= @bill_date %>, the charge failed. Can you please check your stripe account status? Thank you!
|
||||
<% end %>
|
||||
</p>
|
||||
<br/>
|
||||
|
||||
Best Regards,<br/>
|
||||
JamKazam
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<% provide(:title, @subject) %>
|
||||
|
||||
<% if @card_declined %>
|
||||
When we tried to distribute a payment to you on <%= @bill_date %>, the charge was declined by stripe. Can you please check your stripe account status? Thank you!
|
||||
<% elsif @card_expired %>
|
||||
When we tried to distribute a payment to you on <%= @bill_date %>, the charge was declined by stripe due to a card expiration. Can you please check your stripe account status? Thank you!
|
||||
<% else %>
|
||||
For some reason, when we tried to distribute a payment to you on <%= @bill_date %>, the charge failed. Can you please check your stripe account status? Thank you!
|
||||
<% end %>
|
||||
|
||||
Best Regards,
|
||||
JamKazam
|
||||
|
|
@ -128,7 +128,17 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
|
|||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def should_attribute_payment?(teacher_payment)
|
||||
if created_within_affiliate_window(teacher_payment.teacher, teacher_payment.created_at)
|
||||
product_info = shopping_cart.product_info
|
||||
# subtract the total quantity from the freebie quantity, to see how much we should attribute to them
|
||||
real_quantity = product_info[:quantity].to_i - product_info[:marked_for_redeem].to_i
|
||||
{fee_in_cents: (product_info[:price] * 100 * real_quantity * rate.to_f).round}
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def cumulative_earnings_in_dollars
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
module JamRuby
|
||||
class AffiliatePaymentCharge < Charge
|
||||
|
||||
has_one :teacher_payment, class_name: "JamRuby::TeacherPayment", foreign_key: :affiliate_charge_id
|
||||
|
||||
def distribution
|
||||
@distribution ||= teacher_payment.teacher_distribution
|
||||
end
|
||||
|
||||
def max_retries
|
||||
9999999
|
||||
end
|
||||
|
||||
def teacher
|
||||
@teacher ||= teacher_payment.teacher
|
||||
end
|
||||
|
||||
def charged_user
|
||||
teacher
|
||||
end
|
||||
|
||||
def do_charge
|
||||
|
||||
# source will let you supply a token. But... how to get a token in this case?
|
||||
|
||||
stripe_charge = Stripe::Charge.create(
|
||||
:amount => amount_in_cents,
|
||||
:currency => "usd",
|
||||
:customer => APP_CONFIG.stripe[:source_customer],
|
||||
:description => construct_description,
|
||||
:destination => teacher.teacher.stripe_account_id,
|
||||
:application_fee => fee_in_cents,
|
||||
)
|
||||
|
||||
stripe_charge
|
||||
end
|
||||
|
||||
def do_send_notices
|
||||
UserMailer.teacher_distribution_done(teacher_payment)
|
||||
end
|
||||
|
||||
def do_send_unable_charge
|
||||
UserMailer.teacher_distribution_fail(teacher_payment)
|
||||
end
|
||||
|
||||
def construct_description
|
||||
teacher_payment.teacher_distribution.description
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,25 +1,40 @@
|
|||
module JamRuby
|
||||
class Charge
|
||||
validates :sent_notices, inclusion: {in: [true, false]}
|
||||
class Charge < ActiveRecord::Base
|
||||
|
||||
validates :sent_billing_notices, inclusion: {in: [true, false]}
|
||||
|
||||
def max_retries
|
||||
raise "not implemented"
|
||||
end
|
||||
def do_charge
|
||||
def do_charge(force)
|
||||
raise "not implemented"
|
||||
end
|
||||
def do_send_notices
|
||||
raise "not implemented"
|
||||
end
|
||||
def do_send_unable_charge
|
||||
raise "not implemented"
|
||||
end
|
||||
def charge_retry_hours
|
||||
24
|
||||
end
|
||||
def charged_user
|
||||
raise "not implemented"
|
||||
end
|
||||
|
||||
def bill_lesson
|
||||
def charge(force = false)
|
||||
|
||||
stripe_charge = nil
|
||||
|
||||
if !self.billed
|
||||
|
||||
# check if we can bill at the moment
|
||||
if last_billing_attempt_at && (24.hours.ago < last_billing_attempt_at)
|
||||
return
|
||||
if !force && last_billing_attempt_at && (charge_retry_hours.hours.ago < last_billing_attempt_at)
|
||||
return false
|
||||
end
|
||||
|
||||
if !billing_should_retry
|
||||
return
|
||||
if !force && !billing_should_retry
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -31,34 +46,24 @@ module JamRuby
|
|||
|
||||
begin
|
||||
|
||||
do_charge
|
||||
|
||||
if sale.errors.any?
|
||||
self.billing_error_reason = 'sale_error'
|
||||
self.billing_error_detail = sale.errors.inspect
|
||||
line_item = sale.sale_line_items[0]
|
||||
if line_item && line_item.errors.any?
|
||||
self.billing_error_detail = "#{self.billing_error_detail}\n\n#{line_item.errors.inspect}"
|
||||
end
|
||||
self.save(validate: false)
|
||||
return false
|
||||
else
|
||||
self.billed = true
|
||||
self.billed_at = Time.now
|
||||
self.save(validate: false)
|
||||
end
|
||||
stripe_charge = do_charge(force)
|
||||
self.stripe_charge_id = stripe_charge.id
|
||||
self.billed = true
|
||||
self.billed_at = Time.now
|
||||
self.save(validate: false)
|
||||
rescue Stripe::StripeError => e
|
||||
|
||||
stripe_handler(e)
|
||||
|
||||
subject = "Unable to charge user #{student.email} for lesson #{self.id} (stripe)"
|
||||
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (stripe)"
|
||||
body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
AdminMailer.alerts({subject: subject, body: body})
|
||||
UserMailer.student_unable_charge(self)
|
||||
do_send_unable_charge
|
||||
|
||||
return false
|
||||
rescue Exception => e
|
||||
subject = "Unable to charge user #{student.email} for lesson #{self.id} (unhandled)"
|
||||
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (unhandled)"
|
||||
body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
AdminMailer.alerts({subject: subject, body: body})
|
||||
unhandled_handler(e)
|
||||
return false
|
||||
|
|
@ -70,8 +75,7 @@ module JamRuby
|
|||
# If the charge is successful, then we post the charge to the student’s payment history,
|
||||
# and associate the charge with the lesson, so that everyone knows the student has paid, and we send an email
|
||||
|
||||
UserMailer.student_lesson_normal_done(self).deliver
|
||||
UserMailer.teacher_lesson_normal_done(self).deliver
|
||||
do_send_notices
|
||||
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
|
|
@ -80,8 +84,7 @@ module JamRuby
|
|||
self.save(validate: false)
|
||||
end
|
||||
|
||||
|
||||
return true
|
||||
return stripe_charge
|
||||
end
|
||||
|
||||
def unhandled_handler(e, reason = 'unhandled_exception')
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@ module JamRuby
|
|||
|
||||
@@log = Logging.logger[LessonPackagePurchase]
|
||||
|
||||
def name
|
||||
lesson_package_type.sale_display
|
||||
end
|
||||
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge
|
||||
|
||||
# who purchased the lesson package?
|
||||
belongs_to :user, class_name: "JamRuby::User", :foreign_key => "user_id", inverse_of: :lesson_purchases
|
||||
belongs_to :lesson_package_type, class_name: "JamRuby::LessonPackageType"
|
||||
belongs_to :teacher, class_name: "JamRuby::User"
|
||||
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
|
||||
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
|
||||
|
||||
has_one :sale_line_item, class_name: "JamRuby::SaleLineItem"
|
||||
|
||||
|
|
@ -20,10 +19,18 @@ module JamRuby
|
|||
validates :lesson_package_type, presence: true
|
||||
validates :price, presence: true
|
||||
|
||||
after_save :after_save
|
||||
after_create :add_test_drives
|
||||
after_create :create_charge
|
||||
|
||||
def after_save
|
||||
def create_charge
|
||||
self.lesson_payment_charge = LessonPaymentCharge.new
|
||||
lesson_payment_charge.amount_in_cents = 0
|
||||
lesson_payment_charge.fee_in_cents = 0
|
||||
lesson_payment_charge.lesson_package_purchase = self
|
||||
lesson_payment_charge.save!
|
||||
end
|
||||
|
||||
def add_test_drives
|
||||
if self.lesson_package_type.is_test_drive?
|
||||
new_test_drives = user.remaining_test_drives + 4
|
||||
User.where(id: user.id).update_all(remaining_test_drives: new_test_drives)
|
||||
|
|
@ -32,8 +39,13 @@ module JamRuby
|
|||
|
||||
end
|
||||
|
||||
|
||||
def name
|
||||
lesson_package_type.sale_display
|
||||
end
|
||||
|
||||
def amount_charged
|
||||
sale_line_item.sale.recurly_total_in_cents / 100.0
|
||||
lesson_payment_charge.amount_in_cents / 100.0
|
||||
end
|
||||
|
||||
def self.create(user, lesson_booking, lesson_package_type, year = nil, month = nil)
|
||||
|
|
@ -69,144 +81,53 @@ module JamRuby
|
|||
def description(lesson_booking)
|
||||
lesson_package_type.description(lesson_booking)
|
||||
end
|
||||
end
|
||||
|
||||
def month_name
|
||||
if recurring
|
||||
Date.new(year, month, 1).strftime('%B')
|
||||
else
|
||||
'non-monthly paid lesson'
|
||||
def stripe_description(lesson_booking)
|
||||
description(lesson_booking)
|
||||
end
|
||||
end
|
||||
|
||||
def student
|
||||
user
|
||||
end
|
||||
|
||||
|
||||
def bill_monthly(force = false)
|
||||
# let's attempt to bill for the month
|
||||
if !self.billed
|
||||
|
||||
|
||||
# check if we can bill at the moment
|
||||
if !force && last_billing_attempt_at && (24.hours.ago < last_billing_attempt_at)
|
||||
return
|
||||
def month_name
|
||||
if recurring
|
||||
Date.new(year, month, 1).strftime('%B')
|
||||
else
|
||||
'non-monthly paid lesson'
|
||||
end
|
||||
end
|
||||
|
||||
if !force && !billing_should_retry
|
||||
return
|
||||
def student
|
||||
user
|
||||
end
|
||||
|
||||
|
||||
def bill_monthly(force = false)
|
||||
lesson_payment_charge.charge(force)
|
||||
|
||||
if lesson_payment_charge.billed
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(:validate => false)
|
||||
end
|
||||
|
||||
|
||||
# bill the user right now. if it fails, move on; will be tried again
|
||||
self.billing_attempts = self.billing_attempts + 1
|
||||
self.billing_should_retry = self.billing_attempts < 5
|
||||
self.last_billing_attempt_at = Time.now
|
||||
self.save(validate: false)
|
||||
|
||||
begin
|
||||
|
||||
sale = Sale.purchase_lesson(student, lesson_booking, lesson_booking.lesson_package_type, nil, self)
|
||||
|
||||
if sale.errors.any?
|
||||
self.billing_error_reason = 'sale_error'
|
||||
self.billing_error_detail = sale.errors.inspect
|
||||
line_item = sale.sale_line_items[0]
|
||||
if line_item && line_item.errors.any?
|
||||
self.billing_error_detail = "#{self.billing_error_detail}\n\n#{line_item.errors.inspect}"
|
||||
end
|
||||
self.save(validate: false)
|
||||
return false
|
||||
else
|
||||
self.billed = true
|
||||
self.billed_at = Time.now
|
||||
self.save(validate: false)
|
||||
|
||||
lesson_booking.unsuspend! if lesson_booking.is_suspended?
|
||||
end
|
||||
rescue Stripe::StripeError => e
|
||||
|
||||
stripe_handler(e)
|
||||
|
||||
if !billing_should_retry
|
||||
lesson_booking.suspend!
|
||||
end
|
||||
|
||||
subject = "Unable to charge user #{student.email} for lesson #{self.id} (stripe=#{billing_error_reason})"
|
||||
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
AdminMailer.alerts({subject: subject, body: body})
|
||||
UserMailer.student_unable_charge_monthly(self)
|
||||
|
||||
if lesson_booking.is_suspended?
|
||||
UserMailer.teacher_unable_charge_monthly(self)
|
||||
end
|
||||
|
||||
return false
|
||||
rescue Exception => e
|
||||
|
||||
subject = "Unable to charge user #{student.email} for lesson #{self.id} (unhandled)"
|
||||
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
AdminMailer.alerts({subject: subject, body: body})
|
||||
unhandled_handler(e)
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if !self.sent_billing_notices
|
||||
# If the charge is successful, then we post the charge to the student’s payment history,
|
||||
# and associate the charge with the lesson, so that everyone knows the student has paid, and we send an email
|
||||
def is_card_declined?
|
||||
billed == false && billing_error_reason == 'card_declined'
|
||||
end
|
||||
|
||||
UserMailer.student_lesson_monthly_charged(self).deliver
|
||||
UserMailer.teacher_lesson_monthly_charged(self).deliver
|
||||
def is_card_expired?
|
||||
billed == false && billing_error_reason == 'card_expired'
|
||||
end
|
||||
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(validate: false)
|
||||
def last_billed_at_date
|
||||
last_billing_attempt_at.strftime("%B %d, %Y") if last_billing_attempt_at
|
||||
end
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def stripe_handler(e)
|
||||
|
||||
msg = e.to_s
|
||||
|
||||
if msg.include?('declined')
|
||||
self.billing_error_reason = 'card_declined'
|
||||
self.billing_error_detail = msg
|
||||
elsif msg.include?('expired')
|
||||
self.billing_error_reason = 'card_expired'
|
||||
self.billing_error_detail = msg
|
||||
elsif msg.include?('processing')
|
||||
self.billing_error_reason = 'processing_error'
|
||||
self.billing_error_detail = msg
|
||||
else
|
||||
self.billing_error_reason = 'stripe'
|
||||
self.billing_error_detail = msg
|
||||
def update_payment_url
|
||||
APP_CONFIG.external_root_url + "/client#/jamclass/update-payment"
|
||||
end
|
||||
|
||||
self.save(validate: false)
|
||||
end
|
||||
|
||||
def is_card_declined?
|
||||
billed == false && billing_error_reason == 'card_declined'
|
||||
end
|
||||
|
||||
def is_card_expired?
|
||||
billed == false && billing_error_reason == 'card_expired'
|
||||
end
|
||||
|
||||
def last_billed_at_date
|
||||
last_billing_attempt_at.strftime("%B %d, %Y") if last_billing_attempt_at
|
||||
end
|
||||
|
||||
|
||||
def update_payment_url
|
||||
APP_CONFIG.external_root_url + "/client#/jamclass/update-payment"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def stripe_description(lesson_booking)
|
||||
description(lesson_booking)
|
||||
end
|
||||
|
||||
def is_single_free?
|
||||
id == SINGLE_FREE
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
module JamRuby
|
||||
class LessonPaymentCharge < Charge
|
||||
|
||||
has_one :lesson_session, class_name: "JamRuby::LessonSession", foreign_key: :charge_id
|
||||
has_one :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase", foreign_key: :charge_id
|
||||
|
||||
def max_retries
|
||||
5
|
||||
end
|
||||
|
||||
def charged_user
|
||||
@charged_user ||= target.student
|
||||
end
|
||||
|
||||
def resolve_target
|
||||
if is_lesson?
|
||||
lesson_session
|
||||
else
|
||||
lesson_package_purchase
|
||||
end
|
||||
end
|
||||
def target
|
||||
@target ||= resolve_target
|
||||
end
|
||||
|
||||
def lesson_booking
|
||||
@lesson_booking ||= target.lesson_booking
|
||||
end
|
||||
|
||||
def student
|
||||
charged_user
|
||||
end
|
||||
|
||||
def is_lesson?
|
||||
!lesson_session.nil?
|
||||
end
|
||||
|
||||
def do_charge(force)
|
||||
|
||||
if is_lesson?
|
||||
result = Sale.purchase_lesson(student, lesson_booking, lesson_booking.lesson_package_type, lesson_session)
|
||||
else
|
||||
result = Sale.purchase_lesson(student, lesson_booking, lesson_booking.lesson_package_type, nil, lesson_package_purchase, force)
|
||||
lesson_booking.unsuspend! if lesson_booking.is_suspended?
|
||||
end
|
||||
|
||||
stripe_charge = result[:stripe_charge]
|
||||
self.amount_in_cents = stripe_charge.amount
|
||||
self.save(validate: false)
|
||||
|
||||
stripe_charge
|
||||
end
|
||||
|
||||
def do_send_notices
|
||||
if is_lesson?
|
||||
UserMailer.student_lesson_normal_done(lesson_session).deliver
|
||||
UserMailer.teacher_lesson_normal_done(lesson_session).deliver
|
||||
else
|
||||
UserMailer.student_lesson_monthly_charged(lesson_package_purchase).deliver
|
||||
UserMailer.teacher_lesson_monthly_charged(lesson_package_purchase).deliver
|
||||
end
|
||||
end
|
||||
|
||||
def do_send_unable_charge
|
||||
if is_lesson?
|
||||
UserMailer.student_unable_charge(lesson_session)
|
||||
else
|
||||
if !billing_should_retry
|
||||
lesson_booking.suspend!
|
||||
end
|
||||
|
||||
UserMailer.student_unable_charge_monthly(self)
|
||||
if lesson_booking.is_suspended?
|
||||
# let the teacher know that we are having problems collecting from the student
|
||||
UserMailer.teacher_unable_charge_monthly(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,8 +5,13 @@ module JamRuby
|
|||
|
||||
attr_accessor :accepting, :creating, :countering, :countered_slot, :countered_lesson
|
||||
|
||||
|
||||
@@log = Logging.logger[LessonSession]
|
||||
|
||||
delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge
|
||||
delegate :is_test_drive?, :is_single_free?, :is_normal?, to: :lesson_booking
|
||||
|
||||
|
||||
STATUS_REQUESTED = 'requested'
|
||||
STATUS_CANCELED = 'canceled'
|
||||
STATUS_MISSED = 'missed'
|
||||
|
|
@ -26,6 +31,7 @@ module JamRuby
|
|||
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
|
||||
belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
|
||||
belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id
|
||||
belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
|
||||
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
|
||||
|
||||
validates :duration, presence: true, numericality: {only_integer: true}
|
||||
|
|
@ -38,14 +44,22 @@ module JamRuby
|
|||
validates :teacher_canceled, inclusion: {in: [true, false]}
|
||||
validates :student_canceled, inclusion: {in: [true, false]}
|
||||
validates :success, inclusion: {in: [true, false]}
|
||||
validates :bill, inclusion: {in: [true, false]}
|
||||
validates :billed, inclusion: {in: [true, false]}
|
||||
validates :sent_notices, inclusion: {in: [true, false]}
|
||||
validates :post_processed, inclusion: {in: [true, false]}
|
||||
|
||||
validate :validate_creating, :if => :creating
|
||||
validate :validate_accepted, :if => :accepting
|
||||
after_save :after_counter, :if => :countering
|
||||
after_save :manage_slot_changes
|
||||
after_create :create_charge
|
||||
|
||||
def create_charge
|
||||
self.lesson_payment_charge = LessonPaymentCharge.new
|
||||
lesson_payment_charge.amount_in_cents = 0
|
||||
lesson_payment_charge.fee_in_cents = 0
|
||||
lesson_payment_charge.lesson_session = self
|
||||
lesson_payment_charge.save!
|
||||
end
|
||||
|
||||
def manage_slot_changes
|
||||
# if this slot changed, we need to update the time. But LessonBooking does this for us, for requested/accepted .
|
||||
|
|
@ -76,7 +90,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
def self.complete_sessions
|
||||
MusicSession.joins(lesson_session: :lesson_booking).where('session_removed_at IS NOT NULL').where('analysed = true').where('post_processed = false').where('billing_should_retry = true').each do |music_session|
|
||||
MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('session_removed_at IS NOT NULL').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
|
||||
end
|
||||
|
|
@ -94,9 +108,9 @@ module JamRuby
|
|||
self.analysed_at = Time.now
|
||||
self.analysed = true
|
||||
|
||||
if lesson_booking.requires_per_session_billing?
|
||||
self.bill = true
|
||||
end
|
||||
# if lesson_booking.requires_per_session_billing?
|
||||
# self.bill = true
|
||||
# end
|
||||
|
||||
if self.save
|
||||
# send out emails appropriate for this type of session
|
||||
|
|
@ -106,11 +120,7 @@ module JamRuby
|
|||
|
||||
|
||||
def amount_charged
|
||||
if lesson_package_purchase
|
||||
lesson_package_purchase.sale_line_item.sale.recurly_total_in_cents / 100.0
|
||||
else
|
||||
nil
|
||||
end
|
||||
lesson_payment_charge.amount_in_cents / 100.0
|
||||
end
|
||||
|
||||
def analysis_to_json(analysis)
|
||||
|
|
@ -143,153 +153,43 @@ module JamRuby
|
|||
return
|
||||
end
|
||||
|
||||
begin
|
||||
if lesson_booking.is_test_drive?
|
||||
test_drive_completed
|
||||
elsif lesson_booking.is_normal?
|
||||
if lesson_booking.is_weekly_payment? || lesson_booking.is_monthly_payment?
|
||||
recurring_completed
|
||||
else
|
||||
normal_lesson_completed
|
||||
end
|
||||
if lesson_booking.is_test_drive?
|
||||
test_drive_completed
|
||||
elsif lesson_booking.is_normal?
|
||||
if lesson_booking.is_weekly_payment? || lesson_booking.is_monthly_payment?
|
||||
recurring_completed
|
||||
else
|
||||
normal_lesson_completed
|
||||
end
|
||||
rescue Exception => e
|
||||
self.unhandled_handler(e)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def bill_lesson
|
||||
|
||||
if !self.billed
|
||||
lesson_payment_charge.charge
|
||||
|
||||
# check if we can bill at the moment
|
||||
if last_billing_attempt_at && (24.hours.ago < last_billing_attempt_at)
|
||||
return
|
||||
end
|
||||
|
||||
if !billing_should_retry
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
# bill the user right now. if it fails, move on; will be tried again
|
||||
self.billing_attempts = self.billing_attempts + 1
|
||||
self.billing_should_retry = self.billing_attempts < 5
|
||||
self.last_billing_attempt_at = Time.now
|
||||
self.save(validate: false)
|
||||
|
||||
begin
|
||||
|
||||
sale = Sale.purchase_lesson(student, lesson_booking, lesson_booking.lesson_package_type, self)
|
||||
|
||||
if sale.errors.any?
|
||||
self.billing_error_reason = 'sale_error'
|
||||
self.billing_error_detail = sale.errors.inspect
|
||||
line_item = sale.sale_line_items[0]
|
||||
if line_item && line_item.errors.any?
|
||||
self.billing_error_detail = "#{self.billing_error_detail}\n\n#{line_item.errors.inspect}"
|
||||
end
|
||||
self.save(validate: false)
|
||||
return false
|
||||
else
|
||||
self.billed = true
|
||||
self.billed_at = Time.now
|
||||
self.save(validate: false)
|
||||
end
|
||||
rescue Stripe::StripeError => e
|
||||
|
||||
stripe_handler(e)
|
||||
|
||||
subject = "Unable to charge user #{student.email} for lesson #{self.id} (stripe)"
|
||||
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
AdminMailer.alerts({subject: subject, body: body})
|
||||
UserMailer.student_unable_charge(self)
|
||||
return false
|
||||
rescue Exception => e
|
||||
subject = "Unable to charge user #{student.email} for lesson #{self.id} (unhandled)"
|
||||
body = "teacher=#{teacher.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
|
||||
AdminMailer.alerts({subject: subject, body: body})
|
||||
unhandled_handler(e)
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if !self.sent_billing_notices
|
||||
# If the charge is successful, then we post the charge to the student’s payment history,
|
||||
# and associate the charge with the lesson, so that everyone knows the student has paid, and we send an email
|
||||
|
||||
UserMailer.student_lesson_normal_done(self).deliver
|
||||
UserMailer.teacher_lesson_normal_done(self).deliver
|
||||
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
if lesson_payment_charge.billed
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(validate: false)
|
||||
self.save(:validate => false)
|
||||
end
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def unhandled_handler(e, reason = 'unhandled_exception')
|
||||
self.billing_error_reason = reason
|
||||
if e.cause
|
||||
self.billing_error_detail = e.cause.to_s + "\n" + e.cause.backtrace.join("\n\t") if e.cause.backtrace
|
||||
self.billing_error_detail << "\n\n"
|
||||
self.billing_error_detail << e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
|
||||
else
|
||||
self.billing_error_detail = e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
|
||||
end
|
||||
self.save(validate: :false)
|
||||
end
|
||||
|
||||
def is_card_declined?
|
||||
billed == false && billing_error_reason == 'card_declined'
|
||||
end
|
||||
|
||||
def is_card_expired?
|
||||
billed == false && billing_error_reason == 'card_expired'
|
||||
end
|
||||
|
||||
def last_billed_at_date
|
||||
last_billing_attempt_at.strftime("%B %d, %Y") if last_billing_attempt_at
|
||||
end
|
||||
def stripe_handler(e)
|
||||
|
||||
msg = e.to_s
|
||||
|
||||
if msg.include?('declined')
|
||||
self.billing_error_reason = 'card_declined'
|
||||
self.billing_error_detail = msg
|
||||
elsif msg.include?('expired')
|
||||
self.billing_error_reason = 'card_expired'
|
||||
self.billing_error_detail = msg
|
||||
elsif msg.include?('processing')
|
||||
self.billing_error_reason = 'processing_error'
|
||||
self.billing_error_detail = msg
|
||||
else
|
||||
self.billing_error_reason = 'stripe'
|
||||
self.billing_error_detail = msg
|
||||
end
|
||||
|
||||
self.save(validate: false)
|
||||
end
|
||||
|
||||
def test_drive_completed
|
||||
if !sent_billing_notices
|
||||
if !sent_notices
|
||||
if success
|
||||
student.test_drive_succeeded(self)
|
||||
else
|
||||
student.test_drive_failed(self)
|
||||
end
|
||||
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(:validate => false)
|
||||
|
|
@ -301,12 +201,12 @@ module JamRuby
|
|||
if lesson_booking.is_monthly_payment?
|
||||
# monthly payments are handled at beginning of month; just poke with email, and move on
|
||||
|
||||
if !sent_billing_notices
|
||||
if !sent_notices
|
||||
# not in spec; just poke user and tell them we saw it was successfully completed
|
||||
UserMailer.monthly_recurring_done(user, lesson_session).deliver
|
||||
UserMailer.monthly_recurring_done(self).deliver
|
||||
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(:validate => false)
|
||||
|
|
@ -317,21 +217,21 @@ module JamRuby
|
|||
else
|
||||
if lesson_booking.is_monthly_payment?
|
||||
# bad session; just poke user
|
||||
if !sent_billing_notices
|
||||
UserMailer.monthly_recurring_no_bill(user, self).deliver
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
if !sent_notices
|
||||
UserMailer.monthly_recurring_no_bill(self).deliver
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(:validate => false)
|
||||
end
|
||||
|
||||
else
|
||||
if !sent_billing_notices
|
||||
if !sent_notices
|
||||
# bad session; just poke user
|
||||
UserMailer.student_weekly_recurring_no_bill(user, self).deliver
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
UserMailer.student_weekly_recurring_no_bill(student, self).deliver
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(:validate => false)
|
||||
|
|
@ -345,11 +245,11 @@ module JamRuby
|
|||
if success
|
||||
bill_lesson
|
||||
else
|
||||
if !sent_billing_notices
|
||||
if !sent_notices
|
||||
UserMailer.student_lesson_normal_no_bill(self).deliver
|
||||
UserMailer.teacher_lesson_no_bill(self).deliver
|
||||
self.sent_billing_notices = true
|
||||
self.sent_billing_notices_at = Time.now
|
||||
self.sent_notices = true
|
||||
self.sent_notices_at = Time.now
|
||||
self.post_processed = true
|
||||
self.post_processed_at = Time.now
|
||||
self.save(:validate => false)
|
||||
|
|
@ -587,6 +487,14 @@ module JamRuby
|
|||
Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
|
||||
end
|
||||
|
||||
def description(lesson_booking)
|
||||
lesson_booking.lesson_package_type.description(lesson_booking)
|
||||
end
|
||||
|
||||
def stripe_description(lesson_booking)
|
||||
description(lesson_booking)
|
||||
end
|
||||
|
||||
def home_url
|
||||
APP_CONFIG.external_root_url + "/client#/jamclass"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -217,7 +217,8 @@ module JamRuby
|
|||
end
|
||||
|
||||
# this is easy to make generic, but right now, it just purchases lessons
|
||||
def self.purchase_lesson(current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil)
|
||||
def self.purchase_lesson(current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil, force = false)
|
||||
stripe_charge = nil
|
||||
sale = nil
|
||||
# everything needs to go into a transaction! If anything goes wrong, we need to raise an exception to break it
|
||||
Sale.transaction(:requires_new => true) do
|
||||
|
|
@ -226,15 +227,13 @@ module JamRuby
|
|||
|
||||
if sale.valid?
|
||||
|
||||
price_info = charge_stripe_for_lesson(current_user, lesson_booking, lesson_package_type, lesson_session, lesson_package_purchase)
|
||||
|
||||
sale_line_item = SaleLineItem.create_from_lesson_package(current_user, sale, lesson_package_type)
|
||||
|
||||
price_info = charge_stripe_for_lesson(current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session, lesson_package_purchase, force)
|
||||
|
||||
if !sale_line_item.valid?
|
||||
raise "invalid sale_line_item object for user #{current_user.email} and lesson_booking #{lesson_booking.id}"
|
||||
end
|
||||
sale_line_item.lesson_package_purchase = price_info[:purchase]
|
||||
sale_line_item.save
|
||||
# sale.source = 'stripe'
|
||||
sale.recurly_subtotal_in_cents = price_info[:subtotal_in_cents]
|
||||
sale.recurly_tax_in_cents = price_info[:tax_in_cents]
|
||||
|
|
@ -242,17 +241,25 @@ module JamRuby
|
|||
sale.recurly_currency = price_info[:currency]
|
||||
sale.stripe_charge_id = price_info[:charge_id]
|
||||
sale.save
|
||||
stripe_charge = price_info[:charge]
|
||||
else
|
||||
# should not get out of testing. This would be very rare (i.e., from a big regression). Sale is always valid at this point.
|
||||
raise "invalid sale object"
|
||||
end
|
||||
|
||||
end
|
||||
sale
|
||||
{sale: sale, stripe_charge: stripe_charge}
|
||||
end
|
||||
|
||||
def self.charge_stripe_for_lesson(current_user, lesson_booking, lesson_package_type, sale_line_item, lesson_session = nil, lesson_package_purchase = nil, force = false)
|
||||
if lesson_package_purchase
|
||||
target = lesson_package_purchase
|
||||
elsif lesson_session
|
||||
target = lesson_session
|
||||
else
|
||||
target = lesson_package_type
|
||||
end
|
||||
|
||||
def self.charge_stripe_for_lesson(current_user, lesson_booking, lesson_package_type, lesson_session = nil, lesson_package_purchase = nil)
|
||||
current_user.sync_stripe_customer
|
||||
|
||||
purchase = lesson_package_purchase
|
||||
|
|
@ -276,20 +283,23 @@ module JamRuby
|
|||
tax_in_cents = (subtotal_in_cents * tax_percent).round
|
||||
total_in_cents = subtotal_in_cents + tax_in_cents
|
||||
|
||||
charge = Stripe::Charge.create(
|
||||
stripe_charge = Stripe::Charge.create(
|
||||
:amount => total_in_cents,
|
||||
:currency => "usd",
|
||||
:customer => current_user.stripe_customer_id,
|
||||
:description => purchase.description(lesson_booking)
|
||||
:description => target.stripe_description(lesson_booking)
|
||||
)
|
||||
|
||||
sale_line_item.lesson_package_purchase = purchase
|
||||
sale_line_item.save
|
||||
|
||||
price_info = {}
|
||||
price_info[:subtotal_in_cents] = subtotal_in_cents
|
||||
price_info[:tax_in_cents] = tax_in_cents
|
||||
price_info[:total_in_cents] = total_in_cents
|
||||
price_info[:currency] = 'USD'
|
||||
price_info[:charge_id] = charge.id
|
||||
price_info[:change] = charge
|
||||
price_info[:charge_id] = stripe_charge.id
|
||||
price_info[:charge] = stripe_charge
|
||||
price_info[:purchase] = purchase
|
||||
price_info
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module JamRuby
|
|||
has_many :lesson_sessions, :class_name => "JamRuby::LessonSession"
|
||||
has_many :lesson_package_purchases, :class_name => "JamRuby::LessonPackagePurchase"
|
||||
has_one :review_summary, :class_name => "JamRuby::ReviewSummary", as: :target
|
||||
has_one :user, :class_name => 'JamRuby::User'
|
||||
has_one :user, :class_name => 'JamRuby::User', foreign_key: :teacher_id
|
||||
|
||||
validates :user, :presence => true
|
||||
validates :biography, length: {minimum: 5, maximum: 4096}, :if => :validate_introduction
|
||||
|
|
|
|||
|
|
@ -1,14 +1,56 @@
|
|||
module JamRuby
|
||||
class TeacherPayment
|
||||
class TeacherDistribution < ActiveRecord::Base
|
||||
|
||||
belongs_to :teacher, class: "JamRuby::User"
|
||||
belongs_to :charge, class: "JamRuby::Charge"
|
||||
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: "teacher_id"
|
||||
belongs_to :teacher_payment, class_name: "JamRuby::TeacherPayment"
|
||||
belongs_to :lesson_session, class_name: "JamRuby::LessonSession"
|
||||
belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
|
||||
|
||||
validates :teacher, presence: true
|
||||
validates :charge, presence: true
|
||||
validates :amount, presence: true
|
||||
validates :amount_in_cents, presence: true
|
||||
|
||||
def amount
|
||||
amount_in_cents / 100.0
|
||||
end
|
||||
|
||||
def student
|
||||
if lesson_session
|
||||
lesson_session.student
|
||||
else
|
||||
lesson_package_purchase.student
|
||||
end
|
||||
end
|
||||
|
||||
def month_name
|
||||
lesson_package_purchase.month_name
|
||||
end
|
||||
|
||||
def is_test_drive?
|
||||
lesson_session && lesson_session.is_test_drive?
|
||||
end
|
||||
|
||||
def is_normal?
|
||||
lesson_session && !lesson_session.is_test_drive?
|
||||
end
|
||||
|
||||
def is_monthly?
|
||||
!lesson_package_purchase.nil?
|
||||
end
|
||||
|
||||
def description
|
||||
if lesson_session
|
||||
if lesson_session.lesson_booking.is_test_drive?
|
||||
"Test Drive session with #{lesson_session.lesson_booking.student.name} on #{lesson_session.scheduled_start.to_date}"
|
||||
elsif lesson_session.lesson_booking.is_normal?
|
||||
if lesson_session.lesson_booking.is_weekly_payment? || lesson_session.lesson_booking.is_monthly_payment?
|
||||
raise "Should not be here"
|
||||
else
|
||||
"A session with #{lesson_session.lesson_booking.student.name} on #{lesson_session.scheduled_start.to_date}"
|
||||
end
|
||||
end
|
||||
else
|
||||
"Monthly session for the month of #{lesson_package_purchase.month_name} with #{lesson_package_purchase.lesson_booking.student.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
module JamRuby
|
||||
class TeacherPayment < ActiveRecord::Base
|
||||
|
||||
belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id
|
||||
belongs_to :teacher_payment_charge, class_name: "JamRuby::TeacherPaymentCharge", foreign_key: :charge_id
|
||||
has_one :teacher_distribution, class_name: "JamRuby::TeacherDistribution"
|
||||
|
||||
|
||||
def self.daily_check
|
||||
teacher_payments
|
||||
end
|
||||
|
||||
def teacher_distributions
|
||||
[teacher_distribution]
|
||||
end
|
||||
|
||||
def self.pending_teacher_payments
|
||||
User.select(['users.id']).joins(:teacher).joins(:teacher_distributions).where('teachers.stripe_account_id IS NOT NULL').where('teacher_distributions.distributed = false').where('teacher_distributions.ready = true').uniq
|
||||
end
|
||||
|
||||
def self.teacher_payments
|
||||
pending_teacher_payments.each do |row|
|
||||
teacher = User.find(row['id'])
|
||||
|
||||
TeacherDistribution.where(teacher_id: teacher.id).where(ready:true).where(distributed: false).each do |distribution|
|
||||
payment = TeacherPayment.charge(teacher)
|
||||
if payment.nil? || !payment.teacher_payment_charge.billed
|
||||
puts "NOT BILLED"
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def amount
|
||||
amount_in_cents / 100.0
|
||||
end
|
||||
|
||||
def is_card_declined?
|
||||
teacher_payment_charge.is_card_declined?
|
||||
end
|
||||
|
||||
def is_card_expired?
|
||||
teacher_payment_charge.is_card_expired?
|
||||
end
|
||||
|
||||
def last_billed_at_date
|
||||
teacher_payment_charge.last_billed_at_date
|
||||
end
|
||||
def charge_retry_hours
|
||||
22 # thi is only run once a day, so we make sure that slightly differences in trigger time don't cause a skip for a day
|
||||
end
|
||||
|
||||
def calculate_teacher_fee
|
||||
if teacher_distribution.is_test_drive?
|
||||
0
|
||||
else
|
||||
(amount_in_cents * 0.28).round
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# will find, for a given teacher, an outstading unsuccessful payment or make a new one.
|
||||
# it will then associate a charge with it, and then execute the charge.
|
||||
def self.charge(teacher)
|
||||
payment = TeacherPayment.joins(:teacher_payment_charge).where('teacher_payments.teacher_id = ?', teacher.id).where('charges.billed = false').order(:created_at).first
|
||||
if payment.nil?
|
||||
payment = TeacherPayment.new
|
||||
payment.teacher = teacher
|
||||
else
|
||||
payment = TeacherPayment.find(payment.id)
|
||||
end
|
||||
|
||||
if payment.teacher_distribution.nil?
|
||||
teacher_distribution = TeacherDistribution.where(teacher_id: teacher.id).where(ready:true).where(distributed: false).order(:created_at).first
|
||||
if teacher_distribution.nil?
|
||||
return
|
||||
end
|
||||
payment.teacher_distribution = teacher_distribution
|
||||
end
|
||||
|
||||
|
||||
payment.amount_in_cents = payment.teacher_distribution.amount_in_cents
|
||||
payment.fee_in_cents = payment.calculate_teacher_fee
|
||||
|
||||
if payment.teacher_payment_charge.nil?
|
||||
charge = TeacherPaymentCharge.new
|
||||
charge.amount_in_cents = payment.amount_in_cents
|
||||
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.fee_in_cents = payment.fee_in_cents
|
||||
charge.save!
|
||||
end
|
||||
|
||||
payment.save!
|
||||
|
||||
payment.teacher_payment_charge.charge
|
||||
|
||||
if payment.teacher_payment_charge.billed
|
||||
payment.teacher_distribution.distributed = true
|
||||
payment.teacher_distribution.save!
|
||||
end
|
||||
payment
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,47 +1,52 @@
|
|||
module JamRuby
|
||||
class TeacherPaymentCharge < Charge
|
||||
|
||||
has_one :teacher_payment, class: "JamRuby::TeacherDistribution"
|
||||
has_one :teacher_payment, class_name: "JamRuby::TeacherPayment", foreign_key: :charge_id
|
||||
|
||||
def distribution
|
||||
@distribution ||= teacher_payment.teacher_distribution
|
||||
end
|
||||
|
||||
def max_retries
|
||||
1
|
||||
9999999
|
||||
end
|
||||
|
||||
def teacher
|
||||
@teacher ||= teacher_distribution.teacher
|
||||
@teacher ||= teacher_payment.teacher
|
||||
end
|
||||
|
||||
def amount_in_cents
|
||||
amount * 100
|
||||
def charged_user
|
||||
teacher
|
||||
end
|
||||
|
||||
def uncollected_charges
|
||||
T.where(teacher_id: teacher.id).where(distributed: false).where(billed:true).where(type: 'JamRuby::StudentLessonCharge')
|
||||
end
|
||||
def do_charge
|
||||
|
||||
@uncollected_charges = uncollected_charges
|
||||
charge = Stripe::Charge.create(
|
||||
|
||||
def do_charge(force)
|
||||
|
||||
# source will let you supply a token. But... how to get a token in this case?
|
||||
|
||||
stripe_charge = Stripe::Charge.create(
|
||||
:amount => amount_in_cents,
|
||||
:currency => "usd",
|
||||
:source => APP_CONFIG.stripe_charge_token,
|
||||
:customer => APP_CONFIG.stripe[:source_customer],
|
||||
:description => construct_description,
|
||||
:destination => teacher.stripe_account_id
|
||||
:destination => teacher.teacher.stripe_account_id,
|
||||
:application_fee => fee_in_cents,
|
||||
)
|
||||
|
||||
price_info = {}
|
||||
price_info[:subtotal_in_cents] = subtotal_in_cents
|
||||
price_info[:tax_in_cents] = tax_in_cents
|
||||
price_info[:total_in_cents] = total_in_cents
|
||||
price_info[:currency] = 'USD'
|
||||
price_info[:charge_id] = charge.id
|
||||
price_info[:change] = charge
|
||||
price_info[:purchase] = purchase
|
||||
price_info
|
||||
stripe_charge
|
||||
end
|
||||
|
||||
def do_send_notices
|
||||
UserMailer.teacher_distribution_done(teacher_payment)
|
||||
end
|
||||
|
||||
def do_send_unable_charge
|
||||
UserMailer.teacher_distribution_fail(teacher_payment)
|
||||
end
|
||||
|
||||
def construct_description
|
||||
teacher_distribution.
|
||||
teacher_payment.teacher_distribution.description
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -174,6 +174,8 @@ module JamRuby
|
|||
has_many :lesson_purchases, :class_name => "JamRuby::LessonPackagePurchase", :foreign_key => "user_id", inverse_of: :user
|
||||
has_many :student_lesson_bookings, :class_name => "JamRuby::LessonBooking", :foreign_key => "user_id", inverse_of: :user
|
||||
has_many :teacher_lesson_bookings, :class_name => "JamRuby::LessonBooking", :foreign_key => "teacher_id", inverse_of: :teacher
|
||||
has_many :teacher_distributions, :class_name => "JamRuby::TeacherDistribution", :foreign_key => "teacher_id", inverse_of: :teacher
|
||||
has_many :teacher_payments, :class_name => "JamRuby::TeacherPayment", :foreign_key => "teacher_id", inverse_of: :teacher
|
||||
|
||||
# Shopping carts
|
||||
has_many :shopping_carts, :class_name => "JamRuby::ShoppingCart"
|
||||
|
|
@ -192,7 +194,7 @@ module JamRuby
|
|||
|
||||
has_one :musician_search, :class_name => 'JamRuby::MusicianSearch'
|
||||
has_one :band_search, :class_name => 'JamRuby::BandSearch'
|
||||
belongs_to :teacher, :class_name => 'JamRuby::Teacher'
|
||||
belongs_to :teacher, :class_name => 'JamRuby::Teacher', foreign_key: :teacher_id
|
||||
|
||||
has_many :jam_track_session, :class_name => "JamRuby::JamTrackSession"
|
||||
|
||||
|
|
@ -1820,6 +1822,14 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def should_attribute_payment?(teacher_payment)
|
||||
if affiliate_referral
|
||||
referral_info = affiliate_referral.should_attribute_payment?(teacher_payment)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def redeem_free_credit
|
||||
using_free_credit = false
|
||||
if self.has_redeemable_jamtrack
|
||||
|
|
@ -1908,7 +1918,8 @@ module JamRuby
|
|||
booking = card_approved(params[:token], params[:zip])
|
||||
if params[:test_drive]
|
||||
self.reload
|
||||
test_drive = Sale.purchase_test_drive(self, booking)
|
||||
result = Sale.purchase_test_drive(self, booking)
|
||||
test_drive = result[:sale]
|
||||
elsif params[:normal]
|
||||
self.reload
|
||||
end
|
||||
|
|
@ -1953,16 +1964,28 @@ module JamRuby
|
|||
end
|
||||
|
||||
def has_rated_teacher(teacher)
|
||||
if teacher.is_a?(JamRuby::User)
|
||||
teacher = teacher.teacher
|
||||
end
|
||||
Review.where(target_id: teacher.id).where(target_type: teacher.class.to_s).count > 0
|
||||
end
|
||||
|
||||
def has_rated_student(student)
|
||||
Review.where(target_id: student.id).where(target_type: "JamRuby::User").count > 0
|
||||
end
|
||||
|
||||
def teacher_profile_url
|
||||
"#{APP_CONFIG.external_root_url}/client#/profile/teacher/#{id}"
|
||||
end
|
||||
|
||||
def ratings_url
|
||||
"#{APP_CONFIG.external_root_url}/client?selected=ratings#/profile/teacher/#{id}"
|
||||
end
|
||||
|
||||
def student_ratings_url
|
||||
"#{APP_CONFIG.external_root_url}/client?selected=ratings#/profile/#{id}"
|
||||
end
|
||||
|
||||
def self.search_url
|
||||
"#{APP_CONFIG.external_root_url}/client#/jamclass/searchOptions"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ module JamRuby
|
|||
def self.perform
|
||||
@@log.debug("waking up")
|
||||
|
||||
TeacherPayment.daily_check
|
||||
|
||||
bounced_emails
|
||||
|
||||
calendar_manager = CalendarManager.new
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ FactoryGirl.define do
|
|||
|
||||
factory :teacher, :class => JamRuby::Teacher do
|
||||
association :user, factory: :user
|
||||
price_per_lesson_60_cents 3000
|
||||
price_per_month_60_cents 3000
|
||||
end
|
||||
|
||||
factory :musician_instrument, :class => JamRuby::MusicianInstrument do
|
||||
|
|
@ -963,7 +965,6 @@ FactoryGirl.define do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
factory :lesson_session, class: 'JamRuby::LessonSession' do
|
||||
|
||||
ignore do
|
||||
|
|
@ -980,6 +981,31 @@ FactoryGirl.define do
|
|||
#teacher_complete true
|
||||
#student_complete true
|
||||
end
|
||||
|
||||
factory :charge, class: 'JamRuby::Charge' do
|
||||
type 'JamRuby::Charge'
|
||||
amount_in_cents 1000
|
||||
end
|
||||
|
||||
factory :teacher_payment_charge, parent: :charge, class: 'JamRuby::TeacherPaymentCharge' do
|
||||
type 'JamRuby::TeacherPaymentCharge'
|
||||
end
|
||||
|
||||
|
||||
factory :teacher_payment, class: 'JamRuby::TeacherPayment' do
|
||||
association :teacher, factory: :teacher_user
|
||||
association :teacher_payment_charge, factory: :teacher_payment_charge
|
||||
amount_in_cents 1000
|
||||
end
|
||||
|
||||
# you gotta pass either lesson_session or lesson_package_purchase for this to make sense
|
||||
factory :teacher_distribution, class: 'JamRuby::TeacherDistribution' do
|
||||
association :teacher, factory: :teacher_user
|
||||
association :teacher_payment, factory: :teacher_payment
|
||||
ready false
|
||||
amount_in_cents 1000
|
||||
end
|
||||
|
||||
|
||||
factory :ip_blacklist, class: "JamRuby::IpBlacklist" do
|
||||
remote_ip '1.1.1.1'
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
booking.card_presumed_ok.should be_true
|
||||
booking.sent_notices.should be_true
|
||||
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
lesson.amount_charged.should be nil
|
||||
lesson.amount_charged.should be 0.0
|
||||
lesson.reload
|
||||
|
||||
user.reload
|
||||
|
|
@ -131,29 +131,9 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
notification.description.should eql NotificationTypes::LESSON_MESSAGE
|
||||
notification.message.should eql "Your lesson request is confirmed!"
|
||||
|
||||
# let user pay for it
|
||||
LessonBooking.hourly_check
|
||||
|
||||
# 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 = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should be true
|
||||
if lesson_session.billing_error_detail
|
||||
puts "testdrive 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.billed.should be true
|
||||
user.reload
|
||||
user.lesson_purchases.length.should eql 1
|
||||
lesson_purchase = user.lesson_purchases[0]
|
||||
|
|
@ -174,11 +154,33 @@ describe "Monthly Recurring Lesson Flow" do
|
|||
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
|
||||
lesson.amount_charged.should eql (sale.recurly_total_in_cents / 100.0).to_f
|
||||
|
||||
# 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 = JSON.parse(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 true
|
||||
lesson_session.sent_billing_notices.should be false
|
||||
user.reload
|
||||
user.remaining_test_drives.should eql 0
|
||||
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
|
||||
UserMailer.deliveries.length.should eql 1 # one for student
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ describe "Normal Lesson Flow" do
|
|||
booking.card_presumed_ok.should be_true
|
||||
booking.sent_notices.should be_true
|
||||
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
lesson.amount_charged.should be nil
|
||||
lesson.amount_charged.should be 0.0
|
||||
lesson.reload
|
||||
|
||||
user.reload
|
||||
|
|
@ -96,7 +96,7 @@ describe "Normal Lesson Flow" do
|
|||
|
||||
StripeMock.prepare_card_error(:card_declined)
|
||||
|
||||
lesson_session.billing_attempts.should eql 0
|
||||
lesson_session.lesson_payment_charge.billing_attempts.should eql 0
|
||||
user.lesson_purchases.length.should eql 0
|
||||
LessonSession.hourly_check
|
||||
lesson_session.reload
|
||||
|
|
@ -104,7 +104,7 @@ describe "Normal Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should eql true
|
||||
lesson_session.billing_attempts.should eql 1
|
||||
puts lesson_session.billing_error_detail
|
||||
lesson_session.billing_error_reason.should eql 'card_declined'
|
||||
lesson_session.billed.should eql false
|
||||
|
|
@ -120,7 +120,6 @@ describe "Normal Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should eql true
|
||||
lesson_session.billing_error_reason.should eql 'card_declined'
|
||||
lesson_session.billed.should eql false
|
||||
lesson_session.billing_attempts.should eql 1
|
||||
|
|
@ -137,7 +136,6 @@ describe "Normal Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should eql true
|
||||
lesson_session.billing_attempts.should eql 2
|
||||
lesson_session.billing_error_reason.should eql 'card_expired'
|
||||
lesson_session.billed.should eql false
|
||||
|
|
@ -154,7 +152,6 @@ describe "Normal Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should eql true
|
||||
lesson_session.billing_attempts.should eql 3
|
||||
lesson_session.billing_error_reason.should eql 'processing_error'
|
||||
lesson_session.billed.should eql false
|
||||
|
|
@ -171,8 +168,10 @@ describe "Normal Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should eql true
|
||||
lesson_session.billing_attempts.should eql 4
|
||||
if lesson_session.billing_error_detail
|
||||
lesson_session.billing_error_detail
|
||||
end
|
||||
lesson_session.billing_error_reason.should eql 'processing_error'
|
||||
lesson_session.billed.should eql true
|
||||
user.reload
|
||||
|
|
@ -198,6 +197,7 @@ describe "Normal Lesson Flow" do
|
|||
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
|
||||
lesson.reload
|
||||
lesson.amount_charged.should eql (sale.recurly_total_in_cents / 100.0).to_f
|
||||
lesson_session.billing_error_reason.should eql 'processing_error'
|
||||
lesson_session.sent_billing_notices.should be true
|
||||
|
|
@ -230,7 +230,7 @@ describe "Normal Lesson Flow" do
|
|||
booking.card_presumed_ok.should be_true
|
||||
booking.sent_notices.should be_true
|
||||
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
lesson.amount_charged.should be nil
|
||||
lesson.amount_charged.should eql 0.0
|
||||
lesson.reload
|
||||
|
||||
user.reload
|
||||
|
|
@ -340,7 +340,6 @@ describe "Normal Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should be true
|
||||
if lesson_session.billing_error_detail
|
||||
puts "testdrive 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
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ describe "Recurring Lesson Flow" do
|
|||
booking.card_presumed_ok.should be_true
|
||||
booking.sent_notices.should be_true
|
||||
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
|
||||
lesson.amount_charged.should be nil
|
||||
lesson.amount_charged.should be 0.0
|
||||
lesson.reload
|
||||
|
||||
user.reload
|
||||
|
|
@ -149,7 +149,6 @@ describe "Recurring Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should be true
|
||||
if lesson_session.billing_error_detail
|
||||
puts "testdrive 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
|
||||
|
|
|
|||
|
|
@ -23,10 +23,14 @@ describe "TestDrive Lesson Flow" do
|
|||
booking.card_presumed_ok.should be_false
|
||||
booking.should eql user.unprocessed_test_drive
|
||||
booking.sent_notices.should be_false
|
||||
user.reload
|
||||
user.remaining_test_drives.should eql 0
|
||||
|
||||
########## Need validate their credit card
|
||||
token = create_stripe_token
|
||||
result = user.payment_update({token: token, zip: '78759', test_drive: true})
|
||||
user.reload
|
||||
user.remaining_test_drives.should eql 3
|
||||
booking = result[:lesson]
|
||||
lesson = booking.lesson_sessions[0]
|
||||
test_drive = result[:test_drive]
|
||||
|
|
@ -158,13 +162,12 @@ describe "TestDrive Lesson Flow" do
|
|||
analysis = JSON.parse(lesson_session.analysis)
|
||||
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
|
||||
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
|
||||
lesson_session.bill.should be false
|
||||
lesson_session.billed.should be false
|
||||
if lesson_session.billing_error_detail
|
||||
puts "testdrive 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.billing_error_reason.should be_nil
|
||||
lesson_session.sent_billing_notices.should be true
|
||||
lesson_session.sent_notices.should be true
|
||||
purchase = lesson_session.lesson_package_purchase
|
||||
purchase.should_not be nil
|
||||
purchase.price_in_cents.should eql 4999
|
||||
|
|
|
|||
|
|
@ -0,0 +1,293 @@
|
|||
require 'spec_helper'
|
||||
|
||||
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(:teacher) { FactoryGirl.create(:user, teacher: teacher_obj) }
|
||||
let(:teacher2) { FactoryGirl.create(:user, teacher: teacher_obj2) }
|
||||
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)}
|
||||
|
||||
describe "pending_teacher_payments" do
|
||||
|
||||
it "empty" do
|
||||
TeacherPayment.pending_teacher_payments.count.should eql 0
|
||||
end
|
||||
|
||||
it "one distribution" do
|
||||
test_drive_distribution.touch
|
||||
|
||||
payments = TeacherPayment.pending_teacher_payments
|
||||
payments.count.should eql 0
|
||||
|
||||
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!
|
||||
|
||||
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
|
||||
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_")
|
||||
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!
|
||||
|
||||
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.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)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
module Mock
|
||||
module StripeMock
|
||||
class ErrorQueue
|
||||
def clear
|
||||
@queue = []
|
||||
|
|
@ -27,7 +27,7 @@ def testdrive_lesson(user, teacher, slots = nil)
|
|||
|
||||
|
||||
booking = LessonBooking.book_test_drive(user, teacher, slots, "Hey I've heard of you before.")
|
||||
puts "BOOKING #{booking.errors.inspect}"
|
||||
#puts "BOOKING #{booking.errors.inspect}"
|
||||
booking.errors.any?.should be_false
|
||||
lesson = booking.lesson_sessions[0]
|
||||
booking.card_presumed_ok.should be_true
|
||||
|
|
@ -60,7 +60,7 @@ def normal_lesson(user, teacher, slots = nil)
|
|||
end
|
||||
|
||||
booking = LessonBooking.book_normal(user, teacher, slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
|
||||
puts "NORMAL BOOKING #{booking.errors.inspect}"
|
||||
# puts "NORMAL BOOKING #{booking.errors.inspect}"
|
||||
booking.errors.any?.should be_false
|
||||
lesson = booking.lesson_sessions[0]
|
||||
booking.card_presumed_ok.should be_true
|
||||
|
|
|
|||
|
|
@ -285,9 +285,18 @@ def app_config
|
|||
def end_of_wait_window_forgiveness_minutes
|
||||
1
|
||||
end
|
||||
|
||||
def test_drive_wait_period_year
|
||||
1
|
||||
end
|
||||
|
||||
def stripe
|
||||
{
|
||||
:publishable_key => 'pk_test_HLTvioRAxN3hr5fNfrztZeoX',
|
||||
:secret_key => 'sk_test_OkjoIF7FmdjunyNsdVqJD02D',
|
||||
:source_customer => 'cus_88Vp44SLnBWMXq' # seth@jamkazam.com in JamKazam-test account
|
||||
}
|
||||
end
|
||||
private
|
||||
|
||||
def audiomixer_workspace_path
|
||||
|
|
@ -300,7 +309,6 @@ def app_config
|
|||
dev_path = "#{dev_path}/audiomixer/audiomixer/audiomixerapp"
|
||||
dev_path if File.exist? dev_path
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
klass.new
|
||||
|
|
@ -359,6 +367,46 @@ def friend(user1, user2)
|
|||
FactoryGirl.create(:friendship, user: user2, friend: user1)
|
||||
end
|
||||
|
||||
def stripe_oauth_client
|
||||
# from here in the JamKazam Test account: https://dashboard.stripe.com/account/applications/settings
|
||||
client_id = "ca_88T6HlHg1NRyKFossgWIz1Tf431Jshft"
|
||||
options = {
|
||||
:site => 'https://connect.stripe.com',
|
||||
:authorize_url => '/oauth/authorize',
|
||||
:token_url => '/oauth/token'
|
||||
}
|
||||
|
||||
@stripe_oauth_client ||= OAuth2::Client.new(client_id, APP_CONFIG.stripe[:publishable_key], options)
|
||||
end
|
||||
|
||||
def stripe_account2_id
|
||||
# seth+stripe+test1@jamkazam.com / jam12345
|
||||
"acct_17sCNpDcwjPgpqRL"
|
||||
end
|
||||
def stripe_account1_id
|
||||
# seth+stripe1@jamkazam.com / jam123
|
||||
=begin
|
||||
curl -X POST https://connect.stripe.com/oauth/token \
|
||||
-d client_secret=sk_test_OkjoIF7FmdjunyNsdVqJD02D \
|
||||
-d code=ac_88U1TDwBgao4I3uYyyFEO3pVbbEed6tm \
|
||||
-d grant_type=authorization_code
|
||||
|
||||
# {
|
||||
"access_token": "sk_test_q8WZbdQXt7RGRkBR0fhgohG6",
|
||||
"livemode": false,
|
||||
"refresh_token": "rt_88U3csV42HtY1P1Cd9KU2GCez3wixgsHtIHaQbeeu1dXVWo9",
|
||||
"token_type": "bearer",
|
||||
"stripe_publishable_key": "pk_test_s1YZDczylyRUvhAGeVhxqznp",
|
||||
"stripe_user_id": "acct_17sCNpDcwjPgpqRL",
|
||||
"scope": "read_write"
|
||||
}
|
||||
=end
|
||||
|
||||
|
||||
"acct_17sCEyH8FcKpNSnR"
|
||||
|
||||
end
|
||||
|
||||
def create_stripe_token(exp_month = 2017)
|
||||
Stripe::Token.create(
|
||||
:card => {
|
||||
|
|
|
|||
|
|
@ -109,5 +109,11 @@ SampleApp::Application.configure do
|
|||
config.video_available = "full"
|
||||
config.guard_against_fraud = true
|
||||
config.error_on_fraud = false
|
||||
|
||||
config.stripe = {
|
||||
:publishable_key => 'pk_test_HLTvioRAxN3hr5fNfrztZeoX',
|
||||
:secret_key => 'sk_test_OkjoIF7FmdjunyNsdVqJD02D',
|
||||
:source_customer => 'cus_88Vp44SLnBWMXq'
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue