package dev.lions.unionflow.server.resource; import dev.lions.unionflow.server.api.payment.*; import dev.lions.unionflow.server.entity.SouscriptionOrganisation; import dev.lions.unionflow.server.payment.orchestration.PaymentOrchestrator; import dev.lions.unionflow.server.payment.orchestration.PaymentProviderRegistry; import dev.lions.unionflow.server.repository.SouscriptionOrganisationRepository; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; /** * Endpoints de paiement unifiés — abstraction multi-provider. * Remplace à terme les endpoints Wave-spécifiques. */ @Slf4j @Path("/api/paiements") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class PaiementUnifieResource { @Inject PaymentOrchestrator orchestrator; @Inject PaymentProviderRegistry registry; @Inject SouscriptionOrganisationRepository souscriptionRepository; /** * Initie un paiement via le provider demandé (ou le provider par défaut). * *
Exemple : {@code POST /api/paiements/initier?provider=WAVE}
*/
@POST
@Path("/initier")
@RolesAllowed({"MEMBRE_ACTIF", "ADMIN_ORGANISATION", "TRESORIER", "SUPER_ADMIN"})
public Response initier(
@QueryParam("provider") String provider,
PaiementInitierRequest req) {
try {
// Si une souscription est fournie, utiliser le providerDefaut de sa formule
String resolvedProvider = provider;
if (req.souscriptionId() != null) {
resolvedProvider = souscriptionRepository.findByIdOptional(req.souscriptionId())
.map(SouscriptionOrganisation::getFormule)
.map(f -> f.getProviderDefaut())
.filter(p -> p != null && !p.isBlank())
.orElse(provider);
}
CheckoutRequest checkoutRequest = new CheckoutRequest(
req.montant(),
req.devise() != null ? req.devise() : "XOF",
req.telephone(),
req.email(),
req.reference(),
req.successUrl(),
req.cancelUrl(),
Map.of()
);
CheckoutSession session = orchestrator.initierPaiement(checkoutRequest, resolvedProvider);
return Response.ok(session).build();
} catch (PaymentException e) {
return Response.status(e.getHttpStatus())
.entity(Map.of("error", e.getMessage(), "provider", e.getProviderCode()))
.build();
}
}
/**
* Webhook entrant d'un provider. Vérifie la signature et met à jour le statut.
* Route : {@code POST /api/paiements/webhook/{provider}}
*/
@POST
@Path("/webhook/{provider}")
@PermitAll
@Consumes(MediaType.WILDCARD)
public Response webhook(
@PathParam("provider") String providerCode,
String rawBody,
@Context HttpHeaders httpHeaders) {
try {
PaymentProvider provider = registry.get(providerCode.toUpperCase());
Map