| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- /*******************************************************************************
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
- *
- * 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.
- *******************************************************************************/
- package com.v2gclarity.risev2g.evcc.states;
- 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;
- /**
- * 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)
- */
- authorizationReq.setId("ID1");
- }
-
- 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
- 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<DCEVPowerDeliveryParameterType> jaxbDcEvPowerDeliveryParameter = new JAXBElement<>(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVPowerDeliveryParameter"),
- DCEVPowerDeliveryParameterType.class,
- ((IDCEVController) getCommSessionContext().getEvController()).getEVPowerDeliveryParameter());
- powerDeliveryReq.setEVPowerDeliveryParameter(jaxbDcEvPowerDeliveryParameter);
- }
-
- 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");
-
- 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");
-
- if (selectedPaymentOption == null) {
- selectedPaymentOption = getCommSessionContext().getEvController().getPaymentOption(getCommSessionContext().getPaymentOptions());
- getCommSessionContext().setSelectedPaymentOption(selectedPaymentOption);
- }
-
- return selectedPaymentOption;
- }
- }
|