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:
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)
|
Reference in New Issue
Block a user