import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js'; export type TExportFormat = 'CSV' | 'PDF' | 'MT940'; export class BunqExport { private bunqAccount: BunqAccount; private monetaryAccount: BunqMonetaryAccount; public id?: number; public created?: string; public updated?: string; public status?: string; constructor(bunqAccount: BunqAccount, monetaryAccount: BunqMonetaryAccount) { this.bunqAccount = bunqAccount; this.monetaryAccount = monetaryAccount; } /** * Create a new export */ public async create(options: { statementFormat: TExportFormat; dateStart: string; dateEnd: string; regionalFormat?: 'EUROPEAN' | 'UK_US'; includeAttachment?: boolean; }): Promise { await this.bunqAccount.apiContext.ensureValidSession(); const response = await this.bunqAccount.getHttpClient().post( `/v1/user/${this.bunqAccount.userId}/monetary-account/${this.monetaryAccount.id}/customer-statement`, { statement_format: options.statementFormat, date_start: options.dateStart, date_end: options.dateEnd, regional_format: options.regionalFormat || 'EUROPEAN', include_attachment: options.includeAttachment || false } ); if (response.Response && response.Response[0] && response.Response[0].Id) { this.id = response.Response[0].Id.id; return this.id; } throw new Error('Failed to create export'); } /** * Get export details */ public async get(): Promise { if (!this.id) { throw new Error('Export ID not set'); } await this.bunqAccount.apiContext.ensureValidSession(); const response = await this.bunqAccount.getHttpClient().get( `/v1/user/${this.bunqAccount.userId}/monetary-account/${this.monetaryAccount.id}/customer-statement/${this.id}` ); if (response.Response && response.Response[0]) { const data = response.Response[0].CustomerStatement; this.status = data.status; return data; } throw new Error('Export not found'); } /** * Delete export */ public async delete(): Promise { if (!this.id) { throw new Error('Export ID not set'); } await this.bunqAccount.apiContext.ensureValidSession(); await this.bunqAccount.getHttpClient().delete( `/v1/user/${this.bunqAccount.userId}/monetary-account/${this.monetaryAccount.id}/customer-statement/${this.id}` ); } /** * List exports */ public static async list( bunqAccount: BunqAccount, monetaryAccountId: number ): Promise { await bunqAccount.apiContext.ensureValidSession(); const response = await bunqAccount.getHttpClient().list( `/v1/user/${bunqAccount.userId}/monetary-account/${monetaryAccountId}/customer-statement` ); return response.Response || []; } /** * Download the export content */ public async downloadContent(): Promise { if (!this.id) { throw new Error('Export ID not set'); } // Ensure the export is complete before downloading const status = await this.get(); if (status.status !== 'COMPLETED') { throw new Error(`Export is not ready for download. Status: ${status.status}`); } // For PDF statements, use the /content endpoint directly const downloadUrl = `${this.bunqAccount.apiContext.getBaseUrl()}/v1/user/${this.bunqAccount.userId}/monetary-account/${this.monetaryAccount.id}/customer-statement/${this.id}/content`; const response = await fetch(downloadUrl, { method: 'GET', headers: { 'X-Bunq-Client-Authentication': this.bunqAccount.apiContext.getSession().getContext().sessionToken, 'User-Agent': 'bunq-api-client/1.0.0', 'Cache-Control': 'no-cache' } }); if (!response.ok) { const responseText = await response.text(); throw new Error(`Failed to download export: HTTP ${response.status} - ${responseText}`); } const arrayBuffer = await response.arrayBuffer(); return Buffer.from(arrayBuffer); } /** * Save export to file */ public async saveToFile(filePath: string): Promise { const content = await this.downloadContent(); await plugins.smartfile.memory.toFs(content, filePath); } /** * Wait for export to complete */ public async waitForCompletion(maxWaitMs: number = 60000): Promise { const startTime = Date.now(); while (true) { const details = await this.get(); if (details.status === 'COMPLETED') { return; } if (details.status === 'FAILED') { throw new Error('Export failed'); } if (Date.now() - startTime > maxWaitMs) { throw new Error('Export timed out'); } // Wait 2 seconds before checking again await new Promise(resolve => setTimeout(resolve, 2000)); } } /** * Create and download export in one go */ public static async createAndDownload( bunqAccount: BunqAccount, monetaryAccount: BunqMonetaryAccount, options: { statementFormat: TExportFormat; dateStart: string; dateEnd: string; regionalFormat?: 'EUROPEAN' | 'UK_US'; includeAttachment?: boolean; outputPath: string; } ): Promise { const bunqExport = new BunqExport(bunqAccount, monetaryAccount); // Create export await bunqExport.create({ statementFormat: options.statementFormat, dateStart: options.dateStart, dateEnd: options.dateEnd, regionalFormat: options.regionalFormat, includeAttachment: options.includeAttachment }); // Wait for completion await bunqExport.waitForCompletion(); // Save to file await bunqExport.saveToFile(options.outputPath); } } /** * Export builder for easier export creation */ export class ExportBuilder { private bunqAccount: BunqAccount; private monetaryAccount: BunqMonetaryAccount; private options: any = {}; constructor(bunqAccount: BunqAccount, monetaryAccount: BunqMonetaryAccount) { this.bunqAccount = bunqAccount; this.monetaryAccount = monetaryAccount; } /** * Set format to CSV */ public asCsv(): this { this.options.statementFormat = 'CSV'; return this; } /** * Set format to PDF */ public asPdf(): this { this.options.statementFormat = 'PDF'; return this; } /** * Set format to MT940 */ public asMt940(): this { this.options.statementFormat = 'MT940'; return this; } /** * Set date range */ public dateRange(startDate: string, endDate: string): this { this.options.dateStart = startDate; this.options.dateEnd = endDate; return this; } /** * Set last N days */ public lastDays(days: number): this { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); // Format as DD-MM-YYYY for bunq API const formatDate = (date: Date): string => { const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}-${month}-${year}`; }; this.options.dateStart = formatDate(startDate); this.options.dateEnd = formatDate(endDate); return this; } /** * Set last month */ public lastMonth(): this { const now = new Date(); const startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1); const endDate = new Date(now.getFullYear(), now.getMonth(), 0); // Format as DD-MM-YYYY for bunq API const formatDate = (date: Date): string => { const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}-${month}-${year}`; }; this.options.dateStart = formatDate(startDate); this.options.dateEnd = formatDate(endDate); return this; } /** * Set regional format */ public regionalFormat(format: 'EUROPEAN' | 'UK_US'): this { this.options.regionalFormat = format; return this; } /** * Include attachments */ public includeAttachments(include: boolean = true): this { this.options.includeAttachment = include; return this; } /** * Create the export */ public async create(): Promise { if (!this.options.statementFormat) { throw new Error('Export format is required'); } if (!this.options.dateStart || !this.options.dateEnd) { throw new Error('Date range is required'); } const bunqExport = new BunqExport(this.bunqAccount, this.monetaryAccount); await bunqExport.create(this.options); return bunqExport; } /** * Create and download to file */ public async downloadTo(filePath: string): Promise { const bunqExport = await this.create(); await bunqExport.waitForCompletion(); await bunqExport.saveToFile(filePath); } /** * Create and download export as Buffer */ public async download(): Promise { const bunqExport = await this.create(); await bunqExport.waitForCompletion(); return bunqExport.downloadContent(); } /** * Create and download export as ArrayBuffer */ public async downloadAsArrayBuffer(): Promise { const buffer = await this.download(); return buffer.buffer.slice( buffer.byteOffset, buffer.byteOffset + buffer.byteLength ); } }