Aller au contenu
Candidater

Journal des modifications

Les évolutions de Cohez.io, de la plus récente à la plus ancienne.

52 versions correspondent à « paiement ».

Versions 1 à 10 sur 52

0.25.16.0 — 2026-10-06

Ajouts

  • Fiche de traitement /dpa (#357, 2026-10-06). Le DPA signé par les associations (article 2.3) renvoie à une fiche consultable en ligne. Elle existe désormais, en version 2026-10. Elle décrit les fonctionnalités et les données traitées par chacune, les fonctionnalités optionnelles (vérification santé, enregistrement audio, identification des adhérents connus) et les garanties de l'assistance par IA. Elle liste aussi les sous-traitants ultérieurs (Clever Cloud, OVHcloud, Brevo, Laravel Nightwatch, Mistral AI), les mesures de sécurité et l'historique des versions. Elle annonce la phase de test (beta) et signale la gouvernance comme en cours de développement. La page est en noindex, hors sitemap, avec un lien dans le pied de page.
  • Contrat et facturation des associations abonnées dans /confidentialite (#357, 2026-10-06). Cette nouvelle section décrit le traitement dont l'éditeur est responsable : données du représentant, de l'association, du contrat signé et des factures, bases légales (6.1.b, 6.1.c), durées de conservation (contrat + 5 ans, pièces comptables 10 ans). Les destinataires sont Subnoto (signature électronique), Qonto (banque, factures, comptabilité) et l'administration fiscale. La facturation se fait par virement, sans prestataire de paiement en ligne.
  • Journal des modifications public /changelog (#357, 2026-10-06). La vitrine publie ce fichier, dix versions par page, avec une recherche plein texte qui ignore casse et accents (les pages de résultats sont en noindex) : suggestions de recherche, bouton d'effacement, mots trouvés surlignés, pagination numérotée. Les intitulés de section sont traduits (Ajouts, Modifications, Corrections…). Lien « Journal des modifications » dans le pied de page ; page indexable et au sitemap, 404 sur les sous-domaines de tenant et d'administration. Dans le back-office des associations, la version en cours s'affiche à gauche du bouton « Aide » et ouvre le journal dans un nouvel onglet. Les quatre mentions de l'ancien nom du produit sont réécrites dans le fichier.

Modifications

  • Conditions générales reprises du contrat d'abonnement (#357, 2026-10-06). /conditions-generales suit désormais la numérotation du contrat signé par les Partenaires Fondateurs (articles 1 à 18, et 3 bis). La phase de test vient en premier, et le contrat signé prévaut en cas de différence. Trois clauses contredisaient le contrat et sont corrigées :

    • la reconduction suit le rappel de l'article L.215-1 du Code de la consommation, au lieu d'un préavis de 30 jours ;
    • les remboursements au prorata et le droit de rétractation de 14 jours sont rétablis ;
    • la responsabilité est plafonnée à 500 € (5 000 € pour les données), sans exclure les pannes de l'hébergeur.

    Seul le tarif Partenaire Fondateur y figure.

  • « Partenaire Fondateur » remplace « association pilote » (#357, 2026-10-06). Le site reprend le nom du contrat, sur l'accueil, /tarifs, /merci et les pages légales, ainsi que dans le mail de candidature. La période se nomme « phase de test ». Le bouton de la barre de navigation devient « Candidater ». L'objectif Matomo « Candidature pilote » et la clé vitrine.pilot_seats_total ne changent pas.

  • Polices auto-hébergées (#357, 2026-10-06). Fraunces, Geist et Geist Mono sont servies depuis les assets de l'application (@fontsource, sous-ensemble latin) au lieu de Bunny Fonts. Aucun service de polices tiers ne reste dans la CSP ni dans le cache PWA.

  • Bandeaux « document de travail » et « non opposable » retirés des pages légales (#357, 2026-10-06). Les pages restent en noindex jusqu'à leur relecture juridique.

Corrections

  • Mentions légales d'une entreprise individuelle (#357, 2026-10-06). « Adresse » remplace « Siège social », et le téléphone de l'éditeur est affiché (LCEN). Il se règle avec la variable VITRINE_LEGAL_PHONE, à définir sur Clever Cloud ; vide, la ligne n'apparaît pas.

0.25.11.0 — 2026-09-29

Ajouts

  • Aide en ligne : rubrique Paniers & paiements (#333, 2026-09-29). Six guides pour les trésoriers et dirigeants : comprendre un panier, versements, paiement manuel, paiement en ligne HelloAsso, relances, annuler une inscription. Les guides décrivent le comportement actuel, limites comprises (pas de remboursement par Cohez.io, versement corrigé par rejet, pas de paiement en plusieurs fois HelloAsso). L'accueil de l'aide ajoute l'étape « Choisir les moyens de paiement » avant l'ouverture, et les pages Inscriptions renvoient vers la nouvelle rubrique. Chemins figés par des tests ; captures dans une PR suivante.

0.25.0.1 — 2026-09-04

Ajouts

  • Chaque méthode de paiement affiche désormais des instructions de règlement, identiques sur les trois surfaces qui les rendent (#286, 2026-09-04). PaymentInstructionsResolver remplace BankTransferInstructionsResolver — supprimé sans résidu — et produit un PaymentInstructionsSet de 0 à 2 blocs (acompte / reste à régler) à chaînes déjà rendues ; l'e-mail de confirmation de panier, l'e-mail de relance et l'écran de succès public se contentent d'afficher : trois partials Blade (HTML, texte, markdown) et le composant Vue PaymentInstructions.vue. Auparavant, seul le virement produisait des instructions ; chèque, espèces, carte hors ligne, HelloAsso et moyen personnalisé n'en affichaient aucune.
  • La description d'un moyen de paiement personnalisé survit désormais au submit du formulaire d'inscription publique (#286, 2026-09-04). Elle est portée par le bloc d'instructions rendu sur les trois surfaces au lieu de disparaître une fois l'inscription passée.

Corrections

  • La carte bancaire hors ligne annonçait un règlement en ligne (#286, 2026-09-04). PaymentInstructionsResolver::actionText() dégroupe désormais PaymentMethod::CreditCard (« Règlement par carte bancaire sur place, auprès de l'association. ») de PaymentMethod::HelloAsso (paiement en ligne), auparavant confondues sous la même phrase.
  • L'ordre du chèque se replie sur le nom du tenant (#286, 2026-09-04). tenant_payment_methods ne porte pas encore de champ dédié au destinataire ; PaymentInstructionsResolver::checkPayee() rend tenant->name. Le paramètre $row, inutilisé pour l'instant, est conservé pour y brancher ce champ sans changer les appelants.
  • Les deux canaux de paiement d'une règle d'acompte sont désormais distingués (#286, 2026-09-04). CampaignDepositRule::settlement_method est le canal de l'acompte (« Moyen de règlement de l'acompte » côté admin), tandis que la méthode choisie par l'adhérent — qui déclenche la règle via applicable_payment_methods — reste celle du reste à régler. Quand les deux blocs sont rendus, chacun porte son étiquette de portée (« Pour l'acompte », puis « Pour le solde ») : les deux e-mails alignent les blocs sans séparateur visuel, et un second bloc nu s'y lit comme une seconde consigne d'acompte. L'étiquetage du bloc du reste est décidé après filtrage : resté seul, ce bloc couvre tout le montant réclamé et non un reliquat, donc il ne porte aucune étiquette — comme la ligne « Moyen de paiement : … » qu'affichait l'e-mail auparavant. Le docblock de CampaignDepositRule, qui décrivait settlement_method comme la méthode du solde, est corrigé.
  • L'e-mail de relance annonçait un total de panier que le bloc « Pour l'acompte » contredisait en silence (#286, 2026-09-08). Sous règle d'acompte, la relance affichait le solde entier du panier puis, juste en dessous, un bloc d'instructions étiqueté « Pour l'acompte » ne portant aucun chiffre : le lecteur rattachait le total au canal de l'acompte et pouvait virer l'intégralité sur le mauvais canal. CartPaymentReminderMail calcule désormais le montant réellement exigible — par ligne, l'acompte restant (deposit_amount_due - total_paid) tant qu'il est strictement positif et inférieur au solde, sinon le solde — et le mail ajoute « À régler maintenant (acompte) : … » suivi du reliquat, uniquement quand cette somme est strictement inférieure au total. Garde d'homogénéité : la ligne n'apparaît que si toutes les inscriptions relancées sont en contexte d'acompte. Les blocs d'instructions sont résolus sur la seule ligne représentative ; sur un panier mixte, une somme étiquetée « acompte » ne correspondrait à aucun des canaux affichés, donc le total reste seul et le comportement est inchangé. Correctif du montant réclamé côté relance, hors périmètre initial des instructions de paiement de cette PR.
  • La ligne représentative d'un panier pouvait être une ligne gratuite dont la méthode de paiement avait été effacée, faisant partir la confirmation sans instructions (#286, 2026-09-04). whereIn sur des UUID ne garantit aucun ordre : le panier est désormais trié par member_index, et la ligne représentative choisie par le nouveau helper CartRepresentative::pick() — la première ligne au montant dû strictement positif, ou la première ligne à défaut d'un panier entièrement gratuit.
  • Le mail de confirmation d'un panier réglé (0 €, ou soldé) ne porte plus la ligne « Moyen de paiement : — » (#286, 2026-09-04). Un panier Validated et soldé (hasOutstandingBalance() à false) fait court-circuiter PaymentInstructionsResolver::forCart() en PaymentInstructionsSet::empty() : aucun bloc d'instructions n'est rendu, volontairement — pas de paiement dû, pas d'instructions. Un panier Validated mais encore partiellement payé continue de produire le bloc de son reste à régler. À l'inverse, un panier encore dû dont aucune méthode ne se résout reçoit un bloc générique (« Moyen de paiement : — » et phrase de règlement générique), comme avant : le jeu vide signifie donc « plus rien à régler », jamais « méthode inconnue ». FreeCartSettlementTest > I-1 est aligné sur ce comportement.
  • Un moyen de paiement dépublié pouvait voir son RIB ressortir dans les mails et sur l'écran de succès public (#286, 2026-09-08). Les deux requêtes TenantPaymentMethod de PaymentInstructionsResolver::resolveMethodKey() ne filtraient ni is_enabled ni is_public ; elles passent désormais par le scope public(), le même que le reste de la surface publique. Le canal le plus exposé est settlement_method : il est choisi par l'admin dans CampaignDepositRule::eligiblePaymentMethods(), qui rend tous les enums sauf HelloAsso sans consulter la configuration du tenant — il ne passait donc par aucun filtre de publication ailleurs. Ligne absente ⇒ pas d'échec : le libellé de la méthode reste rendu, seuls les détails (IBAN, BIC…) disparaissent au profit de la phrase générique. Aucun orderBy n'est ajouté : l'index partiel uq_tenant_std_payment garantit au plus une ligne par (tenant, méthode standard).
  • Une panne de résolution retirait toute la section paiement des mails au lieu du seul bloc de détails (#286, 2026-09-08). Le catch (Throwable) de PaymentInstructionsResolver::forCart() rendait PaymentInstructionsSet::empty(), qui signifie « plus rien à régler » pour les trois surfaces — la confirmation et la relance annonçaient donc l'absence de paiement dû sur un panier encore dû. Avant ce résolveur, PublicCartConfirmationMail calculait sa ligne « Moyen de paiement » de son côté et une panne ne coûtait que le bloc RIB. Le catch renvoie désormais le bloc générique tant qu'un solde subsiste ; le jeu vide n'est conservé que si le panier est soldé — ou si la lecture du solde échoue elle aussi, seul cas où il reste l'état sûr. PublicCartConfirmationMail charge en outre payments explicitement : $settledNotice appelle hasOutstandingBalance(), et ne dépendre que du chargement en effet de bord du résolveur rendait cette lecture sensible à l'ordre des appels.

0.25.0.0 — 2026-09-04

Ajouts

  • Le registre des salariés existe, et un salarié peut participer à une réunion sans qu'on lui fabrique une fiche d'adhérent (#277, 2026-09-02). Nouvelle table employees et pivot employee_employee_types — un salarié cumule plusieurs types —, enums EmployeeType et EmployeeStatus, EmployeePolicy, pages Inertia Employees/Index|Show|Form. users.employee_id (nullable) relie un compte à sa fiche RH ; meeting_participants.person_id ajoute un quatrième mode de participation, mutuellement exclusif des trois autres. Un rôle coach et une permission view_own_courses ouvrent un calendrier en lecture seule, vers lequel la connexion redirige directement : il n'expose que les métadonnées des cours, aucune donnée personnelle d'adhérent.
  • Les comptes user_type = 'employee' sans fiche RH sont listés sur le registre, en mode dégradé (#280, 2026-09-02). EmployeeController::index() expose unlinkedStaff — les comptes du tenant dont employee_id est nul —, rendus avec un badge « Sans fiche RH » et un bouton « Créer la fiche ». Aucune donnée n'est écrite et aucun backfill n'est lancé : c'est un révélateur d'un écart existant, pas une migration. Le formulaire ne pré-remplit person_id que si un seul persons.email correspond (comparaison en minuscules, en une requête, sans N+1). La section disparaît dès qu'un filtre de statut est posé, et au-delà de la première page, où elle n'aurait plus de sens.
  • Kit de formation EPGV 44 et profils de règlement du jeu de démonstration (#273, 2026-08-27). Docs/Formations/EPGV-44/ reçoit un déroulé animateur, des fiches pratiques et un support projeté. Côté données, EpgvSeeder répartit les inscriptions sur des profils de règlement contrastés — 7 soldées, 2 partielles, 1 chèque annoncé mais non encaissé, 2 sans aucun paiement — pour que le tableau de bord de démonstration affiche 3 impayés plutôt qu'un jeu uniformément soldé, qui ne montrait aucun des écrans de relance. createPaymentReceived() et createPaymentPending() acceptent désormais des paramètres optionnels.
  • Comptes et réunion de formation du tenant demo-epgv-44 (#275, 2026-08-27). Six comptes admin (mot de passe password), un helper createEmployeeUser(), et la réunion « Présentation et formation Cohézio » avec ses six participants : la démonstration part d'un tenant peuplé au lieu d'écrans vides.

Corrections

  • Les compteurs du tableau de bord ignoraient le statut des inscriptions (#278, 2026-09-01). seasonRegistrations, unpaidCount et unpaidMembers passent par deux helpers privés qui appliquent whereNotIn('status', RegistrationStatus::terminalStatuses()). Mesuré sur une copie de la production : 527 inscriptions de saison annoncées pour 467 réelles, et 69 impayés annoncés pour 13. Un dirigeant relançait donc cinq fois trop de monde. collectedAmount n'est volontairement pas filtré : un paiement received reste encaissé après l'annulation de l'inscription qui l'a motivé — ne pas « compléter » ce correctif en l'y ajoutant.
  • Une inscription annulée interdisait toute réinscription du même membre sur la même campagne (#279, 2026-09-03). La contrainte UNIQUE totale (member_id, campaign_id) est remplacée par un index unique partiel, where status not in ('cancelled', 'abandoned'). Mesuré sur lezards-animes : 60 couples (membre, campagne) ne portaient plus que des inscriptions annulées — 60 personnes bloquées, avec un SQLSTATE 23505 pour seule explication. RegistrationDuplicateChecker est aligné sur terminalStatuses() ; c'est un no-op fonctionnel aujourd'hui, la méthode ne rendant que cancelled — l'index partiel EST le correctif. abandoned est nommé d'avance à dessein : status est un varchar(255) et non un enum PostgreSQL, l'ajouter à terminalStatuses() suffira le jour venu.
  • Findings de la revue de code du registre des salariés (#281, 2026-09-02). Treize sur quatorze corrigés. Le tableau de bord vide désormais unpaidMembers, recentActivity et closingSoonCampaigns côté serveur pour les rôles sans view_members : un coach recevait la charge utile complète et on ne comptait que sur le front pour ne pas l'afficher. mapPerson() est réduit à id / prénom / nom / e-mail, avec un test qui fige la forme exacte de la charge utile (sans etc()), pour qu'un champ rajouté fasse rougir la suite. Sont également corrigés : la double invitation sur une même fiche salarié, l'unicité d'un participant par réunion, l'eager-load de participants.person dans GenerateMeetingReportJob, le plafonnement de unlinkedStaff(), la validation d'employee_id en query-string comme UUID du tenant, et la seconde sortie de connexion de Fortify — celle du 2FA — qui ne redirigeait pas le coach.
  • La liste des adhérents annonçait « En attente » à des adhérents dont l'adhésion court (#283, 2026-09-04). Le badge et la pilule de filtre portant l'axe administratif (members.status) sont retirés de members/Index.vue et de MemberFilters.vue ; il ne reste que l'état dérivé des périodes de memberships. Motif : aucun écran n'écrit cette colonne — ni la création, ni le formulaire d'édition, et les boutons Désactiver / Réactiver touchent is_active —, si bien qu'elle restait figée sur sa valeur de création. Le trou est dans le front, pas dans le domaine : Member::changeStatus(), UpdateMemberUseCase et la route members.update-status savent l'écrire dès que la charge utile porte le champ. Le paramètre status reste donc honoré côté serveur et applyFilters le reconduit d'une requête à la suivante, sans quoi un signet le portant n'aurait survécu qu'à un seul chargement. PersonMemberTab.vue affiche encore le badge hérité : hors périmètre, connu. MembersLegacyStatusAxisHiddenTest verrouille le retrait sur le source — le réexposer suppose d'abord qu'un écran pose la valeur.

Notes techniques

  • Cette version est d'abord un rattrapage de documentation. Les sept premières PR ci-dessus sont parties en production entre le 27 août et le 3 septembre 2026 sans commit de release ; chacune est datée de son merge. Seule #283 est livrée par cette version : elle n'était pas déployée quand ce fichier a été écrit, et c'est pour ne pas reproduire le retard de documentation qu'il vient de combler qu'elle y figure. Les sept premières sont regroupées sous un seul numéro plutôt que réparties sur des versions intermédiaires que VERSION n'a jamais portées et qu'aucun déploiement n'a servies. Le segment mineur bouge parce que le registre des salariés ajoute des tables et un module.
  • Course::coachEmployee() a été renommée findActiveCoachEmployee(), et ce n'est pas cosmétique. Une méthode au nom de relation qui rend un Employee au lieu d'une Relation fait appeler getResults() par Eloquent dès qu'un eager-load ou un accès en propriété la croise : erreur fatale, pas avertissement.
  • Le down() de la migration de l'index partiel lève une RuntimeException. Restaurer une contrainte UNIQUE totale échouerait dès qu'un couple (membre, campagne) porte une inscription annulée et une active — soit exactement ce que cette version rend possible. La migration est à considérer comme irréversible en production.
  • recentActivity() n'est toujours pas filtré par statut : les inscriptions annulées continuent d'y figurer. Connu, hors périmètre de #278, à traiter avant que la saison 2026-2027 passe Active.
  • Employees/Form.vue expose encore person_id en UUID brut. Connu, non corrigé : le champ attend un sélecteur de personne.
  • Les PR #225, #237 et #266 à #269 ne sont listées nulle part ici : toutes ont été mergées entre le 21 et le 24 août 2026, donc hors de la fenêtre couverte par cette version. #225 et #237 sont par ailleurs des montées de dépendances Dependabot, que ce fichier ne détaille jamais — il documente l'activation de Dependabot et les gates de CI, pas les bumps individuels ; #266 à #269 sont des changements internes sans effet sur l'application (fichiers temporaires Livewire ignorés, notes de roadmap, retrait des références JetBrains, discipline de workflow).

0.22.1.0 — 2026-07-31

Ajouts

  • Codes de réduction par campagne. Un adhérent saisit un code dans le formulaire public (ou un agent en back-office) et obtient une remise en pourcentage (1-100 %) sur son panier. Chaque code est configuré par campagne : ciblage optionnel par rubrique, quota d'utilisations, date d'expiration (inclusive). Techniquement, un code est une PricingRule dont la colonne code est non-null — même table, même moteur de calcul que les remises automatiques ; Campaign::pricingRules() filtre désormais whereNull('code') pour que les dix appelants existants ne voient jamais un code. CRUD back-office sur la carte campagne (créer, modifier, archiver, supprimer), saisie côté funnel avec badge de confirmation et remise reflétée dans le total affiché. Doc : Docs/Features/Promo-Codes.md.
  • Consommation par panier, pas par compteur. La table promo_code_redemptions enregistre une ligne par panier ; deux index uniques partiels portent les règles métier (un seul code par panier, une seule consommation active par panier). Le quota est gardé deux fois — pré-filtre dans CartPricingRules::resolveUsableCode() et garde post-verrou dans RedeemPromoCodeService — et la ligne est libérée à l'abandon du panier. La remise survit aux mutations de cours : MutateRegistrationCoursesService et CartMutationService recalculent avec CartPricingRules::forCart(), pas avec la relation filtrée.
  • Validation immédiate d'un panier ramené à 0 €. Un panier intégralement couvert par un code ne passe plus par HelloAsso : il est validé sur-le-champ, le moyen de paiement est réinitialisé et le mail de confirmation part sans ligne de reste à payer. Le code de réduction appliqué est tracé dans le snapshot de facture et affiché sur le détail panier.

Sécurité

  • Oracle d'existence de codes fermé. L'autorisation vivait dans le contrôleur, alors que Laravel résout rules() et withValidator() avant lui : n'importe quel utilisateur authentifié du tenant pouvait distinguer « code inexistant » de « code existant mais interdit » via les messages de validation. L'autorisation est remontée dans StorePromoCodeRequest::authorize(). Côté public, la réponse d'application d'un code est uniformisée et un seau anti-énumération partagé limite les tentatives.
  • Fuite de quota fermée. Le décompte pouvait être contourné en concurrence ; la consommation se fait maintenant sous verrou, dans une transaction gardée, et le prédicat est testé des deux côtés (code utilisable / code épuisé).

Modifications

  • La suite de tests ne peut plus atteindre PostgreSQL. La connexion pgsql est dé-configurée pour les tests et tests/Unit/TestDatabaseIsolationTest.php monte la garde — il vérifie aussi qu'aucune trace d'un flag d'opt-in ne revient dans le dépôt. Motif : un tel flag avait vidé la base de développement. Les agrégats sensibles à la divergence SQLite↔PostgreSQL se vérifient désormais à la main sur la base de dev, jamais via la suite.

0.19.1.0 — 2026-07-06

Modifications

  • Découpage du contrôleur des inscriptions par ressource HTTP : le RegistrationController monolithique (2 170 lignes, 27 actions) est scindé en 6 contrôleurs dédiés — CRUD inscriptions, transitions de statut (valider/refuser/annuler), gestion de cours (ajout/retrait/échange + aperçus), panier (détail, paiement, facture PDF, membres, notes), relances et export CSV. Aucun changement de comportement : URLs, noms de routes et autorisations strictement identiques (déplacements verbatim vérifiés mécaniquement).
  • Les aides partagées (filtres de liste, représentant de panier, options de campagne, noms de cours) deviennent des traits réutilisables Concerns/, et la liste des raisons comptant comme « relance manuelle » est centralisée sur l'énum RelanceReason::MANUAL_REASONS (valeurs inchangées).

Ajouts

  • Test unitaire de l'énum RelanceReason : libellés et contenu exact de MANUAL_REASONS désormais verrouillés.

0.19.0.1 — 2026-07-03

Corrections

  • Mail « Paiement reçu » reflète le solde du panier, pas la ventilation par inscription : PaymentReceivedMail interrogeait le solde de la seule inscription réglée, qui peut diverger du solde réel du panier après une re-ventilation des paiements. Le mail agrège désormais toutes les inscriptions non terminales du panier et affiche le solde VersementService::cartOutstandingCents — élimine les mails annonçant un reste à payer alors que le panier est soldé.

0.19.0.0 — 2026-07-03

Ajouts

  • Lien de paiement en ligne pour panier offline impayé : depuis le détail panier, un bouton du panneau de relance génère un lien de paiement HelloAsso pour un panier initialement réglé hors ligne (chèque, virement, espèces) qui reste impayé — sans obliger l'adhérent à repasser par tout le formulaire. Guardé par token public valide, solde panier > 0 relu sous verrou, et HelloAsso configuré pour le tenant (le bouton est masqué sinon).
  • Verrou de checkout HelloAsso anti double-clic/double-onglet : Cache::lock par panier (30s, attente 5s) sérialise les checkouts concurrents sur le même jeu d'inscriptions ; le solde est relu sous verrou juste avant chaque appel HelloAsso, avec dégradation gracieuse (HelloAssoAlreadySettledException) si un règlement concurrent a déjà tout soldé. Les appels HTTP HelloAsso sont bornés à 10s pour ne jamais dépasser la durée du verrou.

Corrections

  • Le montant réclamé par la relance et le checkout HelloAsso se base désormais sur le solde du panier (VersementService::cartOutstandingCents), plus sur la ventilation par inscription — élimine les relances erronées après une re-ventilation des paiements à l'annulation.
  • Élimination de plusieurs N+1 (registration.payments, registration.balance) dans HelloAssoRelanceService et le flux de retry HelloAsso.
  • Exclusion des paiements HelloAsso fantômes de la relance panier : payableCartPayments() (et canSendManualRelance(), createOnlinePaymentLink()) retournent vide dès que le panier n'a plus de solde dû au niveau versement (VersementService::cartOutstandingCents), même si un Payment Pending/Rejected résiduel survit sur une inscription réglée hors ligne entre-temps ; garde sur destinataire absent ; resynchronisation de Payment.amount au moment du checkout.

0.18.2.0 — 2026-06-30

Ajouts

  • Visibilité publique des moyens de paiement (is_public) : nouveau flag indépendant de is_enabled sur TenantPaymentMethod. Permet de configurer un moyen de paiement disponible uniquement en back-office (ex. CB physique, virement interne) sans l'exposer sur le formulaire public d'inscription.
  • Toggle « Proposé au public » dans les paramètres moyens de paiement : second interrupteur par méthode (désactivé si la méthode est désactivée). Désactiver une méthode force automatiquement is_public = false côté serveur.
  • Méthodes custom dans la saisie de versement back-office : les méthodes custom activées (payment_method IS NULL) sont désormais proposées dans le dialog « Enregistrer un versement », avec validation du custom_payment_method_id.
  • Scope scopePublic() sur TenantPaymentMethod : filtre is_enabled = true AND is_public = true, utilisé par le formulaire public d'inscription.

Modifications

  • Le formulaire public d'inscription filtre désormais les moyens de paiement via scopePublic() (au lieu de scopeEnabled()) — seules les méthodes activées et marquées publiques sont proposées.

0.17.0.0 — 2026-06-29

Ajouts

  • Panneau paiement inline dans le détail panier : récap des versements (montant, statut, méthode, dates prévue/reçue, référence, notes) et formulaire d'ajout directement sur la page détail panier — plus besoin d'ouvrir une modale ou de naviguer vers le plan de paiement.

Modifications

  • Enregistrer un versement redirige désormais vers la page d'origine (détail panier ou plan de paiement) au lieu de forcer la navigation vers le plan de paiement.
  • Le détail panier expose les données complètes des versements (dates, référence, notes) au lieu d'un simple statut.

Corrections

  • Sécurité : la redirection post-enregistrement de versement est désormais restreinte aux routes autorisées du panier (protège contre une éventuelle manipulation du header Referer).