2025-07-18 10:31:12 +00:00
|
|
|
# @apiclient.xyz/bunq
|
2025-07-18 12:31:42 +00:00
|
|
|
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
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
```
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
```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';
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Initialize the client
|
2025-07-18 10:31:12 +00:00
|
|
|
const bunq = new BunqAccount({
|
|
|
|
apiKey: 'your-api-key',
|
|
|
|
deviceName: 'My App',
|
2025-07-18 12:31:42 +00:00
|
|
|
environment: 'PRODUCTION' // or 'SANDBOX' for testing
|
2025-07-18 10:31:12 +00:00
|
|
|
});
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Initialize connection
|
2025-07-18 10:31:12 +00:00
|
|
|
await bunq.init();
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Get your accounts
|
2025-07-18 10:31:12 +00:00
|
|
|
const accounts = await bunq.getAccounts();
|
2025-07-18 12:31:42 +00:00
|
|
|
console.log(`Found ${accounts.length} accounts`);
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Get recent transactions
|
2025-07-18 10:31:12 +00:00
|
|
|
const transactions = await accounts[0].getTransactions();
|
2025-07-18 12:31:42 +00:00
|
|
|
transactions.forEach(tx => {
|
|
|
|
console.log(`${tx.created}: ${tx.amount.value} ${tx.amount.currency} - ${tx.description}`);
|
|
|
|
});
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Always cleanup when done
|
2025-07-18 10:31:12 +00:00
|
|
|
await bunq.stop();
|
|
|
|
```
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
## Core Examples
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
### Account Management
|
2025-07-18 10:31:12 +00:00
|
|
|
|
|
|
|
```typescript
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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
|
|
|
|
2025-07-18 12:31:42 +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')
|
2025-07-18 12:31:42 +00:00
|
|
|
.description('Birthday gift')
|
2025-07-18 10:31:12 +00:00
|
|
|
.create();
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
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);
|
2025-07-18 12:31:42 +00:00
|
|
|
|
|
|
|
// Create multiple payments in one API call
|
|
|
|
const batchId = await batch.create(account, [
|
2025-07-18 12:10:29 +00:00
|
|
|
{
|
2025-07-18 12:31:42 +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
|
|
|
},
|
|
|
|
{
|
2025-07-18 12:31:42 +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
|
|
|
}
|
|
|
|
]);
|
2025-07-18 12:31:42 +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
|
|
|
```
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
### Payment Requests
|
2025-07-18 10:31:12 +00:00
|
|
|
|
|
|
|
```typescript
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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
|
|
|
|
2025-07-18 12:31:42 +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);
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Update PIN
|
|
|
|
await card.updatePin('1234', '5678');
|
2025-07-18 10:31:12 +00:00
|
|
|
|
|
|
|
// Block a card
|
2025-07-18 12:31:42 +00:00
|
|
|
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, {
|
2025-07-18 12:31:42 +00:00
|
|
|
type: 'MASTERCARD',
|
|
|
|
subType: 'PHYSICAL',
|
2025-07-18 10:31:12 +00:00
|
|
|
nameOnCard: 'JOHN DOE',
|
2025-07-18 12:31:42 +00:00
|
|
|
secondLine: 'Travel Card',
|
|
|
|
monetaryAccountId: account.id
|
2025-07-18 10:31:12 +00:00
|
|
|
});
|
|
|
|
```
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
### Webhooks
|
2025-07-18 10:31:12 +00:00
|
|
|
|
|
|
|
```typescript
|
2025-07-18 12:31:42 +00:00
|
|
|
// Setup webhook server
|
|
|
|
const webhookServer = new BunqWebhookServer(bunq, {
|
|
|
|
port: 3000,
|
|
|
|
publicUrl: 'https://myapp.com/webhooks'
|
|
|
|
});
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Start server and register with bunq
|
|
|
|
await webhookServer.start();
|
|
|
|
await webhookServer.register();
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Manual webhook management
|
|
|
|
const webhook = new BunqWebhook(bunq, account);
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
2025-07-18 12:31:42 +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);
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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();
|
2025-07-18 12:31:42 +00:00
|
|
|
|
|
|
|
// 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
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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');
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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');
|
2025-07-18 12:31:42 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
```
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
### User & Session Management
|
2025-07-18 10:31:12 +00:00
|
|
|
|
|
|
|
```typescript
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
});
|
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
});
|
|
|
|
|
2025-07-18 12:31:42 +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 12:31:42 +00:00
|
|
|
```
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +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
|
2025-07-18 12:31:42 +00:00
|
|
|
// Create sandbox environment
|
2025-07-18 10:31:12 +00:00
|
|
|
const sandboxBunq = new BunqAccount({
|
2025-07-18 12:31:42 +00:00
|
|
|
apiKey: '', // Will be generated
|
|
|
|
deviceName: 'My Test App',
|
2025-07-18 10:31:12 +00:00
|
|
|
environment: 'SANDBOX'
|
|
|
|
});
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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);
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Re-initialize with the generated key
|
2025-07-18 10:31:12 +00:00
|
|
|
const bunq = new BunqAccount({
|
|
|
|
apiKey: apiKey,
|
2025-07-18 12:31:42 +00:00
|
|
|
deviceName: 'My Test App',
|
2025-07-18 10:31:12 +00:00
|
|
|
environment: 'SANDBOX'
|
|
|
|
});
|
|
|
|
await bunq.init();
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// 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
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
```typescript
|
|
|
|
// Old
|
|
|
|
import BunqJSClient from '@bunq-community/bunq-js-client';
|
|
|
|
const bunqJSClient = new BunqJSClient();
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
// Old
|
|
|
|
await bunqJSClient.install();
|
|
|
|
await bunqJSClient.registerDevice();
|
|
|
|
await bunqJSClient.registerSession();
|
2025-07-18 10:31:12 +00:00
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
The library includes comprehensive test coverage:
|
2025-07-18 12:10:29 +00:00
|
|
|
|
|
|
|
```bash
|
2025-07-18 12:31:42 +00:00
|
|
|
# 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
|
|
|
```
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
## Requirements
|
2025-07-18 12:10:29 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
- Node.js 14.x or higher
|
|
|
|
- TypeScript 4.5 or higher (for TypeScript users)
|
2025-07-18 12:10:29 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
## Contributing
|
2025-07-18 12:10:29 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
2025-07-18 12:10:29 +00:00
|
|
|
|
2025-07-18 12:31:42 +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
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
## License
|
2020-06-20 01:47:53 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
2020-06-20 01:47:53 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
---
|
2020-06-20 01:47:53 +00:00
|
|
|
|
|
|
|
For further information read the linked docs at the top of this readme.
|
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
> By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
2020-06-20 01:47:53 +00:00
|
|
|
|
2025-07-18 12:31:42 +00:00
|
|
|
[](https://maintainedby.lossless.com)
|