diff --git a/pom.xml b/pom.xml
index 2e5351a..f882da2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -151,6 +151,41 @@
${liquibase.version}
+
+
+ com.redsys
+ apiSha512V2
+ 2.0
+ system
+ ${project.basedir}/src/main/resources/lib/apiSha512V2.jar
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.47
+ system
+ ${project.basedir}/src/main/resources/lib/bcprov-jdk15on-1.4.7.jar
+
+
+
+ commons-codec
+ commons-codec
+ 1.3
+ system
+ ${project.basedir}/src/main/resources/lib/commons-codec-1.3.jar
+
+
+
+ org.json
+ json
+ 1.0
+ system
+ ${project.basedir}/src/main/resources/lib/org.json.jar
+
+
+
diff --git a/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java b/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java
new file mode 100644
index 0000000..6a7b43b
--- /dev/null
+++ b/src/main/java/com/imprimelibros/erp/redsys/RedsysController.java
@@ -0,0 +1,53 @@
+package com.imprimelibros.erp.redsys;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+@RequestMapping("/pagos/redsys")
+public class RedsysController {
+
+ private final RedsysService service;
+ public RedsysController(RedsysService service) { this.service = service; }
+
+ @PostMapping("/crear")
+ public String crearPago(@RequestParam String order,
+ @RequestParam long amountCents,
+ Model model) {
+ var payReq = new RedsysService.PaymentRequest(order, amountCents, "Compra en ImprimeLibros");
+ var form = service.buildRedirectForm(payReq);
+ model.addAttribute("action", form.action());
+ model.addAttribute("signatureVersion", form.signatureVersion());
+ model.addAttribute("merchantParameters", form.merchantParameters());
+ model.addAttribute("signature", form.signature());
+ return "payments/redsys-redirect"; // Thymeleaf
+ }
+
+ @PostMapping("/notify")
+ @ResponseBody
+ public ResponseEntity notifyRedsys(@RequestParam("Ds_Signature") String dsSig,
+ @RequestParam("Ds_SignatureVersion") String dsSigVer,
+ @RequestParam("Ds_MerchantParameters") String dsParams) throws Exception {
+ var notif = service.validateAndParse(dsSig, dsSigVer, dsParams);
+
+ // 1) Idempotencia: marca el pedido si aún no procesado.
+ // 2) Verifica importe/moneda/pedido contra tu base de datos.
+ // 3) Autoriza en tu sistema si notif.authorized() == true.
+
+ return ResponseEntity.ok("OK");
+ }
+
+ @GetMapping("/ok")
+ public String ok() { return "payments/success"; }
+
+ @GetMapping("/ko")
+ public String ko() { return "payments/failure"; }
+
+}
+
diff --git a/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java b/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java
new file mode 100644
index 0000000..33ac9eb
--- /dev/null
+++ b/src/main/java/com/imprimelibros/erp/redsys/RedsysService.java
@@ -0,0 +1,89 @@
+package com.imprimelibros.erp.redsys;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class RedsysService {
+
+ @Value("${redsys.merchant-code}") private String merchantCode;
+ @Value("${redsys.terminal}") private String terminal;
+ @Value("${redsys.currency}") private String currency;
+ @Value("${redsys.transaction-type}") private String txType;
+ @Value("${redsys.secret-key}") private String secretKey;
+ @Value("${redsys.urls.ok}") private String urlOk;
+ @Value("${redsys.urls.ko}") private String urlKo;
+ @Value("${redsys.urls.notify}") private String urlNotify;
+ @Value("${redsys.environment}") private String env;
+
+ public record PaymentRequest(String order, long amountCents, String description) {}
+
+ public record FormPayload(String action, String signatureVersion, String merchantParameters, String signature) {}
+
+ public FormPayload buildRedirectForm(PaymentRequest req) {
+ // RedsysAPI proviene del JAR oficial
+ com.redsys.api.RedsysAPI api = new com.redsys.api.RedsysAPI();
+
+ Map mp = new HashMap<>();
+ mp.put("DS_MERCHANT_AMOUNT", String.valueOf(req.amountCents()));
+ mp.put("DS_MERCHANT_ORDER", req.order());
+ mp.put("DS_MERCHANT_MERCHANTCODE", merchantCode);
+ mp.put("DS_MERCHANT_CURRENCY", currency);
+ mp.put("DS_MERCHANT_TRANSACTIONTYPE", txType);
+ mp.put("DS_MERCHANT_TERMINAL", terminal);
+ mp.put("DS_MERCHANT_MERCHANTNAME", "Tu Comercio");
+ mp.put("DS_MERCHANT_PRODUCTDESCRIPTION", req.description());
+ mp.put("DS_MERCHANT_URLOK", urlOk);
+ mp.put("DS_MERCHANT_URLKO", urlKo);
+ mp.put("DS_MERCHANT_MERCHANTURL", urlNotify);
+
+ String merchantParameters = api.createMerchantParameters(mp);
+ String signature = api.createMerchantSignature(secretKey);
+
+ String action = "test".equalsIgnoreCase(env)
+ ? "https://sis-t.redsys.es:25443/sis/realizarPago"
+ : "https://sis.redsys.es/sis/realizarPago";
+
+ return new FormPayload(action, "HMAC_SHA256_V1", merchantParameters, signature);
+ }
+
+ // Validación de la notificación on-line (webhook).
+ public RedsysNotification validateAndParse(String dsSignature, String dsSignatureVersion, String dsMerchantParametersB64) {
+ com.redsys.api.RedsysAPI api = new com.redsys.api.RedsysAPI();
+
+ // 1) Validar firma
+ String calc = api.createMerchantSignatureNotif(secretKey, dsMerchantParametersB64);
+ if (!Objects.equals(calc, dsSignature)) {
+ throw new IllegalArgumentException("Firma Redsys no válida");
+ }
+
+ // 2) Decodificar parámetros
+ String json = api.decodeMerchantParameters(dsMerchantParametersB64);
+ Map params = new com.fasterxml.jackson.databind.ObjectMapper()
+ .readValue(json, new com.fasterxml.jackson.core.type.TypeReference<>() {});
+ // Campos típicos: Ds_Order, Ds_Amount, Ds_Currency, Ds_Response, etc.
+ return RedsysNotification.from(params);
+ }
+
+ public static record RedsysNotification(String order, String dsResponse, long amountCents, String currency) {
+ static RedsysNotification from(Map p) {
+ String order = (String) p.get("Ds_Order");
+ String resp = String.valueOf(p.get("Ds_Response"));
+ long amount = Long.parseLong((String) p.get("Ds_Amount"));
+ String curr = String.valueOf(p.get("Ds_Currency"));
+ return new RedsysNotification(order, resp, amount, curr);
+ }
+ // Éxito si 0–99.
+ public boolean authorized() {
+ try {
+ int r = Integer.parseInt(dsResponse);
+ return r >= 0 && r <= 99;
+ } catch (Exception e) { return false; }
+ }
+ }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 321a344..f089b65 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -105,3 +105,16 @@ spring.liquibase.change-log=classpath:db/changelog/master.yml
# spring.liquibase.url=jdbc:mysql://localhost:3306/imprimelibros
# spring.liquibase.user=tu_user
# spring.liquibase.password=tu_pass
+
+
+# Redsys
+redsys.environment=test
+redsys.merchant-code=124760810
+redsys.terminal=1
+redsys.currency=978
+redsys.transaction-type=0
+redsys.secret-key=sq7HjrUOBfKmC576ILgskD5srU870gJ7
+redsys.urls.ok=https://localhost:8080/pagos/redsys/ok
+redsys.urls.ko=https://localhost:8080/pagos/redsys/ko
+redsys.urls.notify=https://localhost:8080/pagos/redsys/notify
+
diff --git a/src/main/resources/lib/apiSha512V2.jar b/src/main/resources/lib/apiSha512V2.jar
new file mode 100644
index 0000000..5858b1d
Binary files /dev/null and b/src/main/resources/lib/apiSha512V2.jar differ
diff --git a/src/main/resources/lib/bcprov-jdk15on-1.4.7.jar b/src/main/resources/lib/bcprov-jdk15on-1.4.7.jar
new file mode 100644
index 0000000..0b80922
Binary files /dev/null and b/src/main/resources/lib/bcprov-jdk15on-1.4.7.jar differ
diff --git a/src/main/resources/lib/commons-codec-1.3.jar b/src/main/resources/lib/commons-codec-1.3.jar
new file mode 100644
index 0000000..957b675
Binary files /dev/null and b/src/main/resources/lib/commons-codec-1.3.jar differ
diff --git a/src/main/resources/lib/org.json.jar b/src/main/resources/lib/org.json.jar
new file mode 100644
index 0000000..5372cb0
Binary files /dev/null and b/src/main/resources/lib/org.json.jar differ