refactor(pricing): refonte v7.0 — 3 Cloud (Basic 189$/Essentiel 349$/Pro 549$) + DictIA Local (5998$ An1) + Pro+ soumission

Remplace l'ancien pricing (DictIA 8 / 16 / Cloud) par la nouvelle structure
canonique v7.0 : 4 forfaits + 1 sentinel quote-only.

Changements clés :
- pricing_card.html : signature étendue (badge, recommended, capacity_audio,
  capacity_storage, gpu, yearly_renewal, cta_label) + format prix server-side
  avec NBSP OQLF (5998 -> 5 998 $)
- _pricing_tiers.html : 4 cards (Cloud Basic 189$, Cloud Essentiel 349$,
  Cloud Pro 549$+485$ RECOMMANDÉ, DictIA Local 5998$ An1) + chip Pro+
  soumission -> /contact?pro-plus=1
- plans.py : refonte complète avec yearly_renewal_env (DictIA Local An 2+ =
  500$/an) + is_quote_only sentinel (Pro+ -> redirect /contact, jamais Stripe)
- routes.py : Pro+ intercepté avant le flow Stripe Checkout
- env.stripe.example : nouveau naming STRIPE_CLOUD_BASIC|ESSENTIEL|PRO_*
  + STRIPE_DICTIA_LOCAL_SETUP/RENEWAL_YEARLY
- tarifs.html : header "Quatre forfaits", matrice comparative 4 colonnes,
  FAQ enrichie (7 questions incluant DictIA Local + onboarding Pro + Pro+)
- fonctionnalites.html : section Architecture refondue (4 cards v7.0)
- landing.html : ROI footnote + cycle "189$" + wave "189$/mois" actualisés
- roi_calculator.js : recalibrage sur Cloud ESSENTIEL 349$ × 12 = 4188$/an
- routes.py marketing : FAQ "DictIA 8 et 16" -> "DictIA LOCAL"
- contact.html : "déploiements DictIA 16" -> "Cloud PRO" + "DictIA LOCAL"

Tests :
- test_marketing_landing_template.py : assertions prix v7.0 (189/349/549/5998),
  4 slugs (cloud-basic, cloud-essentiel, cloud-pro, dictia-local), Pro+ chip,
  capacity chips, RECOMMANDÉ sur Cloud PRO
- test_marketing_secondary_pages.py : 4 cards + Pro+ chip + matrice 4 col +
  FAQ 7 questions
- test_stripe_checkout.py : env vars v7.0, slugs cloud-basic/cloud-pro/
  dictia-local + nouveau test pro-plus -> /contact + tests setup pour Cloud PRO
  et DictIA Local
- test_stripe_webhook.py : plan_slug metadata cloud-basic

Status : 28/28 Stripe checkout + 17/17 webhook + 93/98 marketing pass
(les 5 marketing failures sont pré-existantes, non liées au pricing :
test_landing_has_main_nav et test_footer_links_complete = /blog manquant ;
test_trust_bar_has_eyebrow_factual_phrasing + 2 tests conformite =
casing eyebrow + entité é — vérifié par git stash baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-28 21:06:12 -04:00
parent e8c7e5cd43
commit 1c4cafaf69
16 changed files with 648 additions and 301 deletions

View File

@@ -326,54 +326,66 @@ def test_pricing_section_present():
assert 'TPS' in body and 'TVQ' in body, "Missing tax disclaimer (TPS/TVQ)"
def test_pricing_3_tiers_with_canonical_amounts():
"""Pricing has 3 tiers: DictIA 8 (3450/173), DictIA 16 (5750/201), DictIA Cloud (0/369)."""
def test_pricing_4_tiers_with_canonical_amounts_v7():
"""Pricing v7.0 — 4 tiers + Pro+ chip:
Cloud BASIC (189 $/mo), Cloud ESSENTIEL (349 $/mo),
Cloud PRO (549 $/mo + 485 $ onboarding) RECOMMANDÉ,
DictIA LOCAL (5 998 $ An 1 + 500 $/an dès An 2).
"""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# Names
for name in ['DictIA 8', 'DictIA 16', 'DictIA Cloud']:
assert name in body, f"Missing pricing tier: {name}"
# Canonical prices with NBSP per OQLF
assert '3&nbsp;450&nbsp;$' in body, "Missing DictIA 8 setup price"
assert '173&nbsp;$' in body, "Missing DictIA 8 monthly price"
assert '5&nbsp;750&nbsp;$' in body, "Missing DictIA 16 setup price"
assert '201&nbsp;$' in body, "Missing DictIA 16 monthly price"
assert '369&nbsp;$' in body, "Missing DictIA Cloud monthly price (canonical 369$)"
# 4 forfait names
for name in ['Cloud BASIC', 'Cloud ESSENTIEL', 'Cloud PRO', 'DictIA LOCAL']:
assert name in body, f"Missing v7.0 pricing tier: {name}"
# Canonical prices with NBSP per OQLF (formatted by macro)
assert '189&nbsp;$' in body, "Missing Cloud BASIC monthly price (189 $)"
assert '349&nbsp;$' in body, "Missing Cloud ESSENTIEL monthly price (349 $)"
assert '549&nbsp;$' in body, "Missing Cloud PRO monthly price (549 $)"
assert '485&nbsp;$' in body, "Missing Cloud PRO setup/onboarding price (485 $)"
assert '5&nbsp;998&nbsp;$' in body, "Missing DictIA LOCAL An 1 price (5 998 $)"
assert '500&nbsp;$/an' in body, "Missing DictIA LOCAL renewal mention (500 $/an)"
# Pro+ soumission chip
assert 'Pro+' in body, "Missing Pro+ soumission chip"
assert '/contact?pro-plus=1' in body, "Missing Pro+ CTA link"
def test_pricing_recommended_tier_is_dictia_16():
"""DictIA 16 is the visually-recommended tier (RECOMMANDÉ badge + grad-bg frame)."""
def test_pricing_recommended_tier_is_cloud_pro():
"""Cloud PRO is the visually-recommended tier (RECOMMANDÉ badge + grad-bg frame)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
assert 'RECOMMAND' in body, "Missing RECOMMANDÉ badge"
# The recommended tier wraps in grad-bg p-[1.5px] rounded FlexiHub style (V3 brutalist 4px card frame)
assert 'grad-bg p-[1.5px] rounded"' in body or 'grad-bg p-[1.5px] rounded ' in body, \
"Missing FlexiHub gradient frame on recommended tier (rounded 4px)"
assert 'grad-bg p-[1.5px] rounded' in body, \
"Missing FlexiHub gradient frame on recommended tier"
def test_pricing_cta_uses_reserver_pre_launch_wording():
"""CTAs say 'Réserver' not 'Choisir' — pre-launch LPC art. 219 hygiene."""
def test_pricing_cta_labels_v7():
"""CTAs reflect v7.0 forfait choice (Démarrer en Cloud / Choisir Essentiel / Commander Pro / Configurer DictIA Local)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
for slug in ['dictia-8', 'dictia-16', 'dictia-cloud']:
for slug in ['cloud-basic', 'cloud-essentiel', 'cloud-pro', 'dictia-local']:
assert f'href="/checkout/{slug}"' in body, f"Missing checkout link for {slug}"
assert 'Réserver DictIA 8' in body or 'R&eacute;server DictIA 8' in body, "CTA must use 'Réserver' wording (pre-launch)"
# CTA labels match the macro callers in _pricing_tiers.html
assert 'Démarrer en Cloud' in body or 'D&eacute;marrer en Cloud' in body
assert 'Choisir Essentiel' in body
assert 'Commander Pro' in body
assert 'Configurer DictIA Local' in body
def test_pricing_features_use_safe_filter_no_double_escape():
"""Pricing card features piped through | safe — '&nbsp;' must render single-escaped, not double."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# GPU sizes use NBSP
assert 'GPU 8&nbsp;Go RTX' in body, "GPU 8 Go feature missing or NBSP double-escaped"
assert 'GPU 16&nbsp;Go RTX' in body, "GPU 16 Go feature missing or NBSP double-escaped"
# Q&R card must use French Q&R, not English Q&A
assert 'Q&amp;R' in body, "DictIA 16 must mention Q&R (French), not Q&A (English)"
assert 'Q&A' not in body, "Must use French Q&R consistently — no English Q&A"
# Capacity chips use NBSP
assert '~165&nbsp;h audio/mois' in body, "Missing Cloud BASIC capacity chip"
assert '100&nbsp;Go' in body, "Missing Cloud BASIC storage chip"
assert '~660&nbsp;h audio/mois' in body, "Missing Cloud PRO capacity chip"
assert '500&nbsp;Go' in body, "Missing Cloud PRO storage chip"
assert '2&nbsp;To SSD' in body, "Missing DictIA LOCAL storage chip"
# WhisperX precision claim w/ NBSP
assert 'WhisperX Large-v3' in body, "Missing WhisperX Large-v3 mention"
# Loi 25 with NBSP
assert 'Conforme Loi&nbsp;25' in body, "Conforme Loi 25 must use NBSP"
# SLA must be hedged ('visé') not absolute claim
assert 'SLA visé 99,9' in body, "SLA must be hedged 'visé' (pre-launch LPC art. 219 hygiene)"
assert 'Loi&nbsp;25' in body, "Loi 25 must use NBSP"
# Negative: NO double-escape
assert '&amp;nbsp;' not in body, "NBSP must not be double-escaped — | safe missing on pricing macro?"
@@ -450,8 +462,8 @@ def test_pricing_cta_url_no_double_slash():
"""pricing_card uses cta_url.rstrip('/') so href never has '//' (regression guard)."""
client = app.test_client()
body = client.get('/').data.decode('utf-8')
# All 3 CTAs use the default cta_url='/checkout' (no trailing slash) — so /checkout/dictia-X
for slug in ['dictia-8', 'dictia-16', 'dictia-cloud']:
# All 4 CTAs use the default cta_url='/checkout' (no trailing slash) — so /checkout/<slug>
for slug in ['cloud-basic', 'cloud-essentiel', 'cloud-pro', 'dictia-local']:
assert f'href="/checkout/{slug}"' in body, f"Missing single-slash href for {slug}"
assert f'href="/checkout//{slug}"' not in body, f"Double-slash regression for {slug}"
@@ -788,7 +800,7 @@ def test_round2_cycle_section_present():
assert 'Retranscription humaine' in body
assert 'IA cloud américaine' in body
assert 'NON CONFORME' in body
assert '315' in body and '173' in body, "Canonical Cycle pricing must appear"
assert '315' in body and '189' in body, "Canonical Cycle pricing must appear (315 humain vs 189 Cloud BASIC v7.0)"
assert 'Loi 25 conforme' in body
assert '100&nbsp;% hébergé au Québec' in body or '100 % hébergé au Québec' in body
# Phase animation hooks
@@ -811,9 +823,9 @@ def test_round2_wave_section_present():
# Canonical pain labels
assert '4 à 6h pour transcrire 1h' in body
assert 'Délais de 48h à 5 jours' in body
# Canonical solution labels (NBSP-aware)
# Canonical solution labels (NBSP-aware) — v7.0 Cloud BASIC entry price
assert '~2 min pour 1h d' in body
assert '173&nbsp;$/mois' in body or '173 $/mois' in body
assert '189&nbsp;$/mois' in body or '189 $/mois' in body
# Alpine state for interactive slider
assert 'onMove($event)' in body
assert 'isMobile' in body
@@ -989,8 +1001,8 @@ def test_round2_oqlf_nbsp_in_new_sections():
body = client.get('/').data.decode('utf-8')
# Cycle section savings
assert '3&nbsp;924&nbsp;$' in body or '3 924 $' in body
# Wave solution card pricing
assert '173&nbsp;$/mois' in body or 'Dès 173' in body
# Wave solution card pricing — v7.0 Cloud BASIC entry price
assert '189&nbsp;$/mois' in body or 'Dès 189' in body
# Cadre — Loi 25 fine
assert '25 M$' in body or '25&nbsp;M$' in body