# 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 Audit # Given a list of pairs of nonprofit ids and stripe_account_ids (eg [[4341, 'acct_arst'], [3624, 'acct_arst']]) # Find all their available balances on both stripe and CC # Give all the ones that dont match up with the difference # Negative difference = more balance on CC # Positive difference = more balance on Stripe # Returns an array of pairs of ids with balance difference (eg [[4341, -10], [3624, 100]]) def self.match_available_balances(nps, date) date ||= Time.current nps.map do |id, stripe_account_id| cc_bal = QueryPayments.get_payout_totals(QueryPayments.ids_for_payout(id, date: date))['net_amount'] stripe_bal = Stripe::Balance.retrieve(stripe_account: stripe_account_id).available.first.amount [id, stripe_bal - cc_bal] end end # Print a report of whether saved payout balances match up to the sum of the payment records def self.payout_check(id) p = Payout.find(id) gross = p.payments.sum(:gross_amount) fees = p.payments.sum(:fee_total) net = p.payments.sum(:net_amount) puts [ [p.gross_amount, p.fee_total, p.net_amount].join(', ') + ' -- payout columns', [gross, fees, net].join(', ') + ' -- summed from payments', [p.gross_amount - gross, p.fee_total - fees, p.net_amount - net].join(', ') + ' -- differences' ].join("\n") end # Return a list of all Stripe transaction objects for an account def self.find_all_transactions(np_id) logger = ActiveRecord::Base.logger; ActiveRecord::Base.logger = nil acct = Nonprofit.find(np_id).stripe_account_id starting_after = nil transfers = [] loop do new_transfers = Stripe::Transfer.all(limit: 100, starting_after: starting_after, destination: acct).data break if new_transfers.empty? transfers += new_transfers starting_after = new_transfers.last.id end ActiveRecord::Base.logger = logger transfers end # Given a list of Stripe transaction objects, see if any are missing on CommitChange def self.find_missing_charges(transfers) transfers .map { |t| [t.source_transaction, t.amount] } .select { |id, amount| Charge.where(stripe_charge_id: id, amount: amount).empty? } end # Audit some basic balances for a nonprofit with those on Stripe def self.audit_balances(id) np = Nonprofit.find(id) puts "Stripe Dashboard: https://dashboard.stripe.com/#{np.stripe_account_id}" puts "CC Payments: https://commitchange.com/nonprofits/#{id}/payments" puts "CC Payouts: https://commitchange.com/nonprofits/#{id}/payouts" begin stripe_balances = Stripe::Balance.retrieve(stripe_account: np.stripe_account_id) available = stripe_balances['available'].first['amount'] pending = stripe_balances['pending'].first['amount'] rescue Exception => e available = 0 pending = 0 puts "UNRECOGNIZED STRIPE ACCOUNT ID: #{np.stripe_account_id}" end bal = np_balances(id) data = { stripe_net: available + pending, cc_net: bal['net_amount'], diff: bal['net_amount'] - (available + pending) } data end # Get the total gross, net # Pretty much duped from QueryPayments def self.np_balances(np_id) payment_ids_expr = Qx.select('DISTINCT payments.id') .from(:payments) .left_join( [:charges, 'charges.payment_id=payments.id'], [:refunds, 'refunds.payment_id=payments.id'], [:disputes, 'disputes.payment_id=payments.id'] ) .where('payments.nonprofit_id=$id', id: np_id) .and_where('refunds.payment_id IS NOT NULL OR charges.payment_id IS NOT NULL OR disputes.payment_id IS NOT NULL') .and_where(%( (refunds.payment_id IS NOT NULL AND (refunds.disbursed IS NULL OR refunds.disbursed='f')) OR (charges.status='available' OR charges.status='pending') OR (disputes.status='lost') )) Qx.select( 'coalesce(SUM(payments.gross_amount), 0) AS gross_amount', 'coalesce(SUM(payments.fee_total), 0) AS fee_total', 'coalesce(SUM(payments.net_amount), 0) AS net_amount', 'COUNT(payments.*) AS count' ) .from(:payments) .where('payments.id IN ($ids)', ids: payment_ids_expr) .execute .first end end