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