import {
accessTokenForPreview,
isoDate,
mutationPreview,
normalizeAccount,
normalizeTransaction,
plaidRequest,
type MutationGuardInput,
type PlaidAuthInput,
} from './plaid-core.ts'
export type SyncTransactionsInput = PlaidAuthInput & {
cursor?: string
count?: number
}
/**
* Incremental transaction updates (`/transactions/sync`). Pass `cursor` from
* the previous `next_cursor` to page.
*/
export async function syncTransactions(input: SyncTransactionsInput = {}) {
const count = input.count == null ? 100 : Number(input.count)
if (!Number.isFinite(count) || count < 1 || count > 500) {
throw new Error('count must be between 1 and 500.')
}
const data = await plaidRequest({
...input,
path: '/transactions/sync',
includeAccessToken: true,
body: {
...(input.cursor ? { cursor: input.cursor } : {}),
count,
},
})
const added = Array.isArray(data?.added) ? data.added.map(normalizeTransaction) : []
const modified = Array.isArray(data?.modified) ? data.modified.map(normalizeTransaction) : []
const removed = Array.isArray(data?.removed)
? data.removed.map((entry: any) => ({ transaction_id: entry?.transaction_id ?? null }))
: []
return {
added,
modified,
removed,
next_cursor: data?.next_cursor ?? null,
has_more: Boolean(data?.has_more),
accounts: Array.isArray(data?.accounts) ? data.accounts.map(normalizeAccount) : [],
request_id: data?.request_id ?? null,
}
}
export type ListTransactionsInput = PlaidAuthInput & {
startDate?: string | Date
endDate?: string | Date
accountIds?: string[]
count?: number
offset?: number
}
/**
* Date-window transactions (`/transactions/get`). Prefer `syncTransactions`
* for ongoing updates.
*/
export async function listTransactions(input: ListTransactionsInput = {}) {
const startDate = isoDate(input.startDate, 'startDate')
const endDate = isoDate(input.endDate, 'endDate')
if (!startDate || !endDate) {
throw new Error('listTransactions requires startDate and endDate (YYYY-MM-DD).')
}
const count = input.count == null ? 100 : Number(input.count)
if (!Number.isFinite(count) || count < 1 || count > 500) {
throw new Error('count must be between 1 and 500.')
}
const offset = input.offset == null ? 0 : Number(input.offset)
if (!Number.isFinite(offset) || offset < 0) {
throw new Error('offset must be a non-negative number.')
}
const data = await plaidRequest({
...input,
path: '/transactions/get',
includeAccessToken: true,
body: {
start_date: startDate,
end_date: endDate,
options: {
count,
offset,
...(input.accountIds?.length ? { account_ids: input.accountIds } : {}),
},
},
})
return {
transactions: Array.isArray(data?.transactions) ? data.transactions.map(normalizeTransaction) : [],
accounts: Array.isArray(data?.accounts) ? data.accounts.map(normalizeAccount) : [],
total_transactions: typeof data?.total_transactions === 'number' ? data.total_transactions : null,
request_id: data?.request_id ?? null,
}
}
export type RefreshTransactionsInput = MutationGuardInput
/**
* Ask Plaid to check for new transactions (`/transactions/refresh`).
* Defaults to dry-run; live refresh needs `confirm: true`.
*/
export async function refreshTransactions(input: RefreshTransactionsInput = {}) {
const preview = mutationPreview(input, {
action: 'refresh transactions',
method: 'POST',
path: '/transactions/refresh',
body: { access_token: accessTokenForPreview(input) },
})
if (preview) return preview
const data = await plaidRequest({
...input,
path: '/transactions/refresh',
includeAccessToken: true,
})
return {
request_id: data?.request_id ?? null,
}
}
export default syncTransactions