From 0bf89566f1696a5fc50600d24bfc6db67e61e1ee Mon Sep 17 00:00:00 2001 From: mvarlic Date: Wed, 12 Nov 2025 15:50:09 -0500 Subject: [PATCH 1/2] feat: add webpay mall deferred --- .../WebpayPlusMallDeferredController.java | 240 ++++++++++++++++++ .../resources/templates/partials/sidebar.html | 24 ++ .../webpay_plus_mall_deferred/capture.html | 78 ++++++ .../webpay_plus_mall_deferred/commit.html | 86 +++++++ .../webpay_plus_mall_deferred/create.html | 103 ++++++++ .../webpay_plus_mall_deferred/refund.html | 64 +++++ .../webpay_plus_mall_deferred/status.html | 28 ++ 7 files changed, 623 insertions(+) create mode 100644 src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java create mode 100644 src/main/resources/templates/webpay_plus_mall_deferred/capture.html create mode 100644 src/main/resources/templates/webpay_plus_mall_deferred/commit.html create mode 100644 src/main/resources/templates/webpay_plus_mall_deferred/create.html create mode 100644 src/main/resources/templates/webpay_plus_mall_deferred/refund.html create mode 100644 src/main/resources/templates/webpay_plus_mall_deferred/status.html diff --git a/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java new file mode 100644 index 0000000..bc81e94 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallDeferredController.java @@ -0,0 +1,240 @@ +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.TransactionCaptureException; +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-diferido") +public class WebpayPlusMallDeferredController extends BaseController { + private static final String TEMPLATE_FOLDER = "webpay_plus_mall_deferred"; + private static final String BASE_URL = "/webpay-mall"; + private static final String PRODUCT = "Webpay Mall Diferido"; + 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 String VIEW_CAPTURE = TEMPLATE_FOLDER + "/capture"; + + private static final Map NAV_CREATE; + private static final Map NAV_COMMIT; + private static final Map NAV_STATUS; + private static final Map NAV_REFUND; + private static final Map NAV_CAPTURE; + + 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; + + NAV_CAPTURE = new LinkedHashMap<>(); + NAV_CAPTURE.put("request", "Petición"); + NAV_CAPTURE.put("response", "Respuesta"); + NAV_CAPTURE.put("other", "Otras utilidades"); + } + + private final WebpayPlus.MallTransaction tx; + + public WebpayPlusMallDeferredController() { + this.tx = new WebpayPlus.MallTransaction( + new WebpayOptions( + IntegrationCommerceCodes.WEBPAY_PLUS_MALL_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 + "/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_DEFERRED_CHILD1; + var childBuyOrder1 = "childBuyOrder-" + getRandomNumber(); + var amount1 = 1000; + + var childCommerceCode2 = IntegrationCommerceCodes.WEBPAY_PLUS_MALL_DEFERRED_CHILD2; + var childBuyOrder2 = "childBuyOrder-" + getRandomNumber(); + var amount2 = 1000; + + var details = MallTransactionCreateDetails.build() + .add(amount1, childCommerceCode1, childBuyOrder1) + .add(amount2, childCommerceCode2, childBuyOrder2); + + 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 + )); + request.put("details[1]", Map.of( + "amount", amount2, + "commerceCode", childCommerceCode2, + "buyOrder", childBuyOrder2 + )); + + 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; + } + + @GetMapping("/capture") + public String capture( + @RequestParam("token") String token, + @RequestParam("child_buy_order") String childBuyOrder, + @RequestParam("child_commerce_code") String childCommerceCode, + @RequestParam("authorization_code") String authorizationCode, + @RequestParam double amount, + Model model) + throws IOException, TransactionCaptureException { + + model.addAttribute(MODEL_NAVIGATION, NAV_CAPTURE); + addProductAndBreadcrumbs(model, "Capturar", "#"); + + model.addAttribute("token", token); + model.addAttribute("child_buy_order", childBuyOrder); + model.addAttribute("child_commerce_code", childCommerceCode); + + var resp = tx.capture(childCommerceCode, token, childBuyOrder, authorizationCode, amount); + model.addAttribute("response_data", resp); + model.addAttribute("response_data_json", toJson(resp)); + + return VIEW_CAPTURE; + } + + + @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 edc9ecc..a6257de 100644 --- a/src/main/resources/templates/partials/sidebar.html +++ b/src/main/resources/templates/partials/sidebar.html @@ -68,6 +68,30 @@ + +
  • + + +
  • + diff --git a/src/main/resources/templates/webpay_plus_mall_deferred/capture.html b/src/main/resources/templates/webpay_plus_mall_deferred/capture.html new file mode 100644 index 0000000..ddc5094 --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall_deferred/capture.html @@ -0,0 +1,78 @@ +
    +
    +

    Webpay Mall Diferido - Captura de 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, Orden de compra, Código de autorización y monto a + capturar. Se hace de la siguiente manera. +

    + +
    
    +var tx = new WebpayPlus.MallTransaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));    
    +var resp = tx.capture(childCommerceCode, token, childBuyOrder, authorizationCode, amount);
    +        
    + +

    Paso 2: Respuesta

    +

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

    + +
    + +

    ¡Transacción Capturada!

    + +

    + 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. +

    + +

    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.
    • +
    • Consultar Estado: Hasta 7 días después de la transacción, puedes consultar su estado para obtener más detalles.
    • +
    + +
    +
    +
    + + + + + +
    +
    + + CONSULTAR ESTADO +
    +
    +
    +
    +
    diff --git a/src/main/resources/templates/webpay_plus_mall_deferred/commit.html b/src/main/resources/templates/webpay_plus_mall_deferred/commit.html new file mode 100644 index 0000000..aa023c8 --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall_deferred/commit.html @@ -0,0 +1,86 @@ +
    +
    +

    Webpay Mall Diferido - Confirmar transacción

    +

    + En este paso es importante confirmar la transacción para notificar a + Transbank que hemos recibido exitosamente los detalles de la + transacción. + Es importante destacar que si la confirmación no se realiza, la + transacción será caducada. +

    + +

    Paso 1 - Datos recibidos:

    +

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

    + +
    + +

    Paso 2 - Petición:

    +

    + Utilizarás el token recibido para confirmar la transacción mediante + el SDK. +

    + +
    
    +var tx = new WebpayPlus.MallTransaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));            
    +var response = tx.commit(tokenWs);
    +        
    + +

    Paso 3 - 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 "responseCode" tenga un + valor de cero y que el campo "status" sea "AUTHORIZED". +

    + +
    + +

    ¡Transaccion 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 la transacción, podrás consultar el estado de la + transacción. +
    • +
    + +
    +
    +
    +
    + + + + + + +
    + +
    +
    +
    + + + + +
    +
    diff --git a/src/main/resources/templates/webpay_plus_mall_deferred/create.html b/src/main/resources/templates/webpay_plus_mall_deferred/create.html new file mode 100644 index 0000000..39414a7 --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall_deferred/create.html @@ -0,0 +1,103 @@ +
    +
    +

    Webpay Mall Diferido - 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_deferred/refund.html b/src/main/resources/templates/webpay_plus_mall_deferred/refund.html new file mode 100644 index 0000000..ba0e2ce --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall_deferred/refund.html @@ -0,0 +1,64 @@ +
    +
    +

    Webpay Mall Diferido - 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));            
    +var resp = tx.refund(token, childBuyOrder, childCommerceCode, 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_deferred/status.html b/src/main/resources/templates/webpay_plus_mall_deferred/status.html new file mode 100644 index 0000000..13b072f --- /dev/null +++ b/src/main/resources/templates/webpay_plus_mall_deferred/status.html @@ -0,0 +1,28 @@ +
    +
    +

    Webpay Mall Diferido - 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 d8bd10d9f87fd251c4eddf114c32686124b8ec55 Mon Sep 17 00:00:00 2001 From: mvarlic Date: Wed, 12 Nov 2025 15:50:31 -0500 Subject: [PATCH 2/2] feat: update snippet --- src/main/resources/templates/webpay_plus_mall/refund.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/templates/webpay_plus_mall/refund.html b/src/main/resources/templates/webpay_plus_mall/refund.html index 6b7dc01..ae65198 100644 --- a/src/main/resources/templates/webpay_plus_mall/refund.html +++ b/src/main/resources/templates/webpay_plus_mall/refund.html @@ -42,7 +42,7 @@

    Paso 1 - Petición:

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

    Paso 2 - Respuesta