fix(paiement): rendre colonnes legacy nullables + refactor Paiement/PaiementObjet

Migrations :
- V25 : numero_transaction nullable dans paiements (legacy V1 NOT NULL bloquant INSERT)
- V26 : autres colonnes legacy NOT NULL V1 (type_paiement, statut_paiement, etc.)
  rendues nullables pour alignement avec l'entité Paiement

Refactor Paiement/PaiementObjet : mise à jour entités, repository, resource, service
pour cohérence avec le nouveau module Versement. Tests associés supprimés/ajustés.
This commit is contained in:
dahoud
2026-04-15 20:23:30 +00:00
parent 5d028a10bf
commit 217021933e
12 changed files with 582 additions and 2317 deletions

View File

@@ -1,6 +1,10 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.api.dto.paiement.request.CreatePaiementRequest;
import dev.lions.unionflow.server.api.dto.paiement.request.DeclarerPaiementManuelRequest;
import dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest;
import dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementResponse;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementSummaryResponse;
import dev.lions.unionflow.server.service.PaiementService;
@@ -16,193 +20,138 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
/**
* Resource REST pour la gestion des paiements
* Resource REST pour la gestion des paiements (Wave Checkout et paiements manuels).
*
* <p>Endpoints principaux :
* <ul>
* <li>{@code POST /api/paiements/initier-paiement-en-ligne} — démarre le flux Wave QR code</li>
* <li>{@code GET /api/paiements/statut-intention/{intentionId}} — polling du statut Wave</li>
* <li>{@code POST /api/paiements/declarer-manuel} — paiement manuel (espèces/virement)</li>
* <li>{@code GET /api/paiements/mon-historique} — historique du membre connecté</li>
* </ul>
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
* @since 2026-04-13
*/
@Path("/api/paiements")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" })
@Tag(name = "Paiements", description = "Gestion des paiements : création, validation et suivi")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER"})
@Tag(name = "Paiements", description = "Paiements de cotisations — Wave Checkout et manuel")
public class PaiementResource {
private static final Logger LOG = Logger.getLogger(PaiementResource.class);
private static final Logger LOG = Logger.getLogger(PaiementResource.class);
@Inject
PaiementService paiementService;
@Inject
PaiementService paiementService;
/**
* Crée un nouveau paiement
*
* @param request DTO du paiement à créer
* @return Paiement créé
*/
@POST
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
public Response creerPaiement(@Valid CreatePaiementRequest request) {
LOG.infof("POST /api/paiements - Création paiement: %s", request.numeroReference());
PaiementResponse result = paiementService.creerPaiement(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
// ── Lecture ───────────────────────────────────────────────────────────────
/**
* Valide un paiement
*
* @param id ID du paiement
* @return Paiement validé
*/
@POST
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}/valider")
public Response validerPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/paiements/%s/valider", id);
PaiementResponse result = paiementService.validerPaiement(id);
return Response.ok(result).build();
}
@GET
@Path("/{id}")
public Response trouverParId(@PathParam("id") UUID id) {
LOG.infof("GET /api/paiements/%s", id);
PaiementResponse result = paiementService.trouverParId(id);
return Response.ok(result).build();
}
/**
* Annule un paiement
*
* @param id ID du paiement
* @return Paiement annulé
*/
@POST
@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" })
@Path("/{id}/annuler")
public Response annulerPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/paiements/%s/annuler", id);
PaiementResponse result = paiementService.annulerPaiement(id);
return Response.ok(result).build();
}
@GET
@Path("/reference/{numeroReference}")
public Response trouverParNumeroReference(
@PathParam("numeroReference") String numeroReference) {
LOG.infof("GET /api/paiements/reference/%s", numeroReference);
PaiementResponse result = paiementService.trouverParNumeroReference(numeroReference);
return Response.ok(result).build();
}
/**
* Trouve un paiement par son ID
*
* @param id ID du paiement
* @return Paiement
*/
@GET
@Path("/{id}")
public Response trouverParId(@PathParam("id") UUID id) {
LOG.infof("GET /api/paiements/%s", id);
PaiementResponse result = paiementService.trouverParId(id);
return Response.ok(result).build();
}
@GET
@Path("/membre/{membreId}")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION"})
public Response listerParMembre(@PathParam("membreId") UUID membreId) {
LOG.infof("GET /api/paiements/membre/%s", membreId);
List<PaiementSummaryResponse> result = paiementService.listerParMembre(membreId);
return Response.ok(result).build();
}
/**
* Trouve un paiement par son numéro de référence
*
* @param numeroReference Numéro de référence
* @return Paiement
*/
@GET
@Path("/reference/{numeroReference}")
public Response trouverParNumeroReference(@PathParam("numeroReference") String numeroReference) {
LOG.infof("GET /api/paiements/reference/%s", numeroReference);
PaiementResponse result = paiementService.trouverParNumeroReference(numeroReference);
return Response.ok(result).build();
}
@GET
@Path("/mon-historique")
@RolesAllowed({"MEMBRE", "ADMIN", "ADMIN_ORGANISATION"})
public Response getMonHistoriquePaiements(
@QueryParam("limit") @DefaultValue("20") int limit) {
LOG.infof("GET /api/paiements/mon-historique?limit=%d", limit);
List<PaiementSummaryResponse> result = paiementService.getMonHistoriquePaiements(limit);
return Response.ok(result).build();
}
/**
* Liste tous les paiements d'un membre
*
* @param membreId ID du membre
* @return Liste des paiements
*/
@GET
@Path("/membre/{membreId}")
public Response listerParMembre(@PathParam("membreId") UUID membreId) {
LOG.infof("GET /api/paiements/membre/%s", membreId);
List<PaiementSummaryResponse> result = paiementService.listerParMembre(membreId);
return Response.ok(result).build();
}
// ── Administration ────────────────────────────────────────────────────────
/**
* Liste l'historique des paiements du membre connecté (auto-détection).
* Utilisé par la page personnelle "Payer mes Cotisations".
*
* @param limit Nombre maximum de paiements à retourner (défaut : 5)
* @return Liste des derniers paiements
*/
@GET
@Path("/mes-paiements/historique")
@RolesAllowed({ "MEMBRE", "ADMIN", "ADMIN_ORGANISATION" })
public Response getMonHistoriquePaiements(
@QueryParam("limit") @DefaultValue("5") int limit) {
LOG.infof("GET /api/paiements/mes-paiements/historique?limit=%d", limit);
List<PaiementSummaryResponse> result = paiementService.getMonHistoriquePaiements(limit);
return Response.ok(result).build();
}
@POST
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION"})
public Response creerPaiement(@Valid CreatePaiementRequest request) {
LOG.infof("POST /api/paiements — référence: %s", request.numeroReference());
PaiementResponse result = paiementService.creerPaiement(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
/**
* Initie un paiement en ligne via un gateway (Wave, Orange Money, Free Money, Carte).
* Retourne l'URL de redirection vers le gateway.
*
* @param request Données du paiement en ligne
* @return URL de redirection + transaction ID
*/
@POST
@Path("/initier-paiement-en-ligne")
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "ADMIN_ORGANISATION", "USER" })
public Response initierPaiementEnLigne(@Valid dev.lions.unionflow.server.api.dto.paiement.request.InitierPaiementEnLigneRequest request) {
LOG.infof("POST /api/paiements/initier-paiement-en-ligne - cotisation: %s, méthode: %s",
request.cotisationId(), request.methodePaiement());
dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse result =
paiementService.initierPaiementEnLigne(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
@POST
@Path("/{id}/valider")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION"})
public Response validerPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/paiements/%s/valider", id);
PaiementResponse result = paiementService.validerPaiement(id);
return Response.ok(result).build();
}
/**
* Initie un dépôt sur compte épargne via Wave (même flux que cotisations).
* Retourne wave_launch_url pour ouvrir l'app Wave puis retour deep link.
*/
@POST
@Path("/initier-depot-epargne-en-ligne")
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "ADMIN_ORGANISATION", "USER" })
public Response initierDepotEpargneEnLigne(@Valid dev.lions.unionflow.server.api.dto.paiement.request.InitierDepotEpargneRequest request) {
LOG.infof("POST /api/paiements/initier-depot-epargne-en-ligne - compte: %s, montant: %s",
request.compteId(), request.montant());
dev.lions.unionflow.server.api.dto.paiement.response.PaiementGatewayResponse result =
paiementService.initierDepotEpargneEnLigne(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
@POST
@Path("/{id}/annuler")
@RolesAllowed({"ADMIN", "ADMIN_ORGANISATION", "MEMBRE"})
public Response annulerPaiement(@PathParam("id") UUID id) {
LOG.infof("POST /api/paiements/%s/annuler", id);
PaiementResponse result = paiementService.annulerPaiement(id);
return Response.ok(result).build();
}
/**
* Polling du statut d'une IntentionPaiement Wave.
* Si Wave a confirmé le paiement, réconcilie automatiquement la cotisation (PAYEE) et retourne COMPLETEE.
* Le client web appelle cet endpoint toutes les 3 secondes pendant l'affichage du QR code.
*
* @param intentionId UUID de l'intention (clientReference retourné par initier-paiement-en-ligne)
* @return Statut courant + waveLaunchUrl (pour re-générer le QR si besoin) + message
*/
@GET
@Path("/statut-intention/{intentionId}")
@RolesAllowed({ "MEMBRE", "MEMBRE_ACTIF", "ADMIN", "ADMIN_ORGANISATION", "USER" })
public Response getStatutIntention(@PathParam("intentionId") java.util.UUID intentionId) {
LOG.infof("GET /api/paiements/statut-intention/%s", intentionId);
dev.lions.unionflow.server.api.dto.paiement.response.IntentionStatutResponse result =
paiementService.verifierStatutIntention(intentionId);
return Response.ok(result).build();
}
// ── Flux Wave Checkout (QR code web) ──────────────────────────────────────
/**
* Déclare un paiement manuel (espèces, virement, chèque).
* Le paiement est créé avec le statut EN_ATTENTE_VALIDATION.
* Le trésorier devra le valider via une page admin.
*
* @param request Données du paiement manuel
* @return Paiement créé (statut EN_ATTENTE_VALIDATION)
*/
@POST
@Path("/declarer-paiement-manuel")
@RolesAllowed({ "MEMBRE", "ADMIN", "ADMIN_ORGANISATION" })
public Response declarerPaiementManuel(@Valid dev.lions.unionflow.server.api.dto.paiement.request.DeclarerPaiementManuelRequest request) {
LOG.infof("POST /api/paiements/declarer-paiement-manuel - cotisation: %s, méthode: %s",
request.cotisationId(), request.methodePaiement());
PaiementResponse result = paiementService.declarerPaiementManuel(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
/**
* Initie un paiement Wave via Checkout QR code.
* Le web encode le {@code waveLaunchUrl} en QR code, l'utilisateur le scanne
* depuis l'app Wave. Après confirmation, Wave redirige vers la success URL.
*/
@POST
@Path("/initier-paiement-en-ligne")
@RolesAllowed({"MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "USER"})
public Response initierPaiementEnLigne(@Valid InitierPaiementEnLigneRequest request) {
LOG.infof("POST /api/paiements/initier-paiement-en-ligne — cotisation: %s, méthode: %s",
request.cotisationId(), request.methodePaiement());
PaiementGatewayResponse result = paiementService.initierPaiementEnLigne(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
/**
* Polling du statut d'une intention de paiement Wave.
* Appelé toutes les 3 secondes par le web pendant que l'utilisateur scanne le QR code.
* Retourne {@code confirme=true} dès que Wave confirme le paiement.
*/
@GET
@Path("/statut-intention/{intentionId}")
@RolesAllowed({"MEMBRE", "ADMIN", "ADMIN_ORGANISATION", "USER"})
public Response getStatutIntention(@PathParam("intentionId") UUID intentionId) {
LOG.infof("GET /api/paiements/statut-intention/%s", intentionId);
IntentionStatutResponse result = paiementService.getStatutIntention(intentionId);
return Response.ok(result).build();
}
// ── Flux manuel ───────────────────────────────────────────────────────────
@POST
@Path("/declarer-manuel")
@RolesAllowed({"MEMBRE", "ADMIN", "ADMIN_ORGANISATION"})
public Response declarerPaiementManuel(@Valid DeclarerPaiementManuelRequest request) {
LOG.infof("POST /api/paiements/declarer-manuel — cotisation: %s, méthode: %s",
request.cotisationId(), request.methodePaiement());
PaiementResponse result = paiementService.declarerPaiementManuel(request);
return Response.status(Response.Status.CREATED).entity(result).build();
}
}