read.markets/tests
Giorgio Gilestro a07fd144ea stripe: per-cadence cooling-off + manage-subscription button
Bundles three related pieces that came out of the operator's first
end-to-end test of the paid flow:

1. Manage subscription button on /settings (paid users with a real
   Stripe sub — i.e. not credit-granted access). POSTs to the existing
   /api/stripe/portal endpoint; Stripe-hosted customer portal handles
   card updates, cancellation, monthly↔annual switch, invoice history.
   Replaces the stale "Paid features unlock with Paddle (D.3) or
   invite credits" hint for free users with a live link to /pricing.

2. Per-cadence cooling-off treatment:

   - **Annual £70**: 14-day free trial via
     subscription_data.trial_period_days=14. No money moves during
     the trial, so the CCR 2013 14-day refund question doesn't arise
     (nothing paid = nothing to refund). Card is still required at
     checkout so Stripe can charge on day 15.

   - **Monthly £7**: bills immediately. A 14-day trial there would
     give away ~50% of cycle one. Instead, /pricing now carries a
     required tick-box above the Subscribe buttons (subscribe stays
     disabled until checked) — by ticking, the user expressly
     consents to begin performance immediately and acknowledges that
     this extinguishes their statutory 14-day right under Reg 36
     CCR 2013. Consent collected on our own page (not via Stripe's
     account-wide consent_collection.terms_of_service) so each
     product can keep its own Terms URL as we add more.

3. T&C §6 clause 1 split into 1a (annual / trial substitute) +
   1b (monthly / Reg 36 waiver via on-page tick-box). Clause 2
   (post-cooling-off cancellation) unchanged.

Settings page shows "Free trial — N days remaining" while the
sub is in `trialing` status, falling back to "Paid subscription
active." once it transitions to active. Countdown is computed
server-side from User.stripe_trial_end_at (new column, migration
0020) populated by the subscription.created/updated webhook from
the Stripe trial_end timestamp; cleared on the trialing→active
transition and on revoke.

Drive-by: fixed a structlog kwarg-name collision on
`log.warning(..., event=event_type, ...)` in both polar_webhook.py
and stripe_billing.py — `event` is structlog's positional event
name and "got multiple values" crashed the user-not-found log
path. Renamed to `event_type=` everywhere it appeared. Caught by
the new trialing-stores-trial-end test.

Tests
- 4 new in test_stripe_billing.py covering monthly (no trial, no
  consent_collection), annual (trial, no consent), trialing stores
  trial_end, trialing→active clears trial_end.
- 1 existing test renamed + reworked for the consent split.
- Full suite: 224 passed, 5 skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:06:19 +02:00
..
fixtures phase B (1/4): CSV parser + InstrumentMap (T212 shortcode → Yahoo ticker) 2026-05-16 10:53:08 +01:00
conftest.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
test_access.py phase D milestones 1+2: referral system + paid-access gate 2026-05-21 23:25:35 +01:00
test_api_helpers.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
test_branding_consistency.py ui: light theme by default (dark is opt-in) 2026-05-22 21:51:23 +01:00
test_chat_and_log_gates.py pricing: land £7/£70 paid tier and make behaviour match 2026-05-26 11:34:37 +02:00
test_cli.py phase D milestones 1+2: referral system + paid-access gate 2026-05-21 23:25:35 +01:00
test_config_loading.py test: drop stale "pie" assertion from test_default_groups_present 2026-05-26 00:20:01 +02:00
test_csv_import.py sync: encrypted cloud backup for portfolios + settings UX rework 2026-05-23 16:15:54 +02:00
test_digest_prompts.py digest: factor tone clause; kw-only digest helper; empty-data test 2026-05-25 23:00:07 +02:00
test_email_digest_job.py test+fix: make the suite run cleanly in the test container 2026-05-26 00:11:18 +02:00
test_email_render.py email: render_digest_email — multipart digest template 2026-05-25 23:02:05 +02:00
test_email_service.py brand: rename product to "Read the Markets" (read.markets) 2026-05-22 19:39:38 +01:00
test_email_unsubscribe.py email: tighten unsubscribe — test isolation, accurate comments, tighter assertion 2026-05-25 23:10:29 +02:00
test_glossary.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
test_instrument_map.py phase B (1/4): CSV parser + InstrumentMap (T212 shortcode → Yahoo ticker) 2026-05-16 10:53:08 +01:00
test_market_parsing.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
test_news_parsing.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
test_news_tagging.py news: auto-tag headlines + market-aware cadence + filter UI 2026-05-21 23:25:03 +01:00
test_news_window.py test+fix: make the suite run cleanly in the test container 2026-05-26 00:11:18 +02:00
test_openrouter_prompt.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
test_otp_service.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
test_pending_cookie.py phase D milestones 1+2: referral system + paid-access gate 2026-05-21 23:25:35 +01:00
test_polar_webhook.py polar: build /api/polar/webhook handler 2026-05-26 17:42:41 +02:00
test_portfolio_analysis.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
test_portfolio_sync_api.py sync: detect orphaned blobs (pepper rotation) + fix AESGCM arg order 2026-05-25 12:49:11 +02:00
test_portfolio_sync_service.py sync: encrypted cloud backup for portfolios + settings UX rework 2026-05-23 16:15:54 +02:00
test_referral.py phase D milestones 1+2: referral system + paid-access gate 2026-05-21 23:25:35 +01:00
test_settings_digest_api.py settings: digest opt-in + tone (PATCH /api/settings/digest + UI) 2026-05-25 23:23:03 +02:00
test_stripe_billing.py stripe: per-cadence cooling-off + manage-subscription button 2026-05-26 20:06:19 +02:00
test_universe_unlinkability.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
test_verify_subscribe.py beta-launch: respect returning-user opt-out + show digest job in ops LEDs 2026-05-25 23:33:53 +02:00