# 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 module InsertRecurringDonation # Create a recurring_donation, donation, payment, charge, and activity # See controllers/nonprofits/recurring_donations_controller#create for the data params to pass in def self.with_stripe(data) data = data.with_indifferent_access ParamValidation.new(data, InsertDonation.common_param_validations .merge(token: { required: true, format: UUID::Regex })) if data[:recurring_donation].nil? data[:recurring_donation] = {} else ParamValidation.new(data[:recurring_donation], interval: { is_integer: true }, start_date: { can_be_date: true }, time_unit: { included_in: %w[month day week year] }, paydate: { is_integer: true }) if data[:recurring_donation][:paydate] data[:recurring_donation][:paydate] = data[:recurring_donation][:paydate].to_i end ParamValidation.new(data[:recurring_donation], paydate: { min: 1, max: 28 }) end source_token = QuerySourceToken.get_and_increment_source_token(data[:token], nil) tokenizable = source_token.tokenizable QuerySourceToken.validate_source_token_type(source_token) entities = RetrieveActiveRecordItems.retrieve_from_keys(data, Supporter => :supporter_id, Nonprofit => :nonprofit_id) entities = entities.merge(RetrieveActiveRecordItems.retrieve_from_keys(data, { Campaign => :campaign_id, Event => :event_id, Profile => :profile_id }, true)) InsertDonation.validate_entities(entities) ## does the card belong to the supporter? if tokenizable.holder != entities[:supporter_id] raise ParamValidation::ValidationError.new("Supporter #{entities[:supporter_id].id} does not own card #{tokenizable.id}", key: :token) end data['card_id'] = tokenizable.id result = {} data[:date] = Time.now data = data.merge(payment_provider: payment_provider(data)) data = data.except(:old_donation).except('old_donation') # if start date is today, make initial charge first test_start_date = get_test_start_date(data) if test_start_date.nil? || Time.current >= test_start_date result = result.merge(InsertDonation.insert_charge(data)) if result['charge']['status'] == 'failed' raise ChargeError, result['charge']['failure_message'] end end # Create the donation record result['donation'] = InsertDonation.insert_donation(data, entities) entities[:donation_id] = result['donation'] # Create the recurring_donation record result['recurring_donation'] = insert_recurring_donation(data, entities) # Update charge foreign keys if result['payment'] InsertDonation.update_donation_keys(result) trx = entities[:supporter_id].transactions.build(amount: data['amount'], created: data['date']) don = trx.donations.build(amount: result['donation'].amount, legacy_donation: result['donation']) stripe_t = trx.build_subtransaction( subtransactable: StripeTransaction.new(amount: data['amount']), subtransaction_payments:[ SubtransactionPayment.new( paymentable: StripeCharge.new(payment: Payment.find(result['payment']['id']))) ], created: data['date'] ); trx.save! don.save! stripe_t.save! stripe_t.subtransaction_payments.each{|stp| stp.publish_created} stripe_t.publish_created don.publish_created trx.publish_created # Create the activity record result['activity'] = InsertActivities.for_recurring_donations([result['payment'].id]) end # Send receipts Houdini.event_publisher.announce(:recurring_donation_create, result['donation'], entities[:supporter_id].locale) result end def self.with_sepa(data) data = set_defaults(data) data = data.merge(payment_provider: payment_provider(data)) result = {} if Time.current >= data[:recurring_donation][:start_date] result = result.merge(InsertDonation.insert_charge(data)) end result['donation'] = Psql.execute(Qexpr.new.insert(:donations, [ data.except(:recurring_donation) ]).returning('*')).first result['recurring_donation'] = Psql.execute(Qexpr.new.insert(:recurring_donations, [ data[:recurring_donation].merge(donation_id: result['donation']['id']) ]).returning('*')).first InsertDonation.update_donation_keys(result) if result['payment'] Houdini.event_publisher.announce(:recurring_donation_create, result['donation'], entities[:supporter_id].locale) { status: 200, json: result } end # the data model here is brutal. This needs to get cleaned up. def self.convert_donation_to_recurring_donation(donation_id) ParamValidation.new({ donation_id: donation_id }, donation_id: { required: true, is_integer: true }) don = Donation.where('id = ? ', donation_id).first unless don raise ParamValidation::ValidationError.new("#{donation_id} is not a valid donation", key: :donation_id, val: donation_id) end rd = insert_recurring_donation({ amount: don.amount, email: don.supporter.email, anonymous: don.anonymous, origin_url: don.origin_url, recurring_donation: { start_date: don.created_at, paydate: convert_date_to_valid_paydate(don.created_at) }, date: don.created_at }, supporter_id: don.supporter, nonprofit_id: don.nonprofit, donation_id: don) don.recurring_donation = rd don.recurring = true don.payment.kind = 'RecurringDonation' don.payment.save! rd.save! don.save! rd end def self.insert_recurring_donation(data, entities) rd = RecurringDonation.new rd.amount = data[:amount] rd.nonprofit = entities[:nonprofit_id] rd.donation = entities[:donation_id] rd.supporter_id = entities[:supporter_id].id rd.active = true rd.edit_token = SecureRandom.uuid rd.n_failures = 0 rd.email = entities[:supporter_id].email rd.interval = data[:recurring_donation][:interval].blank? ? 1 : data[:recurring_donation][:interval] rd.time_unit = data[:recurring_donation][:time_unit].blank? ? 'month' : data[:recurring_donation][:time_unit] rd.start_date = if data[:recurring_donation][:start_date].blank? Time.current.beginning_of_day elsif data[:recurring_donation][:start_date].is_a? Time data[:recurring_donation][:start_date] else Chronic.parse(data[:recurring_donation][:start_date]) end if rd.time_unit == 'month' && rd.interval == 1 && data[:recurring_donation][:paydate].nil? rd.paydate = convert_date_to_valid_paydate(rd.start_date) else rd.paydate = data[:recurring_donation][:paydate] end rd.save! rd end def self.get_test_start_date(data) unless data[:recurring_donation] && data[:recurring_donation][:start_date] return nil end Chronic.parse(data[:recurring_donation][:start_date]) end def self.locale_for_supporter(supporter_id) Psql.execute( Qexpr.new.select(:locale).from(:supporters) .where('id=$id', id: supporter_id) ).first['locale'] end def self.payment_provider(data) if data[:card_id] :credit_card elsif data[:direct_debit_detail_id] :sepa end end def self.convert_date_to_valid_paydate(date) day = date.day day > 28 ? 28 : day end end