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;