# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later require 'query/query_recurring_donations' require 'insert/insert_supporter_notes' require 'format/date' require 'format/currency' module UpdateRecurringDonations # Update the card id and name for a given recurring donation (provide rd['donation_id']) def self.update_card_id(rd, token) rd = rd&.with_indifferent_access ParamValidation.new({rd: rd, token: token}, { rd: {is_hash: true, required: true}, token: {format: UUID::Regex, required: true} }) ParamValidation.new(rd, { id: {is_reference: true, required: true} }) source_token = QuerySourceToken.get_and_increment_source_token(token, nil) tokenizable = source_token.tokenizable entities = RetrieveActiveRecordItems.retrieve_from_keys(rd, RecurringDonation => :id ) validate_entities(entities[:id], tokenizable) Qx.transaction do rec_don = entities[:id] donation = rec_don.donation #TODO This is stupid but the two are used together inconsistently. We should scrap one or the other. donation.card = tokenizable rec_don.card_id = tokenizable rec_don.n_failures = 0 rec_don.save! donation.save! InsertSupporterNotes.create([{content: "This supporter updated their card for their recurring donation with ID #{rec_don.id}", supporter_id: rec_don.supporter.id, user_id: 540}]) end return QueryRecurringDonations.fetch_for_edit(rd[:id])['recurring_donation'] end # Update the paydate for a given recurring donation (provide rd['id']) def self.update_paydate(rd, paydate) return ValidationError.new(['Invalid paydate']) unless (1..28).include?(paydate.to_i) Psql.execute(Qexpr.new.update(:recurring_donations, paydate: paydate).where("id=$id", id: rd['id'])) rd['paydate'] = paydate return rd end # @param [RecurringDonation] rd # @param [String] token # @param [Integer] amount def self.update_amount(rd, token, amount) ParamValidation.new({amount: amount, rd: rd, token: token}, {amount: {is_integer: true, min: 50, required:true}, rd: {required:true, is_a: RecurringDonation}, token: {required:true, format: UUID::Regex} }) source_token = QuerySourceToken.get_and_increment_source_token(token, nil) tokenizable = source_token.tokenizable validate_entities(rd, tokenizable) previous_amount = rd.amount donation = rd.donation Qx.transaction do #TODO This is stupid but the two are used together inconsistently. We should scrap one or the other. rd.card = tokenizable rd.amount = amount rd.n_failures= 0 donation.card = tokenizable donation.amount = amount rd.save! donation.save! end EmailJobQueue.queue(JobTypes::NonprofitRecurringDonationChangeAmountJob, rd.id, previous_amount) EmailJobQueue.queue(JobTypes::DonorRecurringDonationChangeAmountJob,rd.id, previous_amount) rd end def self.update_from_start_dates RecurringDonation.inactive.where("start_date >= ?", Date.today).update_all(active: true) end def self.update_from_end_dates RecurringDonation.active.where("end_date < ?", Date.today).update_all(active: false) end # Cancel a recurring donation (set active='f') and record the supporter/user email who did it def self.cancel(rd_id, email, dont_notify_nonprofit=false) Psql.execute( Qexpr.new.update(:recurring_donations, { active: false, cancelled_by: email, cancelled_at: Time.current }) .where("id=$id", id: rd_id.to_i) ) rd = QueryRecurringDonations.fetch_for_edit(rd_id)['recurring_donation'] InsertSupporterNotes.create([{supporter_id: rd['supporter_id'], content: "This supporter's recurring donation for $#{Format::Currency.cents_to_dollars(rd['amount'])} was cancelled by #{rd['cancelled_by']} on #{Format::Date.simple(rd['cancelled_at'])}", user_id: 540}]) if (!dont_notify_nonprofit) DonationMailer.delay.nonprofit_recurring_donation_cancellation(rd['donation_id']) end return rd end def self.update(rd, params) params = set_defaults(params) if params[:donation] rd.donation.update_attributes(params[:donation]) return rd.donation unless rd.donation.valid? params = params.except(:donation) end rd.update_attributes(params) return rd end def self.set_defaults(params) if params[:donation] && params[:donation][:dollars] params[:donation][:amount] = Format::Currency.dollars_to_cents(params[:donation][:dollars]) params[:donation] = params[:donation].except(:dollars) params[:amount] = params[:donation][:amount] end if params[:end_date_str] if params[:end_date_str].blank? || params[:end_date_str] == 'None' params[:end_date] = nil else params[:end_date] = Chronic.parse(params[:end_date_str]) end params = params.except(:end_date_str) end return params end # @param [RecurringDonation] rd # @param [Card] tokenizable def self.validate_entities(rd, tokenizable) if (rd.cancelled_at) raise ParamValidation::ValidationError.new("Recurring Donation #{rd.id} is already cancelled.", key: :id) end if (tokenizable.deleted) raise ParamValidation::ValidationError.new("Tokenized card #{tokenizable.id} is not valid.", key: :token) end if tokenizable.holder != rd.supporter raise ParamValidation::ValidationError.new("Supporter #{rd.supporter.id} does not own card #{tokenizable.id}", key: :token) end end end