Suggests transaction categorization rules during onboarding.
After a sync or import, it groups the uncategorized transactions, asks
Gemini (via laravel/ai) to map the common merchants to categories, and
shows the results for review. The user edits or drops any and creates
the ones they want. During onboarding the accepted rules also categorize
existing transactions right away.
Off by default: it needs the `AiRuleSuggestions` Pennant flag and a
per-user AI consent. The model and thresholds are config-driven.
`ai:suggest-rules {user}` prints what a user would get.
The settings-page surface and monthly regeneration are a follow-up.
## Summary
Every subscription now gets Stripe tax rates attached automatically via
Cashier.
## Changes
- `config/subscriptions.php`: new `tax_rates` array, env
`STRIPE_TAX_RATES` (comma-separated), default
`txr_1TPfzrLRCmKA3oWMNWmkQeq2`
- `app/Models/User.php`: `taxRates()` reads from config — Cashier picks
it up automatically on `newSubscription()` checkout + subscription
creation
- `tests/Feature/SubscriptionTest.php`: 2 tests
## Applies to
- New checkout sessions (`SubscriptionController::checkout`)
- New subscriptions created via Cashier
## Existing subscriptions
Not updated automatically. To sync:
```php
$user->subscription('default')->syncTaxRates();
```
## Notes
Using hard-coded tax rate IDs (not Stripe Tax auto-calc). Switch to
`Cashier::calculateTaxes()` later if desired.
## Summary
Adds a 15-day trial to the monthly and yearly plans. Configurable per
plan (or disabled) via config.
## Changes
- `config/subscriptions.php` — new `trial_days` key per plan (defaults:
monthly=15, yearly=15). Env overrides: `STRIPE_PRO_MONTHLY_TRIAL_DAYS`,
`STRIPE_PRO_YEARLY_TRIAL_DAYS`. Set to `0` to disable.
- `SubscriptionController::checkout` — applies `trialDays()` on the
Cashier subscription builder when `trial_days > 0`.
- Tests — assert `trial_days` surfaced in pricing props; assert
`trialDays(15)` applied on checkout; assert skipped when `0`.
## Notes
Stripe Checkout enforces a **minimum 2-day trial**. Values of `1` will
fail at Stripe. `0` disables cleanly.
## Test plan
```
php artisan test --compact tests/Feature/SubscriptionTest.php
```
## Summary
- update subscription pricing to the final release amounts for monthly
and annual billing
- keep billing UI formatting aligned with the configured currency so
1.99 €/month displays correctly
- refresh subscription and Stripe sync tests to lock the new values in
place
## Summary
- **Monthly equivalent pricing**: yearly plan now shows the per-month
price (e.g. €3.90/month) with a \"Billed annually at €46.80\" note
beneath, matching the paywall behaviour
- **Plan rename**: \"Pro Monthly\" and \"Pro Yearly\" renamed to
\"Standard Monthly\" and \"Standard Yearly\" in config
- **Free plan card**: when the `open-banking` feature flag is active, a
Free card is shown first in the pricing grid — same features as paid
plans but without \"Connect bank accounts\" and \"Priority support\"
- **Connect bank accounts feature**: conditionally prepended to paid
plan feature lists when `open-banking` is enabled
- **Grid layout**: column count dynamically accounts for the optional
free plan card
## Screenshots
<img width="1172" height="862" alt="image"
src="https://github.com/user-attachments/assets/1f2895ed-12fe-4b32-a117-19a584014ae7"
/>
<img width="1173" height="821" alt="image"
src="https://github.com/user-attachments/assets/6f56a697-9908-43c6-9b15-6dd8cec35826"
/>
## Summary
- **Dynamic Stripe price resolution**: Replaces hardcoded
`stripe_price_id` env vars with lookup-key-based resolution
(`stripe_lookup_key`). A new `php artisan stripe:sync-prices` command
creates/updates Stripe prices from `config/subscriptions.php`
automatically.
- **Locale-aware currency formatting**: Replaces all
`getCurrencySymbol() + toFixed(2)` patterns with `formatCurrency()`
(backed by `Intl.NumberFormat`) across `welcome.tsx`, `paywall.tsx`,
`billing.tsx`, and `step-create-account.tsx`, so symbol position and
separators are correct for the user's locale (e.g. `3,90 €` in Spanish).
- **EUR defaults and updated plan prices**: Cashier currency defaulted
to EUR, plan prices updated to €7.80/month and €46.80/year, and
`pricing.currency` is now shared as an Inertia prop.
- **Promo/discount cleanup**: Removed all FOUNDER discount mentions and
Discord community links from the paywall, landing pricing section, and
invitation email.
## Summary
Implements a complete user onboarding flow that guides new users through
setting up their account after registration.
## Features
- **Step-by-step wizard UI** with progress indicator and smooth
animations
- **E2E Encryption setup** with password strength indicator and
explanation of how encryption works
- **Account creation** supporting all account types (checking, savings,
credit card, investment, pension)
- **Category customization** with explanation of category types
(expenses, income, transfer)
- **Smart rules explanation** covering why there is no AI
auto-categorization (privacy & E2EE)
- **Transaction/balance import** based on account type
- **Sync integration** to ensure data consistency with backend
## Flow Diagram
```mermaid
flowchart TD
A[User Registration] --> B[Welcome]
B --> E[Encryption Explained]
E --> C{Has encryption key?}
C -->|No|F[Encryption Setup]
C -->|Yes|G{Has Existing Accounts?}
G -->|Yes| H[Show Existing Accounts]
G -->|No| I[Create First Account]
H --> J[Continue]
I --> K{Account Type?}
K -->|Checking/Savings/Credit Card| L[Import Transactions]
K -->|Investment/Pension| M[Import Balances]
J --> N[Category Types Explanation]
L --> N
M --> N
N --> O[Customize Categories]
O --> P[Smart Rules Explanation]
P --> Q[More Accounts?]
Q -->|Add More| I
Q -->|Finish| R[Complete]
R --> S[Redirect to Dashboard]
S --> T{Subscribed?}
T -->|No| U[Subscribe Page]
T -->|Yes| V[Dashboard]
```
## Technical Changes
### Backend
- Added `onboarded_at` field to users table with migration
- Created `EnsureOnboardingComplete` middleware for redirect logic
- Created `OnboardingController` with index and complete actions
- Custom `RegisterResponse` to redirect new users to onboarding
- Updated `AccountController::store` to return JSON for fetch requests
### Frontend
- `OnboardingLayout` - fullscreen layout with step progress
- `useOnboardingState` hook - manages step navigation and state
- 12 step components for each onboarding screen
- Backend sync after account creation and imports
### Tests
- Feature tests for onboarding middleware
- Updated existing tests to use `onboarded()` factory state
## Summary
Centralizes all pricing/subscription plan configuration in a single PHP
config file, making it easy to modify prices, features, and promotional
codes from one place.
## Changes
### Config (`config/subscriptions.php`)
- Added `plans` array with support for multiple billing cycles (monthly,
yearly, lifetime)
- Each plan includes: name, price, original_price (for discounts),
stripe_price_id, billing_period, and features
- Added `default_plan` setting to control which plan is highlighted
- Added `promo` config with enabled flag, code, description, and badge
### Frontend
- Created TypeScript types for `Plan`, `PromoConfig`, and
`PricingConfig`
- Updated `welcome.tsx` to display all plans in a responsive grid layout
- Updated `paywall.tsx` to show all plans with "Most Popular" and "Best
Value" badges
- Promo code section is now conditionally rendered based on
`promo.enabled`
### Backend
- Updated `HandleInertiaRequests.php` to share pricing config globally
- Removed redundant props from routes
### Tests
- Added test to verify pricing config structure is passed correctly
## How to Use
To modify pricing, edit `config/subscriptions.php`:
- Change `default_plan` to highlight a different plan
- Set `promo.enabled` to `false` (or `PROMO_ENABLED=false` in `.env`) to
hide promo codes
- Add/remove/modify plans as needed