# frozen_string_literal: true # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE require 'insert/insert_donation' require 'insert/insert_supporter_notes' require 'timespan' 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 PayRecurringDonationsJob.perform_later(*id) 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 trx = nonprofit.transactions.build(amount: donation['amount']) 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' rd.update(n_failures: 0) result['recurring_donation'] = rd Houdini.event_publisher.announce(:recurring_donation_payment_succeeded, donation, donation&.supporter&.locale || 'en') donation = trx.donations.build(amount: donation['amount'], designation: donation['designation'], dedication: donation["dedication"]) trx.save! donation.save! donation.publish_created InsertActivities.for_recurring_donations([result['payment']['id']]) else rd.n_failures += 1 rd.save! result['recurring_donation'] = rd Houdini.event_publisher.announce(:recurring_donation_payment_failed, donation) InsertSupporterNotes.create({supporter:Supporter.find(donation['supporter_id']), user: User.find(540), note:{ content: "This supporter had a payment failure for their recurring donation with ID #{rd_id}"}}) 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 FailedRecurringDonationPaymentDonorEmailJob.perform_later Donation.find(rd['donation_id']) if notify_nonprofit FailedRecurringDonationPaymentNonprofitEmailJob.perform_later Donation.find(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 ## add PaymentNotificationJobHere 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 FailedRecurringDonationPaymentDonorEmailJob.perform_later Donation.find(rd['donation_id']) if rd['n_failures'] >= 3 FailedRecurringDonationPaymentNonprofitEmailJob.perform_later Donation.find(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