257 lines
10 KiB
Java
257 lines
10 KiB
Java
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;
|
|
@Inject
|
|
EmailService emailService;
|
|
|
|
@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);
|
|
try {
|
|
emailService.sendPaymentConfirmationEmail(
|
|
manager.getEmail(),
|
|
manager.getFirstName(),
|
|
establishment.getName(),
|
|
sub.getAmountXof() != null ? sub.getAmountXof() : 0,
|
|
sub.getPlan() != null ? sub.getPlan() : "MONTHLY"
|
|
);
|
|
} catch (Exception e) {
|
|
LOG.warn("Envoi email confirmation paiement échoué: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
} else if ("payment.refunded".equals(eventType)) {
|
|
sub.setStatus(EstablishmentSubscription.STATUS_CANCELLED);
|
|
subscriptionRepository.persist(sub);
|
|
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: remboursement traité, établissement désactivé 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);
|
|
if ("payment.failed".equals(eventType)) {
|
|
try {
|
|
emailService.sendPaymentFailureEmail(
|
|
manager.getEmail(),
|
|
manager.getFirstName(),
|
|
establishment.getName()
|
|
);
|
|
} catch (Exception e) {
|
|
LOG.warn("Envoi email échec paiement échoué: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
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.refunded".equals(eventType)) {
|
|
payment.setStatus(EstablishmentPayment.STATUS_CANCELLED);
|
|
} else if ("payment.failed".equals(eventType)) {
|
|
payment.setStatus(EstablishmentPayment.STATUS_FAILED);
|
|
}
|
|
paymentRepository.persist(payment);
|
|
});
|
|
}
|
|
}
|