whisper-money/database/migrations
Víctor Falcón b6bc7756bc
feat(subscriptions): end the trial experiment and make the trial length per plan (#762)
Ends the trial/pricing A/B/C experiment. Everyone gets the control offer
— a free trial — and the trial length becomes a per-plan setting.

## Trial length

| Plan | Before | Now | Env override |
|---|---|---|---|
| Yearly | 15 days | **15 days** | `STRIPE_PRO_YEARLY_TRIAL_DAYS` |
| Monthly | 15 days | **7 days** | `STRIPE_PRO_MONTHLY_TRIAL_DAYS` |

**Note that monthly 15 → 7 is a new bet, not a rollback.** The control
arm was 15 days on both plans, and 7 days on monthly is a value the
experiment never tested (`reduced_trial` was monthly 3 / yearly 7). The
rationale is that the longer commitment earns the longer trial; it ships
here at the same time as the instrument that could measure it is
removed, so it will not be measurable as an isolated effect.

## Final experiment numbers

Archived here because `stats:experiment-funnel` and its collector are
deleted by this PR and the purge migration's `down()` is a no-op.

| Variant | Assigned | Subscribed | Active | Refunded |
|---|---|---|---|---|
| control | 597 | 46 | 13 | 0 |
| reduced_trial | 590 | 50 | 10 | 0 |
| pay_now | 609 | 41 | 21 | 18 |
| legacy | 94 | 55 | 31 | 0 |

## What is deleted

- `App\Features\SubscriptionExperiment` (the Pennant A/B/C assignment)
and the `ExperimentOffer` service.
- The `pay_now` self-service refund: `RefundSelfServe`, the
`settings.billing.refund` route, the controller actions and Discord
embeds, the money-back card in billing settings, and the
`stripe:verify-refund` sandbox command.
- The weekly `stats:experiment-funnel` report, its collector, and the
`ProportionSignificance` / `BinomialProportion` helpers it was the only
caller of, plus its schedule entry.
- The `subscriptions.experiment.*` config block and the orphaned
`es`/`fr` translation strings.
- A data migration purges the ~1,890 stored Pennant assignments.

`subscriptions.refunded_at` is deliberately **kept**: nothing reads it
anymore, but it is the only record of the 18 refunds the experiment
issued. The migration carries a comment saying so.

## Fixes found in review

- **The surviving funnel report was mis-scoring conversions.**
`SubscriptionFunnelCollector` compared every cancellation to one global
trial length. With trials now diverging per plan, a monthly subscriber
who was billed and cancelled on day 10 was scored as never having paid.
It now reads each subscription's own `trial_ends_at`, and the longest
plan trial is used only for deciding when a cohort is old enough to
score. Covered by two new tests.
- **The trial length swapped silently.** It lived on a single line under
the plan selector, which rewrote itself when the user switched plan. Now
that the plans genuinely differ, each plan card shows its own length.
- The report legend no longer quotes a single trial length for both
plans, and warns that the experiment weeks are still inside its window.

## Before merging

- [x] **Unset `SUBSCRIPTION_EXPERIMENT_STARTED_AT` in production** so no
new `pay_now` assignment happens while this waits. Anyone who checks out
under `pay_now` between now and the deploy is charged upfront and then
loses the one-tap refund they were promised at the point of payment.
Checked just before opening this PR: **0 `pay_now` subscriptions
currently inside the 3-day window**, so nobody is stranded today.
- [x] Drop the now-orphaned `SUBSCRIPTION_EXPERIMENT_*` variables from
the production env with the deploy.
- [x] If old containers are still serving while the purge migration
runs, a few assignments can be re-resolved and reappear. Harmless —
re-run `php artisan pennant:purge "App\Features\SubscriptionExperiment"`
once the deploy settles if you want the table clean.

Support note: a manual Stripe refund for a former `pay_now` user will
not disconnect their bank connections, which the automated flow used to
do.

## Demo


https://github.com/user-attachments/assets/3614d488-05c6-405d-a687-bbf45746879a


<!-- PLACEHOLDER: drag the QA video here -->

## QA

Browser-tested against the running app:

- Paywall: annual card shows "15 days free", monthly card "7 days free";
the terms line under the selector follows the selected plan (15 ↔ 7);
mobile viewport renders fine.
- Billing settings: no money-back card for a free user or an active
subscriber; `POST /settings/billing/refund` returns 404.
- No console or network errors on any screen.
- `stats:subscription-funnel` still renders and posts.
- The purge migration leaves 0 `SubscriptionExperiment` rows.

Full suite green (2045 tests) apart from the known local-only
`DashboardTest` 409; `pint`, `lint`, `format` and `build` all clean.
2026-08-12 10:59:55 +02:00
..
0001_01_01_000000_create_users_table.php
…
0001_01_01_000001_create_cache_table.php
…
0001_01_01_000002_create_jobs_table.php
…
2025_08_26_100418_add_two_factor_columns_to_users_table.php
…
2025_11_07_135255_add_encryption_salt_to_users_table.php
…
2025_11_07_135256_create_encrypted_messages_table.php
…
2025_11_07_150038_create_banks_table.php
…
2025_11_07_150122_create_accounts_table.php
…
2025_11_07_150613_create_categories_table.php
…
2025_11_07_150659_create_transactions_table.php
…
2025_11_07_184056_modify_banks_table_add_user_id_and_simplify_name.php
…
2025_11_07_185018_change_banks_logo_to_text.php
…
2025_11_08_140230_change_transactions_id_to_uuid.php
…
2025_11_08_141344_make_category_id_nullable_in_transactions_table.php
…
2025_11_08_144530_add_soft_deletes_to_transactions_table.php
…
2025_11_10_110100_create_automation_rules_table.php
…
2025_11_15_172640_create_account_balances_table.php
…
2025_11_15_175636_change_transactions_amount_to_bigint.php
…
2025_11_15_195739_convert_all_ids_to_uuid.php
…
2025_11_18_184409_create_user_leads_table.php
…
2025_11_28_104227_add_source_to_transactions_table.php
…
2025_11_29_170353_add_type_to_categories_table.php
…
2025_11_29_170955_fix_account_balances_unique_constraint.php
…
2025_12_01_101443_add_unique_constraint_to_categories_table.php
…
2025_12_06_112515_create_customer_columns.php
…
2025_12_06_112516_create_subscriptions_table.php
…
2025_12_06_112517_create_subscription_items_table.php
…
2025_12_06_112518_add_meter_id_to_subscription_items_table.php
…
2025_12_06_112519_add_meter_event_name_to_subscription_items_table.php
…
2025_12_10_142006_add_onboarded_at_to_users_table.php
…
2025_12_12_092647_create_labels_table.php
…
2025_12_12_092650_create_label_transaction_table.php
…
2025_12_12_092651_create_automation_rule_labels_table.php
…
2025_12_12_141955_update_labels_unique_constraint_include_deleted_at.php
…
2025_12_16_151952_create_user_mail_logs_table.php
…
2025_12_19_092437_create_budgets_table.php
…
2025_12_19_092443_create_budget_periods_table.php
…
2025_12_19_092446_create_budget_transactions_table.php
…
2025_12_20_154221_create_features_table.php
…
2025_12_29_063338_add_currency_code_to_users_table.php
…
2026_01_08_192757_add_email_identifier_to_user_mail_logs.php
…
2026_01_08_204223_update_user_mail_logs_unique_constraint.php
…
2026_01_21_151353_add_indexes_for_budget_transaction_assignment.php
…
2026_01_22_071325_add_processing_historical_to_budget_periods.php
…
2026_01_22_143232_add_locale_to_users_table.php
…
2026_02_08_103953_create_user_settings_table.php
…
2026_02_09_110624_add_encrypted_to_accounts_table.php
…
2026_02_09_131403_make_description_iv_nullable_on_transactions.php
…
2026_02_09_144915_create_banking_connections_table.php
…
2026_02_09_144959_add_banking_connection_fields_to_accounts_table.php
…
2026_02_09_144959_add_external_transaction_id_to_transactions_table.php
…
2026_02_10_085759_add_aspsp_logo_to_banking_connections_table.php
…
2026_02_10_095639_add_raw_data_to_transactions_table.php
…
2026_02_11_113115_add_pending_accounts_data_to_banking_connections_table.php
…
2026_02_11_113119_add_linked_at_to_accounts_table.php
…
2026_02_13_083716_add_original_description_to_transactions_table.php
…
2026_02_18_084241_add_api_token_to_banking_connections_table.php
…
2026_02_18_110556_add_api_secret_to_banking_connections_table.php
…
2026_02_20_074846_create_exchange_rates_table.php
…
2026_02_22_180317_add_invested_amount_to_account_balances_table.php
…
2026_02_24_193117_fix_budget_transaction_refund_amounts.php
…
2026_03_03_114620_add_paywall_seen_at_to_users_table.php
…
2026_03_04_085843_add_waitlist_columns_to_user_leads_table.php
…
2026_03_04_094926_add_locale_to_user_leads_table.php
…
2026_03_12_095411_add_iban_to_accounts_table.php
…
2026_03_18_112307_add_cashflow_direction_to_categories_table.php
…
2026_03_19_151039_add_include_loans_in_net_worth_chart_to_user_settings_table.php
…
2026_03_20_110659_create_real_estate_details_table.php
…
2026_03_20_132058_make_bank_id_nullable_on_accounts_table.php
…
2026_03_20_154755_add_include_real_estate_in_net_worth_chart_to_user_settings_table.php
…
2026_03_25_125717_add_revaluation_percentage_to_real_estate_details_table.php
…
2026_03_25_132244_create_loan_details_table.php
…
2026_03_30_123011_add_consecutive_sync_failures_to_banking_connections_table.php
…
2026_03_30_123011_create_banking_sync_logs_table.php
…
2026_04_14_120000_add_email_verification_to_user_leads_table.php
…
2026_04_16_063515_add_bank_transactions_email_cutoff_at_to_banking_connections_table.php
…
2026_04_16_092644_add_timezone_to_users_table.php
…
2026_04_22_084113_add_deleted_at_to_users_table.php
…
2026_04_25_092824_add_launch_columns_to_user_leads_table.php
…
2026_05_05_070442_add_rate_limited_until_to_banking_connections_table.php
…
2026_05_05_132023_convert_custom_budgets_to_monthly.php
…
2026_05_13_085027_add_dedup_fingerprint_to_transactions_table.php
…
2026_05_25_115100_update_default_saving_and_investment_category_types.php
…
2026_05_26_061232_add_reinvitation_columns_to_user_leads_table.php
…
2026_05_27_061513_add_counterparty_names_to_transactions_table.php
…
2026_05_28_073203_update_categories_unique_constraint_for_soft_deletes.php
…
2026_05_29_085835_update_saving_and_investment_category_cashflow_direction.php
…
2026_06_01_085554_create_budget_category_table.php
…
2026_06_01_085554_create_budget_label_table.php
…
2026_06_01_085554_migrate_budget_category_label_to_pivots.php
…
2026_06_02_090000_add_parent_id_to_categories_table.php
…
2026_06_02_094823_add_notify_on_bank_transactions_synced_to_user_settings_table.php
…
2026_06_05_120000_create_saved_filters_table.php
…
2026_06_05_185212_add_state_token_to_banking_connections_table.php
…
2026_06_08_114044_add_analysis_days_to_saved_filters_table.php
…
2026_06_09_124010_add_analysis_mode_to_saved_filters_table.php
…
2026_06_10_083644_add_last_logged_in_at_to_users_table.php
…
2026_06_10_084055_add_last_active_at_to_users_table.php
…
2026_06_12_134225_create_ai_consents_table.php
…
2026_06_12_134523_create_suggestion_runs_table.php
…
2026_06_12_134524_create_rule_suggestions_table.php
…
2026_06_13_000000_add_is_catch_all_to_budgets_table.php
…
2026_06_15_120911_add_ai_categorization_fields_to_transactions_table.php
…
2026_06_15_120911_add_origin_to_automation_rules_table.php
…
2026_06_15_120911_create_category_corrections_table.php
…
2026_06_17_081319_add_ai_suggested_category_to_transactions_table.php
…
2026_06_17_114601_create_integration_requests_table.php
…
2026_06_17_114602_create_integration_request_votes_table.php
…
2026_06_17_120850_add_status_to_integration_requests_table.php
…
2026_06_17_122001_seed_initial_integration_requests.php
…
2026_06_17_141827_add_comment_to_integration_requests_table.php
…
2026_06_18_092830_drop_unique_user_vote_from_integration_request_votes_table.php
…
2026_06_19_134854_create_stuck_cohort_snapshots_table.php
…
2026_06_20_105609_align_accounts_encrypted_flag_with_plaintext_names.php
…
2026_06_20_165235_add_position_to_accounts_table.php
…
2026_06_26_124609_add_ai_model_to_transactions_table.php
…
2026_06_27_000000_backfill_xxx_account_currencies.php
…
2026_06_27_114559_add_refunded_at_to_subscriptions_table.php feat(subscriptions): end the trial experiment and make the trial length per plan (#762) 2026-08-12 10:59:55 +02:00
2026_06_27_154041_add_hidden_on_dashboard_to_accounts_table.php
…
2026_06_29_161916_add_transactions_last_visited_at_to_users_table.php
…
2026_07_01_064324_add_ai_consent_prompt_dismissed_at_to_users_table.php
…
2026_07_02_133321_add_user_source_created_at_index_to_transactions_table.php
…
2026_07_06_120000_create_spaces_table.php
…
2026_07_06_120001_create_space_user_table.php
…
2026_07_06_120002_create_space_invitations_table.php
…
2026_07_06_120003_add_current_space_id_to_users_table.php
…
2026_07_06_120004_add_space_id_to_owned_tables.php
…
2026_07_06_120005_backfill_spaces.php
…
2026_07_17_125642_create_personal_access_tokens_table.php
…
2026_07_17_154027_create_oauth_auth_codes_table.php
…
2026_07_17_154028_create_oauth_access_tokens_table.php
…
2026_07_17_154029_create_oauth_refresh_tokens_table.php
…
2026_07_17_154030_create_oauth_clients_table.php
…
2026_07_17_154031_create_oauth_device_codes_table.php
…
2026_07_18_104433_create_account_import_configs_table.php
…
2026_07_18_111320_add_upsell_source_to_subscriptions_table.php
…
2026_07_24_083854_add_notification_toggles_to_budgets_table.php
…
2026_07_24_083855_add_budget_notification_defaults_to_user_settings_table.php
…
2026_07_24_083855_add_limit_notified_flags_to_budget_periods_table.php
…
2026_07_24_100000_enable_budget_notifications_by_default.php
…
2026_07_25_102200_disable_budget_new_transaction_notifications.php
…
2026_08_09_150908_retry_enable_banking_connections_stuck_after_auth_failures.php
…
2026_08_10_090000_add_ownership_to_accounts_table.php
…
2026_08_10_144811_create_mcp_tool_calls_table.php
…
2026_08_11_101501_purge_subscription_experiment_feature_assignments.php feat(subscriptions): end the trial experiment and make the trial length per plan (#762) 2026-08-12 10:59:55 +02:00