whisper-money/tests/Feature
Víctor Falcón a7dde5fbc5
feat(open-banking): finalize bank connection without a session via state token (#498)
## Why

iOS PWAs hand the bank redirect back to the system browser (Safari),
where the app session does not exist. The old `/open-banking/callback`
sat behind `auth` + `verified` middleware, so Safari hit it with no
session → bounced to `/login`, the callback body never ran, and the
connection stayed `pending` forever (4 stale rows observed in prod).

## What

Make the bank connection flow session-independent end to end.

### 1. State-token callback (session-independent)
- `store()` generates `Str::random(40)`, persists it on the connection,
and passes it to `startAuthorization` as `state`.
- `/open-banking/callback` is now **public**. It resolves the connection
from `state_token` (`resolveConnectionFromState`) and derives the owner
from the connection, not the session.
- Connection is finalized server-side (`session_id`, status,
`state_token = NULL`) before any redirect.
- New migration adds the nullable, unique `state_token` column; the
column is hidden on the model.

### 2. Completion page instead of login bounce
When the callback runs without a session (the Safari case), the
connection is already finalized server-side. Instead of bouncing the
user to `/login`, render a standalone "go back to the app" page — their
session lives in the PWA, not here.

- New page `resources/js/pages/open-banking/connection-complete.tsx`
(success / error states, dark mode).
- `finishRedirect()` renders the page when `!Auth::check()`; logged-in
users still get the normal redirect.

### 3. Onboarding: land on the connections step + poll cross-browser
- A logged-in user finishing the flow lands straight on the onboarding
connections step (`?step=create-account`), now resolved server-side via
a validated `initialStep` prop so the step is deterministic.
- While on that step the page polls every 4s (`usePoll`, `only:
['accounts']`), so a connection finalized in another browser (PWA →
Safari) shows up without a manual refresh.

## Flow

```
POST /authorize → stateToken = Str::random(40)
  → startAuthorization(state = stateToken)
  → create BankingConnection(pending, state_token)
EnableBanking → callback?code&state=stateToken  (PUBLIC, no session needed)
  → find connection WHERE state_token = state
  → createSession(code) → finalize (session_id, status, state_token = NULL)
  → session present? redirect to connections step / mapping
                   : render "go back to the app" completion page
PWA (logged in) → onboarding?step=create-account → polls accounts every 4s
```

## Tests

36 passing. Covers:
- state-token persistence and session-less finalization
- owner resolved from the connection regardless of who is authenticated
- completion page rendered (success + error) when no session; login
fallback when no connection resolves
- logged-in user redirected directly to the onboarding connections step
- `?step=create-account` lands on that step; unknown step falls back to
default
2026-06-06 12:12:07 +02:00
..
Api Remove encryption from bank account names (#104) 2026-02-09 14:15:26 +01:00
Auth fix: verify email via signed link without requiring login (#490) 2026-06-05 10:01:32 +02:00
Commands feat(leads): add user lead re-invite campaign (#432) 2026-05-26 08:35:31 +02:00
Console fix(categories): expose cashflow setting on create (#448) 2026-05-29 11:05:04 +02:00
Jobs feat(loans): backfill historical balances on loan creation (#322) 2026-04-24 13:09:34 +01:00
Listeners feat(budgets): track multiple categories and labels per budget (#466) 2026-06-01 12:32:23 +02:00
Mail feat(leads): add user lead re-invite campaign (#432) 2026-05-26 08:35:31 +02:00
Onboarding feat(open-banking): finalize bank connection without a session via state token (#498) 2026-06-06 12:12:07 +02:00
OpenBanking feat(open-banking): finalize bank connection without a session via state token (#498) 2026-06-06 12:12:07 +02:00
Services feat(leads): cohort-based launch invitations with per-user Stripe coupons (#333) 2026-04-30 15:10:28 +01:00
Settings refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
Sync refactor: Simplify transaction endpoints architecture (#76) 2026-01-25 16:15:17 +01:00
AccountBalanceControllerTest.php feat: investment benefits — show gains/losses on investment accounts (#140) 2026-02-23 13:59:10 +01:00
AccountControllerTest.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
ApplyRealEstateRevaluationTest.php fix(real-estate): compound annual revaluation monthly (#337) 2026-04-27 07:35:51 +01:00
AuthenticatedLayoutSafeAreaTest.php fix: keep iOS content below the notch (#280) 2026-04-13 13:59:05 +01:00
AutomationRuleApplicationTest.php feat(transactions): add counterparty fields (#440) 2026-05-27 16:20:55 +02:00
AutomationRuleEvaluationTest.php feat(transactions): add counterparty fields (#440) 2026-05-27 16:20:55 +02:00
AutomationRuleTest.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
BackfillAccountIbansCommandTest.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
BalanceLookupTest.php feat: investment benefits — show gains/losses on investment accounts (#140) 2026-02-23 13:59:10 +01:00
BudgetHistoricalAssignmentTest.php feat(budgets): track multiple categories and labels per budget (#466) 2026-06-01 12:32:23 +02:00
BudgetPeriodDateTest.php Remove budgets feature flag (#108) 2026-02-12 09:58:01 +01:00
BudgetPeriodServiceTest.php Add yearly budget period (#384) 2026-05-12 13:35:25 +02:00
BudgetTest.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
BudgetTransactionServiceTest.php feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
BulkUpdateTransactionsTest.php refactor: Simplify transaction endpoints architecture (#76) 2026-01-25 16:15:17 +01:00
CancelFreeEnableBankingConnectionsCommandTest.php Cancel Enable Banking connections for free users (#289) 2026-04-15 16:23:03 +02:00
CashflowAnalyticsTest.php feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
CashflowPageTest.php feat(cashflow): add savings and period views (#424) 2026-05-25 16:41:00 +02:00
CurrencyConversionServiceTest.php fix(currency): degrade gracefully when rates return 404 (#449) 2026-05-29 14:44:56 +02:00
DashboardAnalyticsTest.php feat: expand parent categories inline in breakdowns (#486) 2026-06-04 11:19:21 +02:00
DashboardTest.php feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
DecryptTransactionsTest.php feat: Decrypt encrypted transactions on key unlock (#123) 2026-02-16 10:37:43 +01:00
DeleteUserCommandTest.php Support soft-deleted users with reusable emails (#316) 2026-04-22 11:41:41 +01:00
DemoAccountRestrictionsTest.php test: Optimize DemoAccountRestrictionsTest to run under 10s 2026-01-28 21:27:27 +01:00
DisconnectBankingConnectionsCommandTest.php feat(banking): add command to disconnect connections by id (#497) 2026-06-06 11:16:01 +02:00
DiscordWebhookTest.php feat: add Discord admin feed for daily stats and Stripe events (#458) 2026-05-30 18:14:46 +02:00
EncryptionTest.php User Onboarding Flow (#23) 2025-12-12 13:06:08 +01:00
ExampleTest.php Install Pest 2025-11-07 12:01:58 +00:00
ExchangeRateServiceTest.php Fix PHP-LARAVEL-1V exchange rate cache race (#383) 2026-05-12 12:45:45 +02:00
GenerateStripePromotionCodesCommandTest.php feat(stripe): add promo code generator (#311) 2026-04-20 18:15:28 +01:00
IdorVulnerabilityTest.php refactor: Simplify transaction endpoints architecture (#76) 2026-01-25 16:15:17 +01:00
ImportDataTest.php fix: Apply automation rule labels on transaction creation and import (#79) 2026-01-27 11:11:29 +01:00
InertiaSharedDataTest.php feat(transactions): save and reuse transaction filters (#496) 2026-06-05 18:00:14 +02:00
LabelTest.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
LandingAuthOverrideTest.php feat(leads): cohort-based launch invitations with per-user Stripe coupons (#333) 2026-04-30 15:10:28 +01:00
LoanTest.php feat(discord): enrich Stripe event messages and dedupe deliveries (#460) 2026-05-31 12:50:37 +02:00
LocalizationTest.php feat(i18n): add localization test and fix missing Spanish translations (#174) 2026-03-01 12:32:39 +00:00
LoggingConfigTest.php fix(logging): keep laravel.log writable across container UIDs (#451) 2026-05-29 15:10:50 +02:00
MailSenderTest.php Cancel Enable Banking connections for free users (#289) 2026-04-15 16:23:03 +02:00
PlaintextTransactionsTest.php Remove plaintext-transactions feature flag & E2E references (#116) 2026-02-13 11:10:21 +01:00
PopoverSafeAreaTest.php fix: keep iOS popovers below the notch (#282) 2026-04-13 15:19:56 +01:00
PostStripeEventToDiscordTest.php feat(discord): enrich Stripe event messages and dedupe deliveries (#460) 2026-05-31 12:50:37 +02:00
PwaTest.php Harden browser storage and PostHog recording (#402) 2026-05-14 15:57:48 +02:00
ReEvaluateTransactionRulesTest.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
RealEstateAvailabilityTest.php refactor(real-estate): remove Pennant gating (#308) 2026-04-20 13:31:49 +01:00
RealEstateTest.php refactor(real-estate): remove Pennant gating (#308) 2026-04-20 13:31:49 +01:00
ResendLeadVerificationEmailsCommandTest.php feat: resend verification emails to unverified leads (#287) 2026-04-15 09:13:27 +01:00
ResendSyncCommandTest.php feat: Sync new users to Resend contacts (#85) 2026-01-28 21:25:58 +01:00
ResendSyncLeadsCommandTest.php perf(resend): default sync-leads to last 24h window (#354) 2026-05-05 09:57:25 +01:00
RouteNotificationForMailTest.php fix(notifications): skip mail dispatch when recipient email is invalid (#387) 2026-05-13 09:47:50 +01:00
SavedFilterTest.php feat(transactions): save and reuse transaction filters (#496) 2026-06-05 18:00:14 +02:00
SendDailyStatsReportCommandTest.php feat(discord): enrich Stripe event messages and dedupe deliveries (#460) 2026-05-31 12:50:37 +02:00
SentryConfigTest.php fix(sentry): only report errors in production (#467) 2026-06-01 12:41:31 +02:00
SentryUserMiddlewareTest.php Add Sentry user context (#348) 2026-05-04 13:26:50 +01:00
SitemapTest.php User Onboarding Flow (#23) 2025-12-12 13:06:08 +01:00
StripeSubscriptionStatsCommandTest.php feat: add Discord admin feed for daily stats and Stripe events (#458) 2026-05-30 18:14:46 +02:00
SubscriptionTest.php fix: allow managing canceled connections (#417) 2026-05-22 10:52:50 +01:00
SyncBankingConnectionsCommandTest.php chore: Remove account-mapping feature flag (#252) 2026-04-01 12:09:22 +02:00
SyncStripePricesCommandTest.php fix(pricing): update final release prices (#288) 2026-04-15 14:49:02 +01:00
TransactionFilterTest.php fix(transactions): combine category and label filters with OR (#495) 2026-06-05 17:11:02 +02:00
TransactionTest.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
UserLeadTest.php feat: verify waitlist leads (#285) 2026-04-14 11:26:01 +01:00
WelcomeBanksOrderingTest.php feat(open-banking): remove feature flag gating (#297) 2026-04-17 10:20:05 +02:00