Skip to content

Integrate Enable Banking as a bank sync provider#7345

Open
AurelDemiri wants to merge 15 commits intoactualbudget:masterfrom
AurelDemiri:feature/enable-banking
Open

Integrate Enable Banking as a bank sync provider#7345
AurelDemiri wants to merge 15 commits intoactualbudget:masterfrom
AurelDemiri:feature/enable-banking

Conversation

@AurelDemiri
Copy link
Copy Markdown

@AurelDemiri AurelDemiri commented Mar 31, 2026

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 27 12.41 MB → 12.43 MB (+26.38 kB) +0.21%
loot-core 1 4.84 MB → 4.85 MB (+6.25 kB) +0.13%
api 1 3.84 MB → 3.84 MB (+6.11 kB) +0.16%
cli 1 7.89 MB 0%
View detailed bundle stats

desktop-client

Total

Files count Total bundle size % Changed
27 12.41 MB → 12.43 MB (+26.38 kB) +0.21%
Changeset
File Δ Size
src/components/modals/EnableBankingExternalMsgModal.tsx 🆕 +9.67 kB 0 B → 9.67 kB
src/components/modals/EnableBankingInitialiseModal.tsx 🆕 +5.47 kB 0 B → 5.47 kB
src/components/EnableBankingCallback.tsx 🆕 +2.89 kB 0 B → 2.89 kB
src/enablebanking.ts 🆕 +1.91 kB 0 B → 1.91 kB
src/hooks/useEnableBankingStatus.ts 🆕 +741 B 0 B → 741 B
src/components/budget/goals/actions.ts 📈 +67 B (+31.31%) 214 B → 281 B
src/components/modals/CreateAccountModal.tsx 📈 +3.78 kB (+25.17%) 15.01 kB → 18.78 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/Modals.tsx 📈 +413 B (+3.21%) 12.56 kB → 12.97 kB
src/components/modals/SelectLinkedAccountsModal.tsx 📈 +835 B (+2.02%) 40.28 kB → 41.09 kB
src/components/settings/Experimental.tsx 📈 +215 B (+1.90%) 11.07 kB → 11.28 kB
src/components/FinancesApp.tsx 📈 +305 B (+1.71%) 17.44 kB → 17.74 kB
src/components/accounts/AccountSyncCheck.tsx 📈 +142 B (+1.62%) 8.54 kB → 8.68 kB
src/components/banksync/index.tsx 📈 +36 B (+0.73%) 4.84 kB → 4.87 kB
src/components/mobile/banksync/MobileBankSyncPage.tsx 📈 +36 B (+0.63%) 5.6 kB → 5.63 kB
src/components/alerts.tsx 📉 -67 B (-1.80%) 3.64 kB → 3.57 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
static/js/index.js 3.31 MB → 3.34 MB (+26.8 kB) +0.79%

Smaller

Asset File Size % Changed
static/js/narrow.js 363.02 kB → 362.59 kB (-433 B) -0.12%

Unchanged

Asset File Size % Changed
static/js/BackgroundImage.js 121.09 kB 0%
static/js/FormulaEditor.js 852.77 kB 0%
static/js/ReportRouter.js 1.17 MB 0%
static/js/TransactionList.js 82.49 kB 0%
static/js/ca.js 189.75 kB 0%
static/js/da.js 104.66 kB 0%
static/js/de.js 174.38 kB 0%
static/js/en-GB.js 8.2 kB 0%
static/js/en.js 175.65 kB 0%
static/js/es.js 181.8 kB 0%
static/js/fr.js 177.08 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.46 kB 0%
static/js/it.js 165.87 kB 0%
static/js/nb-NO.js 151.85 kB 0%
static/js/nl.js 108.93 kB 0%
static/js/pl.js 88.34 kB 0%
static/js/pt-BR.js 177.44 kB 0%
static/js/resize-observer.js 18.06 kB 0%
static/js/th.js 179.3 kB 0%
static/js/theme.js 30.79 kB 0%
static/js/uk.js 212.6 kB 0%
static/js/useTransactionBatchActions.js 4.33 MB 0%
static/js/wide.js 295 B 0%
static/js/workbox-window.prod.es5.js 7.33 kB 0%
static/js/zh-Hans.js 94.19 kB 0%

loot-core

Total

Files count Total bundle size % Changed
1 4.84 MB → 4.85 MB (+6.25 kB) +0.13%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/app.ts 📈 +4.8 kB (+22.11%) 21.73 kB → 26.53 kB
home/runner/work/actual/actual/packages/loot-core/src/server/post.ts 📈 +481 B (+11.95%) 3.93 kB → 4.4 kB
home/runner/work/actual/actual/packages/loot-core/src/server/server-config.ts 📈 +58 B (+6.48%) 895 B → 953 B
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/sync.ts 📈 +936 B (+4.12%) 22.17 kB → 23.08 kB
View detailed bundle breakdown

Added

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

Removed

Asset File Size % Changed
kcab.worker.DOvC4GKD.js 4.84 MB → 0 B (-4.84 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
1 3.84 MB → 3.84 MB (+6.11 kB) +0.16%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/server/accounts/app.ts 📈 +4.69 kB (+21.94%) 21.4 kB → 26.09 kB
home/runner/work/actual/actual/packages/loot-core/src/server/post.ts 📈 +475 B (+12.23%) 3.79 kB → 4.26 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 📈 +919 B (+4.13%) 21.73 kB → 22.63 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
index.js 3.84 MB → 3.84 MB (+6.11 kB) +0.16%

Smaller
No assets were smaller

Unchanged
No assets were unchanged


cli

Total

Files count Total bundle size % Changed
1 7.89 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.89 MB 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
Copy link
Copy Markdown

netlify bot commented Mar 31, 2026

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit f36a888
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget/deploys/69d68a1e2f178100080dd755
😎 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.

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
Copy link
Copy Markdown

netlify bot commented Mar 31, 2026

Deploy Preview for actualbudget-website ready!

Name Link
🔨 Latest commit dacbea6
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget-website/deploys/69d60b6c9da6d70008a2501e
😎 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
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This PR integrates Enable Banking as a new bank synchronization provider, offering an alternative to the discontinued GoCardless service. It includes OAuth-like authentication flows, account linking interfaces, transaction synchronization handlers, and supporting infrastructure across the desktop client and sync server.

Changes

Cohort / File(s) Summary
Type Definitions & Models
packages/loot-core/src/types/models/enablebanking.ts, packages/loot-core/src/types/models/account.ts, packages/loot-core/src/types/models/bank-sync.ts, packages/loot-core/src/types/models/index.ts, packages/loot-core/src/types/prefs.ts
Added Enable Banking type definitions (accounts, transactions, balances, ASPSPs) and extended existing account sync source and bank sync provider unions to include 'enableBanking'.
Enable Banking Service & Utilities (Backend)
packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts, packages/sync-server/src/app-enablebanking/utils/errors.ts, packages/sync-server/src/app-enablebanking/utils/jwt.ts, packages/sync-server/src/services/secrets-service.js
Implemented Enable Banking API integration service with authenticated HTTP requests, response normalization (transactions, balances, accounts), error handling, and JWT signing. Added secrets management for credentials.
Enable Banking Routes & Middleware (Backend)
packages/sync-server/src/app-enablebanking/app-enablebanking.ts, packages/sync-server/src/app.ts, packages/loot-core/src/server/accounts/app.ts, packages/loot-core/src/server/accounts/sync.ts, packages/loot-core/src/server/server-config.ts, packages/loot-core/src/server/post.ts
Added Express app with routes for auth flow (/auth_callback, /start-auth, /complete-auth, /poll-auth), account/sync endpoints (/aspsps, /status, /configure, /transactions), and account linking logic. Extended post utility to support external abort signals for polling.
Frontend Components
packages/desktop-client/src/components/EnableBankingCallback.tsx, packages/desktop-client/src/components/modals/EnableBankingExternalMsgModal.tsx, packages/desktop-client/src/components/modals/EnableBankingInitialiseModal.tsx
Added callback handler for OAuth redirect, modal for ASPSP selection and bank linking, and modal for Enable Banking configuration (Application ID and secret key upload).
Account Linking & Modal Integration
packages/desktop-client/src/accounts/mutations.ts, packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx, packages/desktop-client/src/components/modals/CreateAccountModal.tsx, packages/desktop-client/src/components/Modals.tsx, packages/desktop-client/src/components/FinancesApp.tsx
Added mutation hook for Enable Banking account linking, extended account selection modal to handle Enable Banking accounts, integrated Enable Banking button/flow into account creation modal with feature flag, and registered new modal types and auth callback route.
Authorization & Reauth Flow
packages/desktop-client/src/enablebanking.ts, packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx
Added authorizeBank function managing the full Enable Banking auth flow (polling with state management, account fetching). Updated reauth logic to dispatch based on account sync source.
Hooks & Feature Infrastructure
packages/desktop-client/src/hooks/useEnableBankingStatus.ts, packages/desktop-client/src/hooks/useFeatureFlag.ts, packages/desktop-client/src/components/settings/Experimental.tsx
Added hook to check Enable Banking configuration status, added feature flag entry, and exposed toggle in experimental settings.
UI Labels & Routing
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/vite.config.ts, packages/desktop-client/src/modals/modalsSlice.ts
Added Enable Banking display labels to sync source mappings, updated mobile type definitions to use shared BankSyncProviders, added Vite PWA route deny-list entry, and defined modal type signatures with callbacks.
Tests & Documentation
packages/sync-server/src/app-enablebanking/services/tests/enablebanking-service.spec.ts, packages/sync-server/src/app-enablebanking/services/tests/normalization.spec.ts, packages/sync-server/src/app-enablebanking/services/tests/fixtures.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-enablebanking/tests/poll-auth.spec.ts, packages/sync-server/package.json, upcoming-release-notes/7345.md
Comprehensive test suites for service integration, normalization, error handling, JWT generation, and route handlers. Added fixtures and release notes.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Client as Desktop Client
    participant EB as Enable Banking API
    participant Server as Sync Server
    participant Callback as Auth Callback
    participant Bank as User's Bank

    User->>Client: Click "Link Bank (Enable Banking)"
    Client->>Server: authorizeEnableBanking(dispatch)
    Server->>Server: Open enablebanking-external-msg modal
    User->>Client: Select country & bank (ASPSP)
    Client->>Server: GET /enablebanking/aspsps?country=XX
    Server->>EB: Fetch available ASPSPs
    EB-->>Server: Return ASPSP list
    Server-->>Client: Return bank options
    User->>Client: Click "Link bank in browser"
    Client->>Server: POST /enablebanking/start-auth<br/>{aspspId, country, redirectUrl, state}
    Server->>EB: Create auth request
    EB-->>Server: Return auth URL
    Server-->>Client: Return auth URL & state
    Client->>Client: Store state in localStorage
    Client->>Client: window.open(authUrl)
    Client->>Server: Begin polling POST /enablebanking/poll-auth<br/>{state}
    Bank->>Bank: User authorizes consent
    Bank->>Callback: Redirect to /enablebanking/auth_callback<br/>?code=AUTH_CODE&state=STATE
    Callback->>Server: POST /enablebanking/complete-auth<br/>{code, state}
    Server->>EB: Create session with code
    EB-->>Server: Return session with accounts
    Server-->>Server: Store session result for state
    Server-->>Callback: Return success
    Callback->>Callback: Close popup window
    Server-->>Client: Poll returns account list
    Client->>Client: Close polling modal
    Client->>Client: Open select-linked-accounts modal
    User->>Client: Review & select accounts to link
    Client->>Server: POST /enablebanking/enablebanking-accounts-link<br/>{externalAccount, ...}
    Server->>Server: Create/link account to Actual
    Server-->>Client: Return success
    Client->>Client: Navigate away, accounts synced
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

API

Poem

🐰 A banking hop, from one to another,
Enable Banking joins the synchronized fray,
OAuth flows and accounts linked together,
Polling awaits the consent display,
New transactions flow like clover in May! 🌿

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses both linked issues (#5445, #5505) by implementing Enable Banking as a bank sync provider to replace discontinued GoCardless support. The implementation includes all necessary components: UI integration, server-side handlers, authentication flow, transaction sync, and enable/disable via feature flag.
Out of Scope Changes check ✅ Passed All changes are within scope for integrating Enable Banking: new components, mutations, server handlers, types, feature flags, UI routes, and sync logic. No unrelated modifications detected.
Title check ✅ Passed The pull request title 'Integrate Enable Banking as a bank sync provider' directly and clearly summarizes the main change—adding Enable Banking as a new bank-sync integration option, which aligns perfectly with the primary objective of replacing discontinued GoCardless support.
Description check ✅ Passed The pull request description clearly describes the addition of Enable Banking as a bank sync provider, explains improvements over the previous approach, and details testing performed.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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

@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 AurelDemiri force-pushed the feature/enable-banking branch from a04e74e to 0cafb4a Compare April 8, 2026 08:09
@AurelDemiri AurelDemiri changed the title Integrate Enable Banking as bank sync provider Integrate Enable Banking as a bank sync provider Apr 8, 2026
@AurelDemiri
Copy link
Copy Markdown
Author

One thing I'd like us to test before merging: what happens when switching an account from GoCardless to Enable Banking? Specifically, this involves importing transactions into an account that already has GoCardless-imported data. I don't have a GoCardless account to test this myself. Could someone with access verify that the transition works cleanly (no duplicate transactions, correct balancing, etc.)?

@youngcw
Copy link
Copy Markdown
Member

youngcw commented Apr 8, 2026

One thing I'd like us to test before merging: what happens when switching an account from GoCardless to Enable Banking? Specifically, this involves importing transactions into an account that already has GoCardless-imported data. I don't have a GoCardless account to test this myself. Could someone with access verify that the transition works cleanly (no duplicate transactions, correct balancing, etc.)?

This shouldn't be much of a problem, and only needs fixed once. Reconcile the account, protecting the old transacitons from being changed, then delete any duplicate transactions that come in. The bank sync settings will have the option to disable reimporting deleted transactions.

@youngcw youngcw removed their assignment Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Issues with the @actual-app/api package 🔍 ready for review

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

3 participants