Projet

Général

Profil

Fonctionnalité #1584

ouvert

Offre commerciale

Fonctionnalité #1584: Offre commerciale

Ajouté par Damien Cuvillier il y a 4 jours. Mis à jour il y a 4 jours.

Statut:
À Tester
Priorité:
Normal
Assigné à:
-
Début:
15/09/2026
Echéance:
% réalisé:

70%

Temps estimé:

Description

Sans être authentifié,
Limité à 20 combinaisons par recherche et 5 recherches par jour.

Déclare ensuite ces 2 offres


Le front web doit se connecter à Opsy pour remplir sa feuille de tarifs et d'offres


Fichiers

clipboard-202609151913-lt9w8.png (484 ko) clipboard-202609151913-lt9w8.png Damien Cuvillier, 15/09/2026 19:13
claude-session-1584.glog (652 ko) claude-session-1584.glog Damien Cuvillier, 15/09/2026 20:44
claude-session-1584.glog (797 ko) claude-session-1584.glog Damien Cuvillier, 15/09/2026 21:10
claude-session-1584.glog (220 ko) claude-session-1584.glog Damien Cuvillier, 16/09/2026 12:49
clipboard-202609151913-lt9w8.png

Mis à jour par Damien Cuvillier il y a 4 jours Actions #1

  • Description mis à jour (diff)

Mis à jour par Damien Cuvillier il y a 4 jours Actions #2

clipboard-202609151913-lt9w8.png

Mis à jour par Damien Cuvillier il y a 4 jours Actions #3

  • % réalisé changé de 0 à 10

Mis à jour par Damien Cuvillier il y a 4 jours Actions #4

  • Statut changé de À prioriser à En cours
  • % réalisé changé de 10 à 20

Mis à jour par Damien Cuvillier il y a 4 jours Actions #5

  • % réalisé changé de 20 à 30

Mis à jour par Damien Cuvillier il y a 4 jours Actions #6

  • % réalisé changé de 30 à 40

Mis à jour par Damien Cuvillier il y a 4 jours Actions #7

  • % réalisé changé de 40 à 50

Mis à jour par Damien Cuvillier il y a 4 jours Actions #8

  • % réalisé changé de 50 à 60

Mis à jour par Damien Cuvillier il y a 4 jours Actions #9

  • % réalisé changé de 60 à 70

Mis à jour par Damien Cuvillier il y a 4 jours Actions #10

Claude Code session summary:

#1584 — Offre commerciale

La feuille de tarifs n'est plus écrite dans le code : elle se lit dans le
catalogue Opsy. Et ce qu'un visiteur a le droit de faire suit la même source.

Le visiteur anonyme : 20 combinaisons, 5 recherches par jour

Ces plafonds étaient affichés mais seul le front les faisait respecter. Ils
sont désormais vérifiés côté serveur, sur /api/search-log — le seul endroit
qui voit la recherche entière : 21 combinaisons sont refusées
(combinations_exceeded, limite 20), la 6ᵉ recherche du jour aussi
(quota_exceeded, limite 5). Vérifié en exécution.

Deux offres, lues chez Opsy

Les sept features du comparatif existaient déjà dans la console ; leurs codes
réels sont maintenant ceux que l'application lit — search, combination,
keyword-generator, selection (et non les codes génériques que j'avais posés
en #1582). docs/OPSY_CATALOG.md donne la fiche complète à saisir :

  • starter — Compte gratuit, prix 0 : 15 recherches/jour, 80 combinaisons,
    générateur, historique, toutes les extensions ;
  • premium — Premium, 20 €/an (30 USD, 25 CHF à confirmer) : recherches et
    combinaisons illimitées, paniers, export CSV, historique.

⚠️ Les deux offres restent à créer dans la console — tu as choisi de les
saisir toi-même, je n'ai pas d'accès REALM_ADMIN au royaume 7app. Dès
qu'elles y sont, la page et les droits suivent sans redéploiement (catalogue
relu toutes les 10 minutes).

Côté droits, l'offre gratuite du catalogue (celle dont les prix sont nuls)
devient ce que reçoit tout compte qui n'a rien acheté : plus besoin d'une
licence émise pour que le compte gratuit ait ses 15 recherches et son
générateur. Trois régimes, dans l'ordre : licence active, puis offre gratuite,
puis — pour l'anonyme — les quota.anon* de l'application.

Le front branché sur le catalogue

PricingModel dresse une carte par offre publiée : nom, accroche, prix dans
chaque devise que le royaume vend, et le comparatif libellé par Opsy (un quota
sans plafond se dit « illimité »). La carte du visiteur anonyme, elle, reste
écrite par l'application — elle ne se vend pas. Le gabarit est devenu une
boucle unique. Le bouton suit l'état du compte : créer un compte, souscrire
(paywall), ou « ma licence » vers le portail. Le paywall ne propose que les
offres payantes.

Sans catalogue joignable, la grille retombe sur les deux cartes d'avant : la
page de tarifs ne se vide jamais.

Une correction au passage

Tous les POST du site partaient en 403 : déclarer notre propre
SecurityFilterChain (migration #1582) avait écarté la protection CSRF adaptée
du starter Opsy, sans la remplacer. Recherche, panier, devise : rien ne
passait. La configuration du starter est reprise dans AppSecurityConfig
(cookie XSRF-TOKEN + en-tête X-XSRF-TOKEN), et les appels fetch du bundle
TS portent le jeton (core/csrf.ts). Sans cela, la limite de recherches
demandée ici n'aurait pas pu fonctionner.

Vérifié

11 tests unitaires (nouveaux) sur PricingModel et LicenseService : cartes
et prix d'un catalogue à deux offres, quotas des trois régimes, repli sans
catalogue. Plus, en exécution contre l'Opsy local : lecture du catalogue réel
(7 features), rendu des cartes, refus au-delà de 20 combinaisons et de
5 recherches par jour, POST acceptés avec le jeton CSRF.

Restes connus

  • Les features extension (quota d'extensions), csv et history sont
    affichées au comparatif mais pas encore appliquées comme droits distincts :
    l'application ouvre toutes les extensions aux comptes et une liste courte aux
    anonymes, et l'export suit le droit aux paniers.
  • L'achat lui-même reste chez Stripe tant qu'Opsy n'expose pas de tunnel de
    commande.

Mis à jour par Damien Cuvillier il y a 4 jours Actions #11

  • % réalisé changé de 70 à 10

La section tarif du site internet doit être alimentée par opsy

fonctionnalités
tarifs dans toutes les devises
nom de l'offre et description

Mis à jour par Damien Cuvillier il y a 4 jours Actions #12

  • Statut changé de À Tester à En cours
  • % réalisé changé de 10 à 20

Mis à jour par Damien Cuvillier il y a 4 jours Actions #13

  • % réalisé changé de 20 à 30

Mis à jour par Damien Cuvillier il y a 4 jours Actions #14

  • % réalisé changé de 30 à 40

Mis à jour par Damien Cuvillier il y a 4 jours Actions #15

  • % réalisé changé de 40 à 50

Mis à jour par Damien Cuvillier il y a 4 jours Actions #16

Claude Code session summary:

#1584 — Offre commerciale

Le visiteur anonyme : 20 combinaisons, 5 recherches par jour

Ces plafonds étaient affichés mais seul le front les faisait respecter. Ils
sont vérifiés côté serveur sur /api/search-log — le seul endroit qui voit la
recherche entière : la 21ᵉ combinaison est refusée (combinations_exceeded,
limite 20) et la 6ᵉ recherche du jour aussi (quota_exceeded, limite 5).
Rejoué en exécution.

Les deux offres

Les sept features du comparatif existaient déjà dans la console ; leurs codes
réels sont ceux que lit l'application — search, combination,
keyword-generator, selection (plus history, extension, csv, affichées
mais pas encore appliquées comme droits distincts).

docs/OPSY_CATALOG.md porte la fiche complète, et
scripts/opsy-catalog.py la rejoue par l'API d'administration —
idempotent, et le même catalogue d'un royaume à l'autre :

scripts/opsy-catalog.py --dry-run                    # le plan, sans rien appeler
export OPSY_ADMIN_TOKEN="<jeton d'un REALM_ADMIN>"
scripts/opsy-catalog.py --api http://auth.7app.localhost:11501
  • starter — Compte gratuit, prix 0 : 15 recherches/jour, 80 combinaisons,
    générateur, historique, extensions ;
  • premium — Premium, 20 €/an (30 USD, 25 CHF à confirmer) : recherches et
    combinaisons illimitées, paniers, export CSV, historique.

⚠️ Le catalogue du royaume 7app est toujours vide : les offres restent à
poser, par le script ou à la main. Je n'ai pas de jeton REALM_ADMIN et je
n'écris pas en base pour contourner les règles d'Opsy (statuts, prix actifs,
audit). Une fois lancé, la page suit sans redéploiement — le catalogue est relu
toutes les 10 minutes.

Côté droits, l'offre gratuite (prix nuls) est ce que reçoit tout compte qui
n'a rien acheté : pas besoin d'une licence émise pour que le compte gratuit ait
ses 15 recherches et son générateur. Trois régimes, dans l'ordre : licence
active, offre gratuite, puis — pour l'anonyme — les quota.anon*.

Le front branché sur le catalogue

PricingModel dresse une carte par offre publiée : nom, accroche, prix dans
chaque devise que le royaume vend, comparatif libellé par Opsy (un quota sans
plafond se dit « illimité »). La carte du visiteur anonyme reste écrite par
l'application — elle ne se vend pas. Le gabarit est une boucle unique ; le
bouton suit l'état du compte (créer un compte, souscrire, « ma licence »), et
le paywall ne propose que les offres payantes. Sans catalogue joignable, la
grille retombe sur les deux cartes d'avant : la page de tarifs ne se vide
jamais.

Vérifié

14 tests unitaires : cartes, prix et comparatif d'un catalogue à deux offres,
quotas des trois régimes, repli sans catalogue. En exécution contre l'Opsy
local : lecture du catalogue réel (7 features), rendu des cartes, refus au-delà
de 20 combinaisons et de 5 recherches par jour. Le script a été éprouvé en
--dry-run et sur ses cas d'erreur (jeton absent, jeton invalide).

Reste non vérifié en conditions réelles : le rendu d'une grille avec de vraies
offres, faute de catalogue garni — c'est ce que couvrent les tests unitaires.

Restes connus

  • extension, csv et history sont au comparatif mais pas appliquées comme
    droits distincts : l'application ouvre toutes les extensions aux comptes et
    une liste courte aux anonymes, et l'export suit le droit aux paniers.
  • L'achat reste chez Stripe tant qu'Opsy n'expose pas de tunnel de commande.

Mis à jour par Damien Cuvillier il y a 4 jours Actions #17

tu dois maintenant brancher la rubrique commerciale aux fonctionnalités et tarifs enregistrés dans Opsy

Mis à jour par Damien Cuvillier il y a 4 jours Actions #18

  • Statut changé de À Tester à À Faire

Mis à jour par Damien Cuvillier il y a 4 jours Actions #19

  • % réalisé changé de 70 à 10

Mis à jour par Damien Cuvillier il y a 4 jours Actions #20

  • Statut changé de À Faire à En cours
  • % réalisé changé de 10 à 20

Mis à jour par Damien Cuvillier il y a 4 jours Actions #21

  • % réalisé changé de 20 à 30

Mis à jour par Damien Cuvillier il y a 4 jours Actions #22

  • % réalisé changé de 30 à 40

Mis à jour par Damien Cuvillier il y a 4 jours Actions #23

  • % réalisé changé de 40 à 50

Mis à jour par Damien Cuvillier il y a 4 jours Actions #24

  • % réalisé changé de 50 à 60

Mis à jour par Damien Cuvillier il y a 4 jours Actions #25

  • % réalisé changé de 60 à 70

Mis à jour par Damien Cuvillier il y a 4 jours Actions #26

Claude Code session summary:

#1584 — Offre commerciale

Le visiteur anonyme

Ses deux plafonds sont ceux demandés, et configurables sans redéploiement
(quota.* dans application.yml) :

Limite Valeur Variable
Combinaisons par recherche 20 ANON_COMBINATIONS_LIMIT
Recherches par jour 5 ANON_DAILY_SEARCHES

Ils sont appliqués côté serveur, pas seulement affichés : /api/check et
/api/search-log refusent en 429 (quota du jour) et 403
(combinations_exceeded) — /api/search-log est le seul point qui voit la
recherche entière, donc le seul qui peut compter les combinaisons.

Le visiteur garde sa carte « Démo » en tête de la feuille de tarifs : elle
décrit ce qu'on fait sans compte et ne se vend pas.

Les deux offres

Elles vivent dans la console Opsy, pas dans le code. La fiche complète
(codes, libellés, prix, comparatif) est dans
docs/OPSY_CATALOG.md et se pose d'un coup par
scripts/opsy-catalog.py, qui rejoue le même
catalogue d'un royaume à l'autre par l'API d'administration :

scripts/opsy-catalog.py --dry-run                    # le plan, sans rien appeler
export OPSY_ADMIN_TOKEN="<jeton d'un REALM_ADMIN>"
scripts/opsy-catalog.py --api http://auth.7app.localhost:11501
starter — Compte gratuit premium — Premium
Prix 0 20 EUR / an (30 USD, 25 CHF)
Recherches / jour 15 illimité
Combinaisons / recherche 80 illimité
Générateur de mots-clés oui oui
Historique oui oui
Paniers / sélections non oui
Export CSV non oui

Le front branché sur Opsy

  • OpsyCatalogService lit GET {issuer}/api/public/catalog/domain-seek et le
    garde 10 minutes : une offre créée ou repricée dans la console apparaît sans
    redéploiement. Opsy injoignable rend un catalogue vide, pas une erreur.
  • PricingModel dresse la feuille de tarifs depuis ce catalogue — une carte par
    offre publiée, dans l'ordre sortOrder, avec les prix de chaque devise que le
    royaume vend et le comparatif libellé par Opsy, dans la langue du visiteur.
    Tant que le royaume ne publie rien, la grille retombe sur les deux cartes que
    l'application portait : la page ne se vide jamais.
  • GET /api/license sert au front ce que le compte détient et ce que
    l'application vend ; le paywall ne propose que les offres payantes et renvoie
    l'achat au portail Opsy.
  • LicenseService arbitre les droits dans cet ordre : licence active → offre
    gratuite du catalogue → plafonds anonymes de l'application.

Corrections apportées au passage

  • Les recherches d'un compte étaient comptées sur son navigateur.
    /api/check et /api/search-log passaient toujours userId = null : le
    plafond de l'offre starter (15/jour) se remettait à zéro d'un navigateur à
    l'autre, et l'écran de quota d'un compte affichait toujours 0. Elles sont
    désormais rattachées au user_profile du compte connecté (anonyme :
    toujours le browserId). Couvert par SearchLogControllerTest.
  • Devise vendue vs devise détectée. Depuis que la liste des devises vient
    du catalogue, un visiteur dont la devise détectée n'est pas vendue par le
    royaume (GBP, si le royaume ne vend qu'EUR/USD/CHF) voyait des cartes sans
    aucun prix
    : toutes les étiquettes [data-currency] restaient masquées. La
    page retombe maintenant sur la première devise réellement chiffrée, et la
    modale de souscription sur le premier prix de l'offre.
  • scripts/opsy-catalog.py n'était pas tout à fait idempotent. Il reposait
    les prix à chaque exécution : Opsy archivait l'ancien (OPSY-TAR-003) et
    relançait la synchronisation Stripe. Un prix actif identique est maintenant
    laissé tel quel.
  • Doc. SUBSCRIPTION_PRICE_{EUR,USD,CHF,GBP} était encore annoncé dans le
    README alors que les prix viennent d'Opsy — la ligne renvoie désormais vers
    docs/OPSY_CATALOG.md.

Vérifications

  • mvn test : 19 tests, tous verts (dont 5 nouveaux sur les deux plafonds et
    leur rattachement au compte).
  • vite build : OK.
  • Les textes sont traduits dans les 5 langues (en, fr, es, de, it), pluriels en
    {count}.

Deux points restés ouverts

  1. Les offres n'ont pas été posées sur un royaume. L'Opsy local
    (domain-seek.auth.7app.localhost:11501) ne répondait pas pendant ce
    travail : le script a été validé en --dry-run seulement. Il reste à le
    lancer une fois avec un jeton REALM_ADMIN, sur dev puis sur les autres
    royaumes.
  2. Les montants sont à confirmer. La capture jointe à la demande n'était
    pas accessible depuis l'environnement de travail (redmine.gotan.dev ne
    résout pas ici). Les prix repris — 20 EUR / 30 USD / 25 CHF par an — et les
    plafonds du starter (15 recherches, 80 combinaisons) sont ceux de
    docs/OPSY_CATALOG.md. Merci de confirmer qu'ils correspondent bien à la
    capture ; ils se corrigent dans la console sans toucher au code.

Formats disponibles : PDF Atom