actualbudget / actualbudget/actual

ING Direct (ING_INGBITMM) fails to link: a pre-created end-user agreement leaves the requisition stuck in UA

Aperta Adatta ai principianti
#8,902 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
TypeScript
Stelle
28.8k
Fork
3k
Merge medio
2g 11h
PR unite (30g)
65

Descrizione

## What happens

Linking an ING Direct (Italy, `ING_INGBITMM`) account through GoCardless never completes. After authenticating at ING, the GoCardless hosted page returns:

```
400
Session expired
Session interrupted or has been already authorized
```

The requisition stays in `UA` (undergoing authentication) and never reaches `LN`, so no accounts are imported. It reproduces on every attempt.

## Environment

- actual-server 26.9.0, self-hosted (Docker)
- Institution `ING_INGBITMM`, GoCardless (Bank Account Data) API
- Reproduced both from the normal browser link flow and against the GoCardless API directly

## Cause

`initSession` always creates an explicit end-user agreement and binds the requisition to it:

```js
async initSession({ ... }) {
const agreement = await this.createAgreement({ institutionId, maxHistoricalDays, accessValidForDays });
return this.createRequisition({ ..., agreement: agreement.id });
}
```

`ING_INGBITMM` cannot finalize a requisition that is bound to a pre-created agreement object. The consent is accepted on ING's side (the agreement flips to `accepted: true`), but GoCardless never advances the requisition past `UA`.

## Isolation

Same institution, same bank account, requisitions created minutes apart in the same window:

- WITHOUT an `agreement` field (GoCardless auto-creates its default): reaches `LN` with both accounts on the first try.
- WITH a pre-created agreement at `access_valid_for_days: 180`, `max_historical_days: 730` (the values Actual sends for ING): stuck in `UA`.
- WITH a pre-created agreement at `90 / 90`: also stuck in `UA`.

The auto-created default agreement is itself `90 / 90`, so the scope is not the trigger. What decides success is whether the agreement is a separate pre-created object referenced by id, or created inline by GoCardless during requisition creation.

Other institutions (e.g. BBVA) link fine either way, so this is specific to ING's connector honouring `supported_features` it can't actually deliver through an explicit agreement.

## Workaround

Patching the bundled server to drop the agreement id (`agreement: agreement.id` → `agreement: void 0`) makes ING link on the first try; GoCardless then auto-creates the default agreement inline.

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Start by locating the `initSession` function in the self-hosted `actual-server` code where `createAgreement` and `createRequisition` are wired. Inspect how the `agreement` field is included in requisition creation for `ING_INGBITMM`, and check whether an inline/default agreement path can be used instead. Reproduce the link flow in a local Docker `actual-server` setup against GoCardless for ING and confirm the status moves from UA to LN; also verify other banks (e.g., BBVA) still link correctly.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
api, backend
Tipo di issue
Bug
Difficoltà
2/5
Tempo stimato
1-3 ore
Stato di attività
Attiva
Chiarezza
Specificata chiaramente
Idoneità per principianti
74/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.