Skip to content

Integrate Enable Banking as a bank sync provider - #7345

Merged
matt-fidd merged 34 commits into
actualbudget:masterfrom
AurelDemiri:feature/enable-banking
May 11, 2026
Merged

Integrate Enable Banking as a bank sync provider#7345
matt-fidd merged 34 commits into
actualbudget:masterfrom
AurelDemiri:feature/enable-banking

Conversation

@AurelDemiri

@AurelDemiri AurelDemiri commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Description

Alternative implementation of Enable Banking as a bank sync provider (EU banks), replacing the approach in #5570 with a leaner architecture and fewer dependencies. This implementation tries its best to copy the patterns and behavior from the existing bank sync integrations.

Improvements over PR #5570 are:

  • Server-side long-polling instead of client-side busy polling
  • PSU header forwarding so we are not limited to 4 req/day under PSD2
  • Consent validity: respects ASPSP maximum_consent_validity, capped at 90-day default. PR 5570 hardcodes to 180 days
  • Proper request timeouts
  • More test cases

My React knowledge is fairly limited. I’m more comfortable with Vue or Angular, so I’d appreciate any comments.

Related issue(s)

Fixes #5445, Fixes #5505

Supersedes PR #5570

Testing

  • Tested end-to-end against Enable Banking sandbox and production environments using Belfius as ASPSP
  • yarn typecheck passes
  • yarn lint passes
  • yarn test passes (17 Enable Banking normalization specs, plus service, poll-auth, error, and JWT test suites)

Checklist

  • Release notes added (see link above)
  • No obvious regressions in affected areas
  • Self-review has been performed - I understand what each change in the code does and why it is needed

Bundle Stats

Bundle Files count Total bundle size % Changed
desktop-client 34 13.93 MB → 13.96 MB (+27.07 kB) +0.19%
loot-core 1 5.27 MB → 5.28 MB (+6.6 kB) +0.12%
api 2 3.9 MB → 3.9 MB (+6.46 kB) +0.16%
cli 1 7.97 MB 0%
crdt 1 43.41 kB 0%
View detailed bundle stats

desktop-client

Total

Files count Total bundle size % Changed
34 13.93 MB → 13.96 MB (+27.07 kB) +0.19%
Changeset
File Δ Size
src/components/modals/EnableBankingExternalMsgModal.tsx 🆕 +10.53 kB 0 B → 10.53 kB
src/components/modals/EnableBankingInitialiseModal.tsx 🆕 +5.47 kB 0 B → 5.47 kB
src/components/EnableBankingCallback.tsx 🆕 +2.79 kB 0 B → 2.79 kB
src/enablebanking.ts 🆕 +2.15 kB 0 B → 2.15 kB
src/hooks/useEnableBankingStatus.ts 🆕 +740 B 0 B → 740 B
src/components/banksync/useBuiltInBankSyncProviders.ts 📈 +2.52 kB (+28.84%) 8.74 kB → 11.26 kB
src/accounts/mutations.ts 📈 +779 B (+6.04%) 12.6 kB → 13.36 kB
src/hooks/useFeatureFlag.ts 📈 +23 B (+4.23%) 544 B → 567 B
src/components/banksync/bankSyncUtils.ts 📈 +57 B (+3.88%) 1.44 kB → 1.49 kB
src/components/alerts.tsx 📈 +119 B (+3.25%) 3.57 kB → 3.69 kB
src/components/Modals.tsx 📈 +291 B (+2.26%) 12.56 kB → 12.85 kB
src/components/banksync/EditSyncAccount.tsx 📈 +168 B (+2.26%) 7.27 kB → 7.44 kB
src/components/modals/SelectLinkedAccountsModal.tsx 📈 +926 B (+2.17%) 41.6 kB → 42.51 kB
src/components/settings/Experimental.tsx 📈 +215 B (+1.93%) 10.89 kB → 11.1 kB
src/components/FinancesApp.tsx 📈 +305 B (+1.63%) 18.26 kB → 18.56 kB
src/components/accounts/AccountSyncCheck.tsx 📈 +142 B (+1.62%) 8.54 kB → 8.68 kB
package.json 📈 +45 B (+0.49%) 8.98 kB → 9.02 kB
src/components/reports/LoadingIndicator.tsx 📉 -119 B (-10.08%) 1.15 kB → 1.04 kB
View detailed bundle breakdown

Added

Asset File Size % Changed
static/js/chart-theme.js 0 B → 800.08 kB (+800.08 kB) -

Removed

Asset File Size % Changed
static/js/alerts.js 800.08 kB → 0 B (-800.08 kB) -100%

Bigger

Asset File Size % Changed
static/js/index.js 1.93 MB → 1.96 MB (+26.07 kB) +1.32%
static/js/extends.js 520.14 kB → 520.92 kB (+802 B) +0.15%
static/js/ScheduleEditForm.js 145.76 kB → 146.45 kB (+704 B) +0.47%
static/js/bankSyncUtils.js 54.1 kB → 54.15 kB (+57 B) +0.10%

Smaller

Asset File Size % Changed
static/js/narrow.js 364.41 kB → 363.89 kB (-536 B) -0.14%

Unchanged

Asset File Size % Changed
static/js/BackgroundImage.js 121.09 kB 0%
static/js/FormulaEditor.js 962.55 kB 0%
static/js/ReportRouter.js 1.22 MB 0%
static/js/TransactionEdit.js 189.54 kB 0%
static/js/TransactionList.js 85.81 kB 0%
static/js/Value.js 4.94 MB 0%
static/js/ca.js 187.91 kB 0%
static/js/client.js 451.37 kB 0%
static/js/da.js 101.38 kB 0%
static/js/de.js 170.55 kB 0%
static/js/en-GB.js 8.2 kB 0%
static/js/en.js 185.11 kB 0%
static/js/es.js 179 kB 0%
static/js/fr.js 178.9 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.46 kB 0%
static/js/it.js 165.01 kB 0%
static/js/nb-NO.js 148.31 kB 0%
static/js/nl.js 106.47 kB 0%
static/js/pl.js 86.52 kB 0%
static/js/pt-BR.js 189.58 kB 0%
static/js/resize-observer.js 18.06 kB 0%
static/js/th.js 174.76 kB 0%
static/js/theme.js 31.67 kB 0%
static/js/uk.js 207.75 kB 0%
static/js/useFormatList.js 4.96 kB 0%
static/js/wide.js 453 B 0%
static/js/workbox-window.prod.es5.js 7.33 kB 0%
static/js/zh-Hans.js 117.91 kB 0%

loot-core

Total

Files count Total bundle size % Changed
1 5.27 MB → 5.28 MB (+6.6 kB) +0.12%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/app.ts 📈 +4.94 kB (+22.50%) 21.96 kB → 26.9 kB
home/runner/work/actual/actual/packages/loot-core/src/server/post.ts 📈 +481 B (+11.90%) 3.95 kB → 4.42 kB
home/runner/work/actual/actual/packages/loot-core/src/server/server-config.ts 📈 +58 B (+6.41%) 905 B → 963 B
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/sync.ts 📈 +1.13 kB (+5.08%) 22.25 kB → 23.38 kB
View detailed bundle breakdown

Added

Asset File Size % Changed
kcab.worker.CfTbTfqX.js 0 B → 5.28 MB (+5.28 MB) -

Removed

Asset File Size % Changed
kcab.worker.hJfPtoKI.js 5.27 MB → 0 B (-5.27 MB) -100%

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged
No assets were unchanged


api

Total

Files count Total bundle size % Changed
2 3.9 MB → 3.9 MB (+6.46 kB) +0.16%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/app.ts 📈 +4.83 kB (+22.56%) 21.4 kB → 26.23 kB
home/runner/work/actual/actual/packages/loot-core/src/server/post.ts 📈 +475 B (+12.20%) 3.8 kB → 4.27 kB
home/runner/work/actual/actual/packages/loot-core/src/server/server-config.ts 📈 +57 B (+6.54%) 872 B → 929 B
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/sync.ts 📈 +1.12 kB (+5.14%) 21.73 kB → 22.85 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
index.js 3.9 MB → 3.9 MB (+6.46 kB) +0.16%

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
models.js 0 B 0%

cli

Total

Files count Total bundle size % Changed
1 7.97 MB 0%
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
cli.js 7.97 MB 0%

crdt

Total

Files count Total bundle size % Changed
1 43.41 kB 0%
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
index.js 43.41 kB 0%

Rewrite Enable Banking modal to match GoCardless pattern

Resolve Enable Banking bugs and improve auth flow
Bug fixes:
- Fix double-negative for DBIT transaction amounts (e.g. '--25.99')
- Fix payeeName counterparty mapping (CRDT→debtor, DBIT→creditor)
- Add missing state validation in EnableBankingCallback and /auth_callback
- Fix stuck loading state in useEnableBankingStatus with try/catch/finally
- Make session-expiry error matching case-insensitive
- Prefer CLAV balance type for startingBalance in /transactions route
- Guard setTimeout in post/del/patch when timeout is null
- Distinguish abort from network failure in post() catch

Credential handling:
- Add validateCredentials() to validate before persisting secrets
- Refactor client to use enablebanking-configure instead of manual secret-set
- Distinguish null (loading) from false (not configured) in setup checks

Poll-auth robustness:
- Add unique waiter IDs to prevent superseded waiter cleanup race
- Always cache results in completedAuths for retry resilience
- Add client disconnect cleanup via res.on('close')
- Cancel poll when Enable Banking modal closes via AbortController
- Prevent concurrent poll controller race with local reference check

Code quality:
- Extract buildSessionResult() to deduplicate auth_callback/complete-auth
- Add enabled parameter to useEnableBankingStatus to skip unused requests
- Add re-entrancy guard on onJump, reset bank on country change
- Refetch bank list after Enable Banking setup completes
- Type enableBankingConfigure config, make state required in completeAuth
- Add AbortError→TIMED_OUT test, fix startAuth test assertion
- Add afterAll vi.unstubAllGlobals() for test cleanup
- Add explanatory comments for bank-per-account model and in-memory maps
- Add SyncServerEnableBankingAccount to ExternalAccount union and
  getInstitutionName parameter type in SelectLinkedAccountsModal
- Use BankSyncProviders type in mobile BankSyncAccountsList instead of
  hardcoded union missing enableBanking
- Add getSecretsError handling to EnableBankingInitialiseModal for
  proper auth/permission error messages
- Replace hardcoded actualbudget#666 color with theme.pageTextSubdued
- Wrap onConnectEnableBanking in try/catch with error notification and
  init modal re-open, matching SimpleFin/PluggyAI pattern
- Translate hardcoded error string in enablebanking.ts
- Add 60s timeout to downloadEnableBankingTransactions matching PluggyAI
- Revert out-of-scope changes to del()/patch() in post.ts
- Revert shared starting balance dedup logic back to master pattern
@netlify

netlify Bot commented Mar 31, 2026

Copy link
Copy Markdown

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit 12ac717
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget/deploys/69ff54cd41cd3d0008a96c91
😎 Deploy Preview https://deploy-preview-7345.demo.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Hello contributor!

We would love to review your PR! Before we can do that, please make sure:

  • ✅ All CI checks pass
  • ✅ The PR is moved from draft to open (if applicable)
  • ✅ The "[WIP]" prefix is removed from the PR title
  • ✅ All CodeRabbit code review comments are resolved (if you disagree with anything - reply to the bot with your reasoning so we can read through it). The bot will eventually approve the PR.

We do this to reduce the TOIL the core contributor team has to go through for each PR and to allow for speedy reviews and merges.

For more information, please see our Contributing Guide.

@netlify

netlify Bot commented Mar 31, 2026

Copy link
Copy Markdown

Deploy Preview for actualbudget-website ready!

Name Link
🔨 Latest commit a90889b
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget-website/deploys/69dbf93bdf050100081bf9a4
😎 Deploy Preview https://deploy-preview-7345.www.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@AurelDemiri
AurelDemiri marked this pull request as ready for review March 31, 2026 21:52
@coderabbitai coderabbitai Bot added the API Issues with the @actual-app/api package label Mar 31, 2026
@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Enable Banking as a new bank-sync provider: client UI and modals, OAuth callback and linking mutation, feature flag and status hook; desktop routing and PWA config; server-side enablebanking proxy app, JWT-backed service client, normalization/polling endpoints, account-link handler, sync downloader, types, and tests.

Changes

Cohort / File(s) Summary
Desktop — Linking & mutations
packages/desktop-client/src/accounts/mutations.ts, packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx
New React Query mutation useLinkAccountEnableBankingMutation; SelectLinkedAccountsModal extended to accept syncSource: 'enableBanking' and call linking flow. Review: mutation payload/invalidations and error/notification handling.
Desktop — OAuth callback & routing
packages/desktop-client/src/components/EnableBankingCallback.tsx, packages/desktop-client/src/components/FinancesApp.tsx
New EnableBankingCallback component reads URL params/state, completes auth via send(...), handles errors, and closes window; route /enablebanking/auth_callback added. Review: URL/state validation and window.close timing.
Desktop — Modals & UI
packages/desktop-client/src/components/modals/EnableBankingInitialiseModal.tsx, packages/desktop-client/src/components/modals/EnableBankingExternalMsgModal.tsx, packages/desktop-client/src/components/Modals.tsx, packages/desktop-client/src/components/modals/CreateAccountModal.tsx
Adds initialise and external-linking modals; Modals registry updated; CreateAccountModal integrates setup/authorize/reset flows behind feature flag. Review: file upload parsing, secret handling, modal options typing, and async control (poll/abort).
Desktop — Labels, mobile & mappings
packages/desktop-client/src/components/banksync/index.tsx, packages/desktop-client/src/components/mobile/banksync/*, packages/desktop-client/src/components/banksync/EditSyncAccount.tsx
Adds enableBanking to provider labels/types and expands field-mapping paths for new transaction fields. Review: type alignments and i18n labels.
Desktop — Hooks, flags & config
packages/desktop-client/src/hooks/useEnableBankingStatus.ts, packages/desktop-client/src/hooks/useFeatureFlag.ts, packages/desktop-client/vite.config.mts, packages/desktop-client/package.json
Adds useEnableBankingStatus hook; default feature flag enableBanking: false; PWA denylist for /enablebanking/*; #enablebanking import alias. Review: hook effect conditions and PWA impact.
Desktop — Account sync UI behavior
packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx
Re-routes authorization dispatch by account_sync_source, calling authorizeEnableBanking when source is enableBanking. Review: dispatch side-effects per-provider.
Core types & prefs
packages/loot-core/src/types/models/enablebanking.ts, .../account.ts, .../bank-sync.ts, .../index.ts, packages/loot-core/src/types/prefs.ts
Adds Enable Banking types; extends AccountSyncSource, BankSyncProviders, and FeatureFlag unions; re-exports in barrel. Review: public type exports and consumer compatibility.
Server config & post client
packages/loot-core/src/server/server-config.ts, packages/loot-core/src/server/post.ts
Adds ENABLEBANKING_SERVER to config; post gains optional externalSignal with unified abort handling and refined error mapping. Review: abort semantics and callers passing signals.
Core accounts handlers & sync
packages/loot-core/src/server/accounts/app.ts, packages/loot-core/src/server/accounts/sync.ts
New account-link handler for Enable Banking and added RPC handlers; sync routes Enable Banking accounts to new downloader and computes starting-balance logic. Review: DB updates, payee creation, and starting-balance computation.
Sync-server — app & service
packages/sync-server/src/app-enablebanking/app-enablebanking.ts, packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts, packages/sync-server/src/app-enablebanking/utils/*
New Express enablebanking app with auth callback, poll semantics, /transactions proxy; service client implements JWT signing, request helpers, normalization, pagination, and error mapping. Review: JWT signing, PSU header forwarding, normalization correctness, and long-poll waiter lifecycle.
Sync-server — mounting & secrets & deps
packages/sync-server/src/app.ts, packages/sync-server/src/services/secrets-service.js, packages/sync-server/package.json
Mounts enablebanking router; adds secret names for applicationId/secretKey; adds jws and types; new module aliases. Review: secrets lifecycle and dependency additions.
Tests & fixtures
packages/sync-server/src/app-enablebanking/services/tests/*, packages/sync-server/src/app-enablebanking/tests/*, packages/sync-server/src/app-enablebanking/utils/tests/*
Comprehensive unit/integration tests and fixtures for service client, normalization, JWT, errors, and endpoint/polling flows. Review: test coverage and mocked behaviors.
Release notes
upcoming-release-notes/7345.md
Adds release note announcing Enable Banking integration.
sequenceDiagram
    actor User
    participant Desktop as Desktop Client
    participant Core as Loot-Core RPC
    participant SyncSvr as Sync Server (/enablebanking)
    participant EB as Enable Banking API
    participant Browser

    User->>Desktop: start Enable Banking setup
    Desktop->>Core: send('enablebanking-configure', creds)
    Core->>SyncSvr: POST /configure
    SyncSvr->>EB: Validate creds (JWT)
    SyncSvr-->>Core: {configured: true}
    Desktop->>Core: send('enablebanking-start-auth', {aspsp,country,redirectUrl})
    Core->>SyncSvr: POST /start-auth
    SyncSvr->>EB: start auth → {authUrl, state}
    SyncSvr-->>Core: {authUrl, state}
    Desktop->>Browser: window.open(authUrl)
    Browser->>SyncSvr: GET /auth_callback?code=...&state=...
    SyncSvr->>EB: exchange code → session + accounts
    SyncSvr-->>Browser: redirect/close (cache result)
    Desktop->>Core: send('enablebanking-poll-auth', {state})
    Core->>SyncSvr: POST /poll-auth
    SyncSvr-->>Core: {accounts}
    Desktop->>Core: send('enablebanking-accounts-link', {externalAccount})
    Core->>Core: create/update account, set sync source, start sync
    Core->>SyncSvr: request /transactions for account
    SyncSvr->>EB: GET /transactions
    SyncSvr-->>Core: normalized transactions & balances
    Core-->>Desktop: link success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped through code with glee and vim,
New banks to sync — a proper win!
States tucked safe and polls that wait,
OAuth windows open the gate,
Accounts arrive — carrots and cheer!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the primary change: integrating Enable Banking as a new bank sync provider.
Linked Issues check ✅ Passed The PR addresses the core requirements in issues #5445 and #5505: providing an alternative bank sync provider to replace the discontinued GoCardless API, enabling users to sync personal bank accounts.
Out of Scope Changes check ✅ Passed All changes are within scope: Enable Banking integration across desktop-client and loot-core packages, feature flag, types, modal flows, account linking, and server proxies align with the PR objective.
Description check ✅ Passed The PR description clearly relates to the changeset: it describes the Enable Banking integration as a bank sync provider with specific architectural improvements and testing details that align with the file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (2)
packages/sync-server/src/app-enablebanking/utils/jwt.ts (1)

10-18: Consider adding explicit return type annotation.

The getJWTBody function lacks an explicit return type, unlike the getJWTHeader function which has the Header type.

♻️ Suggested improvement
+type JWTPayload = {
+  iss: string;
+  aud: string;
+  iat: number;
+  exp: number;
+};
+
-function getJWTBody(exp = 3600) {
+function getJWTBody(exp = 3600): JWTPayload {
   const timestamp = Math.floor(Date.now() / 1000);
   return {
     iss: 'enablebanking.com',
     aud: 'api.enablebanking.com',
     iat: timestamp,
     exp: timestamp + exp,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sync-server/src/app-enablebanking/utils/jwt.ts` around lines 10 -
18, The getJWTBody function should have an explicit return type like
getJWTHeader does; update getJWTBody to declare and use a matching JWT body type
(e.g., JWTBody or an inline type/interface) instead of relying on inferred
typing so the function signature explicitly returns that type; reference
getJWTBody (and getJWTHeader/Header if needed) to locate the implementation and
adjust the function signature to return the defined type that includes iss, aud,
iat, and exp.
packages/desktop-client/src/enablebanking.ts (1)

6-7: Switch these to @desktop-client/... imports.

This file lives under packages/desktop-client/src, so the relative imports here are out of step with the repo convention and the rest of this change.

♻️ Suggested import update
-import { pushModal } from './modals/modalsSlice';
-import type { AppDispatch } from './redux/store';
+import { pushModal } from '@desktop-client/modals/modalsSlice';
+import { type AppDispatch } from '@desktop-client/redux/store';
As per coding guidelines, "Use absolute imports in `desktop-client` - relative imports are enforced against by ESLint."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/desktop-client/src/enablebanking.ts` around lines 6 - 7, Update the
two relative imports to use the repo's desktop-client absolute import prefix:
replace the import of pushModal from './modals/modalsSlice' with an import from
'@desktop-client/modals/modalsSlice', and replace the AppDispatch import from
'./redux/store' with one from '@desktop-client/redux/store'; keep the imported
symbols (pushModal and AppDispatch) unchanged so existing references continue to
work and ESLint's absolute-import rule is satisfied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/desktop-client/src/components/EnableBankingCallback.tsx`:
- Around line 17-18: Ensure the callback state value round-trips exactly by
requiring both the query param state (stateParam) and the stored value
(localStorage.getItem('enablebanking_auth_state')) to exist and be identical
before proceeding; if they match, send that state back to the backend endpoint
(enablebanking-complete-auth) along with the callback data, otherwise treat it
as an error (setStatus('error')) and do not complete the auth. Locate the state
handling logic (variables stateParam and state, and the code path that calls
enablebanking-complete-auth) and replace the current fallback behavior with an
explicit equality check and error handling so stale or injected callbacks cannot
be accepted.

In `@packages/desktop-client/src/components/modals/CreateAccountModal.tsx`:
- Around line 740-742: In CreateAccountModal (look for the JSX using
enableBankingEnabled and isEnableBankingSetupComplete that currently renders
t('Enable Banking')), change the translated provider name to the literal string
"Enable Banking" while keeping the rest of the surrounding sentence using the
translation function; i.e., remove the t(...) wrapper around the provider/brand
token so provider names like "Enable Banking" remain plain strings but keep
using t(...) for the surrounding wording in the component rendering logic.
- Around line 226-229: The Enable Banking CTA is clickable while
useEnableBankingStatus is unresolved (configuredEnableBanking === null), causing
a broken first-click; update the handlers and button props that use
onConnectEnableBanking and isEnableBankingSetupComplete to also read and pass
through the hook's isLoading (or equivalent) flag, disable the button and render
a loading/spinner state (or keep a non-actionable "Loading..." label) until
isLoading is false, and ensure the label switches correctly based on
configuredEnableBanking when resolved; apply the same change to the other
instances referenced (the blocks around the other two button locations).
- Around line 240-247: The addNotification call is creating an action object but
not dispatching it; wrap the addNotification(...) invocation with dispatch(...)
so the action is sent to the Redux store (apply the same fix for the other bare
addNotification usage around CreateAccountModal, e.g., replace
addNotification({...}) with dispatch(addNotification({...})) so the notification
toasts are actually shown).

In
`@packages/desktop-client/src/components/modals/EnableBankingExternalMsgModal.tsx`:
- Around line 154-189: onJump() holds isJumpingRef.current true for the whole
long-poll (onMoveExternal) which prevents the retry/fallback link from reopening
the bank popup; fix by starting onMoveExternal without blocking the ref — call
onMoveExternal and store the returned promise (e.g. const resPromise =
onMoveExternal({...})), then set isJumpingRef.current = false immediately so the
UI can retry, and await the stored promise (const res = await resPromise)
afterwards; keep existing try/finally cleanup and error handling around the
awaited result and use the same symbols (onJump, isJumpingRef, onMoveExternal,
onSuccess, selectedAspsp, bankOptions) to locate and update the code.

In
`@packages/desktop-client/src/components/modals/EnableBankingInitialiseModal.tsx`:
- Around line 87-94: In EnableBankingInitialiseModal.tsx the error branch
currently uses result.data.error_type (machine codes) for user-facing text;
change it to prefer the serialized human message (result.data.message) or a
local mapping from known error_type values to friendly strings, falling back to
the existing generic t(...) fallback; update the setError call that references
result.data.error_type to use result.data.message || mappedMessage || t('Could
not validate the credentials. Please check your Application ID and secret key.')
and ensure setIsValid(false) remains unchanged.

In `@packages/desktop-client/src/enablebanking.ts`:
- Around line 68-87: The code treats any successful HTTP response as success
even when the body contains an error; update the poll handling after
sendCatch('enablebanking-poll-auth', ...) to check pollResp.data or pollData for
body-level errors (e.g. pollData?.data?.error or pollData?.error) before
extracting accounts: if a body error exists handle timeout (return { error:
'timeout' }) or return the same { error: 'unknown', message: ... } shape as the
top-level error branch, otherwise proceed to set accounts from
pollData?.data?.accounts ?? pollData?.accounts ?? []; this ensures sendCatch,
pollResp, pollData and accounts are validated correctly before opening the
account picker.

In `@packages/loot-core/src/server/accounts/app.ts`:
- Around line 980-1019: The global enableBankingPollController causes races
between concurrent auth flows; change to track controllers per flow (e.g., a
Map<string, AbortController> keyed by the incoming state or a generated flowId).
In enableBankingPollAuth create a new AbortController and store it in the map
under the state (or return a flowId to the caller), use controller.signal in the
post call, and in the finally block only delete the map entry for that
state/flowId if it still points to the same controller. Update
stopEnableBankingPollAuth to accept the state/flowId and lookup+abort only that
controller (then remove it from the map); remove the old process-wide
enableBankingPollController variable and replace usages with the per-flow map.
Ensure function names referenced: enableBankingPollController (remove),
enableBankingPollAuth, stopEnableBankingPollAuth are updated accordingly.
- Around line 925-933: The validation treats maxConsentValidity as days but the
rest of the flow (modal forwarding aspsp.maximum_consent_validity and
enableBankingService.startAuth()) uses seconds, so update the check in the
maxConsentValidity validation (the variable maxConsentValidity in this block) to
compare against seconds not days: keep the Number.isFinite/Number.isInteger and
>0 checks, but replace the upper bound 3650 with 3650 * 24 * 60 * 60 (or an
equivalent seconds constant) so values representing 30–90 days in seconds (e.g.,
2_592_000–7_776_000) are accepted. Ensure the error string remains
'invalid_max_consent_validity'.

In
`@packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts`:
- Around line 211-214: The current normalization always strips the original sign
then applies a sign based solely on tx.credit_debit_indicator, which loses a
leading '-' when the indicator is undefined; change the logic around
rawAmount/signedAmount so you first read amtStr =
tx.transaction_amount.amount.trim(), then if tx.credit_debit_indicator is
undefined preserve the original sign (only remove a leading '+' but keep a
leading '-'), otherwise remove any leading sign and apply '-' when
tx.credit_debit_indicator === 'DBIT' and no prefix when it's a credit; update
the variables rawAmount and signedAmount accordingly to use
tx.transaction_amount.amount, tx.credit_debit_indicator, and the adjusted
amtStr.
- Around line 367-393: The loop detects repeated continuation keys one request
too late because it compares result.continuation_key to previousContinuationKey
(which lags two iterations); change the logic to compare the newly returned
result.continuation_key against the current continuationKey (the key used for
this request) immediately after the fetch, and break if they match, then update
continuationKey = result.continuation_key; remove or repurpose
previousContinuationKey since comparing to continuationKey is sufficient. This
involves modifying the do/while in enablebanking-service.ts around
getTransactions, replacing the if that checks result.continuation_key ===
previousContinuationKey with a check result.continuation_key === continuationKey
and moving/updating the assignments so previousContinuationKey is no longer
needed.

In `@upcoming-release-notes/7345.md`:
- Line 6: The sentence "Integrate Enable Banking as bank sync provider" is
missing the article "a"; update that line (the string "Integrate Enable Banking
as bank sync provider") to read "Integrate Enable Banking as a bank sync
provider" to fix the grammatical error.

---

Nitpick comments:
In `@packages/desktop-client/src/enablebanking.ts`:
- Around line 6-7: Update the two relative imports to use the repo's
desktop-client absolute import prefix: replace the import of pushModal from
'./modals/modalsSlice' with an import from '@desktop-client/modals/modalsSlice',
and replace the AppDispatch import from './redux/store' with one from
'@desktop-client/redux/store'; keep the imported symbols (pushModal and
AppDispatch) unchanged so existing references continue to work and ESLint's
absolute-import rule is satisfied.

In `@packages/sync-server/src/app-enablebanking/utils/jwt.ts`:
- Around line 10-18: The getJWTBody function should have an explicit return type
like getJWTHeader does; update getJWTBody to declare and use a matching JWT body
type (e.g., JWTBody or an inline type/interface) instead of relying on inferred
typing so the function signature explicitly returns that type; reference
getJWTBody (and getJWTHeader/Header if needed) to locate the implementation and
adjust the function signature to return the defined type that includes iss, aud,
iat, and exp.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 258a7a19-91d0-4e21-96cb-6afbf1c9f709

📥 Commits

Reviewing files that changed from the base of the PR and between 3b14fd0 and e4b9d9c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (41)
  • packages/desktop-client/src/accounts/mutations.ts
  • packages/desktop-client/src/components/EnableBankingCallback.tsx
  • packages/desktop-client/src/components/FinancesApp.tsx
  • packages/desktop-client/src/components/Modals.tsx
  • packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx
  • packages/desktop-client/src/components/banksync/index.tsx
  • packages/desktop-client/src/components/mobile/banksync/BankSyncAccountsList.tsx
  • packages/desktop-client/src/components/mobile/banksync/MobileBankSyncPage.tsx
  • packages/desktop-client/src/components/modals/CreateAccountModal.tsx
  • packages/desktop-client/src/components/modals/EnableBankingExternalMsgModal.tsx
  • packages/desktop-client/src/components/modals/EnableBankingInitialiseModal.tsx
  • packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx
  • packages/desktop-client/src/components/settings/Experimental.tsx
  • packages/desktop-client/src/enablebanking.ts
  • packages/desktop-client/src/hooks/useEnableBankingStatus.ts
  • packages/desktop-client/src/hooks/useFeatureFlag.ts
  • packages/desktop-client/src/modals/modalsSlice.ts
  • packages/desktop-client/vite.config.ts
  • packages/loot-core/src/server/accounts/app.ts
  • packages/loot-core/src/server/accounts/sync.ts
  • packages/loot-core/src/server/post.ts
  • packages/loot-core/src/server/server-config.ts
  • packages/loot-core/src/types/models/account.ts
  • packages/loot-core/src/types/models/bank-sync.ts
  • packages/loot-core/src/types/models/enablebanking.ts
  • packages/loot-core/src/types/models/index.ts
  • packages/loot-core/src/types/prefs.ts
  • packages/sync-server/package.json
  • packages/sync-server/src/app-enablebanking/app-enablebanking.ts
  • packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts
  • packages/sync-server/src/app-enablebanking/services/tests/enablebanking-service.spec.ts
  • packages/sync-server/src/app-enablebanking/services/tests/fixtures.ts
  • packages/sync-server/src/app-enablebanking/services/tests/normalization.spec.ts
  • packages/sync-server/src/app-enablebanking/tests/poll-auth.spec.ts
  • packages/sync-server/src/app-enablebanking/utils/errors.ts
  • packages/sync-server/src/app-enablebanking/utils/jwt.ts
  • packages/sync-server/src/app-enablebanking/utils/tests/errors.spec.ts
  • packages/sync-server/src/app-enablebanking/utils/tests/jwt.spec.ts
  • packages/sync-server/src/app.ts
  • packages/sync-server/src/services/secrets-service.js
  • upcoming-release-notes/7345.md

Comment thread packages/desktop-client/src/components/EnableBankingCallback.tsx Outdated
Comment thread packages/desktop-client/src/components/modals/CreateAccountModal.tsx Outdated
Comment thread packages/desktop-client/src/components/modals/CreateAccountModal.tsx Outdated
Comment thread packages/desktop-client/src/components/modals/CreateAccountModal.tsx Outdated
Comment thread packages/loot-core/src/server/accounts/app.ts
Comment thread packages/loot-core/src/server/accounts/app.ts Outdated
Comment thread packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts Outdated
Comment thread upcoming-release-notes/7345.md Outdated
@MatissJanis

Copy link
Copy Markdown
Member

Thanks for taking this on! Looking forward to reviewing it and merging!

But first things first: can you go through the coderabbit comments? Some of those are valid things to fix. Others - you can disregard (but then reply back to the bot and tell him why; he will then resolve the comment). Eventually coderabbitai will approve and that's when I (we) step in for a final review :)

@matt-fidd
matt-fidd force-pushed the master branch 2 times, most recently from 5c7c70d to d262f7d Compare April 5, 2026 17:13
@AurelDemiri

Copy link
Copy Markdown
Contributor Author

@MatissJanis Wrapping up, just pushed the final batch. Everything from your first-pass plus matt-fidd's /transactions request and the CodeRabbit pass-2 items is in. Master is also merged in (the crypto.randomUUID() change from #7529 is plumbed through the two enable-banking call sites).

@morvy Can you post the raw responses you get from enable banking? This would help with figuring out a fix.

@morvy

morvy commented Apr 29, 2026

Copy link
Copy Markdown

@AurelDemiri I'm in touch with EB and they're investigating, because my bank sends Debit, then from EB it comes as CRDT so it's the transformation of the data on their side, not in the app / your code.

@MatissJanis

Copy link
Copy Markdown
Member

Code LGTM, but I can't do a full e2e test of the flow currently as I have broken the devserver with the build script changes I've been doing (oops!). Will fix the build, update the branch here and perform final e2e test prior to giving the approval. Hang in a bit longer. 🤞

MatissJanis and others added 2 commits May 9, 2026 16:06
Reconcile with master's bank-sync provider refactor by wiring Enable
Banking into the new useBuiltInBankSyncProviders hook (gated by the
enableBanking feature flag), and add upgradingAccountId to the Enable
Banking flow so it matches the other providers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns with master's revert in actualbudget#7734 (crypto.randomUUID back to uuid
library). Two stray spots remained in Enable Banking code: the
link-account flow in loot-core/server/accounts/app.ts and the OAuth
state token in sync-server/app-enablebanking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@MatissJanis MatissJanis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM on my end!

Need one more maintainer approval before merging.

@pmarinab

Copy link
Copy Markdown

I know it’s still early for this, but seeing that this PR is finally nearing completion, it would be very helpful to include the web help for EnableBanking.

image

@matt-fidd matt-fidd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! This looks good to me, I unfortunately don't have a bank in a country EB covers, but I linked through a sandbox account that that all worked as expected.

Great work on this, it's amazing to get it over the line, thank you.

As above, some documentation would be great before this is included in the next major release (26.6 in early June) but that's not a blocker for this to be merged.


const SYNC_PROVIDER_KEYS = [
...BUILT_IN_BANK_SYNC_PROVIDERS,
'enableBanking',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Should this be added to BUILT_IN_BANK_SYNC_PROVIDERS instead?

@matt-fidd
matt-fidd added this pull request to the merge queue May 11, 2026
Merged via the queue into actualbudget:master with commit 0fd510a May 11, 2026
38 checks passed
@sweenu

sweenu commented May 11, 2026

Copy link
Copy Markdown

Should it be used with a Sandbox application or Production?

@matt-fidd

Copy link
Copy Markdown
Member

Should it be used with a Sandbox application or Production?

I believe you'll need to use production to get your actual transaction information

@ProtoxiDe22

Copy link
Copy Markdown

Hello, i'm testing the flow, i have found a couple minor problems with it. Not sure if it makes sense to post them here, but i'm not sure opening an issue is correct right now since this is pre-release.

  • initially i tried setting up the integration with a non-complete setup on enable banking (the eb application was not activated), however, actual didn't report any errors in the frontend, and when trying to link accounts, nothing would just show up when trying to select the bank.
    The logs (understandably) contained this
    actual_server-1 | 2026-05-11T17:37:52.729Z actual:enable-banking:errors Enable Banking API error: status=403 body={"code":403,"message":"Application is not active"} actual_server-1 | 2026-05-11T17:37:52.730Z info: POST 200 /enablebanking/aspsps

  • resetting the enable bank credentials from the bank sync page, the frontend throws an error, but in reality the request went through just fine and the credentials were reset. I couldn't find anything relevant in the js logs, and the server logs just reported the successful request
    actual_server-1 | 2026-05-11T17:39:15.157Z actual:secrets-db setting secret 'enablebanking_applicationId' to 'null' actual_server-1 | 2026-05-11T17:39:15.164Z info: POST 200 /secret

image
  • On the last step of linking the account, the frontend (correctly) preselects an account to link. However, the confirm button of the modal is disabled, and only enables if i click "remove bank sync" and then "set up bank sync" on the same account
image after re-selecting the account in actual, the button is enabled correctly and the flow terminates just fine.

I use this occasion to thank the author for their work, and the maintainers for (finally :) ) merging this.

@daneov

daneov commented May 11, 2026

Copy link
Copy Markdown

@ProtoxiDe22 regarding your first bullet:

  • It should, or does on my side, work even when the application is in the Restricted state. There's a big green button "Link accounts" on the Enable Banking side, which basically whitelists the accounts you can link to on Actual, without having to request anything.

I do think creating one or more issue(s) out of these would be better, since it is now merged into the main branch. That gives each issue a dedicated spot/context, and we wouldn't need to pollute the PR any further :)

@ProtoxiDe22

Copy link
Copy Markdown

@daneov yes, the error was mine, not configuring the eb application properly. the problem I'm reporting is the fact that actual fails silently instead of showing the 403 error in the frontend

@matt-fidd

Copy link
Copy Markdown
Member

You can add feedback to #7799

I've got a PR up to show it in the UI too

ravilushqa added a commit to ravilushqa/homelab that referenced this pull request May 17, 2026
…rged) (#182)

* fix(actual): switch to official actual-server:edge (Enable Banking merged)

- Replace custom ghcr.io/ravilushqa/actual-enablebanking image
- Use official actualbudget/actual-server:edge which includes PR#7345 (Enable Banking)
- Remove dependency on custom Dockerfile.enablebanking build chain

Refs: actualbudget/actual#7345

* chore(actual): remove custom enablebanking build pipeline

- Delete GitHub Actions workflow that built custom Docker image
- Delete Dockerfile.enablebanking (no longer needed with upstream merge)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] GoCardless Bank Account Data is being discontinued, replace with Enable Banking API [Feature] Alternative to GoCardless