feat: DTOs, Services et Resources REST pour Paiements et Wave
DTOs créés: - PaiementDTO avec validation complète - CompteWaveDTO avec validation format téléphone - TransactionWaveDTO avec tous les champs nécessaires Services créés: - PaiementService: CRUD complet, validation, annulation, calculs - WaveService: Gestion comptes Wave, transactions, vérification Resources REST créées: - PaiementResource: Endpoints CRUD, validation, annulation, recherche - WaveResource: Endpoints comptes et transactions Wave Respect strict DRY/WOU: - Patterns de service cohérents avec MembreService - Patterns de resource cohérents avec OrganisationResource - Gestion d'erreurs standardisée - Validation complète des DTOs
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package dev.lions.unionflow.server.api.dto.paiement;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* DTO pour la gestion des paiements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class PaiementDTO extends BaseDTO {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Numéro de référence unique */
|
||||
@NotBlank(message = "Le numéro de référence est obligatoire")
|
||||
private String numeroReference;
|
||||
|
||||
/** Montant du paiement */
|
||||
@NotNull(message = "Le montant est obligatoire")
|
||||
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
private BigDecimal montant;
|
||||
|
||||
/** Code devise (ISO 3 lettres) */
|
||||
@NotBlank(message = "Le code devise est obligatoire")
|
||||
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
|
||||
private String codeDevise;
|
||||
|
||||
/** Méthode de paiement */
|
||||
@NotNull(message = "La méthode de paiement est obligatoire")
|
||||
private MethodePaiement methodePaiement;
|
||||
|
||||
/** Statut du paiement */
|
||||
@NotNull(message = "Le statut du paiement est obligatoire")
|
||||
private StatutPaiement statutPaiement;
|
||||
|
||||
/** Date de paiement */
|
||||
private LocalDateTime datePaiement;
|
||||
|
||||
/** Date de validation */
|
||||
private LocalDateTime dateValidation;
|
||||
|
||||
/** Validateur (email de l'administrateur) */
|
||||
private String validateur;
|
||||
|
||||
/** Référence externe */
|
||||
private String referenceExterne;
|
||||
|
||||
/** URL de preuve de paiement */
|
||||
private String urlPreuve;
|
||||
|
||||
/** Commentaires et notes */
|
||||
private String commentaire;
|
||||
|
||||
/** Adresse IP de l'initiateur */
|
||||
private String ipAddress;
|
||||
|
||||
/** User-Agent de l'initiateur */
|
||||
private String userAgent;
|
||||
|
||||
/** ID du membre payeur */
|
||||
@NotNull(message = "Le membre payeur est obligatoire")
|
||||
private UUID membreId;
|
||||
|
||||
/** ID de la transaction Wave (si applicable) */
|
||||
private UUID transactionWaveId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.lions.unionflow.server.api.dto.wave;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* DTO pour la gestion des comptes Wave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CompteWaveDTO extends BaseDTO {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Numéro de téléphone Wave */
|
||||
@NotBlank(message = "Le numéro de téléphone est obligatoire")
|
||||
@Pattern(
|
||||
regexp = "^\\+225[0-9]{8}$",
|
||||
message = "Le numéro de téléphone Wave doit être au format +225XXXXXXXX")
|
||||
private String numeroTelephone;
|
||||
|
||||
/** Statut du compte */
|
||||
private StatutCompteWave statutCompte;
|
||||
|
||||
/** Identifiant Wave API (encrypté) */
|
||||
private String waveAccountId;
|
||||
|
||||
/** Environnement (SANDBOX ou PRODUCTION) */
|
||||
private String environnement;
|
||||
|
||||
/** Date de dernière vérification */
|
||||
private LocalDateTime dateDerniereVerification;
|
||||
|
||||
/** Commentaires */
|
||||
private String commentaire;
|
||||
|
||||
/** ID de l'organisation (si compte d'organisation) */
|
||||
private UUID organisationId;
|
||||
|
||||
/** ID du membre (si compte de membre) */
|
||||
private UUID membreId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package dev.lions.unionflow.server.api.dto.wave;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* DTO pour la gestion des transactions Wave
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class TransactionWaveDTO extends BaseDTO {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Identifiant Wave de la transaction */
|
||||
@NotBlank(message = "L'identifiant Wave est obligatoire")
|
||||
private String waveTransactionId;
|
||||
|
||||
/** Identifiant de requête Wave */
|
||||
private String waveRequestId;
|
||||
|
||||
/** Référence Wave */
|
||||
private String waveReference;
|
||||
|
||||
/** Type de transaction */
|
||||
@NotNull(message = "Le type de transaction est obligatoire")
|
||||
private TypeTransactionWave typeTransaction;
|
||||
|
||||
/** Statut de la transaction */
|
||||
@NotNull(message = "Le statut de la transaction est obligatoire")
|
||||
private StatutTransactionWave statutTransaction;
|
||||
|
||||
/** Montant de la transaction */
|
||||
@NotNull(message = "Le montant est obligatoire")
|
||||
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
private BigDecimal montant;
|
||||
|
||||
/** Frais de transaction */
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 10, fraction = 2)
|
||||
private BigDecimal frais;
|
||||
|
||||
/** Montant net */
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
private BigDecimal montantNet;
|
||||
|
||||
/** Code devise */
|
||||
@NotBlank(message = "Le code devise est obligatoire")
|
||||
@Pattern(regexp = "^[A-Z]{3}$", message = "Le code devise doit être un code ISO à 3 lettres")
|
||||
private String codeDevise;
|
||||
|
||||
/** Numéro téléphone payeur */
|
||||
private String telephonePayeur;
|
||||
|
||||
/** Numéro téléphone bénéficiaire */
|
||||
private String telephoneBeneficiaire;
|
||||
|
||||
/** Métadonnées JSON */
|
||||
private String metadonnees;
|
||||
|
||||
/** Nombre de tentatives */
|
||||
private Integer nombreTentatives;
|
||||
|
||||
/** Date de dernière tentative */
|
||||
private LocalDateTime dateDerniereTentative;
|
||||
|
||||
/** Message d'erreur */
|
||||
private String messageErreur;
|
||||
|
||||
/** ID du compte Wave */
|
||||
@NotNull(message = "Le compte Wave est obligatoire")
|
||||
private UUID compteWaveId;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO;
|
||||
import dev.lions.unionflow.server.service.PaiementService;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour la gestion des paiements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/paiements")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@PermitAll
|
||||
public class PaiementResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PaiementResource.class);
|
||||
|
||||
@Inject PaiementService paiementService;
|
||||
|
||||
/**
|
||||
* Crée un nouveau paiement
|
||||
*
|
||||
* @param paiementDTO DTO du paiement à créer
|
||||
* @return Paiement créé
|
||||
*/
|
||||
@POST
|
||||
public Response creerPaiement(@Valid PaiementDTO paiementDTO) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.creerPaiement(paiementDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @param paiementDTO DTO avec les modifications
|
||||
* @return Paiement mis à jour
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
public Response mettreAJourPaiement(@PathParam("id") UUID id, @Valid PaiementDTO paiementDTO) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.mettreAJourPaiement(id, paiementDTO);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la mise à jour du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return Paiement validé
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/valider")
|
||||
public Response validerPaiement(@PathParam("id") UUID id) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.validerPaiement(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la validation du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la validation du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annule un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return Paiement annulé
|
||||
*/
|
||||
@POST
|
||||
@Path("/{id}/annuler")
|
||||
public Response annulerPaiement(@PathParam("id") UUID id) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.annulerPaiement(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'annulation du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de l'annulation du paiement: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un paiement par son ID
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return Paiement
|
||||
*/
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public Response trouverParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.trouverParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage()))
|
||||
.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) {
|
||||
try {
|
||||
PaiementDTO result = paiementService.trouverParNumeroReference(numeroReference);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Paiement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du paiement");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du paiement: " + e.getMessage()))
|
||||
.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) {
|
||||
try {
|
||||
List<PaiementDTO> result = paiementService.listerParMembre(membreId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des paiements");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des paiements: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,213 +1,264 @@
|
||||
package dev.lions.unionflow.server.resource;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveBalanceDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveWebhookDTO;
|
||||
import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO;
|
||||
import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.service.WaveService;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Content;
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Resource REST pour l'intégration Wave Money
|
||||
* Resource REST pour l'intégration Wave Mobile Money
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@Path("/api/wave")
|
||||
@Tag(name = "Wave Money", description = "API d'intégration Wave Money pour les paiements")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@PermitAll
|
||||
public class WaveResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(WaveResource.class);
|
||||
|
||||
@Inject WaveService waveService;
|
||||
|
||||
// ========================================
|
||||
// COMPTES WAVE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée un nouveau compte Wave
|
||||
*
|
||||
* @param compteWaveDTO DTO du compte à créer
|
||||
* @return Compte créé
|
||||
*/
|
||||
@POST
|
||||
@Path("/checkout/sessions")
|
||||
@Operation(
|
||||
summary = "Créer une session de paiement Wave",
|
||||
description = "Crée une nouvelle session de paiement via l'API Wave Checkout")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Session créée avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveCheckoutSessionDTO.class)))
|
||||
@APIResponse(responseCode = "400", description = "Données invalides")
|
||||
@APIResponse(responseCode = "500", description = "Erreur serveur")
|
||||
public Response creerSessionPaiement(
|
||||
@Parameter(description = "Montant à payer", required = true) @QueryParam("montant")
|
||||
@NotNull
|
||||
@DecimalMin("0.01")
|
||||
BigDecimal montant,
|
||||
@Parameter(description = "Devise (XOF par défaut)") @QueryParam("devise") String devise,
|
||||
@Parameter(description = "URL de succès", required = true) @QueryParam("successUrl")
|
||||
@NotBlank
|
||||
String successUrl,
|
||||
@Parameter(description = "URL d'erreur", required = true) @QueryParam("errorUrl")
|
||||
@NotBlank
|
||||
String errorUrl,
|
||||
@Parameter(description = "Référence UnionFlow") @QueryParam("reference")
|
||||
String referenceUnionFlow,
|
||||
@Parameter(description = "Description du paiement") @QueryParam("description")
|
||||
String description,
|
||||
@Parameter(description = "ID de l'organisation") @QueryParam("organisationId") UUID organisationId,
|
||||
@Parameter(description = "ID du membre") @QueryParam("membreId") UUID membreId) {
|
||||
@Path("/comptes")
|
||||
public Response creerCompteWave(@Valid CompteWaveDTO compteWaveDTO) {
|
||||
try {
|
||||
WaveCheckoutSessionDTO session =
|
||||
waveService.creerSessionPaiement(
|
||||
montant,
|
||||
devise,
|
||||
successUrl,
|
||||
errorUrl,
|
||||
referenceUnionFlow,
|
||||
description,
|
||||
organisationId,
|
||||
membreId);
|
||||
|
||||
return Response.ok(session).build();
|
||||
|
||||
CompteWaveDTO result = waveService.creerCompteWave(compteWaveDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse(e.getMessage()))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la session: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors de la création de la session");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
LOG.errorf(e, "Erreur lors de la création du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/checkout/sessions/{sessionId}")
|
||||
@Operation(
|
||||
summary = "Vérifier le statut d'une session",
|
||||
description = "Récupère le statut d'une session de paiement Wave")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Statut récupéré avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveCheckoutSessionDTO.class)))
|
||||
@APIResponse(responseCode = "404", description = "Session non trouvée")
|
||||
public Response verifierStatutSession(
|
||||
@Parameter(description = "ID de la session Wave", required = true) @PathParam("sessionId")
|
||||
@NotBlank
|
||||
String sessionId) {
|
||||
/**
|
||||
* Met à jour un compte Wave
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @param compteWaveDTO DTO avec les modifications
|
||||
* @return Compte mis à jour
|
||||
*/
|
||||
@PUT
|
||||
@Path("/comptes/{id}")
|
||||
public Response mettreAJourCompteWave(@PathParam("id") UUID id, @Valid CompteWaveDTO compteWaveDTO) {
|
||||
try {
|
||||
WaveCheckoutSessionDTO session = waveService.verifierStatutSession(sessionId);
|
||||
return Response.ok(session).build();
|
||||
|
||||
CompteWaveDTO result = waveService.mettreAJourCompteWave(id, compteWaveDTO);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la vérification du statut: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors de la vérification du statut");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la mise à jour du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie un compte Wave
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return Compte vérifié
|
||||
*/
|
||||
@POST
|
||||
@Path("/webhooks")
|
||||
@Operation(
|
||||
summary = "Recevoir un webhook Wave",
|
||||
description = "Endpoint pour recevoir les notifications webhook de Wave")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Webhook reçu et traité",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveWebhookDTO.class)))
|
||||
@APIResponse(responseCode = "400", description = "Webhook invalide")
|
||||
@APIResponse(responseCode = "401", description = "Signature invalide")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response recevoirWebhook(
|
||||
@Parameter(description = "Payload du webhook", required = true) String payload,
|
||||
@jakarta.ws.rs.HeaderParam("X-Wave-Signature") String signature) {
|
||||
@Path("/comptes/{id}/verifier")
|
||||
public Response verifierCompteWave(@PathParam("id") UUID id) {
|
||||
try {
|
||||
// Récupérer les headers
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
if (signature != null) {
|
||||
headers.put("X-Wave-Signature", signature);
|
||||
}
|
||||
|
||||
WaveWebhookDTO webhook = waveService.traiterWebhook(payload, signature, headers);
|
||||
return Response.ok(webhook).build();
|
||||
|
||||
} catch (SecurityException e) {
|
||||
LOG.warnf("Signature webhook invalide: %s", e.getMessage());
|
||||
return Response.status(Response.Status.UNAUTHORIZED).build();
|
||||
|
||||
CompteWaveDTO result = waveService.verifierCompteWave(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du traitement du webhook: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors du traitement du webhook");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
LOG.errorf(e, "Erreur lors de la vérification du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la vérification du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par son ID
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return Compte Wave
|
||||
*/
|
||||
@GET
|
||||
@Path("/balance")
|
||||
@Operation(
|
||||
summary = "Consulter le solde Wave",
|
||||
description = "Récupère le solde disponible du wallet Wave")
|
||||
@APIResponse(
|
||||
responseCode = "200",
|
||||
description = "Solde récupéré avec succès",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = WaveBalanceDTO.class)))
|
||||
public Response consulterSolde() {
|
||||
@Path("/comptes/{id}")
|
||||
public Response trouverCompteWaveParId(@PathParam("id") UUID id) {
|
||||
try {
|
||||
WaveBalanceDTO balance = waveService.consulterSolde();
|
||||
return Response.ok(balance).build();
|
||||
|
||||
CompteWaveDTO result = waveService.trouverCompteWaveParId(id);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la consultation du solde: %s", e.getMessage());
|
||||
Map<String, String> erreur = new HashMap<>();
|
||||
erreur.put("erreur", "Erreur lors de la consultation du solde");
|
||||
erreur.put("message", e.getMessage());
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(erreur).build();
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par numéro de téléphone
|
||||
*
|
||||
* @param numeroTelephone Numéro de téléphone
|
||||
* @return Compte Wave ou null
|
||||
*/
|
||||
@GET
|
||||
@Path("/test")
|
||||
@Operation(
|
||||
summary = "Tester la connexion Wave",
|
||||
description = "Teste la connexion et la configuration de l'API Wave")
|
||||
@APIResponse(responseCode = "200", description = "Test effectué")
|
||||
public Response testerConnexion() {
|
||||
Map<String, Object> resultat = waveService.testerConnexion();
|
||||
return Response.ok(resultat).build();
|
||||
@Path("/comptes/telephone/{numeroTelephone}")
|
||||
public Response trouverCompteWaveParTelephone(@PathParam("numeroTelephone") String numeroTelephone) {
|
||||
try {
|
||||
CompteWaveDTO result = waveService.trouverCompteWaveParTelephone(numeroTelephone);
|
||||
if (result == null) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Compte Wave non trouvé"))
|
||||
.build();
|
||||
}
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche du compte Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche du compte Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les comptes Wave d'une organisation
|
||||
*
|
||||
* @param organisationId ID de l'organisation
|
||||
* @return Liste des comptes Wave
|
||||
*/
|
||||
@GET
|
||||
@Path("/comptes/organisation/{organisationId}")
|
||||
public Response listerComptesWaveParOrganisation(@PathParam("organisationId") UUID organisationId) {
|
||||
try {
|
||||
List<CompteWaveDTO> result = waveService.listerComptesWaveParOrganisation(organisationId);
|
||||
return Response.ok(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la liste des comptes Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la liste des comptes Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TRANSACTIONS WAVE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crée une nouvelle transaction Wave
|
||||
*
|
||||
* @param transactionWaveDTO DTO de la transaction à créer
|
||||
* @return Transaction créée
|
||||
*/
|
||||
@POST
|
||||
@Path("/transactions")
|
||||
public Response creerTransactionWave(@Valid TransactionWaveDTO transactionWaveDTO) {
|
||||
try {
|
||||
TransactionWaveDTO result = waveService.creerTransactionWave(transactionWaveDTO);
|
||||
return Response.status(Response.Status.CREATED).entity(result).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la création de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le statut d'une transaction Wave
|
||||
*
|
||||
* @param waveTransactionId Identifiant Wave de la transaction
|
||||
* @param statut Nouveau statut
|
||||
* @return Transaction mise à jour
|
||||
*/
|
||||
@PUT
|
||||
@Path("/transactions/{waveTransactionId}/statut")
|
||||
public Response mettreAJourStatutTransaction(
|
||||
@PathParam("waveTransactionId") String waveTransactionId, StatutTransactionWave statut) {
|
||||
try {
|
||||
TransactionWaveDTO result = waveService.mettreAJourStatutTransaction(waveTransactionId, statut);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Transaction Wave non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la mise à jour du statut de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(
|
||||
new ErrorResponse(
|
||||
"Erreur lors de la mise à jour du statut de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une transaction Wave par son identifiant Wave
|
||||
*
|
||||
* @param waveTransactionId Identifiant Wave
|
||||
* @return Transaction Wave
|
||||
*/
|
||||
@GET
|
||||
@Path("/transactions/{waveTransactionId}")
|
||||
public Response trouverTransactionWaveParId(@PathParam("waveTransactionId") String waveTransactionId) {
|
||||
try {
|
||||
TransactionWaveDTO result = waveService.trouverTransactionWaveParId(waveTransactionId);
|
||||
return Response.ok(result).build();
|
||||
} catch (jakarta.ws.rs.NotFoundException e) {
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(new ErrorResponse("Transaction Wave non trouvée"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la recherche de la transaction Wave");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(new ErrorResponse("Erreur lors de la recherche de la transaction Wave: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Classe interne pour les réponses d'erreur */
|
||||
public static class ErrorResponse {
|
||||
public String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,174 +1,309 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.PaiementDTO;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Paiement;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.PaiementRepository;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service métier pour la gestion des paiements Mobile Money Intègre Wave Money, Orange Money, et
|
||||
* Moov Money
|
||||
* Service métier pour la gestion des paiements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-15
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class PaiementService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PaiementService.class);
|
||||
|
||||
@Inject PaiementRepository paiementRepository;
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
/**
|
||||
* Initie un paiement Mobile Money
|
||||
* Crée un nouveau paiement
|
||||
*
|
||||
* @param paymentData données du paiement
|
||||
* @return informations du paiement initié
|
||||
* @param paiementDTO DTO du paiement à créer
|
||||
* @return DTO du paiement créé
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> initiatePayment(@Valid Map<String, Object> paymentData) {
|
||||
LOG.infof("Initiation d'un paiement");
|
||||
public PaiementDTO creerPaiement(PaiementDTO paiementDTO) {
|
||||
LOG.infof("Création d'un nouveau paiement: %s", paiementDTO.getNumeroReference());
|
||||
|
||||
try {
|
||||
String operateur = (String) paymentData.get("operateur");
|
||||
BigDecimal montant = new BigDecimal(paymentData.get("montant").toString());
|
||||
String numeroTelephone = (String) paymentData.get("numeroTelephone");
|
||||
String cotisationId = (String) paymentData.get("cotisationId");
|
||||
Paiement paiement = convertToEntity(paiementDTO);
|
||||
paiement.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
// Générer un ID unique pour le paiement
|
||||
String paymentId = UUID.randomUUID().toString();
|
||||
String numeroReference = "PAY-" + System.currentTimeMillis();
|
||||
paiementRepository.persist(paiement);
|
||||
LOG.infof("Paiement créé avec succès: ID=%s, Référence=%s", paiement.getId(), paiement.getNumeroReference());
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("id", paymentId);
|
||||
response.put("cotisationId", cotisationId);
|
||||
response.put("numeroReference", numeroReference);
|
||||
response.put("montant", montant);
|
||||
response.put("codeDevise", "XOF");
|
||||
response.put("methodePaiement", operateur != null ? operateur.toUpperCase() : "WAVE");
|
||||
response.put("statut", "PENDING");
|
||||
response.put("dateTransaction", LocalDateTime.now().toString());
|
||||
response.put("numeroTransaction", numeroReference);
|
||||
response.put("operateurMobileMoney", operateur != null ? operateur.toUpperCase() : "WAVE");
|
||||
response.put("numeroTelephone", numeroTelephone);
|
||||
response.put("dateCreation", LocalDateTime.now().toString());
|
||||
|
||||
// Métadonnées
|
||||
Map<String, Object> metadonnees = new HashMap<>();
|
||||
metadonnees.put("source", "unionflow_mobile");
|
||||
metadonnees.put("operateur", operateur);
|
||||
metadonnees.put("numero_telephone", numeroTelephone);
|
||||
metadonnees.put("cotisation_id", cotisationId);
|
||||
response.put("metadonnees", metadonnees);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf("Erreur lors de l'initiation du paiement: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de l'initiation du paiement: " + e.getMessage());
|
||||
}
|
||||
return convertToDTO(paiement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le statut d'un paiement
|
||||
* Met à jour un paiement existant
|
||||
*
|
||||
* @param paymentId ID du paiement
|
||||
* @return statut du paiement
|
||||
* @param id ID du paiement
|
||||
* @param paiementDTO DTO avec les modifications
|
||||
* @return DTO du paiement mis à jour
|
||||
*/
|
||||
public Map<String, Object> getPaymentStatus(@NotNull String paymentId) {
|
||||
LOG.infof("Récupération du statut du paiement: %s", paymentId);
|
||||
@Transactional
|
||||
public PaiementDTO mettreAJourPaiement(UUID id, PaiementDTO paiementDTO) {
|
||||
LOG.infof("Mise à jour du paiement ID: %s", id);
|
||||
|
||||
// Simulation du statut
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("id", paymentId);
|
||||
status.put("statut", "COMPLETED"); // Simulation d'un paiement réussi
|
||||
status.put("dateModification", LocalDateTime.now().toString());
|
||||
status.put("message", "Paiement traité avec succès");
|
||||
Paiement paiement =
|
||||
paiementRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));
|
||||
|
||||
return status;
|
||||
if (!paiement.peutEtreModifie()) {
|
||||
throw new IllegalStateException("Le paiement ne peut plus être modifié (statut finalisé)");
|
||||
}
|
||||
|
||||
updateFromDTO(paiement, paiementDTO);
|
||||
paiement.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
paiementRepository.persist(paiement);
|
||||
LOG.infof("Paiement mis à jour avec succès: ID=%s", id);
|
||||
|
||||
return convertToDTO(paiement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide un paiement
|
||||
*
|
||||
* @param id ID du paiement
|
||||
* @return DTO du paiement validé
|
||||
*/
|
||||
@Transactional
|
||||
public PaiementDTO validerPaiement(UUID id) {
|
||||
LOG.infof("Validation du paiement ID: %s", id);
|
||||
|
||||
Paiement paiement =
|
||||
paiementRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));
|
||||
|
||||
if (paiement.isValide()) {
|
||||
LOG.warnf("Le paiement ID=%s est déjà validé", id);
|
||||
return convertToDTO(paiement);
|
||||
}
|
||||
|
||||
paiement.setStatutPaiement(StatutPaiement.VALIDE);
|
||||
paiement.setDateValidation(LocalDateTime.now());
|
||||
paiement.setValidateur(keycloakService.getCurrentUserEmail());
|
||||
paiement.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
paiementRepository.persist(paiement);
|
||||
LOG.infof("Paiement validé avec succès: ID=%s", id);
|
||||
|
||||
return convertToDTO(paiement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Annule un paiement
|
||||
*
|
||||
* @param paymentId ID du paiement
|
||||
* @param cotisationId ID de la cotisation
|
||||
* @return résultat de l'annulation
|
||||
* @param id ID du paiement
|
||||
* @return DTO du paiement annulé
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> cancelPayment(
|
||||
@NotNull String paymentId, @NotNull String cotisationId) {
|
||||
LOG.infof("Annulation du paiement: %s pour cotisation: %s", paymentId, cotisationId);
|
||||
public PaiementDTO annulerPaiement(UUID id) {
|
||||
LOG.infof("Annulation du paiement ID: %s", id);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("id", paymentId);
|
||||
result.put("cotisationId", cotisationId);
|
||||
result.put("statut", "CANCELLED");
|
||||
result.put("dateAnnulation", LocalDateTime.now().toString());
|
||||
result.put("message", "Paiement annulé avec succès");
|
||||
Paiement paiement =
|
||||
paiementRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));
|
||||
|
||||
return result;
|
||||
if (!paiement.peutEtreModifie()) {
|
||||
throw new IllegalStateException("Le paiement ne peut plus être annulé (statut finalisé)");
|
||||
}
|
||||
|
||||
paiement.setStatutPaiement(StatutPaiement.ANNULE);
|
||||
paiement.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
paiementRepository.persist(paiement);
|
||||
LOG.infof("Paiement annulé avec succès: ID=%s", id);
|
||||
|
||||
return convertToDTO(paiement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'historique des paiements
|
||||
* Trouve un paiement par son ID
|
||||
*
|
||||
* @param filters filtres de recherche
|
||||
* @return liste des paiements
|
||||
* @param id ID du paiement
|
||||
* @return DTO du paiement
|
||||
*/
|
||||
public List<Map<String, Object>> getPaymentHistory(Map<String, Object> filters) {
|
||||
LOG.info("Récupération de l'historique des paiements");
|
||||
|
||||
// Simulation d'un historique vide pour l'instant
|
||||
return List.of();
|
||||
public PaiementDTO trouverParId(UUID id) {
|
||||
return paiementRepository
|
||||
.findByIdOptional(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Paiement non trouvé avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie le statut d'un service de paiement
|
||||
* Trouve un paiement par son numéro de référence
|
||||
*
|
||||
* @param serviceType type de service (WAVE, ORANGE_MONEY, MOOV_MONEY)
|
||||
* @return statut du service
|
||||
* @param numeroReference Numéro de référence
|
||||
* @return DTO du paiement
|
||||
*/
|
||||
public Map<String, Object> checkServiceStatus(@NotNull String serviceType) {
|
||||
LOG.infof("Vérification du statut du service: %s", serviceType);
|
||||
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("service", serviceType);
|
||||
status.put("statut", "OPERATIONAL");
|
||||
status.put("disponible", true);
|
||||
status.put("derniereMiseAJour", LocalDateTime.now().toString());
|
||||
|
||||
return status;
|
||||
public PaiementDTO trouverParNumeroReference(String numeroReference) {
|
||||
return paiementRepository
|
||||
.findByNumeroReference(numeroReference)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Paiement non trouvé avec la référence: " + numeroReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les statistiques de paiement
|
||||
* Liste tous les paiements d'un membre
|
||||
*
|
||||
* @param filters filtres pour les statistiques
|
||||
* @return statistiques des paiements
|
||||
* @param membreId ID du membre
|
||||
* @return Liste des paiements
|
||||
*/
|
||||
public Map<String, Object> getPaymentStatistics(Map<String, Object> filters) {
|
||||
LOG.info("Récupération des statistiques de paiement");
|
||||
public List<PaiementDTO> listerParMembre(UUID membreId) {
|
||||
return paiementRepository.findByMembreId(membreId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalPaiements", 0);
|
||||
stats.put("montantTotal", BigDecimal.ZERO);
|
||||
stats.put("paiementsReussis", 0);
|
||||
stats.put("paiementsEchoues", 0);
|
||||
stats.put("paiementsEnAttente", 0);
|
||||
stats.put(
|
||||
"operateurs",
|
||||
Map.of(
|
||||
"WAVE", 0,
|
||||
"ORANGE_MONEY", 0,
|
||||
"MOOV_MONEY", 0));
|
||||
/**
|
||||
* Calcule le montant total des paiements validés dans une période
|
||||
*
|
||||
* @param dateDebut Date de début
|
||||
* @param dateFin Date de fin
|
||||
* @return Montant total
|
||||
*/
|
||||
public BigDecimal calculerMontantTotalValides(LocalDateTime dateDebut, LocalDateTime dateFin) {
|
||||
return paiementRepository.calculerMontantTotalValides(dateDebut, dateFin);
|
||||
}
|
||||
|
||||
return stats;
|
||||
// ========================================
|
||||
// MÉTHODES PRIVÉES
|
||||
// ========================================
|
||||
|
||||
/** Convertit une entité en DTO */
|
||||
private PaiementDTO convertToDTO(Paiement paiement) {
|
||||
if (paiement == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PaiementDTO dto = new PaiementDTO();
|
||||
dto.setId(paiement.getId());
|
||||
dto.setNumeroReference(paiement.getNumeroReference());
|
||||
dto.setMontant(paiement.getMontant());
|
||||
dto.setCodeDevise(paiement.getCodeDevise());
|
||||
dto.setMethodePaiement(paiement.getMethodePaiement());
|
||||
dto.setStatutPaiement(paiement.getStatutPaiement());
|
||||
dto.setDatePaiement(paiement.getDatePaiement());
|
||||
dto.setDateValidation(paiement.getDateValidation());
|
||||
dto.setValidateur(paiement.getValidateur());
|
||||
dto.setReferenceExterne(paiement.getReferenceExterne());
|
||||
dto.setUrlPreuve(paiement.getUrlPreuve());
|
||||
dto.setCommentaire(paiement.getCommentaire());
|
||||
dto.setIpAddress(paiement.getIpAddress());
|
||||
dto.setUserAgent(paiement.getUserAgent());
|
||||
|
||||
if (paiement.getMembre() != null) {
|
||||
dto.setMembreId(paiement.getMembre().getId());
|
||||
}
|
||||
if (paiement.getTransactionWave() != null) {
|
||||
dto.setTransactionWaveId(paiement.getTransactionWave().getId());
|
||||
}
|
||||
|
||||
dto.setDateCreation(paiement.getDateCreation());
|
||||
dto.setDateModification(paiement.getDateModification());
|
||||
dto.setActif(paiement.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité */
|
||||
private Paiement convertToEntity(PaiementDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Paiement paiement = new Paiement();
|
||||
paiement.setNumeroReference(dto.getNumeroReference());
|
||||
paiement.setMontant(dto.getMontant());
|
||||
paiement.setCodeDevise(dto.getCodeDevise());
|
||||
paiement.setMethodePaiement(dto.getMethodePaiement());
|
||||
paiement.setStatutPaiement(dto.getStatutPaiement() != null ? dto.getStatutPaiement() : StatutPaiement.EN_ATTENTE);
|
||||
paiement.setDatePaiement(dto.getDatePaiement());
|
||||
paiement.setDateValidation(dto.getDateValidation());
|
||||
paiement.setValidateur(dto.getValidateur());
|
||||
paiement.setReferenceExterne(dto.getReferenceExterne());
|
||||
paiement.setUrlPreuve(dto.getUrlPreuve());
|
||||
paiement.setCommentaire(dto.getCommentaire());
|
||||
paiement.setIpAddress(dto.getIpAddress());
|
||||
paiement.setUserAgent(dto.getUserAgent());
|
||||
|
||||
// Relation Membre
|
||||
if (dto.getMembreId() != null) {
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(dto.getMembreId())
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
|
||||
paiement.setMembre(membre);
|
||||
}
|
||||
|
||||
// Relation TransactionWave sera gérée par WaveService
|
||||
|
||||
return paiement;
|
||||
}
|
||||
|
||||
/** Met à jour une entité à partir d'un DTO */
|
||||
private void updateFromDTO(Paiement paiement, PaiementDTO dto) {
|
||||
if (dto.getMontant() != null) {
|
||||
paiement.setMontant(dto.getMontant());
|
||||
}
|
||||
if (dto.getCodeDevise() != null) {
|
||||
paiement.setCodeDevise(dto.getCodeDevise());
|
||||
}
|
||||
if (dto.getMethodePaiement() != null) {
|
||||
paiement.setMethodePaiement(dto.getMethodePaiement());
|
||||
}
|
||||
if (dto.getStatutPaiement() != null) {
|
||||
paiement.setStatutPaiement(dto.getStatutPaiement());
|
||||
}
|
||||
if (dto.getDatePaiement() != null) {
|
||||
paiement.setDatePaiement(dto.getDatePaiement());
|
||||
}
|
||||
if (dto.getDateValidation() != null) {
|
||||
paiement.setDateValidation(dto.getDateValidation());
|
||||
}
|
||||
if (dto.getValidateur() != null) {
|
||||
paiement.setValidateur(dto.getValidateur());
|
||||
}
|
||||
if (dto.getReferenceExterne() != null) {
|
||||
paiement.setReferenceExterne(dto.getReferenceExterne());
|
||||
}
|
||||
if (dto.getUrlPreuve() != null) {
|
||||
paiement.setUrlPreuve(dto.getUrlPreuve());
|
||||
}
|
||||
if (dto.getCommentaire() != null) {
|
||||
paiement.setCommentaire(dto.getCommentaire());
|
||||
}
|
||||
if (dto.getIpAddress() != null) {
|
||||
paiement.setIpAddress(dto.getIpAddress());
|
||||
}
|
||||
if (dto.getUserAgent() != null) {
|
||||
paiement.setUserAgent(dto.getUserAgent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,281 +1,393 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveBalanceDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO;
|
||||
import dev.lions.unionflow.server.api.dto.paiement.WaveWebhookDTO;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutSession;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.TypeEvenement;
|
||||
import dev.lions.unionflow.server.api.dto.wave.CompteWaveDTO;
|
||||
import dev.lions.unionflow.server.api.dto.wave.TransactionWaveDTO;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
|
||||
import dev.lions.unionflow.server.entity.CompteWave;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.TransactionWave;
|
||||
import dev.lions.unionflow.server.repository.CompteWaveRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.TransactionWaveRepository;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service pour l'intégration Wave Money
|
||||
* Gère les sessions de paiement, les webhooks et la consultation du solde
|
||||
* Service métier pour l'intégration Wave Mobile Money
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
* @version 3.0
|
||||
* @since 2025-01-29
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class WaveService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(WaveService.class);
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.api.key")
|
||||
Optional<String> waveApiKey;
|
||||
@Inject CompteWaveRepository compteWaveRepository;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.api.secret")
|
||||
Optional<String> waveApiSecret;
|
||||
@Inject TransactionWaveRepository transactionWaveRepository;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.api.base.url", defaultValue = "https://api.wave.com/v1")
|
||||
String waveApiBaseUrl;
|
||||
@Inject OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.environment", defaultValue = "sandbox")
|
||||
String waveEnvironment;
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
@ConfigProperty(name = "wave.webhook.secret")
|
||||
Optional<String> waveWebhookSecret;
|
||||
@Inject KeycloakService keycloakService;
|
||||
|
||||
/**
|
||||
* Crée une session de paiement Wave Checkout
|
||||
* Crée un nouveau compte Wave
|
||||
*
|
||||
* @param compteWaveDTO DTO du compte à créer
|
||||
* @return DTO du compte créé
|
||||
*/
|
||||
@Transactional
|
||||
public CompteWaveDTO creerCompteWave(CompteWaveDTO compteWaveDTO) {
|
||||
LOG.infof("Création d'un nouveau compte Wave: %s", compteWaveDTO.getNumeroTelephone());
|
||||
|
||||
// Vérifier l'unicité du numéro de téléphone
|
||||
if (compteWaveRepository.findByNumeroTelephone(compteWaveDTO.getNumeroTelephone()).isPresent()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Un compte Wave existe déjà pour ce numéro: " + compteWaveDTO.getNumeroTelephone());
|
||||
}
|
||||
|
||||
CompteWave compteWave = convertToEntity(compteWaveDTO);
|
||||
compteWave.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
compteWaveRepository.persist(compteWave);
|
||||
LOG.infof("Compte Wave créé avec succès: ID=%s, Téléphone=%s", compteWave.getId(), compteWave.getNumeroTelephone());
|
||||
|
||||
return convertToDTO(compteWave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un compte Wave
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @param compteWaveDTO DTO avec les modifications
|
||||
* @return DTO du compte mis à jour
|
||||
*/
|
||||
@Transactional
|
||||
public CompteWaveDTO mettreAJourCompteWave(UUID id, CompteWaveDTO compteWaveDTO) {
|
||||
LOG.infof("Mise à jour du compte Wave ID: %s", id);
|
||||
|
||||
CompteWave compteWave =
|
||||
compteWaveRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id));
|
||||
|
||||
updateFromDTO(compteWave, compteWaveDTO);
|
||||
compteWave.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
compteWaveRepository.persist(compteWave);
|
||||
LOG.infof("Compte Wave mis à jour avec succès: ID=%s", id);
|
||||
|
||||
return convertToDTO(compteWave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie un compte Wave
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return DTO du compte vérifié
|
||||
*/
|
||||
@Transactional
|
||||
public CompteWaveDTO verifierCompteWave(UUID id) {
|
||||
LOG.infof("Vérification du compte Wave ID: %s", id);
|
||||
|
||||
CompteWave compteWave =
|
||||
compteWaveRepository
|
||||
.findByIdOptional(id)
|
||||
.orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id));
|
||||
|
||||
compteWave.setStatutCompte(StatutCompteWave.VERIFIE);
|
||||
compteWave.setDateDerniereVerification(LocalDateTime.now());
|
||||
compteWave.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
compteWaveRepository.persist(compteWave);
|
||||
LOG.infof("Compte Wave vérifié avec succès: ID=%s", id);
|
||||
|
||||
return convertToDTO(compteWave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par son ID
|
||||
*
|
||||
* @param id ID du compte
|
||||
* @return DTO du compte
|
||||
*/
|
||||
public CompteWaveDTO trouverCompteWaveParId(UUID id) {
|
||||
return compteWaveRepository
|
||||
.findByIdOptional(id)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(() -> new NotFoundException("Compte Wave non trouvé avec l'ID: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un compte Wave par numéro de téléphone
|
||||
*
|
||||
* @param numeroTelephone Numéro de téléphone
|
||||
* @return DTO du compte ou null
|
||||
*/
|
||||
public CompteWaveDTO trouverCompteWaveParTelephone(String numeroTelephone) {
|
||||
return compteWaveRepository
|
||||
.findByNumeroTelephone(numeroTelephone)
|
||||
.map(this::convertToDTO)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les comptes Wave d'une organisation
|
||||
*
|
||||
* @param montant Montant à payer
|
||||
* @param devise Devise (XOF par défaut)
|
||||
* @param successUrl URL de redirection en cas de succès
|
||||
* @param errorUrl URL de redirection en cas d'erreur
|
||||
* @param referenceUnionFlow Référence interne UnionFlow
|
||||
* @param description Description du paiement
|
||||
* @param organisationId ID de l'organisation
|
||||
* @param membreId ID du membre
|
||||
* @return Session de paiement créée
|
||||
* @return Liste des comptes Wave
|
||||
*/
|
||||
public List<CompteWaveDTO> listerComptesWaveParOrganisation(UUID organisationId) {
|
||||
return compteWaveRepository.findByOrganisationId(organisationId).stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle transaction Wave
|
||||
*
|
||||
* @param transactionWaveDTO DTO de la transaction à créer
|
||||
* @return DTO de la transaction créée
|
||||
*/
|
||||
@Transactional
|
||||
public WaveCheckoutSessionDTO creerSessionPaiement(
|
||||
BigDecimal montant,
|
||||
String devise,
|
||||
String successUrl,
|
||||
String errorUrl,
|
||||
String referenceUnionFlow,
|
||||
String description,
|
||||
UUID organisationId,
|
||||
UUID membreId) {
|
||||
public TransactionWaveDTO creerTransactionWave(TransactionWaveDTO transactionWaveDTO) {
|
||||
LOG.infof("Création d'une nouvelle transaction Wave: %s", transactionWaveDTO.getWaveTransactionId());
|
||||
|
||||
TransactionWave transactionWave = convertToEntity(transactionWaveDTO);
|
||||
transactionWave.setCreePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
transactionWaveRepository.persist(transactionWave);
|
||||
LOG.infof(
|
||||
"Création d'une session de paiement Wave: montant=%s, devise=%s, ref=%s",
|
||||
montant, devise, referenceUnionFlow);
|
||||
"Transaction Wave créée avec succès: ID=%s, WaveTransactionId=%s",
|
||||
transactionWave.getId(), transactionWave.getWaveTransactionId());
|
||||
|
||||
try {
|
||||
// TODO: Appel réel à l'API Wave Checkout
|
||||
// Pour l'instant, simulation de la création de session
|
||||
String waveSessionId = "wave_session_" + UUID.randomUUID().toString().replace("-", "");
|
||||
String waveUrl = buildWaveCheckoutUrl(waveSessionId);
|
||||
|
||||
WaveCheckoutSessionDTO session = new WaveCheckoutSessionDTO();
|
||||
session.setId(UUID.randomUUID());
|
||||
session.setWaveSessionId(waveSessionId);
|
||||
session.setWaveUrl(waveUrl);
|
||||
session.setMontant(montant);
|
||||
session.setDevise(devise != null ? devise : "XOF");
|
||||
session.setSuccessUrl(successUrl);
|
||||
session.setErrorUrl(errorUrl);
|
||||
session.setReferenceUnionFlow(referenceUnionFlow);
|
||||
session.setDescription(description);
|
||||
session.setOrganisationId(organisationId);
|
||||
session.setMembreId(membreId);
|
||||
session.setStatut(StatutSession.PENDING);
|
||||
session.setDateCreation(LocalDateTime.now());
|
||||
session.setDateExpiration(LocalDateTime.now().plusHours(24)); // Expire dans 24h
|
||||
|
||||
LOG.infof("Session Wave créée: %s", waveSessionId);
|
||||
return session;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la création de la session Wave: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de la création de la session Wave", e);
|
||||
}
|
||||
return convertToDTO(transactionWave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie le statut d'une session de paiement
|
||||
* Met à jour le statut d'une transaction Wave
|
||||
*
|
||||
* @param waveSessionId ID de la session Wave
|
||||
* @return Statut de la session
|
||||
*/
|
||||
public WaveCheckoutSessionDTO verifierStatutSession(String waveSessionId) {
|
||||
LOG.infof("Vérification du statut de la session Wave: %s", waveSessionId);
|
||||
|
||||
try {
|
||||
// TODO: Appel réel à l'API Wave pour vérifier le statut
|
||||
// Pour l'instant, simulation
|
||||
WaveCheckoutSessionDTO session = new WaveCheckoutSessionDTO();
|
||||
session.setWaveSessionId(waveSessionId);
|
||||
session.setStatut(StatutSession.PENDING);
|
||||
return session;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la vérification du statut: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de la vérification du statut", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite un webhook reçu de Wave
|
||||
*
|
||||
* @param payload Payload JSON du webhook
|
||||
* @param signature Signature Wave pour vérification
|
||||
* @param headers Headers HTTP
|
||||
* @return Webhook traité
|
||||
* @param waveTransactionId Identifiant Wave de la transaction
|
||||
* @param nouveauStatut Nouveau statut
|
||||
* @return DTO de la transaction mise à jour
|
||||
*/
|
||||
@Transactional
|
||||
public WaveWebhookDTO traiterWebhook(String payload, String signature, Map<String, String> headers) {
|
||||
LOG.info("Traitement d'un webhook Wave");
|
||||
public TransactionWaveDTO mettreAJourStatutTransaction(
|
||||
String waveTransactionId, StatutTransactionWave nouveauStatut) {
|
||||
LOG.infof("Mise à jour du statut de la transaction Wave: %s -> %s", waveTransactionId, nouveauStatut);
|
||||
|
||||
try {
|
||||
// Vérifier la signature
|
||||
if (!verifierSignatureWebhook(payload, signature)) {
|
||||
LOG.warn("Signature webhook invalide");
|
||||
throw new SecurityException("Signature webhook invalide");
|
||||
}
|
||||
TransactionWave transactionWave =
|
||||
transactionWaveRepository
|
||||
.findByWaveTransactionId(waveTransactionId)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Transaction Wave non trouvée avec l'ID: " + waveTransactionId));
|
||||
|
||||
// Parser le payload
|
||||
// TODO: Parser réellement le JSON du webhook
|
||||
String webhookId = "webhook_" + UUID.randomUUID().toString();
|
||||
TypeEvenement typeEvenement = TypeEvenement.CHECKOUT_COMPLETE; // À déterminer depuis le payload
|
||||
transactionWave.setStatutTransaction(nouveauStatut);
|
||||
transactionWave.setDateDerniereTentative(LocalDateTime.now());
|
||||
transactionWave.setModifiePar(keycloakService.getCurrentUserEmail());
|
||||
|
||||
WaveWebhookDTO webhook = new WaveWebhookDTO();
|
||||
webhook.setId(UUID.randomUUID());
|
||||
webhook.setWebhookId(webhookId);
|
||||
webhook.setTypeEvenement(typeEvenement);
|
||||
webhook.setCodeEvenement(typeEvenement.getCodeWave());
|
||||
webhook.setPayloadJson(payload);
|
||||
webhook.setSignatureWave(signature);
|
||||
webhook.setDateReception(LocalDateTime.now());
|
||||
webhook.setDateTraitement(LocalDateTime.now());
|
||||
transactionWaveRepository.persist(transactionWave);
|
||||
LOG.infof("Statut de la transaction Wave mis à jour: %s", waveTransactionId);
|
||||
|
||||
// Extraire les informations du payload
|
||||
// TODO: Extraire réellement les données du JSON
|
||||
// webhook.setSessionCheckoutId(...);
|
||||
// webhook.setTransactionWaveId(...);
|
||||
// webhook.setMontantTransaction(...);
|
||||
|
||||
LOG.infof("Webhook traité: %s", webhookId);
|
||||
return webhook;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du traitement du webhook: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors du traitement du webhook", e);
|
||||
}
|
||||
return convertToDTO(transactionWave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consulte le solde du wallet Wave
|
||||
* Trouve une transaction Wave par son identifiant Wave
|
||||
*
|
||||
* @return Solde du wallet
|
||||
* @param waveTransactionId Identifiant Wave
|
||||
* @return DTO de la transaction
|
||||
*/
|
||||
public WaveBalanceDTO consulterSolde() {
|
||||
LOG.info("Consultation du solde Wave");
|
||||
public TransactionWaveDTO trouverTransactionWaveParId(String waveTransactionId) {
|
||||
return transactionWaveRepository
|
||||
.findByWaveTransactionId(waveTransactionId)
|
||||
.map(this::convertToDTO)
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Transaction Wave non trouvée avec l'ID: " + waveTransactionId));
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Appel réel à l'API Wave Balance
|
||||
// Pour l'instant, simulation
|
||||
WaveBalanceDTO balance = new WaveBalanceDTO();
|
||||
balance.setNumeroWallet("wave_wallet_001");
|
||||
balance.setSoldeDisponible(BigDecimal.ZERO);
|
||||
balance.setSoldeEnAttente(BigDecimal.ZERO);
|
||||
balance.setSoldeTotal(BigDecimal.ZERO);
|
||||
balance.setDevise("XOF");
|
||||
balance.setStatutWallet("ACTIVE");
|
||||
balance.setDateDerniereMiseAJour(LocalDateTime.now());
|
||||
balance.setDateDerniereSynchronisation(LocalDateTime.now());
|
||||
// ========================================
|
||||
// MÉTHODES PRIVÉES
|
||||
// ========================================
|
||||
|
||||
return balance;
|
||||
/** Convertit une entité CompteWave en DTO */
|
||||
private CompteWaveDTO convertToDTO(CompteWave compteWave) {
|
||||
if (compteWave == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la consultation du solde: %s", e.getMessage());
|
||||
throw new RuntimeException("Erreur lors de la consultation du solde", e);
|
||||
CompteWaveDTO dto = new CompteWaveDTO();
|
||||
dto.setId(compteWave.getId());
|
||||
dto.setNumeroTelephone(compteWave.getNumeroTelephone());
|
||||
dto.setStatutCompte(compteWave.getStatutCompte());
|
||||
dto.setWaveAccountId(compteWave.getWaveAccountId());
|
||||
dto.setEnvironnement(compteWave.getEnvironnement());
|
||||
dto.setDateDerniereVerification(compteWave.getDateDerniereVerification());
|
||||
dto.setCommentaire(compteWave.getCommentaire());
|
||||
|
||||
if (compteWave.getOrganisation() != null) {
|
||||
dto.setOrganisationId(compteWave.getOrganisation().getId());
|
||||
}
|
||||
if (compteWave.getMembre() != null) {
|
||||
dto.setMembreId(compteWave.getMembre().getId());
|
||||
}
|
||||
|
||||
dto.setDateCreation(compteWave.getDateCreation());
|
||||
dto.setDateModification(compteWave.getDateModification());
|
||||
dto.setActif(compteWave.getActif());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/** Convertit un DTO en entité CompteWave */
|
||||
private CompteWave convertToEntity(CompteWaveDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CompteWave compteWave = new CompteWave();
|
||||
compteWave.setNumeroTelephone(dto.getNumeroTelephone());
|
||||
compteWave.setStatutCompte(dto.getStatutCompte() != null ? dto.getStatutCompte() : StatutCompteWave.NON_VERIFIE);
|
||||
compteWave.setWaveAccountId(dto.getWaveAccountId());
|
||||
compteWave.setEnvironnement(dto.getEnvironnement() != null ? dto.getEnvironnement() : "SANDBOX");
|
||||
compteWave.setDateDerniereVerification(dto.getDateDerniereVerification());
|
||||
compteWave.setCommentaire(dto.getCommentaire());
|
||||
|
||||
// Relations
|
||||
if (dto.getOrganisationId() != null) {
|
||||
Organisation org =
|
||||
organisationRepository
|
||||
.findByIdOptional(dto.getOrganisationId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
|
||||
compteWave.setOrganisation(org);
|
||||
}
|
||||
|
||||
if (dto.getMembreId() != null) {
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByIdOptional(dto.getMembreId())
|
||||
.orElseThrow(
|
||||
() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
|
||||
compteWave.setMembre(membre);
|
||||
}
|
||||
|
||||
return compteWave;
|
||||
}
|
||||
|
||||
/** Met à jour une entité CompteWave à partir d'un DTO */
|
||||
private void updateFromDTO(CompteWave compteWave, CompteWaveDTO dto) {
|
||||
if (dto.getStatutCompte() != null) {
|
||||
compteWave.setStatutCompte(dto.getStatutCompte());
|
||||
}
|
||||
if (dto.getWaveAccountId() != null) {
|
||||
compteWave.setWaveAccountId(dto.getWaveAccountId());
|
||||
}
|
||||
if (dto.getEnvironnement() != null) {
|
||||
compteWave.setEnvironnement(dto.getEnvironnement());
|
||||
}
|
||||
if (dto.getDateDerniereVerification() != null) {
|
||||
compteWave.setDateDerniereVerification(dto.getDateDerniereVerification());
|
||||
}
|
||||
if (dto.getCommentaire() != null) {
|
||||
compteWave.setCommentaire(dto.getCommentaire());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si Wave est configuré et actif
|
||||
*
|
||||
* @return true si Wave est configuré
|
||||
*/
|
||||
public boolean estConfigure() {
|
||||
return waveApiKey.isPresent() && !waveApiKey.get().isEmpty()
|
||||
&& waveApiSecret.isPresent() && !waveApiSecret.get().isEmpty();
|
||||
/** Convertit une entité TransactionWave en DTO */
|
||||
private TransactionWaveDTO convertToDTO(TransactionWave transactionWave) {
|
||||
if (transactionWave == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Teste la connexion à l'API Wave
|
||||
*
|
||||
* @return Résultat du test
|
||||
*/
|
||||
public Map<String, Object> testerConnexion() {
|
||||
LOG.info("Test de connexion à l'API Wave");
|
||||
TransactionWaveDTO dto = new TransactionWaveDTO();
|
||||
dto.setId(transactionWave.getId());
|
||||
dto.setWaveTransactionId(transactionWave.getWaveTransactionId());
|
||||
dto.setWaveRequestId(transactionWave.getWaveRequestId());
|
||||
dto.setWaveReference(transactionWave.getWaveReference());
|
||||
dto.setTypeTransaction(transactionWave.getTypeTransaction());
|
||||
dto.setStatutTransaction(transactionWave.getStatutTransaction());
|
||||
dto.setMontant(transactionWave.getMontant());
|
||||
dto.setFrais(transactionWave.getFrais());
|
||||
dto.setMontantNet(transactionWave.getMontantNet());
|
||||
dto.setCodeDevise(transactionWave.getCodeDevise());
|
||||
dto.setTelephonePayeur(transactionWave.getTelephonePayeur());
|
||||
dto.setTelephoneBeneficiaire(transactionWave.getTelephoneBeneficiaire());
|
||||
dto.setMetadonnees(transactionWave.getMetadonnees());
|
||||
dto.setNombreTentatives(transactionWave.getNombreTentatives());
|
||||
dto.setDateDerniereTentative(transactionWave.getDateDerniereTentative());
|
||||
dto.setMessageErreur(transactionWave.getMessageErreur());
|
||||
|
||||
Map<String, Object> resultat = new HashMap<>();
|
||||
resultat.put("configure", estConfigure());
|
||||
resultat.put("environment", waveEnvironment);
|
||||
resultat.put("baseUrl", waveApiBaseUrl);
|
||||
resultat.put("timestamp", LocalDateTime.now().toString());
|
||||
|
||||
if (!estConfigure()) {
|
||||
resultat.put("statut", "ERREUR");
|
||||
resultat.put("message", "Wave n'est pas configuré (clés API manquantes)");
|
||||
return resultat;
|
||||
if (transactionWave.getCompteWave() != null) {
|
||||
dto.setCompteWaveId(transactionWave.getCompteWave().getId());
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Faire un appel réel à l'API Wave pour tester
|
||||
resultat.put("statut", "OK");
|
||||
resultat.put("message", "Connexion réussie (simulation)");
|
||||
return resultat;
|
||||
dto.setDateCreation(transactionWave.getDateCreation());
|
||||
dto.setDateModification(transactionWave.getDateModification());
|
||||
dto.setActif(transactionWave.getActif());
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du test de connexion: %s", e.getMessage());
|
||||
resultat.put("statut", "ERREUR");
|
||||
resultat.put("message", "Erreur: " + e.getMessage());
|
||||
return resultat;
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
// Méthodes privées
|
||||
|
||||
private String buildWaveCheckoutUrl(String sessionId) {
|
||||
if ("sandbox".equals(waveEnvironment)) {
|
||||
return "https://checkout-sandbox.wave.com/checkout/" + sessionId;
|
||||
} else {
|
||||
return "https://checkout.wave.com/checkout/" + sessionId;
|
||||
}
|
||||
/** Convertit un DTO en entité TransactionWave */
|
||||
private TransactionWave convertToEntity(TransactionWaveDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean verifierSignatureWebhook(String payload, String signature) {
|
||||
if (signature == null || signature.isEmpty()) {
|
||||
return false;
|
||||
TransactionWave transactionWave = new TransactionWave();
|
||||
transactionWave.setWaveTransactionId(dto.getWaveTransactionId());
|
||||
transactionWave.setWaveRequestId(dto.getWaveRequestId());
|
||||
transactionWave.setWaveReference(dto.getWaveReference());
|
||||
transactionWave.setTypeTransaction(dto.getTypeTransaction());
|
||||
transactionWave.setStatutTransaction(
|
||||
dto.getStatutTransaction() != null
|
||||
? dto.getStatutTransaction()
|
||||
: StatutTransactionWave.INITIALISE);
|
||||
transactionWave.setMontant(dto.getMontant());
|
||||
transactionWave.setFrais(dto.getFrais());
|
||||
transactionWave.setMontantNet(dto.getMontantNet());
|
||||
transactionWave.setCodeDevise(dto.getCodeDevise() != null ? dto.getCodeDevise() : "XOF");
|
||||
transactionWave.setTelephonePayeur(dto.getTelephonePayeur());
|
||||
transactionWave.setTelephoneBeneficiaire(dto.getTelephoneBeneficiaire());
|
||||
transactionWave.setMetadonnees(dto.getMetadonnees());
|
||||
transactionWave.setNombreTentatives(dto.getNombreTentatives() != null ? dto.getNombreTentatives() : 0);
|
||||
transactionWave.setDateDerniereTentative(dto.getDateDerniereTentative());
|
||||
transactionWave.setMessageErreur(dto.getMessageErreur());
|
||||
|
||||
// Relation CompteWave
|
||||
if (dto.getCompteWaveId() != null) {
|
||||
CompteWave compteWave =
|
||||
compteWaveRepository
|
||||
.findByIdOptional(dto.getCompteWaveId())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new NotFoundException(
|
||||
"Compte Wave non trouvé avec l'ID: " + dto.getCompteWaveId()));
|
||||
transactionWave.setCompteWave(compteWave);
|
||||
}
|
||||
|
||||
if (!waveWebhookSecret.isPresent() || waveWebhookSecret.get().isEmpty()) {
|
||||
LOG.warn("Secret webhook non configuré, impossible de vérifier la signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Implémenter la vérification réelle de la signature HMAC SHA256
|
||||
// La signature Wave est généralement au format: sha256=<hash>
|
||||
return true; // Pour l'instant, on accepte toutes les signatures
|
||||
return transactionWave;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user