Subversion Repositories SmartDukaan

Rev

Rev 13545 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package in.shop2020.payment.service.handler;

import in.shop2020.model.v1.order.RechargeOrder;
import in.shop2020.payment.domain.Refund;
import in.shop2020.payment.handler.PaymentGatewayHandler;
import in.shop2020.payment.handler.PaymentHandler;
import in.shop2020.payment.handler.PaymentRequiringExtraProcessingHandler;
import in.shop2020.payment.handler.RefundHandler;
import in.shop2020.payment.service.handler.IPaymentHandler.Errors;
import in.shop2020.payments.Attribute;
import in.shop2020.payments.ExtraPaymentProcessingType;
import in.shop2020.payments.Payment;
import in.shop2020.payments.PaymentException;
import in.shop2020.payments.PaymentGateway;
import in.shop2020.payments.PaymentService.Iface;
import in.shop2020.payments.PaymentStatus;
import in.shop2020.thrift.clients.TransactionClient;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.thrift.TException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mongodb.util.Hash;

public class PaymentServiceHandler implements Iface {
    
    private static final Log logger = LogFactory.getLog(PaymentServiceHandler.class);

    
    /**
     * Enum of all statuses that can be returned by the HDFC gateway
     * 
     * @author Chandranshu
     * 
     */
    public enum HdfcPaymentReturnStatus{
        APPROVED("APPROVED"),
        NOT_APPROVED("NOT APPROVED"),
        CAPTURED("CAPTURED"),
        NOT_CAPTURED ("NOT CAPTURED"),
        CANCELLED ("CANCELLED"),
        DENIED_BY_RISK("DENIED BY RISK"),
        HOST_TIMEOUT("HOST TIMEOUT"),
        SUCCESS("SUCCESS"),
        FAILURE("FAILURE");
        private String value;
        HdfcPaymentReturnStatus(String value) {
            this.value = value;
        }
        public String value(){
            return this.value;
        }
    }
    
        public static final long PAYMENT_NOT_CREATED = -1;
        
        private static final long HDFC_GATEWAY_ID = 1;
        private static final long EBS_GATEWAY_ID = 2;
        private static final long PAYU_GATEWAY_ID = 19;
        private static final long EBAY_GATEWAY_ID = 16;
        private static final long SNAPDEAL_GATEWAY_ID = 18;
        private static final List<Long> HDFC_EMI_GATEWAY_IDS = Arrays.asList(5L,10L,11L,12L,14L);
        private static final long WALLET_GATEWAY_ID = 8;
        
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        PaymentHandler paymentHandler = (PaymentHandler) context.getBean("paymentHandler");
    PaymentRequiringExtraProcessingHandler paymentRequiringExtraProcessingHandler =
        (PaymentRequiringExtraProcessingHandler) context.getBean("paymentRequiringExtraProcessingHandler");

    PaymentGatewayHandler paymentGatewayHandler = (PaymentGatewayHandler) context.getBean("paymentGatewayHandler");
        RefundHandler refundHandler = (RefundHandler) context.getBean("refundHandler");

        public void setDataSourceUrl(String dbHost){
                org.apache.commons.dbcp.BasicDataSource ds = (org.apache.commons.dbcp.BasicDataSource)context.getBean("dataSource");
                ds.setUrl(dbHost);
        }
        
        public String getDataSourceUrl(){
                org.apache.commons.dbcp.BasicDataSource ds = (org.apache.commons.dbcp.BasicDataSource)context.getBean("dataSource");
                return ds.getUrl();
        }
        
        @Override
        public long createPayment(long userId, double amount, long gatewayId, long txnId, boolean isDigital) throws PaymentException, TException {
            logger.info("Creating payment corresponding to our txn id:" + txnId);
                in.shop2020.payment.domain.Payment payment = new in.shop2020.payment.domain.Payment();
                payment.setUserId(userId);
                payment.setAmount(amount);
                payment.setGatewayId(gatewayId);
                payment.setMerchantTxnId(txnId);
                payment.setStatus(PaymentStatus.INIT.getValue());
                payment.setDigital(isDigital);

                return paymentHandler.insertPayment(payment);
        }

        @Override
        public List<Payment> getPaymentsForUser(long userId, long fromTime, long toTime, PaymentStatus status, long gatewayId) throws PaymentException, TException {
            logger.info("Getting payments from " + fromTime + " to " + toTime + " for user: " + userId);
                int statusValue = -1;
                if(status != null)
                        statusValue = status.getValue();
                else
                        statusValue = -1;
                return getThriftPayments(paymentHandler.getPaymentsForUser(userId, fromTime, toTime, statusValue, gatewayId));
        }

        @Override
        public List<Payment> getPayments(long fromTime, long toTime, PaymentStatus status, long gatewayId) throws PaymentException,     TException {
            logger.info("Getting payments from " + fromTime + " to " + toTime);
                int statusValue = -1;
                if(status != null)
                        statusValue = status.getValue();
                else
                        statusValue = -1;
                return getThriftPayments(paymentHandler.getPayments(fromTime, toTime, statusValue, gatewayId));
        }
        
        @Override
        public List<Payment> getPaymentsByCapturedDate(long fromTime, long toTime, long gatewayId) throws PaymentException, TException {
                logger.info("Getting payments from " + fromTime + " to " + toTime + " for " + gatewayId);
                return getThriftPayments(paymentHandler.getPaymentsByCapturedDate(fromTime, toTime, gatewayId));
        }

        @Override
        public PaymentGateway getPaymentGateway(long id) throws PaymentException, TException {
            logger.info("Getting payment gateway with id:" + id);
                return paymentGatewayHandler.getPaymentGateway(id).getThriftPaymentGateway();
        }
        
        @Override
        public List<PaymentGateway> getActivePaymentGateways() throws PaymentException, TException {
            logger.info("Getting all active payment gateways");
            return getThriftPaymentGateways(paymentGatewayHandler.getActivePaymentGateways());
        }
        
        @Override
        public Payment getPayment(long id) throws PaymentException, TException {
            logger.info("Getting payment with id: " + id);
                return paymentHandler.getPayment(id).getThriftPayment();
        }

        @Override
        public List<Payment> getPaymentForTxnId(long txnId) throws PaymentException, TException {
            logger.info("Getting payment for the txn id: " + txnId);
                return getThriftPayments(paymentHandler.getPaymentForTxn(txnId));
        }
        
        @Override
    public List<Payment> getPaymentForRechargeTxnId(long txnId) throws PaymentException, TException {
        logger.info("Getting payment for the txn id: " + txnId);
        return getThriftPayments(paymentHandler.getPaymentForRechargeTxn(txnId));
    }
        
        @Override
        public Payment getSuccessfulPaymentForTxnId(long txnId) throws PaymentException, TException {
                
                for (Payment payment: getPaymentForTxnId(txnId))        {
                        if (payment.getStatus() == PaymentStatus.SUCCESS || payment.getStatus() == PaymentStatus.PARTIALLY_CAPTURED)    {
                                return payment;
                        }
                }
                return null;
        }

        @Override
        public boolean updatePaymentDetails(long id, String gatewayPaymentId,
                        String sessionId, String gatewayTxnStatus, String description,
                        String gatewayTxnId, String authCode, String referenceCode,
                        String errorCode, PaymentStatus status, String gatewayTxnDate,
                        List<Attribute> attributes) throws PaymentException, TException {
            logger.info("Updating details of payment id: " + id);
                in.shop2020.payment.domain.Payment payment = paymentHandler.getPayment(id);
                payment.setGatewayPaymentId(gatewayPaymentId);
                payment.setSessionId(sessionId);
                payment.setGatewayTxnStatus(gatewayTxnStatus);
                payment.setDescription(description);
                payment.setGatewayTxnId(gatewayTxnId);
                payment.setAuthCode(authCode);
                payment.setReferenceCode(referenceCode);
                payment.setErrorCode(errorCode);
                if(status!=null){
                        payment.setStatus(status.getValue());
                        if(status.equals(PaymentStatus.SUCCESS))
                                payment.setSuccessTimestamp(new Date());
                        else if(status.equals(PaymentStatus.FAILED)) {
                            payment.setErrorTimestamp(new Date());
                            persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
                        }
                        else if (status.equals(PaymentStatus.PROVISIONALLY_CAPTURED)) {
                            // FIXME different column to note provisional capture timestamp
                            // FIXME Requires extra processing at Crm end for actual manual capture
                            payment.setProvisionalCaptureTimestamp(new Date());
                            persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.PENDING_CAPTURE);
                        }
                }
                
                payment.setGatewayTxnDate(gatewayTxnDate);
                
                Map<String, String> attrMap = new HashMap<String, String>();
                if(attributes != null){
                        for(Attribute attribute : attributes){
                                attrMap.put(attribute.getName(), attribute.getValue());
                        }
                }
                
                paymentHandler.updatePayment(payment, attrMap);
                return true;
        }

        /**
         * Persists a given payment Id in another table for future processing by CRM etc.
         * Failed payments generally require a follow-up to help customers through our
         * CRM-Outbound team.
         *
         * @param payment  the payment object that failed.
         * @param type TODO
         */
        private void persistPaymentRequiringExtraProcessing(in.shop2020.payment.domain.Payment payment, ExtraPaymentProcessingType type) {
            try {
            paymentRequiringExtraProcessingHandler.insert(payment.getId(), type);
        } catch (Exception e) {
            logger.error("Could not persist payment: " + payment.getId(), e);
        }
    }

    @Override
        public List<Double> getSuccessfulPaymentsAmountRange() throws TException {
            logger.info("Getting the range of successful payments.");
                List<Double> minMaxAmounts = new ArrayList<Double>();
                Map<String, Float> minMax = paymentHandler.getMinMaxPaymentAmount();
                minMaxAmounts.add(Double.parseDouble(Float.toString(minMax.get("MIN"))));
                minMaxAmounts.add(Double.parseDouble(Float.toString(minMax.get("MAX"))));
                return minMaxAmounts;
        }

    @Override
    public String initializeHdfcPayment(long merchantPaymentId, boolean isMobile) throws PaymentException, TException {
        logger.info("Initializing HDFC payment with id: " + merchantPaymentId);
        in.shop2020.payment.domain.Payment payment = paymentHandler.getPayment(merchantPaymentId);
        String redirectURL;
        try {
            redirectURL = HdfcPaymentHandler.initializeHdfcPayment(payment, this, isMobile);
        } catch (Exception e) {
            throw new PaymentException(102, "Error while initiliazing payment. Check service log for more details.");
        }
        return redirectURL;
    }

    @Override
        public String doHdfcPaymentForDigitalOrder(long merchantPaymentId, long rechargeOrderId, String phone, boolean isMobile) throws PaymentException, TException {
        logger.info("Initializing HDFC payment with id: " + merchantPaymentId);
        logger.info("doHdfcPaymentForDigitalOrder phone---- " + phone);

        in.shop2020.payment.domain.Payment payment = paymentHandler.getPayment(merchantPaymentId);
        TransactionClient tc = new TransactionClient();
        String redirectURL;
        RechargeOrder rechargeOrder;
        try {
            rechargeOrder = tc.getClient().getRechargeOrder(rechargeOrderId);
            redirectURL = HdfcPaymentHandler.initializeHdfcPayment(payment, rechargeOrder, phone, this, isMobile);
        } catch (Exception e) {
            throw new PaymentException(102, "Error while initiliazing payment. Check service log for more details.");
        }
        
        return redirectURL;
    }
        
        @Override
    public String initializeHdfcEmiPayment(long merchantPaymentId, boolean isMobile) throws PaymentException, TException {
        logger.info("Initializing HDFC payment with id: " + merchantPaymentId);
        in.shop2020.payment.domain.Payment payment = paymentHandler.getPayment(merchantPaymentId);
        String redirectURL;
        try {
            redirectURL = HdfcEmiPaymentHandler.initializeHdfcPayment(payment, this, isMobile);
        } catch (Exception e) {
            throw new PaymentException(102, "Error while initiliazing payment. Check service log for more details.");
        }
        return redirectURL;
    }
        
        @Override
        public synchronized boolean refundPayment(long merchantTxnId, double amount, boolean isDigital) throws PaymentException, TException {
        logger.info("Attempting to refund payment of amount " + amount + " corresponding to our transaction " + merchantTxnId);
        List<in.shop2020.payment.domain.Payment> payments;
        if(isDigital){
                payments = paymentHandler.getPaymentForRechargeTxn(merchantTxnId);
        }else{
                payments = paymentHandler.getPaymentForTxn(merchantTxnId);
        }
        if(payments ==null || payments.isEmpty())
            throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);
        
        if(payments ==null || payments.isEmpty())
            throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);

        in.shop2020.payment.domain.Payment payment = payments.get(0);
        
        if(payment.getAmount() < amount){
                logger.warn("Refund amount is more than payment amount.");
            return false;
        }
        
        switch(PaymentStatus.findByValue(payment.getStatus())){
        case PENDING:
            logger.error("Attempt to refund a non-authorized payment");
            return false;
        case INIT:
            logger.warn("Attempt to refund a non-authorized payment");
            return false;
        case AUTHORIZED:
                logger.warn("Attempt to refund a non-captured payment");
            return false;
        case CAPTURE_IN_PROCESS:
                logger.warn("Attempt to refund a non-captured payment");
            return false;
        case PROVISIONALLY_CAPTURED:
                logger.warn("Attempt to refund a non-captured payment");
            return false;
        case REFUNDED:
                logger.warn("Attempt to refund a refunded payment");
            return false;
        case SUCCESS:
            break;
        case FAILED:
            logger.error("Attempting to capture a failed payment");
            return false;
        }
        
        long gatewayId = payment.getGatewayId();
        
        if(gatewayId == HDFC_GATEWAY_ID){
            //Refund the HDFC payment
            return refundHdfcPayment(payment, amount);
        }else if (gatewayId == EBS_GATEWAY_ID){
            //Refund the EBS payment
            return refundEbsPayment(payment, amount);
        } 
        else if (HDFC_EMI_GATEWAY_IDS.contains(gatewayId)){
             //Capture and update the HDFC EMI payment
            return refundHdfcEmiPayment(payment, amount);
        }else if (gatewayId == PAYU_GATEWAY_ID) {
                //Refund PayU
                return refundPayUPayment(payment, amount);
                
        } 
        /*else if(gatewayId == WALLET_GATEWAY_ID) {
                return refundWalletPayment(payment, amount);
        }*/
        
        logger.error("We have an captured payment from unknown gateway: " + gatewayId);
        return false;
    }
        
        
        private boolean refundWalletPayment(in.shop2020.payment.domain.Payment payment, double amount) throws PaymentException {
                //TransactionClient tc = new TransactionClient();
                Map<String, String> captureResult = new HashMap<String, String>();
                payment.setGatewayTxnStatus("Refunded to wallet");
                payment.setStatus(PaymentStatus.REFUNDED.getValue());
                payment.setRefundAmount(amount);
                

        SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        captureResult.put(IPaymentHandler.REFUND_TXN_ID, captureResult.get(IPaymentHandler.REFUND_TXN_ID));
        captureResult.put(IPaymentHandler.REFUND_TIME, captureTimeDateFormatter.format(new Date()));

                return true;
        }
        
        private boolean refundPayUPayment (
                        in.shop2020.payment.domain.Payment payment, double amount) throws PaymentException{
                Map<String, String> captureResult = PayuPaymentHandler.refundPayment(payment, amount);
                if(captureResult.containsKey(IPaymentHandler.ERROR)){
            payment.setDescription(captureResult.get(IPaymentHandler.ERROR));
            payment.setErrorCode(captureResult.get(IPaymentHandler.ERR_CODE));
            payment.setErrorTimestamp(new Date());
                if(captureResult.get(IPaymentHandler.ERR_CODE).equals(Errors.CAPTURE_FAILURE)) {
                paymentHandler.updatePayment(payment, captureResult);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
                } else {
                        logger.error("Refund attempt failed for Payu payment with id: " + payment.getId());
                        /*payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());
                        paymentHandler.updatePayment(payment, captureResult);
                        throw new PaymentException(106, captureResult.get(IPaymentHandler.ERROR));*/
                }
                return false;
        } else {
                
                payment.setGatewayTxnStatus("Refund initiated");
            payment.setStatus(PaymentStatus.REFUNDED.getValue());
            payment.setRefundAmount(amount);

            SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            captureResult.put(IPaymentHandler.REFUND_TXN_ID, captureResult.get(IPaymentHandler.REFUND_TXN_ID));
            captureResult.put(IPaymentHandler.REFUND_TIME, captureTimeDateFormatter.format(new Date()));
            paymentHandler.updatePayment(payment, captureResult);
            return true;
        }
        }

        @Override
    public long createRefund(long orderId, long merchantTxnId, double amount) throws PaymentException, TException{
                logger.info("Attempting to create a refund for order: " + orderId);
//              if(!refundPayment(merchantTxnId, amount, false)){
//                      logger.warn("Not able to refund corresponding to the merchant txn " + merchantTxnId);
//              }
                List<in.shop2020.payment.domain.Payment> payments = paymentHandler.getPaymentForTxn(merchantTxnId);
                if(payments ==null || payments.isEmpty())
                        throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);
                
                in.shop2020.payment.domain.Payment payment = payments.get(0);
                if(payment.getStatus() != PaymentStatus.SUCCESS.getValue())
                        throw new PaymentException(104, "No successful payments found corresponding to the merchant txn " + merchantTxnId);

                Refund refund = new Refund();
                refund.setOrderId(orderId);
                refund.setPaymentId(payment.getId());
                refund.setGatewayId(payment.getGatewayId());
                refund.setAmount(amount);
                refund.setAttempts(0);
                return refundHandler.createRefund(refund);
    }
        
    @Override
    public synchronized boolean capturePayment(long merchantTxnId, boolean isDigital) throws PaymentException, TException {
        logger.info("Attempting to capture payment corresponding to our transaction " + merchantTxnId + " and Is Digital is:" + isDigital);
        List<in.shop2020.payment.domain.Payment> payments;
        if(isDigital){
                payments = paymentHandler.getPaymentForRechargeTxn(merchantTxnId);
        }else{
                payments = paymentHandler.getPaymentForTxn(merchantTxnId);
        }
        if(payments ==null || payments.isEmpty())
            throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);
        
        in.shop2020.payment.domain.Payment payment = payments.get(0);
        switch(PaymentStatus.findByValue(payment.getStatus())){
        case PENDING:
            logger.error("Attempt to capture a non-authorized payment");
            return false;
        case INIT:
            logger.warn("Attempt to capture a non-authorized payment");
            return false;
        case AUTHORIZED:
        case CAPTURE_IN_PROCESS:
            //Actual work to be done in this case. Let the call proceed.
            break;
        case PROVISIONALLY_CAPTURED:
            logger.info("Attempting to capture a payment that is provisonally captured but we can let the client proceed.");
            return true;
        case SUCCESS:
            logger.warn("Attempting to capture an already captured payment but we can let the client proceed.");
            return true;
        case FAILED:
            logger.error("Attempting to capture a failed payment");
            return false;
        }
        
        long gatewayId = payment.getGatewayId();
        
        if(gatewayId == HDFC_GATEWAY_ID){
            //Capture and update the HDFC payment
            return captureAndUpdateHdfcPayment(payment);
        } else if (gatewayId == EBS_GATEWAY_ID){
            //Capture and update the EBS payment
            return captureAndUpdateEbsPayment(payment);
        } else if (HDFC_EMI_GATEWAY_IDS.contains(gatewayId)){
            //Capture and update the HDFC EMI payment
            return captureAndUpdateHdfcEmiPayment(payment);
        } else if (gatewayId == EBAY_GATEWAY_ID || gatewayId == SNAPDEAL_GATEWAY_ID) {
                return true;
        } else if (gatewayId == PAYU_GATEWAY_ID) {
                return captureAndUpdatePayuPayment(payment);
        }
        
        logger.error("We have an authorized payment from unknown gateway: " + gatewayId);
        return false;
    }
    
    private boolean captureAndUpdatePayuPayment(in.shop2020.payment.domain.Payment payment)  throws PaymentException{
        long merchantPaymentId = payment.getId();
        
        logger.info("Capturing Payu payment with id: " + merchantPaymentId);
        Map<String, String> attrMap = new HashMap<String, String>();
        Map<String, String> captureResult = PayuPaymentHandler.captureTransaction(merchantPaymentId + "", payment.getGatewayTxnId());
        if(captureResult.containsKey(IPaymentHandler.ERROR)){
            payment.setDescription(captureResult.get(IPaymentHandler.ERROR));
            payment.setErrorCode(captureResult.get(IPaymentHandler.ERR_CODE));
            payment.setErrorTimestamp(new Date());
                if(captureResult.get(IPaymentHandler.ERR_CODE).equals(Errors.CAPTURE_FAILURE)) {
                payment.setStatus(PaymentStatus.FAILED.getValue());
                paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
                } else {
                        logger.error("Capture attempt failed for Payu payment with id: " + merchantPaymentId);
                        payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());
                        paymentHandler.updatePayment(payment, attrMap);
                        throw new PaymentException(106, captureResult.get(IPaymentHandler.ERROR));
                }
                return false;
        } else {
                logger.info("Capture attempt successful for HDFC payment with id: " + merchantPaymentId);
            payment.setDescription("Payment Captured");
            payment.setGatewayTxnStatus("captured");
            payment.setStatus(PaymentStatus.SUCCESS.getValue());
            payment.setSuccessTimestamp(new Date());           
            payment.setReferenceCode(captureResult.get(PayuPaymentHandler.REF_NO));
            attrMap.put(IPaymentHandler.CAPTURE_TXN_ID, captureResult.get(IPaymentHandler.CAPTURE_TXN_ID));
            attrMap.put(IPaymentHandler.CAPTURE_REF_ID, captureResult.get(IPaymentHandler.CAPTURE_REF_ID));
            attrMap.put(IPaymentHandler.CAPTURE_AUTH_ID, captureResult.get(IPaymentHandler.CAPTURE_AUTH_ID));

            SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            attrMap.put(HdfcPaymentHandler.CAPTURE_TIME, captureTimeDateFormatter.format(new Date()));

            paymentHandler.updatePayment(payment, attrMap);
            return true;
        }
        }

        @Override
    public boolean partiallyCapturePayment(long merchantTxnId, double amount, String xferBy, String xferTxnId, long xferDate) throws PaymentException, TException {
        logger.info("Attempting to partially capture payment corresponding to our transaction " + merchantTxnId);
        List<in.shop2020.payment.domain.Payment> payments = paymentHandler.getPaymentForTxn(merchantTxnId);
        if(payments ==null || payments.isEmpty())
            throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);
        
        in.shop2020.payment.domain.Payment payment = payments.get(0);
        switch(PaymentStatus.findByValue(payment.getStatus())){
        case PENDING:
            // COD payments lie in this state before settlement.
        case INIT:
        case PARTIALLY_CAPTURED:
        case AUTHORIZED:
            // COD payments would not be in this state but we are processing
            // payments in this state as well for forward compatibility since
            // someday we'd want to be able to capture authorized CC payments
            // partially.
            break;
        case SUCCESS:
            logger.warn("Attempting to capture an already captured payment but we can let the client proceed.");
            return true;
        case FAILED:
            logger.error("Attempting to capture a failed payment");
            return false;
        }
        SimpleDateFormat mysqlDateFormatter = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
        String xferDateStr = mysqlDateFormatter.format(new Date(xferDate));

        return settleAndUpdateCodPayment(payment, amount, xferBy, xferTxnId, xferDateStr);
    }
    
    /**
     * Capture the HDFC payment represented by the given payment object. If the
     * capture attempt is not successful, we mark this payment as failed. We
     * don't retry or anything. We'll add the support of multiple attempts later
     * on.
     * 
     * @param payment The payment which has to be captured.
     * @return True if the payment attempt is successful, false if not.
     * @throws PaymentException 
     */
    private boolean captureAndUpdateHdfcPayment(in.shop2020.payment.domain.Payment payment) throws PaymentException {
        long merchantPaymentId = payment.getId();
        logger.info("Capturing HDFC payment with id: " + merchantPaymentId);
        Map<String, String> captureResult = HdfcPaymentHandler.capturePayment(payment);
        String captureStatus = captureResult.get(IPaymentHandler.STATUS);
        String gatewayStatus = captureResult.get(IPaymentHandler.GATEWAY_STATUS);

        Map<String, String> attrMap = new HashMap<String, String>();
        if (!captureStatus.trim().equals("0") 
                || !HdfcPaymentReturnStatus.CAPTURED.value().equals(gatewayStatus)) {
            // Failure
            logger.error("Capture attempt failed for HDFC payment with id: " + merchantPaymentId);
            String description = captureResult.get(IPaymentHandler.ERROR);
            String errorCode = captureResult.get(IPaymentHandler.ERR_CODE);

            payment.setDescription(description);
            payment.setErrorCode(errorCode);
            payment.setErrorTimestamp(new Date());

            if (IPaymentHandler.Errors.CONN_FAILURE.code.equals(errorCode)) {
                payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());
                paymentHandler.updatePayment(payment, attrMap);
                throw new PaymentException(106, "Could not capture due to connection issue");
            }
            else {
                payment.setStatus(PaymentStatus.FAILED.getValue());
                paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
            }

            return false;
        } else {
            // Success
            logger.info("Capture attempt successful for HDFC payment with id: " + merchantPaymentId);
            payment.setDescription("Payment Captured");
            payment.setGatewayTxnStatus(gatewayStatus);
            payment.setStatus(PaymentStatus.SUCCESS.getValue());
            payment.setSuccessTimestamp(new Date());           
            
            attrMap.put(IPaymentHandler.CAPTURE_TXN_ID, captureResult.get(IPaymentHandler.CAPTURE_TXN_ID));
            attrMap.put(IPaymentHandler.CAPTURE_REF_ID, captureResult.get(IPaymentHandler.CAPTURE_REF_ID));
            attrMap.put(IPaymentHandler.CAPTURE_AUTH_ID, captureResult.get(IPaymentHandler.CAPTURE_AUTH_ID));

            SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            attrMap.put(HdfcPaymentHandler.CAPTURE_TIME, captureTimeDateFormatter.format(new Date()));

            paymentHandler.updatePayment(payment, attrMap);
            return true;
          }
    }

    /**
     * Capture the HDFC EMI payment represented by the given payment object. If
     * the capture attempt is not successful, we mark this payment as failed. We
     * don't retry or anything. We'll add the support of multiple attempts later
     * on.
     * 
     * @param payment
     *            The payment which has to be captured.
     * @return True if the payment attempt is successful, false if not.
     * @throws PaymentException 
     */
    private boolean captureAndUpdateHdfcEmiPayment(in.shop2020.payment.domain.Payment payment) throws PaymentException{
        long merchantPaymentId = payment.getId();
        logger.info("Capturing HDFC payment with id: " + merchantPaymentId);
        Map<String, String> captureResult = HdfcEmiPaymentHandler.capturePayment(payment);
        String captureStatus = captureResult.get(IPaymentHandler.STATUS);
        String gatewayStatus = captureResult.get(IPaymentHandler.GATEWAY_STATUS);

        Map<String, String> attrMap = new HashMap<String, String>();
        if (!captureStatus.trim().equals("0") 
                || !HdfcPaymentReturnStatus.CAPTURED.value().equals(gatewayStatus)) {
            // Failure
            logger.error("Capture attempt failed for HDFC payment with id: " + merchantPaymentId);
            String description = captureResult.get(IPaymentHandler.ERROR);
            String errorCode = captureResult.get(IPaymentHandler.ERR_CODE);

            payment.setDescription(description);
            payment.setErrorCode(errorCode);
            payment.setErrorTimestamp(new Date());                

            // Not marking payments as failed in case of connection issues
            if (IPaymentHandler.Errors.CONN_FAILURE.code.equals(errorCode)) {
                payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());
                paymentHandler.updatePayment(payment, attrMap);
                throw new PaymentException(106, "Could not capture due to connection issue");
            }
            else {
                payment.setStatus(PaymentStatus.FAILED.getValue());
                paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
            }

            return false;
        } else {
            // Success
            logger.info("Capture attempt successful for HDFC payment with id: " + merchantPaymentId);
            payment.setDescription("Payment Captured");
            payment.setGatewayTxnStatus(gatewayStatus);
            payment.setStatus(PaymentStatus.SUCCESS.getValue());
            payment.setSuccessTimestamp(new Date());
            
            attrMap.put(IPaymentHandler.CAPTURE_TXN_ID, captureResult.get(IPaymentHandler.CAPTURE_TXN_ID));
            attrMap.put(IPaymentHandler.CAPTURE_REF_ID, captureResult.get(IPaymentHandler.CAPTURE_REF_ID));
            attrMap.put(IPaymentHandler.CAPTURE_AUTH_ID, captureResult.get(IPaymentHandler.CAPTURE_AUTH_ID));

            SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            attrMap.put(HdfcPaymentHandler.CAPTURE_TIME, captureTimeDateFormatter.format(new Date()));

            paymentHandler.updatePayment(payment, attrMap);
            return true;
          }
    }
    
    /**
     * Capture the EBS payment represented by the given payment object. If the
     * capture attempt is not successful, we mark this payment as failed. We
     * don't retry or anything. We'll add the support of multiple attempts later
     * on.
     * 
     * @param payment The payment which has to be captured.
     * @return True if the payment attempt is successful, false if not.
     * @throws PaymentException 
     */
    private boolean captureAndUpdateEbsPayment(in.shop2020.payment.domain.Payment payment) throws PaymentException{
        Map<String, String> captureResult = EbsPaymentHandler.capturePayment(payment);
        String captureStatus = captureResult.get(EbsPaymentHandler.STATUS);
        for(String key : captureResult.keySet()) {
            logger.info("key : " + key + " value : " + captureResult.get(key));
        }
        
        logger.info("capture status : " + captureStatus);
        
        Map<String, String> attrMap = new HashMap<String, String>();
        if("".equals(captureStatus)){
            //Failure
            logger.error("Capture attempt failed for EBS payment with id: " + payment.getId());
            String description = captureResult.get(EbsPaymentHandler.ERROR);
            String errorCode = captureResult.get(EbsPaymentHandler.ERR_CODE);

            payment.setDescription(description);
            payment.setErrorCode(errorCode);
            payment.setErrorTimestamp(new Date());

            if (IPaymentHandler.Errors.CONN_FAILURE.code.equals(errorCode)) {
                payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());            
                paymentHandler.updatePayment(payment, attrMap);
                throw new PaymentException(106, "Could not capture due to connection issue");
            }
            else {
                payment.setStatus(PaymentStatus.FAILED.getValue());            
                paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
            }

            return false;
        }else{
            //Success
            logger.info("Capture attempt successful for EBS payment with id: " + payment.getId());
            payment.setGatewayTxnStatus(captureStatus);
            payment.setStatus(PaymentStatus.SUCCESS.getValue());
            payment.setSuccessTimestamp(new Date());
            
            attrMap.put(IPaymentHandler.CAPTURE_TXN_ID, captureResult.get(IPaymentHandler.CAPTURE_TXN_ID));
            attrMap.put(IPaymentHandler.CAPTURE_TIME, captureResult.get(IPaymentHandler.CAPTURE_TIME));
            paymentHandler.updatePayment(payment, attrMap);
            return true;
        }
    }


    /**
     * Refund the HDFC payment represented by the given payment object. If the
     * refund attempt is not successful, will not any action.
     * 
     * @param payment The payment which has to be captured.
     * @amount amount to be refunded
     * @return True if the payment attempt is successful, false if not.
     * @throws PaymentException 
     */
    private boolean refundHdfcPayment(in.shop2020.payment.domain.Payment payment, double amount) throws PaymentException {
        long merchantPaymentId = payment.getId();
        logger.info("Refunding HDFC payment with id: " + merchantPaymentId);
        Map<String, String> refundResult = HdfcPaymentHandler.refundPayment(payment, amount);
        String refundStatus = refundResult.get(IPaymentHandler.STATUS);
        String gatewayStatus = refundResult.get(IPaymentHandler.GATEWAY_STATUS);

        Map<String, String> attrMap = new HashMap<String, String>();
        if (!refundStatus.trim().equals("0") 
                || !HdfcPaymentReturnStatus.CAPTURED.value().equals(gatewayStatus)) {
                
                logger.error("Refund attempt failed for HDFC payment with id: " + merchantPaymentId);
            String description = refundResult.get(IPaymentHandler.ERROR);
            String errorCode = refundResult.get(IPaymentHandler.ERR_CODE);

            payment.setDescription(description);
            payment.setErrorCode(errorCode);
            payment.setErrorTimestamp(new Date());

            if (IPaymentHandler.Errors.CONN_FAILURE.code.equals(errorCode)) {
                //payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());
                //paymentHandler.updatePayment(payment, attrMap);
                throw new PaymentException(106, "Could not capture due to connection issue. Try Later");
            }
            else {
//                payment.setStatus(PaymentStatus.FAILED.getValue());
//                paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
            }

            return false;
        } else {
            // Success
            logger.info("Refund attempt successful for HDFC payment with id: " + merchantPaymentId);
            payment.setDescription("Payment Refunded");
            payment.setGatewayTxnStatus(gatewayStatus);
            payment.setStatus(PaymentStatus.REFUNDED.getValue());    
            payment.setRefundAmount(amount);
            
            attrMap.put(IPaymentHandler.REFUND_TXN_ID, refundResult.get(IPaymentHandler.REFUND_TXN_ID));
            attrMap.put(IPaymentHandler.REFUND_REF_ID, refundResult.get(IPaymentHandler.REFUND_REF_ID));
            attrMap.put(IPaymentHandler.REFUND_AUTH_ID, refundResult.get(IPaymentHandler.REFUND_AUTH_ID));
            attrMap.put(IPaymentHandler.REFUND_AMNT, refundResult.get(IPaymentHandler.REFUND_AMNT));
            
            SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            attrMap.put(HdfcPaymentHandler.REFUND_TIME, captureTimeDateFormatter.format(new Date()));

            paymentHandler.updatePayment(payment, attrMap);
            return true;
          }
    }


    /**
     * Refund the HDFC EMI payment represented by the given payment object. If
     * the capture attempt is not successful, we will not do anything.
     * 
     * @param payment
     *            The payment which has to be captured.
     * @return True if the payment attempt is successful, false if not.
     * @throws PaymentException 
     */
    private boolean refundHdfcEmiPayment(in.shop2020.payment.domain.Payment payment, double amount) throws PaymentException{
        long merchantPaymentId = payment.getId();
        logger.info("Refunding HDFC payment with id: " + merchantPaymentId);
        Map<String, String> refundResult = HdfcEmiPaymentHandler.refundPayment(payment, amount);
        String refundStatus = refundResult.get(IPaymentHandler.STATUS);
        String gatewayStatus = refundResult.get(IPaymentHandler.GATEWAY_STATUS);

        Map<String, String> attrMap = new HashMap<String, String>();
        if (!refundStatus.trim().equals("0") 
                || !HdfcPaymentReturnStatus.CAPTURED.value().equals(gatewayStatus)) {
            // Failure
            logger.error("Refund attempt failed for HDFC payment with id: " + merchantPaymentId);
            String description = refundResult.get(IPaymentHandler.ERROR);
            String errorCode = refundResult.get(IPaymentHandler.ERR_CODE);

            payment.setDescription(description);
            payment.setErrorCode(errorCode);
            payment.setErrorTimestamp(new Date());                

            // Not marking payments as failed in case of connection issues
            if (IPaymentHandler.Errors.CONN_FAILURE.code.equals(errorCode)) {
             //   payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());
             //   paymentHandler.updatePayment(payment, attrMap);
                throw new PaymentException(106, "Could not capture due to connection issue");
            }
            else {
              //  payment.setStatus(PaymentStatus.FAILED.getValue());
              //  paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
            }

            return false;
        } else {
            // Success
            logger.info("Refund attempt successful for HDFC payment with id: " + merchantPaymentId);
            payment.setDescription("Payment Refunded");
            payment.setGatewayTxnStatus(gatewayStatus);
            payment.setStatus(PaymentStatus.REFUNDED.getValue());           
            payment.setRefundAmount(amount);
            
            attrMap.put(IPaymentHandler.REFUND_TXN_ID, refundResult.get(IPaymentHandler.REFUND_TXN_ID));
            attrMap.put(IPaymentHandler.REFUND_REF_ID, refundResult.get(IPaymentHandler.REFUND_REF_ID));
            attrMap.put(IPaymentHandler.REFUND_AUTH_ID, refundResult.get(IPaymentHandler.REFUND_AUTH_ID));
            attrMap.put(IPaymentHandler.REFUND_AMNT, refundResult.get(IPaymentHandler.REFUND_AMNT));
            
            SimpleDateFormat captureTimeDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            attrMap.put(HdfcPaymentHandler.REFUND_TIME, captureTimeDateFormatter.format(new Date()));

            paymentHandler.updatePayment(payment, attrMap);
            return true;
          }
    }
    
    /**
     * Refund the EBS payment represented by the given payment object. If the
     * capture attempt is not successful, we will ignore. We don't retry or anything. 
     * We'll add the support of multiple attempts later on.
     * 
     * @param payment The payment which has to be captured.
     * @amount Amount to be refunded
     * @return True if the payment attempt is successful, false if not.
     * @throws PaymentException 
     */
    private boolean refundEbsPayment(in.shop2020.payment.domain.Payment payment, double amount) throws PaymentException{
        Map<String, String> refundResult = EbsPaymentHandler.refundPayment(payment, amount);
        String refundStatus = refundResult.get(EbsPaymentHandler.STATUS);
        
        Map<String, String> attrMap = new HashMap<String, String>();
        if("".equals(refundStatus)){
            //Failure
            logger.error("Refund attempt failed for EBS payment with id: " + payment.getId());
            String description = refundResult.get(EbsPaymentHandler.ERROR);
            String errorCode = refundResult.get(EbsPaymentHandler.ERR_CODE);

            payment.setDescription(description);
            payment.setErrorCode(errorCode);
            payment.setErrorTimestamp(new Date());

            if (IPaymentHandler.Errors.CONN_FAILURE.code.equals(errorCode)) {
//                payment.setStatus(PaymentStatus.CAPTURE_IN_PROCESS.getValue());            
//                paymentHandler.updatePayment(payment, attrMap);
                throw new PaymentException(106, "Could not capture due to connection issue");
            }
            else {
//                payment.setStatus(PaymentStatus.FAILED.getValue());            
//                paymentHandler.updatePayment(payment, attrMap);
                persistPaymentRequiringExtraProcessing(payment, ExtraPaymentProcessingType.FAILED_PAYMENTS);
            }

            return false;
        }else{
            //Success
            logger.info("Refund attempt successful for EBS payment with id: " + payment.getId());
            payment.setGatewayTxnStatus(refundStatus);
            payment.setStatus(PaymentStatus.REFUNDED.getValue());
            payment.setRefundAmount(amount);
            
            attrMap.put(IPaymentHandler.REFUND_TXN_ID, refundResult.get(IPaymentHandler.REFUND_TXN_ID));
            attrMap.put(IPaymentHandler.REFUND_TIME, refundResult.get(IPaymentHandler.REFUND_TIME));
            paymentHandler.updatePayment(payment, attrMap);
            return true;
        }
    }
    
    
    /**
     * Updates the settlement details of COD payments. Sets payment status as
     * either PARTIALLY CAPTURED or SUCCESS depending on whether the complete
     * amount has been captured. Other parameters are set as attributes.
     * 
     * @param payment
     *            The payment which needs to be updated.
     * @param amount
     *            Amount that has been captured.
     * @param xferBy
     *            Entity which transferred the money.
     * @param xferTxnId
     *            Transaction Id of the transfer.
     * @param xferDateStr
     *            Date on which the transfer took place.
     * @return true if the payment details were updated successfully.
     * @throws PaymentException
     *             if the captured amount will become more than the actual
     *             amount after this update.
     */
    private boolean settleAndUpdateCodPayment(in.shop2020.payment.domain.Payment payment, double amount, String xferBy, String xferTxnId, String xferDateStr) throws PaymentException{
        Map<String, String> attrMap = payment.getAttributeMap();
        
        double captureAmount = 0;
        String captureAmntStr = attrMap.get(IPaymentHandler.CAPTURE_AMNT);
        if(captureAmntStr != null)
            captureAmount = Double.parseDouble(captureAmntStr);
        captureAmount += amount;
        // If capture amount higher than payment amount by more than 50 paisa,
        // there is some issue and we should raise exception.
        if(captureAmount - payment.getAmount() > 0.5){
                throw new PaymentException(105, "We've got a settlement request for an amount which is more than the transaction value.");
        }

        // If capture amount differs from payment amount by less than 50 paisa, lets mark the payment as successful. 
        // Else we can safely assume there will be some more orders for the payment, leading to make the payment as partially captured.
        if(Math.abs(captureAmount - payment.getAmount()) < 0.5){
                payment.setStatus(PaymentStatus.SUCCESS.getValue());
        }else {
                payment.setStatus(PaymentStatus.PARTIALLY_CAPTURED.getValue());   
        }
        payment.setSuccessTimestamp(new Date());
        attrMap.put(IPaymentHandler.CAPTURE_AMNT, captureAmount + "");
        attrMap.put(IPaymentHandler.XFER_TXN_ID, xferTxnId);
        attrMap.put(IPaymentHandler.XFER_TXN_DATE, xferDateStr);
        attrMap.put(IPaymentHandler.XFER_BY, xferBy);
        paymentHandler.updatePayment(payment, attrMap);
        return true;
    }
    
    /**
     * Creates a list of thrift payment objects corresponding to a list of
     * payment data objects.
     * 
     * @param daoPayments
     *            A list of payment DAO.
     * @return A list of Thrift payment objects.
     */
    private List<Payment> getThriftPayments(List<in.shop2020.payment.domain.Payment> daoPayments){
        
        List<Payment> payments = new ArrayList<Payment>();
        for(in.shop2020.payment.domain.Payment payment : daoPayments){
            payments.add(payment.getThriftPayment());
        }
        return payments;
    }

    /**
     * Creates a list of thrift payment gateway objects corresponding to a list of
     * payment gateway data objects.
     * 
     * @param daoPaymentGateways
     *            A list of payment gateway DAO.
     * @return A list of Thrift payment gateway objects.
     */
    private List<PaymentGateway> getThriftPaymentGateways(List<in.shop2020.payment.domain.PaymentGateway> daoPaymentGateways){
        
        List<PaymentGateway> paymentGateways = new ArrayList<PaymentGateway>();
        for(in.shop2020.payment.domain.PaymentGateway paymentGateway : daoPaymentGateways){
            paymentGateways.add(paymentGateway.getThriftPaymentGateway());
        }
        return paymentGateways;
    }

        @Override
        public boolean isAlive() {
            try {
            return !paymentGatewayHandler.getActivePaymentGateways().isEmpty();
        } catch (Exception e) {
            logger.error("Could not fetch payment gateways", e);
        }

        return false;
        }
        
           
    @Override
    public void closeSession() throws TException {
        // TODO Auto-generated method stub      
    }

    @Override
    public List<Long> getPaymentsRequiringExtraProcessing (
            ExtraPaymentProcessingType category) throws TException {
        return paymentRequiringExtraProcessingHandler.getPaymentIds(category);
    }

    @Override
    public void markPaymentAsProcessed(long paymentId,
            ExtraPaymentProcessingType category) throws TException {
        paymentRequiringExtraProcessingHandler.delete(paymentId, category);
    }

        @Override
        public PaymentStatus getPaymentStatusAtGateway(long merchantTxnId, double amount, boolean isDigital) throws PaymentException, TException {
                logger.info("Attempting to get status of payment of amount " + amount + " corresponding to our transaction " + merchantTxnId);
                List<in.shop2020.payment.domain.Payment> payments;
                if(isDigital){
                        payments = paymentHandler.getPaymentForRechargeTxn(merchantTxnId);
                }else{
                        payments = paymentHandler.getPaymentForTxn(merchantTxnId);
                }
                if(payments ==null || payments.isEmpty())
                    throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);
                
                if(payments ==null || payments.isEmpty())
                    throw new PaymentException(104, "No payments found corresponding to the merchant txn " + merchantTxnId);

                in.shop2020.payment.domain.Payment payment = payments.get(0);
                long gatewayId = payment.getGatewayId();
                
                if(gatewayId == HDFC_GATEWAY_ID){
                    return HdfcPaymentHandler.validateHdfcPayment(payment, amount);
                }else if (gatewayId == EBS_GATEWAY_ID){
                    //return validateEbsPayment(payment, amount);
                } 
                else if (HDFC_EMI_GATEWAY_IDS.contains(gatewayId)){
                    //return validateHdfcEmiPayment(payment, amount);
                }
                
                logger.error("We have an payment from unknown gateway: " + gatewayId);
                return PaymentStatus.INIT;
            }

        
}