The ATG Consumer Commerce product provides the ability to cancel and edit existing orders through the Customer Service Module. Because there are many different ways to present the cancel order functionality to the customer and there are many different business rules governing order cancellation restriction, this functionality is implemented outside the core product. The ability of a user to cancel his own orders is a Pioneer Cycling specific piece of functionality.

The business rules for Pioneer Cycling dictate that any order that still has actions pending may be cancelled, so in order.jsp we used a Switch servlet bean on the order’s state to determine if it may be cancelled:

<dsp:droplet name="Switch">
 <dsp:param name="value" param="order.stateAsString"/>

 <dsp:oparam name="NO_PENDING_ACTION">

  <!-- Snip -->

   This order has been completed.  You cannot cancel it.
   </dsp:oparam>

   <dsp:oparam name="default">

  <!-- Snip -->

 <dsp:a href="cancel_order.jsp">
 <dsp:param name="orderId" param="order.id"/>
      cancel the order</dsp:a><br>
 </dsp:oparam>
</dsp:droplet>

The link to cancel_order.jsp contains an orderId parameter to keep track of which order to cancel. cancel_order.jsp is a very simple page. The form used to cancel the order exists for the sake of the user interface. It gives the customer the chance to change her mind about the cancellation.

The next page actually does the canceling. Note the hidden orderId parameter that gets passed to the next page with the form submission. When displaying data like this on the web there is always a security concern. In order to thwart malicious users who might hit the order URL and try out different orderId parameters to gain access to private data, we allowed users to see only the orders that they have placed. By default this security feature is turned on, but you can disable the security feature by setting the property /atg/commerce/order/OrderLookup.enableSecurity=false.

<dsp:form action="order_cancelled.jsp">
  Are you sure you wish to Cancel Order #<dsp:valueof param="orderId"><i>none</i>
</dsp:valueof>
  <p>
  If you do not wish to cancel your order, you may use your browser's back button
to return to the order.
  <input name="orderId" type="hidden" value='<dsp:valueof param="orderId"/>'>
  <BR><br>
  <input type="submit" value="CANCEL ORDER">
</dsp:form>

The action of the form, order_cancelled.jsp, is the next stop when the customer clicks the “Cancel Order” button. This is the page that actually cancels the order using the CancelOrder servlet bean.

If the cancellation is initiated without any problem, the successoparam is rendered. Otherwise the erroroparam is rendered and the error message is displayed. (Canceling an order is an asynchronous process in which the form handler sends a Java message to the fulfillment system that then performs the cancel order operation. Since it is asynchronous, there is no guarantee that it will complete by the time the “This Order Has Been Cancelled” page displays to the user. Also, it is possible that the fulfillment process might not be running. The cancel request could be sent and the order would not actually be cancelled until the fulfillment system was brought back up.) Generally, in the Pioneer Cycling store it takes only tens of seconds. We included a message to users so that they will understand if orders do not show up as cancelled right away on their Order History pages. Here is the relevant part of order_cancelled.jsp:

<dsp:droplet name="/atg/commerce/order/CancelOrder">
 <dsp:oparam name="success">
   The Cancel Order instruction has been sent to the order processor.
   <P>
   It may take several minutes for the order to
   disappear from the list of orders on your <dsp:a href="order_history.jsp">order
   history</dsp:a> page.
 </dsp:oparam>
 <dsp:oparam name="error">
   <dsp:valueof param="errorMsg"/>
 </dsp:oparam>
</dsp:droplet>

The CancelOrder servlet bean extends the OrderLookup servlet bean and therefore has many similarities in its configuration. The main difference is that it has an as additional property, the OrderCanceller, that is the component that communicates with the fulfillment system to cancel the order. Here is /atg/commerce/order/CancelOrder.properties:

$class=atg.projects.b2cstore.CancelOrder
$scope=global

orderCanceller=OrderCanceller
orderManager=OrderManager

openStates=\
    submitted,\
    processing,\
    pending_merchant_action,\
    pending_customer_action

closedStates=\
    no_pending_action

profilePath=/atg/userprofiling/Profile
enableSecurity=true

The OrderCanceller used by CancelOrder is configured as follows in /atg/commerce/order/OrderCanceller.properties:

$class=atg.projects.b2cstore.OrderCanceller
$scope=global

transactionManager=/atg/dynamo/transaction/TransactionManager
messageSourceName=OrderCanceller
modifyPort=ModifyOrderPort

OrderCanceller.java:

package atg.projects.b2cstore;

// Commerce
import atg.commerce.order.*;
import atg.commerce.fulfillment.*;
import atg.commerce.*;
import atg.commerce.states.*;
import atg.commerce.messaging.*;

// Java classes
import javax.jms.*;
import javax.transaction.*;
import javax.transaction.xa.*;
import java.util.ResourceBundle.*;
import java.util.*;

/**
 * This class will contain methods needed to cancel an order by sending
 * messages to the fulfillment subsystem.
 *
 * @version $Change: 228696 $$DateTime: 2002/02/11 13:07:58 $$Author: releng $ */
public class OrderCanceller extends SourceSinkTemplate
{
  //-------------------------------------
  /** Class version string */
  public static final String CLASS_VERSION = "$Change: 228696 $$DateTime:
2002/02/11 13:07:58 $$Author: releng $";

  //---------------------------------------------------------------------------
  // property:MessageSourceName
  //---------------------------------------------------------------------------

  /** the name used by this class when it acts as a message source **/
  private String mMessageSourceName = "OrderCanceller";

  //---------------------------------------------------------------------------
  /**
   * Sets the name used by this class when it acts as a message source
   * so that it's messages can be identified.
   **/
  public void setMessageSourceName(String pMessageSourceName) {
    mMessageSourceName = pMessageSourceName;
  }

  //---------------------------------------------------------------------------
  /**
   * Gets the name used by this class when it acts as a message source
   * so that it's messages can be identified.
   **/
  public String getMessageSourceName() {
    return mMessageSourceName;
  }

  /** Port name for sending modify order messages */
  String mModifyPort = null;

  //-------------------------------------
  /**
   * Sets Port name for sending modify order messages
   **/
  public void setModifyPort(String pModifyPort) {
    mModifyPort = pModifyPort;
  }

  //-------------------------------------
  /**
   * Returns Port name for sending modify order messages
   **/
  public String getModifyPort() {
    return mModifyPort;
  }

  //-------------------------------------
  /**
   * Assemble and send a message to cancel the order
   * @param orderId the id of the order to cancel.
   **/
  public void sendCancelOrder(String pOrderId) {

    Modification[] mods = new Modification[1];
    ModifyOrder message = new ModifyOrder();

    message.setOrderId(pOrderId);
    message.setSource(getMessageSourceName());
    message.setOriginalSource(getMessageSourceName());
    message.setOriginalId(message.getId());

    GenericRemove gr = new GenericRemove();
    gr.setTargetType(Modification.TARGET_ORDER);
    gr.setTargetId(pOrderId);
    mods[0] = gr;

    message.setModifications(mods);

    try {
      sendCommerceMessage(message, getModifyPort());
    } catch(JMSException j) {
      logError(j);
    }
  }

} // end of class

CancelOrder.java:

package atg.projects.b2cstore;

import javax.servlet.*;
import javax.servlet.http.*;

import atg.commerce.CommerceException;
import atg.commerce.order.*;
import atg.commerce.states.*;
import atg.nucleus.naming.ParameterName;
import atg.nucleus.naming.ComponentName;
import atg.servlet.*;
import atg.repository.RepositoryException;
import atg.repository.RepositoryItem;

import java.io.*;
import java.util.List;
import java.util.Locale;

/**
 * This servlet cancels the order for the orderId paramteter passed in.
 * It takes as parameters:
 * <dl>
 *
 * <dt>orderId
 *
 * <dd>the id of the order to cancel
 *
 * </dl>
 *
 * It renders the following oparams:
 * <dl>
 *
 * <dt>success
 *
 * <dd>The oparam success is rendered once if the cancel was successful
 *
 * <dt>error
 *
 * <dd>error will be rendered if an error occurred
 *
 * </dl>
 *
 * It sets the following output params:
 * <dl>
 *
 * <dt>errorMsg
 *
 * <dd>if an error occurred this will be the detailed error message
 *     for the user.
 *
 * </dl>
 *
 * This droplet has a security feature that allows only the current user to
 * cancel his own orders.  This feature is enabled by default.  To disable
 * it, set the property enableSecurity=false
 */
public class CancelOrder extends OrderLookup {
  //-------------------------------------
  // Class version string
  public static final String CLASS_VERSION = "$Id: CancelOrder.java,v 1.2
  2000/06/07 19:17:43 cynthia Exp $";

  //-------------------------------------
  // Constants
  //-------------------------------------
  public final static String SUCCESS = "success";


  //-------------------------------------
  // property: orderCanceller
  OrderCanceller mOrderCanceller;
  public void setOrderCanceller(OrderCanceller pOrderCanceller) {
    mOrderCanceller = pOrderCanceller;
  }
  public OrderCanceller getOrderCanceller() {
    return mOrderCanceller;
  }

  //-------------------------------------
  /**
   * service the request
   */
  public void service(DynamoHttpServletRequest pReq,
                      DynamoHttpServletResponse pRes)
            throws ServletException, IOException
  {
    // Get the orderId parameter from the request.  It was set by the
    // hidden form field on the cancel_order.jsp page.
    String orderId = pReq.getParameter(ORDERID);
    if (orderId != null) {
      try {
        if (isLoggingDebug())
          logDebug("orderId = " + orderId);

        // Make sure that an order for that orderId exists by asking the
        //OrderManager with the orderExists(orderId) method.  We then make double
       //sure by doing orderManager.loadOrder(orderId) and making sure it is not
       //null, but that is optional.
        Order order = null;
        if (getOrderManager().orderExists(orderId))
          order = getOrderManager().loadOrder(orderId);

        if (order == null) {
          String errMsg = OrderUserMessage.format(MSG_NO_SUCH_ORDER,
          getUserLocale(pReq, pRes));
          pReq.setParameter(ERRORMESSAGE, errMsg);
          pReq.serviceLocalParameter(ERROR, pReq, pRes);
          return;
        }

        // Make sure that the current logged in user is the owner of this order by
        // resolving the current user's profile id and comparing it to the order's
        //owner.
        if (isEnableSecurity()) {
          if (isLoggingDebug())
            logDebug("checking ownership. current user is
            " + getCurrentProfileId(pReq) + " order owner is
            " +order.getProfileId());
          if (!order.getProfileId().equals(getCurrentProfileId(pReq)))
            {String errMsg = OrderUserMessage.format(MSG_NO_PERMISSION_FOR_ORDER,
            getUserLocale(pReq, pRes));
            pReq.setParameter(ERRORMESSAGE, errMsg);
            pReq.serviceLocalParameter(ERROR, pReq, pRes);
            return;
          }
        }

        // Now we actually cancel the order
        getOrderCanceller().sendCancelOrder(orderId);

        // Everything went well, so we will send out the success oparam
        pReq.serviceLocalParameter(SUCCESS, pReq, pRes);
      }
      catch (CommerceException e) {
        if (isLoggingError())
          logError(e);

        // Something went wrong.  This will cause the error oparam to be rendered
        //and the errorMsg parameter to be set for the jsp page.
        String errMsg = OrderUserMessage.format(MSG_GENERAL_ERROR,
                        getUserLocale(pReq, pRes));
        pReq.setParameter(ERRORMESSAGE, errMsg);
        pReq.serviceLocalParameter(ERROR, pReq, pRes);
      }
      return;
    }

    // Oops! It looks like somebody forgot to pass in an orderId parameter.
    if (orderId == null) {
      String errMsg = OrderUserMessage.format(MSG_NO_PARAM_SPECIFIED,
                      getUserLocale(pReq, pRes));
      pReq.setParameter(ERRORMESSAGE, errMsg);
      pReq.serviceLocalParameter(ERROR, pReq, pRes);
      return;
    }
  }
}
 
loading table of contents...