Files
bunq/readme.md

642 lines
16 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);
// Activate a new card
const card = cards.find(c => c.status === 'INACTIVE');
if (card) {
await card.activate('123456'); // Activation code
}
// Update spending limits
await card.updateLimit('500.00', 'EUR');
2025-07-18 10:31:12 +00:00
// Update PIN
await card.updatePin('1234', '5678');
2025-07-18 10:31:12 +00:00
// 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' }
]);
2025-07-18 10:31:12 +00:00
// Order a new card
const newCard = await BunqCard.order(bunq, {
type: 'MASTERCARD',
subType: 'PHYSICAL',
2025-07-18 10:31:12 +00:00
nameOnCard: 'JOHN DOE',
secondLine: 'Travel Card',
monetaryAccountId: account.id
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()
.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'));
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
### OAuth Integration
```typescript
// Create OAuth client
const oauth = new BunqOAuth({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/callback'
2025-07-18 10:31:12 +00:00
});
// Generate authorization URL
const authUrl = oauth.getAuthorizationUrl({
state: 'random-state-string',
accounts: ['NL91ABNA0417164300'] // Pre-select accounts
2025-07-18 10:31:12 +00:00
});
// Exchange code for access token
const token = await oauth.exchangeCode(authorizationCode);
// Use OAuth token with bunq client
const bunq = new BunqAccount({
accessToken: token.access_token,
environment: 'PRODUCTION'
2025-07-18 10:31:12 +00:00
});
```
2025-07-18 10:31:12 +00:00
### Error Handling
```typescript
import { BunqApiError, BunqRateLimitError, BunqAuthError } 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 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);
}
}
```
### 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();
// Sandbox-specific features
await sandboxBunq.topUpSandboxAccount(account.id, '500.00');
await sandboxBunq.simulateCardTransaction(card.id, '25.00', 'NL');
```
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
## Contributing
2025-07-18 12:10:29 +00:00
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
2025-07-18 12:10:29 +00:00
## 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/)
2025-07-18 10:31:12 +00:00
## License
2020-06-20 01:47:53 +00:00
MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
2020-06-20 01:47:53 +00:00
---
2020-06-20 01:47:53 +00:00
For further information read the linked docs at the top of this readme.
> By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
2020-06-20 01:47:53 +00:00
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)