UNPKG

expo-finance-kit

Version:

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

310 lines 11.4 kB
/** * Validation utilities for Expo Finance Kit * Ensures data integrity and type safety */ import { FinanceKitErrorCode, TransactionStatus, AccountType, CreditDebitIndicator, TransactionType, } from '../ExpoFinanceKit.types'; import { createFinanceKitError } from './errors'; /** * Validates account query options * @param options - Options to validate * @throws {FinanceKitError} If validation fails */ export function validateAccountQueryOptions(options) { if (options.accountTypes) { options.accountTypes.forEach(type => { if (!Object.values(AccountType).includes(type)) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, `Invalid account type: ${type}`); } }); } if (options.currencyCodes) { options.currencyCodes.forEach(code => { if (typeof code !== 'string' || code.length !== 3) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, `Invalid currency code: ${code}. Must be a 3-letter ISO code.`); } }); } } /** * Validates transaction query options * @param options - Options to validate * @throws {FinanceKitError} If validation fails */ export function validateTransactionQueryOptions(options) { if (options.startDate && options.endDate) { const start = options.startDate instanceof Date ? options.startDate : new Date(options.startDate); const end = options.endDate instanceof Date ? options.endDate : new Date(options.endDate); if (start > end) { throw createFinanceKitError(FinanceKitErrorCode.InvalidDateRange, 'Start date must be before end date'); } } if (options.minAmount !== undefined && options.maxAmount !== undefined) { if (options.minAmount > options.maxAmount) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, 'Minimum amount must be less than maximum amount'); } } if (options.limit !== undefined) { if (options.limit <= 0 || !Number.isInteger(options.limit)) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, 'Limit must be a positive integer'); } } if (options.offset !== undefined) { if (options.offset < 0 || !Number.isInteger(options.offset)) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, 'Offset must be a non-negative integer'); } } if (options.transactionTypes) { options.transactionTypes.forEach(type => { if (!Object.values(TransactionType).includes(type)) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, `Invalid transaction type: ${type}`); } }); } if (options.statuses) { options.statuses.forEach(status => { if (!Object.values(TransactionStatus).includes(status)) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, `Invalid transaction status: ${status}`); } }); } if (options.creditDebitIndicator) { if (!Object.values(CreditDebitIndicator).includes(options.creditDebitIndicator)) { throw createFinanceKitError(FinanceKitErrorCode.Unknown, `Invalid credit/debit indicator: ${options.creditDebitIndicator}`); } } } /** * Validates balance query options * @param options - Options to validate * @throws {FinanceKitError} If validation fails */ export function validateBalanceQueryOptions(options) { if (options.accountIds) { options.accountIds.forEach(id => { if (typeof id !== 'string' || id.trim().length === 0) { throw createFinanceKitError(FinanceKitErrorCode.InvalidAccountId, 'Account ID must be a non-empty string'); } }); } } /** * Transforms raw account data from native module * @param rawAccount - Raw account data * @returns Transformed account object */ export function transformAccount(rawAccount) { return { id: rawAccount.id, institutionName: rawAccount.institutionName, displayName: rawAccount.displayName, accountDescription: rawAccount.accountDescription, currencyCode: rawAccount.currencyCode, accountType: rawAccount.accountType, balance: rawAccount.balance, }; } /** * Transforms raw transaction data from native module * @param rawTransaction - Raw transaction data * @returns Transformed transaction object */ export function transformTransaction(rawTransaction) { // Map transaction status from native format let status; switch (rawTransaction.status) { case 'authorized': status = TransactionStatus.Authorized; break; case 'booked': status = TransactionStatus.Booked; break; case 'pending': status = TransactionStatus.Pending; break; case 'rejected': status = TransactionStatus.Rejected; break; default: status = TransactionStatus.Pending; } // Map transaction type from native format let transactionType; switch (rawTransaction.transactionType) { case 'adjustment': transactionType = TransactionType.Adjustment; break; case 'atm': transactionType = TransactionType.ATM; break; case 'billPayment': transactionType = TransactionType.BillPayment; break; case 'check': transactionType = TransactionType.Check; break; case 'deposit': transactionType = TransactionType.Deposit; break; case 'directDebit': transactionType = TransactionType.DirectDebit; break; case 'directDeposit': transactionType = TransactionType.DirectDeposit; break; case 'dividend': transactionType = TransactionType.Dividend; break; case 'fee': transactionType = TransactionType.Fee; break; case 'interest': transactionType = TransactionType.Interest; break; case 'loan': transactionType = TransactionType.Loan; break; case 'pointOfSale': transactionType = TransactionType.PointOfSale; break; case 'refund': transactionType = TransactionType.Refund; break; case 'standingOrder': transactionType = TransactionType.StandingOrder; break; case 'transfer': transactionType = TransactionType.Transfer; break; case 'withdrawal': transactionType = TransactionType.Withdrawal; break; default: transactionType = TransactionType.Unknown; } return { id: rawTransaction.id, accountId: rawTransaction.accountId, amount: rawTransaction.amount, // Keep original amount sign currencyCode: rawTransaction.currencyCode, transactionDate: rawTransaction.transactionDate, merchantName: rawTransaction.merchantName, transactionDescription: rawTransaction.transactionDescription, merchantCategoryCode: rawTransaction.merchantCategoryCode ? parseInt(rawTransaction.merchantCategoryCode) : undefined, status, transactionType, creditDebitIndicator: rawTransaction.creditDebitIndicator, }; } /** * Normalizes transaction amount based on account type and credit/debit indicator * @param amount - The raw transaction amount * @param accountType - The type of account (asset or liability) * @param creditDebitIndicator - Whether the transaction is a credit or debit * @returns Normalized amount (positive for increases in value, negative for decreases) */ export function normalizeTransactionAmount(amount, accountType, creditDebitIndicator) { // For asset accounts: // - Credits (deposits) are positive (increase asset value) // - Debits (withdrawals) are negative (decrease asset value) // // For liability accounts (e.g., credit cards): // - Credits (payments) are negative (decrease debt) // - Debits (charges) are positive (increase debt) const absAmount = Math.abs(amount); if (accountType === AccountType.Asset) { return creditDebitIndicator === CreditDebitIndicator.Credit ? absAmount : -absAmount; } else { // Liability account return creditDebitIndicator === CreditDebitIndicator.Debit ? absAmount : -absAmount; } } /** * Transforms raw balance data from native module * @param rawBalance - Raw balance data * @returns Transformed balance object */ export function transformBalance(rawBalance) { return { id: rawBalance.id, accountId: rawBalance.accountId, amount: rawBalance.amount, currencyCode: rawBalance.currencyCode, }; } /** * Validates a currency code * @param code - Currency code to validate * @returns Boolean indicating validity */ export function isValidCurrencyCode(code) { return typeof code === 'string' && /^[A-Z]{3}$/.test(code); } /** * Validates an account ID (UUID format) * @param id - ID to validate * @returns Boolean indicating validity */ export function isValidAccountId(id) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; return typeof id === 'string' && uuidRegex.test(id); } /** * Sanitizes transaction description * @param description - Description to sanitize * @returns Sanitized description */ export function sanitizeTransactionDescription(description) { if (!description || typeof description !== 'string') { return ''; } // Remove excessive whitespace and trim return description.replace(/\s+/g, ' ').trim(); } /** * Formats amount for display * @param amount - Amount to format * @param currencyCode - Currency code * @returns Formatted amount string */ export function formatAmount(amount, currencyCode) { const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: currencyCode, minimumFractionDigits: 2, maximumFractionDigits: 2, }); return formatter.format(amount); } /** * Type guard for checking if a value is a valid AuthorizationStatus */ export const isAuthorizationStatus = (value) => { return ['notDetermined', 'denied', 'authorized', 'unavailable'].includes(value); }; /** * Type guard for checking if a value is a valid Transaction */ export const isTransaction = (value) => { return (typeof value === 'object' && value !== null && typeof value.id === 'string' && typeof value.accountId === 'string' && typeof value.amount === 'number' && typeof value.currencyCode === 'string' && typeof value.transactionDate === 'number'); }; /** * Type guard for checking if a value is a valid Account */ export const isAccount = (value) => { return (typeof value === 'object' && value !== null && typeof value.id === 'string' && typeof value.institutionName === 'string' && typeof value.displayName === 'string' && typeof value.currencyCode === 'string' && Object.values(AccountType).includes(value.accountType)); }; //# sourceMappingURL=validators.js.map