Subversion Repositories SmartDukaan

Rev

Rev 3649 | Rev 4008 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package in.shop2020.payment.service.handler;

import in.shop2020.crm.CRMService.Client;
import in.shop2020.payment.domain.Refund;
import in.shop2020.payment.handler.PaymentGatewayHandler;
import in.shop2020.payment.handler.PaymentHandler;
import in.shop2020.payment.handler.RefundHandler;
import in.shop2020.payments.Attribute;
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.CRMClient;

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

import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class PaymentServiceHandler implements Iface {
    
    private static Logger logger = LoggerFactory.getLogger(PaymentServiceHandler.class);
    
    /**
     * Enum of all statuses that can be returned by the HDFC gateway
     * 
     * @author Chandranshu
     * 
     */
    private 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");
        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 HDFC_EMI_GATEWAY_ID = 5;
        
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        PaymentHandler paymentHandler = (PaymentHandler) context.getBean("paymentHandler");
        PaymentGatewayHandler paymentGatewayHandler = (PaymentGatewayHandler) context.getBean("paymentGatewayHandler");
        RefundHandler refundHandler = (RefundHandler) context.getBean("refundHandler");

        @Override
        public long createPayment(long userId, double amount, long gatewayId, long txnId) 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());

                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 PaymentGateway getPaymentGateway(long id) throws PaymentException, TException {
            logger.info("Getting payment gateway with id:" + id);
                return paymentGatewayHandler.getPaymentGateway(id).getThriftPaymentGateway();
        }

        @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 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());
                            createTicketForFailedPayment(payment);
                        }
                }
                
                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;
        }

        /**
         * Creates a ticket in CRM tool for each customer whose payment got failed.
         * Later, CRM agents follow-up with the users and try making a sale.
         *
         * We swallow the exceptions raised and just log them at error level. This is
         * done to not make Website unaffected by any CRM related issues.
         *
         * @param payment  the payment object that failed.
         */
        private void createTicketForFailedPayment(in.shop2020.payment.domain.Payment payment) {
        try {
            Client crmClient = new CRMClient().getClient();

            // This call is aync (oneway). This ensures that website response
            // is sent in time.
            crmClient.processPaymentFailure(payment.getUserId());
        } catch (TTransportException e) {
            logger.error("Could not create CRM client", e);
        } catch (TException e) {
            logger.error("Could not process paymentId: " + 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) 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);
                } 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) 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);
        } catch (Exception e) {
            throw new PaymentException(102, "Error while initiliazing payment. Check service log for more details.");
        }
        return redirectURL;
    }
        
        @Override
    public long createRefund(long orderId, long merchantTxnId, double amount) throws PaymentException, TException{
            logger.info("Attempting to create a refund for order: " + orderId);
                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 boolean capturePayment(long merchantTxnId) throws PaymentException, TException {
        logger.info("Attempting to 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:
            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:
            //Actual work to be done in this case. Let the call proceed.
            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;
        }
        
        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 (gatewayId == HDFC_EMI_GATEWAY_ID){
            //Capture and update the HDFC EMI payment
            return captureAndUpdateHdfcEmiPayment(payment);
        }
        
        logger.error("We have an authorized payment from unknown gateway: " + gatewayId);
        return false;
    }
    
    @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.
     */
    private boolean captureAndUpdateHdfcPayment(in.shop2020.payment.domain.Payment payment){
        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.setStatus(PaymentStatus.FAILED.getValue());
            payment.setErrorTimestamp(new Date());
            paymentHandler.updatePayment(payment, attrMap);
            createTicketForFailedPayment(payment);
            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.
     */
    private boolean captureAndUpdateHdfcEmiPayment(in.shop2020.payment.domain.Payment payment){
        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.setStatus(PaymentStatus.FAILED.getValue());
            payment.setErrorTimestamp(new Date());
            paymentHandler.updatePayment(payment, attrMap);
            createTicketForFailedPayment(payment);
            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.
     */
    private boolean captureAndUpdateEbsPayment(in.shop2020.payment.domain.Payment payment){
        Map<String, String> captureResult = EbsPaymentHandler.capturePayment(payment);
        String captureStatus = captureResult.get(EbsPaymentHandler.STATUS);
        
        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.setStatus(PaymentStatus.FAILED.getValue());
            payment.setErrorTimestamp(new Date());
            paymentHandler.updatePayment(payment, attrMap);
            createTicketForFailedPayment(payment);
            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;
        }
    }

    /**
     * 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(captureAmount > payment.getAmount())
            throw new PaymentException(105, "We've got a settlement request for an amount which is more than the transaction value.");

        if(captureAmount < payment.getAmount()){
            payment.setStatus(PaymentStatus.PARTIALLY_CAPTURED.getValue());
        } else {
            payment.setStatus(PaymentStatus.SUCCESS.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;
    }

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