If your sites require it, you can extend the payment process to support a new payment method. This section provides information on how to create and support a new payment method, using an example called the StorePoints payment method. In this example, customers earn store points when they purchase items, and they can later redeem those points on other purchases. The StorePoints system is just one example of the many new payment methods you can implement.

The StorePoints system is implemented using the following basic steps. Detailed instructions are provided in the referenced subsections that follow.

  1. Create the new StorePoints PaymentGroup. See Creating a New PaymentGroup.

  2. Create a new repository item type for the StorePointsPaymentGroup. Associate the new item type to the new class by adding an entry to the beanNameToItemDescriptorMap property of the OrderTools component. Add the new class to the paymentTypeClassMap. See Integrating a New Commerce Object: Using a New Item Descriptor.

  3. Implement the payment processors involved in operating on the StorePoints PaymentGroup. See Implementing Processors for the New PaymentGroup.

  4. Define the pipeline that processes the StorePoints PaymentGroup for authorization, debits, and credits, and configure the PaymentManager to invoke it when appropriate. See Integrating the New Payment Processors into the PaymentManager.

  5. Extend the Order validation process to validate the StorePoints PaymentGroup during checkout. See Extending Order Validation to Support New Payment Methods in the next section.

The following diagram shows the relationships between the objects involved in processing a payment transaction. In Oracle ATG Web Commerce, by default the CreditCard, GiftCertificate, StoreCredit, and InvoiceRequest systems are provided.

Creating a New PaymentGroup

The first step in creating and supporting a new StorePoints payment method is to create a new PaymentGroup named StorePoints. The new PaymentGroup allows you to distinguish StorePoints from other payment groups and to store data relating to the store points system. The following code sample is an example of the StorePoints PaymentGroup.

package store.some.package;

import atg.commerce.order.*;

public class StorePoints extends PaymentGroupImpl
{
  public StorePoints() {
  }

  public String getUserId() {
    return (String) getPropertyValue("userId");
  }

  public void setUserId(String pUserId) {
    setPropertyValue("userId", pUserId);
  }

  public int getNumberOfPoints() {
    return ((Integer) getPropertyValue("numberOfPoints")).intValue();
  }

  public void setNumberOfPoints(int pNumberOfPoints) {
    setPropertyValue("numberOfPoints", new Integer(pNumberOfPoints));
  }
}
Implementing Processors for the New PaymentGroup

As previously mentioned, the default payment pipelines are composed of two processors. The first processor aggregates the necessary information for performing the requested payment action (for example, CREDIT) and creates an XXXInfo object (for example, CreditCardInfo) for use in that action. The second processor actually performs the operation – authorizing, debiting, or crediting the appropriate payment method. For example, the creditCardProcessorChain pipeline is composed of the CreateCreditCardInfo processor (class atg.commerce.payment.processor.ProcCreateCreditCardInfo) and the ProcessCreditCard processor (class atg.commerce.payment.processor.ProcProcessCreditCard). The ProcessCreditCard processor calls through to a CreditCardProcessor object to perform the actual operations. The specific object used to perform the actual operations is retrieved from PaymentManager.creditCardProcessor, which points to an object instantiated from a class that implements the atg.payment.creditcard.CreditCardProcessor interface.

For the StorePoints PaymentGroup, you need to implement similar processors -- a pipeline processor to create the XXXInfo object for the StorePoints PaymentGroup, a second pipeline processor to authorize, debit, and credit the StorePoints PaymentGroup, and a processor that implements a StorePointsProcessor interface and actually performs the payment operations.

First, write the StorePointsProcessor interface that defines the authorize(), debit(), and credit() methods for the StorePoints PaymentGroup, as shown in the following code example. Note that the authorize, debit, and credit methods of the StorePointsProcessor interface all return a StorePointsStatus object, which represents the transaction on the StorePoints PaymentGroup. This object is discussed in more detail later in this section.

package store.some.package;

import atg.payment.*;

public interface StorePointsProcessor
{
  /**
   * Authorize the amount in StorePoints
   *
   * @param pStorePointsInfo the StorePointsInfo reference which contains
   *        all the authorization data
   * @return a StorePointsStatus object detailing the results of the
   *        authorization
   */
  public StorePointsStatus authorize(StorePointsInfo pStorePointsInfo);

  /**
   * Debit the amount in StorePoints after authorization
   *
   * @param pStorePointsInfo the StorePointsInfo reference which contains
   *        all the debit data
   * @param pStatus the StorePointsStatus object which contains
   *        information about the transaction. This should be the object
   *        which was returned from authorize().
   * @return a StorePointsStatus object detailing the results of the debit
   */
  public StorePointsStatus debit(StorePointsInfo pStorePointsInfo,
                                    StorePointsStatus pStatus);

  /**
   * Credit the amount in StorePoints after debiting
   *
   * @param pStorePointsInfo the StorePointsInfo reference which contains
   *        all the credit data
   * @param pStatus the StorePointsStatus object which contains
   *        information about the transaction. This should be the object
   *        which was returned from debit().
   * @return a StorePointsStatus object detailing the results of the
   *        credit
   */
  public StorePointsStatus credit(StorePointsInfo pStorePointsInfo,
                                  StorePointsStatus pStatus);

  /**
   * Credit the amount in StorePoints outside the context of an Order
   *
   * @param pStorePointsInfo the StorePointsInfo reference which contains
   *        all the credit data
   * @return a StorePointsStatus object detailing the results of the
   *        credit
   */
  public StorePointsStatus credit(StorePointsInfo pStorePointsInfo);
}

Second, write an implementation of the StorePointsProcessor interface named StorePointsProcessorImpl. StorePointsProcessorImpl must work with the resources needed to carry out the transactions. For example, if the customer’s points data are stored in a database table, then its methods must operate against that table, reading and writing values to reflect the operation. For a different custom payment method, the implementation must work with whatever 3rd-party resources are needed to carry out the transactions.

The following code sample is taken from the StorePointsProcessorImpl class, an example of an implementation of the StorePointsProcessor interface. You can assume that StorePointsProcessorImpl extends GenericService and, therefore, can use standard Oracle ATG Web Commerce logging calls.

Note in the code sample that the authorize, debit, and credit methods of StorePointsProcessorImpl all return a PaymentStatus object, which represents the results of transaction performed by the pipeline. Recall that a PaymentStatus object contains properties such as amount, errorMessage, transactionId, transactionSuccess, and transactionTimestamp. It is discussed in more detail later in this section.

/**
   * This method will obtain the <code>StorePointsInfo</code> object from
   * the pParams parameter and invoke the
   * {@link #authorize<code>authorize</code>} method.
   *
   * @param pParams PaymentManagerPipelineArgs object which contains the
   *  StorePointsInfo object.
   * @return a PaymentStatus object that will detail the authorize details
   * @exception CommerceException if an error occurs
   */
  public PaymentStatus authorizePaymentGroup(PaymentManagerPipelineArgs
pParams)
    throws CommerceException
  {
    StorePointsInfo spi = null;

    try {
      spi = (StorePointsInfo)pParams.getPaymentInfo();
    }
    catch (ClassCastException cce) {
      if (isLoggingError())
        logError("Expecting class of type StorePointsInfo but got: " +
                  pParams.getPaymentInfo().getClass().getName());

      throw cce;
    }
    return authorize(spi);
  }
/**
   * This method will obtain the <code>StorePointsInfo</code> object from
   * the pParams parameter and invoke the {@link #debit<code>debit</code>}
   * method.
   *
   * @param pParams PaymentManagerPipelineArgs object which contains the
   *  StorePointsInfo and StorePointsStatus objects.
   * @return a PaymentStatus object that will detail the debit details
   * @exception CommerceException if an error occurs
   */
  public PaymentStatus debitPaymentGroup(PaymentManagerPipelineArgs
pParams)
    throws CommerceException
  {
    StorePointsInfo spi = null;

    try {
      spi = (StorePointsInfo)pParams.getPaymentInfo();
    }
    catch (ClassCastException cce) {
      if (isLoggingError())
        logError("Expecting class of type StorePointsInfo but got: " +
                  pParams.getPaymentInfo().getClass().getName());

      throw cce;
    }

    StorePointsStatus authStatus = null;

    PaymentGroup pg = pParams.getPaymentGroup();
    try {
      authStatus = (StorePointsStatus)
pParams.getPaymentManager().getLastAuthorizationStatus(pg);
    }
    catch (ClassCastException cce) {
      if (isLoggingError()) {
        String authStatusClassName =
pParams.getPaymentManager().getLastAuthorizationStatus(pg).getClass().getN
ame();
        logError("Expecting class of type StorePointsStatus but got: " +
                  authStatusClassName);
      }
      throw cce;
    }

    return debit(spi, authStatus);
  }

  /**
   * This method will obtain the <code>StorePointsInfo</code> object from
   * the pParams parameter and invoke the
   * {@link #credit<code>credit</code>} method.
   *
   * @param pParams PaymentManagerPipelineArgs object which contains the
   *  StorePointsInfo, PaymentGroup and StorePointsStatus object.
   * @return a PaymentStatus object that will detail the credit details
   * @exception CommerceException if an error occurs
   */
  public PaymentStatus creditPaymentGroup(PaymentManagerPipelineArgs
pParams)
    throws CommerceException
  {
    StorePointsInfo spi = null;

    try {
      spi = (StorePointsInfo)pParams.getPaymentInfo();
    }
    catch (ClassCastException cce) {
      if (isLoggingError())
        logError("Expecting class of type StorePointsInfo but got: " +
                  pParams.getPaymentInfo().getClass().getName());
      throw cce;
    }

    StorePointsStatus debitStatus = null;

      PaymentGroup pg = pParams.getPaymentGroup();
    try {
      debitStatus = (StorePointsStatus)
pParams.getPaymentManager().getLastDebitStatus(pg);
    }
    catch (ClassCastException cce) {
      if (isLoggingError()) {
        String debitStatusClassName =
pParams.getPaymentManager().getLastDebitStatus(pg).getClass().getName();
        logError("Expecting class of type StorePointsStatus but got: " +
                  debitStatusClassName);
      }

      throw cce;
    }

    return credit(spi, debitStatus);
  }

Third, implement a pipeline processor that performs the payment transactions for the StorePoints PaymentGroup by calling through to StorePointsProcessorImpl. You might call this pipeline processor class ProcProcessStorePoints. Because the implementation will be called within the context of a pipeline, it must also implement the atg.service.pipeline.PipelineProcessor interface. Oracle ATG Web Commerce provides an abstract class that implements both the PipelineProcessor interface and several other helper methods that determine what action is requested (authorize, debit, or credit) and then dispatch to the appropriate method call. This abstract class is atg.commerce.payment.processor.ProcProcessPaymentGroup. By extending ProcProcessPaymentGroup, you only need to define three abstract methods: authorizePaymentGroup(), debitPaymentGroup() and creditPaymentGroup(). These methods should call through to their respective methods in the StorePointsProcessorImpl object, passing in the data from the PaymentManagerPipelineArgs object that is supplied as a parameter. Additionally, ProcProcessStorePoints should include an additional property named storePointsProcessor that can be set to the StorePointsProcessor object that actually performs the payment operations. In this example, ProcProcessStorePoints.storePointsProcessor would be set StorePointsProcessorImpl.

Recall from the previous code example of StorePointsProcessorImpl that the StorePoints PaymentGroup itself is not passed as a parameter to the StorePointsProcessorImpl processor. This keeps the payment processors independent of the commerce objects in Commerce. Instead, before the ProcProcessStorePoints pipeline processor is invoked, a previous pipeline processor must aggregate the necessary information for performing the requested payment action, create an XXXInfo object for use in that action, and finally add the XXXInfo object to the PaymentManagerPipelineArgs Dictionary object. The Dictionary object is then passed as an argument “downstream” to the ProcProcessStorePoints pipeline processor and on to the StorePointsProcessorImpl processor.

In this StorePoints example, the XXXInfo object might be called StorePointsInfo, and the processor that creates it might be called ProcCreateStorePointsInfo. The StorePointsInfo object must hold all of the data required by the methods in StorePointsProcessorImpl. It might hold a user ID (Profile ID) and the number of points for the operation. The following code sample is an example of the StorePointsInfo class.

package store.some.package;

public class StorePointsInfo
{
  public StorePointsInfo() {
  }

  private String mUserId = null;
  public String getUserId() {
    return mUserId;
  }
  public void setUserId(String pUserId) {
    mUserId = pUserId;
  }

  private int mNumberOfPoints = 0;
  public int getNumberOfPoints() {
    return mNumberOfPoints;
  }
  public void setNumberOfPoints(int pNumberOfPoints) {
    mNumberOfPoints = pNumberOfPoints;
  }
}

Next, implement the ProcCreateStorePointsInfo processor that must construct the StorePointsInfo object and add it to the PaymentManagerPipelineArgs Dictionary object. As with the StorePointsProcessorImpl class, the ProcCreateStorePointsInfo class must implement the atg.service.pipeline.PipelineProcessor interface because the implementation will be called within the context of a pipeline. The following code sample is an example of the ProcCreateStorePointsInfo class.

package store.some.package;

import atg.nucleus.GenericService;
import atg.service.pipeline.PipelineProcessor;
import atg.service.pipeline.PipelineResult;
import atg.commerce.order.*;
import atg.commerce.payment.*;

/**
 * This pipeline processor element is called to create generic
 * StorePointsInfo objects from instances of the StorePoints
 * payment group. It places them into the pipeline argument dictionary so
 * that downstream pipeline processors can retrieve them by calling
 * <code>PaymentManagerPipelineArgs.getPaymentInfo()</code>.
 *
 * <p>This processor is designed so that the StorePointsInfo class can
 * easily be extended. See
 * {@link #setStorePointsInfoClass "<code>setStorePointsInfoClass</code>"}
 * and
 * {@link #addDataToStorePoints "<code>addDataToStorePointsInfo</code>"}
 * for more information.
 *
 */

public class ProcCreateStorePointsInfo
  extends GenericService
  implements PipelineProcessor
{

  /** The possible return value for this processor. **/
  public static final int SUCCESS = 1;

  //---------------------------------------------------------------------
  // property: StorePointsInfoClass

  String mStorePointsInfoClass = "store.some.package.StorePointsInfo";

  /**
   * Return the class to instantiate when creating a new StorePointsInfo
   * object.
   **/

  public String getStorePointsInfoClass() {
    return mStorePointsInfoClass;
  }

  /**
   * Specify the class to instantiate when creating a new StorePointsInfo
   * object.  If the <code>StorePointsInfo</code> class is extended to
   * include more information, this property can be changed to reflect the
   * new class.
   **/

  public void setStorePointsInfoClass(String pStorePointsInfoClass) {
    mStorePointsInfoClass = pStorePointsInfoClass;
  }

  //----------------------------------------------------------------------

  /**
   * This method populates the <code>StorePointsInfo</code> object with
   * data. If the additional data is required, a subclass of
   * <code>StorePointsInfo</code> can be created with additional
   * properties, the <code>storePointsInfoClass</code> property can be
   * changed to specify the new class, and this method can be overridden
   * to add data for the new properties (or another pipeline processor
   * could be added after this processor to populate the additional
   * properties).
   *
   * @param pOrder
   *    The order being paid for.
   * @param pPaymentGroup
   *    The payment group being processed.
   * @param pAmount
   *    The amount being authorized, debited, or credited
   * @param pParams
   *    The parameter dictionary passed to this pipeline processor
   * @param pStorePointsInfo
   *    An object that holds information understood by the store
   *    points payment processor.
   **/

  protected void addDataToStorePointsInfo(Order pOrder,
       StorePoints pPaymentGroup, double pAmount,
       PaymentManagerPipelineArgs pParams, StorePointsInfo
       pStorePointsInfo)
  {
    pStorePointsInfo.setUserId(pPaymentGroup.getUserId());
    pStorePointsInfo.setNumberOfPoints(pPaymentGroup.getNumberOfPoints());
  }

  //----------------------------------------------------------------------

  /**
   * Factory method to create a new StorePointsInfo object.  The class
   * that is created is that specified by the
   * <code>storePointsInfoClass</code> property, and must be a subclass
   * of <code>store.some.package.StorePointsInfo</code>
   *
   * @return
   *   An object of the class specified by
   *   <code>storePointsInfoClass</code>
   * @throws Exception
   *   if any instantiation error occurs when creating the info object
   **/

  protected StorePointsInfo getStorePointsInfo()
    throws Exception
  {
    if (isLoggingDebug())
      logDebug("Making a new instance of type: " +
getStorePointsInfoClass());

    StorePointsInfo spi = (StorePointsInfo)
      Class.forName(getStorePointsInfoClass()).newInstance();

    return spi;
  }

  //----------------------------------------------------------------------

  /**
   * Generate a StorePointsInfo object of the class specified by
   * <code>StorePointsInfoClass</code>, populate it with data from a
   * <code>StorePoints</code> payment group by calling
   * <code>addDataToStorePointsInfo</code>, and add it to the pipeline
   * argument dictionary so that downstream pipeline processors can access
   * it.
   *
   * @param pParam
   *   Parameter dictionary of type PaymentManagerPipelineArgs.
   * @param pResult
   *   Pipeline result object, not used by this method.
   * @return
   *   An integer value used to determine which pipeline processor is
   *   called next.
   * @throws Exception
   *   If any error occurs creating or populating the store points info
   *   object.
   **/

  public int runProcess(Object pParam, PipelineResult pResult)
    throws Exception
  {
    PaymentManagerPipelineArgs params =
(PaymentManagerPipelineArgs)pParam;
    Order order = params.getOrder();
    StorePoints storePoints = (StorePoints)params.getPaymentGroup();
    double amount = params.getAmount();

    // create and populate store points info class
    StorePointsInfo spi = getStorePointsInfo();
    addDataToStorePointsInfo(order, storePoints, amount, params, spi);

    if (isLoggingDebug())
      logDebug("Putting StorePointsInfo object into pipeline: " +
spi.toString());

    params.setPaymentInfo(spi);

    return SUCCESS;
  }

  //----------------------------------------------------------------------

  /**
   * Return the possible return values for this processor.  This processor
   * always returns a success code.
   **/

  public int[] getRetCodes() {
    int retCodes[] = {SUCCESS};
    return retCodes;
  }

As previously mentioned, the StorePointsStatus object represents a transaction on a StorePoints PaymentGroup. When the PaymentManager gets this object, it adds it to one of the authorizationStatus, debitStatus, or creditStatus List objects in the PaymentGroup. The specific list to which it is added depends on the operation.

Because none of the StorePointsProcessor methods throw exceptions, all operations must return an object that implements the PaymentStatus interface, as PaymentStatusImpl does. Therefore, when you implement StorePointsStatus, you should extend PaymentStatusImpl, which implements the atg.payment.PaymentStatus interface. Follow the steps in Extending the Purchase Process to ensure that objects are persisted properly. The following table describes the properties in the PaymentStatus interface.

PaymentStatus Property

Type

Description

transactionId

String

A unique ID for the transaction that is generated by the payment processor.

amount

double

The amount of the transaction.

transactionSuccess

boolean

Indicates whether the transaction was successful. True indicates that the transaction succeeded. False indicates that it failed.

errorMessage

String

A detailed error message about the failure.

transactionTimestamp

Date

The time that the transaction was executed.

Below is an example of the StorePointsStatus class. All properties in this object must have values. The most important property is transactionSuccess. If transactionSuccess is false, then an exception is thrown with the message in the errorMessage property.

package store.some.package;

import atg.payment.*;

public class StorePointsStatus extends PaymentStatusImpl
{
  public StorePointsStatus() {
  }

  private String mConfirmationNumber = null;
  public String getConfirmationNumber() {
    return mConfirmationNumber;
  }
  public void setConfirmationNumber(String pConfirmationNumber) {
    mConfirmationNumber = pConfirmationNumber;
  }
}
Integrating the New Payment Processors into the PaymentManager

Integrating the new payment processors that you created in step 2 for the StorePoints PaymentGroup. This involves two steps:

See the sections that follow for details.

Creating the Pipeline

To create the pipeline that creates the StorePointsInfo objects and performs actions on the StorePoints PaymentGroup:

  1. Configure a pipeline processor to create the StorePointsInfo object. To do this, create a Nucleus component for the ProcCreateStorePointsInfo object. To create a Nucleus component located at /store/payment/processor/CreateStorePointsInfo, place the following properties file into Nucleus at that path:

    $class=store.some.package.ProcCreateStorePointsInfo
    $scope=global
    storePointsInfoClass=store.some.package.StorePointsInfo

  2. Configure a pipeline processor to authorize, debit, and credit the StorePoints payment method. To do this, create a Nucleus component for the ProcProcessStorePoints object. To create a Nucleus component located at /store/payment/processor/ProcessStorePoints, place the following properties file into Nucleus at that path:

    $class=store.some.package.ProcProcessStorePoints
    $scope=global
    storePointsProcessor=/store/payment/StorePointsProcessorImpl

    Note that the ProcessStorePoints.storePointsProcessor property is set to the StorePointsProcessor object that actually performs the payment operations. ProcessStorePoints calls through to this object to perform the operations. In this example, it would be set to the StorePointsProcessorImpl Nucleus component.

  3. Create a Nucleus component for the StorePointsProcessorImpl object. This is the object that actual performs the payment operations on the StorePoints payment method. To create a Nucleus component located at /store/payment/StorePointsProcessor, place the following properties file into Nucleus at that path:

    $class=store.some.package.StorePointsProcessorImpl
    $scope=global

  4. Define the storePointsProcessorChain pipeline and add it to the pipelines used by the /atg/commerce/payment/PaymentPipelineManager. To do this, create a paymentpipeline.xml file in Nucleus at /atg/commerce/payment/paymentpipeline.xml. This XML file should define the single pipeline that operates on StorePoints PaymentGroups; it will be combined with the existing XML definition of payment pipelines using XML file combination.

    The following is a code example of the storePointsProcessorChain pipeline:

<pipelinemanager>
<!-- This chain is used to process a store point payment group-->
<!-- This single chain knows how to auth/credit/debit a        -->
<!-- payment group.  It also creates the StorePointInfo object-->

<pipelinechain name="storePointProcessorChain" transaction="TX_REQUIRED"
headlink="createStorePointInfo">
  <pipelinelink name="createStorePointInfo" transaction="TX_MANDATORY">
    <processor jndi="/store/payment/processor/CreateStorePointInfo"/>
    <transition returnvalue="1" link="processStorePoints"/>
  </pipelinelink>
  <pipelinelink name="processStorePoints" transaction="TX_MANDATORY">
    <processor jndi="/store/payment/processor/ProcessStorePoints"/>
  </pipelinelink>
</pipelinechain>
</pipelinemanager>

Configure the PaymentManager to Invoke the StorePointsProcessorChain

To configure the PaymentManager to invoke the storePointsProcessorChain pipeline when an operation is requested on a StorePoints PaymentGroup, you need to add a new entry to PaymentManager.paymentGroupToChainNameMap. The paymentGroupToChainNameMap property stores a mapping of PaymentGroup class names to the names of the pipelines to invoke when an operation for those PaymentGroups is requested.

To add a new entry to PaymentManager.paymentGroupToChainNameMap, layer on a configuration file that makes an additional entry to the paymentGroupToChainNameMap property. The new configuration file would be located in Nucleus at /atg/commerce/payment/PaymentManager and would look like the following:

PaymentGroupToChainNameMap+=Store.some.package.StorePoints=storePointsProc
essorChain

Copyright © 1997, 2012 Oracle and/or its affiliates. All rights reserved.

Legal Notices