Type de paiement à montant libre (pensé pour le mécénat)
ci-cd / build-backend (push) Successful in 33s
ci-cd / build-frontend (push) Successful in 18s
ci-cd / deploy (push) Successful in 12s

FormPaymentOption.free_amount (migration 0021) : case « Montant libre »
sur l'écran Inscriptions ; le montant configuré devient le minimum et le
bénévole saisit le sien sur le formulaire public (payment_amount, virgule
ou point, 2 décimales max, plafond 10 000 € contre une faute de frappe —
422 form.invalid_free_amount sinon). Validé côté client avant l'envoi,
le serveur restant seul juge. Le montant choisi est celui du checkout
SumUp, recopié sur la fiche, l'email de confirmation et le cerfa.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-10-03 13:49:27 +02:00
co-authored by Claude Opus 5.5
parent ccd769d04e
commit c49ce520e3
13 changed files with 280 additions and 7 deletions
+84
View File
@@ -1089,3 +1089,87 @@ def test_pending_status_polling_is_rate_limited_per_token(
pending_status_limiter.hit(token)
assert raw_client.get(_form_url(edition, f"/public/pending/{token}")).status_code == 429
def _configure_free_amount(client, edition, minimum=10):
"""Type de paiement « montant libre » : `amount` est le minimum."""
client.put(_form_url(edition, "/fields"), json={"fields": ["WILLING_TO_PAY_FEE"]})
client.patch(
_form_url(edition), json={"sumup_merchant_code": _MERCHANT, "sumup_api_key": _API_KEY}
)
resp = client.post(
_form_url(edition, "/payment-options"),
json={"label": "Don libre", "amount": minimum, "free_amount": True},
)
assert resp.status_code == 201
assert resp.json()["free_amount"] is True
return resp.json()["id"]
def test_free_amount_option_is_exposed_on_the_public_form(raw_client, client, edition):
_configure_free_amount(client, edition)
config = raw_client.get(_public(edition, "/public")).json()
assert config["payment_options"][0]["free_amount"] is True
assert config["payment_options"][0]["amount"] == 10
def test_free_amount_uses_the_amount_chosen_by_the_volunteer(
raw_client, client, edition, monkeypatch
):
option_id = _configure_free_amount(client, edition)
checkouts = []
def fake_checkout(**kw):
checkouts.append(kw)
return SumUpCheckout(id="co_free", status="PENDING", hosted_checkout_url="https://x")
monkeypatch.setattr("app.api.forms.create_checkout", fake_checkout)
resp = _submit(
raw_client, edition,
first_name="Alex", last_name="Martin", hcaptcha_token="x",
payment_option_id=option_id, payment_amount="75,50",
)
assert resp.status_code == 201
assert checkouts[0]["amount"] == 75.5
token = resp.json()["pending_token"]
_pay(raw_client, edition, monkeypatch, checkout_id="co_free")
volunteer_id = _pending_status(raw_client, edition, token)["volunteer_id"]
fiche = client.get(f"/editions/{edition.id}/volunteers/{volunteer_id}").json()
assert fiche["fee_amount"] == 75.5
assert fiche["fee_payment_label"] == "Don libre"
@pytest.mark.parametrize("amount", [None, "", "abc", "9.99", "0", "-5", "10000.01", "12.345"])
def test_free_amount_rejects_missing_or_out_of_range_amounts(
raw_client, client, edition, monkeypatch, amount
):
option_id = _configure_free_amount(client, edition)
monkeypatch.setattr("app.api.forms.create_checkout", lambda **kw: None)
fields = dict(
first_name="Alex", last_name="Martin", hcaptcha_token="x", payment_option_id=option_id
)
if amount is not None:
fields["payment_amount"] = amount
resp = _submit(raw_client, edition, **fields)
assert resp.status_code == 422
detail = resp.json()["detail"]
assert detail["code"] == "form.invalid_free_amount"
assert detail["params"] == {"min": 10.0, "max": 10000.0}
def test_fixed_amount_option_ignores_any_amount_sent(raw_client, client, edition, monkeypatch):
option_id = _configure_sumup(client, edition, amount=12.5)
checkouts = []
def fake_checkout(**kw):
checkouts.append(kw)
return SumUpCheckout(id="co_fixed", status="PENDING", hosted_checkout_url="https://x")
monkeypatch.setattr("app.api.forms.create_checkout", fake_checkout)
resp = _submit(
raw_client, edition,
first_name="Alex", last_name="Martin", hcaptcha_token="x",
payment_option_id=option_id, payment_amount="1",
)
assert resp.status_code == 201
assert checkouts[0]["amount"] == 12.5