314 lines
8.4 KiB
TypeScript
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);
|
|
}
|
|
} |