# frozen_string_literal: true # 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' })) 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 unless 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 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 }]) 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 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') (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