com.konakartadmin.modules.payment.authorizenet.AdminPayment.java Source code

Java tutorial

Introduction

Here is the source code for com.konakartadmin.modules.payment.authorizenet.AdminPayment.java

Source

//
// (c) 2006 DS Data Systems UK Ltd, All rights reserved.
//
// DS Data Systems and KonaKart and their respective logos, are 
// trademarks of DS Data Systems UK Ltd. All rights reserved.
//
// The information in this document is free software; you can redistribute 
// it and/or modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This software 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
// Lesser General Public License for more details.
//

package com.konakartadmin.modules.payment.authorizenet;

import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.torque.TorqueException;

import com.anet.api.ARBAPI;
import com.anet.api.ARBCreditCard;
import com.anet.api.ARBCustomer;
import com.anet.api.ARBMessage;
import com.anet.api.ARBNameAndAddress;
import com.anet.api.ARBOrder;
import com.anet.api.ARBPayment;
import com.anet.api.ARBPaymentSchedule;
import com.anet.api.ARBSubscription;
import com.anet.api.util.BasicXmlDocument;
import com.konakart.app.NameValue;
import com.konakart.app.PaymentOptions;
import com.konakart.appif.CreditCardIf;
import com.konakart.bl.modules.ordertotal.OrderTotalMgr;
import com.konakart.util.KKConstants;
import com.konakart.util.PrettyXmlPrinter;
import com.konakartadmin.app.AdminCountry;
import com.konakartadmin.app.AdminCurrency;
import com.konakartadmin.app.AdminIpnHistory;
import com.konakartadmin.app.AdminOrder;
import com.konakartadmin.app.AdminOrderStatusHistory;
import com.konakartadmin.app.AdminOrderTotal;
import com.konakartadmin.app.AdminOrderUpdate;
import com.konakartadmin.app.AdminPaymentSchedule;
import com.konakartadmin.app.AdminSubscription;
import com.konakartadmin.app.KKAdminException;
import com.konakartadmin.appif.KKAdminIf;
import com.konakartadmin.modules.payment.AdminBasePayment;
import com.konakartadmin.modules.payment.AdminPaymentIf;
import com.workingdogs.village.DataSetException;

/**
 * Class used to communicate with the payment gateway from the admin app
 */
public class AdminPayment extends AdminBasePayment implements AdminPaymentIf {
    /** the log */
    protected static Log log = LogFactory.getLog(AdminPayment.class);

    private static String code = "authorizenet";

    // Configuration Keys

    /**
     * The Authorize.Net zone, if greater than zero, should reference a GeoZone. If the
     * DeliveryAddress of the order isn't within that GeoZone, then we throw an exception
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_ZONE = "MODULE_PAYMENT_AUTHORIZENET_ZONE";

    /**
     * The order for displaying this payment gateway on the UI
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_SORT_ORDER = "MODULE_PAYMENT_AUTHORIZENET_SORT_ORDER";

    /**
     * The Authorize.Net Url used to POST the payment request.
     * "https://secure.authorize.net/gateway/transact.dll"
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_REQUEST_URL = "MODULE_PAYMENT_AUTHORIZENET_REQUEST_URL";

    /**
     * The Authorize.Net Url used to POST the Automated Recurring Billing request.
     * "https://apitest.authorize.net/xml/v1/request.api"
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_ARB_REQUEST_URL = "MODULE_PAYMENT_AUTHORIZENET_ARB_REQUEST_URL";

    /**
     * The Authorize.Net API Login ID for this installation
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_LOGIN = "MODULE_PAYMENT_AUTHORIZENET_LOGIN";

    /**
     * The Authorize.Net transaction key for this installation
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_TXNKEY = "MODULE_PAYMENT_AUTHORIZENET_TXNKEY";

    /**
     * Used to make test transactions
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_TESTMODE = "MODULE_PAYMENT_AUTHORIZENET_TESTMODE";

    /**
     * To show CVV field
     */
    private final static String MODULE_PAYMENT_AUTHORIZENET_SHOW_CVV = "MODULE_PAYMENT_AUTHORIZENET_SHOW_CVV";

    // Authorize.net constants for response
    private static final int respCodePosition = 1;

    private static final int respTextPosition = 4;

    private static final int txnIdPosition = 7;

    private static final String approved = "1";

    private static final String declined = "2";

    private static final String error = "3";

    // Return codes and descriptions
    private static final int RET0 = 0;

    private static final String RET0_DESC = "Transaction OK";

    private static final int RET1 = -1;

    private static final String RET1_DESC = "There was an unexpected Gateway Response. Response = ";

    private static final int RET2 = -2;

    private static final String RET2_DESC = "Transaction Error";

    // Order history comments. These comments are associated with the order.
    private static final String ORDER_HISTORY_COMMENT_OK = "Authorize.net payment successful. Authorize.net TransactionId = ";

    private static final String ORDER_HISTORY_COMMENT_KO = "Authorize.net payment not successful. Authorize.net Reply = ";

    /**
     * Constructor
     * 
     * @param eng
     * @throws Exception
     * @throws DataSetException
     * @throws TorqueException
     */
    public AdminPayment(KKAdminIf eng) throws TorqueException, DataSetException, Exception {
        super.init(eng);
    }

    /**
     * This method executes the transaction with the payment gateway. The action attribute of the
     * options object instructs the method as to what transaction should be executed. E.g. It could
     * be a payment or a payment confirmation for a transaction that has already been authorized
     * etc.
     * 
     * @param options
     * @return Returns an array of NameValue objects that may contain any return information
     *         considered useful by the caller.
     * @throws Exception
     */
    public NameValue[] execute(PaymentOptions options) throws Exception {
        checkRequired(options, "PaymentOptions", "options");

        // Read the configuration variables
        ConfigVariables configs = getConfigVariables();

        /*
         * Decide what to do based on the action. We could be using this class to manage
         * subscriptions
         */
        if (options.getAction() == KKConstants.ACTION_CREATE_SUBSCRIPTION) {
            return createSubscription(options);
        } else if (options.getAction() == KKConstants.ACTION_UPDATE_SUBSCRIPTION) {
            return updateSubscription(options);
        } else if (options.getAction() == KKConstants.ACTION_CANCEL_SUBSCRIPTION) {
            return cancelSubscription(options);
        } else if (options.getAction() == KKConstants.ACTION_GET_SUBSCRIPTION_STATUS) {
            return getSubscriptionStatus(options);
        }

        String errorDesc = null;
        String gatewayResult = null;
        String transactionId = null;

        AdminIpnHistory ipnHistory = new AdminIpnHistory();
        ipnHistory.setModuleCode(code);

        AdminOrder order = getAdminOrderMgr().getOrderForOrderId(options.getOrderId());
        if (order == null) {
            throw new KKAdminException("An order does not exist for id = " + options.getOrderId());
        }

        // Get the scale for currency calculations
        AdminCurrency currency = getAdminCurrMgr().getCurrency(order.getCurrencyCode());
        if (currency == null) {
            throw new KKAdminException("A currency does not exist for code = " + order.getCurrencyCode());
        }
        int scale = new Integer(currency.getDecimalPlaces()).intValue();

        List<NameValue> parmList = new ArrayList<NameValue>();

        parmList.add(new NameValue("x_delim_data", "True"));
        parmList.add(new NameValue("x_relay_response", "False"));
        parmList.add(new NameValue("x_login", configs.getAuthorizeNetLoginId()));
        parmList.add(new NameValue("x_tran_key", configs.getAuthorizeNetTxnKey()));
        parmList.add(new NameValue("x_delim_char", ","));
        parmList.add(new NameValue("x_encap_char", ""));
        parmList.add(new NameValue("x_method", "CC"));

        // AuthorizeNet requires details of the final price - inclusive of tax.
        BigDecimal total = null;
        for (int i = 0; i < order.getOrderTotals().length; i++) {
            AdminOrderTotal ot = order.getOrderTotals()[i];
            if (ot.getClassName().equals(OrderTotalMgr.ot_total)) {
                total = ot.getValue().setScale(scale, BigDecimal.ROUND_HALF_UP);
            }
        }

        if (total == null) {
            throw new KKAdminException("An Order Total was not found");
        }

        parmList.add(new NameValue("x_amount", total.toString()));
        parmList.add(new NameValue("x_currency_code", currency.getCode()));
        parmList.add(new NameValue("x_invoice_num", order.getId())); // TODO
        parmList.add(new NameValue("x_test_request", (configs.isTestMode() ? "TRUE" : "FALSE")));

        // Set the billing address

        // Set the billing name. If the name field consists of more than two strings, we take the
        // last one as being the surname.
        String bName = order.getBillingName();
        if (bName != null) {
            String[] names = bName.split(" ");
            int len = names.length;
            if (len >= 2) {
                StringBuffer firstName = new StringBuffer();
                for (int i = 0; i < len - 1; i++) {
                    if (firstName.length() == 0) {
                        firstName.append(names[i]);
                    } else {
                        firstName.append(" ");
                        firstName.append(names[i]);
                    }
                }
                parmList.add(new NameValue("x_first_name", firstName.toString()));
                parmList.add(new NameValue("x_last_name", names[len - 1]));
            }
        }
        parmList.add(new NameValue("x_company", order.getBillingCompany()));
        parmList.add(new NameValue("x_city", order.getBillingCity()));
        parmList.add(new NameValue("x_state", order.getBillingState()));
        parmList.add(new NameValue("x_zip", order.getBillingPostcode()));
        parmList.add(new NameValue("x_phone", order.getCustomerTelephone()));
        parmList.add(new NameValue("x_cust_id", order.getCustomerId()));
        parmList.add(new NameValue("x_email", order.getCustomerEmail()));

        StringBuffer addrSB = new StringBuffer();
        addrSB.append(order.getBillingStreetAddress());
        if (order.getBillingSuburb() != null && order.getBillingSuburb().length() > 0) {
            addrSB.append(", ");
            addrSB.append(order.getBillingSuburb());
        }
        if (addrSB.length() > 60) {
            parmList.add(new NameValue("x_address", addrSB.substring(0, 56) + "..."));
        } else {
            parmList.add(new NameValue("x_address", addrSB.toString()));
        }

        // Country requires the two letter country code
        AdminCountry country = getAdminTaxMgr().getCountryByName(order.getBillingCountry());
        if (country != null) {
            parmList.add(new NameValue("x_country", country.getIsoCode2()));
        }

        /*
         * The following code may be customized depending on the process which could be any of the
         * following:
         */
        int mode = 1;

        if (mode == 1 && options.getCreditCard() != null) {
            /*
             * 1 . Credit card details have been passed into the method and so we use those for the
             * payment.
             */
            parmList.add(new NameValue("x_card_num", options.getCreditCard().getCcNumber()));
            parmList.add(new NameValue("x_card_code", options.getCreditCard().getCcCVV()));
            parmList.add(new NameValue("x_exp_date", options.getCreditCard().getCcExpires()));
            parmList.add(new NameValue("x_type", "AUTH_CAPTURE"));
        } else if (mode == 2) {
            /*
             * 2 . We get the Credit card details from the order since they were encrypted and saved
             * on the order.
             */
            parmList.add(new NameValue("x_card_num", order.getCcNumber()));
            parmList.add(new NameValue("x_card_code", order.getCcCVV()));
            parmList.add(new NameValue("x_exp_date", order.getCcExpires()));
            parmList.add(new NameValue("x_type", "AUTH_CAPTURE"));
        } else if (mode == 3) {
            /*
             * 3.If when the products were ordered through the store front application, an AUTH_ONLY
             * transaction was done instead of an AUTH_CAPTURE transaction and so now we need to do
             * a PRIOR_AUTH_CAPTURE transaction using the transaction id that was saved on the order
             * when the status was set.
             */
            /*
             * Get the transaction id from the status trail of the order. This bit of code will have
             * to be customized because it depends which state was used to capture the transaction
             * id and whether the transaction id is the only string in the comments field or whether
             * it has to be parsed out. The transaction id may also have been stored in an Order
             * custom field in which case it can be retrieved directly from there.
             */
            String authTransId = null;
            if (order.getStatusTrail() != null) {
                for (int i = 0; i < order.getStatusTrail().length; i++) {
                    AdminOrderStatusHistory sh = order.getStatusTrail()[i];
                    if (sh.getOrderStatus().equals("ENTER_THE_STATUS_WHERE_THE_AUTH_TRANS_ID_WAS SAVED")) {
                        authTransId = sh.getComments();
                    }
                }
            }
            if (authTransId == null) {
                throw new KKAdminException(
                        "The authorized transaction cannot be confirmed since a transaction id cannot be found on the order");
            }

            parmList.add(new NameValue("x_type", "PRIOR_AUTH_CAPTURE"));
            parmList.add(new NameValue("x_trans_id", authTransId));
        }

        AdminPaymentDetails pDetails = new AdminPaymentDetails();
        pDetails.setParmList(parmList);
        pDetails.setRequestUrl(configs.getAuthorizeNetRequestUrl());

        // Do the post
        String gatewayResp = postData(pDetails);

        gatewayResp = URLDecoder.decode(gatewayResp, "UTF-8");
        if (log.isDebugEnabled()) {
            log.debug("Unformatted GatewayResp = \n" + gatewayResp);
        }

        // Process the parameters sent in the callback
        StringBuffer sb = new StringBuffer();
        String[] parms = gatewayResp.split(",");
        if (parms != null) {
            for (int i = 0; i < parms.length; i++) {
                String parm = parms[i];
                sb.append(getRespDesc(i + 1));
                sb.append("=");
                sb.append(parm);
                sb.append("\n");

                if (i + 1 == respTextPosition) {
                    errorDesc = parm;
                } else if (i + 1 == respCodePosition) {
                    gatewayResult = parm;
                    ipnHistory.setGatewayResult(parm);
                } else if (i + 1 == txnIdPosition) {
                    ipnHistory.setGatewayTransactionId(parm);
                    transactionId = parm;
                }
            }
        }

        // Put the response in the ipnHistory record
        ipnHistory.setGatewayFullResponse(sb.toString());

        if (log.isDebugEnabled()) {
            log.debug("Formatted Authorize.net response data:");
            log.debug(sb.toString());
        }

        AdminOrderUpdate updateOrder = new AdminOrderUpdate();
        updateOrder.setUpdatedById(order.getCustomerId());

        // Determine whether the request was successful or not.
        if (gatewayResult != null && gatewayResult.equals(approved)) {
            String comment = ORDER_HISTORY_COMMENT_OK + transactionId;
            getAdminOrderMgr().updateOrder(order.getId(), com.konakart.bl.OrderMgr.PAYMENT_RECEIVED_STATUS,
                    comment, /* notifyCustomer */
                    false, updateOrder);

            // Save the ipnHistory
            ipnHistory.setKonakartResultDescription(RET0_DESC);
            ipnHistory.setKonakartResultId(RET0);
            ipnHistory.setOrderId(order.getId());
            ipnHistory.setCustomerId(order.getCustomerId());
            getAdminOrderMgr().insertIpnHistory(ipnHistory);

        } else if (gatewayResult != null && (gatewayResult.equals(declined) || gatewayResult.equals(error))) {
            String comment = ORDER_HISTORY_COMMENT_KO + errorDesc;
            getAdminOrderMgr().updateOrder(order.getId(), com.konakart.bl.OrderMgr.PAYMENT_DECLINED_STATUS,
                    comment, /* notifyCustomer */
                    false, updateOrder);

            // Save the ipnHistory
            ipnHistory.setKonakartResultDescription(RET0_DESC);
            ipnHistory.setKonakartResultId(RET0);
            ipnHistory.setOrderId(order.getId());
            ipnHistory.setCustomerId(order.getCustomerId());
            getAdminOrderMgr().insertIpnHistory(ipnHistory);

        } else {
            /*
             * We only get to here if there was an unknown response from the gateway
             */
            String comment = RET1_DESC + gatewayResult;
            getAdminOrderMgr().updateOrder(order.getId(), com.konakart.bl.OrderMgr.PAYMENT_DECLINED_STATUS,
                    comment, /* notifyCustomer */
                    false, updateOrder);

            // Save the ipnHistory
            ipnHistory.setKonakartResultDescription(RET1_DESC + gatewayResult);
            ipnHistory.setKonakartResultId(RET1);
            ipnHistory.setOrderId(order.getId());
            ipnHistory.setCustomerId(order.getCustomerId());
            getAdminOrderMgr().insertIpnHistory(ipnHistory);
        }

        return new NameValue[] { new NameValue("ret", "0") };
    }

    /**
     * Get the configuration variables
     * 
     * @return Returns an object containing the values of the configuration variables
     * 
     * @throws Exception
     * @throws DataSetException
     * @throws TorqueException
     */
    public ConfigVariables getConfigVariables() throws TorqueException, DataSetException, Exception {
        ConfigVariables configs = new ConfigVariables();

        String confVal = getAdminConfigMgr().getConfigurationValue(MODULE_PAYMENT_AUTHORIZENET_REQUEST_URL);
        if (confVal == null) {
            throw new KKAdminException(
                    "The Configuration MODULE_PAYMENT_AUTHORIZENET_REQUEST_URL must be set to the URL for"
                            + " sending the request to Authorize.Net. (e.g. https://secure.authorize.net/gateway/transact.dll)");
        }
        configs.setAuthorizeNetRequestUrl(confVal);

        confVal = getAdminConfigMgr().getConfigurationValue(MODULE_PAYMENT_AUTHORIZENET_ARB_REQUEST_URL);
        configs.setAuthorizeNetARBRequestUrl(confVal);

        confVal = getAdminConfigMgr().getConfigurationValue(MODULE_PAYMENT_AUTHORIZENET_LOGIN);
        if (confVal == null) {
            throw new KKAdminException("The Configuration MODULE_PAYMENT_AUTHORIZENET_LOGIN must be set to the"
                    + " Authorize.Net API ID for this installation");
        }
        configs.setAuthorizeNetLoginId(confVal);

        confVal = getAdminConfigMgr().getConfigurationValue(MODULE_PAYMENT_AUTHORIZENET_TXNKEY);
        if (confVal == null) {
            throw new KKAdminException("The Configuration MODULE_PAYMENT_AUTHORIZENET_TXNKEY must be set to the"
                    + " Current Authorize.Net Transaction Key for this installation");
        }

        configs.setAuthorizeNetTxnKey(confVal);
        configs.setTestMode(
                getAdminConfigMgr().getConfigurationValueAsBool(MODULE_PAYMENT_AUTHORIZENET_TESTMODE, true));
        configs.setZone(getAdminConfigMgr().getConfigurationIntValueOrDefault(MODULE_PAYMENT_AUTHORIZENET_ZONE, 0));
        configs.setSortOrder(
                getAdminConfigMgr().getConfigurationIntValueOrDefault(MODULE_PAYMENT_AUTHORIZENET_SORT_ORDER, 0));
        configs.setShowCVV(
                getAdminConfigMgr().getConfigurationValueAsBool(MODULE_PAYMENT_AUTHORIZENET_SHOW_CVV, true));

        return configs;
    }

    /**
     * Authorize.net returns a response as delimiter separated variables. In order to make them
     * readable, we tag each one with a description before saving in the ipnHistory table.
     * 
     * @param position
     * @return Response Description
     */
    private String getRespDesc(int position) {
        String ret = "unknown";
        switch (position) {
        case 1:
            ret = "Response Code";
            break;
        case 2:
            ret = "Response Subcode";
            break;
        case 3:
            ret = "Response Reason Code";
            break;
        case 4:
            ret = "Response Reason Text";
            break;
        case 5:
            ret = "Approval Code";
            break;
        case 6:
            ret = "AVS Result Code";
            break;
        case 7:
            ret = "Transaction ID";
            break;
        case 8:
            ret = "Invoice Number";
            break;
        case 9:
            ret = "Description";
            break;
        case 10:
            ret = "Amount";
            break;
        case 11:
            ret = "Method";
            break;
        case 12:
            ret = "Transaction Type";
            break;
        case 13:
            ret = "Customer ID";
            break;
        case 14:
            ret = "Cardholder First Name";
            break;
        case 15:
            ret = "Cardholder Last Name";
            break;
        case 16:
            ret = "Company";
            break;
        case 17:
            ret = "Billing Address";
            break;
        case 18:
            ret = "City";
            break;
        case 19:
            ret = "State";
            break;
        case 20:
            ret = "Zip";
            break;
        case 21:
            ret = "Country";
            break;
        case 22:
            ret = "Phone";
            break;
        case 23:
            ret = "Fax";
            break;
        case 24:
            ret = "Email";
            break;
        case 25:
            ret = "Ship to First Name";
            break;
        case 26:
            ret = "Ship to Last Name";
            break;
        case 27:
            ret = "Ship to Company";
            break;
        case 28:
            ret = "Ship to Address";
            break;
        case 29:
            ret = "Ship to City";
            break;
        case 30:
            ret = "Ship to State";
            break;
        case 31:
            ret = "Ship to Zip";
            break;
        case 32:
            ret = "Ship to Country";
            break;
        case 33:
            ret = "Tax Amount";
            break;
        case 34:
            ret = "Duty Amount";
            break;
        case 35:
            ret = "Freight Amount";
            break;
        case 36:
            ret = "Tax Exempt Flag";
            break;
        case 37:
            ret = "PO Number";
            break;
        case 38:
            ret = "MD5 Hash";
            break;
        case 39:
            ret = "(CVV2/CVC2/CID)Response Code";
            break;
        case 40:
            ret = "(CAVV) Response Code";
            break;
        default:
            break;
        }

        return ret;
    }

    /**
     * This method calls AuthorizeNet to create a subscription. The KK subscription object is
     * updated to include the AuthorizeNet subscription id. All transaction information is stored in
     * the IPN History.
     * 
     * @param options
     *            PaymentOptions containing information necessary to carry out the transaction
     * @return Returns an array of NameValue objects
     * @throws Exception
     */
    protected NameValue[] createSubscription(PaymentOptions options) throws Exception {
        if (options == null) {
            throw new KKAdminException(
                    "A null PaymentOptions object was passed to the createSubscription() method");
        }

        if (options.getCreditCard() == null) {
            throw new KKAdminException("A null Credit Card object was passed to the createSubscription() method");
        }

        AdminOrder order = getAdminOrderMgr().getOrderForOrderId(options.getOrderId());
        if (order == null) {
            throw new KKAdminException("An order does not exist for id = " + options.getOrderId());
        }

        AdminSubscription kkSubscription = getAdminBillingMgr().getSubscription(options.getSubscriptionId());
        if (kkSubscription == null) {
            throw new KKAdminException("A subscription does not exist for id = " + options.getSubscriptionId());
        }
        if (kkSubscription.getPaymentSchedule() == null) {
            throw new KKAdminException("A payment schedule does not exist for the subscription with id = "
                    + options.getSubscriptionId());
        }

        // Read the configuration variables
        ConfigVariables configs = getConfigVariables();

        ARBAPI api = new ARBAPI(configs.getAuthorizeNetLoginId(), configs.getAuthorizeNetTxnKey());

        // Get the kk payment schedule
        AdminPaymentSchedule kkPaymentSchedule = kkSubscription.getPaymentSchedule();
        // Get the KK Credit card
        CreditCardIf kkCreditCard = options.getCreditCard();

        // Get the authorize net time unit which is days or months
        String anTimeUnit = null;
        if (kkPaymentSchedule.getTimeUnit() == AdminPaymentSchedule.DAILY) {
            anTimeUnit = "days";
        } else if (kkPaymentSchedule.getTimeUnit() == AdminPaymentSchedule.MONTHLY) {
            anTimeUnit = "months";
        } else {
            throw new KKAdminException("Authorize.Net does not support a time unit of : "
                    + kkPaymentSchedule.getTimeUnit() + " . It only supports days or months.");
        }

        // Get the authorize net total occurrences ( 9999 maps to the kk value of -1 which means
        // never ending)
        int anTotalOccurrences = 9999;
        if (kkPaymentSchedule.getNumPayments() > -1) {
            anTotalOccurrences = kkPaymentSchedule.getNumPayments();
        }

        ARBPaymentSchedule newSchedule = new ARBPaymentSchedule();
        newSchedule.setIntervalLength(kkPaymentSchedule.getTimeLength());
        newSchedule.setSubscriptionUnit(anTimeUnit);
        newSchedule.setStartDate(kkSubscription.getStartDate());
        newSchedule.setTotalOccurrences(anTotalOccurrences);
        newSchedule.setTrialOccurrences(0);

        // Create a new credit card
        ARBCreditCard creditCard = new ARBCreditCard();
        creditCard.setCardNumber(kkCreditCard.getCcNumber());
        creditCard.setExpirationDate(kkCreditCard.getCcExpires());
        if (configs.isShowCVV()) {
            creditCard.setCardCode(kkCreditCard.getCcCVV());
        }
        ARBPayment payment = new ARBPayment(creditCard);

        // Create a billing info
        ARBNameAndAddress billingInfo = new ARBNameAndAddress();

        // Set the billing name. If the name field consists of more than two strings, we take the
        // last one as being the surname.
        String bName = order.getBillingName();
        if (bName != null) {
            String[] names = bName.split(" ");
            int len = names.length;
            if (len >= 2) {
                StringBuffer firstName = new StringBuffer();
                for (int i = 0; i < len - 1; i++) {
                    if (firstName.length() == 0) {
                        firstName.append(names[i]);
                    } else {
                        firstName.append(" ");
                        firstName.append(names[i]);
                    }
                }
                billingInfo.setFirstName(firstName.toString());
                billingInfo.setLastName(names[len - 1]);
            }
        }

        // Create a customer and specify billing info
        ARBCustomer customer = new ARBCustomer();
        customer.setId(String.valueOf(order.getCustomerId()));
        customer.setBillTo(billingInfo);

        // Create an order object and add the order id
        ARBOrder arbOrder = new ARBOrder();
        arbOrder.setInvoiceNumber(Integer.toString(order.getId()));

        // Create a subscription and specify payment, schedule and customer
        ARBSubscription newSubscription = new ARBSubscription();
        newSubscription.setPayment(payment);
        newSubscription.setSchedule(newSchedule);
        newSubscription.setCustomer(customer);
        newSubscription.setAmount(kkSubscription.getAmount().doubleValue());
        newSubscription.setTrialAmount((kkSubscription.getTrialAmount() == null) ? new Double(0)
                : kkSubscription.getTrialAmount().doubleValue());
        newSubscription.setOrder(arbOrder);

        // Give this subscription a name = Order Number
        newSubscription.setName(order.getOrderNumber());

        // Create a new subscription request from the subscription object
        // Returns XML document. Also holds internal pointer as current_request.
        api.createSubscriptionRequest(newSubscription);

        // Common code to manage the Gateway Post
        manageARBGatewayPost(configs, api, kkSubscription, /* create */true);

        // Remove credit card details
        newSubscription.setPayment(null);
        payment = null;
        creditCard = null;
        kkCreditCard = null;

        return new NameValue[] { new NameValue("ret", "0") };
    }

    /**
     * Method that needs to be implemented to update a subscription
     * 
     * @param options
     * @return Returns an array of NameValue objects that may contain any return information
     *         considered useful by the caller.
     * @throws Exception
     */
    protected NameValue[] updateSubscription(PaymentOptions options) throws Exception {
        if (options == null) {
            throw new KKAdminException(
                    "A null PaymentOptions object was passed to the createSubscription() method");
        }

        if (options.getCreditCard() == null) {
            throw new KKAdminException("A null Credit Card object was passed to the createSubscription() method");
        }

        AdminOrder order = getAdminOrderMgr().getOrderForOrderId(options.getOrderId());
        if (order == null) {
            throw new KKAdminException("An order does not exist for id = " + options.getOrderId());
        }

        AdminSubscription kkSubscription = getAdminBillingMgr().getSubscription(options.getSubscriptionId());
        if (kkSubscription == null) {
            throw new KKAdminException("A subscription does not exist for id = " + options.getSubscriptionId());
        }
        if (kkSubscription.getPaymentSchedule() == null) {
            throw new KKAdminException("A payment schedule does not exist for the subscription with id = "
                    + options.getSubscriptionId());
        }

        // Read the configuration variables
        ConfigVariables configs = getConfigVariables();

        ARBAPI api = new ARBAPI(configs.getAuthorizeNetLoginId(), configs.getAuthorizeNetTxnKey());

        // Get the kk payment schedule
        // AdminPaymentSchedule kkPaymentSchedule = kkSubscription.getPaymentSchedule();
        // Get the KK Credit card
        CreditCardIf kkCreditCard = options.getCreditCard();

        // Get the authorize net total occurrences ( 9999 maps to the kk value of -1 which means
        // never ending)
        // int anTotalOccurrences = 9999;
        // if (kkPaymentSchedule.getNumPayments() > -1)
        // {
        // anTotalOccurrences = kkPaymentSchedule.getNumPayments();
        // }

        /*
         * Not typical to change payment schedule
         */
        // Length and unit cannot be changed
        // ARBPaymentSchedule newSchedule = new ARBPaymentSchedule();
        // newSchedule.setStartDate(kkSubscription.getStartDate());
        // newSchedule.setTotalOccurrences(anTotalOccurrences);
        // newSchedule.setTrialOccurrences(0);

        // Create a new credit card
        ARBCreditCard creditCard = new ARBCreditCard();
        creditCard.setCardNumber(kkCreditCard.getCcNumber());
        creditCard.setExpirationDate(kkCreditCard.getCcExpires());
        if (configs.isShowCVV()) {
            creditCard.setCardCode(kkCreditCard.getCcCVV());
        }
        ARBPayment payment = new ARBPayment(creditCard);

        /*
         * Not typical to change billing info
         */
        // Create a billing info
        // ARBNameAndAddress billingInfo = new ARBNameAndAddress();
        //
        // // Set the billing name. If the name field consists of more than two strings, we take the
        // // last one as being the surname.
        // String bName = order.getBillingName();
        // if (bName != null)
        // {
        // String[] names = bName.split(" ");
        // int len = names.length;
        // if (len >= 2)
        // {
        // StringBuffer firstName = new StringBuffer();
        // for (int i = 0; i < len - 1; i++)
        // {
        // if (firstName.length() == 0)
        // {
        // firstName.append(names[i]);
        // } else
        // {
        // firstName.append(" ");
        // firstName.append(names[i]);
        // }
        // }
        // billingInfo.setFirstName(firstName.toString());
        // billingInfo.setLastName(names[len - 1]);
        // }
        // }
        //
        // // Create a customer and specify billing info
        // ARBCustomer customer = new ARBCustomer();
        // customer.setId(String.valueOf(order.getCustomerId()));
        // customer.setBillTo(billingInfo);

        // Create a subscription and specify payment, schedule and customer
        ARBSubscription updateSubscription = new ARBSubscription();
        updateSubscription.setPayment(payment);
        // updateSubscription.setSchedule(newSchedule);
        // updateSubscription.setCustomer(customer);
        updateSubscription.setAmount(kkSubscription.getAmount().doubleValue());
        // updateSubscription.setTrialAmount((kkSubscription.getTrialAmount() == null) ? new
        // Double(0)
        // : kkSubscription.getTrialAmount().doubleValue());

        // Give this subscription a name = Order Number
        // updateSubscription.setName(order.getOrderNumber());

        // Set code
        updateSubscription.setSubscriptionId(kkSubscription.getSubscriptionCode());

        // Create a new subscription request from the subscription object. Also holds internal
        // pointer as current_request.
        api.updateSubscriptionRequest(updateSubscription);

        // Common code to manage the Gateway Post
        manageARBGatewayPost(configs, api, kkSubscription, /* create */false);

        // Remove credit card details
        updateSubscription.setPayment(null);
        payment = null;
        creditCard = null;
        kkCreditCard = null;

        return new NameValue[] { new NameValue("ret", "0") };
    }

    /**
     * Method that needs to be implemented to cancel a subscription
     * 
     * @param options
     * @return Returns an array of NameValue objects that may contain any return information
     *         considered useful by the caller.
     * @throws Exception
     */
    protected NameValue[] cancelSubscription(PaymentOptions options) throws Exception {
        AdminSubscription kkSubscription = getAdminBillingMgr().getSubscription(options.getSubscriptionId());
        if (kkSubscription == null) {
            throw new KKAdminException("A subscription does not exist for id = " + options.getSubscriptionId());
        }

        // Read the configuration variables
        ConfigVariables configs = getConfigVariables();

        ARBAPI api = new ARBAPI(configs.getAuthorizeNetLoginId(), configs.getAuthorizeNetTxnKey());

        // Create a subscription and specify subscription id
        ARBSubscription cancelSubscription = new ARBSubscription();
        cancelSubscription.setSubscriptionId(kkSubscription.getSubscriptionCode());

        // Create a new subscription request from the subscription object. Also holds internal
        // pointer as current_request.
        api.cancelSubscriptionRequest(cancelSubscription);

        // Common code to manage the Gateway Post
        manageARBGatewayPost(configs, api, kkSubscription, /* create */false);

        return new NameValue[] { new NameValue("ret", "0") };
    }

    /**
     * Return the status of a subscription. When active it returns the string "active"
     * 
     * @param options
     * @return Returns an array of NameValue objects that may contain any return information
     *         considered useful by the caller.
     * @throws Exception
     */
    protected NameValue[] getSubscriptionStatus(PaymentOptions options) throws Exception {

        AdminSubscription kkSubscription = getAdminBillingMgr().getSubscription(options.getSubscriptionId());
        if (kkSubscription == null) {
            throw new KKAdminException("A subscription does not exist for id = " + options.getSubscriptionId());
        }

        // Read the configuration variables
        ConfigVariables configs = getConfigVariables();

        ARBAPI api = new ARBAPI(configs.getAuthorizeNetLoginId(), configs.getAuthorizeNetTxnKey());

        // Create a subscription and specify subscription id
        //
        ARBSubscription getSubscriptionStatus = new ARBSubscription();
        getSubscriptionStatus.setSubscriptionId(kkSubscription.getSubscriptionCode());

        // Create a new subscription request from the subscription object.
        // Also holds internal pointer as current_request.
        //
        api.getSubscriptionStatusRequest(getSubscriptionStatus);

        // Common code to manage the Gateway Post
        String status = manageARBGatewayPost(configs, api, kkSubscription, /* create */false);

        // Return the status
        return new NameValue[] { new NameValue("status", status) };
    }

    /**
     * Common code for all recurring billing requests. Returns the status which is valid for the
     * getSubscriptionStatus() API call.
     * 
     * @param configs
     * @param api
     * @throws Exception
     */
    private String manageARBGatewayPost(ConfigVariables configs, ARBAPI api, AdminSubscription kkSubscription,
            boolean create) throws Exception {
        BasicXmlDocument reqXML = api.getCurrentRequest();
        String reqStr = reqXML.dump();

        if (log.isDebugEnabled()) {
            try {
                log.debug("Formatted GatewayRequest =\n" + PrettyXmlPrinter.printXml(reqStr));
            } catch (Exception e) {
                log.debug("Exception pretty-printing gateway request: " + e.getMessage());
            }
        }

        AdminPaymentDetails pDetails = new AdminPaymentDetails();
        pDetails.setSendData(reqStr);
        pDetails.setRequestUrl(configs.getAuthorizeNetARBRequestUrl());
        pDetails.setContentType("text/xml");

        // Do the post
        String gatewayResp = postData(pDetails);

        if (log.isDebugEnabled()) {
            log.debug("Unformatted GatewayResp = \n" + gatewayResp);
        }

        AdminIpnHistory ipnHistory = new AdminIpnHistory();
        ipnHistory.setModuleCode(code);
        ipnHistory.setGatewayFullResponse(gatewayResp);
        ipnHistory.setOrderId(kkSubscription.getOrderId());
        ipnHistory.setCustomerId(kkSubscription.getCustomerId());
        ipnHistory.setSubscriptionId(kkSubscription.getId());

        try {
            api.parseResponse(gatewayResp);
        } catch (Exception e) {
            ipnHistory.setKonakartResultDescription(RET1_DESC + e.getMessage());
            ipnHistory.setKonakartResultId(RET1);
            getAdminOrderMgr().insertIpnHistory(ipnHistory);
            throw e;
        }

        if (log.isDebugEnabled()) {
            log.debug("Result Code : " + api.getResultCode());
            log.debug("Subscription Id : " + api.getResultSubscriptionId());
        }

        if (api.getResultCode() != null && api.getResultCode().equalsIgnoreCase("OK")) {
            ipnHistory.setKonakartResultDescription(RET0_DESC);
            ipnHistory.setKonakartResultId(RET0);
            if (create) {
                // Update the subscription to add the AuthorizeNet subscription code
                kkSubscription.setSubscriptionCode(api.getResultSubscriptionId());
                getAdminBillingMgr().updateSubscription(kkSubscription);
            }
        } else {
            ipnHistory.setKonakartResultDescription(RET2_DESC);
            ipnHistory.setKonakartResultId(RET2);
        }

        // Set the Gateway Result
        StringBuffer sb = new StringBuffer(api.getResultCode());
        if (api.getMessages() != null) {
            for (Iterator<ARBMessage> iterator = api.getMessages().iterator(); iterator.hasNext();) {
                ARBMessage msg = iterator.next();
                sb.append(" - ");
                sb.append(msg.getCode());
                // If we have only one msg then add description
                if (api.getMessages().size() == 1) {
                    sb.append(" - ");
                    sb.append(msg.getText());
                }
            }
        }

        String gatewayRes = sb.toString();
        if (gatewayRes.length() > 128) {
            gatewayRes = gatewayRes.substring(0, 128);
        }
        ipnHistory.setGatewayResult(gatewayRes);

        ipnHistory.setGatewayTransactionId(api.getResultSubscriptionId());
        getAdminOrderMgr().insertIpnHistory(ipnHistory);

        return api.getResultStatus();
    }

    /**
     * Used to store the configuration data of this module
     */
    protected class ConfigVariables {
        private int sortOrder = -1;

        // The Authorize.Net Url used to POST the payment request.
        // "https://secure.authorize.net/gateway/transact.dll"
        private String authorizeNetRequestUrl;

        // The Authorize.Net Automated Recurring Billing Url used to POST the payment request.
        // "https://apitest.authorize.net/xml/v1/request.api"
        private String authorizeNetARBRequestUrl;

        // Login ID
        private String authorizeNetLoginId;

        // Transaction Key
        private String authorizeNetTxnKey;

        // Test/Live Mode indicator
        private boolean testMode = true;

        // Show the CVV entry field on the UI
        private boolean showCVV = true;

        // zone where AuthorizeNet will be made available
        private int zone;

        /**
         * @return the sortOrder
         */
        public int getSortOrder() {
            return sortOrder;
        }

        /**
         * @param sortOrder
         *            the sortOrder to set
         */
        public void setSortOrder(int sortOrder) {
            this.sortOrder = sortOrder;
        }

        /**
         * @return the authorizeNetRequestUrl
         */
        public String getAuthorizeNetRequestUrl() {
            return authorizeNetRequestUrl;
        }

        /**
         * @param authorizeNetRequestUrl
         *            the authorizeNetRequestUrl to set
         */
        public void setAuthorizeNetRequestUrl(String authorizeNetRequestUrl) {
            this.authorizeNetRequestUrl = authorizeNetRequestUrl;
        }

        /**
         * @return the authorizeNetLoginId
         */
        public String getAuthorizeNetLoginId() {
            return authorizeNetLoginId;
        }

        /**
         * @param authorizeNetLoginId
         *            the authorizeNetLoginId to set
         */
        public void setAuthorizeNetLoginId(String authorizeNetLoginId) {
            this.authorizeNetLoginId = authorizeNetLoginId;
        }

        /**
         * @return the authorizeNetTxnKey
         */
        public String getAuthorizeNetTxnKey() {
            return authorizeNetTxnKey;
        }

        /**
         * @param authorizeNetTxnKey
         *            the authorizeNetTxnKey to set
         */
        public void setAuthorizeNetTxnKey(String authorizeNetTxnKey) {
            this.authorizeNetTxnKey = authorizeNetTxnKey;
        }

        /**
         * @return the testMode
         */
        public boolean isTestMode() {
            return testMode;
        }

        /**
         * @param testMode
         *            the testMode to set
         */
        public void setTestMode(boolean testMode) {
            this.testMode = testMode;
        }

        /**
         * @return the showCVV
         */
        public boolean isShowCVV() {
            return showCVV;
        }

        /**
         * @param showCVV
         *            the showCVV to set
         */
        public void setShowCVV(boolean showCVV) {
            this.showCVV = showCVV;
        }

        /**
         * @return the zone
         */
        public int getZone() {
            return zone;
        }

        /**
         * @param zone
         *            the zone to set
         */
        public void setZone(int zone) {
            this.zone = zone;
        }

        /**
         * @return the authorizeNetARBRequestUrl
         */
        public String getAuthorizeNetARBRequestUrl() {
            return authorizeNetARBRequestUrl;
        }

        /**
         * @param authorizeNetARBRequestUrl
         *            the authorizeNetARBRequestUrl to set
         */
        public void setAuthorizeNetARBRequestUrl(String authorizeNetARBRequestUrl) {
            this.authorizeNetARBRequestUrl = authorizeNetARBRequestUrl;
        }
    }
}