From ebd6125e16413d1c0332340f3e862cb3d70178e1 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:23:20 -0300 Subject: [PATCH 01/78] feat: add TransaccionCompletaController for full transaction handling --- .../TransaccionCompletaController.java | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java new file mode 100644 index 0000000..bd3e34f --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -0,0 +1,208 @@ +package cl.transbank.webpay.example.controllers; + +import cl.transbank.common.IntegrationApiKeys; +import cl.transbank.common.IntegrationCommerceCodes; +import cl.transbank.common.IntegrationType; +import cl.transbank.webpay.common.WebpayOptions; +import cl.transbank.webpay.exception.*; +import cl.transbank.webpay.transaccioncompleta.FullTransaction; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; + +@Log4j2 +@Controller +@RequestMapping("/transaccion-completa") +public class TransaccionCompletaController extends BaseController { + private static final String TEMPLATE_FOLDER = "transaccion_completa"; + private static final String BASE_URL = "/transaccion-completa"; + private static final String PRODUCT = "Webpay Transacción Completa"; + + private static final String VIEW_INDEX = TEMPLATE_FOLDER + "/index"; + private static final String VIEW_CREATE = TEMPLATE_FOLDER + "/create"; + private static final String VIEW_INSTALLMENTS = TEMPLATE_FOLDER + "/installments"; + private static final String VIEW_COMMIT = TEMPLATE_FOLDER + "/commit"; + private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status"; + private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund"; + + private static final Map NAV_INDEX; + private static final Map NAV_CREATE; + private static final Map NAV_INSTALLMENTS; + private static final Map NAV_COMMIT; + private static final Map NAV_STATUS; + private static final Map NAV_REFUND; + + static { + NAV_INDEX = new LinkedHashMap<>(); + NAV_INDEX.put("form", "Formulario"); + + NAV_CREATE = new LinkedHashMap<>(); + NAV_CREATE.put("request", "Petición"); + NAV_CREATE.put("response", "Respuesta"); + NAV_CREATE.put("form", "Formulario"); + + NAV_INSTALLMENTS = new LinkedHashMap<>(); + NAV_INSTALLMENTS.put("request", "Petición"); + NAV_INSTALLMENTS.put("response", "Respuesta"); + NAV_INSTALLMENTS.put("form", "Formulario"); + + NAV_COMMIT = new LinkedHashMap<>(); + NAV_COMMIT.put("request", "Petición"); + NAV_COMMIT.put("response", "Respuesta"); + NAV_COMMIT.put("form", "Formulario"); + + NAV_STATUS = new LinkedHashMap<>(); + NAV_STATUS.put("request", "Petición"); + NAV_STATUS.put("response", "Respuesta"); + + NAV_REFUND = NAV_STATUS; + } + + private final FullTransaction tx; + + public TransaccionCompletaController() { + this.tx = new FullTransaction( + new WebpayOptions( + IntegrationCommerceCodes.TRANSACCION_COMPLETA, + IntegrationApiKeys.WEBPAY, + IntegrationType.TEST + ) + ); + } + + private void addProductAndBreadcrumbs(Model model, String label, String url) { + var breadcrumbs = new LinkedHashMap(); + breadcrumbs.put("Inicio", "/"); + breadcrumbs.put(PRODUCT, BASE_URL); + if (label != null) { + breadcrumbs.put(label, url); + } + model.addAttribute("product", PRODUCT); + model.addAttribute("breadcrumbs", breadcrumbs); + } + + @GetMapping("") + public String index(Model model) { + model.addAttribute("navigation", NAV_INDEX); + addProductAndBreadcrumbs(model, null, null); + return VIEW_INDEX; + } + + @PostMapping("/create") + public String create( + HttpServletRequest req, + @RequestParam("number") String number, + @RequestParam("expiry") String expiry, + @RequestParam("cvc") String cvc, + Model model + ) throws TransactionCreateException, IOException { + model.addAttribute("navigation", NAV_CREATE); + addProductAndBreadcrumbs(model, "Crear Transacción", BASE_URL + "/create"); + + String cardNumber = number.replaceAll("\\s+", ""); + String[] expiryParts = expiry.split("/"); + String month = expiryParts.length > 0 ? expiryParts[0] : ""; + String year = expiryParts.length > 1 ? expiryParts[1] : ""; + String cardExpiry = year + "/" + month; + + String buyOrder = "O-" + getRandomNumber(); + String sessionId = "S-" + getRandomNumber(); + double amount = 1000 + new Random().nextInt(1001); + + var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); + req.getSession().setAttribute("transaccion_completa_amount", amount); + + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_CREATE; + } + + @PostMapping("/installments") + public String installments( + @RequestParam("token") String token, + @RequestParam("installments_number") byte installmentsNumber, + Model model + ) throws TransactionInstallmentException, IOException { + model.addAttribute("navigation", NAV_INSTALLMENTS); + addProductAndBreadcrumbs(model, "Consulta de cuotas", BASE_URL + "/installments"); + + var resp = tx.installments(token, installmentsNumber); + model.addAttribute("request_token", token); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_INSTALLMENTS; + } + + @GetMapping("/commit") + public String commit( + HttpServletRequest req, + @RequestParam("token") String token, + @RequestParam(value = "idQueryInstallments", required = false) Long idQueryInstallments, + Model model + ) throws TransactionCommitException, IOException { + model.addAttribute("navigation", NAV_COMMIT); + addProductAndBreadcrumbs(model, "Confirmar", BASE_URL + "/commit"); + + Byte deferredPeriodIndex = null; + Boolean gracePeriod = Boolean.FALSE; + + var resp = tx.commit(token, idQueryInstallments, deferredPeriodIndex, gracePeriod); + Object amount = req.getSession().getAttribute("transaccion_completa_amount"); + req.getSession().removeAttribute("transaccion_completa_amount"); + + model.addAttribute("amount", amount); + model.addAttribute("request_token", token); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_COMMIT; + } + + @GetMapping("/status") + public String status( + @RequestParam("token") String token, + Model model + ) throws TransactionStatusException, IOException { + model.addAttribute("navigation", NAV_STATUS); + addProductAndBreadcrumbs(model, "Estado de transacción", BASE_URL + "/status"); + + var resp = tx.status(token); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_STATUS; + } + + @GetMapping("/refund") + public String refund( + @RequestParam("token") String token, + @RequestParam("amount") double amount, + Model model + ) throws TransactionRefundException, IOException { + model.addAttribute("navigation", NAV_REFUND); + addProductAndBreadcrumbs(model, "Reversa", BASE_URL + "/refund"); + + var resp = tx.refund(token, amount); + model.addAttribute("request_token", token); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_REFUND; + } + + @ExceptionHandler(Exception.class) + public String handleException(Exception e, Model model) { + log.error("Error inesperado", e); + model.addAttribute("error", e.getMessage()); + return VIEW_ERROR; + } +} From 8b03e388e944c28ad5a6f85ef82d8914741d41ec Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:23:29 -0300 Subject: [PATCH 02/78] feat: update styles for card components and improve CSS formatting --- src/main/resources/static/css/styles.css | 63 +++++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 001d2e2..dbccbe3 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -52,9 +52,9 @@ --tbk-red: #f6f7f9; --tbk-table-header: rgb(35 39 47/0.95); --shadow-elevation: inset 0 0 0 1px hsla(0, 0%, 100%, 0.08); - --white-elevation: 0 0 0 1px hsla(0, 0%, 100%, 0.15), - 0px 0.8px 2px rgba(0, 0, 0, 0.032), 0px 2.7px 6.7px rgba(0, 0, 0, 0.048), - 0px 12px 30px rgba(0, 0, 0, 0.08); + --white-elevation: + 0 0 0 1px hsla(0, 0%, 100%, 0.15), 0px 0.8px 2px rgba(0, 0, 0, 0.032), + 0px 2.7px 6.7px rgba(0, 0, 0, 0.048), 0px 12px 30px rgba(0, 0, 0, 0.08); --tbk-white-red: #ffb9b93b; } @@ -269,6 +269,10 @@ code { margin-bottom: 16px; } +.mr-16 { + margin-right: 16px; +} + .tbk-link { color: var(--tbk-red); text-decoration: none; @@ -651,8 +655,10 @@ code { display: flex; flex-direction: column; border-radius: 4px; - box-shadow: 0px 1px 2px 0px rgba(14, 21, 32, 0.18), - 0px 0px 2px 0px rgba(14, 21, 32, 0.12), 0px 0px 2px 0px rgba(0, 0, 0, 0.04); + box-shadow: + 0px 1px 2px 0px rgba(14, 21, 32, 0.18), + 0px 0px 2px 0px rgba(14, 21, 32, 0.12), + 0px 0px 2px 0px rgba(0, 0, 0, 0.04); color: var(--tbk-black); top: 104px; .dark & { @@ -807,7 +813,9 @@ code { line-height: 1.75rem; font-weight: 700; color: var(--tbk-grey); - transition: opacity 0.5s ease, transform 0.5s ease; + transition: + opacity 0.5s ease, + transform 0.5s ease; padding-left: 24px; } @@ -1112,6 +1120,49 @@ code { } } +/* tx completa */ + +.card-inputs-container { + display: flex; + flex-direction: column; + gap: 20px; + margin-bottom: 50px; + margin-top: 70px; +} + +.card-split-inputs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 31px; +} + +.card-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + padding-top: 1.5rem; +} + +.card-border { + border: 1px solid #c9d0e4; + border-radius: 1px; + margin-bottom: 30px; + width: 100%; +} + +.card-footer { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.card-wrapper { + z-index: 0; +} + @media (max-width: 1439.98px) { .body-container, .body-container.no-nav { From 3a77a73e24dd678081c2ee247ee0340cfd4d33a6 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:23:59 -0300 Subject: [PATCH 03/78] feat: add card js --- src/main/resources/templates/layout.html | 136 +++++++++++------------ 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html index c14cf99..faf2338 100644 --- a/src/main/resources/templates/layout.html +++ b/src/main/resources/templates/layout.html @@ -1,76 +1,76 @@ - + - - Transbank Sdk Java Example - - - + + Transbank Sdk Java Example + + + - - + + - - - + + + + + - - - + - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
+ gtag("config", "G-6CW8MF50ZX"); + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- - - - - +
+ +
+
+ + + + + From ed0670b1fac24f857bd29558845f79e3a60c16d5 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:24:10 -0300 Subject: [PATCH 04/78] feat: add create transaction page with request and response handling --- .../transaccion_completa/create.html | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa/create.html diff --git a/src/main/resources/templates/transaccion_completa/create.html b/src/main/resources/templates/transaccion_completa/create.html new file mode 100644 index 0000000..557cdbd --- /dev/null +++ b/src/main/resources/templates/transaccion_completa/create.html @@ -0,0 +1,65 @@ +
+
+

Transacción Completa - Crear transacción

+

+ En este paso sucede la creación de la transacción con el objetivo de obtener un identificador único para la misma. +

+ +

Paso 1: Petición

+

+ Comienza importando la librería TransaccionCompleta, y a continuación, crea la transacción necesaria. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.create(buyOrder, sessionId, amount, cvv, cardNumber, cardExpiry);
+    
+ +

Paso 2: Respuesta

+

Una vez creada la transacción, recibirás los siguientes datos de respuesta:

+ +
+ +

¡Transacción creada!

+

Ahora que hemos creado la transacción, se abren dos opciones para continuar:

+
    +
  • + Consultar Cuotas (opcional): Alternativamente puedes realizar consultas de cuotas + para ofrecer opciones de pago a plazos. +
  • +
  • + Confirmar Transacción: Debes confirmar directamente la transacción para + finalizar con el proceso de pago. +
  • +
+ +
+
+ Formulario de redirección + +
+ + + +
+ +
+
+
+
From 52ae8366124a422ccbf98b04f7dfa0b8e175a372 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:24:30 -0300 Subject: [PATCH 05/78] feat: add transaction complete form with card details input --- .../templates/transaccion_completa/index.html | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa/index.html diff --git a/src/main/resources/templates/transaccion_completa/index.html b/src/main/resources/templates/transaccion_completa/index.html new file mode 100644 index 0000000..5da04d3 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa/index.html @@ -0,0 +1,119 @@ +
+
+

Transacción Completa - Formulario

+

+ En esta primera etapa necesitas obtener los datos esenciales de la tarjeta + de crédito, débito o prepago del titular. Utiliza el formulario para + recolectar esta información de manera segura. +

+ +
+
+
+
+
+
+ + +
+ +
+
+ + +
+
+ + + +
+
+
+ + +
+
+
+ + +
+
From 96d6ba1f4a379101abf55910d6a20321bb7f0b76 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:24:41 -0300 Subject: [PATCH 06/78] feat: add transaction confirmation page with request and response details --- .../transaccion_completa/commit.html | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa/commit.html diff --git a/src/main/resources/templates/transaccion_completa/commit.html b/src/main/resources/templates/transaccion_completa/commit.html new file mode 100644 index 0000000..7cfa871 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa/commit.html @@ -0,0 +1,73 @@ +
+
+

Transacción Completa - Confirmar transacción

+

+ En este paso crucial, procederemos a confirmar la transacción con el objetivo de notificar a + Transbank que hemos recibido la transacción de manera exitosa. Es fundamental destacar que si no se confirma la + transacción, esta será caducada. +

+ +

Paso 1: Petición

+

+ Para confirmar la transacción, debes enviar el token correspondiente. En el caso de pagos a + plazos, también debes incluir el ID de la consulta de cuotas. En algunos casos, será necesario proporcionar el + índice del periodo diferido y un valor boolean indicando si se tomará el periodo de gracia. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+var resp = tx.commit(token, idQueryInstallments, deferredPeriodIndex, gracePeriod);
+    
+ +

Paso 2: Respuesta

+

+ Una vez que la transacción ha sido confirmada Transbank proporcionará la siguiente información. Es fundamental + conservar esta respuesta y verificar que el campo "response_code" tenga un valor de cero y que el campo "status" + sea "AUTHORIZED". +

+ +
+ +

¡Listo!

+

+ Con la transacción confirmada, puedes mostrar al usuario una página de éxito de la transacción, + proporcionándole la confirmación de que el proceso se ha completado con éxito. +

+ +

Después de confirmar la transacción, podrás realizar otras operaciones útiles:

+
    +
  • Reembolsar: Puedes reversar o anular el pago según ciertas condiciones + comerciales.
  • +
  • Consultar Estado: Hasta 7 días después de realizada la transacción, podrás + consultar el estado de la transacción.
  • +
+ +
+
+
+ + + +
+ + +
+
+ + CONSULTAR ESTADO +
+
From 054aaf29423b6c0d3291d666432a77f6bae1e928 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:24:48 -0300 Subject: [PATCH 07/78] feat: add installments consultation page with request and response handling --- .../transaccion_completa/installments.html | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa/installments.html diff --git a/src/main/resources/templates/transaccion_completa/installments.html b/src/main/resources/templates/transaccion_completa/installments.html new file mode 100644 index 0000000..5f576e0 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa/installments.html @@ -0,0 +1,81 @@ +
+
+

Transacción Completa - Consulta de cuotas

+

+ En esta etapa, realizaremos una consulta de cuotas para conocer sus condiciones. Es importante + destacar que este paso es opcional y se utiliza únicamente si deseas ofrecer opciones de pago a plazos. +

+ +

Paso 1: Petición

+

Para llevar a cabo la consulta de cuotas, debemos enviar los siguientes datos relevantes.

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.installments(token, installments);
+    
+ +

Paso 2: Respuesta

+

Una vez realizada la consulta de cuotas, recibirás los siguientes datos de respuesta:

+ +
+ +

Confirmar Transacción

+

+ Si decides utilizar cuotas y estás satisfecho con las condiciones obtenidas en la consulta, el + siguiente paso sería confirmar la transacción. +

+ +
+
+ + +
+
+ + +
+ + +
+ + +
+
From 000c13b40e0b3bb1469dc6730af318960eb967ba Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:24:54 -0300 Subject: [PATCH 08/78] feat: add refund transaction page with request and response handling --- .../transaccion_completa/refund.html | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa/refund.html diff --git a/src/main/resources/templates/transaccion_completa/refund.html b/src/main/resources/templates/transaccion_completa/refund.html new file mode 100644 index 0000000..7e46aef --- /dev/null +++ b/src/main/resources/templates/transaccion_completa/refund.html @@ -0,0 +1,51 @@ +
+
+

Transacción Completa - Reembolsar

+

+ En esta etapa, tendrás la posibilidad de solicitar el reembolso del dinero al tarjeta habiente. El + tipo de reembolso (Reversa, Anulación o Anulación parcial) dependerá del monto y el tiempo transcurrido desde la + transacción. +

+ +

Paso 1: Petición

+

+ Para efectuar la solicitud de reembolso, necesitarás el token de la transacción y el monto que + deseas reversar. Si decides anular el monto total, puede resultar en una Reversa o Anulación, según ciertas + condiciones. En caso de un monto menor al total, se realizará una Anulación parcial. Las anulaciones parciales + para tarjetas débito y prepago no están soportadas. +

+

+ En este + link + podrás ver mayor información sobre las condiciones y casos para anular o reversar + transacciones. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+var resp = tx.refund(token, amount);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con el resultado de la reversa o anulación. Evalúa cuidadosamente esta respuesta para + confirmar que el reembolso se haya procesado de manera efectiva. +

+ +
+ + CONSULTAR ESTADO +
+
From 61189dcfec7d1fbdfebb10d534a61e6ff2f33ccf Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 16:25:01 -0300 Subject: [PATCH 09/78] feat: add transaction status page with request and response handling --- .../transaccion_completa/status.html | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa/status.html diff --git a/src/main/resources/templates/transaccion_completa/status.html b/src/main/resources/templates/transaccion_completa/status.html new file mode 100644 index 0000000..4e4b236 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa/status.html @@ -0,0 +1,44 @@ +
+
+

Transacción Completa - Estado de transacción

+

+ En esta fase, tendrás la capacidad de solicitar el estado actual de una transacción hasta 7 días + después de su realización. Es importante destacar que no hay límite en la cantidad de solicitudes de este tipo + durante este período. Sin embargo, una vez transcurridos los 7 días, ya no podrás revisar el estado de la + transacción. +

+ +

Paso 1: Petición

+

+ Para llevar a cabo la solicitud de estado, necesitarás el token correspondiente a la transacción + de la cual deseas obtener información. Utiliza este token para realizar una llamada a + Transaction.status(). +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.status(token);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con la siguiente información. Asegúrate de guardar estos detalles; lo único que necesitas + validar es que el campo "response_code" sea igual a cero. +

+ +
+
+
From 4daaf6b3a79ded3420f2aa02e8fcc925d286d381 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 18 Feb 2026 18:14:57 -0300 Subject: [PATCH 10/78] feat: update navigation labels for transaction confirmation and refund --- .../example/controllers/TransaccionCompletaController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index bd3e34f..e0fc812 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -104,7 +104,7 @@ public String create( Model model ) throws TransactionCreateException, IOException { model.addAttribute("navigation", NAV_CREATE); - addProductAndBreadcrumbs(model, "Crear Transacción", BASE_URL + "/create"); + addProductAndBreadcrumbs(model, "Crear transacción", BASE_URL + "/create"); String cardNumber = number.replaceAll("\\s+", ""); String[] expiryParts = expiry.split("/"); @@ -150,7 +150,7 @@ public String commit( Model model ) throws TransactionCommitException, IOException { model.addAttribute("navigation", NAV_COMMIT); - addProductAndBreadcrumbs(model, "Confirmar", BASE_URL + "/commit"); + addProductAndBreadcrumbs(model, "Confirmar transacción", BASE_URL + "/commit"); Byte deferredPeriodIndex = null; Boolean gracePeriod = Boolean.FALSE; @@ -189,7 +189,7 @@ public String refund( Model model ) throws TransactionRefundException, IOException { model.addAttribute("navigation", NAV_REFUND); - addProductAndBreadcrumbs(model, "Reversa", BASE_URL + "/refund"); + addProductAndBreadcrumbs(model, "Reembolsar", BASE_URL + "/refund"); var resp = tx.refund(token, amount); model.addAttribute("request_token", token); From 7460e2bcfbd2c4e568e84b2bd0785ffe9cfebd76 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 11:52:50 -0300 Subject: [PATCH 11/78] feat: replace Random with ThreadLocalRandom for amount generation --- .../example/controllers/TransaccionCompletaController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index e0fc812..1caa756 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -15,7 +15,7 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; @Log4j2 @Controller @@ -114,7 +114,7 @@ public String create( String buyOrder = "O-" + getRandomNumber(); String sessionId = "S-" + getRandomNumber(); - double amount = 1000 + new Random().nextInt(1001); + double amount = 1000 + ThreadLocalRandom.current().nextInt(1001); var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); req.getSession().setAttribute("transaccion_completa_amount", amount); From d4e7a6ef58b1a6341e46ceae510b6a76493fed71 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 12:57:01 -0300 Subject: [PATCH 12/78] feat: replace ThreadLocalRandom with SecureRandom for amount generation --- .../example/controllers/TransaccionCompletaController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 1caa756..3850e29 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -15,7 +15,7 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; +import java.security.SecureRandom; @Log4j2 @Controller @@ -32,6 +32,8 @@ public class TransaccionCompletaController extends BaseController { private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status"; private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund"; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final Map NAV_INDEX; private static final Map NAV_CREATE; private static final Map NAV_INSTALLMENTS; @@ -114,7 +116,7 @@ public String create( String buyOrder = "O-" + getRandomNumber(); String sessionId = "S-" + getRandomNumber(); - double amount = 1000 + ThreadLocalRandom.current().nextInt(1001); + double amount = 1000 + SECURE_RANDOM.nextInt(1001); var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); req.getSession().setAttribute("transaccion_completa_amount", amount); From 8026794b959e468248a343abc648eb7e50b5ab12 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 14:57:25 -0300 Subject: [PATCH 13/78] feat: ensure amount is a double for transaction creation --- .../example/controllers/TransaccionCompletaController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 3850e29..9b65f7e 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -116,7 +116,7 @@ public String create( String buyOrder = "O-" + getRandomNumber(); String sessionId = "S-" + getRandomNumber(); - double amount = 1000 + SECURE_RANDOM.nextInt(1001); + double amount = 1000.0 + SECURE_RANDOM.nextInt(1001); var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); req.getSession().setAttribute("transaccion_completa_amount", amount); From f6dc683b5260ca1075c98fd82b6a2580cbdc508b Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 15:25:17 -0300 Subject: [PATCH 14/78] feat: define a constant instead of duplicating string --- .../TransaccionCompletaController.java | 83 +++++++++++-------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 9b65f7e..3b0dfcf 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -32,6 +32,19 @@ public class TransaccionCompletaController extends BaseController { private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status"; private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund"; + private static final String NAV_LABEL_FORM = "Formulario"; + private static final String NAV_LABEL_REQUEST = "Petición"; + private static final String NAV_LABEL_RESPONSE = "Respuesta"; + + private static final String ATTR_NAVIGATION = "navigation"; + private static final String ATTR_PRODUCT = "product"; + private static final String ATTR_BREADCRUMBS = "breadcrumbs"; + private static final String ATTR_RESPONSE_DATA = "response_data"; + private static final String ATTR_RESPONSE_DATA_JSON = "response_data_json"; + private static final String ATTR_REQUEST_TOKEN = "request_token"; + private static final String ATTR_AMOUNT = "amount"; + private static final String ATTR_ERROR = "error"; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final Map NAV_INDEX; @@ -43,26 +56,26 @@ public class TransaccionCompletaController extends BaseController { static { NAV_INDEX = new LinkedHashMap<>(); - NAV_INDEX.put("form", "Formulario"); + NAV_INDEX.put("form", NAV_LABEL_FORM); NAV_CREATE = new LinkedHashMap<>(); - NAV_CREATE.put("request", "Petición"); - NAV_CREATE.put("response", "Respuesta"); - NAV_CREATE.put("form", "Formulario"); + NAV_CREATE.put("request", NAV_LABEL_REQUEST); + NAV_CREATE.put("response", NAV_LABEL_RESPONSE); + NAV_CREATE.put("form", NAV_LABEL_FORM); NAV_INSTALLMENTS = new LinkedHashMap<>(); - NAV_INSTALLMENTS.put("request", "Petición"); - NAV_INSTALLMENTS.put("response", "Respuesta"); - NAV_INSTALLMENTS.put("form", "Formulario"); + NAV_INSTALLMENTS.put("request", NAV_LABEL_REQUEST); + NAV_INSTALLMENTS.put("response", NAV_LABEL_RESPONSE); + NAV_INSTALLMENTS.put("form", NAV_LABEL_FORM); NAV_COMMIT = new LinkedHashMap<>(); - NAV_COMMIT.put("request", "Petición"); - NAV_COMMIT.put("response", "Respuesta"); - NAV_COMMIT.put("form", "Formulario"); + NAV_COMMIT.put("request", NAV_LABEL_REQUEST); + NAV_COMMIT.put("response", NAV_LABEL_RESPONSE); + NAV_COMMIT.put("form", NAV_LABEL_FORM); NAV_STATUS = new LinkedHashMap<>(); - NAV_STATUS.put("request", "Petición"); - NAV_STATUS.put("response", "Respuesta"); + NAV_STATUS.put("request", NAV_LABEL_REQUEST); + NAV_STATUS.put("response", NAV_LABEL_RESPONSE); NAV_REFUND = NAV_STATUS; } @@ -86,13 +99,13 @@ private void addProductAndBreadcrumbs(Model model, String label, String url) { if (label != null) { breadcrumbs.put(label, url); } - model.addAttribute("product", PRODUCT); - model.addAttribute("breadcrumbs", breadcrumbs); + model.addAttribute(ATTR_PRODUCT, PRODUCT); + model.addAttribute(ATTR_BREADCRUMBS, breadcrumbs); } @GetMapping("") public String index(Model model) { - model.addAttribute("navigation", NAV_INDEX); + model.addAttribute(ATTR_NAVIGATION, NAV_INDEX); addProductAndBreadcrumbs(model, null, null); return VIEW_INDEX; } @@ -105,7 +118,7 @@ public String create( @RequestParam("cvc") String cvc, Model model ) throws TransactionCreateException, IOException { - model.addAttribute("navigation", NAV_CREATE); + model.addAttribute(ATTR_NAVIGATION, NAV_CREATE); addProductAndBreadcrumbs(model, "Crear transacción", BASE_URL + "/create"); String cardNumber = number.replaceAll("\\s+", ""); @@ -121,8 +134,8 @@ public String create( var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); req.getSession().setAttribute("transaccion_completa_amount", amount); - model.addAttribute("response_data", resp); - model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); return VIEW_CREATE; } @@ -133,13 +146,13 @@ public String installments( @RequestParam("installments_number") byte installmentsNumber, Model model ) throws TransactionInstallmentException, IOException { - model.addAttribute("navigation", NAV_INSTALLMENTS); + model.addAttribute(ATTR_NAVIGATION, NAV_INSTALLMENTS); addProductAndBreadcrumbs(model, "Consulta de cuotas", BASE_URL + "/installments"); var resp = tx.installments(token, installmentsNumber); - model.addAttribute("request_token", token); - model.addAttribute("response_data", resp); - model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); return VIEW_INSTALLMENTS; } @@ -151,7 +164,7 @@ public String commit( @RequestParam(value = "idQueryInstallments", required = false) Long idQueryInstallments, Model model ) throws TransactionCommitException, IOException { - model.addAttribute("navigation", NAV_COMMIT); + model.addAttribute(ATTR_NAVIGATION, NAV_COMMIT); addProductAndBreadcrumbs(model, "Confirmar transacción", BASE_URL + "/commit"); Byte deferredPeriodIndex = null; @@ -161,10 +174,10 @@ public String commit( Object amount = req.getSession().getAttribute("transaccion_completa_amount"); req.getSession().removeAttribute("transaccion_completa_amount"); - model.addAttribute("amount", amount); - model.addAttribute("request_token", token); - model.addAttribute("response_data", resp); - model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute(ATTR_AMOUNT, amount); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); return VIEW_COMMIT; } @@ -174,12 +187,12 @@ public String status( @RequestParam("token") String token, Model model ) throws TransactionStatusException, IOException { - model.addAttribute("navigation", NAV_STATUS); + model.addAttribute(ATTR_NAVIGATION, NAV_STATUS); addProductAndBreadcrumbs(model, "Estado de transacción", BASE_URL + "/status"); var resp = tx.status(token); - model.addAttribute("response_data", resp); - model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); return VIEW_STATUS; } @@ -190,13 +203,13 @@ public String refund( @RequestParam("amount") double amount, Model model ) throws TransactionRefundException, IOException { - model.addAttribute("navigation", NAV_REFUND); + model.addAttribute(ATTR_NAVIGATION, NAV_REFUND); addProductAndBreadcrumbs(model, "Reembolsar", BASE_URL + "/refund"); var resp = tx.refund(token, amount); - model.addAttribute("request_token", token); - model.addAttribute("response_data", resp); - model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); return VIEW_REFUND; } @@ -204,7 +217,7 @@ public String refund( @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute(ATTR_ERROR, e.getMessage()); return VIEW_ERROR; } } From a2b26dceef47bba34e5037d13ea7d73b62af7c6f Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 17:21:35 -0300 Subject: [PATCH 15/78] feat: refactor navigation maps and session attribute handling in TransaccionCompletaController --- .../TransaccionCompletaController.java | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 3b0dfcf..7bdbd30 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -45,39 +45,30 @@ public class TransaccionCompletaController extends BaseController { private static final String ATTR_AMOUNT = "amount"; private static final String ATTR_ERROR = "error"; + private static final String SESSION_AMOUNT = "transaccion_completa_amount"; + private static final String NAV_KEY_REQUEST = "request"; + private static final String NAV_KEY_RESPONSE = "response"; + private static final String NAV_KEY_FORM = "form"; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - private static final Map NAV_INDEX; - private static final Map NAV_CREATE; - private static final Map NAV_INSTALLMENTS; - private static final Map NAV_COMMIT; - private static final Map NAV_STATUS; - private static final Map NAV_REFUND; - - static { - NAV_INDEX = new LinkedHashMap<>(); - NAV_INDEX.put("form", NAV_LABEL_FORM); - - NAV_CREATE = new LinkedHashMap<>(); - NAV_CREATE.put("request", NAV_LABEL_REQUEST); - NAV_CREATE.put("response", NAV_LABEL_RESPONSE); - NAV_CREATE.put("form", NAV_LABEL_FORM); - - NAV_INSTALLMENTS = new LinkedHashMap<>(); - NAV_INSTALLMENTS.put("request", NAV_LABEL_REQUEST); - NAV_INSTALLMENTS.put("response", NAV_LABEL_RESPONSE); - NAV_INSTALLMENTS.put("form", NAV_LABEL_FORM); - - NAV_COMMIT = new LinkedHashMap<>(); - NAV_COMMIT.put("request", NAV_LABEL_REQUEST); - NAV_COMMIT.put("response", NAV_LABEL_RESPONSE); - NAV_COMMIT.put("form", NAV_LABEL_FORM); - - NAV_STATUS = new LinkedHashMap<>(); - NAV_STATUS.put("request", NAV_LABEL_REQUEST); - NAV_STATUS.put("response", NAV_LABEL_RESPONSE); - - NAV_REFUND = NAV_STATUS; + private static final Map NAV_INDEX = createNav(NAV_KEY_FORM); + private static final Map NAV_CREATE = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_INSTALLMENTS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_COMMIT = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_STATUS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE); + private static final Map NAV_REFUND = NAV_STATUS; + + private static Map createNav(String... keys) { + Map nav = new LinkedHashMap<>(); + for (String key : keys) { + switch (key) { + case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); + case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); + case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); + } + } + return nav; } private final FullTransaction tx; @@ -132,7 +123,7 @@ public String create( double amount = 1000.0 + SECURE_RANDOM.nextInt(1001); var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); - req.getSession().setAttribute("transaccion_completa_amount", amount); + req.getSession().setAttribute(SESSION_AMOUNT, amount); model.addAttribute(ATTR_RESPONSE_DATA, resp); model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); @@ -171,8 +162,8 @@ public String commit( Boolean gracePeriod = Boolean.FALSE; var resp = tx.commit(token, idQueryInstallments, deferredPeriodIndex, gracePeriod); - Object amount = req.getSession().getAttribute("transaccion_completa_amount"); - req.getSession().removeAttribute("transaccion_completa_amount"); + Object amount = req.getSession().getAttribute(SESSION_AMOUNT); + req.getSession().removeAttribute(SESSION_AMOUNT); model.addAttribute(ATTR_AMOUNT, amount); model.addAttribute(ATTR_REQUEST_TOKEN, token); From ff2cf2afd24908d94c5df622c174f7649b6c3382 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 17:28:40 -0300 Subject: [PATCH 16/78] feat: add default case to navigation map switch statement in TransaccionCompletaController --- .../example/controllers/TransaccionCompletaController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 7bdbd30..3044682 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -66,6 +66,7 @@ private static Map createNav(String... keys) { case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); + default -> ""; } } return nav; From 6ee86a6263ec1dcbc7786119008443a8a13f197f Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 19 Feb 2026 17:30:50 -0300 Subject: [PATCH 17/78] feat: update default case in navigation map switch statement to an empty block in TransaccionCompletaController --- .../example/controllers/TransaccionCompletaController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 3044682..312b717 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -66,7 +66,7 @@ private static Map createNav(String... keys) { case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); - default -> ""; + default -> { } } } return nav; From 49cf76f682740cff0d65b764f2e64d711abb5105 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:19:36 -0300 Subject: [PATCH 18/78] feat: add TransaccionCompletaDiferidaController for deferred transaction handling --- ...TransaccionCompletaDiferidaController.java | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java new file mode 100644 index 0000000..a2e28a6 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java @@ -0,0 +1,237 @@ +package cl.transbank.webpay.example.controllers; + +import cl.transbank.common.IntegrationApiKeys; +import cl.transbank.common.IntegrationCommerceCodes; +import cl.transbank.common.IntegrationType; +import cl.transbank.webpay.common.WebpayOptions; +import cl.transbank.webpay.exception.*; +import cl.transbank.webpay.transaccioncompleta.FullTransaction; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.LinkedHashMap; +import java.util.Map; + +@Log4j2 +@Controller +@RequestMapping("/transaccion-completa-diferida") +public class TransaccionCompletaDiferidaController extends BaseController { + private static final String TEMPLATE_FOLDER = "transaccion_completa_diferida"; + private static final String BASE_URL = "/transaccion-completa-diferida"; + private static final String PRODUCT = "Webpay Transacción Completa Diferida"; + + private static final String VIEW_INDEX = TEMPLATE_FOLDER + "/index"; + private static final String VIEW_CREATE = TEMPLATE_FOLDER + "/create"; + private static final String VIEW_INSTALLMENTS = TEMPLATE_FOLDER + "/installments"; + private static final String VIEW_COMMIT = TEMPLATE_FOLDER + "/commit"; + private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status"; + private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund"; + private static final String VIEW_CAPTURE = TEMPLATE_FOLDER + "/capture"; + + private static final String NAV_LABEL_FORM = "Formulario"; + private static final String NAV_LABEL_REQUEST = "Petición"; + private static final String NAV_LABEL_RESPONSE = "Respuesta"; + + private static final String ATTR_NAVIGATION = "navigation"; + private static final String ATTR_PRODUCT = "product"; + private static final String ATTR_BREADCRUMBS = "breadcrumbs"; + private static final String ATTR_RESPONSE_DATA = "response_data"; + private static final String ATTR_RESPONSE_DATA_JSON = "response_data_json"; + private static final String ATTR_REQUEST_TOKEN = "request_token"; + private static final String ATTR_AMOUNT = "amount"; + private static final String ATTR_ERROR = "error"; + + private static final String SESSION_AMOUNT = "transaccion_completa_diferida_amount"; + private static final String NAV_KEY_REQUEST = "request"; + private static final String NAV_KEY_RESPONSE = "response"; + private static final String NAV_KEY_FORM = "form"; + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + private static final Map NAV_INDEX = createNav(NAV_KEY_FORM); + private static final Map NAV_CREATE = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_INSTALLMENTS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_COMMIT = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_STATUS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE); + private static final Map NAV_CAPTURE = NAV_STATUS; + private static final Map NAV_REFUND = NAV_STATUS; + + private static Map createNav(String... keys) { + Map nav = new LinkedHashMap<>(); + for (String key : keys) { + switch (key) { + case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); + case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); + case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); + default -> { } + } + } + return nav; + } + + private final FullTransaction tx; + + public TransaccionCompletaDiferidaController() { + this.tx = new FullTransaction( + new WebpayOptions( + IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED, + IntegrationApiKeys.WEBPAY, + IntegrationType.TEST + ) + ); + } + + private void addProductAndBreadcrumbs(Model model, String label, String url) { + var breadcrumbs = new LinkedHashMap(); + breadcrumbs.put("Inicio", "/"); + breadcrumbs.put(PRODUCT, BASE_URL); + if (label != null) { + breadcrumbs.put(label, url); + } + model.addAttribute(ATTR_PRODUCT, PRODUCT); + model.addAttribute(ATTR_BREADCRUMBS, breadcrumbs); + } + + @GetMapping("") + public String index(Model model) { + model.addAttribute(ATTR_NAVIGATION, NAV_INDEX); + addProductAndBreadcrumbs(model, null, null); + return VIEW_INDEX; + } + + @PostMapping("/create") + public String create( + HttpServletRequest req, + @RequestParam("number") String number, + @RequestParam("expiry") String expiry, + @RequestParam("cvc") String cvc, + Model model + ) throws TransactionCreateException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_CREATE); + addProductAndBreadcrumbs(model, "Crear transacción", BASE_URL + "/create"); + + String cardNumber = number.replaceAll("\\s+", ""); + String[] expiryParts = expiry.split("/"); + String month = expiryParts.length > 0 ? expiryParts[0] : ""; + String year = expiryParts.length > 1 ? expiryParts[1] : ""; + String cardExpiry = year + "/" + month; + + String buyOrder = "O-" + getRandomNumber(); + String sessionId = "S-" + getRandomNumber(); + double amount = 1000.0 + SECURE_RANDOM.nextInt(1001); + + var resp = tx.create(buyOrder, sessionId, amount, Short.parseShort(cvc), cardNumber, cardExpiry); + req.getSession().setAttribute(SESSION_AMOUNT, amount); + + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_CREATE; + } + + @PostMapping("/installments") + public String installments( + @RequestParam("token") String token, + @RequestParam("installments_number") byte installmentsNumber, + Model model + ) throws TransactionInstallmentException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_INSTALLMENTS); + addProductAndBreadcrumbs(model, "Consulta de cuotas", BASE_URL + "/installments"); + + var resp = tx.installments(token, installmentsNumber); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_INSTALLMENTS; + } + + @GetMapping("/commit") + public String commit( + HttpServletRequest req, + @RequestParam("token") String token, + @RequestParam(value = "idQueryInstallments", required = false) Long idQueryInstallments, + @RequestParam(value = "deferredPeriodIndex", required = false) Byte deferredPeriodIndex, + @RequestParam(value = "gracePeriod", required = false) Boolean gracePeriod, + Model model + ) throws TransactionCommitException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_COMMIT); + addProductAndBreadcrumbs(model, "Confirmar transacción", BASE_URL + "/commit"); + + Boolean safeGracePeriod = gracePeriod != null ? gracePeriod : Boolean.FALSE; + var resp = tx.commit(token, idQueryInstallments, deferredPeriodIndex, safeGracePeriod); + Object amount = req.getSession().getAttribute(SESSION_AMOUNT); + req.getSession().removeAttribute(SESSION_AMOUNT); + + model.addAttribute(ATTR_AMOUNT, amount); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_COMMIT; + } + + @GetMapping("/capture") + public String capture( + @RequestParam("token") String token, + @RequestParam("buy_order") String buyOrder, + @RequestParam("authorization_code") String authorizationCode, + @RequestParam("amount") double amount, + Model model + ) throws TransactionCaptureException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_CAPTURE); + addProductAndBreadcrumbs(model, "Capturar", BASE_URL + "/capture"); + + var resp = tx.capture(token, buyOrder, authorizationCode, amount); + model.addAttribute(ATTR_AMOUNT, amount); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_CAPTURE; + } + + @GetMapping("/status") + public String status( + @RequestParam("token") String token, + Model model + ) throws TransactionStatusException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_STATUS); + addProductAndBreadcrumbs(model, "Estado de transacción", BASE_URL + "/status"); + + var resp = tx.status(token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_STATUS; + } + + @GetMapping("/refund") + public String refund( + @RequestParam("token") String token, + @RequestParam("amount") double amount, + Model model + ) throws TransactionRefundException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_REFUND); + addProductAndBreadcrumbs(model, "Reembolsar", BASE_URL + "/refund"); + + var resp = tx.refund(token, amount); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_REFUND; + } + + @ExceptionHandler(Exception.class) + public String handleException(Exception e, Model model) { + log.error("Error inesperado", e); + model.addAttribute(ATTR_ERROR, e.getMessage()); + return VIEW_ERROR; + } +} From e6fc12baa796c4c37c869d4750595279672e0586 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:19:49 -0300 Subject: [PATCH 19/78] =?UTF-8?q?feat:=20add=20form=20for=20Transacci?= =?UTF-8?q?=C3=B3n=20Completa=20Diferida=20with=20card=20input=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaccion_completa_diferida/index.html | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/index.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/index.html b/src/main/resources/templates/transaccion_completa_diferida/index.html new file mode 100644 index 0000000..ef46935 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/index.html @@ -0,0 +1,119 @@ +
+
+

Transacción Completa Diferida - Formulario

+

+ En esta primera etapa necesitas obtener los datos esenciales de la tarjeta + de crédito, débito o prepago del titular. Utiliza el formulario para + recolectar esta información de manera segura. +

+ +
+
+
+
+
+
+ + +
+ +
+
+ + +
+
+ + + +
+
+
+ + +
+
+
+ + +
+
From 69759c69e1f4b52c4b875355ab9cfd7a70fc8426 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:19:59 -0300 Subject: [PATCH 20/78] =?UTF-8?q?feat:=20add=20create=20transaction=20form?= =?UTF-8?q?=20for=20Transacci=C3=B3n=20Completa=20Diferida=20with=20instal?= =?UTF-8?q?lments=20and=20confirmation=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaccion_completa_diferida/create.html | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/create.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/create.html b/src/main/resources/templates/transaccion_completa_diferida/create.html new file mode 100644 index 0000000..b7fb3e8 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/create.html @@ -0,0 +1,65 @@ +
+
+

Transacción Completa Diferida - Crear transacción

+

+ En este paso sucede la creación de la transacción con el objetivo de obtener un identificador único para la misma. +

+ +

Paso 1: Petición

+

+ Comienza importando la librería TransaccionCompleta, y a continuación, crea la transacción necesaria. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.create(buyOrder, sessionId, amount, cvv, cardNumber, cardExpiry);
+    
+ +

Paso 2: Respuesta

+

Una vez creada la transacción, recibirás los siguientes datos de respuesta:

+ +
+ +

¡Transacción creada!

+

Ahora que hemos creado la transacción, se abren dos opciones para continuar:

+
    +
  • + Consultar Cuotas (opcional): Alternativamente puedes realizar consultas de cuotas + para ofrecer opciones de pago a plazos. +
  • +
  • + Confirmar Transacción: Debes confirmar directamente la transacción para + finalizar con el proceso de pago. +
  • +
+ +
+
+ Formulario de redirección + +
+ + + +
+ +
+
+
+
From f7b3d5d8de049d8cf88171c27f4f1a179e7e3da6 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:20:10 -0300 Subject: [PATCH 21/78] =?UTF-8?q?feat:=20add=20confirmation=20step=20for?= =?UTF-8?q?=20Transacci=C3=B3n=20Completa=20Diferida=20with=20request=20an?= =?UTF-8?q?d=20response=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaccion_completa_diferida/commit.html | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/commit.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/commit.html b/src/main/resources/templates/transaccion_completa_diferida/commit.html new file mode 100644 index 0000000..14bb893 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/commit.html @@ -0,0 +1,74 @@ +
+
+

Transacción Completa Diferida - Confirmar transacción

+

+ En este paso crucial, procederemos a confirmar la transacción con el objetivo de notificar a + Transbank que hemos recibido la transacción de manera exitosa. Es fundamental destacar que si no se confirma la + transacción, esta será caducada. +

+ +

Paso 1: Petición

+

+ Para confirmar la transacción, debes enviar el token correspondiente. En el caso de pagos a + plazos, también debes incluir el ID de la consulta de cuotas. En algunos casos, será necesario proporcionar el + índice del periodo diferido y un valor boolean indicando si se tomará el periodo de gracia. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+var resp = tx.commit(token, idQueryInstallments, deferredPeriodIndex, gracePeriod);
+    
+ +

Paso 2: Respuesta

+

+ Una vez que la transacción ha sido confirmada Transbank proporcionará la siguiente información. Es fundamental + conservar esta respuesta y verificar que el campo "response_code" tenga un valor de cero y que el campo "status" + sea "AUTHORIZED". +

+ +
+ +

¡Transacción confirmada!

+

+ Ahora que se ha confirmado la transacción, puedes capturar el monto previamente autorizado. El monto a capturar + puede ser igual o menor al monto autorizado. La captura debe efectuarse dentro de un plazo máximo de 7 días + calendario. +

+ +
    +
  • Capturar: Captura un monto igual o menor al previamente autorizado.
  • +
  • Consultar Estado: Hasta 7 días después de realizada la transacción, podrás + consultar el estado de la transacción.
  • +
+ +
+
+
+ + + + + +
+ + +
+
+ + CONSULTAR ESTADO +
+
From 59e39582c29e46e0804c64663dda4d851dd547ef Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:20:15 -0300 Subject: [PATCH 22/78] =?UTF-8?q?feat:=20add=20installments=20consultation?= =?UTF-8?q?=20step=20for=20Transacci=C3=B3n=20Completa=20Diferida=20with?= =?UTF-8?q?=20confirmation=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../installments.html | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/installments.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/installments.html b/src/main/resources/templates/transaccion_completa_diferida/installments.html new file mode 100644 index 0000000..9272670 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/installments.html @@ -0,0 +1,81 @@ +
+
+

Transacción Completa Diferida - Consulta de cuotas

+

+ En esta etapa, realizaremos una consulta de cuotas para conocer sus condiciones. Es importante + destacar que este paso es opcional y se utiliza únicamente si deseas ofrecer opciones de pago a plazos. +

+ +

Paso 1: Petición

+

Para llevar a cabo la consulta de cuotas, debemos enviar los siguientes datos relevantes.

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.installments(token, installments);
+    
+ +

Paso 2: Respuesta

+

Una vez realizada la consulta de cuotas, recibirás los siguientes datos de respuesta:

+ +
+ +

Confirmar Transacción

+

+ Si decides utilizar cuotas y estás satisfecho con las condiciones obtenidas en la consulta, el + siguiente paso sería confirmar la transacción. +

+ +
+
+ + +
+
+ + +
+ + +
+ + +
+
From ab5500d79d4323ab5d44157066c1e17779c5ddf0 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:20:22 -0300 Subject: [PATCH 23/78] =?UTF-8?q?feat:=20add=20capture=20transaction=20ste?= =?UTF-8?q?p=20for=20Transacci=C3=B3n=20Completa=20Diferida=20with=20reque?= =?UTF-8?q?st=20and=20response=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capture.html | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/capture.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/capture.html b/src/main/resources/templates/transaccion_completa_diferida/capture.html new file mode 100644 index 0000000..c5a439b --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/capture.html @@ -0,0 +1,67 @@ +
+
+

Transacción Completa Diferida - Capturar transacción

+

+ En este paso debemos capturar la transacción para realmente capturar el dinero que habia sido previamente + reservado al hacer la transacción. +

+ +

Paso 1: Petición

+

+ Para capturar una transacción, necesitaremos el Token, la Orden de compra, el Código de autorización y el monto + a capturar. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+var resp = tx.capture(token, buyOrder, authorizationCode, amount);
+    
+ +

Paso 2: Respuesta

+

Una vez capturada la transacción, recibirás los siguientes datos de respuesta.

+ +
+ +

¡Listo!

+

+ Con la transacción capturada, puedes mostrar al usuario una página de éxito de la transacción, proporcionándole + la confirmación de que el proceso se ha completado con éxito. +

+ +

Después de capturar la transacción, podrás realizar otras operaciones útiles:

+
    +
  • Reembolsar: Puedes reversar o anular el pago según ciertas condiciones + comerciales.
  • +
  • Consultar Estado: Hasta 7 días después de realizada la transacción, podrás + consultar el estado de la transacción.
  • +
+ +
+
+
+ + + +
+ + +
+
+ + CONSULTAR ESTADO +
+
From ca46e58051d952b56869be4a1fcd116ad4625dac Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:20:27 -0300 Subject: [PATCH 24/78] =?UTF-8?q?feat:=20add=20refund=20step=20for=20Trans?= =?UTF-8?q?acci=C3=B3n=20Completa=20Diferida=20with=20request=20and=20resp?= =?UTF-8?q?onse=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaccion_completa_diferida/refund.html | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/refund.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/refund.html b/src/main/resources/templates/transaccion_completa_diferida/refund.html new file mode 100644 index 0000000..b776498 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/refund.html @@ -0,0 +1,51 @@ +
+
+

Transacción Completa Diferida - Reembolsar

+

+ En esta etapa, tendrás la posibilidad de solicitar el reembolso del dinero al tarjeta habiente. El + tipo de reembolso (Reversa, Anulación o Anulación parcial) dependerá del monto y el tiempo transcurrido desde la + transacción. +

+ +

Paso 1: Petición

+

+ Para efectuar la solicitud de reembolso, necesitarás el token de la transacción y el monto que + deseas reversar. Si decides anular el monto total, puede resultar en una Reversa o Anulación, según ciertas + condiciones. En caso de un monto menor al total, se realizará una Anulación parcial. Las anulaciones parciales + para tarjetas débito y prepago no están soportadas. +

+

+ En este + link + podrás ver mayor información sobre las condiciones y casos para anular o reversar + transacciones. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+var resp = tx.refund(token, amount);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con el resultado de la reversa o anulación. Evalúa cuidadosamente esta respuesta para + confirmar que el reembolso se haya procesado de manera efectiva. +

+ +
+ + CONSULTAR ESTADO +
+
From 85bf34f89336c03105bd21fa9706ab25351c7d52 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 12:20:32 -0300 Subject: [PATCH 25/78] =?UTF-8?q?feat:=20add=20status=20page=20for=20Trans?= =?UTF-8?q?acci=C3=B3n=20Completa=20Diferida=20with=20request=20and=20resp?= =?UTF-8?q?onse=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transaccion_completa_diferida/status.html | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_diferida/status.html diff --git a/src/main/resources/templates/transaccion_completa_diferida/status.html b/src/main/resources/templates/transaccion_completa_diferida/status.html new file mode 100644 index 0000000..87a6187 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/status.html @@ -0,0 +1,44 @@ +
+
+

Transacción Completa Diferida - Estado de transacción

+

+ En esta fase, tendrás la capacidad de solicitar el estado actual de una transacción hasta 7 días + después de su realización. Es importante destacar que no hay límite en la cantidad de solicitudes de este tipo + durante este período. Sin embargo, una vez transcurridos los 7 días, ya no podrás revisar el estado de la + transacción. +

+ +

Paso 1: Petición

+

+ Para llevar a cabo la solicitud de estado, necesitarás el token correspondiente a la transacción + de la cual deseas obtener información. Utiliza este token para realizar una llamada a + Transaction.status(). +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.FullTransaction;
+
+FullTransaction tx = new FullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.status(token);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con la siguiente información. Asegúrate de guardar estos detalles; lo único que necesitas + validar es que el campo "response_code" sea igual a cero. +

+ +
+
+
From 319662dbd3ea2688fe65a42f994d00d74d576f1d Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Fri, 20 Feb 2026 13:56:20 -0300 Subject: [PATCH 26/78] =?UTF-8?q?feat:=20refactor=20navigation=20label=20c?= =?UTF-8?q?reation=20in=20Transacci=C3=B3n=20Completa=20Diferida=20control?= =?UTF-8?q?ler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TransaccionCompletaDiferidaController.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java index a2e28a6..5bc694e 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java @@ -64,16 +64,23 @@ public class TransaccionCompletaDiferidaController extends BaseController { private static Map createNav(String... keys) { Map nav = new LinkedHashMap<>(); for (String key : keys) { - switch (key) { - case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); - case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); - case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); - default -> { } + String label = getNavLabel(key); + if (label != null) { + nav.put(key, label); } } return nav; } + private static String getNavLabel(String key) { + return switch (key) { + case NAV_KEY_REQUEST -> NAV_LABEL_REQUEST; + case NAV_KEY_RESPONSE -> NAV_LABEL_RESPONSE; + case NAV_KEY_FORM -> NAV_LABEL_FORM; + default -> null; + }; + } + private final FullTransaction tx; public TransaccionCompletaDiferidaController() { From 058621fec8467ff44ddff6dce1d22e0e397ee011 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 12 Mar 2026 14:33:32 -0300 Subject: [PATCH 27/78] feat: improve amount formatting in authorization forms and enhance error handling --- .../example/controllers/BaseController.java | 13 +- .../templates/oneclick_mall/authorize.html | 2 +- .../oneclick_mall_deferred/authorize.html | 176 ++++++++++-------- 3 files changed, 116 insertions(+), 75 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java index 3bae43e..70ed48c 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java @@ -1,7 +1,10 @@ package cl.transbank.webpay.example.controllers; import com.google.gson.GsonBuilder; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializer; +import java.math.BigDecimal; import java.util.Random; public abstract class BaseController { @@ -13,8 +16,16 @@ public abstract class BaseController { protected static final String VIEW_RECOVER_ERROR = "error/oneclick/recover"; protected static final String VIEW_REJECTED_ERROR = "error/oneclick/rejected"; + private static final JsonSerializer DOUBLE_SERIALIZER = (value, type, ctx) -> + new JsonPrimitive(new BigDecimal(value.toString()).stripTrailingZeros().toPlainString()); + public String toJson(Object obj) { - return (new GsonBuilder().setPrettyPrinting().create()).toJson(obj); + return new GsonBuilder() + .setPrettyPrinting() + .registerTypeAdapter(Double.class, DOUBLE_SERIALIZER) + .registerTypeAdapter(double.class, DOUBLE_SERIALIZER) + .create() + .toJson(obj); } protected String getRandomNumber() { diff --git a/src/main/resources/templates/oneclick_mall/authorize.html b/src/main/resources/templates/oneclick_mall/authorize.html index 9a0f752..9575bc9 100644 --- a/src/main/resources/templates/oneclick_mall/authorize.html +++ b/src/main/resources/templates/oneclick_mall/authorize.html @@ -120,7 +120,7 @@

Otras utilidades

class="tbk-input-text" type="text" name="amount" - th:value="${detail.amount}" + th:value="${T(java.math.BigDecimal).valueOf(detail.amount).stripTrailingZeros().toPlainString()}" />
diff --git a/src/main/resources/templates/oneclick_mall_deferred/authorize.html b/src/main/resources/templates/oneclick_mall_deferred/authorize.html index e39a89c..221dc6b 100644 --- a/src/main/resources/templates/oneclick_mall_deferred/authorize.html +++ b/src/main/resources/templates/oneclick_mall_deferred/authorize.html @@ -1,21 +1,21 @@
-
+
+

Webpay Oneclick Mall Diferido - Autorizar pago

-

Webpay Oneclick Mall Diferido - Autorizar pago

+

+ En este primer paso, procederemos a autorizar una transacción en la + tarjeta que ha sido previamente inscrita. +

-

- En este primer paso, procederemos a autorizar una transacción en la tarjeta que ha sido previamente - inscrita. -

+

Paso 1: Petición

-

Paso 1: Petición

+

+ Ahora que contamos con el username y el tbkUser obtenidos + durante la inscripción, estamos listos para autorizar transacciones en la + tarjeta inscrita. +

-

- Ahora que contamos con el username y el tbkUser obtenidos durante la inscripción, - estamos listos para autorizar transacciones en la tarjeta inscrita. -

- -

+    

 var options = new WebpayOptions(
     IntegrationCommerceCodes.ONECLICK_MALL_DEFERRED,
     IntegrationApiKeys.WEBPAY,
@@ -39,66 +39,96 @@ 

Paso 1: Petición

var resp = transaction.authorize(username, tbkUser, buyOrder, details);
-

Paso 2: Respuesta

- -

- Una vez que la transacción ha sido autorizada, Transbank proporcionará la siguiente información. - Es fundamental conservar esta respuesta y verificar que el campo responseCode tenga un valor de - 0 y que el campo status sea AUTHORIZED. -

- -
- -

¡Casi listo!

- -

- Ya puedes mostrar al usuario una página de éxito de la transacción. - Debes tener en cuenta que la transacción aún no ha sido capturada, solo se ha retenido el saldo en la - tarjeta del Tarjetahabiente. -

- -
-
- - -
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
- - -
-
+

Paso 2: Respuesta

+ +

+ Una vez que la transacción ha sido autorizada, Transbank proporcionará la + siguiente información. Es fundamental conservar esta respuesta y verificar + que el campo responseCode tenga un valor de 0 y que el campo + status sea AUTHORIZED. +

+ +
+ +

¡Casi listo!

+ +

+ Ya puedes mostrar al usuario una página de éxito de la transacción. Debes + tener en cuenta que la transacción aún no ha sido capturada, solo se ha + retenido el saldo en la tarjeta del Tarjetahabiente. +

+ +
+
+ + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
- - - CONSULTAR ESTADO - - +
+ + + CONSULTAR ESTADO + +
From 70f0e17f7fe3b4cf7f7a909a65b6b4edb7dc8dc2 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 12 Mar 2026 16:05:21 -0300 Subject: [PATCH 28/78] feat: enhance DOUBLE_SERIALIZER to handle NaN and infinite values --- .../webpay/example/controllers/BaseController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java index 70ed48c..a228e22 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java @@ -16,8 +16,12 @@ public abstract class BaseController { protected static final String VIEW_RECOVER_ERROR = "error/oneclick/recover"; protected static final String VIEW_REJECTED_ERROR = "error/oneclick/rejected"; - private static final JsonSerializer DOUBLE_SERIALIZER = (value, type, ctx) -> - new JsonPrimitive(new BigDecimal(value.toString()).stripTrailingZeros().toPlainString()); + private static final JsonSerializer DOUBLE_SERIALIZER = (value, type, ctx) -> { + if (value.isNaN() || value.isInfinite()) { + return new JsonPrimitive(value); + } + return new JsonPrimitive(new BigDecimal(value.toString()).stripTrailingZeros().toPlainString()); + }; public String toJson(Object obj) { return new GsonBuilder() From 8f5a5411461716ad2bc8b5ac01426b7c5ec9fb55 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:18:52 -0300 Subject: [PATCH 29/78] feat: add TransaccionCompletaMallController for handling mall transactions --- .../TransaccionCompletaMallController.java | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java new file mode 100644 index 0000000..ec6682a --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -0,0 +1,280 @@ +package cl.transbank.webpay.example.controllers; + +import cl.transbank.common.IntegrationApiKeys; +import cl.transbank.common.IntegrationCommerceCodes; +import cl.transbank.common.IntegrationType; +import cl.transbank.model.MallTransactionCreateDetails; +import cl.transbank.webpay.common.WebpayOptions; +import cl.transbank.webpay.exception.*; +import cl.transbank.webpay.transaccioncompleta.MallFullTransaction; +import cl.transbank.webpay.transaccioncompleta.model.MallTransactionCommitDetails; +import cl.transbank.webpay.transaccioncompleta.responses.MallFullTransactionInstallmentsDetails; +import cl.transbank.webpay.example.models.MallDetailSession; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Log4j2 +@Controller +@RequestMapping("/transaccion-completa-mall") +public class TransaccionCompletaMallController extends BaseController { + private static final String TEMPLATE_FOLDER = "transaccion_completa_mall"; + private static final String BASE_URL = "/transaccion-completa-mall"; + private static final String PRODUCT = "Webpay Transacción Completa Mall"; + + private static final String VIEW_INDEX = TEMPLATE_FOLDER + "/index"; + private static final String VIEW_CREATE = TEMPLATE_FOLDER + "/create"; + private static final String VIEW_INSTALLMENTS = TEMPLATE_FOLDER + "/installments"; + private static final String VIEW_COMMIT = TEMPLATE_FOLDER + "/commit"; + private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status"; + private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund"; + + private static final String NAV_LABEL_FORM = "Formulario"; + private static final String NAV_LABEL_REQUEST = "Petición"; + private static final String NAV_LABEL_RESPONSE = "Respuesta"; + + private static final String ATTR_NAVIGATION = "navigation"; + private static final String ATTR_PRODUCT = "product"; + private static final String ATTR_BREADCRUMBS = "breadcrumbs"; + private static final String ATTR_RESPONSE_DATA = "response_data"; + private static final String ATTR_RESPONSE_DATA_JSON = "response_data_json"; + private static final String ATTR_REQUEST_TOKEN = "request_token"; + private static final String ATTR_ERROR = "error"; + private static final String ATTR_ID_QUERY_INSTALLMENTS = "id_query_installments"; + + private static final String SESSION_DETAILS = "transaccion_completa_mall_details"; + private static final String NAV_KEY_REQUEST = "request"; + private static final String NAV_KEY_RESPONSE = "response"; + private static final String NAV_KEY_FORM = "form"; + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + private static final Map NAV_INDEX = createNav(NAV_KEY_FORM); + private static final Map NAV_CREATE = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_INSTALLMENTS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_COMMIT = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); + private static final Map NAV_STATUS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE); + private static final Map NAV_REFUND = NAV_STATUS; + + private static Map createNav(String... keys) { + Map nav = new LinkedHashMap<>(); + for (String key : keys) { + switch (key) { + case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); + case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); + case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); + default -> { } + } + } + return nav; + } + + private final MallFullTransaction tx; + + public TransaccionCompletaMallController() { + this.tx = new MallFullTransaction( + new WebpayOptions( + IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL, + IntegrationApiKeys.WEBPAY, + IntegrationType.TEST + ) + ); + } + + private void addProductAndBreadcrumbs(Model model, String label, String url) { + var breadcrumbs = new LinkedHashMap(); + breadcrumbs.put("Inicio", "/"); + breadcrumbs.put(PRODUCT, BASE_URL); + if (label != null) { + breadcrumbs.put(label, url); + } + model.addAttribute(ATTR_PRODUCT, PRODUCT); + model.addAttribute(ATTR_BREADCRUMBS, breadcrumbs); + } + + @GetMapping("") + public String index(Model model) { + model.addAttribute(ATTR_NAVIGATION, NAV_INDEX); + addProductAndBreadcrumbs(model, null, null); + return VIEW_INDEX; + } + + @PostMapping("/create") + public String create( + HttpServletRequest req, + @RequestParam("number") String number, + @RequestParam("expiry") String expiry, + @RequestParam("cvc") String cvc, + Model model + ) throws TransactionCreateException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_CREATE); + addProductAndBreadcrumbs(model, "Crear transacción", BASE_URL + "/create"); + + String cardNumber = number.replaceAll("\\s+", ""); + String[] expiryParts = expiry.split("/"); + String month = expiryParts.length > 0 ? expiryParts[0] : ""; + String year = expiryParts.length > 1 ? expiryParts[1] : ""; + String cardExpiry = year + "/" + month; + + String buyOrder = "O-" + getRandomNumber(); + String sessionId = "S-" + getRandomNumber(); + + var sessionDetails = buildSessionDetails( + IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_CHILD1, + IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_CHILD2 + ); + + var details = MallTransactionCreateDetails.build() + .add(sessionDetails.get(0).getAmount(), sessionDetails.get(0).getCommerceCode(), sessionDetails.get(0).getBuyOrder()) + .add(sessionDetails.get(1).getAmount(), sessionDetails.get(1).getCommerceCode(), sessionDetails.get(1).getBuyOrder()); + + var resp = tx.create(buyOrder, sessionId, cardNumber, cardExpiry, details, Short.parseShort(cvc)); + req.getSession().setAttribute(SESSION_DETAILS, sessionDetails); + + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_CREATE; + } + + @PostMapping("/installments") + public String installments( + HttpServletRequest req, + @RequestParam("token") String token, + @RequestParam("installments_number") byte installmentsNumber, + Model model + ) throws TransactionInstallmentException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_INSTALLMENTS); + addProductAndBreadcrumbs(model, "Consulta de cuotas", BASE_URL + "/installments"); + + List sessionDetails = getSessionDetails(req); + if (sessionDetails.isEmpty()) { + model.addAttribute(ATTR_ERROR, "Debes crear la transacción antes de consultar cuotas."); + return VIEW_ERROR; + } + + var details = MallFullTransactionInstallmentsDetails.build() + .add(sessionDetails.get(0).getCommerceCode(), sessionDetails.get(0).getBuyOrder(), installmentsNumber) + .add(sessionDetails.get(1).getCommerceCode(), sessionDetails.get(1).getBuyOrder(), installmentsNumber); + + var resp = tx.installments(token, details); + Long idQueryInstallments = null; + if (resp != null && resp.getResponseList() != null && !resp.getResponseList().isEmpty()) { + idQueryInstallments = resp.getResponseList().get(0).getIdQueryInstallments(); + } + + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + model.addAttribute(ATTR_ID_QUERY_INSTALLMENTS, idQueryInstallments); + + return VIEW_INSTALLMENTS; + } + + @GetMapping("/commit") + public String commit( + HttpServletRequest req, + @RequestParam("token") String token, + @RequestParam(value = "idQueryInstallments", required = false) Long idQueryInstallments, + @RequestParam(value = "deferredPeriodIndex", required = false) Byte deferredPeriodIndex, + @RequestParam(value = "gracePeriod", required = false) Boolean gracePeriod, + Model model + ) throws TransactionCommitException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_COMMIT); + addProductAndBreadcrumbs(model, "Confirmar transacción", BASE_URL + "/commit"); + + List sessionDetails = getSessionDetails(req); + if (sessionDetails.isEmpty()) { + model.addAttribute(ATTR_ERROR, "Debes crear la transacción antes de confirmar."); + return VIEW_ERROR; + } + + boolean safeGracePeriod = gracePeriod != null ? gracePeriod : Boolean.FALSE; + + var details = MallTransactionCommitDetails.build() + .add(sessionDetails.get(0).getCommerceCode(), sessionDetails.get(0).getBuyOrder(), idQueryInstallments, deferredPeriodIndex, safeGracePeriod) + .add(sessionDetails.get(1).getCommerceCode(), sessionDetails.get(1).getBuyOrder(), idQueryInstallments, deferredPeriodIndex, safeGracePeriod); + + var resp = tx.commit(token, details); + + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_COMMIT; + } + + @GetMapping("/status") + public String status( + @RequestParam("token") String token, + Model model + ) throws TransactionStatusException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_STATUS); + addProductAndBreadcrumbs(model, "Estado de transacción", BASE_URL + "/status"); + + var resp = tx.status(token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_STATUS; + } + + @GetMapping("/refund") + public String refund( + @RequestParam("token") String token, + @RequestParam("buy_order") String buyOrder, + @RequestParam("commerce_code") String commerceCode, + @RequestParam("amount") double amount, + Model model + ) throws TransactionRefundException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_REFUND); + addProductAndBreadcrumbs(model, "Reembolsar", BASE_URL + "/refund"); + + var resp = tx.refund(token, buyOrder, commerceCode, amount); + model.addAttribute(ATTR_REQUEST_TOKEN, token); + model.addAttribute(ATTR_RESPONSE_DATA, resp); + model.addAttribute(ATTR_RESPONSE_DATA_JSON, toJson(resp)); + + return VIEW_REFUND; + } + + private List buildSessionDetails(String commerceCode1, String commerceCode2) { + List details = new ArrayList<>(); + details.add(new MallDetailSession( + 1000.0 + SECURE_RANDOM.nextInt(1001), + commerceCode1, + "O-" + getRandomNumber() + )); + details.add(new MallDetailSession( + 1000.0 + SECURE_RANDOM.nextInt(1001), + commerceCode2, + "O-" + getRandomNumber() + )); + return details; + } + + @SuppressWarnings("unchecked") + private List getSessionDetails(HttpServletRequest req) { + Object value = req.getSession().getAttribute(SESSION_DETAILS); + if (value instanceof List) { + return (List) value; + } + return new ArrayList<>(); + } + + @ExceptionHandler(Exception.class) + public String handleException(Exception e, Model model) { + log.error("Error inesperado", e); + model.addAttribute(ATTR_ERROR, e.getMessage()); + return VIEW_ERROR; + } +} From 5bef76b54919a32a1db7ab8db143f4ba51695015 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:19:07 -0300 Subject: [PATCH 30/78] feat: add loading button styles and functionality for form submissions --- src/main/resources/static/css/styles.css | 29 ++++++++++++++++++++ src/main/resources/static/js/form_loading.js | 18 ++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/main/resources/static/js/form_loading.js diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index dbccbe3..7164273 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -1244,3 +1244,32 @@ code { border: 1px solid #eee; border-radius: 4px; } + +.loading-button { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.loading-button .loading-spinner { + display: none; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.35); + border-top-color: #ffffff; + border-radius: 50%; + animation: capture-spin 0.7s linear infinite; +} + +.loading-button.is-loading .loading-spinner { + display: inline-block; +} + +.loading-button.is-disabled { + opacity: 0.7; + cursor: not-allowed; +} + +@keyframes capture-spin { + to { transform: rotate(360deg); } +} diff --git a/src/main/resources/static/js/form_loading.js b/src/main/resources/static/js/form_loading.js new file mode 100644 index 0000000..11ac545 --- /dev/null +++ b/src/main/resources/static/js/form_loading.js @@ -0,0 +1,18 @@ +document.addEventListener('DOMContentLoaded', function () { + const forms = document.querySelectorAll('[data-loading-form="true"]'); + const buttons = document.querySelectorAll('[data-loading-button="true"]'); + + forms.forEach(function (form) { + form.addEventListener('submit', function () { + buttons.forEach(function (btn) { + btn.disabled = true; + btn.classList.add('is-disabled'); + }); + + const btn = form.querySelector('[data-loading-button="true"]'); + if (btn) { + btn.classList.add('is-loading'); + } + }); + }); +}); From c0a189b56e0ac73ad2cda303e2dd61f2df53add6 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:29:31 -0300 Subject: [PATCH 31/78] feat: create MallDetailSession class for handling mall transaction details --- .../example/models/MallDetailSession.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/models/MallDetailSession.java diff --git a/src/main/java/cl/transbank/webpay/example/models/MallDetailSession.java b/src/main/java/cl/transbank/webpay/example/models/MallDetailSession.java new file mode 100644 index 0000000..72597fd --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/models/MallDetailSession.java @@ -0,0 +1,29 @@ +package cl.transbank.webpay.example.models; + +import java.io.Serializable; + +public class MallDetailSession implements Serializable { + private static final long serialVersionUID = 1L; + + private final double amount; + private final String commerceCode; + private final String buyOrder; + + public MallDetailSession(double amount, String commerceCode, String buyOrder) { + this.amount = amount; + this.commerceCode = commerceCode; + this.buyOrder = buyOrder; + } + + public double getAmount() { + return amount; + } + + public String getCommerceCode() { + return commerceCode; + } + + public String getBuyOrder() { + return buyOrder; + } +} From f4de6ec8e6ce3a18bcdc6af540820252d29ef1d1 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:29:40 -0300 Subject: [PATCH 32/78] feat: remove error logging in exception handler of TransaccionCompletaMallController --- .../example/controllers/TransaccionCompletaMallController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java index ec6682a..d4aa077 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -273,7 +273,6 @@ private List getSessionDetails(HttpServletRequest req) { @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { - log.error("Error inesperado", e); model.addAttribute(ATTR_ERROR, e.getMessage()); return VIEW_ERROR; } From da5d5d3938571f50e2454a27147f6a5a5962146d Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:29:48 -0300 Subject: [PATCH 33/78] feat: add form loading script to layout for improved user experience --- src/main/resources/templates/layout.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html index faf2338..0b6702e 100644 --- a/src/main/resources/templates/layout.html +++ b/src/main/resources/templates/layout.html @@ -69,6 +69,7 @@ + From 60cb21f32f9e56ee0bf849b294ea5d8aff6f1baa Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:30:26 -0300 Subject: [PATCH 34/78] feat: refine text and formatting in transaction form for clarity and consistency --- .../transaccion_completa_diferida/index.html | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_diferida/index.html b/src/main/resources/templates/transaccion_completa_diferida/index.html index ef46935..f78b7c2 100644 --- a/src/main/resources/templates/transaccion_completa_diferida/index.html +++ b/src/main/resources/templates/transaccion_completa_diferida/index.html @@ -3,8 +3,8 @@

Transacción Completa Diferida - Formulario

En esta primera etapa necesitas obtener los datos esenciales de la tarjeta - de crédito, débito o prepago del titular. Utiliza el formulario para - recolectar esta información de manera segura. + de crédito del titular. Utiliza el formulario para recolectar esta + información de manera segura.

@@ -73,7 +73,10 @@

Transacción Completa Diferida - Formulario

+
+
From 2e3719004e5271b2f51bdd4e2c36a8edd753fb6c Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:30:48 -0300 Subject: [PATCH 36/78] feat: add create transaction page for mall payments with detailed steps and form --- .../transaccion_completa_mall/create.html | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall/create.html diff --git a/src/main/resources/templates/transaccion_completa_mall/create.html b/src/main/resources/templates/transaccion_completa_mall/create.html new file mode 100644 index 0000000..1f28b51 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/create.html @@ -0,0 +1,70 @@ +
+
+

Transacción Completa Mall - Crear transacción

+

+ En este paso sucede la creación de la transacción con el objetivo de obtener un identificador único para la misma. +

+ +

Paso 1: Petición

+

+ Comienza importando la librería TransaccionCompleta Mall, y a continuación, crea la transacción necesaria. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.model.MallTransactionCreateDetails;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var details = MallTransactionCreateDetails.build()
+  .add(1000, IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_CHILD1, "O-123")
+  .add(1000, IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_CHILD2, "O-456");
+
+var resp = tx.create(buyOrder, sessionId, cardNumber, cardExpiry, details, cvv);
+    
+ +

Paso 2: Respuesta

+

Una vez creada la transacción, recibirás los siguientes datos de respuesta:

+ +
+ +

¡Transacción creada!

+

Ahora que hemos creado la transacción, se abren dos opciones para continuar:

+
    +
  • + Consultar Cuotas (opcional): Alternativamente puedes realizar consultas de cuotas + para ofrecer opciones de pago a plazos. +
  • +
  • + Confirmar Transacción: Debes confirmar directamente la transacción para + finalizar con el proceso de pago. +
  • +
+ +
+
+ Formulario de redirección + +
+ + + +
+ +
+
+
+
From 463bbe904458bb63d10f6b73827a456aaac228d9 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:30:58 -0300 Subject: [PATCH 37/78] feat: add installments consultation page for mall transactions with form and script --- .../installments.html | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall/installments.html diff --git a/src/main/resources/templates/transaccion_completa_mall/installments.html b/src/main/resources/templates/transaccion_completa_mall/installments.html new file mode 100644 index 0000000..8293ffb --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/installments.html @@ -0,0 +1,144 @@ +
+
+

Transacción Completa Mall - Consulta de cuotas

+

+ En esta etapa, realizaremos una consulta de cuotas para conocer sus + condiciones. Es importante destacar que este paso es opcional y se utiliza + únicamente si deseas ofrecer opciones de pago a plazos. +

+ +

Paso 1: Petición

+

+ Para llevar a cabo la consulta de cuotas, debemos enviar los siguientes + datos relevantes. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+import cl.transbank.webpay.transaccioncompleta.responses.MallFullTransactionInstallmentsDetails;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var details = MallFullTransactionInstallmentsDetails.build()
+  .add("597055555574", "O-123", (byte) 3)
+  .add("597055555575", "O-456", (byte) 3);
+
+var resp = tx.installments(token, details);
+    
+ +

Paso 2: Respuesta

+

+ Una vez realizada la consulta de cuotas, recibirás los siguientes datos de + respuesta: +

+ +
+ +

Confirmar Transacción

+

+ Si decides utilizar cuotas y estás satisfecho con las condiciones + obtenidas en la consulta, el siguiente paso sería confirmar la + transacción. +

+ +
+ + +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + +
+
From 10765eebe2d74fc5fabeabc937bce4a4e9dc965b Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:31:08 -0300 Subject: [PATCH 38/78] feat: add confirmation page for mall transactions with request and response details --- .../transaccion_completa_mall/commit.html | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall/commit.html diff --git a/src/main/resources/templates/transaccion_completa_mall/commit.html b/src/main/resources/templates/transaccion_completa_mall/commit.html new file mode 100644 index 0000000..0ee9dc3 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/commit.html @@ -0,0 +1,83 @@ +
+
+

Transacción Completa Mall - Confirmar transacción

+

+ En este paso crucial, procederemos a confirmar la transacción con el objetivo de notificar a + Transbank que hemos recibido la transacción de manera exitosa. Es fundamental destacar que si no se confirma la + transacción, esta será caducada. +

+ +

Paso 1: Petición

+

+ Para confirmar la transacción, debes enviar el token correspondiente. En el caso de pagos a + plazos, también debes incluir el ID de la consulta de cuotas. En algunos casos, será necesario proporcionar el + índice del periodo diferido y un valor boolean indicando si se tomará el periodo de gracia. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+import cl.transbank.webpay.transaccioncompleta.model.MallTransactionCommitDetails;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var details = MallTransactionCommitDetails.build()
+  .add("597055555574", "O-123", 1L, (byte) 0, false)
+  .add("597055555575", "O-456", 1L, (byte) 0, false);
+
+var resp = tx.commit(token, details);
+    
+ +

Paso 2: Respuesta

+

+ Una vez que la transacción ha sido confirmada Transbank proporcionará la siguiente información. Es fundamental + conservar esta respuesta y verificar que el campo "response_code" tenga un valor de cero y que el campo "status" + sea "AUTHORIZED". +

+ +
+ +

¡Listo!

+

Con la transacción confirmada, puedes mostrar al usuario una página de éxito de la transacción, proporcionando la confirmación de que el proceso se ha completado con éxito.

+

Después de confirmar la transacción, podrás realizar otras operaciones útiles:

+
    +
  • Reembolsar: Puedes reversar o anular el pago según ciertas condiciones comerciales.
  • +
  • Consultar Estado: Hasta 7 días después de realizada la transacción, podrás consultar el estado de la transacción.
  • +
+ +
+
+
+ Reembolsar detalle +
+ + + + + + + +
+ + +
+
+
+ + CONSULTAR ESTADO +
+
From 66c7cc254d19cf57c8672f8ed0ff729cc32bd373 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:31:21 -0300 Subject: [PATCH 39/78] feat: add refund page for mall transactions with request and response details --- .../transaccion_completa_mall/refund.html | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall/refund.html diff --git a/src/main/resources/templates/transaccion_completa_mall/refund.html b/src/main/resources/templates/transaccion_completa_mall/refund.html new file mode 100644 index 0000000..0b80bb5 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/refund.html @@ -0,0 +1,44 @@ +
+
+

Transacción Completa Mall - Reembolsar

+

+ En esta etapa, tendrás la posibilidad de solicitar el reembolso del dinero al tarjeta habiente. El + tipo de reembolso (Reversa, Anulación o Anulación parcial) dependerá del monto y el tiempo transcurrido desde la + transacción. +

+ +

Paso 1: Petición

+

+ Para efectuar la solicitud de reembolso, necesitarás el token de la transacción, el buy order del detalle, + el commerce code hijo y el monto que deseas reversar. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.refund(token, buyOrder, childCommerceCode, amount);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con el resultado de la reversa o anulación. Evalúa cuidadosamente esta respuesta para + confirmar que el reembolso se haya procesado de manera efectiva. +

+ +
+ + CONSULTAR ESTADO +
+
From 661e477d1e89ca1b21f5b0b0be7464ac5c507046 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 10:31:27 -0300 Subject: [PATCH 40/78] feat: add status page for mall transactions with request and response details --- .../transaccion_completa_mall/status.html | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall/status.html diff --git a/src/main/resources/templates/transaccion_completa_mall/status.html b/src/main/resources/templates/transaccion_completa_mall/status.html new file mode 100644 index 0000000..a42939d --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/status.html @@ -0,0 +1,44 @@ +
+
+

Transacción Completa Mall - Estado de transacción

+

+ En esta fase, tendrás la capacidad de solicitar el estado actual de una transacción hasta 7 días + después de su realización. Es importante destacar que no hay límite en la cantidad de solicitudes de este tipo + durante este período. Sin embargo, una vez transcurridos los 7 días, ya no podrás revisar el estado de la + transacción. +

+ +

Paso 1: Petición

+

+ Para llevar a cabo la solicitud de estado, necesitarás el token correspondiente a la transacción + de la cual deseas obtener información. Utiliza este token para realizar una llamada a + MallFullTransaction.status(). +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.status(token);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con la siguiente información. Asegúrate de guardar estos detalles; lo único que necesitas + validar es que el campo "response_code" sea igual a cero. +

+ +
+
+
From 33b3209781222e3cb63309414986ac44b61a8050 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 12:36:56 -0300 Subject: [PATCH 41/78] feat: remove unnecessary default case in navigation switch statement --- .../example/controllers/TransaccionCompletaMallController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java index d4aa077..f98567d 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -72,7 +72,6 @@ private static Map createNav(String... keys) { case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); - default -> { } } } return nav; From bfce824aeb586de91fb64b62fc66b038c07dd863 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 13:02:39 -0300 Subject: [PATCH 42/78] feat: simplify navigation label creation using switch expression --- .../TransaccionCompletaMallController.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java index f98567d..f15e4a0 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -68,10 +68,14 @@ public class TransaccionCompletaMallController extends BaseController { private static Map createNav(String... keys) { Map nav = new LinkedHashMap<>(); for (String key : keys) { - switch (key) { - case NAV_KEY_REQUEST -> nav.put(key, NAV_LABEL_REQUEST); - case NAV_KEY_RESPONSE -> nav.put(key, NAV_LABEL_RESPONSE); - case NAV_KEY_FORM -> nav.put(key, NAV_LABEL_FORM); + String label = switch (key) { + case NAV_KEY_REQUEST -> NAV_LABEL_REQUEST; + case NAV_KEY_RESPONSE -> NAV_LABEL_RESPONSE; + case NAV_KEY_FORM -> NAV_LABEL_FORM; + default -> null; + }; + if (label != null) { + nav.put(key, label); } } return nav; From c8b77003dc44952b53925f28f009ef120d6e6657 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Mon, 30 Mar 2026 14:06:28 -0300 Subject: [PATCH 43/78] feat: update transaction commit details to use optional parameters --- .../templates/transaccion_completa_mall/commit.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_mall/commit.html b/src/main/resources/templates/transaccion_completa_mall/commit.html index 0ee9dc3..f641065 100644 --- a/src/main/resources/templates/transaccion_completa_mall/commit.html +++ b/src/main/resources/templates/transaccion_completa_mall/commit.html @@ -30,9 +30,10 @@

Paso 1: Petición

) ); +// idQueryInstallments, deferredPeriodIndex y gracePeriod son opcionales var details = MallTransactionCommitDetails.build() - .add("597055555574", "O-123", 1L, (byte) 0, false) - .add("597055555575", "O-456", 1L, (byte) 0, false); + .add("597055555574", "O-123", idQueryInstallments, deferredPeriodIndex, gracePeriod) + .add("597055555575", "O-456", idQueryInstallments, deferredPeriodIndex, gracePeriod); var resp = tx.commit(token, details);
From d2fc83d4fa396fb86bdce5fa7442c0fe59827f15 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 00:25:08 -0300 Subject: [PATCH 44/78] feat: add MallFullTransaction instance variable to TransaccionCompletaMallController --- .../controllers/TransaccionCompletaMallController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java index f15e4a0..789c700 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -64,6 +64,8 @@ public class TransaccionCompletaMallController extends BaseController { private static final Map NAV_COMMIT = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE, NAV_KEY_FORM); private static final Map NAV_STATUS = createNav(NAV_KEY_REQUEST, NAV_KEY_RESPONSE); private static final Map NAV_REFUND = NAV_STATUS; + private final MallFullTransaction tx; + private static Map createNav(String... keys) { Map nav = new LinkedHashMap<>(); @@ -81,8 +83,6 @@ private static Map createNav(String... keys) { return nav; } - private final MallFullTransaction tx; - public TransaccionCompletaMallController() { this.tx = new MallFullTransaction( new WebpayOptions( From 6021978340edb6d977b4e2821cc233a44cedff81 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 00:26:01 -0300 Subject: [PATCH 45/78] feat: add CSRF token input to mall transaction forms and fix label formatting --- .../transaccion_completa_diferida/index.html | 4 ++-- .../transaccion_completa_mall/create.html | 1 + .../transaccion_completa_mall/index.html | 1 + .../transaccion_completa_mall/installments.html | 16 ++++++++-------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_diferida/index.html b/src/main/resources/templates/transaccion_completa_diferida/index.html index f78b7c2..a001d16 100644 --- a/src/main/resources/templates/transaccion_completa_diferida/index.html +++ b/src/main/resources/templates/transaccion_completa_diferida/index.html @@ -31,8 +31,8 @@

Transacción Completa Diferida - Formulario

+ >Fecha de Vencimiento (MM/YY) + ¡Transacción creada!
+
Formulario de redirección diff --git a/src/main/resources/templates/transaccion_completa_mall/index.html b/src/main/resources/templates/transaccion_completa_mall/index.html index 49960a2..13f67cf 100644 --- a/src/main/resources/templates/transaccion_completa_mall/index.html +++ b/src/main/resources/templates/transaccion_completa_mall/index.html @@ -13,6 +13,7 @@

Transacción Completa Mall - Formulario

method="POST" id="card-form" > +
diff --git a/src/main/resources/templates/transaccion_completa_mall/installments.html b/src/main/resources/templates/transaccion_completa_mall/installments.html index 8293ffb..a5380e6 100644 --- a/src/main/resources/templates/transaccion_completa_mall/installments.html +++ b/src/main/resources/templates/transaccion_completa_mall/installments.html @@ -63,8 +63,8 @@

Confirmar Transacción

+ >ID de consulta de cuotas (Opcional) + Confirmar Transacción
+ >Indice de periodo diferido (opcional) + Confirmar Transacción
+ >Periodo de gracia (opcional) + Confirmar Transacción
From 4354dd36ed3e53045ff9874244d9106c9be03180 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 00:58:10 -0300 Subject: [PATCH 46/78] feat: improve formatting and readability of the transaction form HTML --- .../transaccion_completa_mall/index.html | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_mall/index.html b/src/main/resources/templates/transaccion_completa_mall/index.html index 13f67cf..da6e8ee 100644 --- a/src/main/resources/templates/transaccion_completa_mall/index.html +++ b/src/main/resources/templates/transaccion_completa_mall/index.html @@ -13,7 +13,12 @@

Transacción Completa Mall - Formulario

method="POST" id="card-form" > - +
@@ -32,8 +37,8 @@

Transacción Completa Mall - Formulario

+ >Fecha de Vencimiento (MM/YY) + Transacción Completa Mall - Formulario +
+
From 3a4128e33a74403e4d6a19580ba999435a046abd Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 10:57:52 -0300 Subject: [PATCH 50/78] feat: add create transaction form for Mall Completa Diferido with options for installments and confirmation --- .../create.html | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall_diferido/create.html diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/create.html b/src/main/resources/templates/transaccion_completa_mall_diferido/create.html new file mode 100644 index 0000000..3b6f606 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/create.html @@ -0,0 +1,71 @@ +
+
+

Transacción Completa Mall Diferido - Crear transacción

+

+ En este paso sucede la creación de la transacción con el objetivo de obtener un identificador único para la misma. +

+ +

Paso 1: Petición

+

+ Comienza importando la librería TransaccionCompleta Mall, y a continuación, crea la transacción necesaria. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.model.MallTransactionCreateDetails;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var details = MallTransactionCreateDetails.build()
+  .add(1000, IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED_CHILD1, "O-123")
+  .add(1000, IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED_CHILD2, "O-456");
+
+var resp = tx.create(buyOrder, sessionId, cardNumber, cardExpiry, details, cvv);
+    
+ +

Paso 2: Respuesta

+

Una vez creada la transacción, recibirás los siguientes datos de respuesta:

+ +
+ +

¡Transacción creada!

+

Ahora que hemos creado la transacción, se abren dos opciones para continuar:

+
    +
  • + Consultar Cuotas (opcional): Alternativamente puedes realizar consultas de cuotas + para ofrecer opciones de pago a plazos. +
  • +
  • + Confirmar Transacción: Debes confirmar directamente la transacción para + finalizar con el proceso de pago. +
  • +
+ + + +
+ Formulario de redirección + +
+ + + +
+ +
+ +
+
From d8e771462c397e54f1115e912ca99d2f13d13604 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 10:58:09 -0300 Subject: [PATCH 51/78] feat: add installments consultation form for Mall Completa Diferido with optional fields and confirmation link --- .../installments.html | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall_diferido/installments.html diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html new file mode 100644 index 0000000..85ed2bc --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html @@ -0,0 +1,100 @@ +
+
+

Transacción Completa Mall Diferido - Consulta de cuotas

+

+ En esta etapa, realizaremos una consulta de cuotas para conocer sus condiciones. Es importante + destacar que este paso es opcional y se utiliza únicamente si deseas ofrecer opciones de pago a plazos. +

+ +

Paso 1: Petición

+

Para llevar a cabo la consulta de cuotas, debemos enviar los siguientes datos relevantes.

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+import cl.transbank.webpay.transaccioncompleta.responses.MallFullTransactionInstallmentsDetails;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var details = MallFullTransactionInstallmentsDetails.build()
+  .add("597055555577", "O-123", (byte) 3)
+  .add("597055555578", "O-456", (byte) 3);
+
+var resp = tx.installments(token, details);
+    
+ +

Paso 2: Respuesta

+

Una vez realizada la consulta de cuotas, recibirás los siguientes datos de respuesta:

+ +
+ +

Confirmar Transacción

+

+ Si decides utilizar cuotas y estás satisfecho con las condiciones obtenidas en la consulta, el + siguiente paso sería confirmar la transacción. +

+ +
+ + +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + +
+
From 36aee464b1c6ba49da5c75f9b90a291f4b93db43 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 10:58:19 -0300 Subject: [PATCH 52/78] feat: add refund process template for Mall Completa Diferido transactions --- .../refund.html | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall_diferido/refund.html diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html b/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html new file mode 100644 index 0000000..141dda2 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html @@ -0,0 +1,44 @@ +
+
+

Transacción Completa Mall Diferido - Reembolsar

+

+ En esta etapa, tendrás la posibilidad de solicitar el reembolso del dinero al tarjeta habiente. El + tipo de reembolso (Reversa, Anulación o Anulación parcial) dependerá del monto y el tiempo transcurrido desde la + transacción. +

+ +

Paso 1: Petición

+

+ Para efectuar la solicitud de reembolso, necesitarás el token de la transacción, el buy order del detalle, + el commerce code hijo y el monto que deseas reversar. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.refund(token, buyOrder, childCommerceCode, amount);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con el resultado de la reversa o anulación. Evalúa cuidadosamente esta respuesta para + confirmar que el reembolso se haya procesado de manera efectiva. +

+ +
+ + CONSULTAR ESTADO +
+
From bee83e74b2de47466fa1905f60815246487769d3 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 10:58:27 -0300 Subject: [PATCH 53/78] feat: add capture transaction template for Mall Completa Diferido with refund and status consultation options --- .../capture.html | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall_diferido/capture.html diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/capture.html b/src/main/resources/templates/transaccion_completa_mall_diferido/capture.html new file mode 100644 index 0000000..4ac779f --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/capture.html @@ -0,0 +1,68 @@ +
+
+

Transacción Completa Mall Diferido - Capturar transacción

+

+ En este paso debemos capturar la transacción para realmente capturar el dinero que habia sido previamente reservado al hacer la transacción. +

+ +

Paso 1: Petición

+

+ Para capturar una transacción, necesitaremos el Token, la Orden de compra, el Código de autorización y el monto a capturar. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.capture(token, childCommerceCode, childBuyOrder, authorizationCode, amount);
+    
+ +

Paso 2: Respuesta

+

Una vez capturada la transacción, recibirás los siguientes datos de respuesta:

+ +
+ +

Otras utilidades

+

+ Con la transacción capturada, puedes mostrar al usuario una página de éxito de la transacción, proporcionándole la confirmación de que el proceso se ha completado con éxito. +

+ +

Después de capturar la transacción, podrás realizar otras operaciones útiles:

+
    +
  • Reembolsar: Puedes reversar o anular el pago según ciertas condiciones comerciales.
  • +
  • Consultar Estado: Hasta 7 días después de realizada la transacción, podrás consultar el estado de la transacción.
  • +
+ +
+
+
+ + + + + +
+ + +
+
+ + CONSULTAR ESTADO +
+
From 580b5b68fe3798ec91fcb2094e89327a56957e0d Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 10:58:36 -0300 Subject: [PATCH 54/78] feat: add confirmation template for Mall Completa Diferido transaction with capture form --- .../commit.html | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall_diferido/commit.html diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/commit.html b/src/main/resources/templates/transaccion_completa_mall_diferido/commit.html new file mode 100644 index 0000000..e5c5b1d --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/commit.html @@ -0,0 +1,81 @@ +
+
+

Transacción Completa Mall Diferido - Confirmar transacción

+

+ En este paso crucial, procederemos a confirmar la transacción con el objetivo de notificar a + Transbank que hemos recibido la transacción de manera exitosa. Es fundamental destacar que si no se confirma la + transacción, esta será caducada. +

+ +

Paso 1: Petición

+

+ Para confirmar la transacción, debes enviar el token correspondiente. En el caso de pagos a + plazos, también debes incluir el ID de la consulta de cuotas. En algunos casos, será necesario proporcionar el + índice del periodo diferido y un valor boolean indicando si se tomará el periodo de gracia. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+import cl.transbank.webpay.transaccioncompleta.model.MallTransactionCommitDetails;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+// idQueryInstallments, deferredPeriodIndex y gracePeriod son opcionales
+var details = MallTransactionCommitDetails.build()
+  .add("597055555577", "O-123", idQueryInstallments, deferredPeriodIndex, gracePeriod)
+  .add("597055555578", "O-456", idQueryInstallments, deferredPeriodIndex, gracePeriod);
+
+var resp = tx.commit(token, details);
+    
+ +

Paso 2: Respuesta

+

+ Una vez que la transacción ha sido confirmada Transbank proporcionará la siguiente información. Es fundamental + conservar esta respuesta y verificar que el campo "response_code" tenga un valor de cero y que el campo "status" + sea "AUTHORIZED". +

+ +
+ +

¡Listo!

+

Ahora que se ha confirmado la transacción, puedes capturar el monto previamente autorizado.

+ +
+
+
+ Capturar +
+ + + + + + + + + +
+ + +
+
+
+ + CONSULTAR ESTADO +
+
From b4225aaa92a31a83d3515feb55b6f561a832efb4 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 11:02:32 -0300 Subject: [PATCH 55/78] feat: add status template for Mall Completa Diferido transactions --- .../status.html | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/resources/templates/transaccion_completa_mall_diferido/status.html diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/status.html b/src/main/resources/templates/transaccion_completa_mall_diferido/status.html new file mode 100644 index 0000000..b26e491 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/status.html @@ -0,0 +1,44 @@ +
+
+

Transacción Completa Mall Diferido - Estado de transacción

+

+ En esta fase, tendrás la capacidad de solicitar el estado actual de una transacción hasta 7 días + después de su realización. Es importante destacar que no hay límite en la cantidad de solicitudes de este tipo + durante este período. Sin embargo, una vez transcurridos los 7 días, ya no podrás revisar el estado de la + transacción. +

+ +

Paso 1: Petición

+

+ Para llevar a cabo la solicitud de estado, necesitarás el token correspondiente a la transacción + de la cual deseas obtener información. Utiliza este token para realizar una llamada a + MallFullTransaction.status(). +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.common.IntegrationType;
+import cl.transbank.webpay.common.WebpayOptions;
+import cl.transbank.webpay.transaccioncompleta.MallFullTransaction;
+
+MallFullTransaction tx = new MallFullTransaction(
+  new WebpayOptions(
+    IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED,
+    IntegrationApiKeys.WEBPAY,
+    IntegrationType.TEST
+  )
+);
+
+var resp = tx.status(token);
+    
+ +

Paso 2: Respuesta

+

+ Transbank responderá con la siguiente información. Asegúrate de guardar estos detalles; lo único que necesitas + validar es que el campo "response_code" sea igual a cero. +

+ +
+
+
From 18877b1cf7e3a0f2f4ca9e2efe57ed6507cba68d Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 31 Mar 2026 11:08:37 -0300 Subject: [PATCH 56/78] feat: improve formatting and readability of installments consultation template for Mall Completa Diferido --- .../installments.html | 112 ++++++++++++------ 1 file changed, 78 insertions(+), 34 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html index 85ed2bc..5ecbeb2 100644 --- a/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html @@ -2,12 +2,16 @@

Transacción Completa Mall Diferido - Consulta de cuotas

- En esta etapa, realizaremos una consulta de cuotas para conocer sus condiciones. Es importante - destacar que este paso es opcional y se utiliza únicamente si deseas ofrecer opciones de pago a plazos. + En esta etapa, realizaremos una consulta de cuotas para conocer sus + condiciones. Es importante destacar que este paso es opcional y se utiliza + únicamente si deseas ofrecer opciones de pago a plazos.

Paso 1: Petición

-

Para llevar a cabo la consulta de cuotas, debemos enviar los siguientes datos relevantes.

+

+ Para llevar a cabo la consulta de cuotas, debemos enviar los siguientes + datos relevantes. +


 import cl.transbank.common.IntegrationApiKeys;
@@ -33,45 +37,83 @@ 

Paso 1: Petición

Paso 2: Respuesta

-

Una vez realizada la consulta de cuotas, recibirás los siguientes datos de respuesta:

+

+ Una vez realizada la consulta de cuotas, recibirás los siguientes datos de + respuesta: +

-
+

Confirmar Transacción

- Si decides utilizar cuotas y estás satisfecho con las condiciones obtenidas en la consulta, el - siguiente paso sería confirmar la transacción. + Si decides utilizar cuotas y estás satisfecho con las condiciones + obtenidas en la consulta, el siguiente paso sería confirmar la + transacción.

- - -
- - + + +
+ +
-
- - +
+ +
-
- - +
+ +
From d6684b8e67fbe95805ca173485674ee12deac46e Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 1 Apr 2026 13:47:45 -0300 Subject: [PATCH 57/78] fix: correct wording in transaction form description for Mall Completa Diferido --- .../templates/transaccion_completa_mall_diferido/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/index.html b/src/main/resources/templates/transaccion_completa_mall_diferido/index.html index 081db69..5c7ad81 100644 --- a/src/main/resources/templates/transaccion_completa_mall_diferido/index.html +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/index.html @@ -3,8 +3,8 @@

Transacción Completa Mall Diferido - Formulario

En esta primera etapa necesitas obtener los datos esenciales de la tarjeta - de crédito, débito o prepago del titular. Utiliza el formulario para - recolectar esta información de manera segura. + de crédito del titular. Utiliza el formulario para recolectar esta + información de manera segura.

From 7ce0bc07d593e5d6c113b044e3ab0cec437ff7c6 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 1 Apr 2026 13:50:39 -0300 Subject: [PATCH 58/78] feat: enhance readability and formatting of refund template for Mall Completa Diferido --- .../refund.html | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html b/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html index 141dda2..61a611d 100644 --- a/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html @@ -2,15 +2,17 @@

Transacción Completa Mall Diferido - Reembolsar

- En esta etapa, tendrás la posibilidad de solicitar el reembolso del dinero al tarjeta habiente. El - tipo de reembolso (Reversa, Anulación o Anulación parcial) dependerá del monto y el tiempo transcurrido desde la + En esta etapa, tendrás la posibilidad de solicitar el reembolso del dinero + al tarjeta habiente. El tipo de reembolso (Reversa, Anulación o Anulación + parcial) dependerá del monto y el tiempo transcurrido desde la transacción.

Paso 1: Petición

- Para efectuar la solicitud de reembolso, necesitarás el token de la transacción, el buy order del detalle, - el commerce code hijo y el monto que deseas reversar. + Para efectuar la solicitud de reembolso, necesitarás el token de la + transacción, el buy order del detalle, el commerce code (tienda) y el + monto que deseas reversar.


@@ -33,12 +35,20 @@ 

Paso 1: Petición

Paso 2: Respuesta

- Transbank responderá con el resultado de la reversa o anulación. Evalúa cuidadosamente esta respuesta para - confirmar que el reembolso se haya procesado de manera efectiva. + Transbank responderá con el resultado de la reversa o anulación. Evalúa + cuidadosamente esta respuesta para confirmar que el reembolso se haya + procesado de manera efectiva.

-
+
+      
+    
- CONSULTAR ESTADO + + CONSULTAR ESTADO +
From 43518b32077db28e3f53e2e05f8f0ce4d184e93f Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 1 Apr 2026 13:58:41 -0300 Subject: [PATCH 59/78] feat: improve formatting and readability of installments template for Mall Completa Diferido --- .../installments.html | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html index 5ecbeb2..74e5cb2 100644 --- a/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html @@ -42,9 +42,9 @@

Paso 2: Respuesta

respuesta:

-
+
+      
+    

Confirmar Transacción

@@ -62,9 +62,9 @@

Confirmar Transacción

/>
- + Confirmar Transacción
- + Confirmar Transacción />
- + Confirmar Transacción
From 8c3bf367755a675415b25391d85a8b4428705bf8 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 22 Apr 2026 16:35:49 -0300 Subject: [PATCH 60/78] feat: add PatpassComercioController and update application properties for header forwarding --- .../PatpassComercioController.java | 251 ++++++++++++++++++ src/main/resources/application.properties | 1 + 2 files changed, 252 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java diff --git a/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java b/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java new file mode 100644 index 0000000..931c66e --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java @@ -0,0 +1,251 @@ +package cl.transbank.webpay.example.controllers; + +import cl.transbank.common.IntegrationApiKeys; +import cl.transbank.common.IntegrationCommerceCodes; +import cl.transbank.patpass.PatpassComercio; +import cl.transbank.patpass.responses.PatpassComercioInscriptionStartResponse; +import cl.transbank.patpass.responses.PatpassComercioTransactionStatusResponse; +import cl.transbank.webpay.exception.InscriptionStartException; +import cl.transbank.webpay.exception.TransactionStatusException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; +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 java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +@Log4j2 +@Controller +@RequestMapping("/patpass-comercio") +public class PatpassComercioController extends BaseController { + private static final String TEMPLATE_FOLDER = "patpass_comercio"; + private static final String BASE_URL = "/patpass-comercio"; + private static final String PRODUCT = "Patpass Comercio"; + private static final String SESSION_TOKEN_KEY = "patpass_j_token"; + private static final String DEFAULT_VOUCHER_URL = + "https://pagoautomaticocontarjetasint.transbank.cl/nuevo-ic-rest/tokenVoucherLogin"; + + private static final String COMMIT_PATH = "/commit"; + private static final String VOUCHER_PATH = "/voucher"; + private static final String NAVIGATION_ATTR = "navigation"; + private static final String VOUCHER_URL = "voucherUrl"; + private static final String ERROR_ATTR = "error"; + private static final String VIEW_START = TEMPLATE_FOLDER + "/start"; + private static final String VIEW_COMMIT = TEMPLATE_FOLDER + COMMIT_PATH; + private static final String VIEW_VOUCHER = TEMPLATE_FOLDER + VOUCHER_PATH; + + private static final Map NAV_START; + private static final Map NAV_COMMIT; + private static final Map NAV_VOUCHER; + + static { + NAV_START = new LinkedHashMap<>(); + NAV_START.put("request", "Petición"); + NAV_START.put("response", "Respuesta"); + NAV_START.put("form", "Formulario"); + NAV_START.put("example", "Ejemplo"); + + NAV_COMMIT = new LinkedHashMap<>(); + NAV_COMMIT.put("data", "Datos recibidos"); + NAV_COMMIT.put("request", "Petición"); + NAV_COMMIT.put("response", "Respuesta"); + NAV_COMMIT.put("form", "Formulario"); + + NAV_VOUCHER = new LinkedHashMap<>(); + NAV_VOUCHER.put("form", "voucher"); + } + + private final PatpassComercio.Inscription inscription; + + public PatpassComercioController() { + this.inscription = PatpassComercio.Inscription.buildForIntegration( + IntegrationCommerceCodes.PATPASS_COMERCIO, + IntegrationApiKeys.PATPASS_COMERCIO + ); + } + + private void addBreadcrumbs(Model model, String label, String url) { + Map breadcrumbs = new LinkedHashMap<>(); + breadcrumbs.put("Inicio", "/"); + breadcrumbs.put(PRODUCT, BASE_URL); + if (label != null) { + breadcrumbs.put(label, url); + } + model.addAttribute("product", PRODUCT); + model.addAttribute("breadcrumbs", breadcrumbs); + } + + @GetMapping({"", "/"}) + public String start(HttpServletRequest request, Model model) + throws IOException, InscriptionStartException { + model.addAttribute(NAVIGATION_ATTR, NAV_START); + addBreadcrumbs(model, null, null); + + String returnUrl = request.getRequestURL().toString().replaceAll("/?$", "") + COMMIT_PATH; + String finalUrl = request.getRequestURL().toString().replaceAll("/?$", "") + VOUCHER_PATH; + + Map requestData = new LinkedHashMap<>(); + requestData.put("serviceId", "Service-" + getRandomNumber()); + requestData.put("maxAmount", 100); + requestData.put("returnUrl", returnUrl); + requestData.put("finalUrl", finalUrl); + requestData.put("name", "Isaac"); + requestData.put("lastName", "Newton"); + requestData.put("secondLastName", "Gonzales"); + requestData.put("rut", "11111111-1"); + requestData.put("phone", "123456734"); + requestData.put("cellPhone", "123456723"); + requestData.put("patpassName", "Membresia de cable"); + requestData.put("personEmail", "developer@continuum.cl"); + requestData.put("commerceEmail", "developer@continuum.cl"); + requestData.put("address", "Satelite 101"); + requestData.put("city", "Santiago"); + + + PatpassComercioInscriptionStartResponse resp = this.inscription.start( + requestData.get("returnUrl").toString(), + requestData.get("name").toString(), + requestData.get("lastName").toString(), + requestData.get("secondLastName").toString(), + requestData.get("rut").toString(), + requestData.get("serviceId").toString(), + requestData.get("finalUrl").toString(), + Double.valueOf(requestData.get("maxAmount").toString()), + requestData.get("phone").toString(), + requestData.get("cellPhone").toString(), + requestData.get("patpassName").toString(), + requestData.get("personEmail").toString(), + requestData.get("commerceEmail").toString(), + requestData.get("address").toString(), + requestData.get("city").toString() + ); + + model.addAttribute("request_data", requestData); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + return VIEW_START; + } + + @PostMapping("/commit") + public String commitPost(@RequestParam(name = "j_token", required = false) String jTokenLower, + @RequestParam(name = "J_TOKEN", required = false) String jTokenUpper, + @RequestParam(name = "token", required = false) String token, + HttpServletRequest request, + Model model) { + String jToken = firstNonBlank(jTokenLower, jTokenUpper, token); + if (isBlank(jToken)) { + model.addAttribute(ERROR_ATTR, "No se recibió el token de inscripción (J_TOKEN)."); + return VIEW_ERROR; + } + + request.getSession().setAttribute(SESSION_TOKEN_KEY, jToken); + return "redirect:" + BASE_URL + COMMIT_PATH; + } + + @GetMapping(COMMIT_PATH) + public String commit(@RequestParam(name = "j_token", required = false) String jTokenLower, + @RequestParam(name = "J_TOKEN", required = false) String jTokenUpper, + @RequestParam(name = "token", required = false) String token, + HttpServletRequest request, + Model model) + throws IOException, TransactionStatusException { + String jToken = getIncomingToken(request.getSession(), jTokenLower, jTokenUpper, token); + if (isBlank(jToken)) { + model.addAttribute(ERROR_ATTR, "No se encontró el token de inscripción (J_TOKEN)."); + return VIEW_ERROR; + } + + model.addAttribute(NAVIGATION_ATTR, NAV_COMMIT); + addBreadcrumbs(model, "Confirmar registro", BASE_URL + COMMIT_PATH); + + PatpassComercioTransactionStatusResponse resp = inscription.status(jToken); + request.getSession().setAttribute(SESSION_TOKEN_KEY, jToken); + + model.addAttribute("token", jToken); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute("response_payload_json", toJson(Map.of( + "authorized", resp.isAuthorized(), + VOUCHER_URL, resp.getVoucherUrl() + ))); + model.addAttribute(VOUCHER_URL, + isBlank(resp.getVoucherUrl()) ? DEFAULT_VOUCHER_URL : resp.getVoucherUrl()); + + return VIEW_COMMIT; + } + + @PostMapping(VOUCHER_PATH) + public String voucherPost(@RequestParam(name = "j_token", required = false) String jTokenLower, + @RequestParam(name = "J_TOKEN", required = false) String jTokenUpper, + @RequestParam(name = "tokenComercio", required = false) String tokenComercio, + @RequestParam(name = "token", required = false) String token, + HttpServletRequest request, + Model model) { + String jToken = firstNonBlank(jTokenLower, jTokenUpper, tokenComercio, token); + if (isBlank(jToken)) { + model.addAttribute(ERROR_ATTR, "No se recibió el token de inscripción (J_TOKEN)."); + return VIEW_ERROR; + } + + request.getSession().setAttribute(SESSION_TOKEN_KEY, jToken); + return "redirect:" + BASE_URL + VOUCHER_PATH; + } + + @GetMapping(VOUCHER_PATH) + public String voucher(@RequestParam(name = "j_token", required = false) String jTokenLower, + @RequestParam(name = "J_TOKEN", required = false) String jTokenUpper, + @RequestParam(name = "tokenComercio", required = false) String tokenComercio, + @RequestParam(name = "token", required = false) String token, + HttpServletRequest request, + Model model) { + String jToken = getIncomingToken(request.getSession(), jTokenLower, jTokenUpper, tokenComercio, token); + if (isBlank(jToken)) { + model.addAttribute(ERROR_ATTR, "No se encontró el token de inscripción (J_TOKEN)."); + return VIEW_ERROR; + } + + model.addAttribute(NAVIGATION_ATTR, NAV_VOUCHER); + addBreadcrumbs(model, "Voucher", BASE_URL + VOUCHER_PATH); + model.addAttribute("token", jToken); + model.addAttribute(VOUCHER_URL, DEFAULT_VOUCHER_URL); + + return VIEW_VOUCHER; + } + + private String getIncomingToken(HttpSession session, String... values) { + String token = firstNonBlank(values); + if (!isBlank(token)) { + return token; + } + Object sessionToken = session.getAttribute(SESSION_TOKEN_KEY); + return sessionToken == null ? null : sessionToken.toString(); + } + + private String firstNonBlank(String... values) { + for (String value : values) { + if (!isBlank(value)) { + return value; + } + } + return null; + } + + private boolean isBlank(String value) { + return value == null || value.isBlank(); + } + + @ExceptionHandler(Exception.class) + public String handleException(Exception e, Model model) { + log.error("Error inesperado", e); + model.addAttribute(ERROR_ATTR, e.getMessage()); + return VIEW_ERROR; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3fc4f8..920a5fe 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ spring.application.name=transbank-sdk-java-example +server.forward-headers-strategy=framework From 927bfe7deb4103cf0df49488de7135ec6fe342d8 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 22 Apr 2026 16:35:59 -0300 Subject: [PATCH 61/78] feat: add start.html template for Patpass Comercio transaction initiation --- .../templates/patpass_comercio/start.html | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/resources/templates/patpass_comercio/start.html diff --git a/src/main/resources/templates/patpass_comercio/start.html b/src/main/resources/templates/patpass_comercio/start.html new file mode 100644 index 0000000..1bae503 --- /dev/null +++ b/src/main/resources/templates/patpass_comercio/start.html @@ -0,0 +1,89 @@ +
+
+

Patpass Comercio - Iniciar Transacción

+

+ En este paso inicial, procederemos a inscribir una tarjeta con el objetivo de obtener un identificador único. + Esto nos permitirá redirigir al Tarjetahabiente hacia el formulario de inscripción en el siguiente paso. +

+ +

+ Todas las transacciones de este proyecto ejemplo son realizadas en ambiente de integración. +

+ +

Paso 1: Petición

+

+ Para comenzar, importa la librería PatpassComercio y luego inicia una inscripción. Tener en cuenta: + el ambiente de integración no admite direcciones locales en los atributos url y finalUrl. +

+ +

+import cl.transbank.common.IntegrationApiKeys;
+import cl.transbank.common.IntegrationCommerceCodes;
+import cl.transbank.patpass.PatpassComercio;
+
+var inscription = PatpassComercio.Inscription.buildForIntegration(
+    IntegrationCommerceCodes.PATPASS_COMERCIO,
+    IntegrationApiKeys.PATPASS_COMERCIO
+);
+
+var resp = inscription.start(
+    returnUrl,
+    name,
+    lastName,
+    secondLastName,
+    rut,
+    serviceId,
+    finalUrl,
+    maxAmount,
+    phone,
+    cellPhone,
+    patpassName,
+    personEmail,
+    commerceEmail,
+    address,
+    city
+);
+        
+ +

Paso 2: Respuesta

+

+ Una vez iniciada la inscripción, recibirás los siguientes datos de respuesta: +

+ +
+ +

Paso 3: Creación del formulario

+

+ Utiliza los datos obtenidos durante la inscripción para generar un formulario, proporcionando al + Tarjetahabiente una experiencia de inscripción fluida y segura. +

+ +

+
+ +

Ejemplo

+

+ Para poder iniciar la inscripción, se necesitan los siguientes datos: +

+ +
+ +
+
+ Formulario de redirección +
+ + + +
+ +
+
+
+
From f8680521fb34b556bd8cddc6468f0cb38606891a Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 22 Apr 2026 16:36:07 -0300 Subject: [PATCH 62/78] feat: add commit.html template for Patpass Comercio registration confirmation --- .../templates/patpass_comercio/commit.html | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/resources/templates/patpass_comercio/commit.html diff --git a/src/main/resources/templates/patpass_comercio/commit.html b/src/main/resources/templates/patpass_comercio/commit.html new file mode 100644 index 0000000..f223e1f --- /dev/null +++ b/src/main/resources/templates/patpass_comercio/commit.html @@ -0,0 +1,55 @@ +
+
+

Patpass Comercio - Confirmar Registro

+

+ Es necesario confirmar el registro; este solo se puede hacer una sola vez o retornará error. +

+ +

Paso 1: Datos recibidos

+

+ Luego de que se termina el flujo en el formulario de inscripción recibirás un POST con la siguiente respuesta. +

+ +

+{
+  "J_TOKEN": ""
+}
+        
+ +

Paso 2: Petición

+

+ Usarás el token recibido para confirmar la inscripción usando el método status de PatpassComercio. +

+ +

+var response = inscription.status(token);
+        
+ +

Paso 3: Respuesta

+

+ Transbank contestará con lo siguiente. Debes guardar esta información; lo importante es validar que + el atributo authorized sea igual a true. +

+ +
+ +

¡Listo!

+

+ Una vez realizada la inscripción y confirmada, puedes visualizar el voucher. +

+ +
+ Formulario de redirección +
+ + +
+ +
+
+
From 490d091a6ff803c329cc203b00b9595afc9c3e37 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 22 Apr 2026 16:36:14 -0300 Subject: [PATCH 63/78] feat: add voucher.html template for Patpass Comercio voucher display --- .../templates/patpass_comercio/voucher.html | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/resources/templates/patpass_comercio/voucher.html diff --git a/src/main/resources/templates/patpass_comercio/voucher.html b/src/main/resources/templates/patpass_comercio/voucher.html new file mode 100644 index 0000000..ba320b8 --- /dev/null +++ b/src/main/resources/templates/patpass_comercio/voucher.html @@ -0,0 +1,22 @@ +
+
+

Patpass Comercio - Voucher

+

+ La inscripción ya se encuentra finalizada. Una vez finalizada la inscripción puedes seguir consultando por el voucher. +

+ +
+ Voucher +
+ + +
+ +
+
+
From 065f3757dcfce755d6f39b1de61d26ede3d85e51 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 23 Apr 2026 12:30:04 -0300 Subject: [PATCH 64/78] feat: improve error handling by providing user-friendly error messages --- .../webpay/example/controllers/BaseController.java | 14 ++++++++++++++ .../controllers/OneclickMallController.java | 2 +- .../OneclickMallDeferredController.java | 2 +- .../controllers/PatpassComercioController.java | 2 +- .../controllers/TransaccionCompletaController.java | 2 +- .../TransaccionCompletaDiferidaController.java | 2 +- .../TransaccionCompletaMallController.java | 2 +- .../TransaccionCompletaMallDiferidoController.java | 2 +- .../example/controllers/WebpayPlusController.java | 2 +- .../controllers/WebpayPlusDeferredController.java | 2 +- .../controllers/WebpayPlusMallController.java | 2 +- .../WebpayPlusMallDeferredController.java | 2 +- 12 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java index a228e22..2cb4300 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java @@ -1,5 +1,6 @@ package cl.transbank.webpay.example.controllers; +import cl.transbank.exception.TransbankException; import com.google.gson.GsonBuilder; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializer; @@ -8,6 +9,8 @@ import java.util.Random; public abstract class BaseController { + private static final String GENERIC_ERROR_MESSAGE = + "Ocurrió un error inesperado al procesar la operación."; protected static final String VIEW_ERROR = "error/error_page"; protected static final String VIEW_ABORTED_ERROR = "error/webpay/aborted"; @@ -35,4 +38,15 @@ public String toJson(Object obj) { protected String getRandomNumber() { return String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); } + + protected String getDisplayableErrorMessage(Exception e) { + Throwable current = e; + while (current != null) { + if (current instanceof TransbankException && current.getMessage() != null && !current.getMessage().isBlank()) { + return current.getMessage(); + } + current = current.getCause(); + } + return GENERIC_ERROR_MESSAGE; + } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallController.java index 66922c4..dc98855 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallController.java @@ -279,7 +279,7 @@ public String refund(@RequestParam("buy_order") String buyOrder, @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute("error", getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallDeferredController.java b/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallDeferredController.java index 49d10a6..469a7ad 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallDeferredController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/OneclickMallDeferredController.java @@ -299,7 +299,7 @@ public String capture( @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute("error", getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java b/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java index 931c66e..65df2d3 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/PatpassComercioController.java @@ -245,7 +245,7 @@ private boolean isBlank(String value) { @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute(ERROR_ATTR, e.getMessage()); + model.addAttribute(ERROR_ATTR, getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java index 312b717..231827c 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -209,7 +209,7 @@ public String refund( @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute(ATTR_ERROR, e.getMessage()); + model.addAttribute(ATTR_ERROR, getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java index 5bc694e..56687e3 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java @@ -238,7 +238,7 @@ public String refund( @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute(ATTR_ERROR, e.getMessage()); + model.addAttribute(ATTR_ERROR, getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java index 789c700..57f03e2 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -276,7 +276,7 @@ private List getSessionDetails(HttpServletRequest req) { @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { - model.addAttribute(ATTR_ERROR, e.getMessage()); + model.addAttribute(ATTR_ERROR, getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallDiferidoController.java b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallDiferidoController.java index beae58a..578ae49 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallDiferidoController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallDiferidoController.java @@ -302,7 +302,7 @@ private List getSessionDetails(HttpServletRequest req) { @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { - model.addAttribute(ATTR_ERROR, e.getMessage()); + model.addAttribute(ATTR_ERROR, getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusController.java b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusController.java index 841f617..facc093 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusController.java @@ -175,7 +175,7 @@ public String refund(@RequestParam("token_ws") String token, @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute("error", getDisplayableErrorMessage(e)); return VIEW_ERROR; } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusDeferredController.java b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusDeferredController.java index c69be62..e04cf48 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusDeferredController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusDeferredController.java @@ -195,7 +195,7 @@ public String refund(@RequestParam("token_ws") String token, @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute("error", getDisplayableErrorMessage(e)); return VIEW_ERROR; } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java index 2440b51..69d87d6 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java @@ -200,7 +200,7 @@ public String refund(@RequestParam("token") String token, @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute("error", getDisplayableErrorMessage(e)); return VIEW_ERROR; } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java index bc81e94..46b152b 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java @@ -233,7 +233,7 @@ public String capture( @ExceptionHandler(Exception.class) public String handleException(Exception e, Model model) { log.error("Error inesperado", e); - model.addAttribute("error", e.getMessage()); + model.addAttribute("error", getDisplayableErrorMessage(e)); return VIEW_ERROR; } From 9cd82e50a136765fe3c30c0499cf60f6af2a1d41 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Thu, 23 Apr 2026 13:51:33 -0300 Subject: [PATCH 65/78] feat: remove server.forward-headers-strategy property from application configuration --- src/main/resources/application.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 920a5fe..f3fc4f8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1 @@ spring.application.name=transbank-sdk-java-example -server.forward-headers-strategy=framework From 2447df5ce7d3311c845f5d593abcb8ccdd7a31b0 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:02:01 -0300 Subject: [PATCH 66/78] feat: add .env to .gitignore to prevent environment file from being tracked --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a01c531..4a81aba 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ Test.java build gradle .DS_Store +.env From 183e4a45ceaa4414ba8cba550209737798fbff59 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:02:12 -0300 Subject: [PATCH 67/78] feat: add .env.example file and update README with environment variable instructions --- .env.example | 4 ++++ README.md | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..86a26d6 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +ONECLICK_MALL_PROMOTIONS_API_KEY=tu-api-key +ONECLICK_MALL_PROMOTIONS_COMMERCE_CODE=tu-commerce-code +ONECLICK_MALL_PROMOTIONS_CHILD1_COMMERCE_CODE=tu-child-commerce-code-1 +ONECLICK_MALL_PROMOTIONS_CHILD2_COMMERCE_CODE=tu-child-commerce-code-2 diff --git a/README.md b/README.md index 72d5146..e1f4a52 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,15 @@ mvn clean install ## Ejecución +El flujo `Webpay Oneclick Mall Promociones` usa estas variables de entorno: + +```bash +ONECLICK_MALL_PROMOTIONS_API_KEY=tu-api-key +ONECLICK_MALL_PROMOTIONS_COMMERCE_CODE=tu-commerce-code +ONECLICK_MALL_PROMOTIONS_CHILD1_COMMERCE_CODE=tu-child-commerce-code-1 +ONECLICK_MALL_PROMOTIONS_CHILD2_COMMERCE_CODE=tu-child-commerce-code-2 +``` + Para poder correr el proyecto en modo desarrollo, debes utilizar el siguiente comando en una consola: ```bash From c347620d7a9dc8390a9284548cfe1fb6de6caa0e Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:03:37 -0300 Subject: [PATCH 68/78] feat: add OneClick Mall promotions configuration to application properties --- src/main/resources/application.properties | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3fc4f8..21666f8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,6 @@ spring.application.name=transbank-sdk-java-example + +oneclick.mall.promotions.api-key=${ONECLICK_MALL_PROMOTIONS_API_KEY:} +oneclick.mall.promotions.commerce-code=${ONECLICK_MALL_PROMOTIONS_COMMERCE_CODE:} +oneclick.mall.promotions.child1-commerce-code=${ONECLICK_MALL_PROMOTIONS_CHILD1_COMMERCE_CODE:} +oneclick.mall.promotions.child2-commerce-code=${ONECLICK_MALL_PROMOTIONS_CHILD2_COMMERCE_CODE:} From 2fbf6ac007490ed83e4a8de7dbaf435fc4467fe9 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:11:02 -0300 Subject: [PATCH 69/78] feat: implement PromotionsOneclickMallController for handling Oneclick Mall promotions --- .../PromotionsOneclickMallController.java | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java diff --git a/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java new file mode 100644 index 0000000..3d26a40 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java @@ -0,0 +1,350 @@ +package cl.transbank.webpay.example.controllers; + +import cl.transbank.common.IntegrationType; +import cl.transbank.webpay.common.WebpayOptions; +import cl.transbank.webpay.exception.*; +import cl.transbank.webpay.oneclick.Oneclick; +import cl.transbank.webpay.oneclick.model.MallTransactionCreateDetails; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +@Log4j2 +@Controller +@RequestMapping("/promotions-oneclick-mall") +public class PromotionsOneclickMallController extends BaseController { + + private static final int AUTHORIZED = 0; + private static final String TEMPLATE_FOLDER = "promotions_oneclick_mall"; + private static final String BASE_URL = "/promotions-oneclick-mall"; + private static final String PRODUCT = "Webpay Oneclick Mall Promociones"; + private static final String MODEL_NAVIGATION = "navigation"; + private static final String MODEL_RESPONSE = "response_data"; + private static final String MODEL_RESPONSE_JSON = "response_data_json"; + private static final String REQUEST_DATA_JSON = "request_data_json"; + private static final String REQUEST = "Petición"; + private static final String RESPONSE = "Respuesta"; + + private static final String VIEW_START = TEMPLATE_FOLDER + "/start"; + private static final String VIEW_FINISH = TEMPLATE_FOLDER + "/finish"; + private static final String VIEW_AUTHORIZE = TEMPLATE_FOLDER + "/authorize"; + private static final String VIEW_DELETE = TEMPLATE_FOLDER + "/delete"; + private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status"; + private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund"; + private static final String VIEW_INFO_BIN = TEMPLATE_FOLDER + "/info_bin"; + + private static final String ENV_API_KEY = "ONECLICK_MALL_PROMOTIONS_API_KEY"; + private static final String ENV_COMMERCE_CODE = "ONECLICK_MALL_PROMOTIONS_COMMERCE_CODE"; + private static final String ENV_CHILD1_COMMERCE_CODE = "ONECLICK_MALL_PROMOTIONS_CHILD1_COMMERCE_CODE"; + private static final String ENV_CHILD2_COMMERCE_CODE = "ONECLICK_MALL_PROMOTIONS_CHILD2_COMMERCE_CODE"; + private static final String TBK_USER = "tbkUser"; + private static final String REQUEST_KEY = "request"; + private static final String RESPONSE_KEY = "response"; + private static final String DATA_KEY = "Datos"; + private static final String USERNAME = "username"; + private static final String REQUEST_DATA = "request_data"; + + private static final Map NAV_START; + private static final Map NAV_FINISH; + private static final Map NAV_FINISH_RECOVER; + private static final Map NAV_FINISH_REJECTED; + private static final Map NAV_AUTHORIZE; + private static final Map NAV_DELETE; + private static final Map NAV_STATUS; + private static final Map NAV_REFUND; + private static final Map NAV_INFO_BIN; + private static final Map DOTENV = loadDotenv(); + + @Value("${oneclick.mall.promotions.api-key:}") + private String apiKey; + + @Value("${oneclick.mall.promotions.commerce-code:}") + private String commerceCode; + + @Value("${oneclick.mall.promotions.child1-commerce-code:}") + private String child1CommerceCode; + + @Value("${oneclick.mall.promotions.child2-commerce-code:}") + private String child2CommerceCode; + + static { + NAV_START = new LinkedHashMap<>(); + NAV_START.put(REQUEST_KEY, REQUEST); + NAV_START.put(RESPONSE_KEY, RESPONSE); + NAV_START.put("form", "Creación del formulario"); + NAV_START.put("example", "Ejemplo"); + + NAV_FINISH = new LinkedHashMap<>(); + NAV_FINISH.put("data", DATA_KEY); + NAV_FINISH.put(REQUEST_KEY, REQUEST); + NAV_FINISH.put(RESPONSE_KEY, RESPONSE); + NAV_FINISH.put("authorize", "Autorizar una transacción"); + + NAV_FINISH_RECOVER = new LinkedHashMap<>(); + NAV_FINISH_RECOVER.put("data", DATA_KEY); + + NAV_FINISH_REJECTED = new LinkedHashMap<>(); + NAV_FINISH_REJECTED.put("data", DATA_KEY); + NAV_FINISH_REJECTED.put(REQUEST_KEY, REQUEST); + NAV_FINISH_REJECTED.put(RESPONSE_KEY, RESPONSE); + + NAV_AUTHORIZE = new LinkedHashMap<>(); + NAV_AUTHORIZE.put(REQUEST_KEY, REQUEST); + NAV_AUTHORIZE.put(RESPONSE_KEY, RESPONSE); + NAV_AUTHORIZE.put("done", "Listo"); + + NAV_DELETE = new LinkedHashMap<>(); + NAV_DELETE.put(REQUEST_KEY, REQUEST); + NAV_DELETE.put(RESPONSE_KEY, RESPONSE); + + NAV_STATUS = new LinkedHashMap<>(); + NAV_STATUS.put(REQUEST_KEY, REQUEST); + NAV_STATUS.put(RESPONSE_KEY, RESPONSE); + + NAV_REFUND = NAV_STATUS; + NAV_INFO_BIN = NAV_STATUS; + } + + @GetMapping({"", "/", "/start"}) + public String start(HttpServletRequest req, Model model) + throws IOException, InscriptionStartException { + model.addAttribute(MODEL_NAVIGATION, NAV_START); + addBreadcrumbs(model, "Iniciar inscripción", "#"); + + String username = "User-" + getRandomNumber(); + String email = "user." + getRandomNumber() + "@example.com"; + String requestUrl = req.getRequestURL().toString(); + String returnUrl = requestUrl.replaceFirst("/start/?$", "").replaceFirst("/$", "") + "/finish"; + + var resp = getInscription().start(username, email, returnUrl); + + Map requestData = Map.of( + USERNAME, username, + "email", email, + "returnUrl", returnUrl + ); + + model.addAttribute(REQUEST_DATA, requestData); + model.addAttribute(REQUEST_DATA_JSON, toJson(requestData)); + model.addAttribute(MODEL_RESPONSE, resp); + model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); + + req.getSession().setAttribute(USERNAME, username); + req.getSession().setAttribute("email", email); + + return VIEW_START; + } + + @GetMapping("/finish") + public String finish(HttpServletRequest req, + @RequestParam Map params, + @RequestParam(name = "TBK_TOKEN", required = false) String token, + @RequestParam(name = "TBK_ORDEN_COMPRA", required = false) String ordenCompra, + Model model) + throws IOException, InscriptionFinishException { + model.addAttribute(MODEL_NAVIGATION, NAV_FINISH); + addBreadcrumbs(model, "Finalizar inscripción", "#"); + + if (ordenCompra != null) { + model.addAttribute(MODEL_NAVIGATION, NAV_FINISH_RECOVER); + model.addAttribute(REQUEST_DATA_JSON, toJson(params)); + return VIEW_RECOVER_ERROR; + } + + String username = (String) req.getSession().getAttribute(USERNAME); + var resp = getInscription().finish(token); + + model.addAttribute(MODEL_RESPONSE, resp); + model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); + + if (resp.getResponseCode() != AUTHORIZED) { + model.addAttribute(MODEL_NAVIGATION, NAV_FINISH_REJECTED); + model.addAttribute(REQUEST_DATA_JSON, toJson(params)); + return VIEW_REJECTED_ERROR; + } + + req.getSession().setAttribute(TBK_USER, resp.getTbkUser()); + + model.addAttribute(REQUEST_DATA, Map.of( + USERNAME, username, + TBK_USER, resp.getTbkUser() + )); + model.addAttribute("token", token); + model.addAttribute(USERNAME, username); + model.addAttribute("tbk_user", resp.getTbkUser()); + model.addAttribute("child_commerce_code1", getConfiguredValue(child1CommerceCode, ENV_CHILD1_COMMERCE_CODE)); + model.addAttribute("child_commerce_code2", getConfiguredValue(child2CommerceCode, ENV_CHILD2_COMMERCE_CODE)); + + return VIEW_FINISH; + } + + @GetMapping("/delete") + public String delete(@RequestParam String username, + @RequestParam("tbk_user") String tbkUser, + Model model) + throws IOException, InscriptionDeleteException { + model.addAttribute(MODEL_NAVIGATION, NAV_DELETE); + addBreadcrumbs(model, "Eliminar inscripción", "#"); + getInscription().delete(tbkUser, username); + return VIEW_DELETE; + } + + @GetMapping("/authorize") + public String authorize( + @RequestParam String username, + @RequestParam("tbk_user") String tbkUser, + @RequestParam("child_commerce_code1") String childCode1, + @RequestParam("child_commerce_code2") String childCode2, + @RequestParam("child_commerce_amount1") double amount1, + @RequestParam("child_commerce_amount2") double amount2, + @RequestParam("child_commerce_installments1") int installments1, + @RequestParam("child_commerce_installments2") int installments2, + Model model) + throws IOException, TransactionAuthorizeException { + model.addAttribute(MODEL_NAVIGATION, NAV_AUTHORIZE); + addBreadcrumbs(model, "Autorizar transacción", "#"); + + String buyOrder = "buyOrder_" + getRandomNumber(); + String childBuyOrder1 = "childBuyOrder1_" + getRandomNumber(); + String childBuyOrder2 = "childBuyOrder2_" + getRandomNumber(); + + var details = MallTransactionCreateDetails + .build() + .add(amount1, childCode1, childBuyOrder1, (byte) installments1) + .add(amount2, childCode2, childBuyOrder2, (byte) installments2); + + var resp = getTransaction().authorize(username, tbkUser, buyOrder, details); + model.addAttribute(MODEL_RESPONSE, resp); + model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); + return VIEW_AUTHORIZE; + } + + @GetMapping("/status") + public String status(@RequestParam("buy_order") String buyOrder, Model model) + throws IOException, TransactionStatusException { + model.addAttribute(MODEL_NAVIGATION, NAV_STATUS); + addBreadcrumbs(model, "Consultar estado", "#"); + var resp = getTransaction().status(buyOrder); + model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); + return VIEW_STATUS; + } + + @GetMapping("/refund") + public String refund(@RequestParam("buy_order") String buyOrder, + @RequestParam("child_buy_order") String childBuyOrder, + @RequestParam("child_commerce_code") String childCommerceCode, + @RequestParam double amount, + Model model) + throws IOException, TransactionRefundException { + model.addAttribute(MODEL_NAVIGATION, NAV_REFUND); + addBreadcrumbs(model, "Reembolso", "#"); + var resp = getTransaction().refund(buyOrder, childCommerceCode, childBuyOrder, amount); + model.addAttribute("buy_order", buyOrder); + model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); + return VIEW_REFUND; + } + + @GetMapping("/info-bin") + public String infoBin(@RequestParam("tbk_user") String tbkUser, Model model) + throws IOException, QueryBinException { + model.addAttribute(MODEL_NAVIGATION, NAV_INFO_BIN); + addBreadcrumbs(model, "Consulta servicio de bines", "#"); + var requestData = Map.of(TBK_USER, tbkUser); + var resp = getBinInfo().queryBin(tbkUser); + model.addAttribute(REQUEST_DATA, requestData); + model.addAttribute(REQUEST_DATA_JSON, toJson(requestData)); + model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); + return VIEW_INFO_BIN; + } + + @ExceptionHandler(Exception.class) + public String handleException(Exception e, Model model) { + log.error("Error inesperado", e); + model.addAttribute("error", getDisplayableErrorMessage(e)); + return VIEW_ERROR; + } + + private void addBreadcrumbs(Model model, String label, String url) { + Map breadcrumbs = new LinkedHashMap<>(); + breadcrumbs.put("Inicio", "/"); + breadcrumbs.put(PRODUCT, BASE_URL); + if (label != null) breadcrumbs.put(label, url); + model.addAttribute("product", PRODUCT); + model.addAttribute("base_url", BASE_URL); + model.addAttribute("breadcrumbs", breadcrumbs); + } + + private Oneclick.MallInscription getInscription() { + return new Oneclick.MallInscription(getOptions()); + } + + private Oneclick.MallTransaction getTransaction() { + return new Oneclick.MallTransaction(getOptions()); + } + + private Oneclick.MallBinInfo getBinInfo() { + return new Oneclick.MallBinInfo(getOptions()); + } + + private WebpayOptions getOptions() { + return new WebpayOptions( + getConfiguredValue(commerceCode, ENV_COMMERCE_CODE), + getConfiguredValue(apiKey, ENV_API_KEY), + IntegrationType.TEST + ); + } + + private String getConfiguredValue(String propertyValue, String envName) { + if (propertyValue != null && !propertyValue.isBlank()) { + return propertyValue; + } + + return getEnv(envName); + } + + private String getEnv(String name) { + String value = System.getenv(name); + if (value == null || value.isBlank()) { + value = DOTENV.get(name); + } + if (value == null || value.isBlank()) { + throw new IllegalStateException("La variable de entorno " + name + " es obligatoria."); + } + return value; + } + + private static Map loadDotenv() { + Path path = Path.of(".env"); + if (!Files.exists(path)) { + return Map.of(); + } + + Map values = new HashMap<>(); + try { + for (String line : Files.readAllLines(path)) { + String trimmed = line.trim(); + if (trimmed.isEmpty() || trimmed.startsWith("#") || !trimmed.contains("=")) { + continue; + } + String[] parts = trimmed.split("=", 2); + values.put(parts[0].trim(), parts[1].trim()); + } + } catch (IOException e) { + return Map.of(); + } + return values; + } +} From 6e39db9c764bc5c6720384dff69c4c02371e2052 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:11:12 -0300 Subject: [PATCH 70/78] feat: add start.html template for Webpay Oneclick Mall inscription process --- .../promotions_oneclick_mall/start.html | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/resources/templates/promotions_oneclick_mall/start.html diff --git a/src/main/resources/templates/promotions_oneclick_mall/start.html b/src/main/resources/templates/promotions_oneclick_mall/start.html new file mode 100644 index 0000000..95760bf --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/start.html @@ -0,0 +1,34 @@ +
+
+

Webpay Oneclick Mall Promociones - Creación de Inscripción

+

En esta etapa comienza el proceso de inscripción del medio de pago. Este paso inicial es fundamental para dirigir al Tarjetahabiente al formulario de inscripción.

+

Todas las transacciones en este proyecto de ejemplo son realizadas en ambiente de integración.

+

Paso 1: Petición

+
    +
  1. Comienza por importar la librería Oneclick en tu proyecto.
  2. +
  3. Luego, inicia una inscripción utilizando las funciones proporcionadas mediante el SDK.
  4. +
+
var options = new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST);
+var inscription = new Oneclick.MallInscription(options);
+var resp = inscription.start(username, email, responseUrl);
+

Paso 2: Respuesta

+

Una vez que hayas iniciado la inscripción, aquí encontrarás los datos de respuesta generados por el proceso.

+
+

Paso 3: Creación del formulario

+

Utiliza estos datos de respuesta para redireccionar al usuario al formulario de inscripción del Tarjetahabiente.

+
+

Ejemplo

+

Para llevar a cabo una inscripción en nuestro sistema, primero debemos crearla.

+
+

Para fines de este ejemplo, haremos visible el campo "TBK_TOKEN", el cual es esencial para completar el proceso.

+ Antes de continuar al formulario de Webpay, asegúrate de contar con los datos de las tarjetas de prueba que están en la documentación. +
+
+ Formulario de redirección + + + +
+
+
+
From 7388d08d97ccbfe8d5e92799ca707577b72bed53 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:11:32 -0300 Subject: [PATCH 71/78] feat: add finish.html template for Webpay Oneclick Mall enrollment completion --- .../promotions_oneclick_mall/finish.html | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/resources/templates/promotions_oneclick_mall/finish.html diff --git a/src/main/resources/templates/promotions_oneclick_mall/finish.html b/src/main/resources/templates/promotions_oneclick_mall/finish.html new file mode 100644 index 0000000..5dd4f51 --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/finish.html @@ -0,0 +1,42 @@ +
+
+

Webpay Oneclick Mall Promociones - Finalizar inscripción

+

En esta fase, completaremos el proceso de inscripción, permitiéndonos posteriormente realizar cargos a la tarjeta inscrita.

+

Paso 1: Datos recibidos

+

Después de finalizar el flujo en el formulario de inscripción, recibirás un GET con la siguiente información:

+
{"TBK_TOKEN":""}
+

Paso 2: Petición de autorización

+

Utiliza el token recibido para finalizar la inscripción mediante una nueva llamada a Oneclick.

+
var response = inscription.finish(token);
+

Paso 3: Respuesta

+

Transbank responderá con información crucial. Guarda estos detalles, ya que serán necesarios para autorizar transacciones futuras.

+
+

¡La tarjeta ya está inscrita!

+

Con la inscripción exitosa se pueden autorizar transacciones.

+

Autorizar una transacción

+

Asegúrate de guardar los datos de la respuesta obtenidos durante la inscripción.

+
+

Después de una inscripción exitosa, tienes tres opciones: autorizar un pago, consultar bines o borrar al usuario que se acaba de inscribir.

+
+ + + + +
+

Tienda 1

+
+
+
+
+

Tienda 2

+
+
+
+
+ +
+
+ CONSULTA BINES + BORRAR USUARIO +
+
From f6965ab4bf868cba29dca1e833972d5b177e7bc6 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:11:40 -0300 Subject: [PATCH 72/78] feat: add info_bin.html template for Webpay Oneclick Mall BIN consultation --- .../promotions_oneclick_mall/info_bin.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/resources/templates/promotions_oneclick_mall/info_bin.html diff --git a/src/main/resources/templates/promotions_oneclick_mall/info_bin.html b/src/main/resources/templates/promotions_oneclick_mall/info_bin.html new file mode 100644 index 0000000..2bed3fa --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/info_bin.html @@ -0,0 +1,13 @@ +
+
+

Webpay Oneclick Mall Promociones - Consulta servicio de bines

+

Con esta operación puedes consultar el BIN asociado al medio de pago inscrito usando el valor de tbkUser. Si el comercio no tiene habilitado este servicio, la respuesta incluirá un error.

+

Paso 1: Petición

+

Para realizar la consulta, necesitarás el tbkUser obtenido al finalizar la inscripción.

+
var resp = binInfo.queryBin(tbkUser);
+
+

Paso 2: Respuesta

+

Transbank responderá con la información del BIN consultado.

+
+
+
From 4497e87674b0b08eefa8cbbc94a4c3adf8c603ad Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:11:47 -0300 Subject: [PATCH 73/78] feat: add authorize.html template for Webpay Oneclick Mall payment authorization --- .../promotions_oneclick_mall/authorize.html | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/resources/templates/promotions_oneclick_mall/authorize.html diff --git a/src/main/resources/templates/promotions_oneclick_mall/authorize.html b/src/main/resources/templates/promotions_oneclick_mall/authorize.html new file mode 100644 index 0000000..dc72a19 --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/authorize.html @@ -0,0 +1,35 @@ +
+
+

Webpay Oneclick Mall Promociones - Autorizar pago

+

En este primer paso, procederemos a autorizar una transacción en la tarjeta que ha sido previamente inscrita.

+

Paso 1: Petición

+

Ahora que contamos con el username y el tbkUser, estamos listos para autorizar transacciones.

+
var details = MallTransactionCreateDetails.build()
+    .add(amount1, childCode1, childBuyOrder1, (byte) installments1)
+    .add(amount2, childCode2, childBuyOrder2, (byte) installments2);
+var resp = transaction.authorize(username, tbkUser, buyOrder, details);
+

Paso 2: Respuesta

+

Verifica que el campo responseCode tenga valor 0 y que el campo status sea AUTHORIZED.

+
+

¡Listo!

+

Después de autorizar la transacción, considera las siguientes utilidades adicionales:

+
    +
  • Reembolsar: Puedes reversar o anular el pago según ciertas condiciones comerciales.
  • +
  • Consultar Estado: Hasta 7 días después de realizada la transacción, podrás consultar el estado.
  • +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ CONSULTAR ESTADO +
+
From 6d25fd5dca94115135afe5309b362e19f22bfc64 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:11:58 -0300 Subject: [PATCH 74/78] feat: add delete, refund, and status templates for Webpay Oneclick Mall promotions --- .../templates/promotions_oneclick_mall/delete.html | 12 ++++++++++++ .../templates/promotions_oneclick_mall/refund.html | 14 ++++++++++++++ .../templates/promotions_oneclick_mall/status.html | 12 ++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/main/resources/templates/promotions_oneclick_mall/delete.html create mode 100644 src/main/resources/templates/promotions_oneclick_mall/refund.html create mode 100644 src/main/resources/templates/promotions_oneclick_mall/status.html diff --git a/src/main/resources/templates/promotions_oneclick_mall/delete.html b/src/main/resources/templates/promotions_oneclick_mall/delete.html new file mode 100644 index 0000000..30225b2 --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/delete.html @@ -0,0 +1,12 @@ +
+
+

Webpay Oneclick Mall Promociones - Borrar usuario

+

En este paso fundamental, procederemos a eliminar la inscripción del usuario y su medio de pago.

+

Paso 1: Petición

+

Para llevar a cabo la eliminación, necesitas el "username" y el "tbkUser".

+
inscription.delete(tbkUser, username);
+

Paso 2: Respuesta

+

En caso de éxito, Transbank responderá con un status code 204 (No Content), y el SDK no retornará ningún valor.

+

En el caso de que no se encuentre el "username" o el "tbkUser", el SDK lanzará una excepción del tipo TransbankException.

+
+
diff --git a/src/main/resources/templates/promotions_oneclick_mall/refund.html b/src/main/resources/templates/promotions_oneclick_mall/refund.html new file mode 100644 index 0000000..361d9c3 --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/refund.html @@ -0,0 +1,14 @@ +
+
+

Webpay Oneclick Mall Promociones - Reembolsar

+

En esta etapa, tienes la opción de solicitar el reembolso del monto al titular de la tarjeta.

+

Paso 1 - Petición

+

Para llevar a cabo el reembolso, necesitas la orden de compra, el código de comercio, la orden hija y el monto.

+

En este link podrás ver mayor información sobre las condiciones y casos para anular o reversar transacciones.

+
var resp = transaction.refund(buyOrder, childCommerceCode, childBuyOrder, amount);
+

Paso 2: Respuesta

+

Transbank responderá con el resultado del proceso de reembolso.

+
+ CONSULTAR ESTADO +
+
diff --git a/src/main/resources/templates/promotions_oneclick_mall/status.html b/src/main/resources/templates/promotions_oneclick_mall/status.html new file mode 100644 index 0000000..8c1bc45 --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/status.html @@ -0,0 +1,12 @@ +
+
+

Webpay Oneclick Mall Promociones - Consultar estado de transacción

+

Puedes solicitar el estado de una transacción hasta 7 días después de su realización. No hay límite de solicitudes durante ese período.

+

Paso 1 - Petición:

+

Para realizar la consulta, necesitarás el buyOrder de la transacción de interés.

+
var response = transaction.status(buyOrder);
+

Paso 2: Respuesta

+

Transbank responderá con la siguiente información.

+
+
+
From 070f7350b45ced2f0ad0b993707e4c43a2d30503 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Tue, 12 May 2026 18:20:20 -0300 Subject: [PATCH 75/78] style: improve formatting and readability of info_bin.html content --- .../promotions_oneclick_mall/info_bin.html | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/resources/templates/promotions_oneclick_mall/info_bin.html b/src/main/resources/templates/promotions_oneclick_mall/info_bin.html index 2bed3fa..ca71de9 100644 --- a/src/main/resources/templates/promotions_oneclick_mall/info_bin.html +++ b/src/main/resources/templates/promotions_oneclick_mall/info_bin.html @@ -1,13 +1,24 @@

Webpay Oneclick Mall Promociones - Consulta servicio de bines

-

Con esta operación puedes consultar el BIN asociado al medio de pago inscrito usando el valor de tbkUser. Si el comercio no tiene habilitado este servicio, la respuesta incluirá un error.

+

+ Con esta operación puedes consultar el BIN asociado al medio de pago + inscrito usando el valor de tbkUser. Si el comercio no tiene + habilitado este servicio, la respuesta incluirá un error. +

Paso 1: Petición

-

Para realizar la consulta, necesitarás el tbkUser obtenido al finalizar la inscripción.

-
var resp = binInfo.queryBin(tbkUser);
+

+ Para realizar la consulta, necesitarás el tbkUser obtenido al + finalizar la inscripción. +

+
var resp = binInfo.queryBin(tbkUser);

Paso 2: Respuesta

-

Transbank responderá con la información del BIN consultado.

+

+ Transbank responderá con la información del BIN consultado. +

From 70900b1c93d51c538193512273428021605811f8 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 13 May 2026 13:13:07 -0300 Subject: [PATCH 76/78] feat: update transbank-sdk-java dependency version to 6.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7141c09..fd9be83 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ com.github.transbankdevelopers transbank-sdk-java - 6.0.0 + 6.1.0 From 3d9654d86bbc06f263b985d19fcaab640f9e3019 Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 13 May 2026 13:32:12 -0300 Subject: [PATCH 77/78] feat: update labels in authorize and capture templates for clarity --- .../oneclick_mall_deferred/authorize.html | 8 +- .../oneclick_mall_deferred/capture.html | 133 ++++++++++-------- .../promotions_oneclick_mall/refund.html | 37 ++++- 3 files changed, 107 insertions(+), 71 deletions(-) diff --git a/src/main/resources/templates/oneclick_mall_deferred/authorize.html b/src/main/resources/templates/oneclick_mall_deferred/authorize.html index 221dc6b..3833576 100644 --- a/src/main/resources/templates/oneclick_mall_deferred/authorize.html +++ b/src/main/resources/templates/oneclick_mall_deferred/authorize.html @@ -70,7 +70,7 @@

¡Casi listo!

Código de comercio (tienda): ¡Casi listo!
Orden de compra (tienda): ¡Casi listo!
Código de autorización (tienda): ¡Casi listo!
Monto a capturar (tienda): -
+
+

Webpay Oneclick Mall Diferido - Capturar transacción diferida

-

Webpay Oneclick Mall Diferido - Capturar transacción diferida

+

+ En este paso debemos capturar la transacción para realmente capturar el + dinero que había sido previamente reservado al hacer la transacción. +

-

- En este paso debemos capturar la transacción para realmente capturar el dinero que había sido - previamente reservado al hacer la transacción. -

+

Paso 1: Petición

-

Paso 1: Petición

+

+ Para capturar una transacción necesitaremos el código de comercio de la + tienda, la orden de compra de la tienda, el código de autorización y el + monto a capturar. Se hace de la siguiente manera: +

-

- Para capturar una transacción necesitaremos el código de comercio de la tienda hija, la orden de compra - de la tienda hija, el código de autorización y el monto a capturar. Se hace de la siguiente manera: -

- -

+    

 var options = new WebpayOptions(
     IntegrationCommerceCodes.ONECLICK_MALL_DEFERRED,
     IntegrationApiKeys.WEBPAY,
@@ -25,58 +25,69 @@ 

Paso 1: Petición

var response = transaction.capture(childCommerceCode, childBuyOrder, authorizationCode, amount);
-

Paso 2: Respuesta

- -

- Una vez creada la transacción, recibirás los siguientes datos de respuesta: -

+

Paso 2: Respuesta

-
+

+ Una vez creada la transacción, recibirás los siguientes datos de + respuesta: +

-

¡Transacción Capturada!

-

- Con la transacción capturada, puedes mostrar al usuario una página de éxito de la transacción, - proporcionándole la confirmación de que el proceso se ha completado con éxito. -

-

- Otras Utilidades: Después de confirmar la transacción, considera las siguientes utilidades - adicionales: -

-

- Reembolso: Evalúa la posibilidad de reversar o anular el pago según ciertas condiciones - comerciales. -

-

- Consulta de Estado: Hasta 7 días después de la transacción, puedes consultar su estado para - obtener más detalles. -

+
-
- - - +

¡Transacción Capturada!

+

+ Con la transacción capturada, puedes mostrar al usuario una página de + éxito de la transacción, proporcionándole la confirmación de que el + proceso se ha completado con éxito. +

+

+ Otras Utilidades: Después de confirmar la transacción, + considera las siguientes utilidades adicionales: +

+

+ Reembolso: Evalúa la posibilidad de reversar o anular el + pago según ciertas condiciones comerciales. +

+

+ Consulta de Estado: Hasta 7 días después de la + transacción, puedes consultar su estado para obtener más detalles. +

-
-
- - -
+ + + + -
- - - CONSULTAR ESTADO - -
-
-
+
+
+ + +
-
+
+ + + CONSULTAR ESTADO + +
+
+ +
diff --git a/src/main/resources/templates/promotions_oneclick_mall/refund.html b/src/main/resources/templates/promotions_oneclick_mall/refund.html index 361d9c3..5732feb 100644 --- a/src/main/resources/templates/promotions_oneclick_mall/refund.html +++ b/src/main/resources/templates/promotions_oneclick_mall/refund.html @@ -1,14 +1,39 @@

Webpay Oneclick Mall Promociones - Reembolsar

-

En esta etapa, tienes la opción de solicitar el reembolso del monto al titular de la tarjeta.

+

+ En esta etapa, tienes la opción de solicitar el reembolso del monto al + titular de la tarjeta. +

Paso 1 - Petición

-

Para llevar a cabo el reembolso, necesitas la orden de compra, el código de comercio, la orden hija y el monto.

-

En este link podrás ver mayor información sobre las condiciones y casos para anular o reversar transacciones.

+

+ Para llevar a cabo el reembolso, necesitas la orden de compra, el código + de comercio(tienda), la orden de compra(tienda) y el monto. +

+

+ En + este link + podrás ver mayor información sobre las condiciones y casos para anular o + reversar transacciones. +

var resp = transaction.refund(buyOrder, childCommerceCode, childBuyOrder, amount);

Paso 2: Respuesta

-

Transbank responderá con el resultado del proceso de reembolso.

-
- CONSULTAR ESTADO +

+ Transbank responderá con el resultado del proceso de reembolso. +

+
+ CONSULTAR ESTADO
From 53a8538a4b0c18872bafc7f2c463fd36c5c8ab8b Mon Sep 17 00:00:00 2001 From: victor mendoza Date: Wed, 13 May 2026 14:42:13 -0300 Subject: [PATCH 78/78] feat: refactor navigation maps in PromotionsOneclickMallController for improved readability and maintainability --- .../example/controllers/BaseController.java | 10 ++ .../PromotionsOneclickMallController.java | 108 +++++++----------- 2 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java index 2cb4300..7dccba6 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java @@ -6,6 +6,8 @@ import com.google.gson.JsonSerializer; import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Random; public abstract class BaseController { @@ -49,4 +51,12 @@ protected String getDisplayableErrorMessage(Exception e) { } return GENERIC_ERROR_MESSAGE; } + + protected static Map navigation(String... entries) { + Map navigation = new LinkedHashMap<>(); + for (int i = 0; i < entries.length; i += 2) { + navigation.put(entries[i], entries[i + 1]); + } + return navigation; + } } diff --git a/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java index 3d26a40..fec997c 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java @@ -57,15 +57,31 @@ public class PromotionsOneclickMallController extends BaseController { private static final String USERNAME = "username"; private static final String REQUEST_DATA = "request_data"; - private static final Map NAV_START; - private static final Map NAV_FINISH; - private static final Map NAV_FINISH_RECOVER; - private static final Map NAV_FINISH_REJECTED; - private static final Map NAV_AUTHORIZE; - private static final Map NAV_DELETE; - private static final Map NAV_STATUS; - private static final Map NAV_REFUND; - private static final Map NAV_INFO_BIN; + private static final Map NAV_START = navigation( + REQUEST_KEY, REQUEST, + RESPONSE_KEY, RESPONSE, + "form", "Creación del formulario", + "example", "Ejemplo" + ); + private static final Map NAV_FINISH = navigation( + "data", DATA_KEY, + REQUEST_KEY, REQUEST, + RESPONSE_KEY, RESPONSE, + "authorize", "Autorizar una transacción" + ); + private static final Map NAV_FINISH_RECOVER = navigation("data", DATA_KEY); + private static final Map NAV_FINISH_REJECTED = navigation( + "data", DATA_KEY, + REQUEST_KEY, REQUEST, + RESPONSE_KEY, RESPONSE + ); + private static final Map NAV_AUTHORIZE = navigation( + REQUEST_KEY, REQUEST, + RESPONSE_KEY, RESPONSE, + "done", "Listo" + ); + private static final Map NAV_DELETE = navigation(REQUEST_KEY, REQUEST, RESPONSE_KEY, RESPONSE); + private static final Map NAV_TWO_STEP = navigation(REQUEST_KEY, REQUEST, RESPONSE_KEY, RESPONSE); private static final Map DOTENV = loadDotenv(); @Value("${oneclick.mall.promotions.api-key:}") @@ -80,49 +96,10 @@ public class PromotionsOneclickMallController extends BaseController { @Value("${oneclick.mall.promotions.child2-commerce-code:}") private String child2CommerceCode; - static { - NAV_START = new LinkedHashMap<>(); - NAV_START.put(REQUEST_KEY, REQUEST); - NAV_START.put(RESPONSE_KEY, RESPONSE); - NAV_START.put("form", "Creación del formulario"); - NAV_START.put("example", "Ejemplo"); - - NAV_FINISH = new LinkedHashMap<>(); - NAV_FINISH.put("data", DATA_KEY); - NAV_FINISH.put(REQUEST_KEY, REQUEST); - NAV_FINISH.put(RESPONSE_KEY, RESPONSE); - NAV_FINISH.put("authorize", "Autorizar una transacción"); - - NAV_FINISH_RECOVER = new LinkedHashMap<>(); - NAV_FINISH_RECOVER.put("data", DATA_KEY); - - NAV_FINISH_REJECTED = new LinkedHashMap<>(); - NAV_FINISH_REJECTED.put("data", DATA_KEY); - NAV_FINISH_REJECTED.put(REQUEST_KEY, REQUEST); - NAV_FINISH_REJECTED.put(RESPONSE_KEY, RESPONSE); - - NAV_AUTHORIZE = new LinkedHashMap<>(); - NAV_AUTHORIZE.put(REQUEST_KEY, REQUEST); - NAV_AUTHORIZE.put(RESPONSE_KEY, RESPONSE); - NAV_AUTHORIZE.put("done", "Listo"); - - NAV_DELETE = new LinkedHashMap<>(); - NAV_DELETE.put(REQUEST_KEY, REQUEST); - NAV_DELETE.put(RESPONSE_KEY, RESPONSE); - - NAV_STATUS = new LinkedHashMap<>(); - NAV_STATUS.put(REQUEST_KEY, REQUEST); - NAV_STATUS.put(RESPONSE_KEY, RESPONSE); - - NAV_REFUND = NAV_STATUS; - NAV_INFO_BIN = NAV_STATUS; - } - @GetMapping({"", "/", "/start"}) public String start(HttpServletRequest req, Model model) throws IOException, InscriptionStartException { - model.addAttribute(MODEL_NAVIGATION, NAV_START); - addBreadcrumbs(model, "Iniciar inscripción", "#"); + addPageMetadata(model, NAV_START, "Iniciar inscripción"); String username = "User-" + getRandomNumber(); String email = "user." + getRandomNumber() + "@example.com"; @@ -155,11 +132,10 @@ public String finish(HttpServletRequest req, @RequestParam(name = "TBK_ORDEN_COMPRA", required = false) String ordenCompra, Model model) throws IOException, InscriptionFinishException { - model.addAttribute(MODEL_NAVIGATION, NAV_FINISH); - addBreadcrumbs(model, "Finalizar inscripción", "#"); + addPageMetadata(model, NAV_FINISH, "Finalizar inscripción"); if (ordenCompra != null) { - model.addAttribute(MODEL_NAVIGATION, NAV_FINISH_RECOVER); + addNavigation(model, NAV_FINISH_RECOVER); model.addAttribute(REQUEST_DATA_JSON, toJson(params)); return VIEW_RECOVER_ERROR; } @@ -171,7 +147,7 @@ public String finish(HttpServletRequest req, model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); if (resp.getResponseCode() != AUTHORIZED) { - model.addAttribute(MODEL_NAVIGATION, NAV_FINISH_REJECTED); + addNavigation(model, NAV_FINISH_REJECTED); model.addAttribute(REQUEST_DATA_JSON, toJson(params)); return VIEW_REJECTED_ERROR; } @@ -196,8 +172,7 @@ public String delete(@RequestParam String username, @RequestParam("tbk_user") String tbkUser, Model model) throws IOException, InscriptionDeleteException { - model.addAttribute(MODEL_NAVIGATION, NAV_DELETE); - addBreadcrumbs(model, "Eliminar inscripción", "#"); + addPageMetadata(model, NAV_DELETE, "Eliminar inscripción"); getInscription().delete(tbkUser, username); return VIEW_DELETE; } @@ -214,8 +189,7 @@ public String authorize( @RequestParam("child_commerce_installments2") int installments2, Model model) throws IOException, TransactionAuthorizeException { - model.addAttribute(MODEL_NAVIGATION, NAV_AUTHORIZE); - addBreadcrumbs(model, "Autorizar transacción", "#"); + addPageMetadata(model, NAV_AUTHORIZE, "Autorizar transacción"); String buyOrder = "buyOrder_" + getRandomNumber(); String childBuyOrder1 = "childBuyOrder1_" + getRandomNumber(); @@ -235,8 +209,7 @@ public String authorize( @GetMapping("/status") public String status(@RequestParam("buy_order") String buyOrder, Model model) throws IOException, TransactionStatusException { - model.addAttribute(MODEL_NAVIGATION, NAV_STATUS); - addBreadcrumbs(model, "Consultar estado", "#"); + addPageMetadata(model, NAV_TWO_STEP, "Consultar estado"); var resp = getTransaction().status(buyOrder); model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); return VIEW_STATUS; @@ -247,10 +220,9 @@ public String refund(@RequestParam("buy_order") String buyOrder, @RequestParam("child_buy_order") String childBuyOrder, @RequestParam("child_commerce_code") String childCommerceCode, @RequestParam double amount, - Model model) + Model model) throws IOException, TransactionRefundException { - model.addAttribute(MODEL_NAVIGATION, NAV_REFUND); - addBreadcrumbs(model, "Reembolso", "#"); + addPageMetadata(model, NAV_TWO_STEP, "Reembolso"); var resp = getTransaction().refund(buyOrder, childCommerceCode, childBuyOrder, amount); model.addAttribute("buy_order", buyOrder); model.addAttribute(MODEL_RESPONSE_JSON, toJson(resp)); @@ -260,8 +232,7 @@ public String refund(@RequestParam("buy_order") String buyOrder, @GetMapping("/info-bin") public String infoBin(@RequestParam("tbk_user") String tbkUser, Model model) throws IOException, QueryBinException { - model.addAttribute(MODEL_NAVIGATION, NAV_INFO_BIN); - addBreadcrumbs(model, "Consulta servicio de bines", "#"); + addPageMetadata(model, NAV_TWO_STEP, "Consulta servicio de bines"); var requestData = Map.of(TBK_USER, tbkUser); var resp = getBinInfo().queryBin(tbkUser); model.addAttribute(REQUEST_DATA, requestData); @@ -277,6 +248,15 @@ public String handleException(Exception e, Model model) { return VIEW_ERROR; } + private void addPageMetadata(Model model, Map navigation, String label) { + addNavigation(model, navigation); + addBreadcrumbs(model, label, "#"); + } + + private void addNavigation(Model model, Map navigation) { + model.addAttribute(MODEL_NAVIGATION, navigation); + } + private void addBreadcrumbs(Model model, String label, String url) { Map breadcrumbs = new LinkedHashMap<>(); breadcrumbs.put("Inicio", "/");