haciendo pagos pendientes

This commit is contained in:
2025-12-22 20:41:21 +01:00
parent 4cc47b4249
commit d4120bb486
18 changed files with 1312 additions and 572 deletions

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.8</version> <version>3.5.9</version>
<relativePath /> <!-- lookup parent from repository --> <relativePath /> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.imprimelibros</groupId> <groupId>com.imprimelibros</groupId>

View File

@ -5,6 +5,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -20,15 +21,17 @@ import com.imprimelibros.erp.cart.dto.DireccionCardDTO;
import com.imprimelibros.erp.cart.dto.DireccionShipment; import com.imprimelibros.erp.cart.dto.DireccionShipment;
import com.imprimelibros.erp.cart.dto.UpdateCartRequest; import com.imprimelibros.erp.cart.dto.UpdateCartRequest;
import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.common.Utils;
import com.imprimelibros.erp.common.email.EmailService;
import com.imprimelibros.erp.direcciones.DireccionService; import com.imprimelibros.erp.direcciones.DireccionService;
import com.imprimelibros.erp.externalApi.skApiClient; import com.imprimelibros.erp.externalApi.skApiClient;
import com.imprimelibros.erp.pedidos.Pedido; import com.imprimelibros.erp.pedidos.PedidoRepository;
import com.imprimelibros.erp.pedidos.PedidoService;
import com.imprimelibros.erp.presupuesto.PresupuestoRepository; import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
@Service @Service
public class CartService { public class CartService {
private final EmailService emailService;
private final CartRepository cartRepo; private final CartRepository cartRepo;
private final CartDireccionRepository cartDireccionRepo; private final CartDireccionRepository cartDireccionRepo;
private final CartItemRepository itemRepo; private final CartItemRepository itemRepo;
@ -36,14 +39,13 @@ public class CartService {
private final PresupuestoRepository presupuestoRepo; private final PresupuestoRepository presupuestoRepo;
private final DireccionService direccionService; private final DireccionService direccionService;
private final skApiClient skApiClient; private final skApiClient skApiClient;
private final PedidoService pedidoService;
private final PresupuestoService presupuestoService; private final PresupuestoService presupuestoService;
private final PedidoRepository pedidoRepository;
public CartService(CartRepository cartRepo, CartItemRepository itemRepo, public CartService(CartRepository cartRepo, CartItemRepository itemRepo,
CartDireccionRepository cartDireccionRepo, MessageSource messageSource, CartDireccionRepository cartDireccionRepo, MessageSource messageSource,
PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo, PresupuestoFormatter presupuestoFormatter, PresupuestoRepository presupuestoRepo, PedidoRepository pedidoRepository,
DireccionService direccionService, skApiClient skApiClient, DireccionService direccionService, skApiClient skApiClient,PresupuestoService presupuestoService, EmailService emailService) {
PedidoService pedidoService, PresupuestoService presupuestoService) {
this.cartRepo = cartRepo; this.cartRepo = cartRepo;
this.itemRepo = itemRepo; this.itemRepo = itemRepo;
this.cartDireccionRepo = cartDireccionRepo; this.cartDireccionRepo = cartDireccionRepo;
@ -51,8 +53,9 @@ public class CartService {
this.presupuestoRepo = presupuestoRepo; this.presupuestoRepo = presupuestoRepo;
this.direccionService = direccionService; this.direccionService = direccionService;
this.skApiClient = skApiClient; this.skApiClient = skApiClient;
this.pedidoService = pedidoService;
this.presupuestoService = presupuestoService; this.presupuestoService = presupuestoService;
this.emailService = emailService;
this.pedidoRepository = pedidoRepository;
} }
public Cart findById(Long cartId) { public Cart findById(Long cartId) {
@ -264,7 +267,7 @@ public class CartService {
} }
double totalBeforeDiscount = base + iva4 + iva21 + shipment; double totalBeforeDiscount = base + iva4 + iva21 + shipment;
int fidelizacion = pedidoService.getDescuentoFidelizacion(cart.getUserId()); int fidelizacion = this.getDescuentoFidelizacion(cart.getUserId());
double descuento = totalBeforeDiscount * fidelizacion / 100.0; double descuento = totalBeforeDiscount * fidelizacion / 100.0;
double total = totalBeforeDiscount - descuento; double total = totalBeforeDiscount - descuento;
@ -291,6 +294,27 @@ public class CartService {
return summary; return summary;
} }
public int getDescuentoFidelizacion(Long userId) {
// descuento entre el 1% y el 6% para clientes fidelidad (mas de 1500€ en el
// ultimo año)
Instant haceUnAno = Instant.now().minusSeconds(365 * 24 * 60 * 60);
double totalGastado = pedidoRepository.sumTotalByCreatedByAndCreatedAtAfter(userId, haceUnAno);
if (totalGastado < 1200) {
return 0;
} else if (totalGastado >= 1200 && totalGastado < 1999) {
return 1;
} else if (totalGastado >= 2000 && totalGastado < 2999) {
return 2;
} else if (totalGastado >= 3000 && totalGastado < 3999) {
return 3;
} else if (totalGastado >= 4000 && totalGastado < 4999) {
return 4;
} else if (totalGastado >= 5000) {
return 5;
}
return 0;
}
public Map<String, Object> getCartSummary(Cart cart, Locale locale) { public Map<String, Object> getCartSummary(Cart cart, Locale locale) {
Map<String, Object> raw = getCartSummaryRaw(cart, locale); Map<String, Object> raw = getCartSummaryRaw(cart, locale);
@ -411,184 +435,6 @@ public class CartService {
cartDireccionRepo.deleteByDireccionIdAndCartStatus(direccionId, Cart.Status.ACTIVE); cartDireccionRepo.deleteByDireccionIdAndCartStatus(direccionId, Cart.Status.ACTIVE);
} }
@Transactional
public Long crearPedido(Long cartId, Long dirFactId, Locale locale) {
Cart cart = this.getCartById(cartId);
List<CartItem> items = cart.getItems();
List<Map<String, Object>> presupuestoRequests = new ArrayList<>();
Map<String, Object> presupuestoDireccionesRequest = new HashMap<>();
List<Long> presupuestoIds = new ArrayList<>();
for (Integer i = 0; i < items.size(); i++) {
CartItem item = items.get(i);
Presupuesto pCart = item.getPresupuesto();
// Asegurarnos de trabajar con la entidad gestionada por JPA
Presupuesto p = presupuestoRepo.findById(pCart.getId())
.orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId()));
Map<String, Object> data_to_send = presupuestoService.toSkApiRequest(p, true);
data_to_send.put("createPedido", 0);
// Recuperar el mapa anidado datosCabecera
@SuppressWarnings("unchecked")
Map<String, Object> datosCabecera = (Map<String, Object>) data_to_send.get("datosCabecera");
if (datosCabecera != null) {
Object tituloOriginal = datosCabecera.get("titulo");
datosCabecera.put(
"titulo",
"[" + (i + 1) + "/" + items.size() + "] " + (tituloOriginal != null ? tituloOriginal : ""));
}
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
data_to_send.put("direcciones", direcciones_presupuesto.get("direcciones"));
data_to_send.put("direccionesFP1", direcciones_presupuesto.get("direccionesFP1"));
presupuestoDireccionesRequest.put(p.getId().toString(), direcciones_presupuesto);
Map<String, Object> result = skApiClient.savePresupuesto(data_to_send);
if (result.containsKey("error")) {
System.out.println("Error al guardar presupuesto en SK");
System.out.println("-------------------------");
System.out.println(result.get("error"));
// decide si seguir con otros items o abortar:
// continue; o bien throw ...
continue;
}
Object dataObj = result.get("data");
if (!(dataObj instanceof Map<?, ?> dataRaw)) {
System.out.println("Formato inesperado de 'data' en savePresupuesto: " + result);
continue;
}
@SuppressWarnings("unchecked")
Map<String, Object> dataMap = (Map<String, Object>) dataRaw;
Long presId = ((Number) dataMap.get("id")).longValue();
String skin = ((String) dataMap.get("iskn")).toString();
p.setProveedor("Safekat");
p.setProveedorRef1(skin);
p.setProveedorRef2(presId);
p.setEstado(Presupuesto.Estado.aceptado);
presupuestoRepo.save(p);
presupuestoIds.add(p.getId());
presupuestoRequests.add(dataMap);
}
// Crear el pedido en base a los presupuestos guardados
if (presupuestoRequests.isEmpty()) {
throw new IllegalStateException("No se pudieron guardar los presupuestos en SK.");
} else {
ArrayList<Long> presupuestoSkIds = new ArrayList<>();
for (Map<String, Object> presData : presupuestoRequests) {
Long presId = ((Number) presData.get("id")).longValue();
presupuestoSkIds.add(presId);
}
Map<String, Object> ids = new HashMap<>();
ids.put("presupuesto_ids", presupuestoSkIds);
Long pedidoId = skApiClient.crearPedido(ids);
if (pedidoId == null) {
throw new IllegalStateException("No se pudo crear el pedido en SK.");
}
Pedido pedidoInterno = pedidoService.crearPedido(
presupuestoIds,
presupuestoDireccionesRequest,
dirFactId,
this.getCartSummaryRaw(cart, locale),
"Safekat",
String.valueOf(pedidoId),
cart.getUserId());
return pedidoInterno.getId();
}
}
public Map<String, Object> getDireccionesPresupuesto(Cart cart, Presupuesto presupuesto) {
List<Map<String, Object>> direccionesPresupuesto = new ArrayList<>();
List<Map<String, Object>> direccionesPrueba = new ArrayList<>();
if (cart.getOnlyOneShipment()) {
List<CartDireccion> direcciones = cart.getDirecciones().stream().limit(1).toList();
if (!direcciones.isEmpty()) {
if (presupuesto.getServiciosJson() != null
&& presupuesto.getServiciosJson().contains("deposito-legal")) {
direccionesPresupuesto.add(direcciones.get(0).toSkMap(
presupuesto.getSelectedTirada(),
presupuesto.getPeso(),
direcciones.get(0).getIsPalets(),
false));
direccionesPresupuesto.add(direcciones.get(0).toSkMapDepositoLegal());
}
else {
direccionesPresupuesto.add(direcciones.get(0).toSkMap(
presupuesto.getSelectedTirada(),
presupuesto.getPeso(),
direcciones.get(0).getIsPalets(),
false));
}
if (presupuesto.getServiciosJson() != null
&& presupuesto.getServiciosJson().contains("ejemplar-prueba")) {
direccionesPrueba.add(direcciones.get(0).toSkMap(
1,
presupuesto.getPeso(),
false,
true));
}
Map<String, Object> direccionesRet = new HashMap<>();
direccionesRet.put("direcciones", direccionesPresupuesto);
if (!direccionesPrueba.isEmpty())
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
else {
direccionesRet.put("direccionesFP1", new ArrayList<>());
}
return direccionesRet;
}
} else {
List<CartDireccion> direcciones = cart.getDirecciones().stream()
.filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(presupuesto.getId()))
.toList();
for (CartDireccion cd : direcciones) {
// direccion de ejemplar de prueba
if (cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) {
continue;
}
if (cd.getUnidades() == null || cd.getUnidades() <= 0) {
direccionesPrueba.add(cd.toSkMap(
1,
presupuesto.getPeso(),
false,
true));
} else {
direccionesPresupuesto.add(cd.toSkMap(
cd.getUnidades(),
presupuesto.getPeso(),
cd.getIsPalets(),
false));
}
}
if (presupuesto.getServiciosJson() != null
&& presupuesto.getServiciosJson().contains("deposito-legal")) {
CartDireccion cd = new CartDireccion();
direccionesPresupuesto.add(cd.toSkMapDepositoLegal());
}
}
Map<String, Object> direccionesRet = new HashMap<>();
direccionesRet.put("direcciones", direccionesPresupuesto);
if (!direccionesPrueba.isEmpty())
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
else {
direccionesRet.put("direccionesFP1", new ArrayList<>());
}
return direccionesRet;
}
/*************************************** /***************************************
* MÉTODOS PRIVADOS * MÉTODOS PRIVADOS

View File

@ -13,9 +13,13 @@ import com.imprimelibros.erp.redsys.RedsysService.RedsysNotification;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import com.imprimelibros.erp.payments.repo.WebhookEventRepository; import com.imprimelibros.erp.payments.repo.WebhookEventRepository;
import com.imprimelibros.erp.pedidos.Pedido;
import com.imprimelibros.erp.pedidos.PedidoLinea;
import com.imprimelibros.erp.pedidos.PedidoService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
@Service @Service
@ -28,18 +32,55 @@ public class PaymentService {
private final WebhookEventRepository webhookEventRepo; private final WebhookEventRepository webhookEventRepo;
private final ObjectMapper om = new ObjectMapper(); private final ObjectMapper om = new ObjectMapper();
private final CartService cartService; private final CartService cartService;
private final PedidoService pedidoService;
public PaymentService(PaymentRepository payRepo, public PaymentService(PaymentRepository payRepo,
PaymentTransactionRepository txRepo, PaymentTransactionRepository txRepo,
RefundRepository refundRepo, RefundRepository refundRepo,
RedsysService redsysService, RedsysService redsysService,
WebhookEventRepository webhookEventRepo, CartService cartService) { WebhookEventRepository webhookEventRepo,
CartService cartService,
PedidoService pedidoService) {
this.payRepo = payRepo; this.payRepo = payRepo;
this.txRepo = txRepo; this.txRepo = txRepo;
this.refundRepo = refundRepo; this.refundRepo = refundRepo;
this.redsysService = redsysService; this.redsysService = redsysService;
this.webhookEventRepo = webhookEventRepo; this.webhookEventRepo = webhookEventRepo;
this.cartService = cartService; this.cartService = cartService;
this.pedidoService = pedidoService;
}
public Payment findFailedPaymentByOrderId(Long orderId) {
return payRepo.findByOrderIdAndStatus(orderId, PaymentStatus.failed)
.orElse(null);
}
public Map<String, Long> getPaymentTransactionData(Long paymentId) {
PaymentTransaction tx = txRepo.findByPaymentIdAndType(
paymentId,
PaymentTransactionType.CAPTURE)
.orElse(null);
if (tx == null) {
return null;
}
String resp_payload = tx.getResponsePayload();
try {
ObjectMapper om = new ObjectMapper();
var node = om.readTree(resp_payload);
Long cartId = null;
Long dirFactId = null;
if (node.has("cartId")) {
cartId = node.get("cartId").asLong();
}
if (node.has("dirFactId")) {
dirFactId = node.get("dirFactId").asLong();
}
return Map.of(
"cartId", cartId,
"dirFactId", dirFactId);
} catch (Exception e) {
return null;
}
} }
/** /**
@ -47,14 +88,15 @@ public class PaymentService {
* oficial (ApiMacSha256). * oficial (ApiMacSha256).
*/ */
@Transactional @Transactional
public FormPayload createRedsysPayment(Long cartId, Long dirFactId, Long amountCents, String currency, String method) public FormPayload createRedsysPayment(Long cartId, Long dirFactId, Long amountCents, String currency, String method, Long orderId)
throws Exception { throws Exception {
Payment p = new Payment(); Payment p = new Payment();
p.setOrderId(null); p.setOrderId(orderId);
Cart cart = this.cartService.findById(cartId); Cart cart = this.cartService.findById(cartId);
if (cart != null && cart.getUserId() != null) { if (cart != null && cart.getUserId() != null) {
p.setUserId(cart.getUserId()); p.setUserId(cart.getUserId());
this.cartService.lockCartById(cartId);
} }
p.setCurrency(currency); p.setCurrency(currency);
p.setAmountTotalCents(amountCents); p.setAmountTotalCents(amountCents);
@ -62,10 +104,6 @@ public class PaymentService {
p.setStatus(PaymentStatus.requires_payment_method); p.setStatus(PaymentStatus.requires_payment_method);
p = payRepo.saveAndFlush(p); p = payRepo.saveAndFlush(p);
// ANTES:
// String dsOrder = String.format("%012d", p.getId());
// AHORA: timestamp
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
String dsOrder = String.format("%012d", now % 1_000_000_000_000L); String dsOrder = String.format("%012d", now % 1_000_000_000_000L);
@ -207,16 +245,12 @@ public class PaymentService {
p.setAmountCapturedCents(p.getAmountCapturedCents() + notif.amountCents); p.setAmountCapturedCents(p.getAmountCapturedCents() + notif.amountCents);
p.setAuthorizedAt(LocalDateTime.now()); p.setAuthorizedAt(LocalDateTime.now());
p.setCapturedAt(LocalDateTime.now()); p.setCapturedAt(LocalDateTime.now());
pedidoService.setOrderAsPaid(p.getOrderId());
} else { } else {
p.setStatus(PaymentStatus.failed); p.setStatus(PaymentStatus.failed);
p.setFailedAt(LocalDateTime.now()); p.setFailedAt(LocalDateTime.now());
} pedidoService.markPedidoAsPaymentDenied(p.getOrderId());
if (authorized) {
Long orderId = processOrder(notif.cartId, notif.dirFactId, locale);
if (orderId != null) {
p.setOrderId(orderId);
}
} }
payRepo.save(p); payRepo.save(p);
@ -311,7 +345,7 @@ public class PaymentService {
} }
@Transactional @Transactional
public Payment createBankTransferPayment(Long cartId, Long dirFactId, long amountCents, String currency) { public Payment createBankTransferPayment(Long cartId, Long dirFactId, long amountCents, String currency, Locale locale, Long orderId) {
Payment p = new Payment(); Payment p = new Payment();
p.setOrderId(null); p.setOrderId(null);
@ -326,6 +360,9 @@ public class PaymentService {
p.setAmountTotalCents(amountCents); p.setAmountTotalCents(amountCents);
p.setGateway("bank_transfer"); p.setGateway("bank_transfer");
p.setStatus(PaymentStatus.requires_action); // pendiente de ingreso p.setStatus(PaymentStatus.requires_action); // pendiente de ingreso
if (orderId != null) {
p.setOrderId(orderId);
}
p = payRepo.save(p); p = payRepo.save(p);
// Crear transacción pendiente // Crear transacción pendiente
@ -406,13 +443,17 @@ public class PaymentService {
// ignorar // ignorar
} }
// 4) Procesar el pedido asociado al carrito (si existe) // 4) Procesar el pedido asociado al carrito (si existe) o marcar el pedido como pagado
if (cartId != null) { if(p.getOrderId() != null) {
Long orderId = processOrder(cartId, dirFactId, locale); pedidoService.setOrderAsPaid(p.getOrderId());
}
/*else if (cartId != null) {
// Se procesa el pedido dejando el estado calculado en processOrder
Long orderId = processOrder(cartId, dirFactId, locale, null);
if (orderId != null) { if (orderId != null) {
p.setOrderId(orderId); p.setOrderId(orderId);
} }
} }*/
payRepo.save(p); payRepo.save(p);
} }
@ -508,30 +549,5 @@ public class PaymentService {
return code >= 0 && code <= 99; return code >= 0 && code <= 99;
} }
/**
* Procesa el pedido asociado al carrito:
* - bloquea el carrito
* - crea el pedido a partir del carrito
*
*/
@Transactional
private Long processOrder(Long cartId, Long dirFactId, Locale locale) {
Cart cart = this.cartService.findById(cartId);
if (cart != null) {
// Bloqueamos el carrito
this.cartService.lockCartById(cart.getId());
// Creamos el pedido
Long orderId = this.cartService.crearPedido(cart.getId(), dirFactId, locale);
if (orderId == null) {
return null;
} else {
// envio de correo de confirmacion de pedido podria ir aqui
return orderId;
}
}
return null;
}
} }

View File

@ -2,10 +2,13 @@
package com.imprimelibros.erp.payments.repo; package com.imprimelibros.erp.payments.repo;
import com.imprimelibros.erp.payments.model.Payment; import com.imprimelibros.erp.payments.model.Payment;
import com.imprimelibros.erp.payments.model.PaymentStatus;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional; import java.util.Optional;
public interface PaymentRepository extends JpaRepository<Payment, Long> { public interface PaymentRepository extends JpaRepository<Payment, Long> {
Optional<Payment> findByGatewayAndGatewayOrderId(String gateway, String gatewayOrderId); Optional<Payment> findByGatewayAndGatewayOrderId(String gateway, String gatewayOrderId);
Optional<Payment> findByOrderIdAndStatus(Long orderId, PaymentStatus status);
} }

View File

@ -14,6 +14,10 @@ import java.util.Optional;
public interface PaymentTransactionRepository extends JpaRepository<PaymentTransaction, Long>, JpaSpecificationExecutor<PaymentTransaction> { public interface PaymentTransactionRepository extends JpaRepository<PaymentTransaction, Long>, JpaSpecificationExecutor<PaymentTransaction> {
List<PaymentTransaction> findByGatewayTransactionId(String gatewayTransactionId); List<PaymentTransaction> findByGatewayTransactionId(String gatewayTransactionId);
Optional<PaymentTransaction> findByIdempotencyKey(String idempotencyKey); Optional<PaymentTransaction> findByIdempotencyKey(String idempotencyKey);
Optional<PaymentTransaction> findByPaymentIdAndType(
Long paymentId,
PaymentTransactionType type
);
Optional<PaymentTransaction> findFirstByPaymentIdAndTypeAndStatusOrderByIdDesc( Optional<PaymentTransaction> findFirstByPaymentIdAndTypeAndStatusOrderByIdDesc(
Long paymentId, Long paymentId,
PaymentTransactionType type, PaymentTransactionType type,

View File

@ -10,12 +10,15 @@ import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
public class PedidoLinea { public class PedidoLinea {
public enum Estado { public enum Estado {
aprobado("pedido.estado.aprobado", 1), pendiente_pago("pedido.estado.pendiente_pago", 1),
maquetacion("pedido.estado.maquetacion", 2), procesando_pago("pedido.estado.procesando_pago", 2),
haciendo_ferro("pedido.estado.haciendo_ferro", 3), denegado_pago("pedido.estado.denegado_pago", 3),
ferro_cliente("pedido.estado.ferro_cliente", 4), aprobado("pedido.estado.aprobado", 4),
produccion("pedido.estado.produccion", 5), maquetacion("pedido.estado.maquetacion", 5),
cancelado("pedido.estado.cancelado", 6); haciendo_ferro("pedido.estado.haciendo_ferro", 6),
ferro_cliente("pedido.estado.ferro_cliente", 7),
produccion("pedido.estado.produccion", 8),
cancelado("pedido.estado.cancelado", 9);
private final String messageKey; private final String messageKey;
private final int priority; private final int priority;

View File

@ -11,6 +11,10 @@ import java.util.Map;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import com.imprimelibros.erp.cart.Cart;
import com.imprimelibros.erp.cart.CartDireccion;
import com.imprimelibros.erp.cart.CartItem;
import com.imprimelibros.erp.cart.CartService;
import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.common.Utils;
import com.imprimelibros.erp.direcciones.Direccion; import com.imprimelibros.erp.direcciones.Direccion;
import com.imprimelibros.erp.presupuesto.PresupuestoRepository; import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
@ -30,10 +34,11 @@ public class PedidoService {
private final DireccionService direccionService; private final DireccionService direccionService;
private final UserService userService; private final UserService userService;
private final PresupuestoService presupuestoService; private final PresupuestoService presupuestoService;
private final CartService cartService;
public PedidoService(PedidoRepository pedidoRepository, PedidoLineaRepository pedidoLineaRepository, public PedidoService(PedidoRepository pedidoRepository, PedidoLineaRepository pedidoLineaRepository,
PresupuestoRepository presupuestoRepository, PedidoDireccionRepository pedidoDireccionRepository, PresupuestoRepository presupuestoRepository, PedidoDireccionRepository pedidoDireccionRepository,
DireccionService direccionService, UserService userService, PresupuestoService presupuestoService) { DireccionService direccionService, UserService userService, PresupuestoService presupuestoService, CartService cartService) {
this.pedidoRepository = pedidoRepository; this.pedidoRepository = pedidoRepository;
this.pedidoLineaRepository = pedidoLineaRepository; this.pedidoLineaRepository = pedidoLineaRepository;
this.presupuestoRepository = presupuestoRepository; this.presupuestoRepository = presupuestoRepository;
@ -41,48 +46,22 @@ public class PedidoService {
this.direccionService = direccionService; this.direccionService = direccionService;
this.userService = userService; this.userService = userService;
this.presupuestoService = presupuestoService; this.presupuestoService = presupuestoService;
this.cartService = cartService;
} }
public int getDescuentoFidelizacion(Long userId) {
// descuento entre el 1% y el 6% para clientes fidelidad (mas de 1500€ en el
// ultimo año)
Instant haceUnAno = Instant.now().minusSeconds(365 * 24 * 60 * 60);
double totalGastado = pedidoRepository.sumTotalByCreatedByAndCreatedAtAfter(userId, haceUnAno);
if (totalGastado < 1200) {
return 0;
} else if (totalGastado >= 1200 && totalGastado < 1999) {
return 1;
} else if (totalGastado >= 2000 && totalGastado < 2999) {
return 2;
} else if (totalGastado >= 3000 && totalGastado < 3999) {
return 3;
} else if (totalGastado >= 4000 && totalGastado < 4999) {
return 4;
} else if (totalGastado >= 5000) {
return 5;
}
return 0;
}
/**
* Crea un pedido a partir de:
* - lista de IDs de presupuesto
* - resumen numérico del carrito (getCartSummaryRaw)
* - datos de proveedor
* - usuario que crea el pedido
*/
@Transactional @Transactional
public Pedido crearPedido( public Pedido crearPedido(
List<Long> presupuestoIds, Long cartId,
Map<String, Object> presupuestoDirecciones,
Long direccionFacturacionId, Long direccionFacturacionId,
Map<String, Object> cartSummaryRaw,
String proveedor, String proveedor,
String proveedorRef, String proveedorRef) {
Long userId) {
Pedido pedido = new Pedido(); Pedido pedido = new Pedido();
Cart cart = cartService.getCartById(cartId);
Map<String, Object> cartSummaryRaw = cartService.getCartSummaryRaw(cart, Locale.getDefault());
// Datos económicos (ojo con las claves, son las del summaryRaw) // Datos económicos (ojo con las claves, son las del summaryRaw)
pedido.setBase((Double) cartSummaryRaw.getOrDefault("base", 0.0d)); pedido.setBase((Double) cartSummaryRaw.getOrDefault("base", 0.0d));
pedido.setEnvio((Double) cartSummaryRaw.getOrDefault("shipment", 0.0d)); pedido.setEnvio((Double) cartSummaryRaw.getOrDefault("shipment", 0.0d));
@ -92,10 +71,13 @@ public class PedidoService {
pedido.setTotal((Double) cartSummaryRaw.getOrDefault("total", 0.0d)); pedido.setTotal((Double) cartSummaryRaw.getOrDefault("total", 0.0d));
// Proveedor // Proveedor
pedido.setProveedor(proveedor); if(proveedor != null && proveedorRef != null) {
pedido.setProveedorRef(proveedorRef); pedido.setProveedor(proveedor);
pedido.setProveedorRef(proveedorRef);
}
// Auditoría mínima // Auditoría mínima
Long userId = cart.getUserId();
pedido.setCreatedBy(userService.findById(userId)); pedido.setCreatedBy(userService.findById(userId));
pedido.setCreatedAt(Instant.now()); pedido.setCreatedAt(Instant.now());
pedido.setDeleted(false); pedido.setDeleted(false);
@ -103,8 +85,36 @@ public class PedidoService {
pedido.setUpdatedBy(userService.findById(userId)); pedido.setUpdatedBy(userService.findById(userId));
// Guardamos el pedido // Guardamos el pedido
Pedido saved = pedidoRepository.save(pedido); Pedido pedidoGuardado = pedidoRepository.save(pedido);
List<CartItem> items = cart.getItems();
for (Integer i = 0; i < items.size(); i++) {
CartItem item = items.get(i);
Presupuesto pCart = item.getPresupuesto();
// Asegurarnos de trabajar con la entidad gestionada por JPA
Presupuesto p = presupuestoRepository.findById(pCart.getId())
.orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId()));
p.setEstado(Presupuesto.Estado.aceptado);
presupuestoRepository.save(p);
PedidoLinea linea = new PedidoLinea();
linea.setPedido(pedidoGuardado);
linea.setPresupuesto(p);
linea.setCreatedBy(userId);
linea.setCreatedAt(LocalDateTime.now());
linea.setEstado(PedidoLinea.Estado.pendiente_pago);
linea.setEstadoManual(false);
pedidoLineaRepository.save(linea);
// Guardar las direcciones asociadas a la línea del pedido
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
saveDireccionesPedidoLinea(direcciones_presupuesto, pedidoGuardado, linea, direccionFacturacionId);
}
/*
// Crear líneas del pedido // Crear líneas del pedido
for (Long presupuestoId : presupuestoIds) { for (Long presupuestoId : presupuestoIds) {
Presupuesto presupuesto = presupuestoRepository.getReferenceById(presupuestoId); Presupuesto presupuesto = presupuestoRepository.getReferenceById(presupuestoId);
@ -114,7 +124,11 @@ public class PedidoService {
linea.setPresupuesto(presupuesto); linea.setPresupuesto(presupuesto);
linea.setCreatedBy(userId); linea.setCreatedBy(userId);
linea.setCreatedAt(LocalDateTime.now()); linea.setCreatedAt(LocalDateTime.now());
linea.setEstado(getEstadoInicial(presupuesto)); if(estadoInicial != null){
linea.setEstado(estadoInicial);
} else {
linea.setEstado(getEstadoInicial(presupuesto));
}
linea.setEstadoManual(false); linea.setEstadoManual(false);
pedidoLineaRepository.save(linea); pedidoLineaRepository.save(linea);
@ -125,10 +139,175 @@ public class PedidoService {
saveDireccionesPedidoLinea(direcciones, saved, linea, direccionFacturacionId); saveDireccionesPedidoLinea(direcciones, saved, linea, direccionFacturacionId);
} }
} }
*/
return saved; return pedidoGuardado;
} }
public Boolean markPedidoAsProcesingPayment(Long pedidoId){
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if(pedido == null){
return false;
}
List<PedidoLinea> lineas = pedidoLineaRepository.findByPedidoId(pedidoId);
for (PedidoLinea linea : lineas) {
linea.setEstado(PedidoLinea.Estado.procesando_pago);
pedidoLineaRepository.save(linea);
}
return true;
}
public Boolean markPedidoAsPaymentDenied(Long pedidoId){
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if(pedido == null){
return false;
}
List<PedidoLinea> lineas = pedidoLineaRepository.findByPedidoId(pedidoId);
for (PedidoLinea linea : lineas) {
linea.setEstado(PedidoLinea.Estado.denegado_pago);
pedidoLineaRepository.save(linea);
}
return true;
}
public Boolean markPedidoAsPaid(Long pedidoId){
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if(pedido == null){
return false;
}
List<PedidoLinea> lineas = pedidoLineaRepository.findByPedidoId(pedidoId);
for (PedidoLinea linea : lineas) {
linea.setEstado(this.getEstadoInicial(linea.getPresupuesto()));
pedidoLineaRepository.save(linea);
// Save presupuesto in SK
}
// Save pedido in SK
return true;
}
public Pedido findById(Long pedidoId){
return pedidoRepository.findById(pedidoId).orElse(null);
}
/*
@Transactional
public Long crearPedido(Long cartId, Long dirFactId, Locale locale) {
return crearPedido(cartId, dirFactId, locale, null);
}
@Transactional
// Crear pedido interno (no en el proveedor) a partir del carrito
public Long crearPedido(Long cartId, Long dirFactId, Locale locale, PedidoLinea.Estado estadoInicial) {
Cart cart = cartService.getCartById(cartId);
List<CartItem> items = cart.getItems();
List<Map<String, Object>> presupuestoRequests = new ArrayList<>();
Map<String, Object> presupuestoDireccionesRequest = new HashMap<>();
List<Long> presupuestoIds = new ArrayList<>();
for (Integer i = 0; i < items.size(); i++) {
CartItem item = items.get(i);
Presupuesto pCart = item.getPresupuesto();
// Asegurarnos de trabajar con la entidad gestionada por JPA
Presupuesto p = presupuestoRepository.findById(pCart.getId())
.orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + pCart.getId()));
/*Map<String, Object> data_to_send = presupuestoService.toSkApiRequest(p, true);
data_to_send.put("createPedido", 0);
// Recuperar el mapa anidado datosCabecera
@SuppressWarnings("unchecked")
Map<String, Object> datosCabecera = (Map<String, Object>) data_to_send.get("datosCabecera");
if (datosCabecera != null) {
Object tituloOriginal = datosCabecera.get("titulo");
datosCabecera.put(
"titulo",
"[" + (i + 1) + "/" + items.size() + "] " + (tituloOriginal != null ? tituloOriginal : ""));
}
Map<String, Object> direcciones_presupuesto = this.getDireccionesPresupuesto(cart, p);
data_to_send.put("direcciones", direcciones_presupuesto.get("direcciones"));
data_to_send.put("direccionesFP1", direcciones_presupuesto.get("direccionesFP1"));
presupuestoDireccionesRequest.put(p.getId().toString(), direcciones_presupuesto);
Map<String, Object> result = skApiClient.savePresupuesto(data_to_send);
if (result.containsKey("error")) {
System.out.println("Error al guardar presupuesto en SK");
System.out.println("-------------------------");
System.out.println(result.get("error"));
// decide si seguir con otros items o abortar:
// continue; o bien throw ...
continue;
}
Object dataObj = result.get("data");
if (!(dataObj instanceof Map<?, ?> dataRaw)) {
System.out.println("Formato inesperado de 'data' en savePresupuesto: " + result);
continue;
}
@SuppressWarnings("unchecked")
Map<String, Object> dataMap = (Map<String, Object>) dataRaw;
Long presId = ((Number) dataMap.get("id")).longValue();
String skin = ((String) dataMap.get("iskn")).toString();
p.setProveedor("Safekat");
p.setProveedorRef1(skin);
p.setProveedorRef2(presId);
p.setEstado(Presupuesto.Estado.aceptado);
presupuestoRepo.save(p);
presupuestoIds.add(p.getId());
presupuestoRequests.add(dataMap);
}
// Crear el pedido en base a los presupuestos guardados
if (presupuestoRequests.isEmpty()) {
throw new IllegalStateException("No se pudieron guardar los presupuestos en SK.");
} else {
ArrayList<Long> presupuestoSkIds = new ArrayList<>();
for (Map<String, Object> presData : presupuestoRequests) {
Long presId = ((Number) presData.get("id")).longValue();
presupuestoSkIds.add(presId);
}
Map<String, Object> ids = new HashMap<>();
ids.put("presupuesto_ids", presupuestoSkIds);
Long pedidoId = skApiClient.crearPedido(ids);
if (pedidoId == null) {
throw new IllegalStateException("No se pudo crear el pedido en SK.");
}
Pedido pedidoInterno = pedidoService.crearPedido(
presupuestoIds,
presupuestoDireccionesRequest,
dirFactId,
this.getCartSummaryRaw(cart, locale),
"Safekat",
String.valueOf(pedidoId),
cart.getUserId(),
estadoInicial);
return pedidoInterno.getId();
}
}
*/
/** Lista de los items del pedido preparados para la vista*/ /** Lista de los items del pedido preparados para la vista*/
@Transactional @Transactional
public List<Map<String, Object>> getLineas(Long pedidoId, Locale locale) { public List<Map<String, Object>> getLineas(Long pedidoId, Locale locale) {
@ -160,17 +339,119 @@ public class PedidoService {
return pedidoDireccionRepository.findByPedidoLinea_Id(pedidoLineaId); return pedidoDireccionRepository.findByPedidoLinea_Id(pedidoLineaId);
} }
public Boolean setOrderAsPaid(Long pedidoId) {
Pedido pedido = pedidoRepository.findById(pedidoId).orElse(null);
if (pedido == null) {
return false;
}
List<PedidoLinea> lineas = pedidoLineaRepository.findByPedidoId(pedidoId);
for (PedidoLinea linea : lineas) {
if (linea.getEstado() == Estado.pendiente_pago) {
Presupuesto presupuesto = linea.getPresupuesto();
linea.setEstado(getEstadoInicial(presupuesto));
pedidoLineaRepository.save(linea);
}
}
return true;
}
/*************************** /***************************
* MÉTODOS PRIVADOS * MÉTODOS PRIVADOS
***************************/ ***************************/
// Obtener las direcciones de envío asociadas a un presupuesto en el carrito
private Map<String, Object> getDireccionesPresupuesto(Cart cart, Presupuesto presupuesto) {
List<Map<String, Object>> direccionesPresupuesto = new ArrayList<>();
List<Map<String, Object>> direccionesPrueba = new ArrayList<>();
if (cart.getOnlyOneShipment()) {
List<CartDireccion> direcciones = cart.getDirecciones().stream().limit(1).toList();
if (!direcciones.isEmpty()) {
if (presupuesto.getServiciosJson() != null
&& presupuesto.getServiciosJson().contains("deposito-legal")) {
direccionesPresupuesto.add(direcciones.get(0).toSkMap(
presupuesto.getSelectedTirada(),
presupuesto.getPeso(),
direcciones.get(0).getIsPalets(),
false));
direccionesPresupuesto.add(direcciones.get(0).toSkMapDepositoLegal());
}
else {
direccionesPresupuesto.add(direcciones.get(0).toSkMap(
presupuesto.getSelectedTirada(),
presupuesto.getPeso(),
direcciones.get(0).getIsPalets(),
false));
}
if (presupuesto.getServiciosJson() != null
&& presupuesto.getServiciosJson().contains("ejemplar-prueba")) {
direccionesPrueba.add(direcciones.get(0).toSkMap(
1,
presupuesto.getPeso(),
false,
true));
}
Map<String, Object> direccionesRet = new HashMap<>();
direccionesRet.put("direcciones", direccionesPresupuesto);
if (!direccionesPrueba.isEmpty())
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
else {
direccionesRet.put("direccionesFP1", new ArrayList<>());
}
return direccionesRet;
}
} else {
List<CartDireccion> direcciones = cart.getDirecciones().stream()
.filter(d -> d.getPresupuesto() != null && d.getPresupuesto().getId().equals(presupuesto.getId()))
.toList();
for (CartDireccion cd : direcciones) {
// direccion de ejemplar de prueba
if (cd.getPresupuesto() == null || !cd.getPresupuesto().getId().equals(presupuesto.getId())) {
continue;
}
if (cd.getUnidades() == null || cd.getUnidades() <= 0) {
direccionesPrueba.add(cd.toSkMap(
1,
presupuesto.getPeso(),
false,
true));
} else {
direccionesPresupuesto.add(cd.toSkMap(
cd.getUnidades(),
presupuesto.getPeso(),
cd.getIsPalets(),
false));
}
}
if (presupuesto.getServiciosJson() != null
&& presupuesto.getServiciosJson().contains("deposito-legal")) {
CartDireccion cd = new CartDireccion();
direccionesPresupuesto.add(cd.toSkMapDepositoLegal());
}
}
Map<String, Object> direccionesRet = new HashMap<>();
direccionesRet.put("direcciones", direccionesPresupuesto);
if (!direccionesPrueba.isEmpty())
direccionesRet.put("direccionesFP1", direccionesPrueba.get(0));
else {
direccionesRet.put("direccionesFP1", new ArrayList<>());
}
return direccionesRet;
}
@Transactional @Transactional
private void saveDireccionesPedidoLinea( private void saveDireccionesPedidoLinea(
Map<String, Map<String, Object>> direcciones, Map<String, Object> direcciones,
Pedido pedido, Pedido pedido,
PedidoLinea linea, Long direccionFacturacionId) { PedidoLinea linea, Long direccionFacturacionId) {
// direccion prueba // direccion prueba
if (direcciones.containsKey("direccionesFP1")) { if (direcciones.containsKey("direccionesFP1")) {
try { try {
@SuppressWarnings("unchecked")
Map<String, Object> fp1 = (Map<String, Object>) direcciones.get("direccionesFP1"); Map<String, Object> fp1 = (Map<String, Object>) direcciones.get("direccionesFP1");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
PedidoDireccion direccion = saveDireccion( PedidoDireccion direccion = saveDireccion(

View File

@ -166,9 +166,16 @@ public class PedidosController {
return text; return text;
}) })
.add("actions", pedido -> { .add("actions", pedido -> {
return "<span class=\'badge bg-success btn-view \' data-id=\'" + pedido.getId() String data = "<span class=\'badge bg-success btn-view \' data-id=\'" + pedido.getId()
+ "\' style=\'cursor: pointer;\'>" + "\' style=\'cursor: pointer;\'>"
+ messageSource.getMessage("app.view", null, locale) + "</span>"; + messageSource.getMessage("app.view", null, locale) + "</span>";
List<PedidoLinea> lineas = repoPedidoLinea.findByPedidoId(pedido.getId());
boolean hasDenegadoPago = lineas.stream()
.anyMatch(linea -> PedidoLinea.Estado.denegado_pago.equals(linea.getEstado()));
if (hasDenegadoPago) {
data += " <span class='badge bg-danger btn-pay' data-amount='" + pedido.getTotal() + "' data-id=\\'" + pedido.getId() + "\\' style='cursor: pointer;'>" + messageSource.getMessage("app.pay", null, locale) + "</span>";
}
return data;
}) })
.where(base) .where(base)
.toJson(total); .toJson(total);

View File

@ -1,10 +1,15 @@
package com.imprimelibros.erp.redsys; package com.imprimelibros.erp.redsys;
import com.imprimelibros.erp.cart.Cart;
import com.imprimelibros.erp.common.Utils; import com.imprimelibros.erp.common.Utils;
import com.imprimelibros.erp.payments.PaymentService; import com.imprimelibros.erp.payments.PaymentService;
import com.imprimelibros.erp.payments.model.Payment; import com.imprimelibros.erp.payments.model.Payment;
import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository;
import com.imprimelibros.erp.pedidos.Pedido;
import com.imprimelibros.erp.pedidos.PedidoService;
import com.imprimelibros.erp.redsys.RedsysService.FormPayload; import com.imprimelibros.erp.redsys.RedsysService.FormPayload;
import groovy.util.logging.Log;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@ -27,6 +32,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Controller @Controller
@ -37,13 +43,16 @@ public class RedsysController {
private final MessageSource messageSource; private final MessageSource messageSource;
private final SpringTemplateEngine templateEngine; private final SpringTemplateEngine templateEngine;
private final ServletContext servletContext; private final ServletContext servletContext;
private final PedidoService pedidoService;
public RedsysController(PaymentService paymentService, MessageSource messageSource, public RedsysController(PaymentService paymentService, MessageSource messageSource,
SpringTemplateEngine templateEngine, ServletContext servletContext) { SpringTemplateEngine templateEngine, ServletContext servletContext,
PedidoService pedidoService) {
this.paymentService = paymentService; this.paymentService = paymentService;
this.messageSource = messageSource; this.messageSource = messageSource;
this.templateEngine = templateEngine; this.templateEngine = templateEngine;
this.servletContext = servletContext; this.servletContext = servletContext;
this.pedidoService = pedidoService;
} }
@PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ -55,9 +64,15 @@ public class RedsysController {
HttpServletResponse response, Locale locale) HttpServletResponse response, Locale locale)
throws Exception { throws Exception {
// Creamos el pedido inteno
Pedido order = pedidoService.crearPedido(cartId, dirFactId, null, null);
if ("bank-transfer".equalsIgnoreCase(method)) { if ("bank-transfer".equalsIgnoreCase(method)) {
// 1) Creamos el Payment interno SIN orderId (null) // 1) Creamos el Payment interno SIN orderId (null)
Payment p = paymentService.createBankTransferPayment(cartId, dirFactId, amountCents, "EUR"); Payment p = paymentService.createBankTransferPayment(cartId, dirFactId, amountCents, "EUR", locale, order.getId());
pedidoService.markPedidoAsProcesingPayment(order.getId());
// 1⃣ Crear la "aplicación" web de Thymeleaf (Jakarta) // 1⃣ Crear la "aplicación" web de Thymeleaf (Jakarta)
JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext); JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext);
@ -89,7 +104,102 @@ public class RedsysController {
} }
// Tarjeta o Bizum (Redsys) // Tarjeta o Bizum (Redsys)
FormPayload form = paymentService.createRedsysPayment(cartId, dirFactId, amountCents, "EUR", method); FormPayload form = paymentService.createRedsysPayment(cartId, dirFactId, amountCents, "EUR", method, order.getId());
String html = """
<html><head><meta charset="utf-8"><title>Redirigiendo a Redsys…</title></head>
<body onload="document.forms[0].submit()">
<form action="%s" method="post">
<input type="hidden" name="Ds_SignatureVersion" value="%s"/>
<input type="hidden" name="Ds_MerchantParameters" value="%s"/>
<input type="hidden" name="Ds_Signature" value="%s"/>
<input type="hidden" name="cartId" value="%d"/>
<noscript>
<p>Haz clic en pagar para continuar</p>
<button type="submit">Pagar</button>
</noscript>
</form>
</body></html>
""".formatted(
form.action(),
form.signatureVersion(),
form.merchantParameters(),
form.signature(), cartId);
byte[] body = html.getBytes(StandardCharsets.UTF_8);
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(body);
}
@PostMapping(value = "/reintentar", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ResponseBody
public ResponseEntity<byte[]> reintentarPago(@RequestParam("amountCents") Long amountCents,
@RequestParam("method") String method, @RequestParam("orderId") Long orderId,
HttpServletRequest request,
HttpServletResponse response, Locale locale)
throws Exception {
// Creamos el pedido inteno
Pedido order = pedidoService.findById(orderId);
// Find the payment with orderId = order.getId() and status = failed
Payment failedPayment = paymentService.findFailedPaymentByOrderId(order.getId());
if (failedPayment == null) {
throw new Exception("No se encontró un pago fallido para el pedido " + order.getId());
}
Long cartId = null;
Long dirFactId = null;
// Find payment transaction details from failedPayment if needed
try{
Map<String, Long> transactionDetails = paymentService.getPaymentTransactionData(failedPayment.getId());
cartId = transactionDetails.get("cartId");
dirFactId = transactionDetails.get("dirFactId");
} catch (Exception e) {
throw new Exception("No se pudieron obtener los detalles de la transacción para el pago " + failedPayment.getId());
}
if ("bank-transfer".equalsIgnoreCase(method)) {
// 1) Creamos el Payment interno SIN orderId (null)
Payment p = paymentService.createBankTransferPayment(cartId, dirFactId, amountCents, "EUR", locale, order.getId());
pedidoService.markPedidoAsProcesingPayment(order.getId());
// 1⃣ Crear la "aplicación" web de Thymeleaf (Jakarta)
JakartaServletWebApplication app = JakartaServletWebApplication.buildApplication(servletContext);
// 2⃣ Construir el intercambio web desde request/response
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
IWebExchange exchange = app.buildExchange(request, response);
// 3⃣ Crear el contexto WebContext con Locale
WebContext ctx = new WebContext(exchange, locale);
String importeFormateado = Utils.formatCurrency(amountCents / 100.0, locale);
ctx.setVariable("importe", importeFormateado);
ctx.setVariable("concepto", "TRANSF-" + p.getOrderId());
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
boolean isAuth = auth != null
&& auth.isAuthenticated()
&& !(auth instanceof AnonymousAuthenticationToken);
ctx.setVariable("isAuth", isAuth);
// 3) Renderizamos la plantilla a HTML
String html = templateEngine.process("imprimelibros/pagos/transfer", ctx);
byte[] body = html.getBytes(StandardCharsets.UTF_8);
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(body);
}
// Tarjeta o Bizum (Redsys)
FormPayload form = paymentService.createRedsysPayment(cartId, dirFactId, amountCents, "EUR", method, order.getId());
String html = """ String html = """
<html><head><meta charset="utf-8"><title>Redirigiendo a Redsys…</title></head> <html><head><meta charset="utf-8"><title>Redirigiendo a Redsys…</title></head>

View File

@ -0,0 +1,32 @@
databaseChangeLog:
- changeSet:
id: 0019-add-estados-pago-to-pedidos-lineas
author: jjo
changes:
- modifyDataType:
tableName: pedidos_lineas
columnName: estado
newDataType: >
enum(
'pendiente_pago',
'procesando_pago',
'aprobado',
'maquetacion',
'haciendo_ferro',
'produccion',
'terminado',
'cancelado'
)
rollback:
- modifyDataType:
tableName: pedidos_lineas
columnName: estado
newDataType: >
enum(
'aprobado',
'maquetacion',
'haciendo_ferro',
'produccion',
'terminado',
'cancelado'
)

View File

@ -0,0 +1,35 @@
databaseChangeLog:
- changeSet:
id: 0020-add-estados-pago-to-pedidos-lineas-2
author: jjo
changes:
- modifyDataType:
tableName: pedidos_lineas
columnName: estado
newDataType: >
enum(
'pendiente_pago',
'procesando_pago',
'denegado_pago',
'aprobado',
'maquetacion',
'haciendo_ferro',
'produccion',
'terminado',
'cancelado'
)
rollback:
- modifyDataType:
tableName: pedidos_lineas
columnName: estado
newDataType: >
enum(
'pendiente_pago',
'procesando_pago',
'aprobado',
'maquetacion',
'haciendo_ferro',
'produccion',
'terminado',
'cancelado'
)

View File

@ -35,3 +35,7 @@ databaseChangeLog:
file: db/changelog/changesets/0017-add-fecha-entrega-to-pedidos-lineas.yml file: db/changelog/changesets/0017-add-fecha-entrega-to-pedidos-lineas.yml
- include: - include:
file: db/changelog/changesets/0018-change-presupuesto-ch-3.yml file: db/changelog/changesets/0018-change-presupuesto-ch-3.yml
- include:
file: db/changelog/changesets/0019-add-estados-pago-to-pedidos-lineas.yml
- include:
file: db/changelog/changesets/0020-add-estados-pago-to-pedidos-lineas-2.yml

View File

@ -11,6 +11,7 @@ app.back=Volver
app.eliminar=Eliminar app.eliminar=Eliminar
app.imprimir=Imprimir app.imprimir=Imprimir
app.view=Ver app.view=Ver
app.pay=Pagar
app.acciones.siguiente=Siguiente app.acciones.siguiente=Siguiente
app.acciones.anterior=Anterior app.acciones.anterior=Anterior

View File

@ -17,6 +17,9 @@ checkout.success.payment=Pago realizado con éxito. Gracias por su compra.
checkout.make-payment=Realizar el pago checkout.make-payment=Realizar el pago
checkout.authorization-required=Certifico que tengo los derechos para imprimir los archivos incluidos en mi pedido y me hago responsable en caso de reclamación de los mismos checkout.authorization-required=Certifico que tengo los derechos para imprimir los archivos incluidos en mi pedido y me hago responsable en caso de reclamación de los mismos
pedido.estado.pendiente_pago=Pendiente de pago
pedido.estado.procesando_pago=Procesando pago
pedido.estado.denegado_pago=Pago denegado
pedido.estado.aprobado=Aprobado pedido.estado.aprobado=Aprobado
pedido.estado.maquetacion=Maquetación pedido.estado.maquetacion=Maquetación
pedido.estado.haciendo_ferro=Haciendo ferro pedido.estado.haciendo_ferro=Haciendo ferro

View File

@ -5,4 +5,42 @@ $(() => {
window.location.href = url; window.location.href = url;
}); });
$(document).on('click', '.btn-pay', function () {
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content');
if (window.$ && csrfToken && csrfHeader) {
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader(csrfHeader, csrfToken);
}
});
}
let pedidoId = $(this).data('id');
let amount = $(this).data('amount');
$.ajax({
url: `/pagos/redsys/reintentar`,
method: 'POST',
data: {
amountCents: amount,
orderId: pedidoId
},
success: function (response) {
if (response && response.formHtml) {
$('body').append(response.formHtml);
$('#redsys-payment-form').submit();
} else {
alert('Error al procesar el pago. Por favor, inténtelo de nuevo.');
}
},
error: function () {
alert('Error al procesar el pago. Por favor, inténtelo de nuevo.');
}
});
});
}) })

View File

@ -18,7 +18,7 @@ public class envioCarroTest {
void addPedido(){ void addPedido(){
Locale locale = Locale.forLanguageTag("es-ES"); Locale locale = Locale.forLanguageTag("es-ES");
cartService.crearPedido(carritoId, null, locale); //cartService.crearPedido(carritoId, null, locale);
} }

View File

@ -17,10 +17,10 @@ public class savePresupuestosTest {
@Test @Test
void testGuardarPresupuesto() { void testGuardarPresupuesto() {
Locale locale = new Locale("es", "ES"); Locale locale = new Locale("es", "ES");
Long resultado = cartService.crearPedido(9L, null, locale); //Long resultado = cartService.crearPedido(9L, null, locale);
System.out.println("📦 Presupuesto guardado:"); System.out.println("📦 Presupuesto guardado:");
System.out.println(resultado); //System.out.println(resultado);
// Aquí irían las aserciones para verificar que el presupuesto se guardó correctamente // Aquí irían las aserciones para verificar que el presupuesto se guardó correctamente
} }