org.egov.collection.integration.pgi.AxisAdaptor.java Source code

Java tutorial

Introduction

Here is the source code for org.egov.collection.integration.pgi.AxisAdaptor.java

Source

/*
 *    eGov  SmartCity eGovernance suite aims to improve the internal efficiency,transparency,
 *    accountability and the service delivery of the government  organizations.
 *
 *     Copyright (C) 2017  eGovernments Foundation
 *
 *     The updated version of eGov suite of products as by eGovernments Foundation
 *     is available at http://www.egovernments.org
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program. If not, see http://www.gnu.org/licenses/ or
 *     http://www.gnu.org/licenses/gpl.html .
 *
 *     In addition to the terms of the GPL license to be adhered to in using this
 *     program, the following additional terms are to be complied with:
 *
 *         1) All versions of this program, verbatim or modified must carry this
 *            Legal Notice.
 *            Further, all user interfaces, including but not limited to citizen facing interfaces,
 *            Urban Local Bodies interfaces, dashboards, mobile applications, of the program and any
 *            derived works should carry eGovernments Foundation logo on the top right corner.
 *
 *            For the logo, please refer http://egovernments.org/html/logo/egov_logo.png.
 *            For any further queries on attribution, including queries on brand guidelines,
 *            please contact contact@egovernments.org
 *
 *         2) Any misrepresentation of the origin of the material is prohibited. It
 *            is required that all modified versions of this material be marked in
 *            reasonable ways as different from the original version.
 *
 *         3) This license does not grant any rights to any user of the program
 *            with regards to rights under trademark law for use of the trade names
 *            or trademarks of eGovernments Foundation.
 *
 *   In case of any queries, you can reach eGovernments Foundation at contact@egovernments.org.
 *
 */
package org.egov.collection.integration.pgi;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.xml.bind.DatatypeConverter;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import org.egov.collection.config.properties.CollectionApplicationProperties;
import org.egov.collection.constants.CollectionConstants;
import org.egov.collection.entity.OnlinePayment;
import org.egov.collection.entity.ReceiptHeader;
import org.egov.infra.admin.master.entity.City;
import org.egov.infra.admin.master.service.CityService;
import org.egov.infra.config.core.ApplicationThreadLocals;
import org.egov.infra.exception.ApplicationException;
import org.egov.infra.exception.ApplicationRuntimeException;
import org.egov.infstr.models.ServiceDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * The PaymentRequestAdaptor class frames the request object for the payment service.
 */
@Service
public class AxisAdaptor implements PaymentGatewayAdaptor {

    private static final Logger LOGGER = Logger.getLogger(AxisAdaptor.class);
    private static final BigDecimal PAISE_RUPEE_CONVERTER = BigDecimal.valueOf(100);
    private static final String UTF8 = "UTF-8";
    private static final String NO_VALUE_RETURNED = "No Value Returned";

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private CollectionApplicationProperties collectionApplicationProperties;

    @Autowired
    private CityService cityService;

    /**
     * This method invokes APIs to frame request object for the payment service passed as parameter
     *
     * @param serviceDetails
     * @param receiptHeader
     * @return
     */
    @Override
    public PaymentRequest createPaymentRequest(final ServiceDetails paymentServiceDetails,
            final ReceiptHeader receiptHeader) {
        LOGGER.debug("inside createPaymentRequest");
        final DefaultPaymentRequest paymentRequest = new DefaultPaymentRequest();
        final LinkedHashMap<String, String> fields = new LinkedHashMap<>(0);
        final StringBuilder requestURL = new StringBuilder();
        final BigDecimal amount = receiptHeader.getTotalAmount();
        final float rupees = Float.parseFloat(amount.toString());
        final Integer rupee = (int) rupees;
        final Float exponent = rupees - (float) rupee;
        final Integer paise = (int) (rupee * PAISE_RUPEE_CONVERTER.intValue()
                + exponent * PAISE_RUPEE_CONVERTER.intValue());
        final StringBuilder returnUrl = new StringBuilder();
        returnUrl.append(paymentServiceDetails.getCallBackurl()).append("?paymentServiceId=")
                .append(paymentServiceDetails.getId());
        fields.put(CollectionConstants.AXIS_ACCESS_CODE, collectionApplicationProperties.axisAccessCode());
        fields.put(CollectionConstants.AXIS_AMOUNT, paise.toString());
        fields.put(CollectionConstants.AXIS_COMMAND, collectionApplicationProperties.axisCommand());
        fields.put(CollectionConstants.AXIS_LOCALE, collectionApplicationProperties.axisLocale());
        fields.put(CollectionConstants.AXIS_MERCHANT_TXN_REF, ApplicationThreadLocals.getCityCode()
                + CollectionConstants.SEPARATOR_HYPHEN + receiptHeader.getId().toString());
        fields.put(CollectionConstants.AXIS_MERCHANT, collectionApplicationProperties.axisMerchant());
        fields.put(CollectionConstants.AXIS_ORDER_INFO, ApplicationThreadLocals.getCityCode()
                + CollectionConstants.SEPARATOR_HYPHEN + ApplicationThreadLocals.getCityName());
        fields.put(CollectionConstants.AXIS_RETURN_URL, returnUrl.toString());
        fields.put(CollectionConstants.AXIS_TICKET_NO, receiptHeader.getConsumerCode());
        fields.put(CollectionConstants.AXIS_VERSION, collectionApplicationProperties.axisVersion());
        final String axisSecureSecret = collectionApplicationProperties.axisSecureSecret();
        if (axisSecureSecret != null) {
            final String secureHash = hashAllFields(fields);
            fields.put(CollectionConstants.AXIS_SECURE_HASH, secureHash);
        }
        fields.put(CollectionConstants.AXIS_SECURE_HASHTYPE, CollectionConstants.AXIS_SECURE_HASHTYPE_VALUE);

        requestURL.append(paymentServiceDetails.getServiceUrl()).append('?');
        appendQueryFields(requestURL, fields);
        paymentRequest.setParameter(CollectionConstants.ONLINEPAYMENT_INVOKE_URL, requestURL);
        LOGGER.info("AXIS payment gateway request: " + paymentRequest.getRequestParameters());
        return paymentRequest;
    }

    private String hashAllFields(final LinkedHashMap<String, String> fields) {

        final String axisSecureSecret = collectionApplicationProperties.axisSecureSecret();
        byte[] decodedKey;
        byte[] hashValue = null;
        // Sort list with field names ascending order
        final List<String> fieldNames = new ArrayList<>(fields.keySet());
        Collections.sort(fieldNames);

        // iterate through field name list and generate message for hashing. Format: fieldname1=fieldvale1?fieldname2=fieldvalue2
        final Iterator<String> itr = fieldNames.iterator();
        final StringBuilder hashingMessage = new StringBuilder();
        int i = 0;
        while (itr.hasNext()) {
            final String fieldName = itr.next();
            final String fieldValue = fields.get(fieldName);
            if (fieldValue != null && fieldValue.length() > 0) {
                if (i != 0)
                    hashingMessage.append("&");
                hashingMessage.append(fieldName).append("=").append(fieldValue);
                i++;
            }
        }
        try {
            decodedKey = Hex.decodeHex(axisSecureSecret.toCharArray());
            SecretKeySpec keySpec = new SecretKeySpec(decodedKey, "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(keySpec);
            byte[] hashingMessageBytes = hashingMessage.toString().getBytes(UTF8);
            hashValue = mac.doFinal(hashingMessageBytes);
        } catch (DecoderException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return DatatypeConverter.printHexBinary(hashValue);
    } // end hashAllFields()

    /**
     * Returns Hex output of byte array
     */
    /*
     * private static String hex(final byte[] input) { // create a StringBuilder 2x the size of the hash array final StringBuilder
     * sb = new StringBuilder(input.length * 2); // retrieve the byte array data, convert it to hex // and add it to the
     * StringBuilder for (final byte element : input) { sb.append(CollectionConstants.AXIS_HEX_TABLE[element >> 4 & 0xf]);
     * sb.append(CollectionConstants.AXIS_HEX_TABLE[element & 0xf]); } return sb.toString(); }
     */

    /**
     * This method parses the given response string into a AXIS payment response object.
     *
     * @param a <code>String</code> representation of the response.
     * @return an instance of <code></code> containing the response information
     */
    @Override
    public PaymentResponse parsePaymentResponse(final String response) {
        LOGGER.info("Response message from Axis Payment gateway: " + response);
        final String[] keyValueStr = response.replace("{", "").replace("}", "").split(",");
        final LinkedHashMap<String, String> fields = new LinkedHashMap<>(0);

        for (final String pair : keyValueStr) {
            final String[] entry = pair.split("=");
            if (entry.length == 2)
                fields.put(entry[0].trim(), entry[1].trim());
        }
        /*
         * If there has been a merchant secret set then sort and loop through all the data in the Virtual Payment Client response.
         * while we have the data, we can append all the fields that contain values (except the secure hash) so that we can create
         * a hash and validate it against the secure hash in the Virtual Payment Client response. NOTE: If the vpc_TxnResponseCode
         * in not a single character then there was a Virtual Payment Client error and we cannot accurately validate the incoming
         * data from the secure hash.
         */

        // remove the vpc_TxnResponseCode code from the response fields as we do
        // not
        // want to include this field in the hash calculation
        final String vpcTxnSecureHash = null2unknown(fields.remove(CollectionConstants.AXIS_SECURE_HASH));
        // defines if error message should be output
        final String axisSecureSecret = collectionApplicationProperties.axisSecureSecret();
        if (axisSecureSecret != null && (fields.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE) != null
                || NO_VALUE_RETURNED.equals(fields.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE)))) {

            // create secure hash and append it to the hash map if it was
            // created
            // remember if SECURE_SECRET = "" it wil not be created
            final String secureHash = hashAllFields(fields);

            // Validate the Secure Hash (remember MD5 hashes are not case
            // sensitive)
            if (!vpcTxnSecureHash.equalsIgnoreCase(secureHash)) {
                // Secure Hash validation failed, add a data field to be
                // displayed later.
                // throw new ApplicationRuntimeException("Axis Bank Payment
                // Secure Hash validation failed");
            }
        }
        return preparePaymentResponse(fields);
    }

    private PaymentResponse preparePaymentResponse(final Map<String, String> fields) {
        final PaymentResponse axisResponse = new DefaultPaymentResponse();
        try {
            // AXIS Payment Gateway returns Response Code 0(Zero) for successful
            // transactions, so converted it to 0300
            // as that is being followed as a standard in other payment
            // gateways.
            final String[] merchantRef = fields.get(CollectionConstants.AXIS_MERCHANT_TXN_REF)
                    .split(CollectionConstants.SEPARATOR_HYPHEN);
            final String receiptId = merchantRef[1];
            final String ulbCode = merchantRef[0];
            final ReceiptHeader receiptHeader;
            final Query qry = entityManager.createNamedQuery(CollectionConstants.QUERY_RECEIPT_BY_ID_AND_CITYCODE);
            qry.setParameter(1, Long.valueOf(receiptId));
            qry.setParameter(2, ulbCode);
            receiptHeader = (ReceiptHeader) qry.getSingleResult();
            axisResponse.setAuthStatus("0".equals(fields.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE))
                    ? CollectionConstants.PGI_AUTHORISATION_CODE_SUCCESS
                    : fields.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE));
            axisResponse.setErrorDescription(fields.get(CollectionConstants.AXIS_RESP_MESSAGE));
            axisResponse.setAdditionalInfo6(receiptHeader.getConsumerCode().replace("-", "").replace("/", ""));
            axisResponse.setReceiptId(receiptId);
            axisResponse.setAdditionalInfo2(fields.get(CollectionConstants.AXIS_ORDER_INFO));
            if (axisResponse.getAuthStatus().equals(CollectionConstants.PGI_AUTHORISATION_CODE_SUCCESS)) {
                axisResponse.setTxnAmount(
                        new BigDecimal(fields.get(CollectionConstants.AXIS_AMOUNT)).divide(PAISE_RUPEE_CONVERTER));
                axisResponse.setTxnReferenceNo(fields.get(CollectionConstants.AXIS_TXN_NO));
                axisResponse.setTxnDate(getTransactionDate(fields.get(CollectionConstants.AXIS_BATCH_NO)));
            }
        } catch (final Exception exp) {
            LOGGER.error(exp);
            throw new ApplicationRuntimeException("Exception during prepare payment response" + exp.getMessage());
        }
        return axisResponse;
    }

    /*
     * This method takes a data String and returns a predefined value if empty If data Sting is null, returns string
     * "No Value Returned", else returns input
     * @param in String containing the data String
     * @return String containing the output String
     */
    private static String null2unknown(final String in) {
        if (in == null || in.length() == 0)
            return NO_VALUE_RETURNED;
        else
            return in;
    } // null2unknown()

    /**
     * This method is for creating a URL query string.
     *
     * @param buf is the inital URL for appending the encoded fields to
     * @param fields is the input parameters from the order page
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    // Method for creating a URL query string
    private void appendQueryFields(final StringBuilder buf, final LinkedHashMap<String, String> fields) {

        // create a list
        final List fieldNames = new ArrayList(fields.keySet());
        final Iterator itr = fieldNames.iterator();

        // move through the list and create a series of URL key/value pairs
        while (itr.hasNext()) {
            final String fieldName = (String) itr.next();
            final String fieldValue = fields.get(fieldName);

            if (fieldValue != null && fieldValue.length() > 0)
                // append the URL parameters
                try {
                    buf.append(URLEncoder.encode(fieldName, UTF8));
                    buf.append('=');
                    buf.append(URLEncoder.encode(fieldValue, UTF8));
                } catch (final UnsupportedEncodingException e) {
                    LOGGER.error("Error appending QueryFields" + e);
                    throw new ApplicationRuntimeException(e.getMessage());
                }
            // add a '&' to the end if we have more fields coming.
            if (itr.hasNext())
                buf.append('&');
        }
    } // appendQueryFields()

    @Transactional
    public PaymentResponse createOfflinePaymentRequest(final OnlinePayment onlinePayment) {
        LOGGER.debug("Inside createOfflinePaymentRequest");
        final PaymentResponse axisResponse = new DefaultPaymentResponse();
        try {
            final HttpPost httpPost = new HttpPost(collectionApplicationProperties.axisReconcileUrl());
            httpPost.setEntity(prepareEncodedFormEntity(onlinePayment));
            final CloseableHttpClient httpclient = HttpClients.createDefault();
            CloseableHttpResponse response;
            HttpEntity responseAxis;
            response = httpclient.execute(httpPost);
            LOGGER.debug("Response Status >>>>>" + response.getStatusLine());
            responseAxis = response.getEntity();
            final Map<String, String> responseAxisMap = prepareResponseMap(responseAxis.getContent());
            axisResponse.setAdditionalInfo6(
                    onlinePayment.getReceiptHeader().getConsumerCode().replace("-", "").replace("/", ""));
            axisResponse.setReceiptId(onlinePayment.getReceiptHeader().getId().toString());
            if (null != responseAxisMap.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE)
                    && !"".equals(responseAxisMap.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE))) {
                axisResponse.setAuthStatus(null != responseAxisMap.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE)
                        && "0".equals(responseAxisMap.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE))
                                ? CollectionConstants.PGI_AUTHORISATION_CODE_SUCCESS
                                : responseAxisMap.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE));
                axisResponse.setErrorDescription(responseAxisMap.get(CollectionConstants.AXIS_RESP_MESSAGE));

                if (CollectionConstants.PGI_AUTHORISATION_CODE_SUCCESS.equals(axisResponse.getAuthStatus())) {
                    axisResponse.setTxnReferenceNo(responseAxisMap.get(CollectionConstants.AXIS_TXN_NO));
                    axisResponse.setTxnAmount(new BigDecimal(responseAxisMap.get(CollectionConstants.AXIS_AMOUNT))
                            .divide(PAISE_RUPEE_CONVERTER));
                    axisResponse.setAdditionalInfo2(responseAxisMap.get(CollectionConstants.AXIS_ORDER_INFO));
                    axisResponse
                            .setTxnDate(getTransactionDate(responseAxisMap.get(CollectionConstants.AXIS_BATCH_NO)));
                }
            } else if (null != responseAxisMap.get(CollectionConstants.AXIS_CHECK_DR_EXISTS)
                    && "N".equals(responseAxisMap.get(CollectionConstants.AXIS_CHECK_DR_EXISTS))) {
                axisResponse.setErrorDescription(CollectionConstants.AXIS_FAILED_ABORTED_MESSAGE);
                axisResponse.setAuthStatus(CollectionConstants.AXIS_ABORTED_AUTH_STATUS);
            }
            LOGGER.debug("receiptid=" + axisResponse.getReceiptId() + "consumercode="
                    + axisResponse.getAdditionalInfo6());
        } catch (final Exception exp) {
            LOGGER.error(exp);
            throw new ApplicationRuntimeException("Exception during create offline requests" + exp.getMessage());
        }
        return axisResponse;
    }

    private UrlEncodedFormEntity prepareEncodedFormEntity(final OnlinePayment onlinePayment) {
        final List<NameValuePair> formData = new ArrayList<>();
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_VERSION,
                collectionApplicationProperties.axisVersion()));
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_COMMAND,
                collectionApplicationProperties.axisCommandQuery()));
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_ACCESS_CODE,
                collectionApplicationProperties.axisAccessCode()));
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_MERCHANT,
                collectionApplicationProperties.axisMerchant()));
        final City cityWebsite = cityService.getCityByURL(ApplicationThreadLocals.getDomainName());
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_MERCHANT_TXN_REF, cityWebsite.getCode()
                + CollectionConstants.SEPARATOR_HYPHEN + onlinePayment.getReceiptHeader().getId().toString()));
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_OPERATOR_ID,
                collectionApplicationProperties.axisOperator()));
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_PASSWORD,
                collectionApplicationProperties.axisPassword()));
        formData.add(new BasicNameValuePair(CollectionConstants.AXIS_ORDER_INFO,
                cityWebsite.getCode() + CollectionConstants.SEPARATOR_HYPHEN + cityWebsite.getName()));
        UrlEncodedFormEntity urlEncodedFormEntity = null;
        try {
            urlEncodedFormEntity = new UrlEncodedFormEntity(formData);
        } catch (final UnsupportedEncodingException e1) {
            LOGGER.error("Error in Create Offline Payment Request" + e1);
        }
        return urlEncodedFormEntity;
    }

    private Date getTransactionDate(final String transDate) throws ApplicationException {
        final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
        try {
            return sdf.parse(transDate);
        } catch (final ParseException e) {
            LOGGER.error("Error occured in parsing the transaction date [" + transDate + "]", e);
            throw new ApplicationException(".transactiondate.parse.error", e);
        }
    }

    private Map<String, String> prepareResponseMap(final InputStream responseContent) {
        String[] pairs;
        final BufferedReader reader = new BufferedReader(new InputStreamReader(responseContent));
        final StringBuilder data = new StringBuilder();
        String line;
        try {
            while ((line = reader.readLine()) != null)
                data.append(line);
            reader.close();
        } catch (final IOException e) {
            LOGGER.error("Error Reading InsputStrem from Axis Bank Response" + e);
        }
        LOGGER.info("ResponseAXIS: " + data.toString());
        pairs = data.toString().split("&");
        final Map<String, String> responseAxisMap = new LinkedHashMap<>();
        for (final String pair : pairs) {
            final int idx = pair.indexOf('=');
            try {
                responseAxisMap.put(URLDecoder.decode(pair.substring(0, idx), UTF8),
                        URLDecoder.decode(pair.substring(idx + 1), UTF8));
            } catch (final UnsupportedEncodingException e) {
                LOGGER.error("Error Decoding Axis Bank Response" + e);
            }
        }
        return responseAxisMap;
    }

}