/*
* eGov suite of products aim to improve the internal efficiency,transparency,
* accountability and the service delivery of the government organizations.
*
* Copyright (C) <2015> 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.
*
* 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.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
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;
/**
* 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);
public static final BigDecimal PAISE_RUPEE_CONVERTER = BigDecimal.valueOf(100);
@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) {
final DefaultPaymentRequest paymentRequest = new DefaultPaymentRequest();
LOGGER.debug("inside createPaymentRequest");
final Map<String, String> fields = new HashMap<String, String>(0);
fields.put(CollectionConstants.AXIS_VERSION, collectionApplicationProperties.axisVersion().toString());
fields.put(CollectionConstants.AXIS_COMMAND, collectionApplicationProperties.axisCommand());
fields.put(CollectionConstants.AXIS_ACCESS_CODE, collectionApplicationProperties.axisAccessCode());
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_LOCALE, collectionApplicationProperties.axisLocale());
fields.put(CollectionConstants.AXIS_TICKET_NO, receiptHeader.getConsumerCode());
fields.put(CollectionConstants.AXIS_ORDER_INFO, ApplicationThreadLocals.getCityCode()
+ CollectionConstants.SEPARATOR_HYPHEN + ApplicationThreadLocals.getCityName());
final StringBuilder returnUrl = new StringBuilder();
returnUrl.append(paymentServiceDetails.getCallBackurl()).append("?paymentServiceId=")
.append(paymentServiceDetails.getId());
fields.put(CollectionConstants.AXIS_RETURN_URL, returnUrl.toString());
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());
fields.put(CollectionConstants.AXIS_AMOUNT, paise.toString());
final String axisSecureSecret = collectionApplicationProperties.axisSecureSecret();
if (axisSecureSecret != null) {
final String secureHash = hashAllFields(fields);
fields.put(CollectionConstants.AXIS_SECURE_HASH, secureHash);
}
final StringBuffer buf = new StringBuffer();
buf.append(paymentServiceDetails.getServiceUrl()).append('?');
appendQueryFields(buf, fields);
paymentRequest.setParameter(CollectionConstants.ONLINEPAYMENT_INVOKE_URL, buf);
LOGGER.info("paymentRequest: " + paymentRequest.getRequestParameters());
return paymentRequest;
}
String hashAllFields(final Map<String, String> fields) {
// create a list and sort it
final List<String> fieldNames = new ArrayList<String>(fields.keySet());
Collections.sort(fieldNames);
// create a buffer for the md5 input and add the secure secret first
final StringBuffer buf = new StringBuffer();
final String axisSecureSecret = collectionApplicationProperties.axisSecureSecret();
buf.append(axisSecureSecret);
// iterate through the list and add the remaining field values
final Iterator<String> itr = fieldNames.iterator();
while (itr.hasNext()) {
final String fieldName = itr.next();
final String fieldValue = fields.get(fieldName);
if (fieldValue != null && fieldValue.length() > 0)
buf.append(fieldValue);
}
MessageDigest md5 = null;
byte[] ba = null;
// create the md5 hash and UTF-8 encode it
try {
md5 = MessageDigest.getInstance("MD5");
ba = md5.digest(buf.toString().getBytes("UTF-8"));
} catch (final Exception e) {
} // wont happen
// return buf.toString();
return hex(ba);
} // end hashAllFields()
/**
* Returns Hex output of byte array
*/
static String hex(final byte[] input) {
// create a StringBuffer 2x the size of the hash array
final StringBuffer sb = new StringBuffer(input.length * 2);
// retrieve the byte array data, convert it to hex
// and add it to the StringBuffer
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 PaymentResponse axisResponse = new DefaultPaymentResponse();
final String[] keyValueStr = response.replace("{", "").replace("}", "").split(",");
final Map<String, String> fields = new HashMap<String, String>(0);
for (final String pair : keyValueStr) {
final String[] entry = pair.split("=");
if (entry.length == 2)
fields.put(entry[0].trim(), entry[1].trim());
}
// 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(fields.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE).equals("0") ? "0300" : fields
.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE));
axisResponse.setErrorDescription(fields.get(CollectionConstants.AXIS_RESP_MESSAGE));
axisResponse.setAdditionalInfo6(receiptHeader.getConsumerCode());
axisResponse.setReceiptId(receiptId);
axisResponse.setTxnAmount(new BigDecimal(fields.get(CollectionConstants.AXIS_AMOUNT))
.divide(PAISE_RUPEE_CONVERTER));
axisResponse.setTxnReferenceNo(fields.get(CollectionConstants.AXIS_TXN_NO));
axisResponse.setAdditionalInfo2(fields.get(CollectionConstants.AXIS_ORDER_INFO));
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
Date transactionDate = null;
try {
transactionDate = sdf.parse(fields.get(CollectionConstants.AXIS_BATCH_NO));
axisResponse.setTxnDate(transactionDate);
} catch (final ParseException e) {
LOGGER.error("Error occured in parsing the transaction date [" + fields.get("vpc_BatchNo") + "]", e);
throw new ApplicationRuntimeException(".transactiondate.parse.error", e);
}
/*
* 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 vpc_Txn_Secure_Hash = null2unknown(fields.remove("vpc_SecureHash"));
// defines if error message should be output
boolean errorExists = false;
final String axisSecureSecret = collectionApplicationProperties.axisSecureSecret();
if (axisSecureSecret != null
&& (fields.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE) != null || fields
.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE) != "No Value Returned")) {
// 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 (vpc_Txn_Secure_Hash.equalsIgnoreCase(secureHash)) {
} else
// Secure Hash validation failed, add a data field to be
// displayed later.
errorExists = true;
} else {
}
null2unknown(fields.get("Title"));
null2unknown(fields.get("AgainLink"));
null2unknown(fields.get("vpc_Amount"));
null2unknown(fields.get("vpc_Locale"));
null2unknown(fields.get("vpc_BatchNo"));
null2unknown(fields.get("vpc_Command"));
null2unknown(fields.get("vpc_Message"));
null2unknown(fields.get("vpc_Version"));
null2unknown(fields.get("vpc_Card"));
null2unknown(fields.get("vpc_OrderInfo"));
null2unknown(fields.get("vpc_ReceiptNo"));
null2unknown(fields.get("vpc_Merchant"));
null2unknown(fields.get("vpc_MerchTxnRef"));
null2unknown(fields.get("vpc_AuthorizeId"));
null2unknown(fields.get("vpc_TransactionNo"));
null2unknown(fields.get("vpc_AcqResponseCode"));
final String txnResponseCode = null2unknown(fields.get("vpc_TxnResponseCode"));
null2unknown(fields.get("vpc_CSCResultCode"));
null2unknown(fields.get("vpc_CSCRequestCode"));
null2unknown(fields.get("vpc_AcqCSCRespCode"));
null2unknown(fields.get("vpc_AVS_City"));
null2unknown(fields.get("vpc_AVS_Country"));
null2unknown(fields.get("vpc_AVS_Street01"));
null2unknown(fields.get("vpc_AVS_PostCode"));
null2unknown(fields.get("vpc_AVS_StateProv"));
null2unknown(fields.get("vpc_AVSResultCode"));
null2unknown(fields.get("vpc_AVSRequestCode"));
null2unknown(fields.get("vpc_AcqAVSRespCode"));
null2unknown(fields.get("vpc_VerType"));
null2unknown(fields.get("vpc_VerStatus"));
null2unknown(fields.get("vpc_VerToken"));
null2unknown(fields.get("vpc_VerSecurityLevel"));
null2unknown(fields.get("vpc_3DSenrolled"));
null2unknown(fields.get("vpc_3DSXID"));
null2unknown(fields.get("vpc_3DSECI"));
null2unknown(fields.get("vpc_3DSstatus"));
// Show this page as an error page if error condition
if (txnResponseCode.equals("7") || txnResponseCode.equals("No Value Returned") || errorExists) {
}
// FINISH TRANSACTION - Process the VPC Response Data
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
void appendQueryFields(final StringBuffer buf, final Map 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 = (String) fields.get(fieldName);
if (fieldValue != null && fieldValue.length() > 0)
// append the URL parameters
try {
buf.append(URLEncoder.encode(fieldName, "UTF-8"));
buf.append('=');
buf.append(URLEncoder.encode(fieldValue, "UTF-8"));
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
// add a '&' to the end if we have more fields coming.
if (itr.hasNext())
buf.append('&');
}
} // appendQueryFields()
public PaymentResponse createOfflinePaymentRequest(final ServiceDetails paymentServiceDetails,
final OnlinePayment onlinePayment) {
LOGGER.debug("Inside createOfflinePaymentRequest");
final PaymentResponse axisResponse = new DefaultPaymentResponse();
try {
final HttpPost httpPost = new HttpPost(collectionApplicationProperties.axisReconcileUrl());
final List<NameValuePair> formData = new ArrayList<NameValuePair>();
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, ApplicationThreadLocals
.getCityCode() + CollectionConstants.SEPARATOR_HYPHEN + ApplicationThreadLocals.getCityName()));
try {
httpPost.setEntity(new UrlEncodedFormEntity(formData));
} catch (final UnsupportedEncodingException e1) {
LOGGER.error("Error Decoding Axis Bank Response" + e1.getMessage());
}
final CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = null;
HttpEntity responseAxis = null;
response = httpclient.execute(httpPost);
LOGGER.debug("Response Status >>>>>" + response.getStatusLine());
responseAxis = response.getEntity();
String[] pairs = null;
final BufferedReader reader = new BufferedReader(new InputStreamReader(responseAxis.getContent()));
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.getMessage());
}
pairs = data.toString().split("&");
final Map<String, String> responseAxisMap = new LinkedHashMap<String, String>();
for (final String pair : pairs) {
final int idx = pair.indexOf("=");
try {
responseAxisMap.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"),
URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
} catch (final UnsupportedEncodingException e) {
LOGGER.error("Error Decoding Axis Bank Response" + e.getMessage());
}
}
axisResponse.setAdditionalInfo6(onlinePayment.getReceiptHeader().getConsumerCode().replace("-", "")
.replace("/", ""));
axisResponse.setReceiptId(onlinePayment.getReceiptHeader().getId().toString());
LOGGER.info("ResponseAXIS: " + data.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)
&& responseAxisMap.get(CollectionConstants.AXIS_TXN_RESPONSE_CODE).equals("0") ? 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)));
axisResponse.setAdditionalInfo2(responseAxisMap.get(CollectionConstants.AXIS_ORDER_INFO));
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
Date transactionDate = null;
try {
transactionDate = sdf.parse(responseAxisMap.get(CollectionConstants.AXIS_BATCH_NO));
axisResponse.setTxnDate(transactionDate);
} catch (final ParseException e) {
LOGGER.error(
"Error occured in parsing the transaction date ["
+ responseAxisMap.get(CollectionConstants.AXIS_BATCH_NO) + "]", e);
throw new ApplicationException(".transactiondate.parse.error", e);
}
}
} else if (null != responseAxisMap.get(CollectionConstants.AXIS_CHECK_DR_EXISTS)
&& responseAxisMap.get(CollectionConstants.AXIS_CHECK_DR_EXISTS).equals("N")) {
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.getMessage());
throw new ApplicationRuntimeException("Exception during create offline requests" + exp.getMessage());
}
return axisResponse;
}
}