whisper-money/database/factories
Víctor Falcón f60e6d7035
feat(banking): add Interactive Brokers sync via Flex Web Service (#581)
## What

Adds **Interactive Brokers** as a banking sync provider (investment
account, balances only), mirroring the Indexa Capital integration.

It uses the **Flex Web Service** rather than IBKR's Web API: the Web API
requires registering as an IBKR third party (business entity, Compliance
approval, RSA-signed OAuth, ~3-5 weeks), which is overkill for read-only
balance sync. Flex is a read-only token + Query ID model that fits our
existing API-key provider shape.

### How the sync works
- The user creates an Activity Flex Query (NAV + Open Positions) and a
Flex Web Service token in their IBKR Client Portal, then pastes both.
- Client flow: `SendRequest` → reference code → poll `GetStatement` →
parse the XML statement.
- Mapping: `EquitySummaryByReportDateInBase@total` → `balance` (daily
rows give historical backfill on first sync); `Σ(OpenPosition
costBasisMoney × fxRateToBase) + cash` → `invested_amount`, so **profit
derives as `balance − invested_amount`** (unrealized P&L), like Indexa
Capital. Everything is already in base currency, so no FX conversion is
needed.
- One statement covers every account, so the syncer fetches once per
connection to respect IB's per-query rate limit.
- IB returns HTTP 200 with an error XML, so the client translates Flex
error codes into the exceptions the sync job already understands:
`RequestException(401)` for token problems, `RequestException(429)` for
throttling, `TransientBankingProviderException` otherwise.

### Connect flow
- New connect/update-credentials endpoints validate the credentials by
pulling a statement, then build pending accounts from it.
- Credentials reuse the encrypted `api_token` (Flex token) and
`api_secret` (Flex Query ID) columns — **no migration**.
- The IB option (two fields) is added to the connect dialog, inline
connect flow, and update-credentials dialog, with Spanish translations.

## Feature flag (why this is a draft)

Gated behind a Pennant feature `App\Features\InteractiveBrokers` (off by
default). It was built against documented/open-source Flex XML fixtures,
**not a live IBKR account** (we don't have one). Before enabling,
validate against a real account (beta tester or a free IBKR account):

```
php artisan feature:enable InteractiveBrokers user@example.com
```

If the parser needs tweaks against real XML, they should be minor
(field-name level).

## Tests
- Client + balance sync: NAV → balance, invested/profit, daily backfill,
since-date incremental, multi-account, GetStatement polling, token-401 /
rate-limit-429 mapping.
- Controller: feature-flag gate (403), valid/invalid credentials,
subscription gate, onboarding auto-create, validation.
- Factory wiring, enum cases, job-level sync, feature-flag visibility
(vitest).
- Spanish translations added (enforced by `LocalizationTest`).

All green: `pint --test`, `phpstan`, OpenBanking + localization suite,
vitest.
2026-06-23 11:39:24 +02:00
..
AccountBalanceFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
AccountFactory.php feat(accounts): add loan amortization projections for loan accounts (#246) 2026-03-26 15:06:09 +01:00
AiConsentFactory.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
AutomationRuleFactory.php feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
BankFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
BankingConnectionFactory.php feat(banking): add Interactive Brokers sync via Flex Web Service (#581) 2026-06-23 11:39:24 +02:00
BudgetFactory.php feat: add catch-all budgets (#527) 2026-06-15 16:07:19 +00:00
BudgetPeriodFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
BudgetTransactionFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
CategoryCorrectionFactory.php feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
CategoryFactory.php feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
ExchangeRateFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
IntegrationRequestFactory.php feat(integration-requests): markdown comments and in-progress status (#553) 2026-06-18 08:36:43 +00:00
IntegrationRequestVoteFactory.php feat(integration-requests): community board to request & vote bank integrations (#550) 2026-06-17 12:50:51 +00:00
LabelFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
LoanDetailFactory.php feat(accounts): add loan amortization projections for loan accounts (#246) 2026-03-26 15:06:09 +01:00
RealEstateDetailFactory.php feat(accounts): add market value and annual revaluation to real estate accounts (#245) 2026-03-26 11:02:20 +01:00
RuleSuggestionFactory.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
SavedFilterFactory.php feat(transactions): save and reuse transaction filters (#496) 2026-06-05 18:00:14 +02:00
SuggestionRunFactory.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
TransactionFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
UserFactory.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
UserLeadFactory.php feat(leads): cohort-based launch invitations with per-user Stripe coupons (#333) 2026-04-30 15:10:28 +01:00
UserMailLogFactory.php feat(drip): email users stuck on the paywall a day after onboarding (#562) 2026-06-19 14:11:12 +00:00
UserSettingFactory.php feat(settings): let users disable bank transactions email (#472) 2026-06-02 12:24:39 +02:00