Files
bunq/ts/bunq.classes.export.ts
Juergen Kunz 40f9142d70 feat(export): add buffer download methods to ExportBuilder
- Added download() method to get statements as Buffer without saving to disk
- Added downloadAsArrayBuffer() method for web API compatibility
- Enhanced documentation for getAccountStatement() method
- Updated README with comprehensive examples
- No breaking changes, backward compatible
2025-08-02 10:56:17 +00:00

357 lines
9.5 KiB
TypeScript

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<number> {
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<any> {
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<void> {
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<any[]> {
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<Buffer> {
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<void> {
const content = await this.downloadContent();
await plugins.smartfile.memory.toFs(content, filePath);
}
/**
* Wait for export to complete
*/
public async waitForCompletion(maxWaitMs: number = 60000): Promise<void> {
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<void> {
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<BunqExport> {
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<void> {
const bunqExport = await this.create();
await bunqExport.waitForCompletion();
await bunqExport.saveToFile(filePath);
}
/**
* Create and download export as Buffer
*/
public async download(): Promise<Buffer> {
const bunqExport = await this.create();
await bunqExport.waitForCompletion();
return bunqExport.downloadContent();
}
/**
* Create and download export as ArrayBuffer
*/
public async downloadAsArrayBuffer(): Promise<ArrayBuffer> {
const buffer = await this.download();
return buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength
);
}
}