houdini/lib/pay_recurring_donation.rb

175 lines
7.4 KiB
Ruby
Raw Normal View History

# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
require 'insert/insert_donation'
require 'insert/insert_supporter_notes'
require 'timespan'
require 'delayed_job_helper'
module PayRecurringDonation
# Pay ALL recurring donations that are currently due; each payment gets a queued delayed_job
# Returns the number of queued jobs
def self.pay_all_due_with_stripe
# Bulk insert the delayed jobs with a single expression
ids = Psql.execute_vectors(
QueryRecurringDonations._all_that_are_due
)[1..-1].flatten
jobs = ids.map do |id|
{handler: DelayedJobHelper.create_handler(PayRecurringDonation, :with_stripe, [id])}
end
Psql.execute(Qexpr.new.insert(:delayed_jobs, jobs, {
common_data: {
run_at: Time.current,
attempts: 0,
failed_at: nil,
last_error: nil,
locked_at: nil,
locked_by: nil,
priority: 0,
queue: "rec-don-payments"
}
}))
return ids
end
# run the payrecurring_donation in development so I can make sure we have the expected failures
# def self._____test_do_not_use_pay_all_due_with_stripe
# # Bulk insert the delayed jobs with a single expression
# ids = Psql.execute_vectors(
# QueryRecurringDonations._all_that_are_due
# )[1..-1].flatten
#
# output = ids.map{|id|
# begin
# i = PayRecurringDonation.with_stripe(id)
# result = {is_error:false, value: i}
# rescue => e
# result = {is_error: true, error_type: e.class.to_s, message: e.message, backtrace: e.backtrace}
# end
#
# result
# }
#
#
#
# return output
# end
# Charge an existing donation via stripe, only if it is due
# Pass in an instance of an existing RecurringDonation
def self.with_stripe(rd_id)
ParamValidation.new({:rd_id => rd_id}, {
:rd_id => {
:required => true,
:is_integer=> true
}
})
rd = RecurringDonation.where('id = ?', rd_id).first
unless rd
raise ParamValidation::ValidationError.new("#{rd_id} is not a valid recurring donation", {:key => :rd_id})
end
return false if !QueryRecurringDonations.is_due?(rd_id)
donation = Donation.where('id = ?', rd['donation_id']).first
unless donation
raise ParamValidation::ValidationError.new("#{rd['donation_id']} is not a valid donation", {})
end
result = {}
result = result.merge(InsertDonation.insert_charge({
'card_id' => donation['card_id'],
'recurring_donation' => true,
'designation' => donation['designation'],
'amount' => donation['amount'],
'nonprofit_id' => donation['nonprofit_id'],
'donation_id' => donation['id'],
'supporter_id' => donation['supporter_id'],
'old_donation' => true
}))
if result['charge']['status'] != 'failed'
result['recurring_donation'] = Psql.execute(
Qexpr.new.update(:recurring_donations, {n_failures: 0})
.where("id=$id", id: rd_id).returning('*')
).first
Delayed::Job.enqueue JobTypes::DonorPaymentNotificationJob.new(rd['donation_id'])
Delayed::Job.enqueue JobTypes::NonprofitPaymentNotificationJob.new(rd['donation_id'])
InsertActivities.for_recurring_donations([result['payment']['id']])
else
result['recurring_donation'] = Psql.execute(
Qexpr.new.update(:recurring_donations, {n_failures: rd['n_failures'] + 1})
.where("id=$id", id: rd_id).returning('*')
).first
DonationMailer.delay.donor_failed_recurring_donation(rd['donation_id'])
if rd['n_failures'] >= 3
DonationMailer.delay.nonprofit_failed_recurring_donation(rd['donation_id'])
end
InsertSupporterNotes.create([{content: "This supporter had a payment failure for their recurring donation with ID #{rd_id}", supporter_id: donation['supporter_id'], user_id: 540}])
end
return result
end
def self.fail_a_recurring_donation(rd, donation, notify_nonprofit=false)
recurring_donation = Psql.execute(
Qexpr.new.update(:recurring_donations, {n_failures: 3})
.where("id=$id", id: rd['id']).returning('*')
).first
DonationMailer.delay.donor_failed_recurring_donation(rd['donation_id'])
if notify_nonprofit
DonationMailer.delay.nonprofit_failed_recurring_donation(rd['donation_id'])
end
InsertSupporterNotes.create([{content: "This supporter had a payment failure for their recurring donation with ID #{rd['id']}", supporter_id: donation['supporter_id'], user_id: 540}])
return recurring_donation
end
# Charge an existing donation via stripe, NO MATTER WHAT
# Pass in an instance of an existing RecurringDonation
def self.with_stripe_BUT_NO_MATTER_WHAT(rd_id, enter_todays_date, run_this=false, set_this_true=false, this_one_needs_to_be_false=true, is_this_run_dangerously="no")
if (PayRecurringDonation::ULTIMATE_VERIFICATION(enter_todays_date, run_this, set_this_true, this_one_needs_to_be_false, is_this_run_dangerously))
rd = Psql.execute("SELECT * FROM recurring_donations WHERE id=#{rd_id}").first
donation = Psql.execute("SELECT * FROM donations WHERE id=#{rd['donation_id']}").first
result = {}
result = result.merge(InsertDonation.insert_charge({
'card_id' => donation['card_id'],
'recurring_donation' => true,
'designation' => donation['designation'],
'amount' => donation['amount'],
'nonprofit_id' => donation['nonprofit_id'],
'donation_id' => donation['id'],
'supporter_id' => donation['supporter_id']
}))
if result['charge']['status'] != 'failed'
result['recurring_donation'] = Psql.execute(
Qexpr.new.update(:recurring_donations, {n_failures: 0})
.where("id=$id", id: rd_id).returning('*')
).first
Delayed::Job.enqueue JobTypes::DonorPaymentNotificationJob.new(rd['donation_id'])
Delayed::Job.enqueue JobTypes::NonprofitPaymentNotificationJob.new(rd['donation_id'])
InsertActivities.for_recurring_donations([result['payment']['id']])
else
result['recurring_donation'] = Psql.execute(
Qexpr.new.update(:recurring_donations, {n_failures: rd['n_failures'] + 1})
.where("id=$id", id: rd_id).returning('*')
).first
DonationMailer.delay.donor_failed_recurring_donation(rd['donation_id'])
if rd['n_failures'] >= 3
DonationMailer.delay.nonprofit_failed_recurring_donation(rd['donation_id'])
end
InsertSupporterNotes.create([{content: "This supporter had a payment failure for their recurring donation with ID #{rd_id}", supporter_id: donation['supporter_id'], user_id: 540}])
end
return result
end
return false
end
def self.ULTIMATE_VERIFICATION(enter_todays_date, run_this=false, set_this_true=false, this_one_needs_to_be_false=true, is_this_run_dangerously="no")
return (Date.parse(enter_todays_date) == Date.today() && run_this && set_this_true && !this_one_needs_to_be_false && is_this_run_dangerously == "run dangerously")
end
end