# 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