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:
@ -30,143 +30,151 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
|
|
||||||
public SecurityConfig(DataSource dataSource) {
|
public SecurityConfig(DataSource dataSource) {
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Beans base ==========
|
// ========== Beans base ==========
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember-me (tabla persistent_logins)
|
// Remember-me (tabla persistent_logins)
|
||||||
@Bean
|
@Bean
|
||||||
public PersistentTokenRepository persistentTokenRepository() {
|
public PersistentTokenRepository persistentTokenRepository() {
|
||||||
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
|
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
|
||||||
repo.setDataSource(dataSource);
|
repo.setDataSource(dataSource);
|
||||||
// repo.setCreateTableOnStartup(true); // solo 1ª vez si necesitas crear la
|
// repo.setCreateTableOnStartup(true); // solo 1ª vez si necesitas crear la
|
||||||
// tabla
|
// tabla
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider que soporta UsernamePasswordAuthenticationToken
|
// Provider que soporta UsernamePasswordAuthenticationToken
|
||||||
private static RequestMatcher pathStartsWith(String... prefixes) {
|
private static RequestMatcher pathStartsWith(String... prefixes) {
|
||||||
return new RequestMatcher() {
|
return new RequestMatcher() {
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(HttpServletRequest request) {
|
public boolean matches(HttpServletRequest request) {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
return false;
|
return false;
|
||||||
for (String p : prefixes) {
|
for (String p : prefixes) {
|
||||||
if (uri.startsWith(p))
|
if (uri.startsWith(p))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(
|
public SecurityFilterChain securityFilterChain(
|
||||||
HttpSecurity http,
|
HttpSecurity http,
|
||||||
@Value("${security.rememberme.key}") String keyRememberMe,
|
@Value("${security.rememberme.key}") String keyRememberMe,
|
||||||
UserDetailsService userDetailsService,
|
UserDetailsService userDetailsService,
|
||||||
PersistentTokenRepository tokenRepo,
|
PersistentTokenRepository tokenRepo,
|
||||||
PasswordEncoder passwordEncoder, UserServiceImpl userServiceImpl) throws Exception {
|
PasswordEncoder passwordEncoder, UserServiceImpl userServiceImpl) throws Exception {
|
||||||
|
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userServiceImpl);
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userServiceImpl);
|
||||||
provider.setPasswordEncoder(passwordEncoder);
|
provider.setPasswordEncoder(passwordEncoder);
|
||||||
http.authenticationProvider(provider);
|
http.authenticationProvider(provider);
|
||||||
http
|
http
|
||||||
.authenticationProvider(provider)
|
.authenticationProvider(provider)
|
||||||
|
|
||||||
.sessionManagement(session -> session
|
.sessionManagement(session -> session
|
||||||
//.invalidSessionUrl("/login?expired")
|
// .invalidSessionUrl("/login?expired")
|
||||||
.maximumSessions(1))
|
.maximumSessions(1))
|
||||||
|
|
||||||
// Ignora CSRF para tu recurso público (sin Ant/Mvc matchers)
|
// Ignora CSRF para tu recurso público (sin Ant/Mvc matchers)
|
||||||
.csrf(csrf -> csrf
|
.csrf(csrf -> csrf
|
||||||
.ignoringRequestMatchers(pathStartsWith("/presupuesto/public/")))
|
.ignoringRequestMatchers(pathStartsWith("/presupuesto/public/"),
|
||||||
// ====== RequestCache: sólo navegaciones HTML reales ======
|
pathStartsWith("/pagos/redsys/")))
|
||||||
.requestCache(rc -> {
|
// ====== RequestCache: sólo navegaciones HTML reales ======
|
||||||
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
.requestCache(rc -> {
|
||||||
|
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
||||||
|
|
||||||
// Navegación HTML (por tipo o por cabecera Accept)
|
// Navegación HTML (por tipo o por cabecera Accept)
|
||||||
RequestMatcher htmlPage = new OrRequestMatcher(
|
RequestMatcher htmlPage = new OrRequestMatcher(
|
||||||
new MediaTypeRequestMatcher(MediaType.TEXT_HTML),
|
new MediaTypeRequestMatcher(MediaType.TEXT_HTML),
|
||||||
new MediaTypeRequestMatcher(MediaType.APPLICATION_XHTML_XML),
|
new MediaTypeRequestMatcher(MediaType.APPLICATION_XHTML_XML),
|
||||||
new RequestHeaderRequestMatcher("Accept", "text/html"));
|
new RequestHeaderRequestMatcher("Accept", "text/html"));
|
||||||
|
|
||||||
// No AJAX
|
// No AJAX
|
||||||
RequestMatcher nonAjax = new NegatedRequestMatcher(
|
RequestMatcher nonAjax = new NegatedRequestMatcher(
|
||||||
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
|
new RequestHeaderRequestMatcher("X-Requested-With",
|
||||||
|
"XMLHttpRequest"));
|
||||||
|
|
||||||
// Excluir sondas .well-known
|
// Excluir sondas .well-known
|
||||||
RequestMatcher notWellKnown = new NegatedRequestMatcher(pathStartsWith("/.well-known/"));
|
RequestMatcher notWellKnown = new NegatedRequestMatcher(
|
||||||
|
pathStartsWith("/.well-known/"));
|
||||||
|
|
||||||
// Excluir estáticos: comunes + tu /assets/**
|
// Excluir estáticos: comunes + tu /assets/**
|
||||||
RequestMatcher notStatic = new AndRequestMatcher(
|
RequestMatcher notStatic = new AndRequestMatcher(
|
||||||
new NegatedRequestMatcher(PathRequest.toStaticResources().atCommonLocations()),
|
new NegatedRequestMatcher(PathRequest.toStaticResources()
|
||||||
new NegatedRequestMatcher(pathStartsWith("/assets/")));
|
.atCommonLocations()),
|
||||||
|
new NegatedRequestMatcher(pathStartsWith("/assets/")));
|
||||||
RequestMatcher cartCount = new AndRequestMatcher(
|
|
||||||
new NegatedRequestMatcher(PathRequest.toStaticResources().atCommonLocations()),
|
|
||||||
new NegatedRequestMatcher(pathStartsWith("/cart/count")));
|
|
||||||
|
|
||||||
cache.setRequestMatcher(new AndRequestMatcher(htmlPage, nonAjax, notStatic, notWellKnown, cartCount));
|
RequestMatcher cartCount = new AndRequestMatcher(
|
||||||
rc.requestCache(cache);
|
new NegatedRequestMatcher(PathRequest.toStaticResources()
|
||||||
})
|
.atCommonLocations()),
|
||||||
// ========================================================
|
new NegatedRequestMatcher(pathStartsWith("/cart/count")));
|
||||||
|
|
||||||
.authorizeHttpRequests(auth -> auth
|
cache.setRequestMatcher(new AndRequestMatcher(htmlPage, nonAjax, notStatic,
|
||||||
// Aquí usa patrones String (no deprecados)
|
notWellKnown, cartCount));
|
||||||
.requestMatchers(
|
rc.requestCache(cache);
|
||||||
"/",
|
})
|
||||||
"/login",
|
// ========================================================
|
||||||
"/signup",
|
|
||||||
"/verify",
|
|
||||||
"/auth/password/**",
|
|
||||||
"/assets/**",
|
|
||||||
"/css/**",
|
|
||||||
"/js/**",
|
|
||||||
"/images/**",
|
|
||||||
"/public/**",
|
|
||||||
"/presupuesto/public/**",
|
|
||||||
"/error",
|
|
||||||
"/favicon.ico",
|
|
||||||
"/.well-known/**", // opcional
|
|
||||||
"/api/pdf/presupuesto/**"
|
|
||||||
).permitAll()
|
|
||||||
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
|
||||||
.anyRequest().authenticated())
|
|
||||||
|
|
||||||
.formLogin(login -> login
|
.authorizeHttpRequests(auth -> auth
|
||||||
.loginPage("/login").permitAll()
|
// Aquí usa patrones String (no deprecados)
|
||||||
.loginProcessingUrl("/login")
|
.requestMatchers(
|
||||||
.usernameParameter("username")
|
"/",
|
||||||
.passwordParameter("password")
|
"/login",
|
||||||
.defaultSuccessUrl("/", false) // respeta SavedRequest (ya filtrada)
|
"/signup",
|
||||||
.failureUrl("/login?error"))
|
"/verify",
|
||||||
|
"/auth/password/**",
|
||||||
|
"/assets/**",
|
||||||
|
"/css/**",
|
||||||
|
"/js/**",
|
||||||
|
"/images/**",
|
||||||
|
"/public/**",
|
||||||
|
"/presupuesto/public/**",
|
||||||
|
"/error",
|
||||||
|
"/favicon.ico",
|
||||||
|
"/.well-known/**", // opcional
|
||||||
|
"/api/pdf/presupuesto/**",
|
||||||
|
"/pagos/redsys/**"
|
||||||
|
)
|
||||||
|
.permitAll()
|
||||||
|
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
||||||
|
.anyRequest().authenticated())
|
||||||
|
|
||||||
.rememberMe(rm -> rm
|
.formLogin(login -> login
|
||||||
.key(keyRememberMe)
|
.loginPage("/login").permitAll()
|
||||||
.rememberMeParameter("remember-me")
|
.loginProcessingUrl("/login")
|
||||||
.rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
|
.usernameParameter("username")
|
||||||
.tokenValiditySeconds(60 * 60 * 24 * 2)
|
.passwordParameter("password")
|
||||||
.userDetailsService(userDetailsService)
|
.defaultSuccessUrl("/", false) // respeta SavedRequest (ya filtrada)
|
||||||
.tokenRepository(tokenRepo))
|
.failureUrl("/login?error"))
|
||||||
|
|
||||||
.logout(logout -> logout
|
.rememberMe(rm -> rm
|
||||||
.logoutUrl("/logout")
|
.key(keyRememberMe)
|
||||||
.logoutSuccessUrl("/")
|
.rememberMeParameter("remember-me")
|
||||||
.invalidateHttpSession(true)
|
.rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
|
||||||
.deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
|
.tokenValiditySeconds(60 * 60 * 24 * 2)
|
||||||
.permitAll());
|
.userDetailsService(userDetailsService)
|
||||||
|
.tokenRepository(tokenRepo))
|
||||||
|
|
||||||
return http.build();
|
.logout(logout -> logout
|
||||||
}
|
.logoutUrl("/logout")
|
||||||
|
.logoutSuccessUrl("/")
|
||||||
|
.invalidateHttpSession(true)
|
||||||
|
.deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
|
||||||
|
.permitAll());
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,21 +77,34 @@ public class PaymentService {
|
|||||||
* decodeMerchantParameters
|
* decodeMerchantParameters
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void handleRedsysNotification(String dsSignature, String dsMerchantParametersB64) throws Exception {
|
public void handleRedsysNotification(String dsSignature, String dsMerchantParameters) throws Exception {
|
||||||
RedsysNotification notif = redsysService.validateAndParseNotification(dsSignature, dsMerchantParametersB64);
|
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)
|
Payment p = payRepo.findByGatewayAndGatewayOrderId("redsys", notif.order)
|
||||||
.orElseThrow(() -> new IllegalStateException("Payment no encontrado para Ds_Order " + notif.order));
|
.orElseThrow(() -> new IllegalStateException("Payment no encontrado para Ds_Order " + notif.order));
|
||||||
|
|
||||||
if (!Objects.equals(p.getCurrency(), notif.currency)) {
|
// 🔹 Opción sencilla: sólo comprobar el importe
|
||||||
throw new IllegalStateException("Divisa inesperada");
|
|
||||||
}
|
|
||||||
if (!Objects.equals(p.getAmountTotalCents(), notif.amountCents)) {
|
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
|
// Si quieres, puedes hacer un check mínimamente decente de divisa numérica:
|
||||||
// transacción
|
// (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
|
if (p.getStatus() == PaymentStatus.CAPTURED
|
||||||
|| p.getStatus() == PaymentStatus.PARTIALLY_REFUNDED
|
|| p.getStatus() == PaymentStatus.PARTIALLY_REFUNDED
|
||||||
|| p.getStatus() == PaymentStatus.REFUNDED) {
|
|| p.getStatus() == PaymentStatus.REFUNDED) {
|
||||||
@ -101,9 +114,10 @@ public class PaymentService {
|
|||||||
PaymentTransaction tx = new PaymentTransaction();
|
PaymentTransaction tx = new PaymentTransaction();
|
||||||
tx.setPayment(p);
|
tx.setPayment(p);
|
||||||
tx.setType(PaymentTransactionType.CAPTURE);
|
tx.setType(PaymentTransactionType.CAPTURE);
|
||||||
tx.setCurrency(p.getCurrency());
|
tx.setCurrency(p.getCurrency()); // "EUR"
|
||||||
tx.setAmountCents(notif.amountCents);
|
tx.setAmountCents(notif.amountCents);
|
||||||
tx.setStatus(notif.authorized() ? PaymentTransactionStatus.SUCCEEDED
|
tx.setStatus(notif.authorized()
|
||||||
|
? PaymentTransactionStatus.SUCCEEDED
|
||||||
: PaymentTransactionStatus.FAILED);
|
: PaymentTransactionStatus.FAILED);
|
||||||
|
|
||||||
Object authCode = notif.raw.get("Ds_AuthorisationCode");
|
Object authCode = notif.raw.get("Ds_AuthorisationCode");
|
||||||
|
|||||||
@ -116,25 +116,26 @@ public class RedsysController {
|
|||||||
@GetMapping("/ko")
|
@GetMapping("/ko")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public ResponseEntity<String> koGet() {
|
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)
|
@PostMapping(value = "/ko", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public ResponseEntity<String> koPost(@RequestParam Map<String, String> form) {
|
public ResponseEntity<String> koPost(@RequestParam Map<String, String> form) {
|
||||||
// Podrías loguear 'form' si quieres ver qué manda Redsys
|
// 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)
|
@PostMapping(value = "/notify", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@Transactional
|
@jakarta.transaction.Transactional
|
||||||
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
public String notifyRedsys(@RequestParam("Ds_Signature") String signature,
|
||||||
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
@RequestParam("Ds_MerchantParameters") String merchantParameters) {
|
||||||
try {
|
try {
|
||||||
paymentService.handleRedsysNotification(signature, merchantParameters);
|
paymentService.handleRedsysNotification(signature, merchantParameters);
|
||||||
return "OK";
|
return "OK";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(); // 👈 para ver el motivo del 500 en logs
|
||||||
return "ERROR";
|
return "ERROR";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,10 +38,12 @@ public class RedsysService {
|
|||||||
|
|
||||||
// ---------- RECORDS ----------
|
// ---------- RECORDS ----------
|
||||||
// Pedido a Redsys
|
// 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
|
// 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) ----------
|
// ---------- MÉTODO PRINCIPAL (TARJETA) ----------
|
||||||
public FormPayload buildRedirectForm(PaymentRequest req) throws Exception {
|
public FormPayload buildRedirectForm(PaymentRequest req) throws Exception {
|
||||||
@ -50,7 +52,7 @@ public class RedsysService {
|
|||||||
|
|
||||||
// ---------- NUEVO: MÉTODO PARA BIZUM ----------
|
// ---------- NUEVO: MÉTODO PARA BIZUM ----------
|
||||||
public FormPayload buildRedirectFormBizum(PaymentRequest req) throws Exception {
|
public FormPayload buildRedirectFormBizum(PaymentRequest req) throws Exception {
|
||||||
return buildRedirectFormInternal(req, true); // true = Bizum (PAYMETHODS = z)
|
return buildRedirectFormInternal(req, true); // true = Bizum (PAYMETHODS = z)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- LÓGICA COMÚN ----------
|
// ---------- LÓGICA COMÚN ----------
|
||||||
@ -58,7 +60,7 @@ public class RedsysService {
|
|||||||
ApiMacSha256 api = new ApiMacSha256();
|
ApiMacSha256 api = new ApiMacSha256();
|
||||||
|
|
||||||
api.setParameter("DS_MERCHANT_AMOUNT", String.valueOf(req.amountCents()));
|
api.setParameter("DS_MERCHANT_AMOUNT", String.valueOf(req.amountCents()));
|
||||||
api.setParameter("DS_MERCHANT_ORDER", req.order()); // Usa 12 dígitos con ceros
|
api.setParameter("DS_MERCHANT_ORDER", req.order()); // Usa 12 dígitos con ceros
|
||||||
api.setParameter("DS_MERCHANT_MERCHANTCODE", merchantCode);
|
api.setParameter("DS_MERCHANT_MERCHANTCODE", merchantCode);
|
||||||
api.setParameter("DS_MERCHANT_CURRENCY", currency);
|
api.setParameter("DS_MERCHANT_CURRENCY", currency);
|
||||||
api.setParameter("DS_MERCHANT_TRANSACTIONTYPE", txType);
|
api.setParameter("DS_MERCHANT_TRANSACTIONTYPE", txType);
|
||||||
@ -103,6 +105,7 @@ public class RedsysService {
|
|||||||
// ---------- STEP 4: Validar notificación ----------
|
// ---------- STEP 4: Validar notificación ----------
|
||||||
public RedsysNotification validateAndParseNotification(String dsSignature, String dsMerchantParametersB64)
|
public RedsysNotification validateAndParseNotification(String dsSignature, String dsMerchantParametersB64)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
// 1) Decodificamos a mapa solo para leer campos
|
||||||
Map<String, Object> mp = decodeMerchantParametersToMap(dsMerchantParametersB64);
|
Map<String, Object> mp = decodeMerchantParametersToMap(dsMerchantParametersB64);
|
||||||
RedsysNotification notif = new RedsysNotification(mp);
|
RedsysNotification notif = new RedsysNotification(mp);
|
||||||
|
|
||||||
@ -110,15 +113,21 @@ public class RedsysService {
|
|||||||
throw new IllegalArgumentException("Falta Ds_Order en Ds_MerchantParameters");
|
throw new IllegalArgumentException("Falta Ds_Order en Ds_MerchantParameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) Calculamos la firma esperada usando el B64 tal cual
|
||||||
ApiMacSha256 api = new ApiMacSha256();
|
ApiMacSha256 api = new ApiMacSha256();
|
||||||
|
// Esta línea es opcional para createMerchantSignatureNotif, pero no molesta:
|
||||||
api.setParameter("Ds_MerchantParameters", dsMerchantParametersB64);
|
api.setParameter("Ds_MerchantParameters", dsMerchantParametersB64);
|
||||||
|
|
||||||
String expected = api.createMerchantSignatureNotif(
|
String expected = api.createMerchantSignatureNotif(
|
||||||
secretKeyBase64,
|
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)) {
|
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");
|
throw new SecurityException("Firma Redsys no válida");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,4 +22,4 @@ safekat.api.password=Safekat2024
|
|||||||
redsys.environment=test
|
redsys.environment=test
|
||||||
redsys.urls.ok=http://localhost:8080/pagos/redsys/ok
|
redsys.urls.ok=http://localhost:8080/pagos/redsys/ok
|
||||||
redsys.urls.ko=http://localhost:8080/pagos/redsys/ko
|
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