whisper-money/app
Víctor Falcón f800847591
feat(banking): back off scheduler when EnableBanking returns 429 (#352)
## Problem

Production logs show repeated EnableBanking 429s on the same
connections, every cron cycle:

```
[2026-05-04 18:00:55] EnableBanking API error status:429
  body: [HUB046] Allowed number of accesses exceeded for consent
[2026-05-04 21:47:12] EnableBanking API error status:429
  body: Daily PSU not present consultation limit has been exceeded
[2026-05-05 00:01:41] same connection, same error
[2026-05-05 06:01:41] same connection, same error
```

Root cause: `SyncBankingConnectionJob` returned early on 429s without
persisting any backoff state. The scheduler kept re-dispatching the same
connection on every run, hammering the provider and burning the daily
quota.

## Fix

Persist a per-connection backoff window so the scheduler stops
re-dispatching until the provider quota resets.

- New `rate_limited_until` column on `banking_connections`.
- On 429: derive the window from
  1. `Retry-After` header if present,
2. "Daily ..." message → next UTC midnight (matches PSU daily limit
semantics),
  3. default 1 hour (consent / generic).
- Job short-circuits with a `Skipped` sync log if the window is still
active.
- Successful sync clears the window.
- `SyncAllBankingConnectionsJob` + `SyncBankingConnections` command
filter out connections still inside their backoff.

## Tests

- Existing rate-limit test updated (now also asserts the backoff is
set).
- New: daily message → next UTC midnight.
- New: `Retry-After` header honoured (1800s).
- New: rate-limited connection skipped without calling provider.
- New: successful sync clears `rate_limited_until`.
- New: scheduler excludes connections whose backoff has not expired.

`php artisan test --compact
--filter="SyncBankingConnectionJobTest|SyncRetryAndLoggingTest|SyncBankingConnectionsCommandTest"`
→ 70 passed.
2026-05-05 09:39:32 +02:00
..
Actions Fix default category creation N+1 (#347) 2026-05-04 13:22:55 +01:00
Console/Commands feat(banking): back off scheduler when EnableBanking returns 429 (#352) 2026-05-05 09:39:32 +02:00
Contracts feat(banking): add banking:backfill-ibans command to populate missing IBANs (#221) 2026-03-12 10:58:55 +00:00
Enums feat(leads): cohort-based launch invitations with per-user Stripe coupons (#333) 2026-04-30 15:10:28 +01:00
Events Add Budgeting Feature to Track and Manage Spending (#36) 2026-01-21 15:25:50 +01:00
Http Add Sentry user context (#348) 2026-05-04 13:26:50 +01:00
Jobs feat(banking): back off scheduler when EnableBanking returns 429 (#352) 2026-05-05 09:39:32 +02:00
Listeners feat(emails): co-founder language, welcome rewrite, and Spanish translations (#208) 2026-03-05 15:22:07 +01:00
Mail feat(leads): cohort-based launch invitations with per-user Stripe coupons (#333) 2026-04-30 15:10:28 +01:00
Models feat(banking): back off scheduler when EnableBanking returns 429 (#352) 2026-05-05 09:39:32 +02:00
Notifications feat: selective retry of failed lead email jobs (#286) 2026-04-15 08:00:29 +01:00
Policies feat(accounts): add real estate asset tracking (#241) 2026-03-24 10:21:32 +00:00
Providers feat(leads): cohort-based launch invitations with per-user Stripe coupons (#333) 2026-04-30 15:10:28 +01:00
Services fix: unblock onboarding after sync failure (#346) 2026-05-04 12:52:21 +01:00