mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-12 16:38:48 +00:00
312 lines
14 KiB
Java
312 lines
14 KiB
Java
package com.imprimelibros.erp.redsys;
|
||
|
||
import com.imprimelibros.erp.cart.Cart;
|
||
import com.imprimelibros.erp.common.Utils;
|
||
import com.imprimelibros.erp.payments.PaymentService;
|
||
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 groovy.util.logging.Log;
|
||
import jakarta.servlet.ServletContext;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
|
||
import org.thymeleaf.context.WebContext;
|
||
import org.thymeleaf.web.IWebExchange;
|
||
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
|
||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||
import org.springframework.security.core.Authentication;
|
||
import org.springframework.security.core.context.SecurityContextHolder;
|
||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||
|
||
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.Map;
|
||
import java.util.UUID;
|
||
|
||
@Controller
|
||
@RequestMapping("/pagos/redsys")
|
||
public class RedsysController {
|
||
|
||
private final PaymentService paymentService;
|
||
private final MessageSource messageSource;
|
||
private final SpringTemplateEngine templateEngine;
|
||
private final ServletContext servletContext;
|
||
private final PedidoService pedidoService;
|
||
|
||
public RedsysController(PaymentService paymentService, MessageSource messageSource,
|
||
SpringTemplateEngine templateEngine, ServletContext servletContext,
|
||
PedidoService pedidoService) {
|
||
this.paymentService = paymentService;
|
||
this.messageSource = messageSource;
|
||
this.templateEngine = templateEngine;
|
||
this.servletContext = servletContext;
|
||
this.pedidoService = pedidoService;
|
||
}
|
||
|
||
@PostMapping(value = "/crear", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||
@ResponseBody
|
||
public ResponseEntity<byte[]> crearPago(@RequestParam("amountCents") Long amountCents,
|
||
@RequestParam("method") String method, @RequestParam("cartId") Long cartId,
|
||
@RequestParam(value = "dirFactId", required = false) Long dirFactId,
|
||
HttpServletRequest request,
|
||
HttpServletResponse response, Locale locale)
|
||
throws Exception {
|
||
|
||
// Creamos el pedido inteno
|
||
Pedido order = pedidoService.crearPedido(cartId, dirFactId, null, null);
|
||
|
||
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 = """
|
||
<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 = """
|
||
<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);
|
||
}
|
||
|
||
// GET: cuando el usuario cae aquí sin parámetros, o Redsys redirige por GET
|
||
@GetMapping("/ok")
|
||
public String okGet(RedirectAttributes redirectAttrs, Model model, Locale locale) {
|
||
return "imprimelibros/pagos/pago-ok";
|
||
}
|
||
|
||
// POST: si Redsys envía Ds_Signature y Ds_MerchantParameters (muchas
|
||
// integraciones ni lo usan)
|
||
@PostMapping(value = "/ok", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||
@ResponseBody
|
||
public ResponseEntity<String> okPost(@RequestParam("Ds_Signature") String signature,
|
||
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||
try {
|
||
// opcional: idempotente, si /notify ya ha hecho el trabajo no pasa nada
|
||
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||
return ResponseEntity.ok("<h2>Pago realizado correctamente</h2><a href=\"/cart\">Volver</a>");
|
||
} catch (Exception e) {
|
||
return ResponseEntity.badRequest()
|
||
.body("<h2>Error validando pago</h2><pre>" + e.getMessage() + "</pre>");
|
||
}
|
||
}
|
||
|
||
@GetMapping("/ko")
|
||
public String koGet(RedirectAttributes redirectAttrs, Model model, Locale locale) {
|
||
|
||
String msg = messageSource.getMessage("checkout.error.payment", null,
|
||
"Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo.",
|
||
locale);
|
||
model.addAttribute("errorPago", msg);
|
||
redirectAttrs.addFlashAttribute("errorPago", msg);
|
||
return "redirect:/cart";
|
||
}
|
||
|
||
@PostMapping(value = "/ko", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||
@ResponseBody
|
||
public ResponseEntity<String> koPost(
|
||
@RequestParam("Ds_Signature") String signature,
|
||
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||
|
||
try {
|
||
// Procesamos la notificación IGUAL que en /ok y /notify
|
||
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||
|
||
// Mensaje para el usuario (pago cancelado/rechazado)
|
||
String html = "<h2>Pago cancelado o rechazado</h2><a href=\"/checkout\">Volver</a>";
|
||
return ResponseEntity.ok(html);
|
||
} catch (Exception e) {
|
||
// Si algo falla al validar/procesar, lo mostramos (útil en entorno de pruebas)
|
||
String html = "<h2>Error procesando notificación KO</h2><pre>" + e.getMessage() + "</pre>";
|
||
return ResponseEntity.badRequest().body(html);
|
||
}
|
||
}
|
||
|
||
@PostMapping(value = "/notify", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||
@ResponseBody
|
||
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
||
@RequestParam("Ds_MerchantParameters") String merchantParameters, Locale locale) {
|
||
try {
|
||
paymentService.handleRedsysNotification(signature, merchantParameters, locale);
|
||
return "OK";
|
||
} catch (Exception e) {
|
||
e.printStackTrace(); // 👈 para ver el motivo del 500 en logs
|
||
return "ERROR";
|
||
}
|
||
}
|
||
|
||
@PostMapping(value = "/refund/{paymentId}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||
@ResponseBody
|
||
public ResponseEntity<String> refund(@PathVariable Long paymentId,
|
||
@RequestParam("amountCents") Long amountCents) {
|
||
try {
|
||
String idem = "refund-" + paymentId + "-" + amountCents + "-" + UUID.randomUUID();
|
||
paymentService.refundViaRedsys(paymentId, amountCents, idem);
|
||
return ResponseEntity.ok("{\"success\":true}");
|
||
} catch (Exception e) {
|
||
return ResponseEntity.badRequest().body("{\"success\":false, \"error\": \"" + e.getMessage() + "\"}");
|
||
}
|
||
}
|
||
}
|