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..2440b51 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/WebpayPlusMallController.java @@ -0,0 +1,207 @@ +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); + + 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; + } + + @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. +

    +
    +
    +
    +
    +