From ed32f773a473521c2857b0d374133df6c45aa697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Jim=C3=A9nez?= Date: Tue, 4 Nov 2025 22:03:03 +0100 Subject: [PATCH] haciendo datatables de los pagos --- .../com/imprimelibros/erp/ErpApplication.java | 2 + .../erp/cart/CartCleanupService.java | 28 +++++ .../erp/cart/CartRepository.java | 14 +++ .../imprimelibros/erp/cart/CartService.java | 17 +++ .../com/imprimelibros/erp/common/Utils.java | 10 ++ .../imprimelibros/erp/pagos imprimelibros.zip | Bin 21240 -> 0 bytes .../erp/payments/PaymentController.java | 104 ++++++++++++++++++ .../erp/payments/PaymentService.java | 42 ++++++- .../repo/PaymentTransactionRepository.java | 3 +- .../erp/redsys/RedsysController.java | 39 ++++--- .../erp/redsys/RedsysService.java | 31 +++++- .../0008-update-cart-status-constraint.yml | 28 +++++ src/main/resources/db/changelog/master.yml | 4 +- src/main/resources/i18n/pagos_es.properties | 10 +- src/main/resources/i18n/pedidos_es.properties | 2 + .../static/assets/css/imprimelibros.css | 10 ++ src/main/resources/static/assets/js/app.js | 9 ++ .../js/pages/imprimelibros/pagos/pagos.js | 61 +++++++++- .../imprimelibros/cart/_cartContent.html | 6 +- .../templates/imprimelibros/cart/cart.html | 4 +- .../imprimelibros/checkout/_summary.html | 1 + .../imprimelibros/pagos/gestion-pagos.html | 22 +++- .../imprimelibros/pagos/tabla-redsys.html | 24 ++++ 23 files changed, 434 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/imprimelibros/erp/cart/CartCleanupService.java delete mode 100644 src/main/java/com/imprimelibros/erp/pagos imprimelibros.zip create mode 100644 src/main/resources/db/changelog/changesets/0008-update-cart-status-constraint.yml create mode 100644 src/main/resources/templates/imprimelibros/pagos/tabla-redsys.html diff --git a/src/main/java/com/imprimelibros/erp/ErpApplication.java b/src/main/java/com/imprimelibros/erp/ErpApplication.java index 0e589a8..97471b8 100644 --- a/src/main/java/com/imprimelibros/erp/ErpApplication.java +++ b/src/main/java/com/imprimelibros/erp/ErpApplication.java @@ -3,8 +3,10 @@ package com.imprimelibros.erp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling @ConfigurationPropertiesScan(basePackages = "com.imprimelibros.erp") public class ErpApplication { diff --git a/src/main/java/com/imprimelibros/erp/cart/CartCleanupService.java b/src/main/java/com/imprimelibros/erp/cart/CartCleanupService.java new file mode 100644 index 0000000..28af416 --- /dev/null +++ b/src/main/java/com/imprimelibros/erp/cart/CartCleanupService.java @@ -0,0 +1,28 @@ +package com.imprimelibros.erp.cart; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +public class CartCleanupService { + + private final CartRepository cartRepository; + + public CartCleanupService(CartRepository cartRepository) { + this.cartRepository = cartRepository; + } + + /** + * Ejecuta cada noche a las 2:00 AM + */ + @Transactional + @Scheduled(cron = "0 0 2 * * *") // cada día a las 02:00 + public void markAbandonedCarts() { + LocalDateTime limite = LocalDateTime.now().minusDays(7); + int updated = cartRepository.markOldCartsAsAbandoned(limite); + System.out.println("Carritos abandonados marcados: " + updated); + } +} diff --git a/src/main/java/com/imprimelibros/erp/cart/CartRepository.java b/src/main/java/com/imprimelibros/erp/cart/CartRepository.java index 481ff51..1547391 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartRepository.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartRepository.java @@ -1,8 +1,12 @@ package com.imprimelibros.erp.cart; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; import java.util.Optional; public interface CartRepository extends JpaRepository { @@ -17,5 +21,15 @@ public interface CartRepository extends JpaRepository { where c.id = :id """) Optional findByIdFetchAll(@Param("id") Long id); + + @Modifying + @Transactional + @Query(""" + UPDATE Cart c + SET c.status = 'ABANDONED' + WHERE c.status = 'ACTIVE' + AND c.updatedAt < :limite + """) + int markOldCartsAsAbandoned(LocalDateTime limite); } diff --git a/src/main/java/com/imprimelibros/erp/cart/CartService.java b/src/main/java/com/imprimelibros/erp/cart/CartService.java index f900791..7b53eda 100644 --- a/src/main/java/com/imprimelibros/erp/cart/CartService.java +++ b/src/main/java/com/imprimelibros/erp/cart/CartService.java @@ -53,6 +53,14 @@ public class CartService { this.pedidoService = pedidoService; } + + public Cart findById(Long cartId) { + return cartRepo.findById(cartId) + .orElseThrow(() -> new IllegalArgumentException("Carrito no encontrado")); + } + + + /** Devuelve el carrito activo o lo crea si no existe. */ @Transactional public Cart getOrCreateActiveCart(Long userId) { @@ -136,6 +144,14 @@ public class CartService { cartRepo.save(cart); } + @Transactional + public void lockCartById(Long cartId) { + Cart cart = cartRepo.findById(cartId) + .orElseThrow(() -> new IllegalArgumentException("Carrito no encontrado")); + cart.setStatus(Cart.Status.LOCKED); + cartRepo.save(cart); + } + @Transactional public long countItems(Long userId) { Cart cart = getOrCreateActiveCart(userId); @@ -293,6 +309,7 @@ public class CartService { summary.put("total", Utils.formatCurrency(total, locale)); summary.put("amountCents", Math.round(total * 100)); summary.put("errorShipmentCost", errorShipementCost); + summary.put("cartId", cart.getId()); return summary; } diff --git a/src/main/java/com/imprimelibros/erp/common/Utils.java b/src/main/java/com/imprimelibros/erp/common/Utils.java index a5262b7..d5aae69 100644 --- a/src/main/java/com/imprimelibros/erp/common/Utils.java +++ b/src/main/java/com/imprimelibros/erp/common/Utils.java @@ -4,6 +4,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.security.Principal; import java.text.NumberFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -320,4 +322,12 @@ public class Utils { resumen.put("servicios", serviciosExtras); return resumen; } + + public static String formatDateTime(LocalDateTime dateTime, Locale locale) { + if (dateTime == null) { + return ""; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm", locale); + return dateTime.format(formatter); + } } diff --git a/src/main/java/com/imprimelibros/erp/pagos imprimelibros.zip b/src/main/java/com/imprimelibros/erp/pagos imprimelibros.zip deleted file mode 100644 index a23c4c8a8eaef3a5673a0fdd6c8dfcc95dbd16eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21240 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 diff --git a/src/main/java/com/imprimelibros/erp/payments/PaymentController.java b/src/main/java/com/imprimelibros/erp/payments/PaymentController.java index 85f946d..2cfe1ca 100644 --- a/src/main/java/com/imprimelibros/erp/payments/PaymentController.java +++ b/src/main/java/com/imprimelibros/erp/payments/PaymentController.java @@ -1,9 +1,36 @@ package com.imprimelibros.erp.payments; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import org.springframework.data.jpa.domain.Specification; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.imprimelibros.erp.payments.model.PaymentTransactionStatus.*; + +import com.imprimelibros.erp.common.Utils; +import com.imprimelibros.erp.configuracion.margenes_presupuestos.MargenPresupuesto; +import com.imprimelibros.erp.datatables.DataTable; +import com.imprimelibros.erp.datatables.DataTablesParser; +import com.imprimelibros.erp.datatables.DataTablesRequest; +import com.imprimelibros.erp.datatables.DataTablesResponse; +import com.imprimelibros.erp.payments.model.Payment; +import com.imprimelibros.erp.payments.model.PaymentTransaction; +import com.imprimelibros.erp.payments.model.PaymentTransactionStatus; +import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository; +import com.imprimelibros.erp.users.User; +import com.imprimelibros.erp.users.UserDao; + +import jakarta.servlet.http.HttpServletRequest; + @Controller @@ -11,10 +38,87 @@ import org.springframework.web.bind.annotation.GetMapping; @PreAuthorize("hasRole('SUPERADMIN')") public class PaymentController { + protected final PaymentTransactionRepository repoPaymentTransaction; + protected final UserDao repoUser; + + public PaymentController(PaymentTransactionRepository repoPaymentTransaction, UserDao repoUser) { + this.repoPaymentTransaction = repoPaymentTransaction; + this.repoUser = repoUser; + } + @GetMapping() public String index() { return "imprimelibros/pagos/gestion-pagos"; } + + @GetMapping(value = "datatable/redsys", produces = "application/json") + @ResponseBody + public DataTablesResponse> getDatatableRedsys(HttpServletRequest request,Locale locale) { + + DataTablesRequest dt = DataTablesParser.from(request); + + List searchable = List.of( + ); + + List orderable = List.of( + + ); + + Specification base = Specification.allOf( + (root, query, cb) -> cb.equal(root.get("status"), PaymentTransactionStatus.succeeded)); + + Long total = repoPaymentTransaction.count(base); + + return DataTable + .of(repoPaymentTransaction, PaymentTransaction.class, dt, searchable) // 'searchable' en DataTable.java + // edita columnas "reales": + .orderable(orderable) + .add("created_at", (pago) -> { + return Utils.formatDateTime(pago.getCreatedAt(), locale); + }) + .add("client", (pago) -> { + if (pago.getPayment() != null && pago.getPayment().getUserId() != null) { + Payment payment = pago.getPayment(); + if(payment.getUserId() != null) { + Optional user = repoUser.findById(payment.getUserId()); + return user.map(User::getFullName).orElse(""); + } + return ""; + } else { + return ""; + } + }) + .add("gateway_order_id", (pago) -> { + if (pago.getPayment() != null) { + return pago.getPayment().getGatewayOrderId(); + } else { + return ""; + } + }) + .add("orderId", (pago) -> { + if (pago.getPayment() != null && pago.getPayment().getOrderId() != null) { + return pago.getPayment().getOrderId().toString(); + } else { + return ""; + } + }) + .add("amount_cents", (pago) -> { + return Utils.formatCurrency(pago.getAmountCents() / 100.0, locale); + }) + .add("actions", (pago) -> { + return "
\n" + + " \n" + + " \n" + + "
"; + }) + .where(base) + // Filtros custom: + .toJson(total); + + } + } diff --git a/src/main/java/com/imprimelibros/erp/payments/PaymentService.java b/src/main/java/com/imprimelibros/erp/payments/PaymentService.java index bba6420..0b98dc5 100644 --- a/src/main/java/com/imprimelibros/erp/payments/PaymentService.java +++ b/src/main/java/com/imprimelibros/erp/payments/PaymentService.java @@ -1,6 +1,8 @@ package com.imprimelibros.erp.payments; import com.fasterxml.jackson.databind.ObjectMapper; +import com.imprimelibros.erp.cart.Cart; +import com.imprimelibros.erp.cart.CartService; import com.imprimelibros.erp.payments.model.*; import com.imprimelibros.erp.payments.repo.PaymentRepository; import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository; @@ -25,28 +27,36 @@ public class PaymentService { private final RedsysService redsysService; private final WebhookEventRepository webhookEventRepo; private final ObjectMapper om = new ObjectMapper(); + private final CartService cartService; public PaymentService(PaymentRepository payRepo, PaymentTransactionRepository txRepo, RefundRepository refundRepo, RedsysService redsysService, - WebhookEventRepository webhookEventRepo) { + WebhookEventRepository webhookEventRepo, CartService cartService) { this.payRepo = payRepo; this.txRepo = txRepo; this.refundRepo = refundRepo; this.redsysService = redsysService; this.webhookEventRepo = webhookEventRepo; + this.cartService = cartService; } + /** * Crea el Payment en BD y construye el formulario de Redsys usando la API * oficial (ApiMacSha256). */ @Transactional - public FormPayload createRedsysPayment(Long orderId, long amountCents, String currency, String method) + public FormPayload createRedsysPayment(Long cartId, long amountCents, String currency, String method) throws Exception { Payment p = new Payment(); - p.setOrderId(orderId); + p.setOrderId(null); + + Cart cart = this.cartService.findById(cartId); + if(cart != null && cart.getUserId() != null) { + p.setUserId(cart.getUserId()); + } p.setCurrency(currency); p.setAmountTotalCents(amountCents); p.setGateway("redsys"); @@ -64,7 +74,7 @@ public class PaymentService { payRepo.save(p); RedsysService.PaymentRequest req = new RedsysService.PaymentRequest(dsOrder, amountCents, - "Compra en Imprimelibros"); + "Compra en Imprimelibros", cartId); if ("bizum".equalsIgnoreCase(method)) { return redsysService.buildRedirectFormBizum(req); @@ -187,8 +197,22 @@ public class PaymentService { p.setStatus(PaymentStatus.failed); p.setFailedAt(LocalDateTime.now()); } + + if(authorized) { + // GENERAR PEDIDO A PARTIR DEL CARRITO + Cart cart = this.cartService.findById(notif.cartId); + if(cart != null) { + // Bloqueamos el carrito + this.cartService.lockCartById(cart.getId()); + // order ID es generado dentro de createOrderFromCart donde se marcan los presupuestos como no editables + // Long orderId = this.cartService.pedidoService.createOrderFromCart(cart.getId(), p.getId()); + // p.setOrderId(orderId); + + } + } payRepo.save(p); + if (!authorized) { ev.setLastError("Payment declined (Ds_Response=" + notif.response + ")"); @@ -262,9 +286,15 @@ public class PaymentService { } @Transactional - public Payment createBankTransferPayment(Long orderId, long amountCents, String currency) { + public Payment createBankTransferPayment(Long cartId, long amountCents, String currency) { Payment p = new Payment(); - p.setOrderId(orderId); + p.setOrderId(null); + + Cart cart = this.cartService.findById(cartId); + if(cart != null && cart.getUserId() != null) { + p.setUserId(cart.getUserId()); + } + p.setCurrency(currency); p.setAmountTotalCents(amountCents); p.setGateway("bank_transfer"); diff --git a/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java b/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java index aac12ad..e0eb955 100644 --- a/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java +++ b/src/main/java/com/imprimelibros/erp/payments/repo/PaymentTransactionRepository.java @@ -6,10 +6,11 @@ import com.imprimelibros.erp.payments.model.PaymentTransactionStatus; import com.imprimelibros.erp.payments.model.PaymentTransactionType; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.Optional; -public interface PaymentTransactionRepository extends JpaRepository { +public interface PaymentTransactionRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByGatewayTransactionId(String gatewayTransactionId); Optional findByIdempotencyKey(String idempotencyKey); Optional findFirstByPaymentIdAndTypeAndStatusOrderByIdDesc( diff --git a/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java b/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java index b0bcf07..56a9e61 100644 --- a/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java +++ b/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java @@ -4,12 +4,16 @@ import com.imprimelibros.erp.payments.PaymentService; import com.imprimelibros.erp.payments.model.Payment; import com.imprimelibros.erp.redsys.RedsysService.FormPayload; +import org.springframework.context.MessageSource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.nio.charset.StandardCharsets; +import java.util.Locale; import java.util.UUID; @Controller @@ -17,19 +21,21 @@ import java.util.UUID; public class RedsysController { private final PaymentService paymentService; + private final MessageSource messageSource; - public RedsysController(PaymentService paymentService) { + public RedsysController(PaymentService paymentService, MessageSource messageSource) { this.paymentService = paymentService; + this.messageSource = messageSource; } @PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @ResponseBody public ResponseEntity crearPago(@RequestParam("amountCents") Long amountCents, - @RequestParam("method") String method) throws Exception { + @RequestParam("method") String method, @RequestParam("cartId") Long cartId) throws Exception { if ("bank-transfer".equalsIgnoreCase(method)) { // 1) Creamos el Payment interno SIN orderId (null) - Payment p = paymentService.createBankTransferPayment(null, amountCents, "EUR"); + Payment p = paymentService.createBankTransferPayment(cartId, amountCents, "EUR"); // 2) Mostramos instrucciones de transferencia String html = """ @@ -55,7 +61,7 @@ public class RedsysController { } // Tarjeta o Bizum (Redsys) - FormPayload form = paymentService.createRedsysPayment(null, amountCents, "EUR", method); + FormPayload form = paymentService.createRedsysPayment(cartId, amountCents, "EUR", method); String html = """ Redirigiendo a Redsys… @@ -64,6 +70,7 @@ public class RedsysController { +