mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 13:19:39 +02:00
150 lines
5.8 KiB
Ruby
150 lines
5.8 KiB
Ruby
|
class PlaidItem::Syncer
|
||
|
attr_reader :plaid_item
|
||
|
|
||
|
def initialize(plaid_item)
|
||
|
@plaid_item = plaid_item
|
||
|
end
|
||
|
|
||
|
def perform_sync(sync)
|
||
|
begin
|
||
|
Rails.logger.info("Fetching and loading Plaid data")
|
||
|
fetch_and_load_plaid_data
|
||
|
plaid_item.update!(status: :good) if plaid_item.requires_update?
|
||
|
|
||
|
plaid_item.accounts.each do |account|
|
||
|
account.sync_later(parent_sync: sync, window_start_date: sync.window_start_date, window_end_date: sync.window_end_date)
|
||
|
end
|
||
|
|
||
|
Rails.logger.info("Plaid data fetched and loaded")
|
||
|
rescue Plaid::ApiError => e
|
||
|
handle_plaid_error(e)
|
||
|
raise e
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def perform_post_sync
|
||
|
plaid_item.auto_match_categories!
|
||
|
end
|
||
|
|
||
|
private
|
||
|
def plaid
|
||
|
plaid_item.plaid_region == "eu" ? plaid_eu : plaid_us
|
||
|
end
|
||
|
|
||
|
def plaid_eu
|
||
|
@plaid_eu ||= Provider::Registry.get_provider(:plaid_eu)
|
||
|
end
|
||
|
|
||
|
def plaid_us
|
||
|
@plaid_us ||= Provider::Registry.get_provider(:plaid_us)
|
||
|
end
|
||
|
|
||
|
def safe_fetch_plaid_data(method)
|
||
|
begin
|
||
|
plaid.send(method, plaid_item)
|
||
|
rescue Plaid::ApiError => e
|
||
|
Rails.logger.warn("Error fetching #{method} for item #{plaid_item.id}: #{e.message}")
|
||
|
nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def handle_plaid_error(error)
|
||
|
error_body = JSON.parse(error.response_body)
|
||
|
|
||
|
if error_body["error_code"] == "ITEM_LOGIN_REQUIRED"
|
||
|
plaid_item.update!(status: :requires_update)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def fetch_and_load_plaid_data
|
||
|
data = {}
|
||
|
|
||
|
# Log what we're about to fetch
|
||
|
Rails.logger.info "Starting Plaid data fetch (accounts, transactions, investments, liabilities)"
|
||
|
|
||
|
item = plaid.get_item(plaid_item.access_token).item
|
||
|
plaid_item.update!(available_products: item.available_products, billed_products: item.billed_products)
|
||
|
|
||
|
# Institution details
|
||
|
if item.institution_id.present?
|
||
|
begin
|
||
|
Rails.logger.info "Fetching Plaid institution details for #{item.institution_id}"
|
||
|
institution = plaid.get_institution(item.institution_id)
|
||
|
plaid_item.update!(
|
||
|
institution_id: item.institution_id,
|
||
|
institution_url: institution.institution.url,
|
||
|
institution_color: institution.institution.primary_color
|
||
|
)
|
||
|
rescue Plaid::ApiError => e
|
||
|
Rails.logger.warn "Failed to fetch Plaid institution details: #{e.message}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Accounts
|
||
|
fetched_accounts = plaid.get_item_accounts(plaid_item).accounts
|
||
|
data[:accounts] = fetched_accounts || []
|
||
|
Rails.logger.info "Processing Plaid accounts (count: #{fetched_accounts.size})"
|
||
|
|
||
|
internal_plaid_accounts = fetched_accounts.map do |account|
|
||
|
internal_plaid_account = plaid_item.plaid_accounts.find_or_create_from_plaid_data!(account, plaid_item.family)
|
||
|
internal_plaid_account.sync_account_data!(account)
|
||
|
internal_plaid_account
|
||
|
end
|
||
|
|
||
|
# Transactions
|
||
|
fetched_transactions = safe_fetch_plaid_data(:get_item_transactions)
|
||
|
data[:transactions] = fetched_transactions || []
|
||
|
|
||
|
if fetched_transactions
|
||
|
Rails.logger.info "Processing Plaid transactions (added: #{fetched_transactions.added.size}, modified: #{fetched_transactions.modified.size}, removed: #{fetched_transactions.removed.size})"
|
||
|
PlaidItem.transaction do
|
||
|
internal_plaid_accounts.each do |internal_plaid_account|
|
||
|
added = fetched_transactions.added.select { |t| t.account_id == internal_plaid_account.plaid_id }
|
||
|
modified = fetched_transactions.modified.select { |t| t.account_id == internal_plaid_account.plaid_id }
|
||
|
removed = fetched_transactions.removed.select { |t| t.account_id == internal_plaid_account.plaid_id }
|
||
|
|
||
|
internal_plaid_account.sync_transactions!(added:, modified:, removed:)
|
||
|
end
|
||
|
|
||
|
plaid_item.update!(next_cursor: fetched_transactions.cursor)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Investments
|
||
|
fetched_investments = safe_fetch_plaid_data(:get_item_investments)
|
||
|
data[:investments] = fetched_investments || []
|
||
|
|
||
|
if fetched_investments
|
||
|
Rails.logger.info "Processing Plaid investments (transactions: #{fetched_investments.transactions.size}, holdings: #{fetched_investments.holdings.size}, securities: #{fetched_investments.securities.size})"
|
||
|
PlaidItem.transaction do
|
||
|
internal_plaid_accounts.each do |internal_plaid_account|
|
||
|
transactions = fetched_investments.transactions.select { |t| t.account_id == internal_plaid_account.plaid_id }
|
||
|
holdings = fetched_investments.holdings.select { |h| h.account_id == internal_plaid_account.plaid_id }
|
||
|
securities = fetched_investments.securities
|
||
|
|
||
|
internal_plaid_account.sync_investments!(transactions:, holdings:, securities:)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Liabilities
|
||
|
fetched_liabilities = safe_fetch_plaid_data(:get_item_liabilities)
|
||
|
data[:liabilities] = fetched_liabilities || []
|
||
|
|
||
|
if fetched_liabilities
|
||
|
Rails.logger.info "Processing Plaid liabilities (credit: #{fetched_liabilities.credit&.size || 0}, mortgage: #{fetched_liabilities.mortgage&.size || 0}, student: #{fetched_liabilities.student&.size || 0})"
|
||
|
PlaidItem.transaction do
|
||
|
internal_plaid_accounts.each do |internal_plaid_account|
|
||
|
credit = fetched_liabilities.credit&.find { |l| l.account_id == internal_plaid_account.plaid_id }
|
||
|
mortgage = fetched_liabilities.mortgage&.find { |l| l.account_id == internal_plaid_account.plaid_id }
|
||
|
student = fetched_liabilities.student&.find { |l| l.account_id == internal_plaid_account.plaid_id }
|
||
|
|
||
|
internal_plaid_account.sync_credit_data!(credit) if credit
|
||
|
internal_plaid_account.sync_mortgage_data!(mortgage) if mortgage
|
||
|
internal_plaid_account.sync_student_loan_data!(student) if student
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|