Files
bunq/readme.md

632 lines
17 KiB
Markdown
Raw Normal View History

2025-07-18 10:31:12 +00:00
# @apiclient.xyz/bunq
A powerful, type-safe TypeScript/JavaScript client for the bunq API with full feature coverage
2020-06-20 01:47:53 +00:00
2025-07-18 10:31:12 +00:00
## Features
2020-06-20 01:47:53 +00:00
### 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
2025-07-18 10:31:12 +00:00
## Installation
```bash
npm install @apiclient.xyz/bunq
```
```bash
yarn add @apiclient.xyz/bunq
```
```bash
pnpm add @apiclient.xyz/bunq
```
2025-07-18 10:31:12 +00:00
## Quick Start
```typescript
import { BunqAccount } from '@apiclient.xyz/bunq';
// Initialize the client
2025-07-18 10:31:12 +00:00
const bunq = new BunqAccount({
apiKey: 'your-api-key',
deviceName: 'My App',
environment: 'PRODUCTION' // or 'SANDBOX' for testing
2025-07-18 10:31:12 +00:00
});
// Initialize connection
2025-07-18 10:31:12 +00:00
await bunq.init();
// Get your accounts
2025-07-18 10:31:12 +00:00
const accounts = await bunq.getAccounts();
console.log(`Found ${accounts.length} accounts`);
2025-07-18 10:31:12 +00:00
// Get recent transactions
2025-07-18 10:31:12 +00:00
const transactions = await accounts[0].getTransactions();
transactions.forEach(tx => {
console.log(`${tx.created}: ${tx.amount.value} ${tx.amount.currency} - ${tx.description}`);
});
2025-07-18 10:31:12 +00:00
// Always cleanup when done
2025-07-18 10:31:12 +00:00
await bunq.stop();
```
## Core Examples
2025-07-18 10:31:12 +00:00
### Account Management
2025-07-18 10:31:12 +00:00
```typescript
// Get all accounts with details
const accounts = await bunq.getAccounts();
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
});
}
// 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'
});
```
### Making Payments
2025-07-18 10:31:12 +00:00
#### Simple Payment
```typescript
// Using the payment builder pattern
const payment = await BunqPayment.builder(bunq, account)
.amount('25.00', 'EUR')
2025-07-18 10:31:12 +00:00
.toIban('NL91ABNA0417164300', 'John Doe')
.description('Birthday gift')
2025-07-18 10:31:12 +00:00
.create();
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
2025-07-18 12:10:29 +00:00
const batch = new BunqPaymentBatch(bunq);
// Create multiple payments in one API call
const batchId = await batch.create(account, [
2025-07-18 12:10:29 +00:00
{
amount: { value: '10.00', currency: 'EUR' },
counterparty_alias: {
type: 'IBAN',
value: 'NL91ABNA0417164300',
name: 'Employee 1'
},
description: 'Salary payment'
2025-07-18 12:10:29 +00:00
},
{
amount: { value: '20.00', currency: 'EUR' },
counterparty_alias: {
type: 'EMAIL',
value: 'freelancer@example.com',
name: 'Freelancer'
},
description: 'Project payment'
2025-07-18 12:10:29 +00:00
}
]);
// 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('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 all scheduled payments
const schedules = await scheduler.list(account);
// Cancel a scheduled payment
await scheduler.delete(account, scheduledId);
2025-07-18 10:31:12 +00:00
```
### Payment Requests
2025-07-18 10:31:12 +00:00
```typescript
// Create a payment request
const request = await BunqRequestInquiry.builder(bunq, account)
.amount('25.00', 'EUR')
.fromEmail('friend@example.com', 'My Friend')
.description('Lunch money')
.allowBunqme() // Generate bunq.me link
.minimumAge(18)
.create();
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' });
```
### Draft Payments (Requires Approval)
2025-07-18 10:31:12 +00:00
```typescript
const draft = new BunqDraftPayment(bunq, account);
// 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'
}
]
});
// Approve the draft
await draft.accept();
// Or reject it
await draft.reject('Budget exceeded');
```
### Card Management
```typescript
2025-07-18 10:31:12 +00:00
// List all cards
const cards = await BunqCard.list(bunq);
// Get card details
for (const card of cards) {
console.log(`Card: ${card.name_on_card}`);
console.log(`Status: ${card.status}`);
console.log(`Type: ${card.type}`)
console.log(`Expiry: ${card.expiry_date}`);
// Get card limits
const limits = card.limit;
console.log(`Daily limit: ${limits.daily_spent}`);
}
// Note: Card management methods like activation, PIN updates, and ordering
// new cards should be performed through the bunq app or API directly.
2025-07-18 10:31:12 +00:00
```
### Webhooks
2025-07-18 10:31:12 +00:00
```typescript
// Setup webhook server
const webhookServer = new BunqWebhookServer(bunq, {
port: 3000,
publicUrl: 'https://myapp.com/webhooks'
});
2025-07-18 10:31:12 +00:00
// Register event handlers
webhookServer.getHandler().onPayment((payment) => {
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);
});
2025-07-18 10:31:12 +00:00
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) => {
if (card.status === 'BLOCKED') {
console.log(`Card blocked: ${card.name_on_card}`);
alertSecurityTeam(card);
}
});
2025-07-18 10:31:12 +00:00
// Start server and register with bunq
await webhookServer.start();
await webhookServer.register();
2025-07-18 10:31:12 +00:00
// Manual webhook management
const webhook = new BunqWebhook(bunq, account);
2025-07-18 10:31:12 +00:00
// Create webhook for specific URL
const webhookId = await webhook.create(account, 'https://myapp.com/bunq-webhook');
2025-07-18 10:31:12 +00:00
// List all webhooks
const webhooks = await webhook.list(account);
// Delete webhook
await webhook.delete(account, webhookId);
2025-07-18 10:31:12 +00:00
```
### File Attachments
```typescript
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')
2025-07-18 10:31:12 +00:00
.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);
2025-07-18 10:31:12 +00:00
```
### Export Statements
```typescript
// Export last month as PDF
await new ExportBuilder(bunq, account)
2025-07-18 10:31:12 +00:00
.asPdf()
.lastMonth()
.downloadTo('/path/to/statement.pdf');
// Export date range as CSV
await new ExportBuilder(bunq, account)
2025-07-18 10:31:12 +00:00
.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()
.lastDays(90) // Last 90 days
.downloadTo('/path/to/statement.sta');
// Export last 30 days with attachments
await new ExportBuilder(bunq, account)
.asPdf()
.lastDays(30)
.includeAttachments(true)
.downloadTo('/path/to/statement-with-attachments.pdf');
2025-07-18 10:31:12 +00:00
```
### User & Session Management
2025-07-18 10:31:12 +00:00
```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' }
]
});
2025-07-18 10:31:12 +00:00
// Session management
const session = bunq.apiContext.getSession();
console.log(`Session expires: ${session.expiryTime}`);
// 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
### Custom Request Headers
```typescript
// Use custom request IDs for idempotency
const payment = await BunqPayment.builder(bunq, account)
.amount('100.00', 'EUR')
.toIban('NL91ABNA0417164300', 'Recipient')
.description('Invoice payment')
.customRequestId('unique-request-id-123') // Prevents duplicate payments
.create();
// The same request ID will return the original payment without creating a duplicate
```
2025-07-18 10:31:12 +00:00
2025-07-22 21:10:41 +00:00
### OAuth Token Support
```typescript
// Using OAuth access token instead of API key
const bunq = new BunqAccount({
apiKey: 'your-oauth-access-token', // OAuth token from bunq OAuth flow
deviceName: 'OAuth App',
environment: 'PRODUCTION',
isOAuthToken: true // Optional: Set for OAuth-specific handling
2025-07-22 21:10:41 +00:00
});
await bunq.init();
// OAuth tokens work just like regular API keys:
// 1. They go through installation → device → session creation
// 2. The OAuth token is used as the 'secret' during authentication
// 3. A session token is created and used for all API calls
2025-07-22 21:10:41 +00:00
const accounts = await bunq.getAccounts();
// According to bunq documentation:
// "Just use the OAuth Token (access_token) as a normal bunq API key"
2025-07-22 21:10:41 +00:00
```
### Error Handling
```typescript
import { BunqApiError } from '@apiclient.xyz/bunq';
try {
await payment.create();
} catch (error) {
if (error instanceof BunqApiError) {
// Handle API errors
console.error('API Error:', error.errors);
error.errors.forEach(e => {
console.error(`- ${e.error_description}`);
});
} else if (error.response?.status === 429) {
// Handle rate limiting
console.error('Rate limited. Please retry after a few seconds.');
await new Promise(resolve => setTimeout(resolve, 5000));
} else if (error.response?.status === 401) {
// Handle authentication errors
console.error('Authentication failed:', error.message);
await bunq.init(); // Re-initialize session
} else {
// Handle other errors
console.error('Unexpected error:', error);
}
}
```
### Pagination
```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;
}
}
// Usage
for await (const transaction of getAllTransactions(account)) {
console.log(`${transaction.created}: ${transaction.description}`);
}
2025-07-18 10:31:12 +00:00
```
### Sandbox Testing
```typescript
// Create sandbox environment
2025-07-18 10:31:12 +00:00
const sandboxBunq = new BunqAccount({
apiKey: '', // Will be generated
deviceName: 'My Test App',
2025-07-18 10:31:12 +00:00
environment: 'SANDBOX'
});
// Create sandbox user with €1000 balance
2025-07-18 10:31:12 +00:00
const apiKey = await sandboxBunq.createSandboxUser();
console.log('Sandbox API key:', apiKey);
// Re-initialize with the generated key
2025-07-18 10:31:12 +00:00
const bunq = new BunqAccount({
apiKey: apiKey,
deviceName: 'My Test App',
2025-07-18 10:31:12 +00:00
environment: 'SANDBOX'
});
await bunq.init();
// The sandbox environment provides €1000 initial balance for testing
// Additional sandbox-specific features can be accessed through the bunq API directly
```
2025-07-18 10:31:12 +00:00
## 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
2025-07-18 10:31:12 +00:00
```typescript
// Old
import BunqJSClient from '@bunq-community/bunq-js-client';
const bunqJSClient = new BunqJSClient();
2025-07-18 10:31:12 +00:00
// New
import { BunqAccount } from '@apiclient.xyz/bunq';
const bunq = new BunqAccount({
apiKey: 'your-api-key',
deviceName: 'My App'
});
2025-07-18 10:31:12 +00:00
// Old
await bunqJSClient.install();
await bunqJSClient.registerDevice();
await bunqJSClient.registerSession();
2025-07-18 10:31:12 +00:00
// New - all handled in one call
await bunq.init();
2025-07-18 10:31:12 +00:00
```
2025-07-18 12:10:29 +00:00
## Testing
The library includes comprehensive test coverage:
2025-07-18 12:10:29 +00:00
```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
2025-07-18 12:10:29 +00:00
```
## Requirements
2025-07-18 12:10:29 +00:00
- Node.js 14.x or higher
- TypeScript 4.5 or higher (for TypeScript users)
2025-07-18 12:10:29 +00:00
## License and Legal Information
2025-07-18 12:10:29 +00:00
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
2025-07-18 10:31:12 +00:00
### Trademarks
2020-06-20 01:47:53 +00:00
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
2020-06-20 01:47:53 +00:00
### Company Information
2020-06-20 01:47:53 +00:00
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
2020-06-20 01:47:53 +00:00
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
2020-06-20 01:47:53 +00:00
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.