import * as plugins from './bunq.plugins'; import { BunqAccount } from './bunq.classes.account'; import { IBunqNotificationFilter } from './bunq.interfaces'; export class BunqNotification { private bunqAccount: BunqAccount; constructor(bunqAccount: BunqAccount) { this.bunqAccount = bunqAccount; } /** * Create notification filter for URL callbacks */ public async createUrlFilter(options: { category: 'BILLING' | 'CARD' | 'CHAT' | 'DRAFT_PAYMENT' | 'IDEAL' | 'MASTERCARD' | 'MONETARY_ACCOUNT' | 'PAYMENT' | 'REQUEST' | 'SCHEDULE_RESULT' | 'SCHEDULE_STATUS' | 'SHARE' | 'TAB_RESULT' | 'USER' | 'FINANCIAL_INSTITUTION' | 'WHITELIST' | 'WHITELIST_RESULT' | 'REQUEST_INQUIRY' | 'REQUEST_INQUIRY_CHAT' | 'REQUEST_RESPONSE' | 'SOFORT' | 'BUNQME_TAB' | 'SUPPORT_CONVERSATION' | 'SLICE_REGISTRY_ENTRY'; notificationTarget: string; }): Promise { await this.bunqAccount.apiContext.ensureValidSession(); const response = await this.bunqAccount.getHttpClient().post( `/v1/user/${this.bunqAccount.userId}/notification-filter-url`, { notification_filters: [{ notification_delivery_method: 'URL', notification_target: options.notificationTarget, category: options.category }] } ); if (response.Response && response.Response[0] && response.Response[0].Id) { return response.Response[0].Id.id; } throw new Error('Failed to create notification filter'); } /** * Create notification filter for push notifications */ public async createPushFilter(options: { category: string; }): Promise { await this.bunqAccount.apiContext.ensureValidSession(); const response = await this.bunqAccount.getHttpClient().post( `/v1/user/${this.bunqAccount.userId}/notification-filter-push`, { notification_filters: [{ notification_delivery_method: 'PUSH', category: options.category }] } ); if (response.Response && response.Response[0] && response.Response[0].Id) { return response.Response[0].Id.id; } throw new Error('Failed to create push notification filter'); } /** * List URL notification filters */ public async listUrlFilters(): Promise { await this.bunqAccount.apiContext.ensureValidSession(); const response = await this.bunqAccount.getHttpClient().list( `/v1/user/${this.bunqAccount.userId}/notification-filter-url` ); return response.Response || []; } /** * List push notification filters */ public async listPushFilters(): Promise { await this.bunqAccount.apiContext.ensureValidSession(); const response = await this.bunqAccount.getHttpClient().list( `/v1/user/${this.bunqAccount.userId}/notification-filter-push` ); return response.Response || []; } /** * Delete URL notification filter */ public async deleteUrlFilter(filterId: number): Promise { await this.bunqAccount.apiContext.ensureValidSession(); await this.bunqAccount.getHttpClient().delete( `/v1/user/${this.bunqAccount.userId}/notification-filter-url/${filterId}` ); } /** * Delete push notification filter */ public async deletePushFilter(filterId: number): Promise { await this.bunqAccount.apiContext.ensureValidSession(); await this.bunqAccount.getHttpClient().delete( `/v1/user/${this.bunqAccount.userId}/notification-filter-push/${filterId}` ); } /** * Clear all URL notification filters */ public async clearAllUrlFilters(): Promise { await this.bunqAccount.apiContext.ensureValidSession(); await this.bunqAccount.getHttpClient().delete( `/v1/user/${this.bunqAccount.userId}/notification-filter-url` ); } /** * Clear all push notification filters */ public async clearAllPushFilters(): Promise { await this.bunqAccount.apiContext.ensureValidSession(); await this.bunqAccount.getHttpClient().delete( `/v1/user/${this.bunqAccount.userId}/notification-filter-push` ); } /** * Create multiple notification filters at once */ public async createMultipleUrlFilters(filters: Array<{ category: string; notificationTarget: string; }>): Promise { await this.bunqAccount.apiContext.ensureValidSession(); const notificationFilters = filters.map(filter => ({ notification_delivery_method: 'URL' as const, notification_target: filter.notificationTarget, category: filter.category })); await this.bunqAccount.getHttpClient().post( `/v1/user/${this.bunqAccount.userId}/notification-filter-url`, { notification_filters: notificationFilters } ); } /** * Setup webhook endpoint for all payment events */ public async setupPaymentWebhook(webhookUrl: string): Promise { const paymentCategories = [ 'PAYMENT', 'DRAFT_PAYMENT', 'SCHEDULE_RESULT', 'REQUEST_INQUIRY', 'REQUEST_RESPONSE', 'MASTERCARD', 'IDEAL', 'SOFORT' ]; const filters = paymentCategories.map(category => ({ category, notificationTarget: webhookUrl })); await this.createMultipleUrlFilters(filters); } /** * Setup webhook endpoint for all account events */ public async setupAccountWebhook(webhookUrl: string): Promise { const accountCategories = [ 'MONETARY_ACCOUNT', 'BILLING', 'USER', 'CARD' ]; const filters = accountCategories.map(category => ({ category, notificationTarget: webhookUrl })); await this.createMultipleUrlFilters(filters); } /** * Verify webhook signature */ public verifyWebhookSignature( body: string, signature: string ): boolean { // Get server public key from context const serverPublicKey = this.bunqAccount.apiContext.getSession().getContext().serverPublicKey; if (!serverPublicKey) { throw new Error('Server public key not available'); } // Verify the signature const verify = plugins.crypto.createVerify('SHA256'); verify.update(body); verify.end(); return verify.verify(serverPublicKey, signature, 'base64'); } } /** * Webhook handler class for processing incoming notifications */ export class BunqWebhookHandler { private handlers: Map = new Map(); /** * Register a handler for a specific event category */ public on(category: string, handler: Function): void { this.handlers.set(category, handler); } /** * Process incoming webhook notification */ public async process(notification: any): Promise { const notificationObject = notification.NotificationUrl; if (!notificationObject) { throw new Error('Invalid notification format'); } const category = notificationObject.category; const handler = this.handlers.get(category); if (handler) { await handler(notificationObject); } // Also check for wildcard handler const wildcardHandler = this.handlers.get('*'); if (wildcardHandler) { await wildcardHandler(notificationObject); } } /** * Register handler for payment events */ public onPayment(handler: (payment: any) => void): void { this.on('PAYMENT', (notification: any) => { if (notification.object && notification.object.Payment) { handler(notification.object.Payment); } }); } /** * Register handler for monetary account events */ public onMonetaryAccount(handler: (account: any) => void): void { this.on('MONETARY_ACCOUNT', (notification: any) => { if (notification.object) { handler(notification.object); } }); } /** * Register handler for card events */ public onCard(handler: (card: any) => void): void { this.on('CARD', (notification: any) => { if (notification.object) { handler(notification.object); } }); } /** * Register handler for request events */ public onRequest(handler: (request: any) => void): void { this.on('REQUEST_INQUIRY', (notification: any) => { if (notification.object && notification.object.RequestInquiry) { handler(notification.object.RequestInquiry); } }); } /** * Register handler for all events */ public onAll(handler: (notification: any) => void): void { this.on('*', handler); } }