Formulaire public : paiement de la cotisation via l'API SumUp (remplace Yapla)
ci-cd / build-backend (push) Failing after 4m12s
ci-cd / build-frontend (push) Skipped
ci-cd / deploy (push) Skipped

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:
AdamThomas
2026-09-29 19:56:04 +02:00
co-authored by Claude Sonnet 5
parent 0be5daa89c
commit 0bd38a6465
25 changed files with 1457 additions and 125 deletions
+24 -13
View File
@@ -92,19 +92,19 @@ async function checkPublicForm() {
const beforeCount = (await api.volunteers.list(e)).length;
await api.forms.setFields(e, {
fields: ['EMAIL', 'POSITION_WISHES', 'ARTIST_WISHES', 'COMMENT'],
fields: ['EMAIL', 'POSITION_WISHES', 'ARTIST_WISHES', 'COMMENT', 'WILLING_TO_PAY_FEE'],
});
const config = await api.forms.getPublicConfig(e);
if (!config.is_open) throw new Error('formulaire public : devrait être ouvert par défaut');
if (config.positions.length !== positions.length) {
throw new Error('formulaire public : postes manquants dans la config publique');
}
if (config.has_logo !== false || config.description !== null || config.yapla_url !== null) {
throw new Error('formulaire public : logo/description/lien Yapla devraient être vides par défaut');
if (config.has_logo !== false || config.description !== null || config.sumup_enabled !== false) {
throw new Error('formulaire public : logo/description/SumUp devraient être vides par défaut');
}
// Le recadrage carré (canvas) et l'upload du logo (FileReader) sont des
// API navigateur — non testables sous Node (voir scripts/smoke.tsx tel
// qu'exécuté ici) ; seul le texte d'introduction et le lien Yapla sont
// qu'exécuté ici) ; seul le texte d'introduction et la config SumUp sont
// vérifiables ici.
await api.forms.setOpen(e, { description: 'Bienvenue au festival de test !' });
const configWithDescription = await api.forms.getPublicConfig(e);
@@ -112,16 +112,24 @@ async function checkPublicForm() {
throw new Error("formulaire public : le texte d'introduction ne revient pas correctement");
}
let invalidYaplaUrlRejectedWith = 'non';
let invalidSumupAmountRejectedWith = 'non';
try {
await api.forms.setOpen(e, { yapla_url: 'yapla.com/exemple' });
await api.forms.paymentOptions.create(e, { label: 'Test invalide', amount: 0 });
} catch (err) {
invalidYaplaUrlRejectedWith = err instanceof ApiError ? err.code : String(err);
invalidSumupAmountRejectedWith = err instanceof ApiError ? err.code : String(err);
}
await api.forms.setOpen(e, { yapla_url: 'https://www.yapla.com/fr/exemple' });
const configWithYapla = await api.forms.getPublicConfig(e);
if (configWithYapla.yapla_url !== 'https://www.yapla.com/fr/exemple') {
throw new Error('formulaire public : le lien Yapla ne revient pas correctement');
await api.forms.setOpen(e, {
sumup_merchant_code: 'MXXXXXXX',
sumup_api_key: 'sup_sk_test',
});
const classique = await api.forms.paymentOptions.create(e, {
label: 'Cotisation classique',
amount: 15,
});
await api.forms.paymentOptions.create(e, { label: 'Mécénat', amount: 50 });
const configWithSumup = await api.forms.getPublicConfig(e);
if (!configWithSumup.sumup_enabled || configWithSumup.payment_options.length !== 2) {
throw new Error('formulaire public : la configuration SumUp ne revient pas correctement');
}
const sameDay = artists.filter((a) => a.performance_date === artists[0]?.performance_date);
@@ -161,6 +169,7 @@ async function checkPublicForm() {
comment: 'Créé par le smoke test',
position_wish_ids: positions.slice(0, 2).map((p) => p.id),
artist_wish_ids: artists[0] ? [artists[0].id] : [],
payment_option_id: classique.id,
});
const created = await api.volunteers.get(e, result.volunteer_id);
const afterCount = (await api.volunteers.list(e)).length;
@@ -170,12 +179,14 @@ async function checkPublicForm() {
{
formOpenByDefault: config.is_open,
descriptionRoundTrips: configWithDescription.description,
invalidYaplaUrlRejectedWith,
yaplaUrlRoundTrips: configWithYapla.yapla_url,
invalidSumupAmountRejectedWith,
sumupEnabledRoundTrips: configWithSumup.sumup_enabled,
tooManyPositionsRejectedWith,
tooManyArtistsRejectedWith,
createdVolunteerName: `${created.first_name} ${created.last_name}`,
createdVolunteerAppearsInList: afterCount === beforeCount + 1,
sumupCheckoutUrlReturned: result.sumup_checkout_url !== null,
createdVolunteerFeeLabel: created.fee_payment_label,
},
null,
2,