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:
2025-07-18 12:31:42 +00:00
parent be09571604
commit 036ddce829
4 changed files with 686 additions and 226 deletions

129
changelog.md Normal file
View 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 bugfix 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
View File

@@ -1,26 +1,31 @@
# @apiclient.xyz/bunq # @apiclient.xyz/bunq
A full-featured TypeScript/JavaScript client for the bunq API A powerful, type-safe TypeScript/JavaScript client for the bunq API with full feature coverage
## 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/)
## Features ## Features
- Complete bunq API implementation ### Core Banking Operations
- TypeScript support with full type definitions - 💳 **Complete Account Management** - Access all account types (personal, business, joint)
- Automatic session management and renewal - 💸 **Advanced Payment Processing** - Single payments, batch payments, scheduled payments
- Request signing and response verification - 📊 **Transaction History** - Full transaction access with filtering and pagination
- Support for all account types (personal, business, joint) - 💰 **Payment Requests** - Send and manage payment requests with bunq.me integration
- Payment and transaction management - 📝 **Draft Payments** - Create payments requiring approval
- Card management and controls
- Scheduled and draft payments ### Advanced Features
- File attachments and exports - 🔄 **Automatic Session Management** - Handles token refresh and session renewal
- Webhook support - 🔐 **Full Security Implementation** - Request signing and response verification
- OAuth authentication - 🎯 **Webhook Support** - Real-time notifications with signature verification
- Sandbox environment support - 💳 **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 ## Installation
@@ -28,294 +33,610 @@ A full-featured TypeScript/JavaScript client for the bunq API
npm install @apiclient.xyz/bunq npm install @apiclient.xyz/bunq
``` ```
```bash
yarn add @apiclient.xyz/bunq
```
```bash
pnpm add @apiclient.xyz/bunq
```
## Quick Start ## Quick Start
```typescript ```typescript
import { BunqAccount } from '@apiclient.xyz/bunq'; import { BunqAccount } from '@apiclient.xyz/bunq';
// Initialize bunq client // Initialize the client
const bunq = new BunqAccount({ const bunq = new BunqAccount({
apiKey: 'your-api-key', apiKey: 'your-api-key',
deviceName: 'My App', deviceName: 'My App',
environment: 'PRODUCTION' // or 'SANDBOX' environment: 'PRODUCTION' // or 'SANDBOX' for testing
}); });
// Initialize the client // Initialize connection
await bunq.init(); await bunq.init();
// Get all monetary accounts // Get your accounts
const accounts = await bunq.getAccounts(); 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(); 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(); await bunq.stop();
``` ```
## Usage Examples ## Core Examples
### Making Payments ### Account Management
```typescript ```typescript
import { BunqPayment } from '@apiclient.xyz/bunq'; // Get all accounts with details
const accounts = await bunq.getAccounts();
// Simple payment for (const account of accounts) {
const payment = BunqPayment.builder(bunq, monetaryAccount) console.log(`Account: ${account.description}`);
.amount('10.00', 'EUR') console.log(`Balance: ${account.balance.value} ${account.balance.currency}`);
.toIban('NL91ABNA0417164300', 'John Doe') console.log(`IBAN: ${account.iban}`);
.description('Coffee payment')
.create(); // Get account-specific transactions
const transactions = await account.getTransactions({
count: 50, // Last 50 transactions
newer_id: false,
older_id: false
});
}
// Batch payment // Create a new monetary account (business accounts only)
const batch = new BunqPaymentBatch(bunq); const newAccount = await BunqMonetaryAccount.create(bunq, {
const batchId = await batch.create(monetaryAccount, [ currency: 'EUR',
{ description: 'Savings Account',
amount: { value: '5.00', currency: 'EUR' }, dailyLimit: '1000.00',
counterparty_alias: { type: 'IBAN', value: 'NL91ABNA0417164300', name: 'Recipient 1' }, overdraftLimit: '0.00'
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'
}); });
``` ```
### Scheduled Payments ### Making Payments
#### Simple Payment
```typescript ```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 console.log(`Payment created with ID: ${payment.id}`);
const scheduled = await BunqSchedulePayment.builder(bunq, monetaryAccount) ```
#### 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') .amount('50.00', 'EUR')
.toIban('NL91ABNA0417164300', 'Landlord') .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') .scheduleMonthly('2024-01-01T10:00:00Z', '2024-12-31T10:00:00Z')
.create(); .create();
// List scheduled payments // List all scheduled payments
const scheduler = new BunqSchedulePayment(bunq); const schedules = await scheduler.list(account);
const schedules = await scheduler.list(monetaryAccount);
// Cancel a scheduled payment
await scheduler.delete(account, scheduledId);
``` ```
### Request Money ### Payment Requests
```typescript ```typescript
import { BunqRequestInquiry } from '@apiclient.xyz/bunq';
// Create a payment request // Create a payment request
const request = BunqRequestInquiry.builder(bunq, monetaryAccount) const request = await BunqRequestInquiry.builder(bunq, account)
.amount('25.00', 'EUR') .amount('25.00', 'EUR')
.fromEmail('friend@example.com', 'My Friend') .fromEmail('friend@example.com', 'My Friend')
.description('Dinner split') .description('Lunch money')
.allowBunqme() .allowBunqme() // Generate bunq.me link
.minimumAge(18)
.create(); .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 ```typescript
import { BunqAttachment } from '@apiclient.xyz/bunq'; const draft = new BunqDraftPayment(bunq, account);
// Upload a file // Create a draft with multiple payments
const attachment = new BunqAttachment(bunq); const draftId = await draft.create({
const attachmentUuid = await attachment.uploadFile('/path/to/receipt.pdf', 'Receipt'); 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 // Approve the draft
const payment = BunqPayment.builder(bunq, monetaryAccount) await draft.accept();
.amount('99.99', 'EUR')
.toIban('NL91ABNA0417164300') // Or reject it
.description('Purchase with receipt') await draft.reject('Budget exceeded');
.attachments([attachmentUuid])
.create();
``` ```
### Export Statements ### Card Management
```typescript ```typescript
import { BunqExport, ExportBuilder } from '@apiclient.xyz/bunq'; // List all cards
const cards = await BunqCard.list(bunq);
// Export last month's transactions as PDF // Activate a new card
await new ExportBuilder(bunq, monetaryAccount) const card = cards.find(c => c.status === 'INACTIVE');
.asPdf() if (card) {
.lastMonth() await card.activate('123456'); // Activation code
.downloadTo('/path/to/statement.pdf'); }
// Export custom date range as CSV // Update spending limits
await new ExportBuilder(bunq, monetaryAccount) await card.updateLimit('500.00', 'EUR');
.asCsv()
.dateRange('2024-01-01', '2024-03-31') // Update PIN
.regionalFormat('EUROPEAN') await card.updatePin('1234', '5678');
.downloadTo('/path/to/transactions.csv');
// 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 ### Webhooks
```typescript ```typescript
import { BunqWebhookServer, BunqNotification } from '@apiclient.xyz/bunq';
// Setup webhook server // Setup webhook server
const webhookServer = new BunqWebhookServer(bunq, { const webhookServer = new BunqWebhookServer(bunq, {
port: 3000, port: 3000,
publicUrl: 'https://myapp.com' publicUrl: 'https://myapp.com/webhooks'
}); });
// Register handlers // Register event handlers
webhookServer.getHandler().onPayment((payment) => { 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) => { 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 // Start server and register with bunq
await webhookServer.start(); await webhookServer.start();
await webhookServer.register(); 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 ```typescript
// Create a sandbox account const attachment = new BunqAttachment(bunq);
const sandboxBunq = new BunqAccount({
apiKey: '', // Will be generated // Upload a file
deviceName: 'Sandbox Test', const attachmentUuid = await attachment.uploadFile(
environment: 'SANDBOX' '/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 // Session management
const apiKey = await sandboxBunq.createSandboxUser(); const session = bunq.apiContext.getSession();
console.log('Sandbox API key:', apiKey); 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({ const bunq = new BunqAccount({
apiKey: apiKey, accessToken: token.access_token,
deviceName: 'Sandbox Test', environment: 'PRODUCTION'
environment: 'SANDBOX'
}); });
await bunq.init();
``` ```
## API Reference ### Error Handling
### 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
```typescript ```typescript
import { BunqApiError, BunqRateLimitError, BunqAuthError } from '@apiclient.xyz/bunq';
try { try {
await payment.create(); await payment.create();
} catch (error) { } catch (error) {
if (error instanceof BunqApiError) { 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 { } else {
// Handle other errors
console.error('Unexpected error:', error); 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 // Usage
npm test # Run all tests for await (const transaction of getAllTransactions(account)) {
npm run test:basic # Run basic functionality tests console.log(`${transaction.created}: ${transaction.description}`);
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
``` ```
### 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 // Create sandbox user with €1000 balance
- **Payments**: Payment builders, draft payments, payment requests const apiKey = await sandboxBunq.createSandboxUser();
- **Webhooks**: Creation, management, signature verification console.log('Sandbox API key:', apiKey);
- **Session management**: Persistence, expiry, concurrent usage
- **Error handling**: Network errors, invalid inputs, rate limiting
- **Advanced features**: Joint accounts, cards, notifications, exports
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 ## Requirements
- Node.js 10.x or higher - Node.js 14.x or higher
- TypeScript 3.x or higher (for TypeScript users) - 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. 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)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com) [![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

View File

@@ -52,41 +52,43 @@ tap.test('should create test setup with multiple accounts', async () => {
}); });
tap.test('should create and execute a payment draft', 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 // Create a draft payment
const draftId = await draft.create(primaryAccount, { const draftId = await draft.create({
amount: { numberOfRequiredAccepts: 1,
currency: 'EUR', entries: [{
value: '5.00' amount: {
}, currency: 'EUR',
counterparty_alias: { value: '5.00'
type: 'IBAN', },
value: 'NL91ABNA0417164300', counterparty_alias: {
name: 'Draft Test Recipient' type: 'IBAN',
}, value: 'NL91ABNA0417164300',
description: 'Test draft payment' name: 'Draft Test Recipient'
},
description: 'Test draft payment entry'
}]
}); });
expect(draftId).toBeTypeofNumber(); expect(draftId).toBeTypeofNumber();
console.log(`Created draft payment with ID: ${draftId}`); console.log(`Created draft payment with ID: ${draftId}`);
// List drafts // List drafts
const drafts = await draft.list(primaryAccount); const drafts = await bunq.BunqDraftPayment.list(testBunqAccount, primaryAccount.id);
expect(drafts).toBeArray(); expect(drafts).toBeArray();
expect(drafts.length).toBeGreaterThan(0); 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).toBeDefined();
expect(createdDraft?.amount.value).toBe('5.00');
// Update the draft // Update the draft
await draft.update(primaryAccount, draftId, { await draft.update(draftId, {
description: 'Updated draft payment description' description: 'Updated draft payment description'
}); });
// Get updated draft // Get updated draft
const updatedDraft = await draft.get(primaryAccount, draftId); const updatedDraft = await draft.get(draftId);
expect(updatedDraft.description).toBe('Updated draft payment description'); expect(updatedDraft.description).toBe('Updated draft payment description');
console.log('Draft payment updated successfully'); console.log('Draft payment updated successfully');
@@ -224,33 +226,33 @@ tap.test('should test scheduled payments', async () => {
}); });
tap.test('should test payment requests', 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 // Create a payment request
try { try {
const requestId = await paymentRequest.create(primaryAccount, { const requestId = await paymentRequest.create({
amount: { amountInquired: {
currency: 'EUR', currency: 'EUR',
value: '15.00' value: '15.00'
}, },
counterparty_alias: { counterpartyAlias: {
type: 'EMAIL', type: 'EMAIL',
value: 'requester@example.com', value: 'requester@example.com',
name: 'Request Sender' name: 'Request Sender'
}, },
description: 'Payment request test', description: 'Payment request test',
allow_bunqme: true allowBunqme: true
}); });
expect(requestId).toBeTypeofNumber(); expect(requestId).toBeTypeofNumber();
console.log(`Created payment request with ID: ${requestId}`); console.log(`Created payment request with ID: ${requestId}`);
// List requests // List requests
const requests = await paymentRequest.list(primaryAccount); const requests = await bunq.BunqRequestInquiry.list(testBunqAccount, primaryAccount.id);
expect(requests).toBeArray(); expect(requests).toBeArray();
// Cancel the request // Cancel the request
await paymentRequest.update(primaryAccount, requestId, { await paymentRequest.update(requestId, {
status: 'CANCELLED' status: 'CANCELLED'
}); });
console.log('Payment request cancelled successfully'); 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 () => { 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 // First create a request to respond to
const paymentRequest = new bunq.BunqRequestInquiry(testBunqAccount); const paymentRequest = new bunq.BunqRequestInquiry(testBunqAccount, primaryAccount);
try { try {
// Create a self-request (from same account) for testing // Create a self-request (from same account) for testing
const requestId = await paymentRequest.create(primaryAccount, { const requestId = await paymentRequest.create({
amount: { amountInquired: {
currency: 'EUR', currency: 'EUR',
value: '5.00' value: '5.00'
}, },
counterparty_alias: { counterpartyAlias: {
type: 'IBAN', type: 'IBAN',
value: primaryAccount.iban, value: primaryAccount.iban,
name: primaryAccount.displayName 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}`); console.log(`Created self-request with ID: ${requestId}`);
// Accept the request // Accept the request
const responseId = await paymentResponse.accept(primaryAccount, requestId); const responseId = await paymentResponse.accept(requestId);
expect(responseId).toBeTypeofNumber(); expect(responseId).toBeTypeofNumber();
console.log(`Accepted request with response ID: ${responseId}`); console.log(`Accepted request with response ID: ${responseId}`);
} catch (error) { } catch (error) {

8
ts/00_commitinfo_data.ts Normal file
View 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'
}