diff --git a/adyenv6core/resources/adyenv6core-beans.xml b/adyenv6core/resources/adyenv6core-beans.xml index fa6fb003d..5b1110e18 100644 --- a/adyenv6core/resources/adyenv6core-beans.xml +++ b/adyenv6core/resources/adyenv6core-beans.xml @@ -62,6 +62,7 @@ + @@ -134,6 +135,18 @@ + + + + + + + + + + + + diff --git a/adyenv6core/resources/adyenv6core-spring.xml b/adyenv6core/resources/adyenv6core-spring.xml index 36c187317..222b094e8 100644 --- a/adyenv6core/resources/adyenv6core-spring.xml +++ b/adyenv6core/resources/adyenv6core-spring.xml @@ -449,6 +449,11 @@ + + + + + diff --git a/adyenv6core/src/com/adyen/commerce/services/impl/CreditCardSubscriptionHandler.java b/adyenv6core/src/com/adyen/commerce/services/impl/CreditCardSubscriptionHandler.java new file mode 100644 index 000000000..c7817c457 --- /dev/null +++ b/adyenv6core/src/com/adyen/commerce/services/impl/CreditCardSubscriptionHandler.java @@ -0,0 +1,42 @@ +package com.adyen.commerce.services.impl; + +import com.adyen.model.checkout.CardDetails; +import com.adyen.model.checkout.CheckoutPaymentMethod; +import com.adyen.model.checkout.PaymentRequest; +import com.adyen.v6.enums.RecurringContractMode; +import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.core.model.user.CustomerModel; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Objects; + +public class CreditCardSubscriptionHandler implements PaymentMethodHandler { + @Override + public boolean canHandle(String paymentMethod) { + return Arrays.stream(CardDetails.TypeEnum.values()).map(CardDetails.TypeEnum::toString).anyMatch(type -> type.equalsIgnoreCase(paymentMethod)); + } + + @Override + public void updatePaymentRequest(PaymentRequest paymentRequest, CartData cartData, RecurringContractMode recurringContractMode, CustomerModel customerModel, Boolean is3DS2Allowed, Boolean guestUserTokenizationEnabled) { + if (StringUtils.isNotEmpty(cartData.getAdyenSelectedReference())) { + + CardDetails cardDetails; + + if (Objects.isNull(paymentRequest.getPaymentMethod()) || Objects.isNull(paymentRequest.getPaymentMethod().getCardDetails())) { + cardDetails = new CardDetails(); + } else { + cardDetails = paymentRequest.getPaymentMethod().getCardDetails(); + } + + cardDetails.setStoredPaymentMethodId(cartData.getAdyenSelectedReference()); + CheckoutPaymentMethod checkoutPaymentMethod = new CheckoutPaymentMethod(); + checkoutPaymentMethod.setActualInstance(cardDetails); + paymentRequest.setPaymentMethod(checkoutPaymentMethod); + + paymentRequest.setRecurringProcessingModel(PaymentRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + + paymentRequest.setShopperInteraction(PaymentRequest.ShopperInteractionEnum.CONTAUTH); + } + } +} diff --git a/adyenv6core/src/com/adyen/commerce/services/impl/DefaultAdyenRequestService.java b/adyenv6core/src/com/adyen/commerce/services/impl/DefaultAdyenRequestService.java index 1ecbffe06..dae6f701e 100644 --- a/adyenv6core/src/com/adyen/commerce/services/impl/DefaultAdyenRequestService.java +++ b/adyenv6core/src/com/adyen/commerce/services/impl/DefaultAdyenRequestService.java @@ -96,6 +96,8 @@ public PaymentRequest createPaymentsRequest(final String merchantAccount, handlePaymentMethodSpecificLogic(paymentRequest, cartData, originPaymentsRequest, recurringContractMode, customerModel, guestUserTokenizationEnabled); + addPaymentMethodTokenizationToPaymentRequest(paymentRequest, cartData); + return paymentRequest; } @@ -396,7 +398,7 @@ protected void handlePaymentMethodSpecificLogic(PaymentRequest paymentRequest, C // Use payment method handler paymentMethodHandlerFactory.getHandler(paymentMethod) - .ifPresent(handler -> handler.updatePaymentRequest(paymentRequest, cartData, + .forEach(handler -> handler.updatePaymentRequest(paymentRequest, cartData, recurringContractMode, customerModel, is3DS2Allowed, guestUserTokenizationEnabled)); } @@ -406,6 +408,18 @@ protected void copySchemePaymentSettings(PaymentRequest paymentRequest, PaymentR paymentRequest.setStorePaymentMethod(originPaymentsRequest.getStorePaymentMethod()); } + protected void addPaymentMethodTokenizationToPaymentRequest(PaymentRequest paymentRequest, CartData cartData) { + if (tokenizeForSubscriptionProducts(cartData)) { + paymentRequest.setStorePaymentMethod(true); + paymentRequest.setRecurringProcessingModel(PaymentRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + } + } + + protected boolean tokenizeForSubscriptionProducts(CartData cartData) { + return !cartData.getSubscriptionOrder() && cartData.getEntries().stream() + .anyMatch(entry -> Objects.nonNull(entry.getProduct().getSubscriptionTerm())); + } + protected AddressData getBillingAddress(CartData cartData) { return Optional.ofNullable(cartData.getPaymentInfo()) .map(paymentInfo -> paymentInfo.getBillingAddress()) diff --git a/adyenv6core/src/com/adyen/commerce/services/impl/IdealSubscriptionHandler.java b/adyenv6core/src/com/adyen/commerce/services/impl/IdealSubscriptionHandler.java new file mode 100644 index 000000000..6238625cb --- /dev/null +++ b/adyenv6core/src/com/adyen/commerce/services/impl/IdealSubscriptionHandler.java @@ -0,0 +1,39 @@ +package com.adyen.commerce.services.impl; + +import com.adyen.model.checkout.CheckoutPaymentMethod; +import com.adyen.model.checkout.IdealDetails; +import com.adyen.model.checkout.PaymentRequest; +import com.adyen.model.checkout.SepaDirectDebitDetails; +import com.adyen.v6.enums.RecurringContractMode; +import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.core.model.user.CustomerModel; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +public class IdealSubscriptionHandler implements PaymentMethodHandler { + @Override + public boolean canHandle(String paymentMethod) { + return Arrays.stream(IdealDetails.TypeEnum.values()).map(IdealDetails.TypeEnum::toString).anyMatch(type -> type.equalsIgnoreCase(paymentMethod)); + + } + + @Override + public void updatePaymentRequest(PaymentRequest paymentRequest, CartData cartData, RecurringContractMode recurringContractMode, CustomerModel customerModel, Boolean is3DS2Allowed, Boolean guestUserTokenizationEnabled) { + if (StringUtils.isNotEmpty(cartData.getAdyenSelectedReference())) { + + + SepaDirectDebitDetails sepaDirectDebitDetails = new SepaDirectDebitDetails(); + + + sepaDirectDebitDetails.setStoredPaymentMethodId(cartData.getAdyenSelectedReference()); + CheckoutPaymentMethod checkoutPaymentMethod = new CheckoutPaymentMethod(); + checkoutPaymentMethod.setActualInstance(sepaDirectDebitDetails); + paymentRequest.setPaymentMethod(checkoutPaymentMethod); + + paymentRequest.setRecurringProcessingModel(PaymentRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + + paymentRequest.setShopperInteraction(PaymentRequest.ShopperInteractionEnum.CONTAUTH); + } + } +} diff --git a/adyenv6core/src/com/adyen/commerce/services/impl/KlarnaSubscriptionHandler.java b/adyenv6core/src/com/adyen/commerce/services/impl/KlarnaSubscriptionHandler.java new file mode 100644 index 000000000..6b5351a46 --- /dev/null +++ b/adyenv6core/src/com/adyen/commerce/services/impl/KlarnaSubscriptionHandler.java @@ -0,0 +1,42 @@ +package com.adyen.commerce.services.impl; + +import com.adyen.model.checkout.CheckoutPaymentMethod; +import com.adyen.model.checkout.KlarnaDetails; +import com.adyen.model.checkout.PaymentRequest; +import com.adyen.v6.enums.RecurringContractMode; +import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.core.model.user.CustomerModel; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Objects; + +public class KlarnaSubscriptionHandler implements PaymentMethodHandler { + @Override + public boolean canHandle(String paymentMethod) { + return Arrays.stream(KlarnaDetails.TypeEnum.values()).map(KlarnaDetails.TypeEnum::toString).anyMatch(type -> type.equalsIgnoreCase(paymentMethod)); + } + + @Override + public void updatePaymentRequest(PaymentRequest paymentRequest, CartData cartData, RecurringContractMode recurringContractMode, CustomerModel customerModel, Boolean is3DS2Allowed, Boolean guestUserTokenizationEnabled) { + if (StringUtils.isNotEmpty(cartData.getAdyenSelectedReference())) { + + KlarnaDetails klarnaDetails; + + if (Objects.isNull(paymentRequest.getPaymentMethod()) || Objects.isNull(paymentRequest.getPaymentMethod().getKlarnaDetails())) { + klarnaDetails = new KlarnaDetails(); + } else { + klarnaDetails = paymentRequest.getPaymentMethod().getKlarnaDetails(); + } + + klarnaDetails.setStoredPaymentMethodId(cartData.getAdyenSelectedReference()); + CheckoutPaymentMethod checkoutPaymentMethod = new CheckoutPaymentMethod(); + checkoutPaymentMethod.setActualInstance(klarnaDetails); + paymentRequest.setPaymentMethod(checkoutPaymentMethod); + + paymentRequest.setRecurringProcessingModel(PaymentRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + + paymentRequest.setShopperInteraction(PaymentRequest.ShopperInteractionEnum.CONTAUTH); + } + } +} diff --git a/adyenv6core/src/com/adyen/commerce/services/impl/PayPalSubscriptionHandler.java b/adyenv6core/src/com/adyen/commerce/services/impl/PayPalSubscriptionHandler.java new file mode 100644 index 000000000..36fff9ebf --- /dev/null +++ b/adyenv6core/src/com/adyen/commerce/services/impl/PayPalSubscriptionHandler.java @@ -0,0 +1,42 @@ +package com.adyen.commerce.services.impl; + +import com.adyen.model.checkout.CheckoutPaymentMethod; +import com.adyen.model.checkout.PayPalDetails; +import com.adyen.model.checkout.PaymentRequest; +import com.adyen.v6.enums.RecurringContractMode; +import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.core.model.user.CustomerModel; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Objects; + +public class PayPalSubscriptionHandler implements PaymentMethodHandler { + @Override + public boolean canHandle(String paymentMethod) { + return Arrays.stream(PayPalDetails.TypeEnum.values()).map(PayPalDetails.TypeEnum::toString).anyMatch(type -> type.equalsIgnoreCase(paymentMethod)); + } + + @Override + public void updatePaymentRequest(PaymentRequest paymentRequest, CartData cartData, RecurringContractMode recurringContractMode, CustomerModel customerModel, Boolean is3DS2Allowed, Boolean guestUserTokenizationEnabled) { + if (StringUtils.isNotEmpty(cartData.getAdyenSelectedReference())) { + + PayPalDetails payPalDetails; + + if (Objects.isNull(paymentRequest.getPaymentMethod()) || Objects.isNull(paymentRequest.getPaymentMethod().getPayPalDetails())) { + payPalDetails = new PayPalDetails(); + } else { + payPalDetails = paymentRequest.getPaymentMethod().getPayPalDetails(); + } + + payPalDetails.setStoredPaymentMethodId(cartData.getAdyenSelectedReference()); + CheckoutPaymentMethod checkoutPaymentMethod = new CheckoutPaymentMethod(); + checkoutPaymentMethod.setActualInstance(payPalDetails); + paymentRequest.setPaymentMethod(checkoutPaymentMethod); + + paymentRequest.setRecurringProcessingModel(PaymentRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + + paymentRequest.setShopperInteraction(PaymentRequest.ShopperInteractionEnum.CONTAUTH); + } + } +} diff --git a/adyenv6core/src/com/adyen/commerce/services/impl/PaymentMethodHandlerFactory.java b/adyenv6core/src/com/adyen/commerce/services/impl/PaymentMethodHandlerFactory.java index d7cc27928..bcee50a17 100644 --- a/adyenv6core/src/com/adyen/commerce/services/impl/PaymentMethodHandlerFactory.java +++ b/adyenv6core/src/com/adyen/commerce/services/impl/PaymentMethodHandlerFactory.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.List; -import java.util.Optional; +import java.util.stream.Collectors; /** * Factory for creating appropriate payment method handlers @@ -19,16 +19,20 @@ public PaymentMethodHandlerFactory() { new CreditCardPaymentHandler(), new OneClickPaymentHandler(), new SchemePaymentHandler(), - new AlternativePaymentHandler() + new AlternativePaymentHandler(), + new CreditCardSubscriptionHandler(), + new IdealSubscriptionHandler(), + new KlarnaSubscriptionHandler(), + new PayPalSubscriptionHandler() ); } /** * Gets the appropriate handler for the given payment method */ - public Optional getHandler(String paymentMethod) { + public List getHandler(String paymentMethod) { return handlers.stream() .filter(handler -> handler.canHandle(paymentMethod)) - .findFirst(); + .collect(Collectors.toUnmodifiableList()); } } \ No newline at end of file diff --git a/adyenv6core/src/com/adyen/v6/constants/StorefrontType.java b/adyenv6core/src/com/adyen/v6/constants/StorefrontType.java index c73de0d11..4cab27604 100644 --- a/adyenv6core/src/com/adyen/v6/constants/StorefrontType.java +++ b/adyenv6core/src/com/adyen/v6/constants/StorefrontType.java @@ -5,7 +5,8 @@ public enum StorefrontType { SPA("spa"), SPARTACUS("spartacus"), CUSTOM("custom"), - EXPRESSOCC("expressocc"); + EXPRESSOCC("expressocc"), + SUBSCRIPTION("subscription"); private final String value; diff --git a/adyenv6core/src/com/adyen/v6/events/TokenizationEvent.java b/adyenv6core/src/com/adyen/v6/events/TokenizationEvent.java new file mode 100644 index 000000000..5cf53f849 --- /dev/null +++ b/adyenv6core/src/com/adyen/v6/events/TokenizationEvent.java @@ -0,0 +1,16 @@ +package com.adyen.v6.events; + +import com.adyen.commerce.data.TokenWebhookRequestData; +import de.hybris.platform.servicelayer.event.events.AbstractEvent; + +public class TokenizationEvent extends AbstractEvent { + private final TokenWebhookRequestData data; + + public TokenizationEvent(final TokenWebhookRequestData data) { + this.data = data; + } + + public TokenWebhookRequestData getData() { + return data; + } +} diff --git a/adyenv6core/src/com/adyen/v6/listeners/TokenizationWebhookEventListener.java b/adyenv6core/src/com/adyen/v6/listeners/TokenizationWebhookEventListener.java new file mode 100644 index 000000000..feb27f44e --- /dev/null +++ b/adyenv6core/src/com/adyen/v6/listeners/TokenizationWebhookEventListener.java @@ -0,0 +1,83 @@ +package com.adyen.v6.listeners; + +import com.adyen.commerce.data.TokenWebhookRequestData; +import com.adyen.v6.events.TokenizationEvent; +import com.adyen.v6.repository.PaymentTransactionRepository; +import de.hybris.platform.core.model.order.AbstractOrderModel; +import de.hybris.platform.core.model.order.payment.PaymentInfoModel; +import de.hybris.platform.core.model.user.CustomerModel; +import de.hybris.platform.payment.model.PaymentTransactionModel; +import de.hybris.platform.servicelayer.event.impl.AbstractEventListener; +import de.hybris.platform.servicelayer.model.ModelService; +import org.apache.commons.lang.NotImplementedException; +import org.apache.log4j.Logger; + +import java.util.Objects; + + +public class TokenizationWebhookEventListener extends AbstractEventListener { + private static final Logger LOG = Logger.getLogger(TokenizationWebhookEventListener.class); + + private PaymentTransactionRepository paymentTransactionRepository; + private ModelService modelService; + + protected static String TOKEN_CREATED = "recurring.token.created"; + + public TokenizationWebhookEventListener() { + super(); + } + + @Override + protected void onEvent(TokenizationEvent tokenizationEvent) { + LOG.debug("Processing Tokenization event"); + + TokenWebhookRequestData data = tokenizationEvent.getData(); + PaymentTransactionModel transactionModel = paymentTransactionRepository.getTransactionModel(data.getEventId()); + if (Objects.isNull(transactionModel)) { + throw new IllegalStateException("No PaymentTransactionModel found for eventId(pspReference): " + data.getEventId()); + } + + AbstractOrderModel order = transactionModel.getOrder(); + + if (Objects.isNull(order)) { + throw new IllegalStateException("No Order connected to PaymentTransaction with code: " + data.getEventId()); + } + + crosscheckWithOrder(order, data); + + if (TOKEN_CREATED.equals(data.getEventType())) { + PaymentInfoModel paymentInfo = order.getPaymentInfo(); + paymentInfo.setAdyenSelectedReference(data.getStoredPaymentMethodId()); + modelService.save(paymentInfo); + } else { + throw new NotImplementedException("TokenizationWebhookEventListener not implemented for type " + data.getEventType()); + } + + } + + protected void crosscheckWithOrder(final AbstractOrderModel order, final TokenWebhookRequestData tokenWebhookRequestData) { + boolean validationResult = true; + validationResult &= ((CustomerModel) order.getPaymentInfo().getUser()).getCustomerID().equals(tokenWebhookRequestData.getShopperReference()); + + validationResult &= order.getStore().getAdyenMerchantAccount().equals(tokenWebhookRequestData.getMerchantAccount()); + + validationResult &= (order.getStore().getAdyenTestMode() && "test".equals(tokenWebhookRequestData.getEnvironment())) || + (!order.getStore().getAdyenTestMode() && "live".equals(tokenWebhookRequestData.getEnvironment())); + + if (!validationResult) { + throw new IllegalArgumentException("Token webhook request is not valid. EventId (pspReference): " + tokenWebhookRequestData.getEventId() + + " type: " + tokenWebhookRequestData.getEventType() + " shopperReference: " + tokenWebhookRequestData.getShopperReference() + + " createdAt: " + tokenWebhookRequestData.getCreatedAt()); + } + + } + + + public void setPaymentTransactionRepository(PaymentTransactionRepository paymentTransactionRepository) { + this.paymentTransactionRepository = paymentTransactionRepository; + } + + public void setModelService(ModelService modelService) { + this.modelService = modelService; + } +} diff --git a/adyenv6core/src/com/adyen/v6/security/AdyenNotificationAuthenticationProvider.java b/adyenv6core/src/com/adyen/v6/security/AdyenNotificationAuthenticationProvider.java index 970ab47f2..455486bd5 100644 --- a/adyenv6core/src/com/adyen/v6/security/AdyenNotificationAuthenticationProvider.java +++ b/adyenv6core/src/com/adyen/v6/security/AdyenNotificationAuthenticationProvider.java @@ -54,6 +54,24 @@ public class AdyenNotificationAuthenticationProvider { public boolean authenticate(final HttpServletRequest request, NotificationRequest notificationRequest, String baseSiteId) { LOG.debug("Trying to authenticate for baseSiteId " + baseSiteId); + BaseStoreModel baseStore = getBaseStore(baseSiteId); + + boolean basicAuthenticated = authenticateBasic(request, baseStore); + boolean checkHMAC = checkHMACFromAdditionalData(notificationRequest, baseStore); + + return basicAuthenticated && checkHMAC; + } + + public boolean authenticate(final HttpServletRequest request, final String requestBody, String baseSiteId) { + BaseStoreModel baseStore = getBaseStore(baseSiteId); + + boolean basicAuthenticated = authenticateBasic(request, baseStore); + boolean checkHMAC = checkHMACFromHeader(request, requestBody, baseStore); + + return basicAuthenticated && checkHMAC; + } + + protected BaseStoreModel getBaseStore(final String baseSiteId) { final BaseSiteModel requestedBaseSite = getBaseSiteService().getBaseSiteForUID(baseSiteId); if (requestedBaseSite != null) { final BaseSiteModel currentBaseSite = getBaseSiteService().getCurrentBaseSite(); @@ -64,17 +82,11 @@ public boolean authenticate(final HttpServletRequest request, NotificationReques BaseStoreModel baseStore = baseStoreService.getCurrentBaseStore(); if (baseStore == null) { - LOG.error("BaseStore does not exist for baseSite: " + baseSiteId); - return false; + throw new RuntimeException("BaseStore does not exist for baseSite: " + baseSiteId); } - - boolean basicAuthenticated = authenticateBasic(request, baseStore); - boolean checkHMAC = checkHMAC(notificationRequest, baseStore); - - return basicAuthenticated && checkHMAC; + return baseStore; } - LOG.error("BaseSite does not exist: " + baseSiteId); - return false; + throw new RuntimeException("BaseSite does not exist: " + baseSiteId); } protected boolean authenticateBasic(final HttpServletRequest request, BaseStoreModel baseStoreModel) { @@ -107,7 +119,7 @@ protected boolean tryToAuthenticate(String name, String password, BaseStoreModel return false; } - protected boolean checkHMAC(NotificationRequest notificationRequest, BaseStoreModel baseStore) { + protected boolean checkHMACFromAdditionalData(NotificationRequest notificationRequest, BaseStoreModel baseStore) { String hmacKey = baseStore.getAdyenNotificationHMACKey(); if (StringUtils.isNotEmpty(hmacKey)) { @@ -129,6 +141,27 @@ protected boolean checkHMAC(NotificationRequest notificationRequest, BaseStoreMo return true; } + protected boolean checkHMACFromHeader(final HttpServletRequest request, final String requestBody, BaseStoreModel baseStore) { + String hmacSignature = request.getHeader("hmacsignature"); + String hmacKey = baseStore.getAdyenNotificationHMACKey(); + + if (StringUtils.isNotEmpty(hmacKey)) { + HMACValidator hmacValidator = new HMACValidator(); + try { + if (!hmacValidator.validateHMAC(hmacSignature, hmacKey, requestBody)) { + LOG.error("Signature check failed"); + return false; + } + } catch (IllegalArgumentException | SignatureException e) { + LOG.error("Signature check exception"); + return false; + } + return true; + } + LOG.warn("HMAC authentication not configured"); + return true; + } + public BaseStoreService getBaseStoreService() { return baseStoreService; } diff --git a/adyenv6core/src/com/adyen/v6/service/DefaultAdyenOrderService.java b/adyenv6core/src/com/adyen/v6/service/DefaultAdyenOrderService.java index d2a3bcba1..7e135635d 100644 --- a/adyenv6core/src/com/adyen/v6/service/DefaultAdyenOrderService.java +++ b/adyenv6core/src/com/adyen/v6/service/DefaultAdyenOrderService.java @@ -143,6 +143,7 @@ public void updatePaymentInfo(OrderModel order, String paymentMethodType, Map info.setAdyenThreeDOffered(Boolean.valueOf(value))); updatePaymentInfo(paymentInfo, additionalData, "threeDAuthenticated", (info, value) -> info.setAdyenThreeDAuthenticated(Boolean.valueOf(value))); + updatePaymentInfo(paymentInfo, additionalData, "tokenization.storedPaymentMethodId", PaymentInfoModel::setAdyenSelectedReference); modelService.save(paymentInfo); } diff --git a/adyenv6notificationv2/resources/adyenv6notificationv2-spring.xml b/adyenv6notificationv2/resources/adyenv6notificationv2-spring.xml index 6d7d8e4b0..e8ade631b 100644 --- a/adyenv6notificationv2/resources/adyenv6notificationv2-spring.xml +++ b/adyenv6notificationv2/resources/adyenv6notificationv2-spring.xml @@ -3,16 +3,22 @@ Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. --> + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> - + + + + + + + + + + diff --git a/adyenv6notificationv2/src/com/adyen/v6/converter/TokenizationWebhookRequestConverter.java b/adyenv6notificationv2/src/com/adyen/v6/converter/TokenizationWebhookRequestConverter.java new file mode 100644 index 000000000..e9a74f138 --- /dev/null +++ b/adyenv6notificationv2/src/com/adyen/v6/converter/TokenizationWebhookRequestConverter.java @@ -0,0 +1,31 @@ +package com.adyen.v6.converter; + +import com.adyen.commerce.data.TokenWebhookRequestData; +import com.adyen.v6.request.TokenizationWebhookRequest; +import de.hybris.platform.servicelayer.dto.converter.ConversionException; +import de.hybris.platform.servicelayer.dto.converter.Converter; + +public class TokenizationWebhookRequestConverter implements Converter { + + @Override + public TokenWebhookRequestData convert(TokenizationWebhookRequest tokenizationWebhookRequest) throws ConversionException { + TokenWebhookRequestData result = new TokenWebhookRequestData(); + + return convert(tokenizationWebhookRequest, result); + } + + @Override + public TokenWebhookRequestData convert(TokenizationWebhookRequest tokenizationWebhookRequest, TokenWebhookRequestData tokenWebhookRequestData) throws ConversionException { + tokenWebhookRequestData.setCreatedAt(tokenizationWebhookRequest.getCreatedAt()); + tokenWebhookRequestData.setEventId(tokenizationWebhookRequest.getEventId()); + tokenWebhookRequestData.setEnvironment(tokenizationWebhookRequest.getEnvironment()); + tokenWebhookRequestData.setEventType(tokenizationWebhookRequest.getType()); + tokenWebhookRequestData.setMerchantAccount(tokenizationWebhookRequest.getData().getMerchantAccount()); + tokenWebhookRequestData.setOperation(tokenizationWebhookRequest.getData().getOperation()); + tokenWebhookRequestData.setShopperReference(tokenizationWebhookRequest.getData().getShopperReference()); + tokenWebhookRequestData.setStoredPaymentMethodId(tokenizationWebhookRequest.getData().getStoredPaymentMethodId()); + tokenWebhookRequestData.setPaymentType(tokenizationWebhookRequest.getData().getType()); + + return tokenWebhookRequestData; + } +} diff --git a/adyenv6notificationv2/src/com/adyen/v6/request/TokenizationWebhookData.java b/adyenv6notificationv2/src/com/adyen/v6/request/TokenizationWebhookData.java new file mode 100644 index 000000000..030491ef8 --- /dev/null +++ b/adyenv6notificationv2/src/com/adyen/v6/request/TokenizationWebhookData.java @@ -0,0 +1,49 @@ +package com.adyen.v6.request; + +public class TokenizationWebhookData { + private String merchantAccount; + private String operation; + private String shopperReference; + private String storedPaymentMethodId; + private String type; + + public String getMerchantAccount() { + return merchantAccount; + } + + public void setMerchantAccount(String merchantAccount) { + this.merchantAccount = merchantAccount; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getShopperReference() { + return shopperReference; + } + + public void setShopperReference(String shopperReference) { + this.shopperReference = shopperReference; + } + + public String getStoredPaymentMethodId() { + return storedPaymentMethodId; + } + + public void setStoredPaymentMethodId(String storedPaymentMethodId) { + this.storedPaymentMethodId = storedPaymentMethodId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/adyenv6notificationv2/src/com/adyen/v6/request/TokenizationWebhookRequest.java b/adyenv6notificationv2/src/com/adyen/v6/request/TokenizationWebhookRequest.java new file mode 100644 index 000000000..82656646a --- /dev/null +++ b/adyenv6notificationv2/src/com/adyen/v6/request/TokenizationWebhookRequest.java @@ -0,0 +1,61 @@ +package com.adyen.v6.request; + +public class TokenizationWebhookRequest { + private String createdAt; + private String eventId; + private String environment; + private String type; + private TokenizationWebhookData data; + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public String getEnvironment() { + return environment; + } + + public void setEnvironment(String environment) { + this.environment = environment; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public TokenizationWebhookData getData() { + return data; + } + + public void setData(TokenizationWebhookData data) { + this.data = data; + } + + @Override + public String toString() { + return "TokenizationWebhookRequest{" + + "createdAt='" + createdAt + '\'' + + ", eventId='" + eventId + '\'' + + ", environment='" + environment + '\'' + + ", type='" + type + '\'' + + ", data=" + data + + '}'; + } +} + diff --git a/adyenv6notificationv2/src/com/adyen/v6/service/AdyenTokenizationWebhookService.java b/adyenv6notificationv2/src/com/adyen/v6/service/AdyenTokenizationWebhookService.java new file mode 100644 index 000000000..ea8952d13 --- /dev/null +++ b/adyenv6notificationv2/src/com/adyen/v6/service/AdyenTokenizationWebhookService.java @@ -0,0 +1,9 @@ +package com.adyen.v6.service; + +import com.adyen.v6.request.TokenizationWebhookRequest; + +public interface AdyenTokenizationWebhookService { + + void onRequest(TokenizationWebhookRequest tokenizationWebhookRequest); + +} diff --git a/adyenv6notificationv2/src/com/adyen/v6/service/DefaultAdyenNotificationV2Service.java b/adyenv6notificationv2/src/com/adyen/v6/service/impl/DefaultAdyenNotificationV2Service.java similarity index 98% rename from adyenv6notificationv2/src/com/adyen/v6/service/DefaultAdyenNotificationV2Service.java rename to adyenv6notificationv2/src/com/adyen/v6/service/impl/DefaultAdyenNotificationV2Service.java index 591d35fb1..f2bb56c06 100644 --- a/adyenv6notificationv2/src/com/adyen/v6/service/DefaultAdyenNotificationV2Service.java +++ b/adyenv6notificationv2/src/com/adyen/v6/service/impl/DefaultAdyenNotificationV2Service.java @@ -1,10 +1,11 @@ -package com.adyen.v6.service; +package com.adyen.v6.service.impl; import com.adyen.model.notification.NotificationRequest; import com.adyen.model.notification.NotificationRequestItem; import com.adyen.v6.events.AbstractNotificationEvent; import com.adyen.v6.events.builder.*; import com.adyen.v6.model.AdyenNotificationModel; +import com.adyen.v6.service.AdyenNotificationV2Service; import com.google.gson.Gson; import de.hybris.platform.servicelayer.event.EventService; import de.hybris.platform.servicelayer.model.ModelService; diff --git a/adyenv6notificationv2/src/com/adyen/v6/service/impl/DefaultAdyenTokenizationWebhookService.java b/adyenv6notificationv2/src/com/adyen/v6/service/impl/DefaultAdyenTokenizationWebhookService.java new file mode 100644 index 000000000..66aec6015 --- /dev/null +++ b/adyenv6notificationv2/src/com/adyen/v6/service/impl/DefaultAdyenTokenizationWebhookService.java @@ -0,0 +1,34 @@ +package com.adyen.v6.service.impl; + +import com.adyen.commerce.data.TokenWebhookRequestData; +import com.adyen.v6.events.TokenizationEvent; +import com.adyen.v6.request.TokenizationWebhookRequest; +import com.adyen.v6.service.AdyenTokenizationWebhookService; +import de.hybris.platform.servicelayer.dto.converter.Converter; +import de.hybris.platform.servicelayer.event.EventService; +import org.apache.log4j.Logger; + +public class DefaultAdyenTokenizationWebhookService implements AdyenTokenizationWebhookService { + private static final Logger LOG = Logger.getLogger(DefaultAdyenTokenizationWebhookService.class); + + private EventService eventService; + private Converter tokenWebhookRequestConverter; + + public void onRequest(TokenizationWebhookRequest tokenizationWebhookRequest) { + LOG.debug("Processing tokenization webhook request: " + tokenizationWebhookRequest); + + TokenWebhookRequestData requestData = tokenWebhookRequestConverter.convert(tokenizationWebhookRequest); + + TokenizationEvent tokenizationEvent = new TokenizationEvent(requestData); + + eventService.publishEvent(tokenizationEvent); + } + + public void setEventService(EventService eventService) { + this.eventService = eventService; + } + + public void setTokenWebhookRequestConverter(Converter tokenWebhookRequestConverter) { + this.tokenWebhookRequestConverter = tokenWebhookRequestConverter; + } +} diff --git a/adyenv6notificationv2/web/src/com/adyen/v6/controller/AdyenTokenizationWebhookController.java b/adyenv6notificationv2/web/src/com/adyen/v6/controller/AdyenTokenizationWebhookController.java new file mode 100644 index 000000000..d7d28cb0f --- /dev/null +++ b/adyenv6notificationv2/web/src/com/adyen/v6/controller/AdyenTokenizationWebhookController.java @@ -0,0 +1,49 @@ +package com.adyen.v6.controller; + +import com.adyen.v6.request.TokenizationWebhookRequest; +import com.adyen.v6.security.AdyenNotificationAuthenticationProvider; +import com.adyen.v6.service.AdyenTokenizationWebhookService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +@Controller +@RequestMapping(value = "/adyen/v6/tokenization/{baseSiteId}") +public class AdyenTokenizationWebhookController { + + private static ObjectMapper objectMapper; + + { + objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Resource(name = "adyenNotificationAuthenticationProvider") + private AdyenNotificationAuthenticationProvider adyenNotificationAuthenticationProvider; + + @Autowired + private AdyenTokenizationWebhookService adyenTokenizationWebhookService; + + @PostMapping + @ResponseBody + public ResponseEntity onReceive(@PathVariable final String baseSiteId, @RequestBody final String tokenizationWebhookRequest, final HttpServletRequest request) throws JsonProcessingException { + + if (!adyenNotificationAuthenticationProvider.authenticate(request, tokenizationWebhookRequest, baseSiteId)) { + throw new AccessDeniedException("Request authentication failed"); + } + + TokenizationWebhookRequest tokenizationRequest = objectMapper.readValue(tokenizationWebhookRequest, TokenizationWebhookRequest.class); + + adyenTokenizationWebhookService.onRequest(tokenizationRequest); + + return ResponseEntity.ok().build(); + } +} diff --git a/adyenv6subscription/resources/adyenv6subscription-beans.xml b/adyenv6subscription/resources/adyenv6subscription-beans.xml index 9e41fb354..bfc507497 100644 --- a/adyenv6subscription/resources/adyenv6subscription-beans.xml +++ b/adyenv6subscription/resources/adyenv6subscription-beans.xml @@ -9,7 +9,7 @@ - + diff --git a/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java b/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java index d3e8f80c1..6b5560391 100644 --- a/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java +++ b/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java @@ -27,7 +27,16 @@ public void afterPlaceOrder(final CommerceCheckoutParameter parameter, final Com LOG.info("Processing order after placement: {}", result.getOrder()); Optional.ofNullable(result.getOrder()) .filter(order -> CollectionUtils.isNotEmpty(order.getChildren())) - .ifPresent(this::createSubscriptionsForOrderEntries); + .ifPresent(this::handleSubscriptionOrder); + } + + protected void handleSubscriptionOrder(OrderModel order){ + order.getChildren().forEach(childOrder -> { + childOrder.setPaymentInfo(getModelService().clone(order.getPaymentInfo())); + getModelService().save(childOrder); + }); + + createSubscriptionsForOrderEntries(order); } protected void createSubscriptionsForOrderEntries(final OrderModel order) { diff --git a/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java b/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java index c4685afad..4f199904d 100644 --- a/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java +++ b/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java @@ -1,6 +1,7 @@ package com.adyen.v6.service.impl; import com.adyen.model.checkout.PaymentResponse; +import com.adyen.v6.constants.StorefrontType; import com.adyen.v6.factory.AdyenPaymentServiceFactory; import com.adyen.v6.model.RequestInfo; import com.adyen.v6.service.AdyenTransactionService; @@ -54,10 +55,12 @@ public OrderModel execute() throws Exception { try { - final RequestInfo request = new RequestInfo(); + final RequestInfo requestInfo = new RequestInfo(); + requestInfo.setStorefrontType(StorefrontType.SUBSCRIPTION); + final PaymentResponse paymentResponse = adyenPaymentServiceFactory .createAdyenCheckoutApiService(baseStoreService.getCurrentBaseStore()) - .authorisePayment(cartConverter.convert(cart), request, (CustomerModel) cart.getUser()); + .processPaymentRequest(cartConverter.convert(cart),null, requestInfo, (CustomerModel) cart.getUser()); final PaymentResponse.ResultCodeEnum resultCode = paymentResponse.getResultCode(); @@ -88,6 +91,9 @@ private CartModel setupCart() { final CartModel cart = cartService.clone(typeService.getComposedTypeForClass(CartModel.class), typeService.getComposedTypeForClass(CartEntryModel.class), subscriptionOrder, (String) keyGenerator.generate()); cart.setSubscriptionOrder(Boolean.TRUE); + //TODO in refactoring + cart.setParent(null); + cart.setPaymentInfo(modelService.clone(subscriptionOrder.getParent().getPaymentInfo())); modelService.save(cart); LOG.debug("Cart setup completed with code: {}", cart.getCode()); return cart;