import crypto from 'crypto'; const API_BASE = 'https://api.enablebanking.com'; function base64url(input) { return Buffer.from(input) .toString('base64') .replace(/=/g, '') .replace(/\+/g, '-') .replace(/\//g, '_'); } export function publicEnableBankingConfig(config) { return { ...config, private_key_pem: undefined, has_private_key: Boolean(config.private_key_pem?.trim()), transaction_categories: config.transaction_categories || {}, accounts: (config.accounts || []).map(account => ({ ...account, display_name: account.alias || account.name || 'Konto', })), }; } export function createEnableBankingJwt({ application_id, private_key_pem, expiresInSeconds = 3600 }) { if (!application_id?.trim()) throw new Error('Enable Banking application_id saknas'); if (!private_key_pem?.trim()) throw new Error('Enable Banking private key saknas'); const iat = Math.floor(Date.now() / 1000); const header = { typ: 'JWT', alg: 'RS256', kid: application_id.trim(), }; const payload = { iss: 'enablebanking.com', aud: 'api.enablebanking.com', iat, exp: iat + expiresInSeconds, }; const unsignedToken = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(payload))}`; const signer = crypto.createSign('RSA-SHA256'); signer.update(unsignedToken); signer.end(); const signature = signer.sign(private_key_pem.trim()); return `${unsignedToken}.${base64url(signature)}`; } export async function enableBankingRequest(config, path, { method = 'GET', body } = {}) { const token = createEnableBankingJwt(config); const response = await fetch(`${API_BASE}${path}`, { method, headers: { Authorization: `Bearer ${token}`, Accept: 'application/json', ...(body ? { 'Content-Type': 'application/json' } : {}), }, body: body ? JSON.stringify(body) : undefined, }); const text = await response.text(); let data = null; if (text) { try { data = JSON.parse(text); } catch { data = text; } } if (!response.ok) { const message = typeof data === 'object' && data?.message ? data.message : typeof data === 'object' && data?.error ? data.error : text || `Enable Banking API error ${response.status}`; const error = new Error(`${message} [${method} ${path}]`); error.status = response.status; error.details = data; error.path = path; error.method = method; throw error; } return data; } export async function fetchAspsps(config, country) { return enableBankingRequest(config, `/aspsps?country=${encodeURIComponent(country)}`); } export async function startAuthorization(config, body) { return enableBankingRequest(config, '/auth', { method: 'POST', body, }); } export async function createSession(config, code) { return enableBankingRequest(config, '/sessions', { method: 'POST', body: { code }, }); } export async function getSession(config, sessionId) { return enableBankingRequest(config, `/sessions/${sessionId}`); } export async function deleteSession(config, sessionId) { return enableBankingRequest(config, `/sessions/${sessionId}`, { method: 'DELETE' }); } export async function getAccountDetails(config, accountId) { return enableBankingRequest(config, `/accounts/${accountId}/details`); } export async function getAccountBalances(config, accountId) { return enableBankingRequest(config, `/accounts/${accountId}/balances`); } export async function getAllTransactions(config, accountId, options = {}) { const allTransactions = []; let continuationKey = null; do { const variants = [ { includeDateFrom: true, includeDateTo: true, includeStrategy: true }, { includeDateFrom: true, includeDateTo: false, includeStrategy: true }, { includeDateFrom: true, includeDateTo: false, includeStrategy: false }, { includeDateFrom: false, includeDateTo: false, includeStrategy: false }, ]; let page = null; let lastError = null; for (const variant of variants) { const params = new URLSearchParams(); if (variant.includeDateFrom && options.date_from) params.set('date_from', options.date_from); if (variant.includeDateTo && options.date_to) params.set('date_to', options.date_to); if (continuationKey) params.set('continuation_key', continuationKey); if (variant.includeStrategy) params.set('strategy', 'all'); const query = params.toString(); try { page = await enableBankingRequest( config, `/accounts/${accountId}/transactions${query ? `?${query}` : ''}` ); break; } catch (error) { lastError = error; const badParams = (error.status === 400 || error.status === 422) && String(error.message || '').toLowerCase().includes('wrong request parameters provided'); if (!badParams) { throw error; } } } if (!page) { throw lastError || new Error('Kunde inte hämta transaktioner'); } const transactionGroups = Array.isArray(page?.transactions) ? page.transactions : [ ...((page?.transactions?.booked ?? []).map(transaction => ({ ...transaction, status: transaction.status || 'booked' }))), ...((page?.transactions?.pending ?? []).map(transaction => ({ ...transaction, status: transaction.status || 'pending' }))), ...((page?.booked ?? []).map(transaction => ({ ...transaction, status: transaction.status || 'booked' }))), ...((page?.pending ?? []).map(transaction => ({ ...transaction, status: transaction.status || 'pending' }))), ]; allTransactions.push(...transactionGroups); continuationKey = page?.continuation_key || null; } while (continuationKey); return allTransactions; }