# @apiclient.xyz/bunq A powerful, type-safe TypeScript/JavaScript client for the bunq API with full feature coverage ## Features ### 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 ```bash 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 the client const bunq = new BunqAccount({ apiKey: 'your-api-key', deviceName: 'My App', environment: 'PRODUCTION' // or 'SANDBOX' for testing }); // Initialize connection await bunq.init(); // Get your accounts const accounts = await bunq.getAccounts(); console.log(`Found ${accounts.length} accounts`); // Get recent transactions const transactions = await accounts[0].getTransactions(); transactions.forEach(tx => { console.log(`${tx.created}: ${tx.amount.value} ${tx.amount.currency} - ${tx.description}`); }); // Always cleanup when done await bunq.stop(); ``` ## Core Examples ### Account Management ```typescript // Get all accounts with details const accounts = await bunq.getAccounts(); for (const account of accounts) { console.log(`Account: ${account.description}`); console.log(`Balance: ${account.balance.value} ${account.balance.currency}`); console.log(`IBAN: ${account.iban}`); // Get account-specific transactions const transactions = await account.getTransactions({ count: 50, // Last 50 transactions newer_id: false, older_id: false }); } // Create a new monetary account (business accounts only) const newAccount = await BunqMonetaryAccount.create(bunq, { currency: 'EUR', description: 'Savings Account', dailyLimit: '1000.00', overdraftLimit: '0.00' }); ``` ### Making Payments #### Simple Payment ```typescript // Using the payment builder pattern const payment = await BunqPayment.builder(bunq, account) .amount('25.00', 'EUR') .toIban('NL91ABNA0417164300', 'John Doe') .description('Birthday gift') .create(); console.log(`Payment created with ID: ${payment.id}`); ``` #### Payment with Custom Request ID (Idempotency) ```typescript // Prevent duplicate payments with custom request IDs const payment = await BunqPayment.builder(bunq, account) .amount('100.00', 'EUR') .toIban('NL91ABNA0417164300', 'Supplier B.V.') .description('Invoice #12345') .customRequestId('invoice-12345-payment') // Prevents duplicate payments .create(); ``` #### Batch Payments ```typescript 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('Rent payment') .scheduleOnce(tomorrow.toISOString()) .create(); // Recurring monthly payment const recurringId = await BunqSchedulePayment.builder(bunq, account) .amount('9.99', 'EUR') .toIban('NL91ABNA0417164300', 'Netflix B.V.') .description('Monthly subscription') .scheduleMonthly('2024-01-01T10:00:00Z', '2024-12-31T10:00:00Z') .create(); // List all scheduled payments const schedules = await scheduler.list(account); // Cancel a scheduled payment await scheduler.delete(account, scheduledId); ``` ### Payment Requests ```typescript // Create a payment request const request = await BunqRequestInquiry.builder(bunq, account) .amount('25.00', 'EUR') .fromEmail('friend@example.com', 'My Friend') .description('Lunch money') .allowBunqme() // Generate bunq.me link .minimumAge(18) .create(); console.log(`Share this link: ${request.bunqmeShareUrl}`); // List pending requests const requests = await BunqRequestInquiry.list(bunq, account.id); const pending = requests.filter(r => r.status === 'PENDING'); // Cancel a request await request.update(requestId, { status: 'CANCELLED' }); ``` ### Draft Payments (Requires Approval) ```typescript const draft = new BunqDraftPayment(bunq, account); // Create a draft with multiple payments const draftId = await draft.create({ numberOfRequiredAccepts: 2, // Requires 2 approvals entries: [ { amount: { value: '1000.00', currency: 'EUR' }, counterparty_alias: { type: 'IBAN', value: 'NL91ABNA0417164300', name: 'Supplier A' }, description: 'Invoice payment' }, { amount: { value: '2000.00', currency: 'EUR' }, counterparty_alias: { type: 'IBAN', value: 'NL91ABNA0417164300', name: 'Supplier B' }, description: 'Equipment purchase' } ] }); // Approve the draft await draft.accept(); // Or reject it await draft.reject('Budget exceeded'); ``` ### Card Management ```typescript // List all cards const cards = await BunqCard.list(bunq); // Activate a new card const card = cards.find(c => c.status === 'INACTIVE'); if (card) { await card.activate('123456'); // Activation code } // Update spending limits await card.updateLimit('500.00', 'EUR'); // 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 // Setup webhook server const webhookServer = new BunqWebhookServer(bunq, { port: 3000, publicUrl: 'https://myapp.com/webhooks' }); // Register event handlers webhookServer.getHandler().onPayment((payment) => { console.log(`New payment: ${payment.amount.value} ${payment.amount.currency}`); console.log(`From: ${payment.counterparty_alias.display_name}`); console.log(`Description: ${payment.description}`); // Your business logic here updateDatabase(payment); sendNotification(payment); }); webhookServer.getHandler().onRequest((request) => { console.log(`New payment request: ${request.amount_inquired.value}`); console.log(`From: ${request.user_alias_created.display_name}`); }); webhookServer.getHandler().onCard((card) => { if (card.status === 'BLOCKED') { console.log(`Card blocked: ${card.name_on_card}`); alertSecurityTeam(card); } }); // 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); ``` ### File Attachments ```typescript const attachment = new BunqAttachment(bunq); // Upload a file const attachmentUuid = await attachment.uploadFile( '/path/to/invoice.pdf', 'Invoice #12345' ); // Attach to payment const payment = await BunqPayment.builder(bunq, account) .amount('150.00', 'EUR') .toIban('NL91ABNA0417164300', 'Accountant') .description('Services rendered') .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' } ] }); // Session management const session = bunq.apiContext.getSession(); console.log(`Session expires: ${session.expiryTime}`); // Manual session refresh await bunq.apiContext.refreshSession(); // Save session for later use const sessionData = bunq.apiContext.exportSession(); await fs.writeFile('bunq-session.json', JSON.stringify(sessionData)); // Restore session const savedSession = JSON.parse(await fs.readFile('bunq-session.json')); bunq.apiContext.importSession(savedSession); ``` ## Advanced Usage ### OAuth Integration ```typescript // Create OAuth client const oauth = new BunqOAuth({ clientId: 'your-client-id', clientSecret: 'your-client-secret', redirectUri: 'https://yourapp.com/callback' }); // 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({ accessToken: token.access_token, environment: 'PRODUCTION' }); ``` ### Error Handling ```typescript import { BunqApiError, BunqRateLimitError, BunqAuthError } from '@apiclient.xyz/bunq'; try { await payment.create(); } catch (error) { if (error instanceof BunqApiError) { // Handle API errors console.error('API Error:', error.errors); error.errors.forEach(e => { console.error(`- ${e.error_description}`); }); } else if (error instanceof BunqRateLimitError) { // Handle rate limiting console.error('Rate limited. Retry after:', error.retryAfter); await sleep(error.retryAfter * 1000); } else if (error instanceof BunqAuthError) { // Handle authentication errors console.error('Authentication failed:', error.message); await bunq.reinitialize(); } else { // Handle other errors console.error('Unexpected error:', error); } } ``` ### Pagination ```typescript // Paginate through all transactions async function* getAllTransactions(account: BunqMonetaryAccount) { let olderId: number | false = false; while (true) { const transactions = await account.getTransactions({ count: 200, older_id: olderId }); if (transactions.length === 0) break; yield* transactions; olderId = transactions[transactions.length - 1].id; } } // Usage for await (const transaction of getAllTransactions(account)) { console.log(`${transaction.created}: ${transaction.description}`); } ``` ### Sandbox Testing ```typescript // Create sandbox environment const sandboxBunq = new BunqAccount({ apiKey: '', // Will be generated deviceName: 'My Test App', environment: 'SANDBOX' }); // Create sandbox user with โ‚ฌ1000 balance const apiKey = await sandboxBunq.createSandboxUser(); console.log('Sandbox API key:', apiKey); // 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 14.x or higher - TypeScript 4.5 or higher (for TypeScript users) ## Contributing 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. > 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)