actualbudget / actualbudget/actual

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

Abierto
#8,902 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
TypeScript
Estrellas
28.7k
Forks
3k
Merge medio
2 d 11 h
PR fusionados (30 d)
65

Descripción

## 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.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.