Files
bunq/ts/bunq.classes.notification.ts
Juergen Kunz 193524f15c update
2025-07-18 10:31:12 +00:00

314 lines
8.4 KiB
TypeScript

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<number> {
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<number> {
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<any[]> {
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<any[]> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<string, Function> = 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<void> {
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);
}
}