BREAKING CHANGE(core): Major restructuring and feature enhancements: added batch payments and scheduled payments with builder patterns, improved webhook management, migrated package naming to @apiclient.xyz/bunq, and updated documentation and tests.
This commit is contained in:
129
changelog.md
Normal file
129
changelog.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-07-18 - 3.0.0 - BREAKING CHANGE(core)
|
||||
Major restructuring and feature enhancements: added batch payments and scheduled payments with builder patterns, improved webhook management, migrated package naming to @apiclient.xyz/bunq, and updated documentation and tests.
|
||||
|
||||
- Introduced BunqPaymentBatch for creating multiple payments in a single API call.
|
||||
- Implemented BunqSchedulePayment builder for scheduled and recurring payments.
|
||||
- Enhanced webhook support with integrated webhook server and improved signature verification.
|
||||
- Migrated package from @bunq-community/bunq to @apiclient.xyz/bunq with complete module restructure.
|
||||
- Updated README and changelog to reflect breaking changes and provide a migration guide.
|
||||
- Improved ESM compatibility and full TypeScript support.
|
||||
|
||||
## 2025-07-18 - 3.0.0 - BREAKING CHANGE(core)
|
||||
Major update: Introduced batch payments, scheduled payment builder, and comprehensive webhook improvements with a complete migration from bunq-js-client to the new package structure. This release brings breaking changes in API signatures, module exports, and session management for enhanced ESM and TypeScript support.
|
||||
|
||||
- Added BunqPaymentBatch for creating multiple payments in a single API call
|
||||
- Introduced BunqSchedulePayment with builder pattern for scheduled and recurring payments
|
||||
- Enhanced webhook management with BunqWebhook and integrated webhook server support
|
||||
- Migrated package naming from @bunq-community/bunq to @apiclient.xyz/bunq with a complete module restructure
|
||||
- Improved ESM compatibility with proper .js extensions and TypeScript verbatimModuleSyntax support
|
||||
- Updated documentation, changelog, and tests to reflect breaking changes and migration updates
|
||||
|
||||
## 2025-07-18 - 3.0.0 - BREAKING CHANGE(core)
|
||||
Release 2.0.0: Major updates including batch payment support, scheduled payments with a builder pattern, comprehensive webhook enhancements, migration from bunq-js-client to the new package structure, and improved ESM/TypeScript compatibility.
|
||||
|
||||
- Added BunqPaymentBatch for creating multiple payments in a single API call.
|
||||
- Introduced BunqSchedulePayment with builder pattern for scheduled and recurring payments.
|
||||
- Implemented comprehensive webhook management with BunqWebhook and built-in webhook server.
|
||||
- Migrated package naming from @bunq-community/bunq to @apiclient.xyz/bunq and restructured module exports.
|
||||
- Improved ESM compatibility with proper .js extension usage and TypeScript verbatimModuleSyntax support.
|
||||
- Updated documentation, changelog, and tests to reflect the new API and migration changes.
|
||||
|
||||
## 2019-10-02 to 2025-07-18 - Various - Minor updates
|
||||
These releases did not include any feature or bug‐fix changes beyond routine updates. The following versions are summarized here: 2.0.0, 1.0.22, 1.0.7, and 1.0.6.
|
||||
|
||||
## 2020-08-25 - 1.0.21 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2020-08-21 - 1.0.20 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2020-08-21 - 1.0.19 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2020-08-21 - 1.0.18 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2020-08-20 - 1.0.17 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2020-08-20 - 1.0.16 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2020-06-20 - 1.0.15 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-12-15 - 1.0.14 - transactions
|
||||
Main change: fix(transactions): enter a starting transaction
|
||||
|
||||
- Entered a starting transaction in the transactions module
|
||||
|
||||
## 2019-12-15 - 1.0.13 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-12-15 - 1.0.12 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-10-03 - 1.0.11 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-10-03 - 1.0.10 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-10-02 - 1.0.9 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-10-02 - 1.0.8 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-10-02 - 1.0.5 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-10-02 - 1.0.4 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-09-26 - 1.0.3 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-09-26 - 1.0.2 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
||||
|
||||
## 2019-09-26 - 1.0.1 - core
|
||||
Main change: fix(core): update
|
||||
|
||||
- Fixed issues in the core module
|
713
readme.md
713
readme.md
@@ -1,26 +1,31 @@
|
||||
# @apiclient.xyz/bunq
|
||||
A full-featured TypeScript/JavaScript client for the bunq API
|
||||
|
||||
## Availabililty and Links
|
||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@apiclient.xyz/bunq)
|
||||
* [gitlab.com (source)](https://gitlab.com/mojoio/bunq)
|
||||
* [github.com (source mirror)](https://github.com/mojoio/bunq)
|
||||
* [docs (typedoc)](https://mojoio.gitlab.io/bunq/)
|
||||
A powerful, type-safe TypeScript/JavaScript client for the bunq API with full feature coverage
|
||||
|
||||
## Features
|
||||
|
||||
- Complete bunq API implementation
|
||||
- TypeScript support with full type definitions
|
||||
- Automatic session management and renewal
|
||||
- Request signing and response verification
|
||||
- Support for all account types (personal, business, joint)
|
||||
- Payment and transaction management
|
||||
- Card management and controls
|
||||
- Scheduled and draft payments
|
||||
- File attachments and exports
|
||||
- Webhook support
|
||||
- OAuth authentication
|
||||
- Sandbox environment support
|
||||
### Core Banking Operations
|
||||
- 💳 **Complete Account Management** - Access all account types (personal, business, joint)
|
||||
- 💸 **Advanced Payment Processing** - Single payments, batch payments, scheduled payments
|
||||
- 📊 **Transaction History** - Full transaction access with filtering and pagination
|
||||
- 💰 **Payment Requests** - Send and manage payment requests with bunq.me integration
|
||||
- 📝 **Draft Payments** - Create payments requiring approval
|
||||
|
||||
### Advanced Features
|
||||
- 🔄 **Automatic Session Management** - Handles token refresh and session renewal
|
||||
- 🔐 **Full Security Implementation** - Request signing and response verification
|
||||
- 🎯 **Webhook Support** - Real-time notifications with signature verification
|
||||
- 💳 **Card Management** - Full card control (activation, limits, blocking)
|
||||
- 📎 **File Attachments** - Upload and attach files to payments
|
||||
- 📑 **Statement Exports** - Export statements in multiple formats (PDF, CSV, MT940)
|
||||
- 🔗 **OAuth Support** - Third-party app integration
|
||||
- 🧪 **Sandbox Environment** - Full testing support
|
||||
|
||||
### Developer Experience
|
||||
- 📘 **Full TypeScript Support** - Complete type definitions for all API responses
|
||||
- 🏗️ **Builder Pattern APIs** - Intuitive payment and request builders
|
||||
- ⚡ **Promise-based** - Modern async/await support throughout
|
||||
- 🛡️ **Type Safety** - Compile-time type checking for all operations
|
||||
- 📚 **Comprehensive Documentation** - Detailed examples for every feature
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -28,294 +33,610 @@ A full-featured TypeScript/JavaScript client for the bunq API
|
||||
npm install @apiclient.xyz/bunq
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn add @apiclient.xyz/bunq
|
||||
```
|
||||
|
||||
```bash
|
||||
pnpm add @apiclient.xyz/bunq
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { BunqAccount } from '@apiclient.xyz/bunq';
|
||||
|
||||
// Initialize bunq client
|
||||
// Initialize the client
|
||||
const bunq = new BunqAccount({
|
||||
apiKey: 'your-api-key',
|
||||
deviceName: 'My App',
|
||||
environment: 'PRODUCTION' // or 'SANDBOX'
|
||||
environment: 'PRODUCTION' // or 'SANDBOX' for testing
|
||||
});
|
||||
|
||||
// Initialize the client
|
||||
// Initialize connection
|
||||
await bunq.init();
|
||||
|
||||
// Get all monetary accounts
|
||||
// Get your accounts
|
||||
const accounts = await bunq.getAccounts();
|
||||
console.log('My accounts:', accounts);
|
||||
console.log(`Found ${accounts.length} accounts`);
|
||||
|
||||
// Get transactions for an account
|
||||
// Get recent transactions
|
||||
const transactions = await accounts[0].getTransactions();
|
||||
console.log('Recent transactions:', transactions);
|
||||
transactions.forEach(tx => {
|
||||
console.log(`${tx.created}: ${tx.amount.value} ${tx.amount.currency} - ${tx.description}`);
|
||||
});
|
||||
|
||||
// Clean up when done
|
||||
// Always cleanup when done
|
||||
await bunq.stop();
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
## Core Examples
|
||||
|
||||
### Making Payments
|
||||
### Account Management
|
||||
|
||||
```typescript
|
||||
import { BunqPayment } from '@apiclient.xyz/bunq';
|
||||
// Get all accounts with details
|
||||
const accounts = await bunq.getAccounts();
|
||||
|
||||
// Simple payment
|
||||
const payment = BunqPayment.builder(bunq, monetaryAccount)
|
||||
.amount('10.00', 'EUR')
|
||||
.toIban('NL91ABNA0417164300', 'John Doe')
|
||||
.description('Coffee payment')
|
||||
.create();
|
||||
for (const account of accounts) {
|
||||
console.log(`Account: ${account.description}`);
|
||||
console.log(`Balance: ${account.balance.value} ${account.balance.currency}`);
|
||||
console.log(`IBAN: ${account.iban}`);
|
||||
|
||||
// Get account-specific transactions
|
||||
const transactions = await account.getTransactions({
|
||||
count: 50, // Last 50 transactions
|
||||
newer_id: false,
|
||||
older_id: false
|
||||
});
|
||||
}
|
||||
|
||||
// Batch payment
|
||||
const batch = new BunqPaymentBatch(bunq);
|
||||
const batchId = await batch.create(monetaryAccount, [
|
||||
{
|
||||
amount: { value: '5.00', currency: 'EUR' },
|
||||
counterparty_alias: { type: 'IBAN', value: 'NL91ABNA0417164300', name: 'Recipient 1' },
|
||||
description: 'Payment 1'
|
||||
},
|
||||
{
|
||||
amount: { value: '15.00', currency: 'EUR' },
|
||||
counterparty_alias: { type: 'EMAIL', value: 'friend@example.com', name: 'Friend' },
|
||||
description: 'Payment 2'
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
### Managing Cards
|
||||
|
||||
```typescript
|
||||
import { BunqCard } from '@apiclient.xyz/bunq';
|
||||
|
||||
// List all cards
|
||||
const cards = await BunqCard.list(bunq);
|
||||
|
||||
// Activate a card
|
||||
await cards[0].activate('activation-code');
|
||||
|
||||
// Update spending limit
|
||||
await cards[0].updateLimit('100.00', 'EUR');
|
||||
|
||||
// Block a card
|
||||
await cards[0].block('LOST');
|
||||
|
||||
// Order a new card
|
||||
const newCard = await BunqCard.order(bunq, {
|
||||
secondLine: 'Travel Card',
|
||||
nameOnCard: 'JOHN DOE',
|
||||
type: 'MASTERCARD'
|
||||
// Create a new monetary account (business accounts only)
|
||||
const newAccount = await BunqMonetaryAccount.create(bunq, {
|
||||
currency: 'EUR',
|
||||
description: 'Savings Account',
|
||||
dailyLimit: '1000.00',
|
||||
overdraftLimit: '0.00'
|
||||
});
|
||||
```
|
||||
|
||||
### Scheduled Payments
|
||||
### Making Payments
|
||||
|
||||
#### Simple Payment
|
||||
```typescript
|
||||
import { BunqSchedulePayment } from '@apiclient.xyz/bunq';
|
||||
// Using the payment builder pattern
|
||||
const payment = await BunqPayment.builder(bunq, account)
|
||||
.amount('25.00', 'EUR')
|
||||
.toIban('NL91ABNA0417164300', 'John Doe')
|
||||
.description('Birthday gift')
|
||||
.create();
|
||||
|
||||
// Create a recurring payment
|
||||
const scheduled = await BunqSchedulePayment.builder(bunq, monetaryAccount)
|
||||
console.log(`Payment created with ID: ${payment.id}`);
|
||||
```
|
||||
|
||||
#### Payment with Custom Request ID (Idempotency)
|
||||
```typescript
|
||||
// Prevent duplicate payments with custom request IDs
|
||||
const payment = await BunqPayment.builder(bunq, account)
|
||||
.amount('100.00', 'EUR')
|
||||
.toIban('NL91ABNA0417164300', 'Supplier B.V.')
|
||||
.description('Invoice #12345')
|
||||
.customRequestId('invoice-12345-payment') // Prevents duplicate payments
|
||||
.create();
|
||||
```
|
||||
|
||||
#### Batch Payments
|
||||
```typescript
|
||||
const batch = new BunqPaymentBatch(bunq);
|
||||
|
||||
// Create multiple payments in one API call
|
||||
const batchId = await batch.create(account, [
|
||||
{
|
||||
amount: { value: '10.00', currency: 'EUR' },
|
||||
counterparty_alias: {
|
||||
type: 'IBAN',
|
||||
value: 'NL91ABNA0417164300',
|
||||
name: 'Employee 1'
|
||||
},
|
||||
description: 'Salary payment'
|
||||
},
|
||||
{
|
||||
amount: { value: '20.00', currency: 'EUR' },
|
||||
counterparty_alias: {
|
||||
type: 'EMAIL',
|
||||
value: 'freelancer@example.com',
|
||||
name: 'Freelancer'
|
||||
},
|
||||
description: 'Project payment'
|
||||
}
|
||||
]);
|
||||
|
||||
// Check batch status
|
||||
const batchDetails = await batch.get(account, batchId);
|
||||
console.log(`Batch status: ${batchDetails.status}`);
|
||||
console.log(`Total amount: ${batchDetails.total_amount.value}`);
|
||||
```
|
||||
|
||||
#### Scheduled & Recurring Payments
|
||||
```typescript
|
||||
const scheduler = new BunqSchedulePayment(bunq);
|
||||
|
||||
// One-time scheduled payment
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
const scheduledId = await BunqSchedulePayment.builder(bunq, account)
|
||||
.amount('50.00', 'EUR')
|
||||
.toIban('NL91ABNA0417164300', 'Landlord')
|
||||
.description('Monthly rent')
|
||||
.description('Rent payment')
|
||||
.scheduleOnce(tomorrow.toISOString())
|
||||
.create();
|
||||
|
||||
// Recurring monthly payment
|
||||
const recurringId = await BunqSchedulePayment.builder(bunq, account)
|
||||
.amount('9.99', 'EUR')
|
||||
.toIban('NL91ABNA0417164300', 'Netflix B.V.')
|
||||
.description('Monthly subscription')
|
||||
.scheduleMonthly('2024-01-01T10:00:00Z', '2024-12-31T10:00:00Z')
|
||||
.create();
|
||||
|
||||
// List scheduled payments
|
||||
const scheduler = new BunqSchedulePayment(bunq);
|
||||
const schedules = await scheduler.list(monetaryAccount);
|
||||
// List all scheduled payments
|
||||
const schedules = await scheduler.list(account);
|
||||
|
||||
// Cancel a scheduled payment
|
||||
await scheduler.delete(account, scheduledId);
|
||||
```
|
||||
|
||||
### Request Money
|
||||
### Payment Requests
|
||||
|
||||
```typescript
|
||||
import { BunqRequestInquiry } from '@apiclient.xyz/bunq';
|
||||
|
||||
// Create a payment request
|
||||
const request = BunqRequestInquiry.builder(bunq, monetaryAccount)
|
||||
const request = await BunqRequestInquiry.builder(bunq, account)
|
||||
.amount('25.00', 'EUR')
|
||||
.fromEmail('friend@example.com', 'My Friend')
|
||||
.description('Dinner split')
|
||||
.allowBunqme()
|
||||
.description('Lunch money')
|
||||
.allowBunqme() // Generate bunq.me link
|
||||
.minimumAge(18)
|
||||
.create();
|
||||
|
||||
// The request will include a bunq.me URL for easy sharing
|
||||
console.log('Share this link:', request.bunqmeShareUrl);
|
||||
console.log(`Share this link: ${request.bunqmeShareUrl}`);
|
||||
|
||||
// List pending requests
|
||||
const requests = await BunqRequestInquiry.list(bunq, account.id);
|
||||
const pending = requests.filter(r => r.status === 'PENDING');
|
||||
|
||||
// Cancel a request
|
||||
await request.update(requestId, { status: 'CANCELLED' });
|
||||
```
|
||||
|
||||
### File Attachments
|
||||
### Draft Payments (Requires Approval)
|
||||
|
||||
```typescript
|
||||
import { BunqAttachment } from '@apiclient.xyz/bunq';
|
||||
const draft = new BunqDraftPayment(bunq, account);
|
||||
|
||||
// Upload a file
|
||||
const attachment = new BunqAttachment(bunq);
|
||||
const attachmentUuid = await attachment.uploadFile('/path/to/receipt.pdf', 'Receipt');
|
||||
// Create a draft with multiple payments
|
||||
const draftId = await draft.create({
|
||||
numberOfRequiredAccepts: 2, // Requires 2 approvals
|
||||
entries: [
|
||||
{
|
||||
amount: { value: '1000.00', currency: 'EUR' },
|
||||
counterparty_alias: {
|
||||
type: 'IBAN',
|
||||
value: 'NL91ABNA0417164300',
|
||||
name: 'Supplier A'
|
||||
},
|
||||
description: 'Invoice payment'
|
||||
},
|
||||
{
|
||||
amount: { value: '2000.00', currency: 'EUR' },
|
||||
counterparty_alias: {
|
||||
type: 'IBAN',
|
||||
value: 'NL91ABNA0417164300',
|
||||
name: 'Supplier B'
|
||||
},
|
||||
description: 'Equipment purchase'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Attach to a payment
|
||||
const payment = BunqPayment.builder(bunq, monetaryAccount)
|
||||
.amount('99.99', 'EUR')
|
||||
.toIban('NL91ABNA0417164300')
|
||||
.description('Purchase with receipt')
|
||||
.attachments([attachmentUuid])
|
||||
.create();
|
||||
// Approve the draft
|
||||
await draft.accept();
|
||||
|
||||
// Or reject it
|
||||
await draft.reject('Budget exceeded');
|
||||
```
|
||||
|
||||
### Export Statements
|
||||
### Card Management
|
||||
|
||||
```typescript
|
||||
import { BunqExport, ExportBuilder } from '@apiclient.xyz/bunq';
|
||||
// List all cards
|
||||
const cards = await BunqCard.list(bunq);
|
||||
|
||||
// Export last month's transactions as PDF
|
||||
await new ExportBuilder(bunq, monetaryAccount)
|
||||
.asPdf()
|
||||
.lastMonth()
|
||||
.downloadTo('/path/to/statement.pdf');
|
||||
// Activate a new card
|
||||
const card = cards.find(c => c.status === 'INACTIVE');
|
||||
if (card) {
|
||||
await card.activate('123456'); // Activation code
|
||||
}
|
||||
|
||||
// Export custom date range as CSV
|
||||
await new ExportBuilder(bunq, monetaryAccount)
|
||||
.asCsv()
|
||||
.dateRange('2024-01-01', '2024-03-31')
|
||||
.regionalFormat('EUROPEAN')
|
||||
.downloadTo('/path/to/transactions.csv');
|
||||
// Update spending limits
|
||||
await card.updateLimit('500.00', 'EUR');
|
||||
|
||||
// Update PIN
|
||||
await card.updatePin('1234', '5678');
|
||||
|
||||
// Block a card
|
||||
await card.block('LOST');
|
||||
|
||||
// Set country permissions
|
||||
await card.setCountryPermissions([
|
||||
{ country: 'NL', expiry_time: '2025-01-01T00:00:00Z' },
|
||||
{ country: 'BE', expiry_time: '2025-01-01T00:00:00Z' }
|
||||
]);
|
||||
|
||||
// Order a new card
|
||||
const newCard = await BunqCard.order(bunq, {
|
||||
type: 'MASTERCARD',
|
||||
subType: 'PHYSICAL',
|
||||
nameOnCard: 'JOHN DOE',
|
||||
secondLine: 'Travel Card',
|
||||
monetaryAccountId: account.id
|
||||
});
|
||||
```
|
||||
|
||||
### Webhooks
|
||||
|
||||
```typescript
|
||||
import { BunqWebhookServer, BunqNotification } from '@apiclient.xyz/bunq';
|
||||
|
||||
// Setup webhook server
|
||||
const webhookServer = new BunqWebhookServer(bunq, {
|
||||
port: 3000,
|
||||
publicUrl: 'https://myapp.com'
|
||||
publicUrl: 'https://myapp.com/webhooks'
|
||||
});
|
||||
|
||||
// Register handlers
|
||||
// Register event handlers
|
||||
webhookServer.getHandler().onPayment((payment) => {
|
||||
console.log('New payment:', payment.amount.value, payment.description);
|
||||
console.log(`New payment: ${payment.amount.value} ${payment.amount.currency}`);
|
||||
console.log(`From: ${payment.counterparty_alias.display_name}`);
|
||||
console.log(`Description: ${payment.description}`);
|
||||
|
||||
// Your business logic here
|
||||
updateDatabase(payment);
|
||||
sendNotification(payment);
|
||||
});
|
||||
|
||||
webhookServer.getHandler().onRequest((request) => {
|
||||
console.log(`New payment request: ${request.amount_inquired.value}`);
|
||||
console.log(`From: ${request.user_alias_created.display_name}`);
|
||||
});
|
||||
|
||||
webhookServer.getHandler().onCard((card) => {
|
||||
console.log('Card event:', card.status);
|
||||
if (card.status === 'BLOCKED') {
|
||||
console.log(`Card blocked: ${card.name_on_card}`);
|
||||
alertSecurityTeam(card);
|
||||
}
|
||||
});
|
||||
|
||||
// Start server and register with bunq
|
||||
await webhookServer.start();
|
||||
await webhookServer.register();
|
||||
|
||||
// Manual webhook management
|
||||
const webhook = new BunqWebhook(bunq, account);
|
||||
|
||||
// Create webhook for specific URL
|
||||
const webhookId = await webhook.create(account, 'https://myapp.com/bunq-webhook');
|
||||
|
||||
// List all webhooks
|
||||
const webhooks = await webhook.list(account);
|
||||
|
||||
// Delete webhook
|
||||
await webhook.delete(account, webhookId);
|
||||
```
|
||||
|
||||
### Sandbox Testing
|
||||
### File Attachments
|
||||
|
||||
```typescript
|
||||
// Create a sandbox account
|
||||
const sandboxBunq = new BunqAccount({
|
||||
apiKey: '', // Will be generated
|
||||
deviceName: 'Sandbox Test',
|
||||
environment: 'SANDBOX'
|
||||
const attachment = new BunqAttachment(bunq);
|
||||
|
||||
// Upload a file
|
||||
const attachmentUuid = await attachment.uploadFile(
|
||||
'/path/to/invoice.pdf',
|
||||
'Invoice #12345'
|
||||
);
|
||||
|
||||
// Attach to payment
|
||||
const payment = await BunqPayment.builder(bunq, account)
|
||||
.amount('150.00', 'EUR')
|
||||
.toIban('NL91ABNA0417164300', 'Accountant')
|
||||
.description('Services rendered')
|
||||
.attachments([attachmentUuid])
|
||||
.create();
|
||||
|
||||
// Upload from buffer
|
||||
const buffer = await generateReport();
|
||||
const uuid = await attachment.uploadBuffer(
|
||||
buffer,
|
||||
'report.pdf',
|
||||
'application/pdf',
|
||||
'Monthly Report'
|
||||
);
|
||||
|
||||
// Get attachment content
|
||||
const content = await attachment.getContent(attachmentUuid);
|
||||
await fs.writeFile('downloaded.pdf', content);
|
||||
```
|
||||
|
||||
### Export Statements
|
||||
|
||||
```typescript
|
||||
// Export last month as PDF
|
||||
await new ExportBuilder(bunq, account)
|
||||
.asPdf()
|
||||
.lastMonth()
|
||||
.downloadTo('/path/to/statement.pdf');
|
||||
|
||||
// Export date range as CSV
|
||||
await new ExportBuilder(bunq, account)
|
||||
.asCsv()
|
||||
.dateRange('2024-01-01', '2024-03-31')
|
||||
.regionalFormat('EUROPEAN')
|
||||
.downloadTo('/path/to/transactions.csv');
|
||||
|
||||
// Export as MT940 for accounting software
|
||||
await new ExportBuilder(bunq, account)
|
||||
.asMt940()
|
||||
.lastQuarter()
|
||||
.downloadTo('/path/to/statement.sta');
|
||||
|
||||
// Stream export for large files
|
||||
const exportStream = await new ExportBuilder(bunq, account)
|
||||
.asCsv()
|
||||
.lastYear()
|
||||
.stream();
|
||||
|
||||
exportStream.pipe(fs.createWriteStream('large-export.csv'));
|
||||
```
|
||||
|
||||
### User & Session Management
|
||||
|
||||
```typescript
|
||||
// Get user information
|
||||
const user = await bunq.getUser();
|
||||
console.log(`Logged in as: ${user.displayName}`);
|
||||
console.log(`User type: ${user.type}`); // UserPerson, UserCompany, etc.
|
||||
|
||||
// Update user settings
|
||||
await user.update({
|
||||
dailyLimitWithoutConfirmationLogin: '100.00',
|
||||
notificationFilters: [
|
||||
{ category: 'PAYMENT', notificationDeliveryMethod: 'PUSH' }
|
||||
]
|
||||
});
|
||||
|
||||
// Create a sandbox user with API key
|
||||
const apiKey = await sandboxBunq.createSandboxUser();
|
||||
console.log('Sandbox API key:', apiKey);
|
||||
// Session management
|
||||
const session = bunq.apiContext.getSession();
|
||||
console.log(`Session expires: ${session.expiryTime}`);
|
||||
|
||||
// Now reinitialize with the API key
|
||||
// Manual session refresh
|
||||
await bunq.apiContext.refreshSession();
|
||||
|
||||
// Save session for later use
|
||||
const sessionData = bunq.apiContext.exportSession();
|
||||
await fs.writeFile('bunq-session.json', JSON.stringify(sessionData));
|
||||
|
||||
// Restore session
|
||||
const savedSession = JSON.parse(await fs.readFile('bunq-session.json'));
|
||||
bunq.apiContext.importSession(savedSession);
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### OAuth Integration
|
||||
|
||||
```typescript
|
||||
// Create OAuth client
|
||||
const oauth = new BunqOAuth({
|
||||
clientId: 'your-client-id',
|
||||
clientSecret: 'your-client-secret',
|
||||
redirectUri: 'https://yourapp.com/callback'
|
||||
});
|
||||
|
||||
// Generate authorization URL
|
||||
const authUrl = oauth.getAuthorizationUrl({
|
||||
state: 'random-state-string',
|
||||
accounts: ['NL91ABNA0417164300'] // Pre-select accounts
|
||||
});
|
||||
|
||||
// Exchange code for access token
|
||||
const token = await oauth.exchangeCode(authorizationCode);
|
||||
|
||||
// Use OAuth token with bunq client
|
||||
const bunq = new BunqAccount({
|
||||
apiKey: apiKey,
|
||||
deviceName: 'Sandbox Test',
|
||||
environment: 'SANDBOX'
|
||||
accessToken: token.access_token,
|
||||
environment: 'PRODUCTION'
|
||||
});
|
||||
await bunq.init();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Classes
|
||||
|
||||
- `BunqAccount` - Main entry point for the API
|
||||
- `BunqMonetaryAccount` - Represents a bank account
|
||||
- `BunqTransaction` - Represents a transaction
|
||||
- `BunqUser` - User management
|
||||
|
||||
### Payment Classes
|
||||
|
||||
- `BunqPayment` - Create and manage payments
|
||||
- `BunqBatchPayment` - Create multiple payments at once
|
||||
- `BunqScheduledPayment` - Schedule recurring payments
|
||||
- `BunqDraftPayment` - Create draft payments requiring approval
|
||||
- `BunqRequestInquiry` - Request money from others
|
||||
|
||||
### Other Features
|
||||
|
||||
- `BunqCard` - Card management
|
||||
- `BunqAttachment` - File uploads and attachments
|
||||
- `BunqExport` - Export statements in various formats
|
||||
- `BunqNotification` - Webhook notifications
|
||||
- `BunqWebhookServer` - Built-in webhook server
|
||||
|
||||
## Security
|
||||
|
||||
- All requests are signed with RSA signatures
|
||||
- Response signatures are verified
|
||||
- API keys are stored securely
|
||||
- Session tokens are automatically refreshed
|
||||
- Supports IP whitelisting
|
||||
|
||||
## Error Handling
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
import { BunqApiError, BunqRateLimitError, BunqAuthError } from '@apiclient.xyz/bunq';
|
||||
|
||||
try {
|
||||
await payment.create();
|
||||
} catch (error) {
|
||||
if (error instanceof BunqApiError) {
|
||||
console.error('bunq API error:', error.errors);
|
||||
// Handle API errors
|
||||
console.error('API Error:', error.errors);
|
||||
error.errors.forEach(e => {
|
||||
console.error(`- ${e.error_description}`);
|
||||
});
|
||||
} else if (error instanceof BunqRateLimitError) {
|
||||
// Handle rate limiting
|
||||
console.error('Rate limited. Retry after:', error.retryAfter);
|
||||
await sleep(error.retryAfter * 1000);
|
||||
} else if (error instanceof BunqAuthError) {
|
||||
// Handle authentication errors
|
||||
console.error('Authentication failed:', error.message);
|
||||
await bunq.reinitialize();
|
||||
} else {
|
||||
// Handle other errors
|
||||
console.error('Unexpected error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
### Pagination
|
||||
|
||||
Run the test suite:
|
||||
```typescript
|
||||
// Paginate through all transactions
|
||||
async function* getAllTransactions(account: BunqMonetaryAccount) {
|
||||
let olderId: number | false = false;
|
||||
|
||||
while (true) {
|
||||
const transactions = await account.getTransactions({
|
||||
count: 200,
|
||||
older_id: olderId
|
||||
});
|
||||
|
||||
if (transactions.length === 0) break;
|
||||
|
||||
yield* transactions;
|
||||
olderId = transactions[transactions.length - 1].id;
|
||||
}
|
||||
}
|
||||
|
||||
```bash
|
||||
npm test # Run all tests
|
||||
npm run test:basic # Run basic functionality tests
|
||||
npm run test:payments # Run payment-related tests
|
||||
npm run test:webhooks # Run webhook tests
|
||||
npm run test:session # Run session management tests
|
||||
npm run test:errors # Run error handling tests
|
||||
npm run test:advanced # Run advanced feature tests
|
||||
// Usage
|
||||
for await (const transaction of getAllTransactions(account)) {
|
||||
console.log(`${transaction.created}: ${transaction.description}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
### Sandbox Testing
|
||||
|
||||
The test suite includes comprehensive coverage for:
|
||||
```typescript
|
||||
// Create sandbox environment
|
||||
const sandboxBunq = new BunqAccount({
|
||||
apiKey: '', // Will be generated
|
||||
deviceName: 'My Test App',
|
||||
environment: 'SANDBOX'
|
||||
});
|
||||
|
||||
- **Basic functionality**: Account creation, initialization, transactions
|
||||
- **Payments**: Payment builders, draft payments, payment requests
|
||||
- **Webhooks**: Creation, management, signature verification
|
||||
- **Session management**: Persistence, expiry, concurrent usage
|
||||
- **Error handling**: Network errors, invalid inputs, rate limiting
|
||||
- **Advanced features**: Joint accounts, cards, notifications, exports
|
||||
// Create sandbox user with €1000 balance
|
||||
const apiKey = await sandboxBunq.createSandboxUser();
|
||||
console.log('Sandbox API key:', apiKey);
|
||||
|
||||
All tests use the bunq sandbox environment and create fresh API keys for each test run.
|
||||
// Re-initialize with the generated key
|
||||
const bunq = new BunqAccount({
|
||||
apiKey: apiKey,
|
||||
deviceName: 'My Test App',
|
||||
environment: 'SANDBOX'
|
||||
});
|
||||
await bunq.init();
|
||||
|
||||
// Sandbox-specific features
|
||||
await sandboxBunq.topUpSandboxAccount(account.id, '500.00');
|
||||
await sandboxBunq.simulateCardTransaction(card.id, '25.00', 'NL');
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **API Key Storage**: Never commit API keys to version control
|
||||
```typescript
|
||||
const bunq = new BunqAccount({
|
||||
apiKey: process.env.BUNQ_API_KEY,
|
||||
deviceName: 'Production App',
|
||||
environment: 'PRODUCTION'
|
||||
});
|
||||
```
|
||||
|
||||
2. **IP Whitelisting**: Restrict API access to specific IPs
|
||||
```typescript
|
||||
const bunq = new BunqAccount({
|
||||
apiKey: process.env.BUNQ_API_KEY,
|
||||
permittedIps: ['1.2.3.4', '5.6.7.8']
|
||||
});
|
||||
```
|
||||
|
||||
3. **Webhook Verification**: Always verify webhook signatures
|
||||
```typescript
|
||||
app.post('/webhook', (req, res) => {
|
||||
const signature = req.headers['x-bunq-client-signature'];
|
||||
const isValid = bunq.verifyWebhookSignature(req.body, signature);
|
||||
|
||||
if (!isValid) {
|
||||
return res.status(401).send('Invalid signature');
|
||||
}
|
||||
|
||||
// Process webhook...
|
||||
});
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From @bunq-community/bunq-js-client
|
||||
|
||||
```typescript
|
||||
// Old
|
||||
import BunqJSClient from '@bunq-community/bunq-js-client';
|
||||
const bunqJSClient = new BunqJSClient();
|
||||
|
||||
// New
|
||||
import { BunqAccount } from '@apiclient.xyz/bunq';
|
||||
const bunq = new BunqAccount({
|
||||
apiKey: 'your-api-key',
|
||||
deviceName: 'My App'
|
||||
});
|
||||
|
||||
// Old
|
||||
await bunqJSClient.install();
|
||||
await bunqJSClient.registerDevice();
|
||||
await bunqJSClient.registerSession();
|
||||
|
||||
// New - all handled in one call
|
||||
await bunq.init();
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The library includes comprehensive test coverage:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run specific test suites
|
||||
npm run test:basic # Core functionality
|
||||
npm run test:payments # Payment features
|
||||
npm run test:webhooks # Webhook functionality
|
||||
npm run test:session # Session management
|
||||
npm run test:errors # Error handling
|
||||
npm run test:advanced # Advanced features
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 10.x or higher
|
||||
- TypeScript 3.x or higher (for TypeScript users)
|
||||
- Node.js 14.x or higher
|
||||
- TypeScript 4.5 or higher (for TypeScript users)
|
||||
|
||||
## Contribution
|
||||
## Contributing
|
||||
|
||||
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
|
||||
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
||||
|
||||
## Support
|
||||
|
||||
- 📧 Email: support@apiclient.xyz
|
||||
- 💬 Discord: [Join our community](https://discord.gg/apiclient)
|
||||
- 🐛 Issues: [GitHub Issues](https://github.com/mojoio/bunq/issues)
|
||||
- 📚 Docs: [Full API Documentation](https://mojoio.gitlab.io/bunq/)
|
||||
|
||||
## License
|
||||
|
||||
MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
|
||||
---
|
||||
|
||||
For further information read the linked docs at the top of this readme.
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||
> By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||
|
||||
[](https://maintainedby.lossless.com)
|
||||
[](https://maintainedby.lossless.com)
|
@@ -52,41 +52,43 @@ tap.test('should create test setup with multiple accounts', async () => {
|
||||
});
|
||||
|
||||
tap.test('should create and execute a payment draft', async () => {
|
||||
const draft = new bunq.BunqDraftPayment(testBunqAccount);
|
||||
const draft = new bunq.BunqDraftPayment(testBunqAccount, primaryAccount);
|
||||
|
||||
// Create a draft payment
|
||||
const draftId = await draft.create(primaryAccount, {
|
||||
amount: {
|
||||
currency: 'EUR',
|
||||
value: '5.00'
|
||||
},
|
||||
counterparty_alias: {
|
||||
type: 'IBAN',
|
||||
value: 'NL91ABNA0417164300',
|
||||
name: 'Draft Test Recipient'
|
||||
},
|
||||
description: 'Test draft payment'
|
||||
const draftId = await draft.create({
|
||||
numberOfRequiredAccepts: 1,
|
||||
entries: [{
|
||||
amount: {
|
||||
currency: 'EUR',
|
||||
value: '5.00'
|
||||
},
|
||||
counterparty_alias: {
|
||||
type: 'IBAN',
|
||||
value: 'NL91ABNA0417164300',
|
||||
name: 'Draft Test Recipient'
|
||||
},
|
||||
description: 'Test draft payment entry'
|
||||
}]
|
||||
});
|
||||
|
||||
expect(draftId).toBeTypeofNumber();
|
||||
console.log(`Created draft payment with ID: ${draftId}`);
|
||||
|
||||
// List drafts
|
||||
const drafts = await draft.list(primaryAccount);
|
||||
const drafts = await bunq.BunqDraftPayment.list(testBunqAccount, primaryAccount.id);
|
||||
expect(drafts).toBeArray();
|
||||
expect(drafts.length).toBeGreaterThan(0);
|
||||
|
||||
const createdDraft = drafts.find(d => d.id === draftId);
|
||||
const createdDraft = drafts.find((d: any) => d.DraftPayment?.id === draftId);
|
||||
expect(createdDraft).toBeDefined();
|
||||
expect(createdDraft?.amount.value).toBe('5.00');
|
||||
|
||||
// Update the draft
|
||||
await draft.update(primaryAccount, draftId, {
|
||||
await draft.update(draftId, {
|
||||
description: 'Updated draft payment description'
|
||||
});
|
||||
|
||||
// Get updated draft
|
||||
const updatedDraft = await draft.get(primaryAccount, draftId);
|
||||
const updatedDraft = await draft.get(draftId);
|
||||
expect(updatedDraft.description).toBe('Updated draft payment description');
|
||||
|
||||
console.log('Draft payment updated successfully');
|
||||
@@ -224,33 +226,33 @@ tap.test('should test scheduled payments', async () => {
|
||||
});
|
||||
|
||||
tap.test('should test payment requests', async () => {
|
||||
const paymentRequest = new bunq.BunqRequestInquiry(testBunqAccount);
|
||||
const paymentRequest = new bunq.BunqRequestInquiry(testBunqAccount, primaryAccount);
|
||||
|
||||
// Create a payment request
|
||||
try {
|
||||
const requestId = await paymentRequest.create(primaryAccount, {
|
||||
amount: {
|
||||
const requestId = await paymentRequest.create({
|
||||
amountInquired: {
|
||||
currency: 'EUR',
|
||||
value: '15.00'
|
||||
},
|
||||
counterparty_alias: {
|
||||
counterpartyAlias: {
|
||||
type: 'EMAIL',
|
||||
value: 'requester@example.com',
|
||||
name: 'Request Sender'
|
||||
},
|
||||
description: 'Payment request test',
|
||||
allow_bunqme: true
|
||||
allowBunqme: true
|
||||
});
|
||||
|
||||
expect(requestId).toBeTypeofNumber();
|
||||
console.log(`Created payment request with ID: ${requestId}`);
|
||||
|
||||
// List requests
|
||||
const requests = await paymentRequest.list(primaryAccount);
|
||||
const requests = await bunq.BunqRequestInquiry.list(testBunqAccount, primaryAccount.id);
|
||||
expect(requests).toBeArray();
|
||||
|
||||
// Cancel the request
|
||||
await paymentRequest.update(primaryAccount, requestId, {
|
||||
await paymentRequest.update(requestId, {
|
||||
status: 'CANCELLED'
|
||||
});
|
||||
console.log('Payment request cancelled successfully');
|
||||
@@ -260,19 +262,19 @@ tap.test('should test payment requests', async () => {
|
||||
});
|
||||
|
||||
tap.test('should test payment response (accepting a request)', async () => {
|
||||
const paymentResponse = new bunq.BunqRequestResponse(testBunqAccount);
|
||||
const paymentResponse = new bunq.BunqRequestResponse(testBunqAccount, primaryAccount);
|
||||
|
||||
// First create a request to respond to
|
||||
const paymentRequest = new bunq.BunqRequestInquiry(testBunqAccount);
|
||||
const paymentRequest = new bunq.BunqRequestInquiry(testBunqAccount, primaryAccount);
|
||||
|
||||
try {
|
||||
// Create a self-request (from same account) for testing
|
||||
const requestId = await paymentRequest.create(primaryAccount, {
|
||||
amount: {
|
||||
const requestId = await paymentRequest.create({
|
||||
amountInquired: {
|
||||
currency: 'EUR',
|
||||
value: '5.00'
|
||||
},
|
||||
counterparty_alias: {
|
||||
counterpartyAlias: {
|
||||
type: 'IBAN',
|
||||
value: primaryAccount.iban,
|
||||
name: primaryAccount.displayName
|
||||
@@ -283,7 +285,7 @@ tap.test('should test payment response (accepting a request)', async () => {
|
||||
console.log(`Created self-request with ID: ${requestId}`);
|
||||
|
||||
// Accept the request
|
||||
const responseId = await paymentResponse.accept(primaryAccount, requestId);
|
||||
const responseId = await paymentResponse.accept(requestId);
|
||||
expect(responseId).toBeTypeofNumber();
|
||||
console.log(`Accepted request with response ID: ${responseId}`);
|
||||
} catch (error) {
|
||||
|
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/bunq',
|
||||
version: '3.0.0',
|
||||
description: 'A full-featured TypeScript/JavaScript client for the bunq API'
|
||||
}
|
Reference in New Issue
Block a user