From 569a9f076bb04316a6f8eed61584ce2138c0d3f8 Mon Sep 17 00:00:00 2001 From: mvarlic Date: Tue, 11 Nov 2025 17:39:46 -0500 Subject: [PATCH 1/6] feat: update table component --- src/main/resources/templates/partials/table.html | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/resources/templates/partials/table.html b/src/main/resources/templates/partials/table.html index 5b2997c..768c5bf 100644 --- a/src/main/resources/templates/partials/table.html +++ b/src/main/resources/templates/partials/table.html @@ -5,19 +5,7 @@
-
-
- -
-
-
- : - -
-
-
-
-
+
From 98e53198a3a0867d4af80566a18eb6c0a2fd2469 Mon Sep 17 00:00:00 2001 From: mvarlic Date: Tue, 11 Nov 2025 17:40:24 -0500 Subject: [PATCH 2/6] feat: add webpay mall component --- .../controllers/WebpayPlusMallController.java | 199 ++++++++++++++++++ .../resources/templates/partials/sidebar.html | 20 ++ .../templates/webpay_plus_mall/commit.html | 89 ++++++++ .../templates/webpay_plus_mall/create.html | 103 +++++++++ .../templates/webpay_plus_mall/refund.html | 64 ++++++ .../templates/webpay_plus_mall/status.html | 28 +++ 6 files changed, 503 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java create mode 100644 src/main/resources/templates/webpay_plus_mall/commit.html create mode 100644 src/main/resources/templates/webpay_plus_mall/create.html create mode 100644 src/main/resources/templates/webpay_plus_mall/refund.html create mode 100644 src/main/resources/templates/webpay_plus_mall/status.html diff --git a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java new file mode 100644 index 0000000..54dadad --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java @@ -0,0 +1,199 @@ +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.model.MallTransactionCreateDetails; +import cl.transbank.webpay.exception.TransactionCommitException; +import cl.transbank.webpay.exception.TransactionCreateException; +import cl.transbank.webpay.exception.TransactionRefundException; +import cl.transbank.webpay.exception.TransactionStatusException; +import cl.transbank.webpay.webpayplus.WebpayPlus; +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; + +@Log4j2 +@Controller +@RequestMapping("/webpay-mall") +public class WebpayPlusMallController extends BaseController { + private static final String TEMPLATE_FOLDER = "webpay_plus_mall"; + private static final String BASE_URL = "/webpay-mall"; + private static final String PRODUCT = "Webpay Mall"; + private static final String MODEL_NAVIGATION = "navigation"; + + private static final String VIEW_CREATE = TEMPLATE_FOLDER + "/create"; + 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_CREATE; + private static final Map NAV_COMMIT; + private static final Map NAV_STATUS; + private static final Map NAV_REFUND; + + static { + NAV_CREATE = new LinkedHashMap<>(); + NAV_CREATE.put("request", "Petición"); + NAV_CREATE.put("response", "Respuesta"); + NAV_CREATE.put("form", "Formulario"); + + NAV_COMMIT = new LinkedHashMap<>(); + NAV_COMMIT.put("data", "Datos recibidos"); + NAV_COMMIT.put("request", "Petición"); + NAV_COMMIT.put("response", "Respuesta"); + NAV_COMMIT.put("operations", "¡Listo!"); + + NAV_STATUS = new LinkedHashMap<>(); + NAV_STATUS.put("request", "Petición"); + NAV_STATUS.put("response", "Respuesta"); + + NAV_REFUND = NAV_STATUS; + } + + private final WebpayPlus.MallTransaction tx; + + public WebpayPlusMallController() { + this.tx = new WebpayPlus.MallTransaction( + new WebpayOptions( + IntegrationCommerceCodes.WEBPAY_PLUS_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 + "/create"); + if (label != null) + breadcrumbs.put(label, url); + model.addAttribute("product", PRODUCT); + model.addAttribute("breadcrumbs", breadcrumbs); + } + + @GetMapping("/create") + public String create(HttpServletRequest req, Model model) throws TransactionCreateException, IOException { + model.addAttribute(MODEL_NAVIGATION, NAV_CREATE); + addProductAndBreadcrumbs(model, null, null); + + String buyOrder = "buyOrder_" + getRandomNumber(); + String sessionId = "sessionId_" + getRandomNumber(); + String returnUrl = req.getRequestURL().toString().replace("create", "commit"); + + var childCommerceCode1 = IntegrationCommerceCodes.WEBPAY_PLUS_MALL_CHILD1; + var childBuyOrder1 = "childBuyOrder-" + getRandomNumber(); + var amount1 = 1000; + + var childCommerceCode2 = IntegrationCommerceCodes.WEBPAY_PLUS_MALL_CHILD2; + var childBuyOrder2 = "childBuyOrder-" + getRandomNumber(); + var amount2 = 1000; + + var details = MallTransactionCreateDetails.build() + .add(amount1, childCommerceCode1, childBuyOrder1) + .add(amount2, childCommerceCode2, childBuyOrder2); + + Map request = new LinkedHashMap<>(Map.ofEntries( + Map.entry("buyOrder", buyOrder), + Map.entry("sessionId", sessionId), + Map.entry("returnUrl", returnUrl), + Map.entry("details", toJson(details.getDetails())) + )); + + model.addAttribute("request", request); + + var resp = tx.create(buyOrder, sessionId, returnUrl, details); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_CREATE; + } + + @PostMapping(value = "/commit") + public String commitPost( + HttpServletRequest req, + @RequestParam Map params, + Model model) { + model.addAttribute("request_data_json", toJson(params)); + model.addAttribute(MODEL_NAVIGATION, NAV_COMMIT); + addProductAndBreadcrumbs(model, "Confirmar transacción", "#"); + return VIEW_FORM_ERROR; + } + + @GetMapping(value = "/commit") + public String commit( + HttpServletRequest req, + @RequestParam Map params, + @RequestParam(name = "token_ws", required = false) String tokenWs, + @RequestParam(name = "TBK_TOKEN", required = false) String tbkToken, + Model model) throws TransactionCommitException, IOException, TransactionStatusException { + + String viewTemplate = VIEW_COMMIT; + model.addAttribute("request_data_json", toJson(params)); + model.addAttribute(MODEL_NAVIGATION, NAV_COMMIT); + addProductAndBreadcrumbs(model, "Confirmar transacción", "#"); + + if (tbkToken != null && tokenWs != null) { + viewTemplate = VIEW_FORM_ERROR; + } else if (tbkToken != null) { + viewTemplate = VIEW_ABORTED_ERROR; + var resp = tx.status(tbkToken); + model.addAttribute("response_data_json", toJson(resp)); + model.addAttribute("response_data", resp); + } else if (tokenWs != null) { + var resp = tx.commit(tokenWs); + model.addAttribute("token", tokenWs); + model.addAttribute("returnUrl", req.getRequestURL().toString()); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + } else { + viewTemplate = VIEW_TIMEOUT_ERROR; + } + return viewTemplate; + } + + @GetMapping("/status") + public String status(@RequestParam("token_ws") String token, Model model) + throws IOException, TransactionStatusException { + model.addAttribute(MODEL_NAVIGATION, NAV_STATUS); + addProductAndBreadcrumbs(model, "Consultar estado de transacción", "#"); + + final var resp = tx.status(token); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_STATUS; + } + + @GetMapping("/refund") + public String refund(@RequestParam("token") String token, + @RequestParam("child_buy_order") String childBuyOrder, + @RequestParam("child_commerce_code") String childCommerceCode, + @RequestParam double amount, + Model model) throws TransactionRefundException, IOException { + + model.addAttribute(MODEL_NAVIGATION, NAV_REFUND); + addProductAndBreadcrumbs(model, "Reembolsar", "#"); + model.addAttribute("token", token); + + final var resp = tx.refund(token, childBuyOrder, childCommerceCode, amount); + 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; + } + +} diff --git a/src/main/resources/templates/partials/sidebar.html b/src/main/resources/templates/partials/sidebar.html index f197ba7..edc9ecc 100644 --- a/src/main/resources/templates/partials/sidebar.html +++ b/src/main/resources/templates/partials/sidebar.html @@ -26,6 +26,26 @@ +
  • + + +
  • +
  • + + + + + + + + + + diff --git a/src/main/resources/templates/webpay_plus_mall/create.html b/src/main/resources/templates/webpay_plus_mall/create.html new file mode 100644 index 0000000..a2978cd --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall/create.html @@ -0,0 +1,103 @@ +
    +
    +

    Webpay Mall - Creación de transacción Mall

    +

    + En esta etapa, se procederá a la creación de una transacción con el + fin de obtener un identificador único. Esto nos permitirá redirigir + al Tarjetahabiente hacia el formulario de pago en el siguiente paso. +

    + +

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

    + +

    Paso 1: Petición

    +
      +
    • + Comienza por importar la librería WebpayPlus en tu proyecto. +
    • +
    • + Luego, crea una transacción utilizando las funciones + proporcionadas mediante el SDK. +
    • +
    + +
    
    +import cl.transbank.common.IntegrationType;
    +import cl.transbank.webpay.common.WebpayOptions;
    +import cl.transbank.webpay.webpayplus.WebpayPlus;
    +
    +var details = MallTransactionCreateDetails.build()
    +                .add(amount1, childCommerceCode1, childBuyOrder1)
    +                .add(amount2, childCommerceCode2, childBuyOrder2);
    +
    +var tx = new WebpayPlus.MallTransaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));
    +
    +var resp = tx.create(buyOrder, sessionId, returnUrl, details);
    +
    + +

    Paso 2: Respuesta

    +

    + Una vez que hayas creado la transacció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 pago al Tarjetahabiente. Este formulario será la + interfaz a través de la cual el usuario realizará su transacción. +

    + +
    
    +
    + + +

    Ejemplo

    +

    + Para llevar a cabo una transacción de compra en nuestro sistema, + primero debemos crear la transacción. Utilizaremos los siguientes + datos para configurar la transacción: +

    + +
    + +

    + Por último, con la respuesta del servicio que confirma la creación + de la transacción, procedemos a crear el formulario de pago. Para + fines de este ejemplo, haremos visible el campo "token_ws", el cual + es esencial para completar el proceso de pago de manera exitosa. +

    + 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 + + +
    +
    +
    +
    diff --git a/src/main/resources/templates/webpay_plus_mall/refund.html b/src/main/resources/templates/webpay_plus_mall/refund.html new file mode 100644 index 0000000..6b7dc01 --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall/refund.html @@ -0,0 +1,64 @@ +
    +
    +

    Webpay Mall - Reembolsar

    +

    + En esta etapa, tienes la opción de solicitar el reembolso del monto + al titular de la tarjeta. Dependiendo del monto y el tiempo + transcurrido desde la transacción, este proceso podría resultar en + una Reversa o Anulación, dependiendo de ciertas condiciones (Reversa + en las primeras 3 horas de la autorización, anulación posterior a + eso), o una Anulación parcial si el monto es menor al total. Las + anulaciones parciales para tarjetas débito y prepago no están + soportadas. +

    + +

    Paso 1 - Petición:

    +

    + Para llevar a cabo el reembolso, necesitas proporcionar el token de la transacción y el monto que deseas reversar. + Si anulas el monto total, podría ser una Reversa o Anulación, dependiendo de ciertas condiciones + (Reversa en las primeras 3 horas de la autorización, anulación posterior a eso), + o una Anulación Parcial si el monto es menor al total. +

    +

    + Algunas consideraciones a tener en cuenta: +

    +
      +
    • + No es posible realizar Anulaciones Parciales en pagos con cuotas. +
    • +
    + +

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

    + +
    
    +var tx = new WebpayPlus.MallTransaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));            
    +final var response = tx.refund(token, amount);
    +        
    + +

    Paso 2 - Respuesta

    +

    + Transbank responderá con el resultado del proceso de reembolso, + indicando si se ha realizado una Reversa, Anulación o Anulación + Parcial. +

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

    Webpay Mall - 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 de este tipo durante + ese período. Sin embargo, una vez pasados los 7 días, ya no podrás + revisar su estado. +

    + +

    Paso 1 - Petición:

    +

    + Para realizar la consulta, necesitas el token de la transacción de la cual deseas obtener el estado. Utiliza este token para realizar una llamada al SDK. +

    +
    
    +var tx = new WebpayPlus.MallTransaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));            
    +final var response = tx.status(token);
    +        
    + +

    Paso 2 - Respuesta

    +

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

    +
    +
    +
    +
    +
    From 8011e4b3d62fca8ff1e66cf61070882d06e4fe86 Mon Sep 17 00:00:00 2001 From: mvarlic Date: Tue, 11 Nov 2025 17:40:57 -0500 Subject: [PATCH 3/6] feat: replace POST to GET --- src/main/resources/templates/webpay_plus/commit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/templates/webpay_plus/commit.html b/src/main/resources/templates/webpay_plus/commit.html index fe931a0..377eccd 100644 --- a/src/main/resources/templates/webpay_plus/commit.html +++ b/src/main/resources/templates/webpay_plus/commit.html @@ -14,7 +14,7 @@

    Webpay Plus - Confirmar transacción

    Paso 1 - Datos recibidos:

    Después de completar el flujo en el formulario de pago, recibirás un - POST con la siguiente información: + GET con la siguiente información:

    From 0257e9b4fff6fb9f243d115cb9ee04e678c55c8c Mon Sep 17 00:00:00 2001 From: mvarlic Date: Tue, 11 Nov 2025 17:42:48 -0500 Subject: [PATCH 4/6] feat: update snippet --- src/main/resources/templates/webpay_plus/create.html | 2 +- src/main/resources/templates/webpay_plus_deferred/create.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/webpay_plus/create.html b/src/main/resources/templates/webpay_plus/create.html index f43ba0b..70c1e79 100644 --- a/src/main/resources/templates/webpay_plus/create.html +++ b/src/main/resources/templates/webpay_plus/create.html @@ -23,7 +23,7 @@

    Paso 1: Petición

    import cl.transbank.webpay.common.WebpayOptions; import cl.transbank.webpay.webpayplus.WebpayPlus; -tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST)); +var tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST)); var resp = tx.create(buyOrder, sessionId, amount, returnUrl); diff --git a/src/main/resources/templates/webpay_plus_deferred/create.html b/src/main/resources/templates/webpay_plus_deferred/create.html index 61f1de8..389b69b 100644 --- a/src/main/resources/templates/webpay_plus_deferred/create.html +++ b/src/main/resources/templates/webpay_plus_deferred/create.html @@ -23,7 +23,7 @@

    Paso 1: Petición

    import cl.transbank.webpay.common.WebpayOptions; import cl.transbank.webpay.webpayplus.WebpayPlus; -tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST)); +var tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST)); var resp = tx.create(buyOrder, sessionId, amount, returnUrl); From f57710a225a58ad16e2023b84c6af8570e5d85f3 Mon Sep 17 00:00:00 2001 From: mvarlic Date: Tue, 11 Nov 2025 18:32:31 -0500 Subject: [PATCH 5/6] feat: update table --- .../controllers/WebpayPlusMallController.java | 15 +++++++++++-- .../resources/templates/partials/table.html | 21 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) 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 54dadad..501cee3 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java @@ -100,12 +100,23 @@ public String create(HttpServletRequest req, Model model) throws TransactionCrea var details = MallTransactionCreateDetails.build() .add(amount1, childCommerceCode1, childBuyOrder1) .add(amount2, childCommerceCode2, childBuyOrder2); - + Map request = new LinkedHashMap<>(Map.ofEntries( Map.entry("buyOrder", buyOrder), Map.entry("sessionId", sessionId), Map.entry("returnUrl", returnUrl), - Map.entry("details", toJson(details.getDetails())) + Map.entry("details", new Object[]{ + Map.of( + "amount", amount1, + "commerceCode", childCommerceCode1, + "buyOrder", childBuyOrder1 + ), + Map.of( + "amount", amount2, + "commerceCode", childCommerceCode2, + "buyOrder", childBuyOrder2 + ) + }) )); model.addAttribute("request", request); diff --git a/src/main/resources/templates/partials/table.html b/src/main/resources/templates/partials/table.html index 768c5bf..2161366 100644 --- a/src/main/resources/templates/partials/table.html +++ b/src/main/resources/templates/partials/table.html @@ -4,8 +4,25 @@
    Valor
    -
    -
    +
    +
    + + +
    +
    + +
    +
    +
    + : + +
    +
    +
    +
    +
    +
    + From db6e24e88bd1d6ad5684027f9fc03106551c8e38 Mon Sep 17 00:00:00 2001 From: mvarlic Date: Wed, 12 Nov 2025 12:40:30 -0500 Subject: [PATCH 6/6] feat: update table component --- .../controllers/WebpayPlusMallController.java | 17 +++++++---------- .../resources/templates/partials/table.html | 14 +++++--------- 2 files changed, 12 insertions(+), 19 deletions(-) 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 501cee3..2440b51 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java @@ -101,22 +101,19 @@ public String create(HttpServletRequest req, Model model) throws TransactionCrea .add(amount1, childCommerceCode1, childBuyOrder1) .add(amount2, childCommerceCode2, childBuyOrder2); - Map request = new LinkedHashMap<>(Map.ofEntries( - Map.entry("buyOrder", buyOrder), - Map.entry("sessionId", sessionId), - Map.entry("returnUrl", returnUrl), - Map.entry("details", new Object[]{ - Map.of( + var request = new LinkedHashMap(); + request.put("buyOrder", buyOrder); + request.put("sessionId", sessionId); + request.put("returnUrl", returnUrl); + request.put("details[0]", Map.of( "amount", amount1, "commerceCode", childCommerceCode1, "buyOrder", childBuyOrder1 - ), - Map.of( + )); + request.put("details[1]", Map.of( "amount", amount2, "commerceCode", childCommerceCode2, "buyOrder", childBuyOrder2 - ) - }) )); model.addAttribute("request", request); diff --git a/src/main/resources/templates/partials/table.html b/src/main/resources/templates/partials/table.html index 2161366..cb5099c 100644 --- a/src/main/resources/templates/partials/table.html +++ b/src/main/resources/templates/partials/table.html @@ -4,18 +4,14 @@
    Valor
    -
    -
    -
    - - -
    -
    +
    +
    +
    -
    +
    - : + :