mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
trabajando en el notify
This commit is contained in:
@ -90,7 +90,8 @@ public class SecurityConfig {
|
||||
|
||||
// Ignora CSRF para tu recurso público (sin Ant/Mvc matchers)
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers(pathStartsWith("/presupuesto/public/")))
|
||||
.ignoringRequestMatchers(pathStartsWith("/presupuesto/public/"),
|
||||
pathStartsWith("/pagos/redsys/")))
|
||||
// ====== RequestCache: sólo navegaciones HTML reales ======
|
||||
.requestCache(rc -> {
|
||||
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
||||
@ -103,21 +104,26 @@ public class SecurityConfig {
|
||||
|
||||
// No AJAX
|
||||
RequestMatcher nonAjax = new NegatedRequestMatcher(
|
||||
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
|
||||
new RequestHeaderRequestMatcher("X-Requested-With",
|
||||
"XMLHttpRequest"));
|
||||
|
||||
// Excluir sondas .well-known
|
||||
RequestMatcher notWellKnown = new NegatedRequestMatcher(pathStartsWith("/.well-known/"));
|
||||
RequestMatcher notWellKnown = new NegatedRequestMatcher(
|
||||
pathStartsWith("/.well-known/"));
|
||||
|
||||
// Excluir estáticos: comunes + tu /assets/**
|
||||
RequestMatcher notStatic = new AndRequestMatcher(
|
||||
new NegatedRequestMatcher(PathRequest.toStaticResources().atCommonLocations()),
|
||||
new NegatedRequestMatcher(PathRequest.toStaticResources()
|
||||
.atCommonLocations()),
|
||||
new NegatedRequestMatcher(pathStartsWith("/assets/")));
|
||||
|
||||
RequestMatcher cartCount = new AndRequestMatcher(
|
||||
new NegatedRequestMatcher(PathRequest.toStaticResources().atCommonLocations()),
|
||||
new NegatedRequestMatcher(PathRequest.toStaticResources()
|
||||
.atCommonLocations()),
|
||||
new NegatedRequestMatcher(pathStartsWith("/cart/count")));
|
||||
|
||||
cache.setRequestMatcher(new AndRequestMatcher(htmlPage, nonAjax, notStatic, notWellKnown, cartCount));
|
||||
cache.setRequestMatcher(new AndRequestMatcher(htmlPage, nonAjax, notStatic,
|
||||
notWellKnown, cartCount));
|
||||
rc.requestCache(cache);
|
||||
})
|
||||
// ========================================================
|
||||
@ -139,8 +145,10 @@ public class SecurityConfig {
|
||||
"/error",
|
||||
"/favicon.ico",
|
||||
"/.well-known/**", // opcional
|
||||
"/api/pdf/presupuesto/**"
|
||||
).permitAll()
|
||||
"/api/pdf/presupuesto/**",
|
||||
"/pagos/redsys/**"
|
||||
)
|
||||
.permitAll()
|
||||
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
||||
.anyRequest().authenticated())
|
||||
|
||||
|
||||
@ -77,21 +77,34 @@ public class PaymentService {
|
||||
* decodeMerchantParameters
|
||||
*/
|
||||
@Transactional
|
||||
public void handleRedsysNotification(String dsSignature, String dsMerchantParametersB64) throws Exception {
|
||||
RedsysNotification notif = redsysService.validateAndParseNotification(dsSignature, dsMerchantParametersB64);
|
||||
public void handleRedsysNotification(String dsSignature, String dsMerchantParameters) throws Exception {
|
||||
RedsysNotification notif = redsysService.validateAndParseNotification(dsSignature, dsMerchantParameters);
|
||||
|
||||
// Log útil para depurar
|
||||
System.out.println(">> Redsys notify: order=" + notif.order +
|
||||
" amountCents=" + notif.amountCents +
|
||||
" currency=" + notif.currency +
|
||||
" response=" + notif.response);
|
||||
|
||||
Payment p = payRepo.findByGatewayAndGatewayOrderId("redsys", notif.order)
|
||||
.orElseThrow(() -> new IllegalStateException("Payment no encontrado para Ds_Order " + notif.order));
|
||||
|
||||
if (!Objects.equals(p.getCurrency(), notif.currency)) {
|
||||
throw new IllegalStateException("Divisa inesperada");
|
||||
}
|
||||
// 🔹 Opción sencilla: sólo comprobar el importe
|
||||
if (!Objects.equals(p.getAmountTotalCents(), notif.amountCents)) {
|
||||
throw new IllegalStateException("Importe inesperado");
|
||||
throw new IllegalStateException("Importe inesperado: esperado=" +
|
||||
p.getAmountTotalCents() + " recibido=" + notif.amountCents);
|
||||
}
|
||||
|
||||
// Idempotencia sencilla: si ya está capturado o reembolsado, no creamos otra
|
||||
// transacción
|
||||
// Si quieres, puedes hacer un check mínimamente decente de divisa numérica:
|
||||
// (si usas siempre EUR)
|
||||
/*
|
||||
* if (!"978".equals(notif.currency)) {
|
||||
* throw new IllegalStateException("Divisa Redsys inesperada: " +
|
||||
* notif.currency);
|
||||
* }
|
||||
*/
|
||||
|
||||
// Idempotencia simple: si ya está capturado o reembolsado, no hacemos nada
|
||||
if (p.getStatus() == PaymentStatus.CAPTURED
|
||||
|| p.getStatus() == PaymentStatus.PARTIALLY_REFUNDED
|
||||
|| p.getStatus() == PaymentStatus.REFUNDED) {
|
||||
@ -101,9 +114,10 @@ public class PaymentService {
|
||||
PaymentTransaction tx = new PaymentTransaction();
|
||||
tx.setPayment(p);
|
||||
tx.setType(PaymentTransactionType.CAPTURE);
|
||||
tx.setCurrency(p.getCurrency());
|
||||
tx.setCurrency(p.getCurrency()); // "EUR"
|
||||
tx.setAmountCents(notif.amountCents);
|
||||
tx.setStatus(notif.authorized() ? PaymentTransactionStatus.SUCCEEDED
|
||||
tx.setStatus(notif.authorized()
|
||||
? PaymentTransactionStatus.SUCCEEDED
|
||||
: PaymentTransactionStatus.FAILED);
|
||||
|
||||
Object authCode = notif.raw.get("Ds_AuthorisationCode");
|
||||
|
||||
@ -116,25 +116,26 @@ public class RedsysController {
|
||||
@GetMapping("/ko")
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> koGet() {
|
||||
return ResponseEntity.ok("<h2>Pago cancelado o rechazado</h2><a href=\"/cart\">Volver</a>");
|
||||
return ResponseEntity.ok("<h2>Pago cancelado o rechazado</h2><a href=\"/checkout\">Volver</a>");
|
||||
}
|
||||
|
||||
@PostMapping(value = "/ko", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> koPost(@RequestParam Map<String, String> form) {
|
||||
// Podrías loguear 'form' si quieres ver qué manda Redsys
|
||||
return ResponseEntity.ok("<h2>Pago cancelado o rechazado</h2><a href=\"/cart\">Volver</a>");
|
||||
return ResponseEntity.ok("<h2>Pago cancelado o rechazado</h2><a href=\"/checkout\">Volver</a>");
|
||||
}
|
||||
|
||||
@PostMapping(value = "/notify", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@ResponseBody
|
||||
@Transactional
|
||||
@jakarta.transaction.Transactional
|
||||
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
||||
try {
|
||||
paymentService.handleRedsysNotification(signature, merchantParameters);
|
||||
return "OK";
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // 👈 para ver el motivo del 500 en logs
|
||||
return "ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,10 +38,12 @@ public class RedsysService {
|
||||
|
||||
// ---------- RECORDS ----------
|
||||
// Pedido a Redsys
|
||||
public record PaymentRequest(String order, long amountCents, String description) {}
|
||||
public record PaymentRequest(String order, long amountCents, String description) {
|
||||
}
|
||||
|
||||
// Payload para el formulario
|
||||
public record FormPayload(String action, String signatureVersion, String merchantParameters, String signature) {}
|
||||
public record FormPayload(String action, String signatureVersion, String merchantParameters, String signature) {
|
||||
}
|
||||
|
||||
// ---------- MÉTODO PRINCIPAL (TARJETA) ----------
|
||||
public FormPayload buildRedirectForm(PaymentRequest req) throws Exception {
|
||||
@ -103,6 +105,7 @@ public class RedsysService {
|
||||
// ---------- STEP 4: Validar notificación ----------
|
||||
public RedsysNotification validateAndParseNotification(String dsSignature, String dsMerchantParametersB64)
|
||||
throws Exception {
|
||||
// 1) Decodificamos a mapa solo para leer campos
|
||||
Map<String, Object> mp = decodeMerchantParametersToMap(dsMerchantParametersB64);
|
||||
RedsysNotification notif = new RedsysNotification(mp);
|
||||
|
||||
@ -110,15 +113,21 @@ public class RedsysService {
|
||||
throw new IllegalArgumentException("Falta Ds_Order en Ds_MerchantParameters");
|
||||
}
|
||||
|
||||
// 2) Calculamos la firma esperada usando el B64 tal cual
|
||||
ApiMacSha256 api = new ApiMacSha256();
|
||||
// Esta línea es opcional para createMerchantSignatureNotif, pero no molesta:
|
||||
api.setParameter("Ds_MerchantParameters", dsMerchantParametersB64);
|
||||
|
||||
String expected = api.createMerchantSignatureNotif(
|
||||
secretKeyBase64,
|
||||
api.decodeMerchantParameters(dsMerchantParametersB64)
|
||||
dsMerchantParametersB64 // 👈 AQUÍ va el B64, NO el JSON
|
||||
);
|
||||
|
||||
// 3) Comparamos en constante time, normalizando Base64 URL-safe
|
||||
if (!safeEqualsB64(dsSignature, expected)) {
|
||||
System.out.println("Firma Redsys no válida");
|
||||
System.out.println("Ds_Signature (Redsys) = " + dsSignature);
|
||||
System.out.println("Expected (local) = " + expected);
|
||||
throw new SecurityException("Firma Redsys no válida");
|
||||
}
|
||||
|
||||
|
||||
@ -22,4 +22,4 @@ safekat.api.password=Safekat2024
|
||||
redsys.environment=test
|
||||
redsys.urls.ok=http://localhost:8080/pagos/redsys/ok
|
||||
redsys.urls.ko=http://localhost:8080/pagos/redsys/ko
|
||||
redsys.urls.notify=https://hns2jx2x-8080.uks1.devtunnels.ms/pagos/redsys/notify
|
||||
redsys.urls.notify=https://orological-sacrilegiously-lucille.ngrok-free.dev/pagos/redsys/notify
|
||||
Reference in New Issue
Block a user