UNPKG

expo-finance-kit

Version:

Native Expo module for Apple FinanceKit - Access financial data from Apple Card and other accounts

254 lines 10.5 kB
/** * Transaction management module for Expo Finance Kit * Handles fetching and managing financial transaction data */ import ExpoFinanceKit from '../ExpoFinanceKitModule'; import { TransactionStatus, CreditDebitIndicator, FinanceKitErrorCode } from '../ExpoFinanceKit.types'; import { ensureAuthorized } from '../helpers'; import { validateTransactionQueryOptions, transformTransaction, normalizeTransactionAmount } from '../utils/validators'; import { createFinanceKitError } from '../utils/errors'; import { getAccountById } from './accounts'; /** * Fetches transactions based on query options * @param options - Query options for filtering transactions * @returns Promise resolving to array of transactions */ export async function getTransactions(options = {}) { const isAuthorized = await ensureAuthorized(); if (!isAuthorized) { throw createFinanceKitError(FinanceKitErrorCode.Unauthorized, 'User has not authorized access to financial data'); } validateTransactionQueryOptions(options); try { // Convert dates to timestamps const startDate = options.startDate ? (options.startDate instanceof Date ? options.startDate.getTime() : options.startDate) : undefined; const endDate = options.endDate ? (options.endDate instanceof Date ? options.endDate.getTime() : options.endDate) : undefined; // Fetch transactions from native module const transactions = await ExpoFinanceKit.getTransactions(options.accountId || undefined, startDate, endDate); // Transform and apply additional filters let filteredTransactions = transactions.map(transformTransaction); // Apply additional filters not supported natively if (options.minAmount !== undefined) { filteredTransactions = filteredTransactions.filter((t) => t.amount >= options.minAmount); } if (options.maxAmount !== undefined) { filteredTransactions = filteredTransactions.filter((t) => t.amount <= options.maxAmount); } if (options.merchantName) { const searchTerm = options.merchantName.toLowerCase(); filteredTransactions = filteredTransactions.filter((t) => t.merchantName?.toLowerCase().includes(searchTerm) || t.transactionDescription.toLowerCase().includes(searchTerm)); } if (options.transactionTypes && options.transactionTypes.length > 0) { filteredTransactions = filteredTransactions.filter((t) => options.transactionTypes.includes(t.transactionType)); } if (options.statuses && options.statuses.length > 0) { filteredTransactions = filteredTransactions.filter((t) => options.statuses.includes(t.status)); } if (options.creditDebitIndicator) { filteredTransactions = filteredTransactions.filter((t) => t.creditDebitIndicator === options.creditDebitIndicator); } if (options.merchantCategoryCodes && options.merchantCategoryCodes.length > 0) { filteredTransactions = filteredTransactions.filter((t) => t.merchantCategoryCode && options.merchantCategoryCodes.includes(t.merchantCategoryCode)); } // Apply sorting if (options.sortBy) { filteredTransactions.sort((a, b) => { let compareValue = 0; switch (options.sortBy) { case 'date': compareValue = a.transactionDate - b.transactionDate; break; case 'amount': compareValue = a.amount - b.amount; break; case 'merchantName': compareValue = (a.merchantName || '').localeCompare(b.merchantName || ''); break; } return options.sortOrder === 'desc' ? -compareValue : compareValue; }); } // Apply pagination if (options.offset !== undefined || options.limit !== undefined) { const offset = options.offset || 0; const limit = options.limit || filteredTransactions.length; filteredTransactions = filteredTransactions.slice(offset, offset + limit); } return filteredTransactions; } catch (error) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, 'Failed to fetch transactions', { originalError: error }); } } /** * Fetches transactions for a specific account * @param accountId - The account ID to fetch transactions for * @param options - Additional query options * @returns Promise resolving to array of transactions */ export async function getTransactionsByAccount(accountId, options = {}) { if (!accountId || typeof accountId !== 'string') { throw createFinanceKitError(FinanceKitErrorCode.InvalidAccountId, 'Invalid account ID provided'); } return getTransactions({ ...options, accountId }); } /** * Fetches recent transactions (last 30 days) * @param limit - Maximum number of transactions to return * @returns Promise resolving to array of recent transactions */ export async function getRecentTransactions(limit = 50) { const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); return getTransactions({ startDate: thirtyDaysAgo, limit, sortBy: 'date', sortOrder: 'desc', }); } /** * Fetches transactions for a specific date range * @param startDate - Start date for the range * @param endDate - End date for the range * @returns Promise resolving to array of transactions */ export async function getTransactionsByDateRange(startDate, endDate) { if (startDate > endDate) { throw createFinanceKitError(FinanceKitErrorCode.InvalidDateRange, 'Start date must be before end date'); } return getTransactions({ startDate, endDate, sortBy: 'date', sortOrder: 'desc', }); } /** * Fetches all income transactions (credits) * @param options - Additional query options * @returns Promise resolving to array of income transactions */ export async function getIncomeTransactions(options = {}) { return getTransactions({ ...options, creditDebitIndicator: CreditDebitIndicator.Credit, }); } /** * Fetches all expense transactions (debits) * @param options - Additional query options * @returns Promise resolving to array of expense transactions */ export async function getExpenseTransactions(options = {}) { return getTransactions({ ...options, creditDebitIndicator: CreditDebitIndicator.Debit, }); } /** * Fetches pending transactions * @param options - Additional query options * @returns Promise resolving to array of pending transactions */ export async function getPendingTransactions(options = {}) { return getTransactions({ ...options, statuses: [TransactionStatus.Pending], }); } /** * Searches transactions by merchant name or description * @param searchTerm - The search term * @param options - Additional query options * @returns Promise resolving to array of matching transactions */ export async function searchTransactions(searchTerm, options = {}) { if (!searchTerm || searchTerm.trim().length === 0) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, 'Search term cannot be empty'); } return getTransactions({ ...options, merchantName: searchTerm, }); } /** * Groups transactions by date * @param transactions - Array of transactions to group * @returns Map of date strings to transaction arrays */ export function groupTransactionsByDate(transactions) { const grouped = new Map(); transactions.forEach(transaction => { const date = new Date(transaction.transactionDate).toDateString(); const existing = grouped.get(date) || []; grouped.set(date, [...existing, transaction]); }); return grouped; } /** * Normalizes transaction amounts based on their account types * @param transactions - Array of transactions to normalize * @returns Promise resolving to transactions with normalized amounts */ export async function normalizeTransactionAmounts(transactions) { // Create a map to cache account lookups const accountCache = new Map(); // Normalize each transaction const normalizedTransactions = await Promise.all(transactions.map(async (transaction) => { // Check cache first let account = accountCache.get(transaction.accountId); // If not in cache, fetch the account if (account === undefined) { account = await getAccountById(transaction.accountId); accountCache.set(transaction.accountId, account); } // If account not found, return transaction as-is if (!account) { console.warn(`Account not found for transaction ${transaction.id}, using original amount`); return transaction; } // Normalize the amount based on account type const normalizedAmount = normalizeTransactionAmount(transaction.amount, account.accountType, transaction.creditDebitIndicator); return { ...transaction, amount: normalizedAmount }; })); return normalizedTransactions; } /** * Calculates transaction statistics * @param transactions - Array of transactions to analyze * @returns Object containing transaction statistics */ export function calculateTransactionStats(transactions) { const stats = { total: transactions.length, totalAmount: 0, totalIncome: 0, totalExpenses: 0, averageTransaction: 0, largestExpense: 0, largestIncome: 0, }; transactions.forEach(transaction => { stats.totalAmount += transaction.amount; if (transaction.creditDebitIndicator === CreditDebitIndicator.Credit) { stats.totalIncome += transaction.amount; stats.largestIncome = Math.max(stats.largestIncome, transaction.amount); } else { stats.totalExpenses += transaction.amount; stats.largestExpense = Math.max(stats.largestExpense, transaction.amount); } }); stats.averageTransaction = stats.total > 0 ? stats.totalAmount / stats.total : 0; return stats; } //# sourceMappingURL=transactions.js.map