Implementing the payment plug-in API methods

In this step, you will implement the payment plug-in API methods in the bean class of the payment plug-in session bean.

About this task

The following table summarizes the methods and how they should be implemented:

Method Implementation summary
checkPaymentInstruction The implementation of this method should ensure that the payment instruction has all the required attributes and that the attributes are syntactically valid.
validatePaymentInstruction The implementation of this method should call the payment service provider to validate the account referenced by the payment instruction. Note that WebSphere Commerce does not call this method by default.
approve The implementation of this method should call the payment service provider to authorize the payment.
deposit The implementation of this method should call the payment service provider to settle the payment.
approveAndDeposit The implementation of this method should call the payment service provider to authorize and settle the payment in a single transaction.
credit The implementation of this method should call the payment service provider to process the refund.
reverseApproval The implementation of this method should call the payment service provider to cancel the authorization.
reverseDeposit The implementation of this method should call the payment service provider to cancel the settlement.
reverseCredit The implementation of this method should call the payment service provider to cancel the refund.
checkHealth The implementation of this method should check whether the payment plug-in is configured properly or not.
getMessage The implementation of this method should return the error message corresponding to the message key.

To edit the bean class of the payment plug-in session bean:

Procedure

  1. Open WebSphere Commerce Developer.
  2. Select the Java EE perspective.
  3. In the Enterprise Explorer view, expand MyPaymentPlugin > ejbModule > com.mycompany.payment.plugin; right-click MyPaymentPluginBean.java and select Open.

Results

In the MyPaymentPluginBean.java editor ensure the following steps are taken:

  1. Add the following import statements:
    
    import java.math.BigDecimal;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.rmi.RemoteException;
    import java.util.Calendar;
    
    import javax.xml.rpc.ServiceException;
    
    import test.CardData;
    import test.PSPSimServiceLocator;
    import test.TransactionData;
    
    import com.ibm.commerce.key.ECKeyManager;
    import com.ibm.commerce.payments.plugin.CommunicationException;
    import com.ibm.commerce.payments.plugin.ConfigurationException;
    import com.ibm.commerce.payments.plugin.ExtendedData;
    import com.ibm.commerce.payments.plugin.FinancialException;
    import com.ibm.commerce.payments.plugin.FinancialTransaction;
    import com.ibm.commerce.payments.plugin.FunctionNotSupportedException;
    import com.ibm.commerce.payments.plugin.InternalErrorException;
    import com.ibm.commerce.payments.plugin.InvalidDataException;
    import com.ibm.commerce.payments.plugin.InvalidPaymentInstructionException;
    import com.ibm.commerce.payments.plugin.PaymentInstruction;
    import com.ibm.commerce.payments.plugin.PluginContext;
    import com.ibm.commerce.payments.plugin.PluginException;
    import com.ibm.commerce.payments.plugin.PluginMessages;
    
  2. Add the following method:
    
    private String generateTransactionId() throws PluginException {
     try {
      ECKeyManager km = ECKeyManager.singleton();
      return km.getNextKey("MyPaymentPlugin").toString();
     }
     catch(Exception e) {
      throw new InternalErrorException(e);
     }
    }
    

    This method is used to generate tracking numbers by calling the WebSphere Commerce Key Manager. Tracking numbers are used by the financial transaction methods to track financial transactions across multiple systems.

  3. Implement the payment plug-in API methods as follows:

    checkPaymentInstruction

    You will implement the checkPaymentInstruction method to check the payment instruction attributes required by the Payment Service Provider Simulator, including account (credit card number), cc_brand (credit card brand), expire_month (expiry month) and expire_year (expiry year):

    
    public void checkPaymentInstruction(PluginContext pluginContext,
      PaymentInstruction paymentInstruction) throws PluginException {
     ExtendedData piData = paymentInstruction.getExtendedData();
     String cardBrand = piData.getString(PaymentInstruction.CC_BRAND);
     String cardNumber = paymentInstruction.getAccount();
     String expiryMonth = piData.getString(PaymentInstruction.EXPIREMONTH);
     String expiryYear = piData.getString(PaymentInstruction.EXPIREYEAR);
     if(cardBrand == null || cardNumber == null || cardNumber.length() != 16) {
      InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
      e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      e.setMessageKey("PLUGIN_INVALID_CARD_NUMBER");
      throw e;
     }
     if(expiryMonth == null || expiryYear == null) {
      InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
      e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      e.setMessageKey("PLUGIN_INVALID_EXPIRY_DATE");
      throw e;
     }
     try {
      int m = Integer.parseInt(expiryMonth);
      int y = Integer.parseInt(expiryYear);
      Calendar cal = Calendar.getInstance();
      if(y < cal.get(Calendar.YEAR) || y == cal.get(Calendar.YEAR) && m < cal.get(Calendar.MONTH) + 1) {
       InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_INVALID_EXPIRY_DATE");
       throw e;
      }
     }
     catch(NumberFormatException e) {
      InvalidPaymentInstructionException ee = new InvalidPaymentInstructionException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_INVALID_EXPIRY_DATE");
      throw ee;
     }
    }
    
    Note:
    • The credit card number is accessed by calling paymentInstruction.getAccount().
    • Other payment instruction attributes are accessed by calling paymentInstruction.getExtendedData().getString(s), where s is the name of the attribute For example, the credit card brand is accessed by calling paymentInstruction.getExtendedData().getString("cc_brand").
    • The InvalidPaymentInstructionException is thrown when any of the payment instruction attributes is missing or invalid.

    validatePaymentInstruction

    You will implement the validatePaymentInstruction method to call the Payment Service Provider Simulator through its Web service client to simulate the validation of the credit card account:

    
    public void validatePaymentInstruction(PluginContext pluginContext,
      PaymentInstruction paymentInstruction) throws PluginException {
     ExtendedData piData = paymentInstruction.getExtendedData();
     try {
      CardData cardData = new PSPSimServiceLocator().getPSPSim().getCardStatus(piData.getString(PaymentInstruction.CC_BRAND), paymentInstruction.getAccount());
      if(cardData == null) {
       InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_INVALID_CARD_NUMBER");
       throw e;
      }
      String expiryDate = piData.getString(PaymentInstruction.EXPIREMONTH) + piData.getString(PaymentInstruction.EXPIREYEAR).substring(2);
      if(!expiryDate.equals(cardData.getExpiryDate())) {
       InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_INCORRECT_EXPIRY_DATE");
       throw e;
      }
      if(!cardData.getStatus().equals("A")) {
       InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_CARD_SUSPENDED");
       throw e;
      }
      if(cardData.getCreditLimit().compareTo(new BigDecimal(0)) == 0) {
       InvalidPaymentInstructionException e = new InvalidPaymentInstructionException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_CREDIT_LIMIT_EXCEEDED");
       throw e;
      }
     }
     catch(ServiceException e) {
      CommunicationException ee = new CommunicationException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_COMMUNICATION_EXCEPTION");
      throw ee;
     }
     catch(RemoteException e) {
      InternalErrorException ee = new InternalErrorException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_INTERNAL_ERROR_EXCEPTION");
      throw ee;
     }
    }
    
    Note:

    approve

    You will implement the approve method to call the Payment Service Provider Simulator Web service client to simulate the authorization of the payment:

    
    public FinancialTransaction approve(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     String txId = generateTransactionId();
     financialTransaction.setTrackingId(txId);
     try {
      PaymentInstruction pi = financialTransaction.getPayment().getPaymentInstruction();
      ExtendedData piData = pi.getExtendedData();
      TransactionData txData = new TransactionData();
      txData.setMerchantId((String)pluginContext.getConfiguration().getProperties().get("merchantId"));
      txData.setTransactionId(txId);
      txData.setTransactionType("A");
      txData.setCardBrand(piData.getString(PaymentInstruction.CC_BRAND));
      txData.setCardNumber(pi.getAccount());
      txData.setExpiryDate(piData.getString(PaymentInstruction.EXPIREMONTH) + piData.getString(PaymentInstruction.EXPIREYEAR).substring(2));
      txData.setAmount(financialTransaction.getRequestedAmount());
      txData.setCurrency(pi.getCurrency());
      txData = new PSPSimServiceLocator().getPSPSim(new URL((String)pluginContext.getConfiguration().getProperties().get("url"))).processTransaction(txData);
      String rc = txData.getReturnCode();
      financialTransaction.setResponseCode(rc);
      if(rc.equals("0000")) {
       financialTransaction.setProcessedAmount(txData.getAmount());
       financialTransaction.setReferenceNumber(txData.getAuthorizationNumber());
      }
      else {
       financialTransaction.setProcessedAmount(new BigDecimal(0));
       InvalidDataException e = new InvalidDataException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_RC_" + rc);
       e.setFinancialTransaction(financialTransaction);
       throw e;
      }
     }
     catch(MalformedURLException e) {
      ConfigurationException ee = new ConfigurationException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_CONFIGURATION_EXCEPTION");
      throw ee;
     }
     catch(ServiceException e) {
      CommunicationException ee = new CommunicationException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_COMMUNICATION_EXCEPTION");
      throw ee;
     }
     catch(RemoteException e) {
      InternalErrorException ee = new InternalErrorException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_INTERNAL_ERROR_EXCEPTION");
      throw ee;
     }
     return financialTransaction;
    }
    
    Notes:
    • The tracking number returned by the generateTransactionId method is used to track the financial transaction across multiple systems. The same tracking number is sent to the Payment Service Provider Simulator and later passed back to WebSphere Commerce by calling financialTransaction.setTrackingId(s), where s is the tracking number.
    • The credit card number and other payment instruction attributes are accessed by calling financialTransaction.getPayment().getPaymentInstruction() to access the payment instruction.
    • The requested amount and currency are accessed by calling financialTransaction.getRequestedAmount() and financialTransaction.getPayment().getPaymentInstruction().getCurrency().
    • Payment plug-in and merchant configuration properties are accessed by calling pluginContext.getConfiguration().getProperties().get(s), where s is the name of the configuration property.
    • The authorized amount returned by the Payment Service Provider Simulator is passed back to WebSphere Commerce by calling financialTransaction.setProcessedAmount(n), where n is the authorized amount.
    • The authorization number returned by the Payment Service Provider Simulator is passed back to WebSphere Commerce by calling financialTransaction.setReferenceNumber(s), where s is the authorization number.
    • The response code returned by the Payment Service Provider Simulator is passed back to WebSphere Commerce by calling financialTransaction.setResponseCode(s), where s is the response code.
    • The FinancialException or InvalidDataException is thrown when the authorization is failed by the payment service provider. The difference between the two exceptions is that the transaction that initiated the payment authorization rolls back for the InvalidDataException, but not for the FinancialException.
    • The ConfigurationException is thrown when the payment plug-in is not configured properly.
    • The CommunicationException is thrown when there are problems communicating with the Payment Service Provider Simulator (for example the Payment Service Provider Simulator is down).
    • The InternalErrorException is thrown for other system errors.

    deposit

    You will implement the deposit method to call the Payment Service Provider Simulator Web service client to simulate the settlement of the payment:

    
    public FinancialTransaction deposit(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     String txId = generateTransactionId();
     financialTransaction.setTrackingId(txId);
     try {
      PaymentInstruction pi = financialTransaction.getPayment().getPaymentInstruction();
      ExtendedData piData = pi.getExtendedData();
      TransactionData txData = new TransactionData();
      txData.setMerchantId((String)pluginContext.getConfiguration().getProperties().get("merchantId"));
      txData.setTransactionId(txId);
      txData.setTransactionType("D");
      txData.setCardBrand(piData.getString(PaymentInstruction.CC_BRAND));
      txData.setCardNumber(pi.getAccount());
      txData.setExpiryDate(piData.getString(PaymentInstruction.EXPIREMONTH) + piData.getString(PaymentInstruction.EXPIREYEAR).substring(2));
      txData.setAmount(financialTransaction.getRequestedAmount());
      txData.setCurrency(pi.getCurrency());
      txData.setAuthorizationNumber(financialTransaction.getPayment().getApproveTransaction().getReferenceNumber());
      txData = new PSPSimServiceLocator().getPSPSim(new URL((String)pluginContext.getConfiguration().getProperties().get("url"))).processTransaction(txData);
      String rc = txData.getReturnCode();
      financialTransaction.setResponseCode(rc);
      if(rc.equals("0000")) {
       financialTransaction.setProcessedAmount(txData.getAmount());
      }
      else {
       financialTransaction.setProcessedAmount(new BigDecimal(0));
       FinancialException e = new FinancialException();
       e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
       e.setMessageKey("PLUGIN_RC_" + rc);
       e.setFinancialTransaction(financialTransaction);
       throw e;
      }
     }
     catch(MalformedURLException e) {
      ConfigurationException ee = new ConfigurationException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_CONFIGURATION_EXCEPTION");
      throw ee;
     }
     catch(ServiceException e) {
      CommunicationException ee = new CommunicationException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_COMMUNICATION_EXCEPTION");
      throw ee;
     }
     catch(RemoteException e) {
      InternalErrorException ee = new InternalErrorException(e);
      ee.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
      ee.setMessageKey("PLUGIN_INTERNAL_ERROR_EXCEPTION");
      throw ee;
     }
     return financialTransaction;
    }
    
    Note:
    • The authorization number previously returned by the Payment Service Provider Simulator is accessed by calling financialTransaction.getPayment().getApproveTransaction().getReferenceNumber().

    Other financial transaction methods

    You will declare other financial transaction methods, including approveAndDeposit, credit, reverseApproval, reverseDeposit and reverseCredit, as unsupported for the sake of simplicity:

    
    public FinancialTransaction approveAndDeposit(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     FunctionNotSupportedException e = new FunctionNotSupportedException();
     e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
     e.setMessageKey("PLUGIN_FUNCTION_NOT_SUPPORTED_EXCEPTION");
     throw e;
    }
    public FinancialTransaction credit(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     FunctionNotSupportedException e = new FunctionNotSupportedException();
     e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
     e.setMessageKey("PLUGIN_FUNCTION_NOT_SUPPORTED_EXCEPTION");
     throw e;
    }
    public FinancialTransaction reverseApproval(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     FunctionNotSupportedException e = new FunctionNotSupportedException();
     e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
     e.setMessageKey("PLUGIN_FUNCTION_NOT_SUPPORTED_EXCEPTION");
     throw e;
    }
    public FinancialTransaction reverseDeposit(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     FunctionNotSupportedException e = new FunctionNotSupportedException();
     e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
     e.setMessageKey("PLUGIN_FUNCTION_NOT_SUPPORTED_EXCEPTION");
     throw e;
    }
    public FinancialTransaction reverseCredit(PluginContext pluginContext,
      FinancialTransaction financialTransaction, boolean retry)
      throws PluginException {
     FunctionNotSupportedException e = new FunctionNotSupportedException();
     e.setResourceBundleName("com.mycompany.payment.plugin.MyPaymentPluginMessages");
     e.setMessageKey("PLUGIN_FUNCTION_NOT_SUPPORTED_EXCEPTION");
     throw e;
    }
    

    checkHealth

    You will implement the checkHealth method to simply return true:

    
    public boolean checkHealth() {
     return true;
    }
    

    getMessage

    You will implement the getMessage method to return the error message corresponding to the message key in the error message properties file:

    
    public String getMessage(PluginContext pluginContext, String messageKey)
      throws PluginException {
     return PluginMessages.getMessage("com.mycompany.payment.plugin.MyPaymentPluginMessages", messageKey, null, pluginContext.getLocale(), null);
    }
    

When you are done, close the MyPaymentPluginBean.java editor and save the changes.

The finished MyPaymentPluginBean.java can be found here.