Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package cl.transbank.webpay.example.controllers;

import cl.transbank.common.IntegrationApiKeys;
import cl.transbank.common.IntegrationCommerceCodes;
import cl.transbank.common.IntegrationType;
import cl.transbank.webpay.common.WebpayOptions;
import cl.transbank.model.MallTransactionCreateDetails;
import cl.transbank.webpay.exception.TransactionCommitException;
import cl.transbank.webpay.exception.TransactionCreateException;
import cl.transbank.webpay.exception.TransactionRefundException;
import cl.transbank.webpay.exception.TransactionStatusException;
import cl.transbank.webpay.webpayplus.WebpayPlus;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

@Log4j2
@Controller
@RequestMapping("/webpay-mall")
public class WebpayPlusMallController extends BaseController {
private static final String TEMPLATE_FOLDER = "webpay_plus_mall";
private static final String BASE_URL = "/webpay-mall";
private static final String PRODUCT = "Webpay Mall";
private static final String MODEL_NAVIGATION = "navigation";

private static final String VIEW_CREATE = TEMPLATE_FOLDER + "/create";
private static final String VIEW_COMMIT = TEMPLATE_FOLDER + "/commit";
private static final String VIEW_STATUS = TEMPLATE_FOLDER + "/status";
private static final String VIEW_REFUND = TEMPLATE_FOLDER + "/refund";

private static final Map<String, String> NAV_CREATE;
private static final Map<String, String> NAV_COMMIT;
private static final Map<String, String> NAV_STATUS;
private static final Map<String, String> NAV_REFUND;

static {
NAV_CREATE = new LinkedHashMap<>();
NAV_CREATE.put("request", "Petición");
NAV_CREATE.put("response", "Respuesta");
NAV_CREATE.put("form", "Formulario");

NAV_COMMIT = new LinkedHashMap<>();
NAV_COMMIT.put("data", "Datos recibidos");
NAV_COMMIT.put("request", "Petición");
NAV_COMMIT.put("response", "Respuesta");
NAV_COMMIT.put("operations", "¡Listo!");

NAV_STATUS = new LinkedHashMap<>();
NAV_STATUS.put("request", "Petición");
NAV_STATUS.put("response", "Respuesta");

NAV_REFUND = NAV_STATUS;
}

private final WebpayPlus.MallTransaction tx;

public WebpayPlusMallController() {
this.tx = new WebpayPlus.MallTransaction(
new WebpayOptions(
IntegrationCommerceCodes.WEBPAY_PLUS_MALL,
IntegrationApiKeys.WEBPAY,
IntegrationType.TEST
)
);
}

private void addProductAndBreadcrumbs(Model model, String label, String url) {
var breadcrumbs = new LinkedHashMap<String, String>();
breadcrumbs.put("Inicio", "/");
breadcrumbs.put(PRODUCT, BASE_URL + "/create");
if (label != null)
breadcrumbs.put(label, url);
model.addAttribute("product", PRODUCT);
model.addAttribute("breadcrumbs", breadcrumbs);
}

@GetMapping("/create")
public String create(HttpServletRequest req, Model model) throws TransactionCreateException, IOException {
model.addAttribute(MODEL_NAVIGATION, NAV_CREATE);
addProductAndBreadcrumbs(model, null, null);

String buyOrder = "buyOrder_" + getRandomNumber();
String sessionId = "sessionId_" + getRandomNumber();
String returnUrl = req.getRequestURL().toString().replace("create", "commit");

var childCommerceCode1 = IntegrationCommerceCodes.WEBPAY_PLUS_MALL_CHILD1;
var childBuyOrder1 = "childBuyOrder-" + getRandomNumber();
var amount1 = 1000;

var childCommerceCode2 = IntegrationCommerceCodes.WEBPAY_PLUS_MALL_CHILD2;
var childBuyOrder2 = "childBuyOrder-" + getRandomNumber();
var amount2 = 1000;

var details = MallTransactionCreateDetails.build()
.add(amount1, childCommerceCode1, childBuyOrder1)
.add(amount2, childCommerceCode2, childBuyOrder2);

var request = new LinkedHashMap<String, Object>();
request.put("buyOrder", buyOrder);
request.put("sessionId", sessionId);
request.put("returnUrl", returnUrl);
request.put("details[0]", Map.of(
"amount", amount1,
"commerceCode", childCommerceCode1,
"buyOrder", childBuyOrder1
));
request.put("details[1]", Map.of(
"amount", amount2,
"commerceCode", childCommerceCode2,
"buyOrder", childBuyOrder2
));

model.addAttribute("request", request);

var resp = tx.create(buyOrder, sessionId, returnUrl, details);
model.addAttribute("response_data", resp);
model.addAttribute("response_data_json", toJson(resp));

return VIEW_CREATE;
}

@PostMapping(value = "/commit")
public String commitPost(
HttpServletRequest req,
@RequestParam Map<String, String> params,
Model model) {
model.addAttribute("request_data_json", toJson(params));
model.addAttribute(MODEL_NAVIGATION, NAV_COMMIT);
addProductAndBreadcrumbs(model, "Confirmar transacción", "#");
return VIEW_FORM_ERROR;
}

@GetMapping(value = "/commit")
public String commit(
HttpServletRequest req,
@RequestParam Map<String, String> params,
@RequestParam(name = "token_ws", required = false) String tokenWs,
@RequestParam(name = "TBK_TOKEN", required = false) String tbkToken,
Model model) throws TransactionCommitException, IOException, TransactionStatusException {

String viewTemplate = VIEW_COMMIT;
model.addAttribute("request_data_json", toJson(params));
model.addAttribute(MODEL_NAVIGATION, NAV_COMMIT);
addProductAndBreadcrumbs(model, "Confirmar transacción", "#");

if (tbkToken != null && tokenWs != null) {
viewTemplate = VIEW_FORM_ERROR;
} else if (tbkToken != null) {
viewTemplate = VIEW_ABORTED_ERROR;
var resp = tx.status(tbkToken);
model.addAttribute("response_data_json", toJson(resp));
model.addAttribute("response_data", resp);
} else if (tokenWs != null) {
var resp = tx.commit(tokenWs);
model.addAttribute("token", tokenWs);
model.addAttribute("returnUrl", req.getRequestURL().toString());
model.addAttribute("response_data", resp);
model.addAttribute("response_data_json", toJson(resp));
} else {
viewTemplate = VIEW_TIMEOUT_ERROR;
}
return viewTemplate;
}

@GetMapping("/status")
public String status(@RequestParam("token_ws") String token, Model model)
throws IOException, TransactionStatusException {
model.addAttribute(MODEL_NAVIGATION, NAV_STATUS);
addProductAndBreadcrumbs(model, "Consultar estado de transacción", "#");

final var resp = tx.status(token);
model.addAttribute("response_data_json", toJson(resp));

return VIEW_STATUS;
}

@GetMapping("/refund")
public String refund(@RequestParam("token") String token,
@RequestParam("child_buy_order") String childBuyOrder,
@RequestParam("child_commerce_code") String childCommerceCode,
@RequestParam double amount,
Model model) throws TransactionRefundException, IOException {

model.addAttribute(MODEL_NAVIGATION, NAV_REFUND);
addProductAndBreadcrumbs(model, "Reembolsar", "#");
model.addAttribute("token", token);

final var resp = tx.refund(token, childBuyOrder, childCommerceCode, amount);
model.addAttribute("response_data_json", toJson(resp));

return VIEW_REFUND;
}

@ExceptionHandler(Exception.class)
public String handleException(Exception e, Model model) {
log.error("Error inesperado", e);
model.addAttribute("error", e.getMessage());
return VIEW_ERROR;
}

}
20 changes: 20 additions & 0 deletions src/main/resources/templates/partials/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@
</li>
</ul>
</li>
<li style="margin-bottom: 20px">
<button class="sidebar-collapsible-title">
<span class="general-text">Webpay Plus Mall</span>
<img
th:src="@{/images/t-arrow.svg}"
class="arrow"
alt="t-arrow"
width="24"
height="24"
/>
</button>
<ul class="collapsible-content" id="webpay-plus-mall">
<li class="collapsible-items">
<a th:href="@{/webpay-mall/create}" class="tbk-sidebar-item">
Flujo Completo
</a>
</li>
</ul>
</li>

<li style="margin-bottom: 20px">
<button class="sidebar-collapsible-title">
<span class="general-text">Webpay Plus Diferido</span>
Expand Down
9 changes: 5 additions & 4 deletions src/main/resources/templates/partials/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@

<th:block th:each="entry : ${data}">
<div class="table-column" th:text="${entry.key}"></div>
<div class="table-column" th:if="${entry.key != 'details'}" th:text="${entry.value}"></div>
<div class="table-column" th:if="${entry.key == 'details'}">
<div class="table-column" th:if="${!entry.key.startsWith('details')}" th:text="${entry.value}"></div>
<div class="table-column" th:if="${entry.key.startsWith('details')}">
<th:block th:each="detail : ${entry.value}">
<div class="details-item">
<div >
<div th:each="det : ${detail}">
<div>
<strong th:text="${det.key}"></strong>:
<span th:text="${det.key}"></span>:
<span th:text="${det.value}"></span>
</div>
</div>
</div>
</th:block>
</div>
</th:block>

</div>
</div>
2 changes: 1 addition & 1 deletion src/main/resources/templates/webpay_plus/commit.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h1>Webpay Plus - Confirmar transacción</h1>
<h2 id="data">Paso 1 - Datos recibidos:</h2>
<p class="mb-32">
Después de completar el flujo en el formulario de pago, recibirás un
POST con la siguiente información:
GET con la siguiente información:
</p>

<pre><code class="language-html" th:text="${returnUrl} + '?token_ws=' + ${token}"></code></pre>
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/webpay_plus/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h2 id="request">Paso 1: Petición</h2>
import cl.transbank.webpay.common.WebpayOptions;
import cl.transbank.webpay.webpayplus.WebpayPlus;

tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));
var tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));
var resp = tx.create(buyOrder, sessionId, amount, returnUrl);
</code></pre>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h2 id="request">Paso 1: Petición</h2>
import cl.transbank.webpay.common.WebpayOptions;
import cl.transbank.webpay.webpayplus.WebpayPlus;

tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));
var tx = new WebpayPlus.Transaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));
var resp = tx.create(buyOrder, sessionId, amount, returnUrl);
</code></pre>

Expand Down
89 changes: 89 additions & 0 deletions src/main/resources/templates/webpay_plus_mall/commit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<div th:replace="~{layout :: layout(~{::content})}">
<div th:fragment="content">
<h1>Webpay Plus Mall - Confirmar transacción</h1>
<p class="mb-32">
En este paso es importante confirmar la transacción para notificar a
Transbank que hemos recibido exitosamente los detalles de la
transacción.
<b
>Es importante destacar que si la confirmación no se realiza, la
transacción será caducada.</b
>
</p>

<h2 id="data">Paso 1 - Datos recibidos:</h2>
<p class="mb-32">
Después de completar el flujo en el formulario de pago, recibirás un
GET con la siguiente información:
</p>

<pre><code class="language-html" th:text="${returnUrl} + '?token_ws=' + ${token}"></code></pre>

<h2 class="mt-32" id="request">Paso 2 - Petición:</h2>
<p class="mb-32">
Utilizarás el token recibido para confirmar la transacción mediante
el SDK.
</p>

<pre><code class="language-java">
var tx = new WebpayPlus.MallTransaction(new WebpayOptions(commerceCode, apiKey, IntegrationType.TEST));
var response = tx.commit(tokenWs);
</code></pre>

<h2 class="mt-32" id="response">Paso 3 - Respuesta:</h2>
<p class="mb-32">
Una vez que la transacción ha sido confirmada Transbank
proporcionará la siguiente información. Es fundamental conservar
esta respuesta y verificar que el campo "responseCode" tenga un
valor de cero y que el campo "status" sea "AUTHORIZED".
</p>

<pre><code class="language-json" th:text="${response_data_json}"></code></pre>

<h2 class="mt-32" id="operations">¡Listo!</h2>
<p class="mb-32">
Con la confirmación exitosa, ya puedes mostrar al usuario una página
de éxito de la transacción, proporcionándole la tranquilidad de que
el proceso ha sido completado con éxito.
</p>
<p>
Después de confirmar la transacción, podrás realizar otras
operaciones útiles:
</p>
<ul>
<li>
<span class="fw-700">Reembolsar:</span> Puedes reversar o anular
el pago según ciertas condiciones comerciales.
</li>
<li>
<span class="fw-700">Consultar Estado:</span> Hasta 7 días
después de la transacción, podrás consultar el estado de la
transacción.
</li>
</ul>

<div th:each="detail : ${response_data.details}">
<form th:action="@{/webpay-mall/refund}" method="get">
<div class="tbk-card">
<div class="input-container">
<label for="amount" class="tbk-label">Monto a reembolsar:</label>
<input type="text" name="amount" class="tbk-input-text" th:value="${detail.amount}" />
<input type="hidden" name="child_commerce_code" th:value="${detail.commerceCode}" />
<input type="hidden" name="child_buy_order" th:value="${detail.buyOrder}" />
<input type="hidden" name="token" th:value="${token}" />
</div>
<div class="tbk-card-footer">
<button class="tbk-button primary">REEMBOLSAR</button>
</div>
</div>
</form>
</div>

<div class="mb-32">
<a th:href="@{/webpay-mall/status(token_ws=${token})}"
class="tbk-button primary mb-32">CONSULTAR ESTADO</a>
</div>


</div>
</div>
Loading