Formulaire public : paiement de la cotisation via l'API SumUp (remplace Yapla)
Remplace le simple lien Yapla statique par une vraie intégration API SumUp : checkout hébergé créé à la soumission du formulaire, statut de paiement (PENDING/PAID/FAILED/EXPIRED) tenu à jour par un webhook SumUp et visible sur la fiche du bénévole. La cotisation n'est plus un montant unique : la coordination configure autant de types de paiement qu'elle veut (libellé + montant, ex. « Cotisation classique », « Mécénat »), et le bénévole en choisit un à l'inscription — le libellé et le montant choisis sont figés sur le bénévole, indépendamment d'une modification ultérieure de la config. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0be5daa89c
commit
0bd38a6465
@@ -80,9 +80,11 @@ les tables sauf les tables d'association.
|
||||
forcément encore en base au moment de l'inscription, donc pas de lien
|
||||
direct vers un `VolunteerCompanionWish` ; la coordination la résout à la
|
||||
main depuis la fiche une fois les deux bénévoles inscrits),
|
||||
`self_declared_available`/`willing_to_pay_fee` (cases à cocher **purement
|
||||
informatives**, sans lien avec `Availability` ni aucun workflow de
|
||||
paiement).
|
||||
`self_declared_available` (case à cocher **purement informative**, sans
|
||||
lien avec `Availability`) et `willing_to_pay_fee` (« je participe à la
|
||||
cotisation » — déclenche, lui, un vrai paiement : voir `fee_payment_label`/
|
||||
`fee_amount`/`fee_checkout_id`/`fee_payment_status`/`fee_paid_at` et le
|
||||
paiement SumUp plus bas).
|
||||
- **`Artist`** : programmation de l'édition (`name`, `performance_date`,
|
||||
`start_time`, `end_time` — `end_time <= start_time` ⇒ le passage finit le
|
||||
lendemain). Unique (`edition_id`, `name`, `performance_date`). La
|
||||
@@ -194,7 +196,22 @@ les tables sauf les tables d'association.
|
||||
affectation n'apparaît pas (rien à imprimer).
|
||||
- **`FormTemplate`** : configuration du formulaire public d'inscription
|
||||
d'une édition (une ligne, créée à la demande). `is_open` coupe les
|
||||
nouvelles soumissions sans effacer la configuration.
|
||||
nouvelles soumissions sans effacer la configuration. `sumup_merchant_code`/
|
||||
`sumup_api_key` sont les identifiants du compte marchand SumUp pour le
|
||||
paiement de la cotisation (`app/sumup/`) — `sumup_api_key` est un secret,
|
||||
jamais renvoyé par l'API (`FormTemplateRead.sumup_api_key_set: bool` à la
|
||||
place ; chaîne vide en écriture ⇒ l'efface).
|
||||
- **`FormPaymentOption`** : un type de paiement de la cotisation (ex.
|
||||
« Cotisation classique », « Mécénat ») — `label` + `amount` (montant
|
||||
fixe), `order`. Liste libre par édition, autant que la coordination veut
|
||||
(comme les créneaux types `DayPart`), gérée depuis l'écran « Inscriptions »
|
||||
(bloc « Types de paiement »). CRUD `GET/POST/PATCH/DELETE
|
||||
/editions/{id}/form/payment-options` (COORDINATION). Unique
|
||||
(`edition_id`, `label`) — 409 `form.duplicate_payment_option_label` sinon ;
|
||||
montant `> 0` requis — 422 `form.invalid_sumup_amount` sinon. Le bénévole
|
||||
en choisit au plus un à l'inscription (voir `Paiement de la cotisation`
|
||||
plus bas) ; supprimer une option n'affecte jamais les bénévoles qui
|
||||
l'avaient déjà choisie (copie figée sur `Volunteer`).
|
||||
- **`FormField`** : un champ activé sur le formulaire (`field_type` ∈
|
||||
`EMAIL` / `PHONE` / `POSITION_WISHES` / `ARTIST_WISHES` / `COMPANION_WISH`
|
||||
/ `AVAILABLE` / `WILLING_TO_PAY_FEE` / `PHOTO` / `COMMENT`, au plus un par
|
||||
@@ -205,14 +222,45 @@ les tables sauf les tables d'association.
|
||||
limite) : au plus **deux** vœux de poste, et au plus **un** artiste
|
||||
souhaité par jour de festival (`app/api/forms.py:_check_one_artist_wish_per_day`).
|
||||
`GET/PUT/PATCH /editions/{id}/form[...]` (COORDINATION) configurent ;
|
||||
`GET /editions/{id}/form/public` et `POST .../form/public/submit` sont les
|
||||
**deux seuls endpoints de toute l'API sans authentification** — protégés
|
||||
par hCaptcha (`app/forms/captcha.py`, `HCAPTCHA_SECRET_KEY` ; vérification
|
||||
`GET /editions/{id}/form/public`, `POST .../form/public/submit` et
|
||||
`POST .../form/public/sumup-webhook` sont les **trois seuls endpoints de
|
||||
toute l'API sans authentification** — les deux premiers protégés par
|
||||
hCaptcha (`app/forms/captcha.py`, `HCAPTCHA_SECRET_KEY` ; vérification
|
||||
désactivée si absente, comme `EMAIL_ENABLED=false`) et un filet de
|
||||
sécurité global par édition (`app/auth/rate_limit.py:form_submission_limiter`,
|
||||
hCaptcha reste la défense principale). Le bénévole créé par une soumission
|
||||
apparaît immédiatement dans la liste de la coordination, comme n'importe
|
||||
quel autre bénévole.
|
||||
hCaptcha reste la défense principale) ; le webhook est appelé par SumUp
|
||||
lui-même, donc sans hCaptcha possible — voir plus bas. Le bénévole créé
|
||||
par une soumission apparaît immédiatement dans la liste de la
|
||||
coordination, comme n'importe quel autre bénévole.
|
||||
- **Paiement de la cotisation (SumUp)** : si le bénévole choisit un type de
|
||||
paiement (`FormPaymentOption`, ci-dessus — le formulaire public
|
||||
n'affiche ce choix, à la place de la case `WILLING_TO_PAY_FEE` toute
|
||||
seule, que si `sumup_enabled` sur `PublicFormConfig` est vrai, c.-à-d.
|
||||
identifiants SumUp renseignés **et** au moins un type de paiement
|
||||
configuré ; sinon la case reste une simple déclaration, comme avant SumUp)
|
||||
et que l'édition a configuré ses identifiants, la soumission crée un
|
||||
checkout hébergé (`app/sumup/client.py:create_checkout`, API SumUp
|
||||
`POST /v0.1/checkouts`, `hosted_checkout.enabled=true`, montant et
|
||||
libellé de l'option choisie) et renvoie son URL de paiement dans
|
||||
`FormSubmissionResult.sumup_checkout_url` (le bénévole y est redirigé
|
||||
juste après son inscription — pas avant, contrairement à l'ancien lien
|
||||
Yapla statique). Le libellé et le montant de l'option choisie sont
|
||||
recopiés sur `Volunteer.fee_payment_label`/`fee_amount` **au moment du
|
||||
choix** — une copie figée, jamais recalculée si l'option est éditée ou
|
||||
supprimée ensuite (`FormPaymentOption` reste une pure configuration,
|
||||
jamais une référence vivante depuis l'historique des bénévoles).
|
||||
`Volunteer.fee_checkout_id` (identifiant SumUp, jamais exposé) et
|
||||
`fee_payment_status` (`PENDING`/`PAID`/`FAILED`/`EXPIRED`, `NULL` tant
|
||||
qu'aucun checkout n'a été créé) suivent l'état ; `fee_paid_at` est posé à
|
||||
la première confirmation `PAID`. Best effort, comme l'envoi d'emails : un
|
||||
échec de l'appel API SumUp (réseau, identifiants invalides…) ne bloque
|
||||
jamais l'inscription. Le statut est tenu à jour par
|
||||
`POST .../form/public/sumup-webhook` (`event_type:
|
||||
CHECKOUT_STATUS_CHANGED`, `id`) : le payload n'étant qu'un identifiant,
|
||||
l'app relit toujours l'état réel via l'API (`get_checkout`) avant de
|
||||
l'appliquer, jamais fait confiance au seul contenu du webhook. Visible par
|
||||
la coordination sur la fiche du bénévole (`ConfigVolunteers.tsx`, libellé
|
||||
+ montant + statut), en lecture seule (pas de bascule manuelle).
|
||||
|
||||
Conventions techniques :
|
||||
|
||||
@@ -537,6 +585,7 @@ l'étape 21.)*
|
||||
│ │ ├── badges/ # génération PDF des planches (pdf.py, ReportLab)
|
||||
│ │ ├── sheets/ # génération PDF des feuilles de poste (pdf.py)
|
||||
│ │ ├── forms/ # hCaptcha (captcha.py) — voir api/forms.py
|
||||
│ │ ├── sumup/ # client API SumUp (client.py) — paiement de la cotisation
|
||||
│ │ ├── bootstrap.py # instance vierge : édition + 1er compte
|
||||
│ │ ├── seed.py # jeu de démonstration (python -m app.seed)
|
||||
│ │ ├── i18n/
|
||||
@@ -651,6 +700,7 @@ Aucune dépendance locale requise hors Docker.
|
||||
| 22 | Feuilles de poste (PDF, complément des badges) : une page par poste et par jour avec le référent, les créneaux, les bénévoles affectés et leur téléphone — bouton depuis la grille des besoins (COORDINATION) ; photo du bénévole affichée sur son écran et sur l'écran référent (en face du nom de chaque membre de l'équipe) | à valider |
|
||||
| 23 | Créneaux : plus de génération automatique à la création d'un poste — création manuelle à horaire libre (`ConfigGrid.tsx`), génération depuis les créneaux types conservée en option (« Générer les créneaux ») ; grille des besoins redessinée en vrai axe des temps (une colonne = 30 min, bornée à la plage utilisée par jour, cases fusionnées pour chaque créneau — `buildTimeGrid`/`placeInTimeGrid`) | à valider |
|
||||
| 24 | Corrections de stabilité de la grille des besoins (suite à l'étape 23) : positionnement explicite (`gridColumn`/`gridRow`) de chaque cellule (un poste aux créneaux non alignés sur 30 min ne décale plus les postes suivants) ; hiérarchie de `z-index` sans égalité entre les en-têtes figés et isolation de l'infobulle de survol dans son propre contexte d'empilement (ne recouvrait plus la ligne « Affectés / idéal » pendant un défilement) | à valider |
|
||||
| 25 | Paiement de la cotisation via l'API SumUp (remplace le lien statique Yapla) : plusieurs types de paiement configurables par édition (`FormPaymentOption` — libellé + montant, ex. « Cotisation classique », « Mécénat »), le bénévole en choisit un à l'inscription, checkout hébergé créé à la soumission si l'édition a configuré ses identifiants SumUp, lien de paiement affiché juste après l'inscription, statut (`PENDING`/`PAID`/`FAILED`/`EXPIRED`) tenu à jour par un webhook SumUp et visible (libellé + montant + statut) sur la fiche du bénévole | à valider |
|
||||
|
||||
**Reste hors périmètre** (modèle prêt à les accueillir) : SMS, constructeur
|
||||
de formulaire, pointage sur site, IA. Frontend : squelette + 6 écrans +
|
||||
|
||||
Reference in New Issue
Block a user