package fr.titionfire.ffsaf.domain.service; import fr.titionfire.ffsaf.data.model.CheckoutModel; import fr.titionfire.ffsaf.data.model.LogModel; import fr.titionfire.ffsaf.data.repository.CheckoutRepository; import fr.titionfire.ffsaf.data.repository.LicenceRepository; import fr.titionfire.ffsaf.rest.client.HelloAssoService; import fr.titionfire.ffsaf.rest.client.dto.CheckoutIntentsRequest; import fr.titionfire.ffsaf.rest.client.dto.CheckoutIntentsResponse; import fr.titionfire.ffsaf.rest.client.dto.CheckoutMetadata; import fr.titionfire.ffsaf.rest.exception.DInternalError; import fr.titionfire.ffsaf.utils.SecurityCtx; import fr.titionfire.ffsaf.utils.Utils; import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.scheduler.Scheduled; import io.smallrye.mutiny.Uni; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.hibernate.reactive.mutiny.Mutiny; import java.util.Calendar; import java.util.Date; import java.util.List; @WithSession @ApplicationScoped public class CheckoutService { @Inject CheckoutRepository repository; @Inject LicenceRepository licenceRepository; @Inject MembreService membreService; @Inject LicenceService licenceService; @Inject LoggerService ls; @RestClient HelloAssoService helloAssoService; @ConfigProperty(name = "frontRootUrl") String frontRootUrl; @ConfigProperty(name = "unitLicencePrice") int unitLicencePrice; @ConfigProperty(name = "helloasso.organizationSlug") String organizationSlug; public Uni canDeleteLicence(long id) { return repository.countByLicenseId(id).invoke(c -> System.out.println(c)).map(count -> count == 0); } public Uni create(List ids, SecurityCtx securityCtx) { return membreService.getByAccountId(securityCtx.getSubject()) .call(membreModel -> Mutiny.fetch(membreModel.getClub())) .chain(membreModel -> { CheckoutModel model = new CheckoutModel(); model.setMembre(membreModel); model.setLicenseIds(ids); model.setPaymentStatus(CheckoutModel.PaymentStatus.UNKNOW); return Panache.withTransaction(() -> repository.persist(model)); }) .chain(checkoutModel -> { CheckoutIntentsRequest request = new CheckoutIntentsRequest(); request.setTotalAmount(unitLicencePrice * checkoutModel.getLicenseIds().size()); request.setInitialAmount(unitLicencePrice * checkoutModel.getLicenseIds().size()); request.setItemName("%d licences %d-%d pour %s".formatted(checkoutModel.getLicenseIds().size(), Utils.getSaison(), Utils.getSaison() + 1, checkoutModel.getMembre().getClub().getName())); request.setBackUrl(frontRootUrl + "/club/member/pay"); request.setErrorUrl(frontRootUrl + "/club/member/pay/error"); request.setReturnUrl(frontRootUrl + "/club/member/pay/return"); request.setContainsDonation(false); request.setPayer(new CheckoutIntentsRequest.Payer(checkoutModel.getMembre().getFname(), checkoutModel.getMembre().getLname(), checkoutModel.getMembre().getEmail())); request.setMetadata(new CheckoutMetadata(checkoutModel.getId())); return helloAssoService.checkout(organizationSlug, request) .call(response -> { checkoutModel.setCheckoutId(response.getId()); return Panache.withTransaction(() -> repository.persist(checkoutModel)); }); }) .onFailure().transform(t -> new DInternalError(t.getMessage())) .map(CheckoutIntentsResponse::getRedirectUrl); } public Uni paymentStatusChange(String state, CheckoutMetadata metadata) { return repository.findById(metadata.getCheckoutDBId()) .chain(checkoutModel -> { CheckoutModel.PaymentStatus newStatus = CheckoutModel.PaymentStatus.valueOf(state.toUpperCase()); Uni uni = Uni.createFrom().nullItem(); if (checkoutModel.getPaymentStatus().equals(newStatus)) return uni; if (newStatus.equals(CheckoutModel.PaymentStatus.AUTHORIZED)) { for (Long id : checkoutModel.getLicenseIds()) { uni = uni.chain(__ -> licenceRepository.findById(id) .onFailure().recoverWithNull() .call(licenceModel -> { if (licenceModel == null) { ls.logAnonymous(LogModel.ActionType.UPDATE, LogModel.ObjectType.Licence, "Fail to save payment for licence (checkout n°" + checkoutModel.getCheckoutId() + ")", "", id); return Uni.createFrom().nullItem(); } ls.logUpdateAnonymous("Paiement de la licence", licenceModel); licenceModel.setPay(true); if (licenceModel.getCertificate() != null && licenceModel.getCertificate() .length() > 3) { if (!licenceModel.isValidate()) ls.logUpdateAnonymous("Validation automatique de la licence", licenceModel); return licenceService.validateLicences(licenceModel); } else { return Panache.withTransaction( () -> licenceRepository.persist(licenceModel)); } })); } } else if (checkoutModel.getPaymentStatus().equals(CheckoutModel.PaymentStatus.AUTHORIZED)) { for (Long id : checkoutModel.getLicenseIds()) { uni = uni.chain(__ -> licenceRepository.findById(id) .onFailure().recoverWithNull() .call(licenceModel -> { if (licenceModel == null) return Uni.createFrom().nullItem(); ls.logUpdateAnonymous("Annulation automatique du paiement de la licence", licenceModel); licenceModel.setPay(false); if (licenceModel.isValidate()) ls.logUpdateAnonymous( "Annulation automatique de la validation de la licence", licenceModel); licenceModel.setValidate(false); return Panache.withTransaction(() -> licenceRepository.persist(licenceModel)); })); } } uni = uni.call(__ -> ls.append()); checkoutModel.setPaymentStatus(newStatus); return uni.chain(__ -> Panache.withTransaction(() -> repository.persist(checkoutModel))); }) .onFailure().invoke(Throwable::printStackTrace) .map(__ -> Response.ok().build()); } @Scheduled(cron = "0 0 * * * ?") Uni everyHours() { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.HOUR, -1); Date dateLimit = calendar.getTime(); return repository.delete("creationDate < ?1 AND (checkoutId IS NULL OR paymentStatus = ?2)", dateLimit, CheckoutModel.PaymentStatus.UNKNOW) .map(__ -> null); } }