From 594c0fd51eca1a0d052356083e17c8df23ee3dfd Mon Sep 17 00:00:00 2001 From: Devon Miller-Junk Date: Tue, 30 Jun 2026 11:13:38 -0400 Subject: [PATCH] feat: Add Origin Filter to Transactions Endpoint, fix date filters --- packages/cli/src/__tests__/cli.test.ts | 16 ++++++++-- .../__tests__/transactions.test.tsx | 1 + .../cli/src/commands/transactions/index.tsx | 2 ++ .../cli/src/commands/transactions/schema.ts | 8 +++++ .../resources/__tests__/transactions.test.ts | 15 +++++++-- packages/sdk/src/resources/interfaces.ts | 3 ++ packages/sdk/src/resources/transactions.ts | 32 +++++++++++++++++-- packages/sdk/src/types/index.ts | 3 ++ 8 files changed, 73 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/__tests__/cli.test.ts b/packages/cli/src/__tests__/cli.test.ts index 7a2e93b..ef94f15 100644 --- a/packages/cli/src/__tests__/cli.test.ts +++ b/packages/cli/src/__tests__/cli.test.ts @@ -927,6 +927,7 @@ describe('production mode', () => { currency: 'usd', created_date: '2026-06-08', description: 'Chase', + origin: 'external_connection', category: 'credit_card_payment', status: 'succeeded', }; @@ -976,6 +977,12 @@ describe('production mode', () => { '2026-04-30', '--category', 'other_services', + '--origin', + 'external_connection', + '--source', + 'csmrpd_a', + '--source', + 'csmrpd_b', '--json', ); @@ -984,9 +991,14 @@ describe('production mode', () => { expect(lastRequest.url).toContain('limit=5'); expect(lastRequest.url).toContain('starting_after=lbctxn_cursor'); expect(lastRequest.url).toContain('ending_before=lbctxn_prev'); - expect(lastRequest.url).toContain('start_date=2026-04-01'); - expect(lastRequest.url).toContain('end_date=2026-04-30'); + expect(lastRequest.url).toContain('date_start=2026-04-01'); + expect(lastRequest.url).toContain('date_end=2026-04-30'); expect(lastRequest.url).toContain('category=other_services'); + expect(lastRequest.url).toContain('origin=external_connection'); + expect(lastRequest.url).toContain('sources%5B%5D=csmrpd_a'); + expect(lastRequest.url).toContain('sources%5B%5D=csmrpd_b'); + expect(lastRequest.url).not.toContain('start_date'); + expect(lastRequest.url).not.toContain('end_date'); expect(lastRequest.url).not.toContain('transaction_category'); }); diff --git a/packages/cli/src/commands/transactions/__tests__/transactions.test.tsx b/packages/cli/src/commands/transactions/__tests__/transactions.test.tsx index eccc1e2..ea09667 100644 --- a/packages/cli/src/commands/transactions/__tests__/transactions.test.tsx +++ b/packages/cli/src/commands/transactions/__tests__/transactions.test.tsx @@ -25,6 +25,7 @@ function transaction(overrides: Partial = {}): Transaction { currency: 'usd', created_date: '2026-06-08', description: 'Chase', + origin: 'external_connection', category: 'credit_card_payment', status: 'succeeded', ...overrides, diff --git a/packages/cli/src/commands/transactions/index.tsx b/packages/cli/src/commands/transactions/index.tsx index bd4eb66..809a7ee 100644 --- a/packages/cli/src/commands/transactions/index.tsx +++ b/packages/cli/src/commands/transactions/index.tsx @@ -38,6 +38,8 @@ export function createTransactionsCli( if (opts.startDate !== undefined) params.start_date = opts.startDate; if (opts.endDate !== undefined) params.end_date = opts.endDate; if (opts.category !== undefined) params.category = opts.category; + if (opts.origin !== undefined) params.origin = opts.origin; + if (opts.source.length > 0) params.sources = opts.source; if (!c.agent && !c.formatExplicit) { return renderInteractive( diff --git a/packages/cli/src/commands/transactions/schema.ts b/packages/cli/src/commands/transactions/schema.ts index fda46d2..5d266f3 100644 --- a/packages/cli/src/commands/transactions/schema.ts +++ b/packages/cli/src/commands/transactions/schema.ts @@ -29,4 +29,12 @@ export const listOptions = z.object({ .optional() .describe('Only include transactions on or before this YYYY-MM-DD date.'), category: z.string().optional().describe('Filter by transaction category.'), + origin: z + .enum(['link', 'external_connection']) + .optional() + .describe('Filter by transaction origin: link or external_connection.'), + source: z + .array(z.string()) + .default([]) + .describe('Filter by source ID. Repeat to include multiple sources.'), }); diff --git a/packages/sdk/src/resources/__tests__/transactions.test.ts b/packages/sdk/src/resources/__tests__/transactions.test.ts index c94bddb..6cd26da 100644 --- a/packages/sdk/src/resources/__tests__/transactions.test.ts +++ b/packages/sdk/src/resources/__tests__/transactions.test.ts @@ -39,6 +39,7 @@ describe('TransactionsResource', () => { currency: 'usd', created_date: '2026-06-08', description: 'Chase', + origin: 'external_connection', category: 'credit_card_payment', status: 'succeeded', }, @@ -63,6 +64,7 @@ describe('TransactionsResource', () => { currency: 'usd', created_date: '2026-06-08', description: 'Chase', + origin: 'external_connection', category: 'credit_card_payment', status: 'succeeded', }, @@ -81,15 +83,24 @@ describe('TransactionsResource', () => { start_date: '2026-06-08', end_date: '2026-06-09', category: 'shopping', + origin: 'link', + sources: ['csmrpd_a', 'csmrpd_b'], }); const url = new URL(mockFetch.mock.calls[0][0]); expect(url.searchParams.get('limit')).toBe('50'); expect(url.searchParams.get('starting_after')).toBe('cursor_a'); expect(url.searchParams.get('ending_before')).toBe('cursor_b'); - expect(url.searchParams.get('start_date')).toBe('2026-06-08'); - expect(url.searchParams.get('end_date')).toBe('2026-06-09'); + expect(url.searchParams.get('date_start')).toBe('2026-06-08'); + expect(url.searchParams.get('date_end')).toBe('2026-06-09'); expect(url.searchParams.get('category')).toBe('shopping'); + expect(url.searchParams.get('origin')).toBe('link'); + expect(url.searchParams.getAll('sources[]')).toEqual([ + 'csmrpd_a', + 'csmrpd_b', + ]); + expect(url.searchParams.has('start_date')).toBe(false); + expect(url.searchParams.has('end_date')).toBe(false); expect(url.searchParams.has('transaction_category')).toBe(false); }); diff --git a/packages/sdk/src/resources/interfaces.ts b/packages/sdk/src/resources/interfaces.ts index df6a3dc..0628fdb 100644 --- a/packages/sdk/src/resources/interfaces.ts +++ b/packages/sdk/src/resources/interfaces.ts @@ -8,6 +8,7 @@ import type { ShippingAddressRecord, SpendRequest, Total, + TransactionOrigin, TransactionsPage, UserInfo, WebBotAuthBlock, @@ -105,6 +106,8 @@ export interface ListTransactionsParams { start_date?: string; end_date?: string; category?: string; + origin?: TransactionOrigin; + sources?: string[]; } export interface ITransactionsResource { diff --git a/packages/sdk/src/resources/transactions.ts b/packages/sdk/src/resources/transactions.ts index c94d0a8..70c2e86 100644 --- a/packages/sdk/src/resources/transactions.ts +++ b/packages/sdk/src/resources/transactions.ts @@ -9,7 +9,11 @@ import type { ITransactionsResource, ListTransactionsParams, } from '@/resources/interfaces'; -import type { Transaction, TransactionsPage } from '@/types/index'; +import type { + Transaction, + TransactionOrigin, + TransactionsPage, +} from '@/types/index'; interface ApiFetchOptions { method: string; @@ -52,6 +56,16 @@ function requireBoolean(value: unknown, field: string): boolean { return value; } +function requireTransactionOrigin( + value: unknown, + field: string, +): TransactionOrigin { + if (value === 'link' || value === 'external_connection') { + return value; + } + throw new TypeError(`Expected ${field} to be a transaction origin`); +} + function normalizeTransactions(value: unknown): Transaction[] { if (!Array.isArray(value)) { throw new TypeError('Expected transactions to be an array'); @@ -78,6 +92,10 @@ function normalizeTransactions(value: unknown): Transaction[] { item.description, `transactions[${index}].description`, ), + origin: requireTransactionOrigin( + item.origin, + `transactions[${index}].origin`, + ), category: requireNullableString( item.category, `transactions[${index}].category`, @@ -203,14 +221,22 @@ export class TransactionsResource implements ITransactionsResource { url.searchParams.set('ending_before', params.ending_before); } if (params.start_date !== undefined) { - url.searchParams.set('start_date', params.start_date); + url.searchParams.set('date_start', params.start_date); } if (params.end_date !== undefined) { - url.searchParams.set('end_date', params.end_date); + url.searchParams.set('date_end', params.end_date); } if (params.category !== undefined) { url.searchParams.set('category', params.category); } + if (params.origin !== undefined) { + url.searchParams.set('origin', params.origin); + } + if (params.sources !== undefined) { + for (const source of params.sources) { + url.searchParams.append('sources[]', source); + } + } return url.toString(); } diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index dd0feb8..83fddbc 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -172,6 +172,8 @@ export interface ShippingAddressRecord { address: ShippingAddress | null; } +export type TransactionOrigin = 'link' | 'external_connection'; + export interface Transaction { id: string; source_id: string | null; @@ -179,6 +181,7 @@ export interface Transaction { currency: string; created_date: string; description: string; + origin: TransactionOrigin; category: string | null; status: string; }