RISE-V2G/RISE-V2G-EVCC/src/main/java/com/v2gclarity/risev2g/evcc/states/ClientState.java

482 lines
21 KiB
Java
Raw Normal View History

2015-06-01 08:19:09 -07:00
/*******************************************************************************
* The MIT License (MIT)
2015-06-01 08:19:09 -07:00
*
* Copyright (c) 2015-207 V2G Clarity (Dr.-Ing. Marc Mültin)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
2015-06-01 08:19:09 -07:00
*******************************************************************************/
package com.v2gclarity.risev2g.evcc.states;
2015-06-01 08:19:09 -07:00
import java.security.KeyStore;
import java.util.Arrays;
import java.util.ListIterator;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import com.v2gclarity.risev2g.evcc.evController.DummyEVController;
import com.v2gclarity.risev2g.evcc.evController.IACEVController;
import com.v2gclarity.risev2g.evcc.evController.IDCEVController;
import com.v2gclarity.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import com.v2gclarity.risev2g.shared.enumerations.CPStates;
import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import com.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import com.v2gclarity.risev2g.shared.misc.State;
import com.v2gclarity.risev2g.shared.utils.ByteUtils;
import com.v2gclarity.risev2g.shared.utils.MiscUtils;
import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingSessionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVPowerDeliveryParameterType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeteringReceiptResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentServiceSelectionResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PowerDeliveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.PreChargeResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDetailResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ServiceDiscoveryResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionSetupResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopReqType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SessionStopResType;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.WeldingDetectionResType;
2015-06-01 08:19:09 -07:00
/**
* Some request messages are to be sent from different states which makes it more convenient (having
* less code and being less error-prone) to keep the creation of those messages in one single class.
*/
public abstract class ClientState extends State {
public ClientState(V2GCommunicationSessionEVCC commSessionContext) {
super(commSessionContext);
}
public V2GCommunicationSessionEVCC getCommSessionContext() {
return (V2GCommunicationSessionEVCC) super.getCommSessionContext();
}
protected boolean isIncomingMessageValid(Object incomingMessage, Class<? extends BodyBaseType> expectedMessage) {
V2GMessage v2gMessage = null;
if (incomingMessage instanceof V2GMessage) {
v2gMessage = (V2GMessage) incomingMessage;
if (!expectedMessage.isAssignableFrom(v2gMessage.getBody().getBodyElement().getValue().getClass())) {
getLogger().fatal("Invalid message (" + v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
return false;
} else {
getLogger().debug(v2gMessage.getBody().getBodyElement().getValue().getClass().getSimpleName().replace("Type", "") + " received");
if (!isHeaderOK(v2gMessage.getHeader())) return false;
if (!isResponseCodeOK(v2gMessage)) return false;
return true;
}
} else {
getLogger().fatal("Incoming message is not a V2GMessage");
return false;
}
}
/**
* Performs the following checks:
* - is the returned session ID the same as the one saved by the EVCC?
* - does the EVCC need to react to a possibly set notification?
* - does the EVCC need to check the signature?
*
* @param header The header of the V2GMessage
*/
private boolean isHeaderOK(MessageHeaderType header) {
// Check sessionID (only if not at state WaitForSessionSetupRes)
if (!this.equals(getCommSessionContext().getStates().get(V2GMessages.SESSION_SETUP_RES)) &&
!Arrays.equals(header.getSessionID(), getCommSessionContext().getSessionID())) {
getLogger().error("Session ID is invalid: " +
"expected " + ByteUtils.toLongFromByteArray(getCommSessionContext().getSessionID()) +
", received " + ByteUtils.toLongFromByteArray(header.getSessionID()));
return false;
}
if (header.getNotification() != null) {
// TODO react on the several notifications
}
/*
* If a signature is present, it is placed in the header. However, not all messages have a
* signature. Therefore, the signature validation is to be done in the respective state itself.
*/
return true;
}
private boolean isResponseCodeOK(V2GMessage responseMessage) {
BodyBaseType bbt = ((V2GMessage) responseMessage).getBody().getBodyElement().getValue();
ResponseCodeType v2gMessageRCT = null;
switch (bbt.getClass().getSimpleName()) {
case "SessionSetupResType":
v2gMessageRCT = ((SessionSetupResType) bbt).getResponseCode();
break;
case "ServiceDiscoveryResType":
v2gMessageRCT = ((ServiceDiscoveryResType) bbt).getResponseCode();
break;
case "ServiceDetailResType":
v2gMessageRCT = ((ServiceDetailResType) bbt).getResponseCode();
break;
case "PaymentServiceSelectionResType":
v2gMessageRCT = ((PaymentServiceSelectionResType) bbt).getResponseCode();
break;
case "PaymentDetailsResType":
v2gMessageRCT = ((PaymentDetailsResType) bbt).getResponseCode();
break;
case "CertificateInstallationResType":
v2gMessageRCT = ((CertificateInstallationResType) bbt).getResponseCode();
break;
case "CertificateUpdateResType":
v2gMessageRCT = ((CertificateUpdateResType) bbt).getResponseCode();
break;
case "AuthorizationResType":
v2gMessageRCT = ((AuthorizationResType) bbt).getResponseCode();
break;
case "ChargeParameterDiscoveryResType":
v2gMessageRCT = ((ChargeParameterDiscoveryResType) bbt).getResponseCode();
break;
case "CableCheckResType":
v2gMessageRCT = ((CableCheckResType) bbt).getResponseCode();
break;
case "PreChargeResType":
v2gMessageRCT = ((PreChargeResType) bbt).getResponseCode();
break;
case "PowerDeliveryResType":
v2gMessageRCT = ((PowerDeliveryResType) bbt).getResponseCode();
break;
case "ChargingStatusResType":
v2gMessageRCT = ((ChargingStatusResType) bbt).getResponseCode();
break;
case "CurrentDemandResType":
v2gMessageRCT = ((CurrentDemandResType) bbt).getResponseCode();
break;
case "MeteringReceiptResType":
v2gMessageRCT = ((MeteringReceiptResType) bbt).getResponseCode();
break;
case "WeldingDetectionResType":
v2gMessageRCT = ((WeldingDetectionResType) bbt).getResponseCode();
break;
case "SessionStopResType":
v2gMessageRCT = ((SessionStopResType) bbt).getResponseCode();
break;
default:
getLogger().error("Response message could not be identified");
return false;
}
if (v2gMessageRCT.toString().startsWith("OK")) return true;
else {
getLogger().error("Negative response code " + v2gMessageRCT.toString());
return false;
}
}
/**
* A ServiceDetailReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*
* Checks if the list of value added services (VAS) which are to be used contains service IDs. Those
* service IDs can be used in a ServiceDetailReq to request more details about the service.
* Each time a ServiceDetailReq is created, the respective service ID is deleted from the list.
*
* @return A ServiceDetailReq with a service ID whose details are requested, if the list of service IDs
* is not empty. Null otherwise.
*/
protected ServiceDetailReqType getServiceDetailReq() {
if (getCommSessionContext().getServiceDetailsToBeRequested().size() > 0) {
ListIterator<Short> listIterator = getCommSessionContext().getServiceDetailsToBeRequested().listIterator();
ServiceDetailReqType serviceDetailReq = new ServiceDetailReqType();
serviceDetailReq.setServiceID((short) listIterator.next());
listIterator.remove();
return serviceDetailReq;
}
return null;
}
/**
* A ServiceDetailReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*/
protected PaymentServiceSelectionReqType paymentServiceSelectionReq() {
PaymentServiceSelectionReqType paymentServiceSelectionReq = new PaymentServiceSelectionReqType();
paymentServiceSelectionReq.setSelectedPaymentOption(getCommSessionContext().getSelectedPaymentOption());
return paymentServiceSelectionReq;
}
/**
* An AuthorizationReq needs to be generated from several states:
* - WaitForPaymentServiceSelectionRes (no genChallege)
* - WaitForPaymentDetailsRes (genChallenge)
* - WaitForAuthorizationRes (no genChallenge, EVSE is still processing)
*
* @return An AuthorizationReq, either empty or with a set genChallenge and ID depending on input parameter
*/
protected AuthorizationReqType getAuthorizationReq(byte[] genChallenge) {
AuthorizationReqType authorizationReq = new AuthorizationReqType();
if (genChallenge != null) {
authorizationReq.setGenChallenge(genChallenge);
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
Commit-Nachricht für RISE V2G - Signature creation and verification has been fixed and works now properly. Has been tested against various industry OEMs and suppliers.
SecurityUtils.java class was substantially revised as part of fixing the signature creation and verification process and extended signature logging - Introduced a new abstract method setMandatoryFieldsForFailedRes() in ServerState which makes sure that for each response the mandatory fields are set properly according to the standard in case a FAILED response code is sent - Introduced a fix in WaitForAuthorizationReq which makes sure that a possible challenge is only checked for if the chosen payment option is „Contract“ (and TLS is used) - Saves now the payment option selected by the EVCC (in state WaitForPaymentServiceSelectionReq) in the SECCs communication session for later reference - To avoid possible race conditions, the transport layer threads needed to be started AFTER the SECC session handler has been initialized. Otherwise the situation might occur that the UDPServer is receiving a UDP client packet and tries to access the MessageHandler object before this object has been created by the SECC session handler. - generateCertificates.sh Shell-Script now includes variables to ease the setting of certificates’ validity periods. Renaming of some certificates (e.g. provLeaf.cert -> cpsLeaf.cert) and keys. All certificates are now also generated in DER encoded form. MOSubCA2 private key is now also created as a PKCS#8 DER encoded file as RISE V2G needs this format to read the private key from file and sign SalesTariffs. - EVCCConfig.properties and SECCConfig.properties have additional property „SignatureVerificationLog“ which enables extended logging information to debug signature creation and verification processes - Catched a (theoretically) possible ClassCastException in WaitForChargeParameterDiscoveryRes in case the EVCC signalizes an EnergyTransferMode (AC or DC) which does not fit to the EVSEChargeParameter sent by the SECC later on - Made sure that by default the sum of the individual time intervals described in the PMaxSchedule and SalesTariff provided in the ChargeParameterDiscoveryRes message are equal to 24 hours as demanded by a requirement. - Renaming of some methods in IBackendInterface for clarity and consistency - Provided a correctly formatted EVSE-ID in DummyACEVSEController and DummyDCEVSEController - Corrected the service name of CertificateUpdate service and CertificateInstallation service to „Service“ according to the standard - Edited GlobalValues so that the string value returned for each global value only needs to be edited once in this file (less error prone)
2017-07-20 06:50:43 -07:00
authorizationReq.setId("ID1");
2015-06-01 08:19:09 -07:00
}
return authorizationReq;
}
/**
* A CableCheckReq needs to be generated from several states:
* - WaitForChargeParameterDiscoveryRes
* - WaitForCableCheckRes (EVSEProcessing = ONGOING)
*
* @return A CableCheckReq
*/
protected CableCheckReqType getCableCheckReq() {
CableCheckReqType cableCheckReq = new CableCheckReqType();
cableCheckReq.setDCEVStatus(((DummyEVController) getCommSessionContext().getEvController()).getDCEVStatus());
return cableCheckReq;
}
/**
* A CurrentDemandReq needs to be generated from several states:
* - WaitForCurrentDemandRes (the initial CurrentDemandReq message)
* - WaitForMeteringReceiptRes
*
* @return A CurrentDemandReq message
*/
protected CurrentDemandReqType getCurrentDemandReq() {
IDCEVController evController = (IDCEVController) getCommSessionContext().getEvController();
CurrentDemandReqType currentDemandReq = new CurrentDemandReqType();
currentDemandReq.setBulkChargingComplete(evController.isBulkChargingComplete());
currentDemandReq.setChargingComplete(evController.isChargingComplete());
currentDemandReq.setDCEVStatus(evController.getDCEVStatus());
currentDemandReq.setEVMaximumCurrentLimit(evController.getMaximumCurrentLimit());
currentDemandReq.setEVMaximumPowerLimit(evController.getMaximumPowerLimit());
currentDemandReq.setEVMaximumVoltageLimit(evController.getMaximumVoltageLimit());
currentDemandReq.setEVTargetCurrent(evController.getTargetCurrent());
currentDemandReq.setEVTargetVoltage(evController.getTargetVoltage());
currentDemandReq.setRemainingTimeToBulkSoC(evController.getRemainingTimeToBulkSOC());
currentDemandReq.setRemainingTimeToFullSoC(evController.getRemainingTimeToFullSOC());
return currentDemandReq;
}
/**
* A ChargeParameterDiscoveryReq needs to be generated from several states:
* - WaitForAuthorizationRes (the initial ChargeParameterDiscoveryReq)
* - WaitForPowerDeliveryRes (in case AC_EVSEStatus requests a renegotiation)
* - WaitForChargingStatusRes (in case AC_EVSEStatus requests a renegotiation)
*
* @return A ChargeParameterDiscoveryReq which itself consists of several complex datatypes.
*/
protected ChargeParameterDiscoveryReqType getChargeParameterDiscoveryReq() {
ChargeParameterDiscoveryReqType chargeParameterDiscoveryReq = new ChargeParameterDiscoveryReqType();
// Optionally limit the number of entries in the SAScheduleTuple by setting MaxEntriesSAScheduleTuple
chargeParameterDiscoveryReq.setRequestedEnergyTransferMode(getRequestedEnergyTransferMode());
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
chargeParameterDiscoveryReq.setEVChargeParameter(((IACEVController) getCommSessionContext().getEvController()).getACEVChargeParamter());
else
chargeParameterDiscoveryReq.setEVChargeParameter(((IDCEVController) getCommSessionContext().getEvController()).getDCEVChargeParamter());
return chargeParameterDiscoveryReq;
}
/**
* A PaymentServiceSelectionReq needs to be generated from several states:
* - WaitForServiceDiscoveryRes
* - WaitForServiceDetailRes
*
* @return A PaymentServiceSelectionReq
*/
protected PaymentServiceSelectionReqType getPaymentServiceSelectionReq() {
PaymentServiceSelectionReqType paymentServiceSelectionReq = new PaymentServiceSelectionReqType();
paymentServiceSelectionReq.setSelectedPaymentOption(getCommSessionContext().getSelectedPaymentOption());
paymentServiceSelectionReq.setSelectedServiceList(getCommSessionContext().getSelectedServices());
return paymentServiceSelectionReq;
}
/**
* A PaymentDetailsReq needs to be generated from several states:
* - WaitForPaymentServiceSelectionRes
* - WaitForCertificateInstallationRes
* - WaitForCertificateUpdateRes
*
* @return A PaymentDetailsReq
*/
protected PaymentDetailsReqType getPaymentDetailsReq() {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
PaymentDetailsReqType paymentDetailsReq = new PaymentDetailsReqType();
EMAIDType emaid = SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
if (emaid != null) {
paymentDetailsReq.setEMAID(SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()).getValue());
paymentDetailsReq.setContractSignatureCertChain(SecurityUtils.getCertificateChain(
evccKeyStore, GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString()));
}
return paymentDetailsReq;
}
/**
* A PowerDeliveryReq needs to be generated from several states:
* - WaitForChargeParameterDiscoveryRes
* - WaitForChargingStatusRes
* - WaitForMeteringReceiptRes
*
* @param chargeProgress Indicates whether to START a charging session, RENEGOTIATE charing parameters
* or STOP the charging session
* @return A ChargeParameterDiscoveryReq which itself consists of several complex datatypes.
*/
protected PowerDeliveryReqType getPowerDeliveryReq(ChargeProgressType chargeProgress) {
PowerDeliveryReqType powerDeliveryReq = new PowerDeliveryReqType();
if (chargeProgress.equals(ChargeProgressType.START)) {
// Signal needed state change after sending PowerDeliveryReq in AC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
getCommSessionContext().setChangeToState(CPStates.STATE_C);
ChargingProfileType chargingProfile = getCommSessionContext().getEvController().getChargingProfile();
powerDeliveryReq.setChargingProfile(chargingProfile);
getCommSessionContext().setChargingProfile(chargingProfile);
} else if (chargeProgress.equals(ChargeProgressType.STOP)) {
// Signal needed state change after sending PowerDeliveryReq in AC charging mode
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
getCommSessionContext().setChangeToState(CPStates.STATE_B);
}
powerDeliveryReq.setChargeProgress(chargeProgress);
powerDeliveryReq.setSAScheduleTupleID(getCommSessionContext().getEvController().getChosenSAScheduleTupleID());
// Set DC_EVPowerDeliveryParameter if in DC charging mode
2015-06-01 08:19:09 -07:00
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MessageHandler method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVPowerDeliveryParameter) and the name in the XSD (DC_EVPowerDeliveryParameter)
*/
JAXBElement jaxbDcEvPowerDeliveryParameter = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVPowerDeliveryParameter"),
DCEVPowerDeliveryParameterType.class,
((IDCEVController) getCommSessionContext().getEvController()).getEVPowerDeliveryParameter());
powerDeliveryReq.setEVPowerDeliveryParameter(jaxbDcEvPowerDeliveryParameter);
2015-06-01 08:19:09 -07:00
}
return powerDeliveryReq;
}
/**
* A SessionStopReq needs to be generated from several states:
* - WaitForPowerDeliveryRes
* - WaitForWeldingDetectionRes
*
* @return A SessionStopReq message
*/
protected SessionStopReqType getSessionStopReq(ChargingSessionType chargingSessionType) {
SessionStopReqType sessionStopReq = new SessionStopReqType();
sessionStopReq.setChargingSession(chargingSessionType);
return sessionStopReq;
}
protected EnergyTransferModeType getRequestedEnergyTransferMode() {
// Check if an EnergyTransferModeType has been requested in a previously paused session
EnergyTransferModeType requestedEnergyTransferMode =
(EnergyTransferModeType) MiscUtils.getPropertyValue("energy.transfermode.requested");
2015-06-01 08:19:09 -07:00
if (requestedEnergyTransferMode == null) {
requestedEnergyTransferMode = getCommSessionContext().getEvController().getRequestedEnergyTransferMode();
getCommSessionContext().setRequestedEnergyTransferMode(requestedEnergyTransferMode);
}
return requestedEnergyTransferMode;
}
protected PaymentOptionType getSelectedPaymentOption() {
// Check if a PaymentOptionType has been requested in a previously paused session
PaymentOptionType selectedPaymentOption = (PaymentOptionType) MiscUtils.getPropertyValue("authentication.mode");
2015-06-01 08:19:09 -07:00
if (selectedPaymentOption == null) {
selectedPaymentOption = getCommSessionContext().getEvController().getPaymentOption(getCommSessionContext().getPaymentOptions());
getCommSessionContext().setSelectedPaymentOption(selectedPaymentOption);
}
return selectedPaymentOption;
}
}