Como crear un nuevo Método de Pago para Liferay Commerce - (Parte 2)
... Terminada la parte 1, podemos continuar con la integración del módulo con la pasarela de pago...
1. En el paquete “com.liferay.commerce.payment.method.redsys” crea una clase llamada “RedsysCommercePaymentMethodRequest” que extenda “CommercePaymentRequest”. Dentro de la clase define un nuevo constructor como abajo (aquí añadimos en ”HttpServletRequest” ya que vamos a necesitar tener acceso a este servlet a futuro):
public RedsysCommercePaymentMethodRequest(BigDecimal amount, String cancelUrl, long commerceOrderId, Locale locale, HttpServletRequest httpServletRequest, String returnUrl, String transactionId) { super(amount, cancelUrl, commerceOrderId, locale, returnUrl, transactionId); _httpServletRequest = httpServletRequest; } public HttpServletRequest getHttpServletRequest(){ return _httpServletRequest; } private final HttpServletRequest _httpServletRequest;
2. En el paquete “com.liferay.commerce.payment.method.redsys” crea una clase llamada “RedsysCommercePaymentRequestProvider” que implemente “CommercePaymentRequestProvider” e implemente el método “getCommercePaymentRequest”.
3. Anota esta clase con @Component y establece 3 propiedades. Debería quedar así:
@Component( immediate = true, property = "commerce.payment.engine.method.key=" + RedsysCommercePaymentMethod.KEY, service = CommercePaymentRequestProvider.class )
4. Ahora, al final de la clase inyecta el servicio de “CommerceOrderLocalService” a través de la anotación @Reference.
5. Dentro del método “getCommercePaymentRequest”, recuperamos el “CommerceOrder” y retornamos una instancia de “RedsysCommercePaymentMethodRequest”. Al final, tu clase debería quedarse así:
@Component( immediate = true, property = "commerce.payment.engine.method.key=" + RedsysCommercePaymentMethod.KEY, service = CommercePaymentRequestProvider.class ) public class RedsysCommercePaymentRequestProvider implements CommercePaymentRequestProvider { @Override public CommercePaymentRequest getCommercePaymentRequest(String cancelUrl, long commerceOrderId, HttpServletRequest httpServletRequest, Locale locale, String returnUrl, String transactionId) throws PortalException { CommerceOrder commerceOrder = _commerceOrderLocalService.getCommerceOrder(commerceOrderId); return new RedsysCommercePaymentMethodRequest( commerceOrder.getTotal(), cancelUrl, commerceOrderId, locale, httpServletRequest, returnUrl, transactionId); } @Reference private CommerceOrderLocalService _commerceOrderLocalService;
6. En el paquete “com.liferay.commerce.payment.method.redsys.connector”, crea una nueva clase llamada “RedsysConfigClient” (la utilizaremos como un bean de los datos de conexión), copia y pega el siguiente código dentro de la clase:
public RedsysConfigClient(String dsSignatureVersion, String signatureSecret, RedsysEnvironment environment, String merchantCode) throws Exception { if (environment == null) { throw new Exception("Invalid Environment specified"); } if (Validator.isBlank(dsSignatureVersion)) { throw new Exception("Invalid DS Signature Version specified"); } if (Validator.isBlank(signatureSecret)) { throw new Exception("Invalid Signature Secret specified"); } if (Validator.isBlank(merchantCode)) { throw new Exception("Invalid Merchant CODE specified"); } _signatureSecret = signatureSecret; _dsSignatureVersion = dsSignatureVersion; _environment = environment; _merchantCode = merchantCode; } public URI getEnvironmentUrl() { return URI.create(_environment.getUrl()); } public String getSignatureSecret() { return _signatureSecret; } public String getDsSignatureVersion() { return _dsSignatureVersion; } public String getMerchantCode() { return _merchantCode; } private final RedsysEnvironment _environment; private final String _signatureSecret; private final String _dsSignatureVersion; private final String _merchantCode;
Entonces, como nuestra conexión con el gateway de pago será por redirección, debemos crear nuestro servlet, entonces :
7. Crea un nuevo paquete:
8. Crea una clase llamada ”RedsysCommercePaymentMethodServlet” que extienda “HttpServlet“, y la anote como un @Component, configura el contexto del Servlet e implementa el método “doGet”. Deberá quedar así:
@Component(immediate = true, property = { "osgi.http.whiteboard.context.path=/" + RedsysCommercePaymentMethodConstants.SERVLET_PATH, "osgi.http.whiteboard.servlet.name=com.liferay.commerce.payment.method.redsys.servlet.RedsysCommercePaymentMethodServlet", "osgi.http.whiteboard.servlet.pattern=/" + RedsysCommercePaymentMethodConstants.SERVLET_PATH + "/*" }, service = Servlet.class) public class RedsysCommercePaymentMethodServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } }
9. Crea otro nuevo paquete:
10. Dentro de este paquete, crea una clase llamada “RedsysCommercePaymentMethodVerifierFilter” que extenda el “AuthVerifierFilter“, la anote como un @Component y la configure para poder acceder al Servlet desde el navegador con un usuario autenticado a través de su sesión. Deberá quedar así:
@Component( immediate = true, property = { "filter.init.auth.verifier.PortalSessionAuthVerifier.urls.includes=/" + RedsysCommercePaymentMethodConstants.SERVLET_PATH + "/*", "osgi.http.whiteboard.filter.name=com.liferay.commerce.payment.method.redsys.servlet.filter.RedsysCommercePaymentMethodVerifierFilter", "osgi.http.whiteboard.servlet.pattern=/" + RedsysCommercePaymentMethodConstants.SERVLET_PATH + "/*" }, service = Filter.class ) public class RedsysCommercePaymentMethodVerifierFilter extends AuthVerifierFilter { }
11. Ahora debemos descargar las librerías de Redsys, las puedes descargar en el portal de “Redsys” en la opción de “GENERACIÓN HMAC_SHA256” o en mi repo Git (sé que es una mala práctica las subidas de .jar al Git, pero aquí por cuestión de facilidades en el acceso lo he puesto en el proyecto, luego tú lo puedes poner en un repositorio de dependencias de tu preferencia y añadirlo a tu proyecto de una manera más elegante). Entonces seguimos:
12. Crea una carpeta “lib” en la raíz de tu módulo, copia y pega los ficheros “.jar” dentro de ella.
13. Abre el fichero “build.gradle” y añade dichos ficheros como dependencia, más la api del “currency”:
compileInclude fileTree(dir: 'libs', include: ['*.jar']) compileOnly group: 'com.liferay.commerce', name: 'com.liferay.commerce.currency.api', version: '7.0.2'
14. Ahora volvemos a la primera clase que hemos creado en el inicio de este proyecto, la “RedsysCommercePaymentMethod”. Dentro de esta clase, vamos a terminar de implementar nuestros métodos existentes. Empezando por el “getPaymenteType”, retornaremos el tipo de pago que estamos implementando, como ya hemos hablado, implementaremos la integración con Redsys por redirección, este método deberá quedar así:
@Override public int getPaymentType() { return CommercePaymentConstants.COMMERCE_PAYMENT_METHOD_TYPE_ONLINE_REDIRECT; }
15. En el método “getServletPath”, retornaremos lo que hemos definido anteriormente.
@Override public String getServletPath() { return RedsysCommercePaymentMethodConstants.SERVLET_PATH; }
16. Cambia el “return” de los 3 métodos “boolean” restantes a “true” para habilitar las 3 opciones que utilizaremos en seguida.
@Override public boolean isCancelEnabled() { return true; } @Override public boolean isCompleteEnabled() { return true; } @Override public boolean isProcessPaymentEnabled() { return true; }
17. Al final de la clase, inyecta los siguientes servicios con la anotación @Reference.
@Reference private CommerceOrderService _commerceOrderService; @Reference private ConfigurationProvider _configurationProvider; @Reference private Http _http; @Reference private Portal _portal;
18. Crea los 2 métodos de abajo dentro de la clase, uno para la recuperación de la configuración del método de pago por canal que hemos guardado y el otro para montar nuestro enlace del servlet para la llamada.
private RedsysPaymentMethodCardGroupServiceConfiguration _getConfiguration(long groupId) throws ConfigurationException { return _configurationProvider.getConfiguration( RedsysPaymentMethodCardGroupServiceConfiguration.class, new GroupServiceSettingsLocator( groupId, RedsysCommercePaymentMethodConstants.SERVICE_NAME)); } private String _getServletUrl( RedsysCommercePaymentMethodRequest redsysCommercePaymentMethodRequest) { return StringBundler.concat( _portal.getPortalURL( redsysCommercePaymentMethodRequest.getHttpServletRequest()), _portal.getPathModule(), StringPool.SLASH, RedsysCommercePaymentMethodConstants.SERVLET_PATH); }
19. Ahora dentro del método “processPayment”, implementaremos la siguiente lógica:
RedsysCommercePaymentMethodRequest redsysCommercePaymentMethodRequest = (RedsysCommercePaymentMethodRequest) commercePaymentRequest; CommerceOrder commerceOrder = _commerceOrderService.getCommerceOrder( redsysCommercePaymentMethodRequest.getCommerceOrderId()); String transactionUuid = PortalUUIDUtil.generate(); String transactionId = StringUtil.replace(transactionUuid, CharPool.DASH, StringPool.BLANK); CommerceCurrency commerceCurrency = commerceOrder.getCommerceCurrency(); Currency currencyIso4217 = Currency.getInstance(commerceCurrency.getCode()); URL returnUrl = new URL(redsysCommercePaymentMethodRequest.getReturnUrl()); String urlReturnDecoded = URLCodec.decodeURL(returnUrl.toString()); String newRedirectOK= StringUtil.extractLast(urlReturnDecoded, RedsysCommercePaymentMethodConstants.REDIRECT_PARAM); String servlet = StringUtil.extractFirst(urlReturnDecoded, RedsysCommercePaymentMethodConstants.REDIRECT_PARAM).concat(StringPool.AMPERSAND.concat(RedsysCommercePaymentMethodConstants.REDIRECT_PARAM)); String urlCodedOK = Base64.getEncoder().encodeToString(newRedirectOK.getBytes()); URL urlOk = new URL(servlet.concat(urlCodedOK)); URL cancelUrl = new URL(redsysCommercePaymentMethodRequest.getCancelUrl()); String urlCancelDecoded = URLCodec.decodeURL(cancelUrl.toString()); String newRedirectKO= StringUtil.extractLast(urlCancelDecoded.toString(), RedsysCommercePaymentMethodConstants.REDIRECT_PARAM); String urlCodedKO = Base64.getEncoder().encodeToString(newRedirectKO.getBytes()); URL urlNok = new URL(servlet.concat(urlCodedKO)); RedsysPaymentMethodCardGroupServiceConfiguration paymentMethodCardGroupServiceConfiguration = _getConfiguration(commerceOrder.getGroupId()); String environment = StringUtil.toUpperCase(paymentMethodCardGroupServiceConfiguration.mode()); String clientSecret = paymentMethodCardGroupServiceConfiguration.clientSecret(); String merchantCode = paymentMethodCardGroupServiceConfiguration.merchantCode(); String dsSignatureV = paymentMethodCardGroupServiceConfiguration.dsSignatureVersion(); String typeTransaction = paymentMethodCardGroupServiceConfiguration.typeTransaction(); String terminal = paymentMethodCardGroupServiceConfiguration.terminal(); RedsysTypeOfTransaction transactionValue = RedsysTypeOfTransaction.valueOf(typeTransaction.toUpperCase()); RedsysConfigClient redsysConfigClient = new RedsysConfigClient(dsSignatureV, clientSecret, RedsysEnvironment.valueOf(environment), merchantCode); ApiMacSha256 apiMacSha256 = new ApiMacSha256(); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_AMOUNT, commerceOrder.getTotal().setScale(2, RoundingMode.DOWN).toString().replace(".", "")); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_ORDER, Long.toString(commerceOrder.getCommerceOrderId())); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_MERCHANTCODE,redsysConfigClient.getMerchantCode()); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_CURRENCY, Integer.toString(currencyIso4217.getNumericCode())); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_TRANSACTIONTYPE, transactionValue.getTypeOfTransaction()); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_TERMINAL, terminal); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_MERCHANTURL, URLCodec.encodeURL(_getServletUrl(redsysCommercePaymentMethodRequest))); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_URLOK, URLCodec.encodeURL(urlOk.toString())); apiMacSha256.setParameter(RedsysCommercePaymentMethodConstants.DS_MERCHANT_URLKO, URLCodec.encodeURL(urlNok.toString())); String claveModuloAdmin = redsysConfigClient.getSignatureSecret(); String signature = apiMacSha256.createMerchantSignature(claveModuloAdmin); String params = apiMacSha256.createMerchantParameters(); String dsSignatureVersion = redsysConfigClient.getDsSignatureVersion(); List<String> resultMessage = new ArrayList<String>(); String url = StringBundler.concat( _getServletUrl(redsysCommercePaymentMethodRequest), StringPool.QUESTION, RedsysCommercePaymentMethodConstants.REDIRECT_URL,StringPool.EQUAL,URLCodec.encodeURL(redsysConfigClient.getEnvironmentUrl().toString()), StringPool.AMPERSAND,RedsysCommercePaymentMethodConstants.PARAMS,StringPool.EQUAL, URLEncoder.encode(params, StringPool.UTF8), StringPool.AMPERSAND,RedsysCommercePaymentMethodConstants.SIGNATURE, StringPool.EQUAL,URLEncoder.encode(signature, StringPool.UTF8), StringPool.AMPERSAND,RedsysCommercePaymentMethodConstants.DS_SIGNATURE_VERSION_PARAM, StringPool.EQUAL,dsSignatureVersion ); CommercePaymentResult commercePaymentResult = new CommercePaymentResult( transactionId, commerceOrder.getCommerceOrderId(), CommerceOrderConstants.ORDER_STATUS_PENDING, true, url, null, resultMessage, true); return commercePaymentResult;
Creamos nuestro “RedsysCommercePaymentMethodRequest” con el valor de “CommercePaymentRequest”, y luego recuperamos algunos valores necesarios para la implementación.
Recuperamos la configuración del método de pago por canal, necesario para la conexión con la pasarela de pago.
Siguiendo la documentación de Redsys, creamos un objeto del tipo “ApiMacSha256”, lo seteamos con los valores necesarios, hacemos las llamadas de dos métodos para encriptar los datos.
Creamos un enlace con nuestro servlet, concatenando 4 valores que necesitaremos en el “form” que crearemos a futuro (redsys-form.jsp).
Por fin, creamos un objeto “CommercePaymentResult” y seteamos en el retorno de nuestro método de “processPayment”. Cómo estamos en el proceso del pago, seteamos también el status del pedido como “ORDER_STATUS_PENDING”.
20. En el paquete “com.liferay.commerce.payment.method.redsys.constants”, crea una nueva clase enum llamada “RedsysMessages” para tratar los mensajes que vendrán de Redsys, copia y pega el código de abajo dentro del enum:
M0000("authorized-transaction-payments-and-pre-authorizations"), M0900("authorized-transaction-refund-and-confirmations"), M0400("transaction-authorized-cancellations"), M0101("expired-card"), M0102("card-temporary-exception-suspicion-fraud"), M0106("pin-attempts-exceeded"), M0125("card-not-effective"), M0129("incorrect-security-code"), M0180("non-service-card"), M0184("error-authentication-holder"), M0190("refusal-without-specifying-reason"), M0191("wrong-expiration-date"), M0202("card-temporary-exception-suspicion-fraud-card-withdrawal"), M0904("trade-not-registered-fuc"), M0909("system-error"), M0913("repeat-order"), M0944("incorrect-session"), M0950("refund-operation-not-allowed"), M9912("issuer-not-available"), M0912("issuer-not-available"), M9064("incorrect-number-card-positions"), M9078("type-operation-not-allowed-for-card"), M9093("card-does-not-exist"), M9094("rejection-international-servers"), M9104("trade-with-secure-and-owner-without-secure-purchase-key"), M9218("trade-does-not-allow-op-safe-for-entry"), M9253("card-does-not-satisfy-check-digit"), M9256("merchant-cannot-perform-pre-authorizations"), M9257("card-does-not-allow-pre-authorization-operations"), M9261("operation-stopped-exceeding-control-sis"), M9913("error-confirmation-merchant-sends-only-applicable-soap-synchronization"), M9914("confirmation-ko-merchant-only-applicable-soap-synchronization"), M9915("users-request-payment-has-been-canceled"), M9928("authorization-cancellation-perform-by-sis"), M9929("cancellation-deferred-authorization-by-merchant"), M9997("another-transaction-is-being-processed-with-same-card"), M9998("operation-in-process-requesting-card-data"), M9999("operation-has_been-redirected-to-issuer-to-authenticate"); public String getMessage() { return _message; } private RedsysMessages(String message) { _message = message; } private final String _message;
21. Ahora volvemos a la primera clase la “RedsysCommercePaymentMethod”. Dentro de esta clase copia y pega el código de abajo, para crear el método para tratar los mensajes recibidos por Redsys:
private List<String> getMessagesFromResponse(RedsysCommercePaymentMethodRequest redsysCommercePaymentMethodRequest){ HttpServletRequest httpServletRequest = redsysCommercePaymentMethodRequest.getHttpServletRequest(); String codResponse = (String) httpServletRequest.getAttribute(RedsysCommercePaymentMethodConstants.COD_RESPONSE_REDSYS); RedsysMessages enumMessage = RedsysMessages.valueOf(("M").concat(codResponse)); String messageLanguage = enumMessage.getMessage(); ResourceBundle resourceBundle = ResourceBundleUtil.getBundle("content.Language", PortalUtil.getLocale(httpServletRequest), getClass()); String messageLocalizable = LanguageUtil.get(resourceBundle, messageLanguage); List<String> messages = new ArrayList<String>(); messages.add(messageLocalizable); return messages; }
22. Ahora dentro del método “cancelPayment”, implementaremos la siguiente lógica
List<String> messages = getMessagesFromResponse(redsysCommercePaymentMethodRequest); return new CommercePaymentResult( redsysCommercePaymentMethodRequest.getTransactionId(), redsysCommercePaymentMethodRequest.getCommerceOrderId(), CommerceOrderPaymentConstants.STATUS_CANCELLED, false, null, null,messages, true);
23. Ahora dentro del método “completePayment”, implementaremos la siguiente lógica
RedsysCommercePaymentMethodRequest redsysCommercePaymentMethodRequest = (RedsysCommercePaymentMethodRequest)commercePaymentRequest; List<String> messages = getMessagesFromResponse(redsysCommercePaymentMethodRequest); return new CommercePaymentResult(redsysCommercePaymentMethodRequest.getTransactionId(), redsysCommercePaymentMethodRequest.getCommerceOrderId(), CommerceOrderConstants.PAYMENT_STATUS_PAID, false, null, null, messages, true);
24. Dentro de “META-INF/resources” crea una nueva carpeta “redsys_form” y dentro un fichero JSP con nombre “redsys-form.jsp”, copia y pega este código (aquí montamos el form que enviaremos a redsys)
<%@ include file="./../init.jsp" %> <%@ page import="java.net.URLDecoder" %> <% String redirectUrlAttribute = (String)request.getAttribute(RedsysCommercePaymentMethodConstants.REDIRECT_URL); String redirectUrl = URLCodec.decodeURL(redirectUrlAttribute); String paramsAtrribute = (String)request.getAttribute(RedsysCommercePaymentMethodConstants.PARAMS); String params = URLDecoder.decode(paramsAtrribute, StringPool.UTF8); String signatureAttribute = (String)request.getAttribute(RedsysCommercePaymentMethodConstants.SIGNATURE); String signature = URLDecoder.decode(signatureAttribute, StringPool.UTF8); String dsSignatureVersion = (String)request.getAttribute(RedsysCommercePaymentMethodConstants.DS_SIGNATURE_VERSION_PARAM); %> <form action="<%= redirectUrl %>" class="hide" id="formRedsys" method="post" name="formRedsys"> <input name="<%= RedsysCommercePaymentMethodConstants.DS_SIGNATURE_VERSION %>" type="hidden" value="<%= dsSignatureVersion %>" /> <input name="<%= RedsysCommercePaymentMethodConstants.DS_MERCHANT_PARAMETERS %>" type="hidden" value="<%= params %>" /> <input name="<%= RedsysCommercePaymentMethodConstants.DS_SIGNATURE %>" type="hidden" value="<%= signature %>" /> <button id="btnContinue">Continue</button> </form> <script> (function () { if(document.readyState == 'complete') { document.querySelector('form').submit() } else { window.addEventListener('load', function(){ document.querySelector('form').submit() }) } })() </script>
25. Abre la clase “RedsysCommercePaymentMethodServlet” y al final de la clase copia y pega este código a seguir, para crear logs e inyectar algunos servicios que iremos a utilizar
private static final Log _log = LogFactoryUtil.getLog(RedsysCommercePaymentMethodServlet.class); @Reference private CommerceOrderService _commerceOrderService; @Reference private ConfigurationProvider _configurationProvider; @Reference private CommercePaymentEngine _commercePaymentEngine; @Reference private CommerceSubscriptionEngine _commerceSubscriptionEngine; @Reference private Portal _portal; @Reference(target = "(osgi.web.symbolicname=com.liferay.commerce.payment.method.redsys)") private ServletContext _servletContext;
26. Ahora crea el método para recuperar las configuraciones
private RedsysPaymentMethodCardGroupServiceConfiguration _getConfiguration(long groupId) throws ConfigurationException { return _configurationProvider.getConfiguration(RedsysPaymentMethodCardGroupServiceConfiguration.class, new GroupServiceSettingsLocator(groupId, RedsysCommercePaymentMethodConstants.SERVICE_NAME)); }
27. Para finalizar, implementamos la lógica dentro del método “doGet”, que deberá quedar así
@Override protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { try { HttpSession httpSession = httpServletRequest.getSession(); if (PortalSessionThreadLocal.getHttpSession() == null) { PortalSessionThreadLocal.setHttpSession(httpSession); } PermissionChecker permissionChecker = PermissionCheckerFactoryUtil.create(_portal.getUser(httpServletRequest)); PermissionThreadLocal.setPermissionChecker(permissionChecker); String redirectCod = ParamUtil.getString(httpServletRequest, "redirect"); if (Validator.isNotNull(redirectCod)) { long groupId = ParamUtil.getLong(httpServletRequest, "groupId"); String uuid = ParamUtil.getString(httpServletRequest, "uuid"); CommerceOrder commerceOrder = _commerceOrderService.getCommerceOrderByUuidAndGroupId(uuid, groupId); byte[] bytesDecoded = Base64.getDecoder().decode(redirectCod); String redirect = new String(bytesDecoded); ApiMacSha256 apiMacSha256 = new ApiMacSha256(); String params = ParamUtil.getString(httpServletRequest, RedsysCommercePaymentMethodConstants.DS_MERCHANT_PARAMETERS); String signature = ParamUtil.getString(httpServletRequest, RedsysCommercePaymentMethodConstants.DS_SIGNATURE); apiMacSha256.decodeMerchantParameters(params); String codResponseRedsys = apiMacSha256.getParameter("Ds_Response"); httpServletRequest.setAttribute(RedsysCommercePaymentMethodConstants.COD_RESPONSE_REDSYS, codResponseRedsys); if (!redirect.contains("cancel")) { RedsysPaymentMethodCardGroupServiceConfiguration paymentMethodCardGroupServiceConfiguration = _getConfiguration(commerceOrder.getGroupId()); String clientSecret = paymentMethodCardGroupServiceConfiguration.clientSecret(); String signatureCalculed = apiMacSha256.createMerchantSignatureNotif(clientSecret, params); if (signatureCalculed.equals(signature)) { String dsOrder = apiMacSha256.getParameter("Ds_Order"); String dsAmount = apiMacSha256.getParameter("Ds_Amount"); String dsMerchantCode = apiMacSha256.getParameter("Ds_MerchantCode"); String dsTerminal = apiMacSha256.getParameter("Ds_Terminal"); String dsTransactionType = apiMacSha256.getParameter("Ds_TransactionType"); String order = Long.toString(commerceOrder.getCommerceOrderId()); String amount = commerceOrder.getTotal().setScale(2, RoundingMode.DOWN)).toString().replace(".",""); String merchantCode = paymentMethodCardGroupServiceConfiguration.merchantCode(); String terminal = paymentMethodCardGroupServiceConfiguration.terminal(); String typeTransaction = paymentMethodCardGroupServiceConfiguration.typeTransaction(); RedsysTypeOfTransaction transactionValue = RedsysTypeOfTransaction.valueOf(typeTransaction.toUpperCase()); if(dsOrder.equalsIgnoreCase(order) && dsAmount.equals(amount) && dsMerchantCode.equalsIgnoreCase(merchantCode)&& dsTerminal.equalsIgnoreCase("0".concat(terminal)) && dsTransactionType.equalsIgnoreCase(transactionValue.getTypeOfTransaction())) { _commercePaymentEngine.completePayment(commerceOrder.getCommerceOrderId(), null,httpServletRequest); }else { _log.error(RedsysCommercePaymentMethodConstants.ERROR_PARAMETERS_COMPARE); throw new Exception(RedsysCommercePaymentMethodConstants.ERROR_PARAMETERS_COMPARE); } }else { _log.error(RedsysCommercePaymentMethodConstants.ERROR_SIGNATURE); throw new Exception(RedsysCommercePaymentMethodConstants.ERROR_SIGNATURE); } } else { _commercePaymentEngine.cancelPayment(commerceOrder.getCommerceOrderId(), null, httpServletRequest); } httpServletResponse.sendRedirect(redirect); } else { RequestDispatcher requestDispatcher = _servletContext.getRequestDispatcher("/redsys_form/redsys-form.jsp"); requestDispatcher.forward(httpServletRequest, httpServletResponse); } }catch (Exception e) { _portal.sendError(e, httpServletRequest, httpServletResponse); }
Cuando no venga el parámetro “redirect” en el httpServletRequest, significa que es una llamada del método de “processPayment” y que debemos entonces hacer un request dispatcher con el servlet lanzado al form creado en “redsys-form.jsp”. Entonces se redireccionará a la plataforma de pago de Redsys.
Cuando venga el parámetro “redirect” en el httpServletRequest, significa que es una respuesta de Redsys, validamos si existe el parámetro “cancel” en la cadena del redirect:
Si contiene el parámetro, llamamos el método “cancelPayment”.
Si no contiene el parámetro, validamos los dados de retorno de la transacción de acuerdo con lo recomendado en la documentación:
Si todo está Ok, llamamos el método “completePayment”.
Si no pasa por la validación, lanzamos un error y el estado del pedido seguirá como “PENDING”.
28. Por terminar, añada las traducciones de las mensajes recibidas por Redsys en el fichero “Language.properties”
##################----- Messages from Redsys -----##################### error-authentication-holder=Error in the authentication of the holder authorized-transaction-payments-and-pre-authorizations=Authorized transaction for payments and pre-authorizations authorized-transaction-refund-and-confirmations=Authorized transaction for refund and confirmations transaction-authorized-cancellations=Transaction authorized for cancellations expired-card=Expired card card-temporary-exception-suspicion-fraud=Card in temporary exception or under suspicion of fraud pin-attempts-exceeded=PIN attempts exceeded card-not-effective=Card not effective incorrect-security-code=Incorrect security code (CVV2 / CVC2) non-service-card=Non-service card refusal-without-specifying-reason=Refusal of the issuer without specifying reason wrong-expiration-date=Wrong expiration date card-temporary-exception-suspicion-fraud-card-withdrawal=Card in temporary exception or under suspicion of fraud with card withdrawal trade-not-registered-fuc=Trade not registered in FUC system-error=System error repeat-order=Repeat order incorrect-session=Incorrect Session refund-operation-not-allowed=Refund operation not allowed issuer-not-available=Issuer not available incorrect-number-card-positions=Incorrect number of card positions type-operation-not-allowed-for-card=Type of operation not allowed for that card card-does-not-exist=Card does not exist rejection-international-servers=Rejection international servers trade-with-secure-and-owner-without-secure-purchase-key=Trade with "secure owner" and owner without secure purchase key trade-does-not-allow-op-safe-for-entry=The trade does not allow op. safe for entry / operations card-does-not-satisfy-check-digit=Card does not satisfy the check-digit merchant-cannot-perform-pre-authorizations=The merchant cannot perform pre-authorizations card-does-not-allow-pre-authorization-operations=This card does not allow pre-authorization operations operation-stopped-exceeding-control-sis=Operation stopped for exceeding the control of restrictions in the entrance to the SIS error-confirmation-merchant-sends-only-applicable-soap-synchronization=Error in the confirmation that the merchant sends to the Virtual TPV (only applicable in the SOAP synchronization option) confirmation-ko-merchant-only-applicable-soap-synchronization=Confirmation "KO" of the merchant (only applicable in the SOAP synchronization option) users-request-payment-has-been-canceled=User's request payment has been canceled authorization-cancellation-perform-by-sis=Authorization cancellation perform by the SIS (batch process) cancellation-deferred-authorization-by-merchant=Cancellation of deferred authorization by the merchant another-transaction-is-being-processed-with-same-card=Another transaction is being processed in SIS with the same card operation-in-process-requesting-card-data=Operation in process of requesting card data operation-has_been-redirected-to-issuer-to-authenticate=Operation that has been redirected to the issuer to authenticate
29. Guarda los cambios y despliega el módulo ejecutando el siguiente comando dentro del directorio de tu módulo en el Liferay Workspace:
commerce-payment-method-redsys% ./../../gradlew deploy
30. Ahora abre tu Liferay Portal (ej.: localhost:8080), abre la tienda online que contenga el Channel creado anteriormente. Añade al carrito unos productos y efectúa la compra. En el step de pago, debería presentarse la opción creada (Redsys), seleccionala y haz click en “continue”
En el step de resumen, te presentará la opción de pago seleccionada y los demás datos de la compra, haz click en “continue” para que redireccione a la pasarela de pago.
Te redireccionará a la plataforma de Redsys (el entorno de test), añade los siguientes datos de prueba para efectuar el pago:
N de Tarjeta: 4548 8120 4940 0004
Caducidad: 12/20
Cód. Seguridad: 123
Tras cumplimentar, haz clicken “Pagar”, para simular el pago con éxito, selecciona “Autenticación con éxito“ y haz click en “Enviar”
Se direccionará al portal, pasando por el método de “completePayment”
Para para simular el pago con error, selecciona “Denegar autenticación” y haz click en “Enviar”
En este caso se redireccionará al portal, pasando por el método “cancelPayment”
Como habéis comprobado, en ambos casos redireccionará de vuelta a Liferay Commerce, con el mensaje de “cancelled” capturado de la request de Redsys (y la opción de volver a intentar el pago) o en caso de éxito con el mensaje de “sucess”.
El código usado en este paso está disponible en una etiqueta con nombre “payment-redsys-last-step” en mi repo de github.
Finalmente terminamos por aquí nuestro “big blog”, ahora con los conocimientos de cómo crear un método de pago nuevo para Liferay Commerce.