Files
bunq/ts/bunq.classes.export.ts
Juergen Kunz 596efa3f06 update
2025-07-18 10:43:39 +00:00

317 lines
8.1 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');
}
// First get the export details to find the attachment
const exportDetails = await this.get();
if (!exportDetails.attachment || exportDetails.attachment.length === 0) {
throw new Error('Export has no attachment');
}
const attachmentUuid = exportDetails.attachment[0].attachment_public_uuid;
// Download the attachment content
const response = await plugins.smartrequest.request(
`${this.bunqAccount.apiContext.getBaseUrl()}/v1/attachment-public/${attachmentUuid}/content`,
{
method: 'GET',
headers: {
'X-Bunq-Client-Authentication': this.bunqAccount.apiContext.getSession().getContext().sessionToken
}
}
);
return Buffer.from(response.body);
}
/**
* 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 === 'COMPLETE') {
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);
this.options.dateStart = startDate.toISOString().split('T')[0];
this.options.dateEnd = endDate.toISOString().split('T')[0];
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);
this.options.dateStart = startDate.toISOString().split('T')[0];
this.options.dateEnd = endDate.toISOString().split('T')[0];
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);
}
}