feat: v2.0 – réorg docker/scripts, prod, résas, abonnements Wave, Flyway base vierge
This commit is contained in:
216
src/main/java/com/lions/dev/service/WavePaymentService.java
Normal file
216
src/main/java/com/lions/dev/service/WavePaymentService.java
Normal file
@@ -0,0 +1,216 @@
|
||||
package com.lions.dev.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.lions.dev.dto.response.establishment.InitiateSubscriptionResponseDTO;
|
||||
import com.lions.dev.entity.establishment.Establishment;
|
||||
import com.lions.dev.entity.establishment.EstablishmentPayment;
|
||||
import com.lions.dev.entity.establishment.EstablishmentSubscription;
|
||||
import com.lions.dev.entity.users.Users;
|
||||
import com.lions.dev.repository.EstablishmentPaymentRepository;
|
||||
import com.lions.dev.repository.EstablishmentRepository;
|
||||
import com.lions.dev.repository.EstablishmentSubscriptionRepository;
|
||||
import com.lions.dev.repository.UsersRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service d'intégration Wave pour le paiement des droits d'accès des établissements.
|
||||
* Crée une session de paiement Wave et traite les webhooks (payment.completed, etc.).
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class WavePaymentService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(WavePaymentService.class);
|
||||
|
||||
private static final int MONTHLY_AMOUNT_XOF = 15_000;
|
||||
private static final int YEARLY_AMOUNT_XOF = 150_000;
|
||||
|
||||
@Inject
|
||||
EstablishmentRepository establishmentRepository;
|
||||
@Inject
|
||||
EstablishmentSubscriptionRepository subscriptionRepository;
|
||||
@Inject
|
||||
EstablishmentPaymentRepository paymentRepository;
|
||||
@Inject
|
||||
UsersRepository usersRepository;
|
||||
|
||||
@ConfigProperty(name = "wave.api.url", defaultValue = "https://api.wave.com")
|
||||
String waveApiUrl;
|
||||
|
||||
@ConfigProperty(name = "wave.api.key", defaultValue = "")
|
||||
Optional<String> waveApiKey;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Initie un paiement Wave pour un établissement (droits d'accès).
|
||||
* Crée une session Wave et retourne l'URL de redirection.
|
||||
*/
|
||||
@Transactional
|
||||
public InitiateSubscriptionResponseDTO initiatePayment(UUID establishmentId, String plan, String clientPhone) {
|
||||
Establishment establishment = establishmentRepository.findById(establishmentId);
|
||||
if (establishment == null) {
|
||||
throw new IllegalArgumentException("Établissement non trouvé : " + establishmentId);
|
||||
}
|
||||
|
||||
int amountXof = EstablishmentSubscription.PLAN_MONTHLY.equals(plan) ? MONTHLY_AMOUNT_XOF : YEARLY_AMOUNT_XOF;
|
||||
String description = "AfterWork - Abonnement " + plan + " - " + establishment.getName();
|
||||
|
||||
EstablishmentSubscription subscription = new EstablishmentSubscription();
|
||||
subscription.setEstablishmentId(establishmentId);
|
||||
subscription.setPlan(plan);
|
||||
subscription.setStatus(EstablishmentSubscription.STATUS_PENDING);
|
||||
subscription.setAmountXof(amountXof);
|
||||
subscriptionRepository.persist(subscription);
|
||||
|
||||
EstablishmentPayment payment = new EstablishmentPayment();
|
||||
payment.setEstablishmentId(establishmentId);
|
||||
payment.setAmountXof(amountXof);
|
||||
payment.setStatus(EstablishmentPayment.STATUS_PENDING);
|
||||
payment.setClientPhone(clientPhone);
|
||||
payment.setPlan(plan);
|
||||
paymentRepository.persist(payment);
|
||||
|
||||
String waveSessionId = null;
|
||||
String paymentUrl = null;
|
||||
|
||||
String apiKey = waveApiKey.orElse("");
|
||||
if (!apiKey.isBlank()) {
|
||||
try {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("amount", amountXof);
|
||||
body.put("currency", "XOF");
|
||||
body.put("description", description);
|
||||
body.put("client_reference", subscription.getId().toString());
|
||||
body.put("customer_phone_number", clientPhone.startsWith("+") ? clientPhone : "+" + clientPhone);
|
||||
|
||||
String bodyJson = objectMapper.writeValueAsString(body);
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(waveApiUrl + "/wave/api/v1/checkout/sessions"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.timeout(Duration.ofSeconds(15))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(bodyJson))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() >= 200 && response.statusCode() < 300) {
|
||||
JsonNode node = objectMapper.readTree(response.body());
|
||||
waveSessionId = node.has("id") ? node.get("id").asText() : null;
|
||||
paymentUrl = node.has("payment_url") ? node.get("payment_url").asText() : null;
|
||||
} else {
|
||||
LOG.warn("Wave API error: " + response.statusCode() + " " + response.body());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Erreur appel Wave API", e);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Wave API key non configurée : utilisation d'une URL de test");
|
||||
paymentUrl = "https://checkout.wave.com/session/test?ref=" + subscription.getId();
|
||||
waveSessionId = "test-" + subscription.getId();
|
||||
}
|
||||
|
||||
subscription.setWaveSessionId(waveSessionId);
|
||||
payment.setWaveSessionId(waveSessionId);
|
||||
subscriptionRepository.persist(subscription);
|
||||
paymentRepository.persist(payment);
|
||||
|
||||
return new InitiateSubscriptionResponseDTO(
|
||||
paymentUrl != null ? paymentUrl : "",
|
||||
waveSessionId,
|
||||
amountXof,
|
||||
plan,
|
||||
EstablishmentSubscription.STATUS_PENDING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un établissement a un abonnement actif (droits d'accès payés).
|
||||
*/
|
||||
public boolean hasActiveSubscription(UUID establishmentId) {
|
||||
return subscriptionRepository.findActiveByEstablishmentId(establishmentId).isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite un webhook Wave (payment.completed, payment.cancelled, etc.).
|
||||
* Met à jour l'abonnement et le paiement.
|
||||
*/
|
||||
@Transactional
|
||||
public void handleWebhook(JsonNode payload) {
|
||||
String eventType = payload.has("type") ? payload.get("type").asText() : null;
|
||||
JsonNode data = payload.has("data") ? payload.get("data") : null;
|
||||
if (data == null) return;
|
||||
|
||||
String sessionId = data.has("id") ? data.get("id").asText() : (data.has("session_id") ? data.get("session_id").asText() : null);
|
||||
if (sessionId == null) return;
|
||||
|
||||
subscriptionRepository.findByWaveSessionId(sessionId).ifPresent(sub -> {
|
||||
UUID establishmentId = sub.getEstablishmentId();
|
||||
Establishment establishment = establishmentRepository.findById(establishmentId);
|
||||
|
||||
if ("payment.completed".equals(eventType)) {
|
||||
sub.setStatus(EstablishmentSubscription.STATUS_ACTIVE);
|
||||
sub.setPaidAt(LocalDateTime.now());
|
||||
if (EstablishmentSubscription.PLAN_MONTHLY.equals(sub.getPlan())) {
|
||||
sub.setExpiresAt(LocalDateTime.now().plusMonths(1));
|
||||
} else {
|
||||
sub.setExpiresAt(LocalDateTime.now().plusYears(1));
|
||||
}
|
||||
subscriptionRepository.persist(sub);
|
||||
// Activer l'établissement et le manager
|
||||
if (establishment != null) {
|
||||
establishment.setIsActive(true);
|
||||
establishmentRepository.persist(establishment);
|
||||
Users manager = establishment.getManager();
|
||||
if (manager != null) {
|
||||
manager.setActive(true);
|
||||
usersRepository.persist(manager);
|
||||
LOG.info("Webhook Wave: établissement et manager activés pour " + establishmentId);
|
||||
}
|
||||
}
|
||||
} else if ("payment.cancelled".equals(eventType) || "payment.expired".equals(eventType)
|
||||
|| "payment.failed".equals(eventType)) {
|
||||
sub.setStatus(EstablishmentSubscription.STATUS_CANCELLED);
|
||||
subscriptionRepository.persist(sub);
|
||||
// Suspendre l'établissement et le manager
|
||||
if (establishment != null) {
|
||||
establishment.setIsActive(false);
|
||||
establishmentRepository.persist(establishment);
|
||||
Users manager = establishment.getManager();
|
||||
if (manager != null) {
|
||||
manager.setActive(false);
|
||||
usersRepository.persist(manager);
|
||||
LOG.info("Webhook Wave: établissement et manager suspendus pour " + establishmentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
paymentRepository.findByWaveSessionId(sessionId).ifPresent(payment -> {
|
||||
if ("payment.completed".equals(eventType)) {
|
||||
payment.setStatus(EstablishmentPayment.STATUS_COMPLETED);
|
||||
} else if ("payment.cancelled".equals(eventType) || "payment.expired".equals(eventType)) {
|
||||
payment.setStatus(EstablishmentPayment.STATUS_CANCELLED);
|
||||
}
|
||||
paymentRepository.persist(payment);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user