/*
* SAEFTCreditTenderActionImpl
*
* 12/30/2002
*
* Copyright:
* Licensed Materials - Property of IBM
* "Restricted Materials of IBM"
* 5724-AEF
* (C) Copyright IBM Corp. 2003.
*
* %W% %E%
*/
package com.ibm.retail.AEF.action;
import com.ibm.retail.AEF.automation.*;
import com.ibm.retail.AEF.util.*;
import com.ibm.retail.si.util.*;
import com.ibm.retail.si.Copyright;
import com.ibm.retail.AEF.factory.*;
import com.ibm.retail.AEF.thread.*;
import com.ibm.retail.AEF.data.*;
import com.ibm.retail.AEF.session.*;
import com.ibm.retail.AEF.event.*;
import java.util.*;
import java.rmi.*;
import java.text.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* SAEFTCreditTenderActionImpl is a class which the POSAutomationProvider uses to accomplish
* adding an EFT credit tender into a transaction on the POS application.
*
*/
public class SAEFTCreditTenderActionImpl extends SAActionImpl
implements AEFPropertyChangeListener
{
static String copyright()
{ return com.ibm.retail.si.Copyright.IBM_COPYRIGHT_SHORT; }
private static Log log = LogFactory.getLog(SAEFTCreditTenderActionImpl.class);
/**
* Constructor
*
* We assume that a non-null amount is provided and that the format of the
* amount has been validated (i.e., numeric, with or without currency symbols).
* We also assume that a non-null account number is provided and that the
* expiration date is valid if it has been provided.
*
* @param request The ActionRequest which contains a HashMap of arguments.
* @exception com.ibm.retail.AEF.util.AEFException
* AEFException return codes are:
*
AEFConst.INVALID_ARGUMENT, AEFConst.ACCOUNT_NUMBER_REQUIRED
*
AEFConst.INVALID_ARGUMENT, AEFConst.INVALID_DATE
*
AEFConst.INVALID_ARGUMENT, AEFConst.INVALID_TENDER_TYPE
*
*/
public SAEFTCreditTenderActionImpl(ActionRequest request) throws AEFException
{
super(request);
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.SAEFTCreditTenderActionImpl().");
log.trace(tempAEFMessage);
}
useTrackData = false;
// validSubstates contains a list of all the substates for which
// an item may be entered. This is a class static, so it is only
// initialized once.
if (validSubstates == null)
{
validSubstates = new Vector();
validSubstates.addElement(Substate.getSubstate("EXPECTING_ITEM"));
validSubstates.addElement(Substate.getSubstate("TRANS_TOTALLED"));
validSubstates.addElement(Substate.getSubstate("CHANGE_OR_BAL_DUE"));
// This next one is bogus, but there is a bug in SA which does not
// reset the substate after an eft error is cleared, which means
// we could be in this substate, and it is still valid to attempt an
// eft credit.
validSubstates.addElement(Substate.getSubstate("WAITING_ON_HOST_RESPONSE"));
}
TenderIdentifier tenderID = (TenderIdentifier)(request.getArguments());
amount = tenderID.getAmount();
amount = amount.trim();
// amount, account number, and expiration date have already been validated by our caller
// Strip out any non-numeric portion of the amount (currency symbol & decimal separator)
amount = AEFUtilities.formatNumericOnly(amount);
String cardType = null;
if (tenderID instanceof CreditIdentifier)
{
CreditIdentifier creditID = (CreditIdentifier)(tenderID);
accountNumber = creditID.getAccountNumber();
expDate = creditID.getExpirationDate();
cardType = creditID.getCardType();
}
else if (tenderID instanceof MSRCreditIdentifier)
{
MSRCreditIdentifier msrCreditID = (MSRCreditIdentifier)(tenderID);
track1Data = msrCreditID.getTrack1Data();
track2Data = msrCreditID.getTrack2Data();
track3Data = msrCreditID.getTrack3Data();
cardType = msrCreditID.getCardType();
useTrackData = true;
}
// Try to determine the tender variety if a card type was given.
if (cardType != null && !cardType.equals(CreditIdentifier.UNKNOWN) && cardType != "")
{
// A card type was specified. Use the tender mapper to try to find out the
// tender variety.
String tenderKey = TenderMapperImpl.getInstance().getKey(cardType);
if (tenderKey == null)
{
String tempString = "SAEFTCreditTenderActionImpl(" + tenderID + "): Credit card type \"" + cardType + "\" doesn't match any tender description in tendermap.properties.";
AEFException tempException = new AEFException(AEFConst.INVALID_ARGUMENT, AEFConst.INVALID_TENDER_TYPE, tempString);
tempAEFMessage.setMessage(tempString);
log.error(tempAEFMessage, tempException);
throw tempException;
}
else
{
tenderVariety = TenderMapperImpl.getInstance().getVariety(tenderKey);
}
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.SAEFTCreditTenderActionImpl().");
log.trace(tempAEFMessage);
}
}
/**
* Perform the action represented by the ActionRequest and return an ActionResult.
*
*
* @param request The ActionRequest which contains the classname and arguments.
* @return Object The Item instance.
* @exception com.ibm.retail.AEF.util.AEFException
* Among the possible AEFException error codes are:
*
AEFConst.INVALID_ARGUMENT, AEFConst.INVALID_TENDER_TYPE
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.ACCOUNT_TENDER_LIMIT
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.CHANGE_AMOUNT_LIMIT
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.NUMBER_OF_TENDERS_LIMIT
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.STAND_IN_COUNT_LIMIT
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.STAND_IN_AMOUNT_LIMIT
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.TENDER_FLOOR_LIMIT
*
AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.CARD_EXPIRED
*
AEFConst.MSR_HOOK_SWIPE_ERROR, AEFConst.MSR_SET_TO_DECODE
*
AEFConst.MSR_HOOK_SWIPE_ERROR, AEFConst.MSR_NOT_ENABLED
*
AEFConst.JAVA_POS_EXCEPTION, see exception cause() for actual javaPos exception
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.TENDER_NOT_AUTHORIZED
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_1
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_2
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_3
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_4
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.VERIFICATION_TIMEOUT
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.CREDIT_NOT_AVAILABLE
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.TOO_LONG_IN_STANDIN
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.PAYMENT_SYSTEM_OFFLINE
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.UNKNOWN_SERVICER
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.SERVICER_CLOSED
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.CARD_TYPE_UNKNOWN
*
AEFConst.INVALID_MANAGER_OVERRIDE_NUMBER
*
AEFConst.KEYLOCK_ERROR, AEFConst.MANAGER_KEY_REQUIRED
*
AEFConst.INVALID_ARGUMENT
*
AEFConst.PROCEDURE_NOT_ALLOWED, AEFConst.EXTERNAL_TENDER_AUTHORIZATION_SUSPENDED
*
AEFConst.TENDER_NOT_ACCEPTED, AEFConst.UNKNOWN_SERVICER
*
Common Errors
*/
public Object performAction() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.performAction().");
log.trace(tempAEFMessage);
}
super.performAction(); // Call super to perform any common processing and clear any errors before we start.
Object retVal = null;
// If app is in secured state, and options are set to automatically do a special
// signon, then create the logon action to do the special signon first.
checkForSpecialSignon();
int seqResult = -1;
//Check precondition that app is in one of the substates which is valid for item entry.
//If not, throw exception.
String currentSubstate = getCurrentSubstate();
if (validSubstates.contains(currentSubstate))
{
// Application is in correct substate for tendering.
// Check if we need to send the total first.
if (currentSubstate.equals(Substate.getSubstate("EXPECTING_ITEM")) ||
currentSubstate.equals(Substate.getSubstate("CHANGE_OR_BAL_DUE")))
{
seqResult = sendTotalSequence();
}
seqResult = sendTenderSequence();
if (seqResult==0)
{
// B048 ACCOUNT NUMBER NEEDED
// This is the normal path.
waitForClearState();
seqResult = sendClearAccountNumberGuidanceSequence();
if (seqResult >= 0)
{
// ENTER ACCOUNT NUMBER OVERRIDE OR CLEAR
if (useTrackData)
{
seqResult = swipeMSR(track1Data, track2Data, track3Data);
if (seqResult == 0)
{
// B531 APPROVED.
// Success, wait for Tender
// If we tendered out the transaction, we may get a
// REMOVE RECEIPT FOR CUSTOMER message we have to handle.
seqResult = waitForNextStateAfterTender();
retVal = waitForTender();
}
}
else
{
seqResult = sendExpiryDateSequence();
if (seqResult >= 0)
{
// Success
seqResult = sendAccountNumberSequence();
if (seqResult == 0)
{
// B531 APPROVED.
// Success, wait for Tender
// If we tendered out the transaction, we may get a
// REMOVE RECEIPT FOR CUSTOMER message we have to handle.
seqResult = waitForNextStateAfterTender();
retVal = waitForTender();
}
}
}
}
}
}
else
{
// Not in valid state for item entry.
String tempString = "SAEFTCreditTenderActionImpl.performAction(): POS application not in proper substate to tender credit.\n Application substate is " + currentSubstate + ".";
AEFException tempException = new AEFException(AEFConst.APPLICATION_NOT_IN_PROPER_STATE, 0, tempString);
tempAEFMessage.setMessage(tempString);
log.error(tempAEFMessage, tempException);
throw tempException;
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.performAction().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the key sequence for the credit tender amount.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception com.ibm.retail.AEF.util.AEFException
*/
public int sendTenderSequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.sendTenderSequence().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
StringBuffer sequenceIdBuf = new StringBuffer();
if (tenderVariety != null)
{
sequenceIdBuf.append("tenderVariety+");
args.put("%tenderVariety.0", tenderVariety);
}
sequenceIdBuf.append("creditAmount");
args.put("%creditAmount.0", amount);
args.put("SEQUENCE_ID", new String(sequenceIdBuf));
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
Condition b048Conditions[] =
{
// Good condition is to be in clear state with a B048 showing.
new PropertyContainsAtIndexCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1,
"B048",
0), // 0 = B048 account number needed (success)
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("CLEAR")),
};
Condition goodConditions[] =
{
new AndCondition(b048Conditions),
};
Condition andConditions[] =
{
// One error condition is that we are in state 1, and have a Bxxx displayed, but it
// is not B048.
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("CLEAR")),
new PropertyNotContainsAtIndexCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1,
"B048",
0), // B048 ACCOUNT NUMBER NEEDED
new PropertyRegexMatchCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1,
"B\\d\\d\\d.*"),
};
Condition badConditions[] =
{
new AndCondition(andConditions),
new PropertyContainsAtIndexCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1,
"B013",
0),
new PropertyContainsAtIndexCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1,
"B049",
0),
};
lock = new ConditionLock();
detector = ((DetectorAccess)(SessionContext.getSession())).getTenderDetector();
// Save any current tender instance number so we are sure to only get a tender
// created after this key sequence is sent.
instanceNumber = detector.getInstanceNumber();
retVal = lock.performActionAndWait("wait-for-account-number-guidance-prompt",
keySequenceAction,
goodConditions,
badConditions,
getTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Wait for account number guidance prompt (B048) Prompt Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
badConditions,
getTimeout(),
retVal);
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.sendTenderSequence().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the key sequence to clear the B048 guidance.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int sendClearAccountNumberGuidanceSequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.sendClearAccountNumberGuidanceSequence().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
args.put("SEQUENCE_ID", "clear-error");
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
Condition goodConditions[] =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ACCT_NUMBER")), // State 7
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ACCT_OVERRIDE")), // State 8
};
lock = new ConditionLock();
retVal = lock.performActionAndWait("wait-for-acct-override-state",
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Send Clear Account Number Guidance Sequence Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout(),
retVal);
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.sendClearAccountNumberGuidanceSequence().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the key sequence to clear the B063 guidance.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int sendClearGuidanceSequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.sendClearGuidanceSequence().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
args.put("SEQUENCE_ID", "clear-error");
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
AbstractPropertyCondition[] conditions =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("CLEAR")), // 0 = A stacked error.
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("OVERRIDE")) // 1 = override state
};
lock = new ConditionLock();
retVal = lock.performActionAndWait("wait-for-override-state",
keySequenceAction,
conditions,
getTimeout());
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.sendClearGuidanceSequence().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the expiration date for the credit tender.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int sendExpiryDateSequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.sendExpiryDateSequence().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
args.put("%0", expDate);
args.put("SEQUENCE_ID", "expiryDate");
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
Condition goodConditions[] =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ACCT_NUMBER")), // State 7
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ACCT_OVERRIDE")), // State 8
};
lock = new ConditionLock();
retVal = lock.performActionAndWait("wait-for-enter-account-state",
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Send Expiry Date Sequence Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout(),
retVal);
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.sendExpiryDateSequence().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the account number for the credit tender.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int sendAccountNumberSequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.sendAccountNumberSequence().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
args.put("%0", accountNumber);
args.put("SEQUENCE_ID", "accountNumber");
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
try
{
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_CREDIT_APPROVED,
"false");
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in sendAccountNumberSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
Condition goodConditions[] =
{
new PropertyEqualsCondition(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_CREDIT_APPROVED,
"true"), // success
};
lock = new ConditionLock();
try
{
dataProvider.addAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in sendAccountNumberSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
try
{
retVal = lock.performActionAndWait("wait-for-LAST-CREDIT-APPROVED",
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getExtendedBadConditions(),
getPaymentHostTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Send Account Number Sequence Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getExtendedBadConditions(),
getTimeout(), retVal);
}
}
catch (AEFException ex)
{
try
{
dataProvider.removeAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in sendAccountNumberSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
tempAEFMessage.setMessage("There was an AEF exception thrown in sendAccountNumberSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, ex);
throw ex;
}
try
{
dataProvider.removeAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in sendAccountNumberSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.sendAccountNumberSequence().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Injects the track data to make it look like it came from the MSR.
*
*
* @return int An integer value representing the result of the operation.
* @exception AEFException
*/
public int swipeMSR(byte[] track1Data, byte[] track2Data, byte[] track3Data) throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.swipeMSR().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
args.put("TRACK1DATA", track1Data);
args.put("TRACK2DATA", track2Data);
args.put("TRACK3DATA", track3Data);
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SwipeMSRAction", args)));
try
{
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_CREDIT_APPROVED,
"false");
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in swipeMSR of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
Condition goodConditions[] =
{
new PropertyEqualsCondition(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_CREDIT_APPROVED,
"true"), // success
};
lock = new ConditionLock();
try
{
dataProvider.addAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in swipeMSR of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
try
{
retVal = lock.performActionAndWait("wait-for-LAST-CREDIT-APPROVED",
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getExtendedBadConditions(),
getPaymentHostTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Swipe MSR Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getExtendedBadConditions(),
getTimeout(), retVal);
}
}
catch (AEFException ex)
{
try
{
dataProvider.removeAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in swipeMSR of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
tempAEFMessage.setMessage("There was an AEF exception thrown in swipeMSR of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, ex);
throw ex;
}
try
{
dataProvider.removeAEFPropertyChangeListener(this,
POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in swipeMSR of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.swipeMSR().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Sends the "total" key sequence.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int sendTotalSequence() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.sendTotalSequence().");
log.trace(tempAEFMessage);
}
int retVal = -1;
args.clear();
args.put("SEQUENCE_ID", "total");
keySequenceAction = (AEFAction)
(actionFactory.makeAction(new ActionRequest("SimpleKeySequenceAction", args)));
Condition andConditions[] =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("TRANS_TOTALLED")),
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("MAIN")),
new PropertyEqualsCondition(WorkstationStatusProperties.CATEGORY,
"UnitOfWorkReceived",
"true"),
};
Condition goodConditions[] =
{
new AndCondition(andConditions),
};
try
{
dataProvider.setPropertyValue(WorkstationStatusProperties.CATEGORY,
"UnitOfWorkReceived",
"false");
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in sendTotalSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
try
{
dataProvider.addAEFPropertyChangeListener(this,
WorkstationStatusProperties.CATEGORY,
WorkstationStatusProperties.UNIT_OF_WORK);
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in sendTotalSequence of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
lock = new ConditionLock();
retVal = lock.performActionAndWait("wait-for-trans-total-substate-and-main-state",
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout());
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("Send Total Sequence Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getBadConditions(),
getTimeout(),
retVal);
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.sendTotalSequence().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Waits for the next states after the tender was successful. This is necessary
* because the transaction may have been tendered out, in which case we will
* get a "REMOVE RECEIPT FOR CUSTOMER" prompt which must be cleared.
*
*
* @return int An integer value representing the result of the key sequence.
* @exception AEFException
*/
public int waitForNextStateAfterTender() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.waitForNextStateAfterTender().");
log.trace(tempAEFMessage);
}
int retVal = -1;
Condition[] positiveBalanceConditions =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("CHANGE_OR_BAL_DUE")),
new PropertyGreaterThanCondition(TransactionTotalsProperties.CATEGORY,
TransactionTotalsProperties.AMOUNT_DUE,
0),
};
Condition[] balanceSatisfiedConditions =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("CHANGE_OR_BAL_DUE")),
new PropertyLessOrEqualCondition(TransactionTotalsProperties.CATEGORY,
TransactionTotalsProperties.AMOUNT_DUE,
0),
};
Condition[] goodConditions =
{
new AndCondition(positiveBalanceConditions),
new AndCondition(balanceSatisfiedConditions),
};
lock = new ConditionLock();
retVal = lock.wait("wait-for-next-state-after-tender-accepted",
goodConditions,
BadConditionsImpl.getInstance().getExtendedBadConditions(),
getTimeout(),
true);
if (retVal < 0)
{
AEFErrorHandler errorHandler = new AEFErrorHandler("After Tender Sequence Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions,
BadConditionsImpl.getInstance().getExtendedBadConditions(),
getTimeout(),
retVal);
}
// If the balance satisfied condition was met, then we need to wait
// for the "REMOVE RECEIPT FOR CUSTOMER" message which is substate 30020,
// or if this prompt is not active, we may wait on the SALES_TRANSACTION_IN_PROGRESS
// property to be turned off. We may also get the prompt for duplicate receipt.
// This message needs to be handled by the error handler.
if (retVal == 1)
{
boolean keepGoing = true;
while (keepGoing)
{
Condition andConditions[] =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ENTER_CLEAR")),
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("PROMPT_FOR_DUPLICATE_RECEIPT")),
};
Condition[] waitForConditions =
{
new PropertyEqualsCondition(WorkstationStatusProperties.CATEGORY,
WorkstationStatusProperties.SALES_TRANSACTION_IN_PROGRESS,
"false"),
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("REMOVE_RECEIPT_FOR_CUSTOMER")),
new AndCondition(andConditions),
};
retVal = lock.wait("wait-for-remove-receipt-prompt-or-dup-reciept-prompt-or-end-of-trans",
waitForConditions,
getTimeout(),
true);
if (retVal == 0)
{
keepGoing = false;
}
else if (retVal == 1)
{
// Wait for the "REMOVE RECEIPT FOR CUSTOMER" prompt to appear in clear state.
String desc = null;
desc = DescriptorsImpl.getInstance().getDescriptor("EFT_DESC", 20); // "REMOVE RECEIPT FOR CUSTOMER"
if (desc == null)
{
log.warn("WARNING: Unable to read EFT \"REMOVE RECEIPT FOR CUSTOMER\" descriptor.");
desc = "REMOVE RECEIPT ";
}
desc = desc.substring(0,20);
Condition[] removeReceiptConditions =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.ANPROMPT_LINE1,
desc),
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("CLEAR")),
};
lock.wait("wait-for-remove-receipt-prompt",
new AndCondition(removeReceiptConditions),
getTimeout(),
true);
Condition[] goodConditions2 =
{
new PropertyNotEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("REMOVE_RECEIPT_FOR_CUSTOMER")),
};
Condition[] badConditions =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("REMOVE_RECEIPT_FOR_CUSTOMER")),
};
lock = new ConditionLock();
// Allow the error handler to process the message.
AEFErrorHandler errorHandler = new AEFErrorHandler("Handle Remove Receipt For Customer Sequence Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions2,
badConditions,
getTimeout(),
retVal);
if (retVal < 0)
{
keepGoing = false;
}
}
else if (retVal == 2)
{
// Got the duplicate receipt prompt which we'll pass to the error handler to handle.
Condition goodConditions3[] =
{
new PropertyNotEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ENTER_CLEAR")),
new PropertyNotEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("PROMPT_FOR_DUPLICATE_RECEIPT")),
};
Condition badConditions3[] =
{
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_STATE,
State.getState("ENTER_CLEAR")),
new PropertyEqualsCondition(POSDeviceProperties.CATEGORY,
POSDeviceProperties.POS_SUB_STATE,
Substate.getSubstate("PROMPT_FOR_DUPLICATE_RECEIPT")),
};
lock = new ConditionLock();
// Allow the error handler to process the message.
AEFErrorHandler errorHandler = new AEFErrorHandler("Handle Prompt For Duplicate Receipt Error");
retVal = errorHandler.handleError(lock,
keySequenceAction,
goodConditions3,
badConditions3,
getTimeout(),
retVal);
if (retVal < 0)
{
keepGoing = false;
}
}
}
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.waitForNextStateAfterTender().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* Waits for a new tender line item to be detected.
*
*
* @return Object The newly detected tender. (Note in this case, it's an ArrayList that
* contains the tender object).
* @exception AEFException
*/
public Object waitForTender() throws AEFException
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.waitForTender().");
log.trace(tempAEFMessage);
}
Object retVal = null;
ObjectDetectorLock objLock = new ObjectDetectorLock();
retVal = objLock.waitForNewObject("wait-for-tender",detector, instanceNumber, getTimeout());
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.waitForTender().");
log.trace(tempAEFMessage);
}
return retVal;
}
/**
* A AEF POSDataProvider property was updated.
* We will be looking for a B531 APPROVED message.
*
* @param evt contains details of the event
*
*/
public void propertyChanged(AEFPropertyChangeEvent evt)
{
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("+Enter SAEFTCreditTenderActionImpl.propertyChanged().");
log.trace(tempAEFMessage);
}
if (evt.getCategoryName().equals(POSDeviceProperties.CATEGORY))
{
// Because we only registered for 2x20 events, this event must contain line display contents.
String line1 = (String)(evt.getNewValue());
String approvedMsg = ConfigProps.get("CREDIT_APPROVED_MESSAGE");
if (approvedMsg == null)
{
approvedMsg = "B531";
}
if (line1.indexOf(approvedMsg) >= 0)
{
// Credit was approved, set the POSDataProvider property.
try
{
dataProvider.setPropertyValue(TransactionStatusProperties.CATEGORY,
TransactionStatusProperties.LAST_CREDIT_APPROVED,
"true");
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in propertyChanged of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
}
} else if (evt.getCategoryName().equals(WorkstationStatusProperties.CATEGORY) &&
evt.getPropertyName().equals(WorkstationStatusProperties.UNIT_OF_WORK) &&
((String)(evt.getNewValue())).equals("true"))
{
try
{
dataProvider.removeAEFPropertyChangeListener(this,
WorkstationStatusProperties.CATEGORY,
WorkstationStatusProperties.UNIT_OF_WORK);
dataProvider.setPropertyValue(WorkstationStatusProperties.CATEGORY,
"UnitOfWorkReceived",
"true");
}
catch (RemoteException re)
{
// Can't get here.
tempAEFMessage.setMessage("There was a remote exception thrown in propertyChanged of SAEFTCreditTenderActionImpl.");
log.error(tempAEFMessage, re);
}
}
if (log.isTraceEnabled())
{
tempAEFMessage.setMessage("-Exit SAEFTCreditTenderActionImpl.propertyChanged().");
log.trace(tempAEFMessage);
}
}
/* Instance Variables */
protected AEFAction keySequenceAction;
protected ItemIdentifier itemID;
protected ConditionLock lock;
protected ObjectDetector detector;
protected int instanceNumber;
protected static Vector validSubstates;
protected String amount;
protected String accountNumber;
protected String expDate;
protected String tenderVariety;
protected byte[] track1Data;
protected byte[] track2Data;
protected byte[] track3Data;
protected boolean useTrackData;
}