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