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/.gitignore b/.gitignore index a01c531..4a81aba 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ Test.java build gradle .DS_Store +.env 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 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 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..7dccba6 100644 --- a/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java +++ b/src/main/java/cl/transbank/webpay/example/controllers/BaseController.java @@ -1,10 +1,18 @@ 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; +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; 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"; @@ -13,11 +21,42 @@ 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) -> { + 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().setPrettyPrinting().create()).toJson(obj); + return new GsonBuilder() + .setPrettyPrinting() + .registerTypeAdapter(Double.class, DOUBLE_SERIALIZER) + .registerTypeAdapter(double.class, DOUBLE_SERIALIZER) + .create() + .toJson(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; + } + + 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/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 new file mode 100644 index 0000000..65df2d3 --- /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, getDisplayableErrorMessage(e)); + return VIEW_ERROR; + } +} 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..fec997c --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/PromotionsOneclickMallController.java @@ -0,0 +1,330 @@ +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 = 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:}") + 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; + + @GetMapping({"", "/", "/start"}) + public String start(HttpServletRequest req, Model model) + throws IOException, InscriptionStartException { + addPageMetadata(model, NAV_START, "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 { + addPageMetadata(model, NAV_FINISH, "Finalizar inscripción"); + + if (ordenCompra != null) { + addNavigation(model, 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) { + addNavigation(model, 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 { + addPageMetadata(model, NAV_DELETE, "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 { + addPageMetadata(model, NAV_AUTHORIZE, "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 { + addPageMetadata(model, NAV_TWO_STEP, "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 { + 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)); + return VIEW_REFUND; + } + + @GetMapping("/info-bin") + public String infoBin(@RequestParam("tbk_user") String tbkUser, Model model) + throws IOException, QueryBinException { + 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); + 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 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", "/"); + 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; + } +} 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..231827c --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaController.java @@ -0,0 +1,215 @@ +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.security.SecureRandom; + +@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 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_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_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 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(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, + Model model + ) throws TransactionCommitException, IOException { + model.addAttribute(ATTR_NAVIGATION, NAV_COMMIT); + addProductAndBreadcrumbs(model, "Confirmar transacción", BASE_URL + "/commit"); + + Byte deferredPeriodIndex = null; + Boolean gracePeriod = Boolean.FALSE; + + var resp = tx.commit(token, idQueryInstallments, deferredPeriodIndex, gracePeriod); + 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("/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, 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 new file mode 100644 index 0000000..56687e3 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaDiferidaController.java @@ -0,0 +1,244 @@ +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) { + 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() { + 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, 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 new file mode 100644 index 0000000..57f03e2 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallController.java @@ -0,0 +1,282 @@ +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 final MallFullTransaction tx; + + + private static Map createNav(String... keys) { + Map nav = new LinkedHashMap<>(); + for (String key : keys) { + 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; + } + + 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) { + 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 new file mode 100644 index 0000000..578ae49 --- /dev/null +++ b/src/main/java/cl/transbank/webpay/example/controllers/TransaccionCompletaMallDiferidoController.java @@ -0,0 +1,308 @@ +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.example.models.MallDetailSession; +import cl.transbank.webpay.transaccioncompleta.MallFullTransaction; +import cl.transbank.webpay.transaccioncompleta.model.MallTransactionCommitDetails; +import cl.transbank.webpay.transaccioncompleta.responses.MallFullTransactionInstallmentsDetails; +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-diferido") +public class TransaccionCompletaMallDiferidoController extends BaseController { + private static final String TEMPLATE_FOLDER = "transaccion_completa_mall_diferido"; + private static final String BASE_URL = "/transaccion-completa-mall-diferido"; + private static final String PRODUCT = "Webpay Transacción Completa Mall Diferido"; + + 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 ATTR_ID_QUERY_INSTALLMENTS = "id_query_installments"; + + private static final String SESSION_DETAILS = "transaccion_completa_mall_diferido_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_CAPTURE = NAV_STATUS; + private static final Map NAV_REFUND = NAV_STATUS; + private final MallFullTransaction tx; + + private static Map createNav(String... keys) { + Map nav = new LinkedHashMap<>(); + for (String key : keys) { + 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; + } + + + public TransaccionCompletaMallDiferidoController() { + this.tx = new MallFullTransaction( + new WebpayOptions( + IntegrationCommerceCodes.TRANSACCION_COMPLETA_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); + 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_DEFERRED_CHILD1, + IntegrationCommerceCodes.TRANSACCION_COMPLETA_MALL_DEFERRED_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("/capture") + public String capture( + @RequestParam("token") String token, + @RequestParam("child_commerce_code") String commerceCode, + @RequestParam("child_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, commerceCode, 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)); + model.addAttribute("child_buy_order", buyOrder); + model.addAttribute("child_commerce_code", commerceCode); + + 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("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) { + 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; } 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; + } +} 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:} diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 001d2e2..7164273 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 { @@ -1193,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'); + } + }); + }); +}); diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html index c14cf99..0b6702e 100644 --- a/src/main/resources/templates/layout.html +++ b/src/main/resources/templates/layout.html @@ -1,76 +1,77 @@ - + - - Transbank Sdk Java Example - - - + + Transbank Sdk Java Example + + + - - + + - - - + + + + + - - - + - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
+ gtag("config", "G-6CW8MF50ZX"); + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- - - - - +
+ +
+
+ + + + + + 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..3833576 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 + +
diff --git a/src/main/resources/templates/oneclick_mall_deferred/capture.html b/src/main/resources/templates/oneclick_mall_deferred/capture.html index 7e35e19..e5d2de5 100644 --- a/src/main/resources/templates/oneclick_mall_deferred/capture.html +++ b/src/main/resources/templates/oneclick_mall_deferred/capture.html @@ -1,21 +1,21 @@
-
+
+

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/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 +
+ + +
+ +
+
+
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 +
+ + + +
+ +
+
+
+
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 +
+ + +
+ +
+
+
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 +
+
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/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 +
+
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..ca71de9 --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/info_bin.html @@ -0,0 +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. +

+

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

+
+
+
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..5732feb --- /dev/null +++ b/src/main/resources/templates/promotions_oneclick_mall/refund.html @@ -0,0 +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. +

+

Paso 1 - Petición

+

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

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

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

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

+ +
+
+
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 +
+
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 +
+
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 + +
+ + + +
+ +
+
+
+
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..a001d16 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_diferida/index.html @@ -0,0 +1,122 @@ +
+
+

Transacción Completa Diferida - Formulario

+

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

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

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

+ +
+
+
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..f641065 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/commit.html @@ -0,0 +1,84 @@ +
+
+

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
+  )
+);
+
+// idQueryInstallments, deferredPeriodIndex y gracePeriod son opcionales
+var details = MallTransactionCommitDetails.build()
+  .add("597055555574", "O-123", idQueryInstallments, deferredPeriodIndex, gracePeriod)
+  .add("597055555575", "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!

+

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 +
+
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..a4decb1 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/create.html @@ -0,0 +1,71 @@ +
+
+

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 + +
+ + + +
+ +
+
+
+
diff --git a/src/main/resources/templates/transaccion_completa_mall/index.html b/src/main/resources/templates/transaccion_completa_mall/index.html new file mode 100644 index 0000000..34f0058 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall/index.html @@ -0,0 +1,128 @@ +
+
+

Transacción Completa Mall - 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. +

+ +
+
+ +
+
+
+
+ + +
+ +
+
+ + +
+
+ + + +
+
+
+ + +
+
+
+ + +
+
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..2c8dbd8 --- /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. +

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

+ +
+
+
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 +
+
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 +
+
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 + +
+ + + +
+ +
+
+
+
diff --git a/src/main/resources/templates/transaccion_completa_mall_diferido/index.html b/src/main/resources/templates/transaccion_completa_mall_diferido/index.html new file mode 100644 index 0000000..5c7ad81 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/index.html @@ -0,0 +1,128 @@ +
+
+

Transacción Completa Mall Diferido - Formulario

+

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

+ +
+
+ +
+
+
+
+ + +
+ +
+
+ + +
+
+ + + +
+
+
+ + +
+
+
+ + +
+
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..74e5cb2 --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/installments.html @@ -0,0 +1,144 @@ +
+
+

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

+ +
+ + +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + +
+
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..61a611d --- /dev/null +++ b/src/main/resources/templates/transaccion_completa_mall_diferido/refund.html @@ -0,0 +1,54 @@ +
+
+

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 (tienda) 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 + +
+
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. +

+ +
+
+