/* * 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: * <br>AEFConst.INVALID_ARGUMENT, AEFConst.ACCOUNT_NUMBER_REQUIRED * <br>AEFConst.INVALID_ARGUMENT, AEFConst.INVALID_DATE * <br>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: * <br>AEFConst.INVALID_ARGUMENT, AEFConst.INVALID_TENDER_TYPE * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.ACCOUNT_TENDER_LIMIT * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.CHANGE_AMOUNT_LIMIT * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.NUMBER_OF_TENDERS_LIMIT * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.STAND_IN_COUNT_LIMIT * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.STAND_IN_AMOUNT_LIMIT * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.TENDER_FLOOR_LIMIT * <br>AEFConst.APPLICATION_LIMIT_EXCEEDED, AEFConst.CARD_EXPIRED * <br>AEFConst.MSR_HOOK_SWIPE_ERROR, AEFConst.MSR_SET_TO_DECODE * <br>AEFConst.MSR_HOOK_SWIPE_ERROR, AEFConst.MSR_NOT_ENABLED * <br>AEFConst.JAVA_POS_EXCEPTION, see exception cause() for actual javaPos exception * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.TENDER_NOT_AUTHORIZED * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_1 * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_2 * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_3 * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.RISK_4 * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.VERIFICATION_TIMEOUT * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.CREDIT_NOT_AVAILABLE * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.TOO_LONG_IN_STANDIN * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.PAYMENT_SYSTEM_OFFLINE * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.UNKNOWN_SERVICER * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.SERVICER_CLOSED * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.CARD_TYPE_UNKNOWN * <br>AEFConst.INVALID_MANAGER_OVERRIDE_NUMBER * <br>AEFConst.KEYLOCK_ERROR, AEFConst.MANAGER_KEY_REQUIRED * <br>AEFConst.INVALID_ARGUMENT * <br>AEFConst.PROCEDURE_NOT_ALLOWED, AEFConst.EXTERNAL_TENDER_AUTHORIZATION_SUSPENDED * <br>AEFConst.TENDER_NOT_ACCEPTED, AEFConst.UNKNOWN_SERVICER * <br><a href="../commonerrorcodes.html">Common Errors</a> */ 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; }