- 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
357 lines
9.5 KiB
TypeScript
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
|
|
);
|
|
}
|
|
} |