How to create a new Payment Method for Liferay Commerce (Part 2)

NOTICE: This blog  post has Spanish and English version.

                      ...Done the part 1, we can continue with the integration of the payment gateway...

  • Step 3, Integrating the payment method with the Redsys gateway by Redirection

1.  In the package "com.liferay.commerce.payment.method.redsys" create a class called "RedsysCommercePaymentMethodRequest" that extends "CommercePaymentRequest". Inside the class define a new constructor as below (here we add in "HttpServletRequest" since we will need to have access to this servlet in the future):

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.  In the package "com.liferay.commerce.payment.method.redsys" create a class called "RedsysCommercePaymentRequestProvider" that implements "CommercePaymentRequestProvider" and implements the method "getCommercePaymentRequest".

3.  Annotate this class with @Component and set 3 properties. It should look like this:

@Component(   immediate = true,
property = "commerce.payment.engine.method.key=" + RedsysCommercePaymentMethod.KEY,
service = CommercePaymentRequestProvider.class
)
Important: note that in the property "commerce.payment.engine.method.key", we will inform the "RedsysCommercePaymentMethod.KEY", as payment engine, this is how we are integrating everything.

4.  Now at the end of the class inject the “CommerceOrderLocalService” service through the @Reference annotation.

5.  Inside the "getCommercePaymentRequest" method, we retrieve the "CommerceOrder" and return an instance of "RedsysCommercePaymentMethodRequest". In the end, your class should look like this:

@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.  In the package “com.liferay.commerce.payment.method.redsys.connector”, create a new class called “RedsysConfigClient” (we will use it as a connection data bean), copy and paste the following code inside the class :

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;

So, as our connection with the payment gateway will be by redirection, we must create our servlet, then:

7.  Create a new package:

  • “ com.liferay.commerce.payment.method.redsys.servlet”

8.  Create a class called "RedsysCommercePaymentMethodServlet" that extends "HttpServlet", and annotates it as a @Component, configures the Servlet context and implements the "doGet" method. It should look like this:

@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.  Create another new package:

  • “ com.liferay.commerce.payment.method.redsys.servlet.filter”

10.  Inside this package, create a class called "RedsysCommercePaymentMethodVerifierFilter" that extends the "AuthVerifierFilter", annotates it as an @Component and configures it to be able to access the Servlet from the browser with an authenticated user through your session. It should look like this:

@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.  Now we must download the Redsys libraries, you can download them in the "Redsys" portal in the option of "GENERATION HMAC_SHA256" or in my Git repo (I know that uploads from .jar to Git are bad practice, however as a matter of course to facilitate in the access I have put it in the project, afterwards you can put it in a repository of dependencies of your preference and add it to your project in a more elegant way). Then we continue:

12.  Create a "lib" folder at the root of your module, copy and paste the ".jar" files into it.

13.  Open the file “build.gradle” and add these files as dependencies, plus the “currency” api:

compileInclude fileTree(dir: 'libs', include: ['*.jar'])    
compileOnly group: 'com.liferay.commerce', name: 'com.liferay.commerce.currency.api', version: '7.0.2'

14.  Now we go back to the first class that we created at the beginning of this project, the "RedsysCommercePaymentMethod". Inside this class, we are going to finish implementing our existing methods. Starting with the "getPaymenteType", we will return the type of payment that we are implementing, as we have already discussed, we will implement the integration with Redsys by redirection, this method should look like this:

@Override
public int getPaymentType() {
  return CommercePaymentConstants.COMMERCE_PAYMENT_METHOD_TYPE_ONLINE_REDIRECT;
}

15.  In the "getServletPath" method, we will return what we have defined previously.

@Override
public String getServletPath() {
  return RedsysCommercePaymentMethodConstants.SERVLET_PATH;
}

16.  Change the "return" of the 3 remaining "boolean" methods to "true" to enable the 3 options that we will use next.

@Override
public boolean isCancelEnabled() {
  return true;
}

@Override
public boolean isCompleteEnabled() {
  return true;
}

@Override
public boolean isProcessPaymentEnabled() {
  return true;
}

17.  At the end of the class, inject the following services with the @Reference annotation.

@Reference
private CommerceOrderService _commerceOrderService;

@Reference
private ConfigurationProvider _configurationProvider;

@Reference
private Http _http;

@Reference
private Portal _portal;

 

18.  Create the 2 methods below within the class, one to retrieve the configuration of the payment method per channel that we have saved and the other to mount our servlet link for the call.

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;
Important: in summary in this implementation, relevant points:
  • We create our "RedsysCommercePaymentMethodRequest" with the value of "CommercePaymentRequest", and then we retrieve some necessary values for the implementation.

  • We recover the configuration of the payment method per channel, necessary for the connection with the payment gateway.

  • Following the Redsys documentation, we create an object of the type "ApiMacSha256", we set it with the necessary values, we call two methods to encrypt the data.

  • We create a link with our servlet, concatenating 4 values that we will need in the "form" that we will create in the future (redsys-form.jsp).

  • Finally, we create a "CommercePaymentResult" object and set the return of our "processPayment" method. As we are in the payment process, we also set the order status as "ORDER_STATUS_PENDING".

20.  In the package "com.liferay.commerce.payment.method.redsys.constants", create a new enum class called "RedsysMessages" to handle the messages that will come from Redsys, copy and paste the code below into the 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.  Now we return to the first class the "RedsysCommercePaymentMethod". Inside this class copy and paste the code below, to create the method to treat the messages received by 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.  Now inside the "cancelPayment" method, we will implement the following logic

 

List<String> messages = getMessagesFromResponse(redsysCommercePaymentMethodRequest);

return new CommercePaymentResult(
redsysCommercePaymentMethodRequest.getTransactionId(),
redsysCommercePaymentMethodRequest.getCommerceOrderId(),
CommerceOrderPaymentConstants.STATUS_CANCELLED, false, null, null,messages, true);

23.  Now inside the "completePayment" method, we will implement the following logic

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);
Important: note that we retrieve the message in the methods, we add to the return object "CommercePaymentResult" and in it we also set the order status according to each method.  

24.  Inside “META-INF/resources” create a new folder “redsys_form” and inside a JSP file with name “redsys-form.jsp”, copy and paste this code (here we assemble the form that we will send to 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. Open the class "RedsysCommercePaymentMethodServlet" and at the end of the class copy and paste this code to follow, to create logs and to inject some services that we will use

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.  Now create the method to retrieve the settings

private RedsysPaymentMethodCardGroupServiceConfiguration _getConfiguration(long groupId) throws ConfigurationException {
        return _configurationProvider.getConfiguration(RedsysPaymentMethodCardGroupServiceConfiguration.class,
                new GroupServiceSettingsLocator(groupId, RedsysCommercePaymentMethodConstants.SERVICE_NAME));
    }

27. Finally, we implement the logic within the "doGet" method, which should look like this

@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);
   }
}
Important: the 3 methods "processPayment", "completePayment", "cancelPayment" always pass through the "doGet" method, so within it we have the 3 logics:
 
  • When the "redirect" parameter does not appear in the httpServletRequest, it means that it is a call of the "processPayment" method and that we must then make a request dispatcher with the servlet launched to the form created in "redsys-form.jsp". You will then be redirected to the Redsys payment platform.

 
  • When the parameter "redirect" comes in the httpServletRequest, it means that it is a response from Redsys, we validate if the parameter "cancel" exists in the redirect string:

    • If it contains the parameter, we call the "cancelPayment" method.

    • If it does not contain the parameter, we validate the return data of the transaction according to what is recommended in the documentation:

      • If everything is Ok, we call the "completePayment" method.

      • If it does not go through validation, we throw an error and the order status will continue as "PENDING".

 

28.  Finally, add the translations of the messages received by Redsys in the file “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
Important: the Redsys test payment system only expects the Euro as the currency, therefore in the Channel, make sure you have defined the currency of your channel as Euro.

29.  Save the changes and deploy the module by executing the following command within the directory of your module in the Liferay Workspace:

commerce-payment-method-redsys% ./../../gradlew deploy

30.  Now open your Liferay Portal (ex: localhost: 8080), open the online store that contains the Channel created previously. Add some products to the cart and make the purchase. In the payment step, the option created (Redsys) should be presented, select it and click on "continue".

In the “Order Summary” step, you will be presented with the selected payment option and the other details of the purchase, click on "continue" to redirect to the payment gateway.

It will redirect you to the Redsys platform (the test environment), add the following test data to make the payment:

  • Credit Card Number: 4548 8120 4940 0004

  • Expiration: 12/20

  • CCV: 123

After completing, click on "Pay", to simulate the successful payment, select "Authentication successful" and click on "Send"

It will be directed to the portal, going through the "completePayment" method

To simulate the payment with error, select "Deny authentication" and click "Send"

In this case it will be redirected to the portal, going through the "cancelPayment" method

As you have seen, in both cases it will redirect back to Liferay Commerce, with the "canceled" message captured from the Redsys request (and the option to retry the payment) or in case of success with the "success" message .

The code used in this step is available in a tag named "payment-redsys-last-step" in my github repo.

Now we come to the end of this “big blog”, with the knowledge of how to create a new payment method for Liferay Commerce.