diff --git a/adyenv6core/src/com/adyen/v6/facades/impl/DefaultAdyenCheckoutFacade.java b/adyenv6core/src/com/adyen/v6/facades/impl/DefaultAdyenCheckoutFacade.java index 5593a532d..aedb731a9 100644 --- a/adyenv6core/src/com/adyen/v6/facades/impl/DefaultAdyenCheckoutFacade.java +++ b/adyenv6core/src/com/adyen/v6/facades/impl/DefaultAdyenCheckoutFacade.java @@ -406,7 +406,7 @@ public OrderData authorisePayment(final HttpServletRequest request, final CartDa CheckoutPaymentsAction action = paymentsResponse.getAction(); LOGGER.info("Authorize payment with result code: " + resultCode + " action: " + (action != null ? action.getType() : "null")); - + // TODO: Put token on order! if (PaymentsResponse.ResultCodeEnum.AUTHORISED == resultCode || PaymentsResponse.ResultCodeEnum.PENDING == resultCode) { return createAuthorizedOrder(paymentsResponse); } @@ -533,7 +533,9 @@ public OrderData handle3DSResponse(final Map details) throws Exc private OrderData createAuthorizedOrder(final PaymentsResponse paymentsResponse) throws InvalidCartException { final CartModel cartModel = cartService.getSessionCart(); final String merchantTransactionCode = cartModel.getCode(); - + if(!paymentsResponse.getAdditionalData().isEmpty()&&paymentsResponse.getAdditionalData().containsKey("recurring.recurringDetailReference")) { + cartModel.getPaymentInfo().setAdyenSelectedReference(paymentsResponse.getAdditionalData().get("recurring.recurringDetailReference")); + } //First save the transactions to the CartModel < AbstractOrderModel getAdyenTransactionService().authorizeOrderModel(cartModel, merchantTransactionCode, paymentsResponse.getPspReference()); @@ -580,6 +582,7 @@ private OrderData fillOrderDataWithPaymentInfo(OrderData orderData, PaymentsResp if (paymentsResponse.getAdditionalData() != null) { orderData.setAdyenPosReceipt(paymentsResponse.getAdditionalData().get("pos.receipt")); + //orderData.getPaymentInfo().setgetPaymentInfo().set } return orderData; diff --git a/adyenv6core/src/com/adyen/v6/model/RequestInfo.java b/adyenv6core/src/com/adyen/v6/model/RequestInfo.java index e590cc819..d7ffafc1b 100644 --- a/adyenv6core/src/com/adyen/v6/model/RequestInfo.java +++ b/adyenv6core/src/com/adyen/v6/model/RequestInfo.java @@ -41,7 +41,7 @@ public RequestInfo(HttpServletRequest request) { this.origin = getOrigin(request); } - private RequestInfo() { + public RequestInfo() { } public String getOrigin(HttpServletRequest request) { diff --git a/adyenv6core/testsrc/com/adyen/v6/factory/AdyenRequestFactoryTest.java b/adyenv6core/testsrc/com/adyen/v6/factory/AdyenRequestFactoryTest.java index ab6ad29e8..d3ecb0cdf 100644 --- a/adyenv6core/testsrc/com/adyen/v6/factory/AdyenRequestFactoryTest.java +++ b/adyenv6core/testsrc/com/adyen/v6/factory/AdyenRequestFactoryTest.java @@ -196,7 +196,7 @@ public void testAuthorise() { testRecurringOption(RecurringContractMode.NONE, null); testRecurringOption(RecurringContractMode.ONECLICK, null); //testRecurringOption(RecurringContractMode.RECURRING, Recurring.ContractEnum.RECURRING); - //testRecurringOption(RecurringContractMode.ONECLICK_RECURRING, Recurring.ContractEnum.RECURRING); + //testRecurringOption(RecurringContractMode.RECURRING, Recurring.ContractEnum.RECURRING); //Test recurring contract when remember-me is set when(cartDataMock.getAdyenRememberTheseDetails()).thenReturn(true); diff --git a/adyenv6subscription/extensioninfo.xml b/adyenv6subscription/extensioninfo.xml index 3247c1522..9414b1b2b 100644 --- a/adyenv6subscription/extensioninfo.xml +++ b/adyenv6subscription/extensioninfo.xml @@ -24,17 +24,10 @@ - + + - - - - - - - - diff --git a/adyenv6subscription/resources/adyenv6subscription-beans.xml b/adyenv6subscription/resources/adyenv6subscription-beans.xml index 449a6f8bd..9e41fb354 100644 --- a/adyenv6subscription/resources/adyenv6subscription-beans.xml +++ b/adyenv6subscription/resources/adyenv6subscription-beans.xml @@ -8,5 +8,8 @@ + + + diff --git a/adyenv6subscription/resources/adyenv6subscription-items.xml b/adyenv6subscription/resources/adyenv6subscription-items.xml index aba9f6987..e58de6883 100644 --- a/adyenv6subscription/resources/adyenv6subscription-items.xml +++ b/adyenv6subscription/resources/adyenv6subscription-items.xml @@ -25,6 +25,16 @@ + + + + If the order is generated by subscription job + + + + + + @@ -35,5 +45,20 @@ + + + + The order the subcription is created from + + + + + The next date that customer should be charged + + + + + + diff --git a/adyenv6subscription/resources/adyenv6subscription-spring.xml b/adyenv6subscription/resources/adyenv6subscription-spring.xml index 1bb473fc9..d9627170d 100644 --- a/adyenv6subscription/resources/adyenv6subscription-spring.xml +++ b/adyenv6subscription/resources/adyenv6subscription-spring.xml @@ -22,4 +22,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/adyenv6subscription/resources/impex/projectdata-subscription-cronjob.impex b/adyenv6subscription/resources/impex/projectdata-subscription-cronjob.impex new file mode 100644 index 000000000..90c29889c --- /dev/null +++ b/adyenv6subscription/resources/impex/projectdata-subscription-cronjob.impex @@ -0,0 +1,5 @@ +INSERT_UPDATE CronJob; code[unique=true];job(code);singleExecutable;sessionLanguage(isocode) +;subscriptionCronJob;subscriptionCronJob;false;en + +INSERT_UPDATE Trigger;cronjob(code)[unique=true];cronExpression +;subscriptionCronJob; 0 0 2 * * ? \ No newline at end of file diff --git a/adyenv6subscription/src/com/adyen/v6/factory/SubscriptionPaymentRequestFactory.java b/adyenv6subscription/src/com/adyen/v6/factory/SubscriptionPaymentRequestFactory.java index 4b48d7be8..a96a4c182 100644 --- a/adyenv6subscription/src/com/adyen/v6/factory/SubscriptionPaymentRequestFactory.java +++ b/adyenv6subscription/src/com/adyen/v6/factory/SubscriptionPaymentRequestFactory.java @@ -2,27 +2,29 @@ import com.adyen.model.checkout.PaymentMethodDetails; import com.adyen.model.checkout.PaymentsRequest; +import com.adyen.model.checkout.details.CardDetails; import com.adyen.model.recurring.RecurringDetailsRequest; +import com.adyen.util.Util; +import com.adyen.v6.constants.Adyenv6coreConstants; import com.adyen.v6.enums.RecurringContractMode; import com.adyen.v6.model.RequestInfo; import com.adyen.v6.paymentmethoddetails.executors.AdyenPaymentMethodDetailsBuilderExecutor; import com.adyen.v6.utils.SubscriptionsUtils; import de.hybris.platform.commercefacades.order.CartFacade; import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.commercefacades.user.data.AddressData; import de.hybris.platform.core.model.user.CustomerModel; import de.hybris.platform.servicelayer.config.ConfigurationService; import org.apache.commons.lang3.BooleanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import javax.annotation.Resource; +import org.springframework.web.bind.annotation.RequestMethod; public class SubscriptionPaymentRequestFactory extends AdyenRequestFactory { private static final Logger LOG = LoggerFactory.getLogger(SubscriptionPaymentRequestFactory.class); - @Resource(name = "cartFacade") private CartFacade cartFacade; public SubscriptionPaymentRequestFactory(ConfigurationService configurationService, AdyenPaymentMethodDetailsBuilderExecutor adyenPaymentMethodDetailsBuilderExecutor) { @@ -30,18 +32,114 @@ public SubscriptionPaymentRequestFactory(ConfigurationService configurationServi } @Override - public PaymentsRequest createPaymentsRequest(String merchantAccount, CartData cartData, RequestInfo requestInfo, CustomerModel customerModel, RecurringContractMode recurringContractMode, Boolean guestUserTokenizationEnabled) { - PaymentsRequest paymentsRequest = super.createPaymentsRequest(merchantAccount, cartData, requestInfo, customerModel, recurringContractMode, guestUserTokenizationEnabled); - paymentsRequest.setShopperInteraction(PaymentsRequest.ShopperInteractionEnum.ECOMMERCE); - if (BooleanUtils.isTrue(SubscriptionsUtils.containsSubscription(getCartFacade().getSessionCart()))) { - paymentsRequest.setRecurringProcessingModel(PaymentsRequest.RecurringProcessingModelEnum.SUBSCRIPTION); - } else { - paymentsRequest.setRecurringProcessingModel(PaymentsRequest.RecurringProcessingModelEnum.CARDONFILE); + public PaymentsRequest createPaymentsRequest(String merchantAccount, CartData cartData, RequestInfo requestInfo, + CustomerModel customerModel, RecurringContractMode recurringContractMode, + Boolean guestUserTokenizationEnabled) { + + LOG.info("Creating PaymentsRequest for merchant account: {}", merchantAccount); + + if (BooleanUtils.isTrue(cartData.getSubscriptionOrder())) { + return createRecurringPaymentsRequest(merchantAccount, cartData, requestInfo, customerModel, + recurringContractMode, guestUserTokenizationEnabled); } + return createRegularPaymentsRequest(merchantAccount, cartData, requestInfo, customerModel, + recurringContractMode, guestUserTokenizationEnabled); + } + + private PaymentsRequest createRegularPaymentsRequest(String merchantAccount, CartData cartData, RequestInfo requestInfo, + CustomerModel customerModel, RecurringContractMode recurringContractMode, + Boolean guestUserTokenizationEnabled) { + LOG.info("Creating regular PaymentsRequest..."); + PaymentsRequest paymentsRequest = super.createPaymentsRequest(merchantAccount, cartData, requestInfo, + customerModel, recurringContractMode, guestUserTokenizationEnabled); + paymentsRequest.setShopperInteraction(PaymentsRequest.ShopperInteractionEnum.CONTAUTH); + + PaymentsRequest.RecurringProcessingModelEnum recurringProcessingModel = BooleanUtils.isTrue( + SubscriptionsUtils.containsSubscription(getCartFacade().getSessionCart())) + ? PaymentsRequest.RecurringProcessingModelEnum.SUBSCRIPTION + : PaymentsRequest.RecurringProcessingModelEnum.CARDONFILE; + + paymentsRequest.setRecurringProcessingModel(recurringProcessingModel); return paymentsRequest; } + protected PaymentsRequest createRecurringPaymentsRequest(final String merchantAccount, final CartData cartData, + final RequestInfo requestInfo, final CustomerModel customerModel, final RecurringContractMode recurringContractMode, + final Boolean guestUserTokenizationEnabled) + { + + LOG.info("Creating RecurringPaymentsRequest for merchant account: {}", merchantAccount); + + final PaymentsRequest paymentsRequest = new PaymentsRequest(); + final String adyenPaymentMethod = cartData.getAdyenPaymentMethod(); + + if (adyenPaymentMethod == null) + { + throw new IllegalArgumentException("Payment method is null"); + } + + updatePaymentRequest(merchantAccount, cartData, requestInfo, customerModel, paymentsRequest); + + final PaymentMethodDetails paymentMethod = adyenPaymentMethodDetailsBuilderExecutor.createPaymentMethodDetails(cartData); + if(paymentMethod instanceof CardDetails) { + ((CardDetails) paymentMethod).setStoredPaymentMethodId(cartData.getAdyenSelectedReference()); + } + paymentMethod.setType(Adyenv6coreConstants.PAYMENT_METHOD_SCHEME); + paymentsRequest.setPaymentMethod(paymentMethod); + + + updateApplicationInfoEcom(paymentsRequest.getApplicationInfo()); + + + paymentsRequest.setRedirectFromIssuerMethod(RequestMethod.POST.toString()); + paymentsRequest.setRedirectToIssuerMethod(RequestMethod.POST.toString()); + paymentsRequest.setShopperInteraction(PaymentsRequest.ShopperInteractionEnum.CONTAUTH); + paymentsRequest.setRecurringProcessingModel(PaymentsRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + + return paymentsRequest; + } + + + private void updatePaymentRequest(final String merchantAccount, final CartData cartData, final RequestInfo requestInfo, + final CustomerModel customerModel, final PaymentsRequest paymentsRequest) + { + + + final String currency = cartData.getTotalPrice().getCurrencyIso(); + final String reference = cartData.getCode(); + + final AddressData billingAddress = cartData.getPaymentInfo() != null ? cartData.getPaymentInfo().getBillingAddress() : null; + final AddressData deliveryAddress = cartData.getDeliveryAddress(); + + paymentsRequest.amount(Util.createAmount(cartData.getTotalPrice().getValue(), currency)).reference(reference).merchantAccount(merchantAccount) + .setCountryCode(getCountryCode(cartData)); + // set shopper details from CustomerModel. + if (customerModel != null) + { + paymentsRequest.setShopperReference(customerModel.getCustomerID()); + paymentsRequest.setShopperEmail(customerModel.getContactEmail()); + } + + // if address details are provided, set it to the PaymentRequest + if (deliveryAddress != null) + { + paymentsRequest.setDeliveryAddress(setAddressData(deliveryAddress)); + } + + if (billingAddress != null) + { + paymentsRequest.setBillingAddress(setAddressData(billingAddress)); + // set PhoneNumber if it is provided + final String phone = billingAddress.getPhone(); + if (phone != null && !phone.isEmpty()) + { + paymentsRequest.setTelephoneNumber(phone); + } + } + + } + @Override public RecurringDetailsRequest createListRecurringDetailsRequest(String merchantAccount, String customerId) { if (SubscriptionsUtils.containsSubscription(getCartFacade().getSessionCart())) { @@ -52,6 +150,7 @@ public RecurringDetailsRequest createListRecurringDetailsRequest(String merchant } + protected CartFacade getCartFacade() { return cartFacade; } diff --git a/adyenv6subscription/src/com/adyen/v6/hooks/AdyenRecurringOrdersCloneAbstractOrderHook.java b/adyenv6subscription/src/com/adyen/v6/hooks/AdyenRecurringOrdersCloneAbstractOrderHook.java new file mode 100644 index 000000000..df374d401 --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/hooks/AdyenRecurringOrdersCloneAbstractOrderHook.java @@ -0,0 +1,85 @@ +package com.adyen.v6.hooks; + +import de.hybris.platform.core.model.order.AbstractOrderEntryModel; +import de.hybris.platform.core.model.order.AbstractOrderModel; +import de.hybris.platform.core.model.order.OrderEntryModel; +import de.hybris.platform.core.model.order.payment.PaymentInfoModel; +import de.hybris.platform.core.model.user.AddressModel; +import de.hybris.platform.order.strategies.ordercloning.CloneAbstractOrderHook; +import de.hybris.platform.servicelayer.internal.model.impl.ItemModelCloneCreator; +import de.hybris.platform.servicelayer.model.ModelService; +import org.apache.commons.collections.CollectionUtils; + +import java.util.List; +import java.util.Optional; + +public class AdyenRecurringOrdersCloneAbstractOrderHook implements CloneAbstractOrderHook { + + private ItemModelCloneCreator itemModelCloneCreator; + private ModelService modelService; + + @Override + public void beforeClone(AbstractOrderModel original, Class abstractOrderClassResult) { + // Nothing to do + } + + @Override + public void afterClone(AbstractOrderModel original, T clone, Class abstractOrderClassResult) { + copyAddress(original.getDeliveryAddress(), clone::setDeliveryAddress); + copyAddress(original.getPaymentAddress(), clone::setPaymentAddress); + copyPaymentInfo(original.getPaymentInfo(), clone::setPaymentInfo); + clone.setSubscriptionOrder(Boolean.TRUE); + if (CollectionUtils.isNotEmpty(clone.getEntries())) { + clone.getEntries().forEach(e -> processEntries(original, (OrderEntryModel) e)); + } + modelService.saveAll(clone); + } + private void copyAddress(AddressModel originalAddress, java.util.function.Consumer addressSetter) { + Optional.ofNullable(originalAddress).ifPresent(address -> { + final AddressModel copiedAddress = (AddressModel) itemModelCloneCreator.copy(address); + copiedAddress.setDuplicate(Boolean.TRUE); + addressSetter.accept(copiedAddress); + modelService.save(copiedAddress); + }); + } + + private void copyPaymentInfo(PaymentInfoModel originalPaymentInfo, java.util.function.Consumer paymentInfoSetter) { + Optional.ofNullable(originalPaymentInfo).ifPresent(paymentInfo -> { + final PaymentInfoModel copiedPaymentInfo = (PaymentInfoModel) itemModelCloneCreator.copy(paymentInfo); + copiedPaymentInfo.setDuplicate(Boolean.TRUE); + paymentInfoSetter.accept(copiedPaymentInfo); + modelService.save(copiedPaymentInfo); + }); + } + + private void processEntries(AbstractOrderModel original, OrderEntryModel entry) { + entry.setOriginalOrderEntry( + (OrderEntryModel) original.getEntries().stream() + .filter(e2 -> entry.getEntryNumber().equals(e2.getEntryNumber())) + .findAny() + .orElse(null)); + entry.setMasterEntry(null); + entry.setCalculated(Boolean.TRUE); + entry.setIsSubscriptionEntry(Boolean.TRUE); + modelService.save(entry); + } + + + @Override + public void beforeCloneEntries(AbstractOrderModel original) { + // Nothing to do + } + + @Override + public void afterCloneEntries(AbstractOrderModel original, List clonedEntries) { + // Nothing to do + } + + public void setItemModelCloneCreator(ItemModelCloneCreator itemModelCloneCreator) { + this.itemModelCloneCreator = itemModelCloneCreator; + } + + public void setModelService(ModelService modelService) { + this.modelService = modelService; + } +} diff --git a/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java b/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java new file mode 100644 index 000000000..d3e8f80c1 --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHook.java @@ -0,0 +1,79 @@ +package com.adyen.v6.hooks; + +import de.hybris.platform.commerceservices.service.data.CommerceCheckoutParameter; +import de.hybris.platform.commerceservices.service.data.CommerceOrderResult; +import de.hybris.platform.core.model.order.AbstractOrderEntryModel; +import de.hybris.platform.core.model.order.AbstractOrderModel; +import de.hybris.platform.core.model.order.OrderModel; +import de.hybris.platform.subscriptionservices.enums.SubscriptionStatus; +import de.hybris.platform.subscriptionservices.model.BillingFrequencyModel; +import de.hybris.platform.subscriptionservices.model.SubscriptionModel; +import de.hybris.platform.subscriptionservices.model.SubscriptionTermModel; +import de.hybris.platform.subscriptionservices.subscription.impl.DefaultSubscriptionCommercePlaceOrderMethodHook; +import org.apache.commons.collections.CollectionUtils; +import org.joda.time.LocalDate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.Optional; + +public class AdyenSubscriptionCommercePlaceOrderMethodHook extends DefaultSubscriptionCommercePlaceOrderMethodHook { + + private static final Logger LOG = LoggerFactory.getLogger(AdyenSubscriptionCommercePlaceOrderMethodHook.class); + + @Override + public void afterPlaceOrder(final CommerceCheckoutParameter parameter, final CommerceOrderResult result) { + LOG.info("Processing order after placement: {}", result.getOrder()); + Optional.ofNullable(result.getOrder()) + .filter(order -> CollectionUtils.isNotEmpty(order.getChildren())) + .ifPresent(this::createSubscriptionsForOrderEntries); + } + + protected void createSubscriptionsForOrderEntries(final OrderModel order) { + LOG.info("Creating subscriptions for order entries: {}", order); + order.getEntries().stream() + .filter(entry -> CollectionUtils.isNotEmpty(entry.getChildEntries()) && CollectionUtils.isEmpty(entry.getEntryGroupNumbers())) + .forEach(this::createSubscriptionFromOrderEntry); + } + + protected void createSubscriptionFromOrderEntry(final AbstractOrderEntryModel entry) { + LOG.info("Creating subscription from order entry: {}", entry); + SubscriptionModel subscription = buildSubscriptionModel(entry); + getModelService().save(subscription); + LOG.info("Subscription created: {}", subscription); + } + + private SubscriptionModel buildSubscriptionModel(final AbstractOrderEntryModel entry) { + SubscriptionModel subscription = getModelService().create(SubscriptionModel.class); + AbstractOrderModel order = entry.getOrder(); + SubscriptionTermModel subscriptionTerm = entry.getProduct().getSubscriptionTerm(); + BillingFrequencyModel billingFrequency = subscriptionTerm.getBillingPlan().getBillingFrequency(); + LocalDate nextChargeDate = calculateNextChargeDate(order.getDate(), billingFrequency.getCode()); + + subscription.setOrderNumber(order.getCode()); + subscription.setPlacedOn(order.getDate()); + subscription.setNextChargeDate(nextChargeDate.toDate()); + subscription.setSubscriptionStatus(SubscriptionStatus.ACTIVE.getCode()); + subscription.setSubscriptionOrder((OrderModel) order); + subscription.setCustomerId(order.getUser().getUid()); + + return subscription; + } + + private LocalDate calculateNextChargeDate(final Date orderDate, final String billingFrequencyCode) { + LocalDate localOrderDate = LocalDate.fromDateFields(orderDate); + switch (billingFrequencyCode.toLowerCase()) { + case "monthly": + return localOrderDate.plusMonths(1); + case "quarterly": + return localOrderDate.plusMonths(3); + case "yearly": + return localOrderDate.plusMonths(12); + default: + String errorMessage = "Unsupported billing frequency code: " + billingFrequencyCode; + LOG.error(errorMessage); + throw new IllegalArgumentException(errorMessage); + } + } +} \ No newline at end of file diff --git a/adyenv6subscription/src/com/adyen/v6/job/SubscriptionCronJob.java b/adyenv6subscription/src/com/adyen/v6/job/SubscriptionCronJob.java new file mode 100644 index 000000000..2dfdcb02b --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/job/SubscriptionCronJob.java @@ -0,0 +1,77 @@ +package com.adyen.v6.job; + +import com.adyen.v6.repository.SubscriptionRepository; +import com.adyen.v6.service.impl.DefaultRecurringOrderService; +import de.hybris.platform.cronjob.enums.CronJobResult; +import de.hybris.platform.cronjob.enums.CronJobStatus; +import de.hybris.platform.cronjob.model.CronJobModel; +import de.hybris.platform.servicelayer.cronjob.AbstractJobPerformable; +import de.hybris.platform.servicelayer.cronjob.PerformResult; +import de.hybris.platform.subscriptionservices.model.SubscriptionModel; +import org.apache.log4j.Logger; + +import java.util.List; + +public class SubscriptionCronJob extends AbstractJobPerformable { + + private static final Logger LOG = Logger.getLogger(SubscriptionCronJob.class); + + private SubscriptionRepository subscriptionRepository; + + private DefaultRecurringOrderService recurringOrderService; + + + @Override + public PerformResult perform(CronJobModel cronJobModel) { + List subscriptionsToCharge = subscriptionRepository.getActiveSubscriptionByNextChargeDay(); + boolean exceptionsOccurred = processSubscriptions(subscriptionsToCharge); + return getResultBasedOnExceptions(exceptionsOccurred); + } + + private boolean processSubscriptions(List subscriptionsToCharge) { + boolean exceptionsOccurred = false; + for (SubscriptionModel subscriptionModel : subscriptionsToCharge) { + exceptionsOccurred |= processSubscription(subscriptionModel); + } + return exceptionsOccurred; + } + + private boolean processSubscription(SubscriptionModel subscriptionModel) { + boolean exceptionOccurred = false; + try { + if (subscriptionModel.getSubscriptionOrder() != null) { + processSubscriptionOrderChildren(subscriptionModel); + } + } catch (Exception e) { + LOG.error("Exception occurred while processing subscription: " + subscriptionModel.getId(), e); + exceptionOccurred = true; + } + return exceptionOccurred; + } + + private void processSubscriptionOrderChildren(SubscriptionModel subscriptionModel) { + subscriptionModel.getSubscriptionOrder().getChildren().forEach(abstractOrderModel -> { + try { + recurringOrderService.createRecurringOrderForSubscription(abstractOrderModel); + } catch (Exception e) { + LOG.error("Exception occurred while creating recurring order for abstractOrderModel: " + abstractOrderModel.getCode(), e); + } + }); + } + + private PerformResult getResultBasedOnExceptions(boolean exceptionsOccurred) { + if (exceptionsOccurred) { + return new PerformResult(CronJobResult.ERROR, CronJobStatus.FINISHED); + } + return new PerformResult(CronJobResult.SUCCESS, CronJobStatus.FINISHED); + } + + public void setSubscriptionRepository(SubscriptionRepository subscriptionRepository) { + this.subscriptionRepository = subscriptionRepository; + } + + + public void setRecurringOrderService(DefaultRecurringOrderService recurringOrderService) { + this.recurringOrderService = recurringOrderService; + } +} diff --git a/adyenv6subscription/src/com/adyen/v6/populators/AdyenSubscriptionOrderPopulator.java b/adyenv6subscription/src/com/adyen/v6/populators/AdyenSubscriptionOrderPopulator.java new file mode 100644 index 000000000..5976382eb --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/populators/AdyenSubscriptionOrderPopulator.java @@ -0,0 +1,15 @@ +package com.adyen.v6.populators; + +import de.hybris.platform.commercefacades.order.converters.populator.AbstractOrderPopulator; +import de.hybris.platform.commercefacades.order.data.OrderData; +import de.hybris.platform.core.model.order.OrderModel; +import de.hybris.platform.servicelayer.dto.converter.ConversionException; +import org.apache.commons.lang3.BooleanUtils; + +public class AdyenSubscriptionOrderPopulator extends AbstractOrderPopulator { + @Override + public void populate(OrderModel orderModel, OrderData orderData) throws ConversionException { + + orderData.setSubscriptionOrder(BooleanUtils.isTrue(orderModel.getSubscriptionOrder())); + } +} diff --git a/adyenv6subscription/src/com/adyen/v6/repository/SubscriptionRepository.java b/adyenv6subscription/src/com/adyen/v6/repository/SubscriptionRepository.java new file mode 100644 index 000000000..5fa52d609 --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/repository/SubscriptionRepository.java @@ -0,0 +1,46 @@ +package com.adyen.v6.repository; + +import com.adyen.v6.model.NotificationItemModel; +import de.hybris.platform.servicelayer.search.FlexibleSearchQuery; +import de.hybris.platform.subscriptionservices.model.SubscriptionModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class SubscriptionRepository extends AbstractRepository { + + private static final Logger LOG = LoggerFactory.getLogger(SubscriptionRepository.class); + private static final String SUBSCRIPTION_QUERY = + "SELECT {pk} FROM {Subscription} " + + "WHERE {subscriptionStatus} = 'active' " + + "AND {nextChargeDate} >= ?startOfDay " + + "AND {nextChargeDate} < ?startOfNextDay " + + "ORDER BY {pk} ASC"; + + public List getActiveSubscriptionByNextChargeDay() { + LOG.debug("Querying for subscriptions"); + return executeSubscriptionQuery( + SUBSCRIPTION_QUERY, + Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()), + Date.from(LocalDate.now().plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()) + ); + } + + private List executeSubscriptionQuery(String query, Date startOfDay, Date startOfNextDay) { + final FlexibleSearchQuery subscriptionQuery = new FlexibleSearchQuery(query); + subscriptionQuery.addQueryParameter("startOfDay", startOfDay); + subscriptionQuery.addQueryParameter("startOfNextDay", startOfNextDay); + + return flexibleSearchService + .search(subscriptionQuery) + .getResult() + .stream() + .map(SubscriptionModel.class::cast) + .collect(Collectors.toList()); + } +} diff --git a/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultRecurringOrderService.java b/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultRecurringOrderService.java new file mode 100644 index 000000000..f175def31 --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultRecurringOrderService.java @@ -0,0 +1,31 @@ +package com.adyen.v6.service.impl; + +import de.hybris.platform.commerceservices.impersonation.ImpersonationContext; +import de.hybris.platform.commerceservices.impersonation.ImpersonationService; +import de.hybris.platform.core.Registry; +import de.hybris.platform.core.model.order.AbstractOrderModel; +import de.hybris.platform.core.model.order.OrderModel; + +public class DefaultRecurringOrderService { + + private ImpersonationService impersonationService; + + + public void createRecurringOrderForSubscription(final AbstractOrderModel originalOrder) throws Exception + { + final ImpersonationService.Executor executor = (ImpersonationService.Executor) Registry + .getApplicationContext() + .getBean(SubscriptionOrderExecutor.BEAN_NAME, originalOrder); + + final ImpersonationContext context = new ImpersonationContext(); + context.setSite(originalOrder.getSite()); + context.setCurrency(originalOrder.getCurrency()); + context.setUser(originalOrder.getUser()); + context.setLanguage(((OrderModel) originalOrder).getLanguage()); + impersonationService.executeInContext(context, executor); + } + + public void setImpersonationService(ImpersonationService impersonationService) { + this.impersonationService = impersonationService; + } +} diff --git a/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultSubscriptionAdyenPaymentService.java b/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultSubscriptionAdyenPaymentService.java index f406e76e7..ffd54304b 100644 --- a/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultSubscriptionAdyenPaymentService.java +++ b/adyenv6subscription/src/com/adyen/v6/service/impl/DefaultSubscriptionAdyenPaymentService.java @@ -12,7 +12,6 @@ import de.hybris.platform.commercefacades.order.CartFacade; import de.hybris.platform.commercefacades.order.data.CartData; import de.hybris.platform.core.model.user.CustomerModel; -import de.hybris.platform.servicelayer.session.SessionService; import de.hybris.platform.store.BaseStoreModel; import de.hybris.platform.store.services.BaseStoreService; import org.apache.commons.lang3.BooleanUtils; @@ -26,6 +25,7 @@ import java.util.List; import static com.adyen.v6.utils.SubscriptionsUtils.containsSubscription; +import static com.adyen.v6.utils.SubscriptionsUtils.findRecurringProcessingModel; public class DefaultSubscriptionAdyenPaymentService extends DefaultAdyenPaymentService { @@ -85,7 +85,7 @@ public PaymentsResponse authorisePayment(final CartData cartData, final RequestI if (BooleanUtils.isTrue(containsSubscription(cartData))) { paymentsRequest.setShopperInteraction(PaymentsRequest.ShopperInteractionEnum.CONTAUTH); - paymentsRequest.setRecurringProcessingModel(PaymentsRequest.RecurringProcessingModelEnum.SUBSCRIPTION); + paymentsRequest.setRecurringProcessingModel(findRecurringProcessingModel(cartData)); } LOG.debug(paymentsRequest); PaymentsResponse paymentsResponse = checkout.payments(paymentsRequest); diff --git a/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java b/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java new file mode 100644 index 000000000..522fd7ec2 --- /dev/null +++ b/adyenv6subscription/src/com/adyen/v6/service/impl/SubscriptionOrderExecutor.java @@ -0,0 +1,171 @@ +package com.adyen.v6.service.impl; + +import com.adyen.model.checkout.PaymentsResponse; +import com.adyen.v6.factory.AdyenPaymentServiceFactory; +import com.adyen.v6.factory.SubscriptionAdyenPaymentServiceFactory; +import com.adyen.v6.factory.SubscriptionPaymentRequestFactory; +import com.adyen.v6.model.RequestInfo; +import com.adyen.v6.service.AdyenPaymentService; +import com.adyen.v6.service.AdyenTransactionService; +import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.commerceservices.enums.SalesApplication; +import de.hybris.platform.commerceservices.impersonation.ImpersonationService; +import de.hybris.platform.commerceservices.order.CommerceCartService; +import de.hybris.platform.commerceservices.order.CommerceCheckoutService; +import de.hybris.platform.commerceservices.service.data.CommerceCheckoutParameter; +import de.hybris.platform.core.model.order.AbstractOrderModel; +import de.hybris.platform.core.model.order.CartEntryModel; +import de.hybris.platform.core.model.order.CartModel; +import de.hybris.platform.core.model.order.OrderModel; +import de.hybris.platform.core.model.user.CustomerModel; +import de.hybris.platform.order.CalculationService; +import de.hybris.platform.order.CartService; +import de.hybris.platform.order.InvalidCartException; +import de.hybris.platform.servicelayer.dto.converter.Converter; +import de.hybris.platform.servicelayer.keygenerator.KeyGenerator; +import de.hybris.platform.servicelayer.model.ModelService; +import de.hybris.platform.servicelayer.type.TypeService; +import de.hybris.platform.stock.exception.InsufficientStockLevelException; +import de.hybris.platform.store.BaseStoreModel; +import de.hybris.platform.store.services.BaseStoreService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SubscriptionOrderExecutor implements ImpersonationService.Executor { + + private static final Logger LOG = LoggerFactory.getLogger(SubscriptionOrderExecutor.class); + + public static final String BEAN_NAME = "subscriptionOrderExecutor"; + + private CartService cartService; + private TypeService typeService; + private AdyenPaymentServiceFactory adyenPaymentServiceFactory; + private AdyenTransactionService adyenTransactionService; + private Converter cartConverter; + private CommerceCheckoutService commerceCheckoutService; + private BaseStoreService baseStoreService; + private ModelService modelService; + private KeyGenerator keyGenerator; + private final AbstractOrderModel subscriptionOrder; + + private SubscriptionAdyenPaymentServiceFactory subscriptionAdyenPaymentServiceFactory; + + public SubscriptionOrderExecutor(final AbstractOrderModel subscriptionOrder) + { + this.subscriptionOrder = subscriptionOrder; + } + + public OrderModel execute() throws Exception { + LOG.info("Executing SubscriptionOrderExecutor for order: {}", subscriptionOrder.getCode()); + + final CartModel cart = setupCart(); + LOG.debug("Cart setup completed with code: {}", cart.getCode()); + + try { + + final RequestInfo request = new RequestInfo(); + final PaymentsResponse paymentResponse = subscriptionAdyenPaymentServiceFactory + .createFromBaseStore(baseStoreService.getCurrentBaseStore()) + .authorisePayment(cartConverter.convert(cart), request, (CustomerModel) cart.getUser()); + + + final PaymentsResponse.ResultCodeEnum resultCode = paymentResponse.getResultCode(); + if (PaymentsResponse.ResultCodeEnum.AUTHORISED == resultCode) { + LOG.info("Payment authorised for cart: {}", cart.getCode()); + adyenTransactionService.authorizeOrderModel(cart, cart.getCode(), paymentResponse.getPspReference()); + return placeOrder(cart); + } else if (PaymentsResponse.ResultCodeEnum.RECEIVED == resultCode) { + LOG.info("Payment received for cart: {}", cart.getCode()); + return placeOrder(cart); + } else if (PaymentsResponse.ResultCodeEnum.PRESENTTOSHOPPER == resultCode) { + LOG.info("Payment presented to shopper for cart: {}", cart.getCode()); + return placeOrder(cart); + } else { + LOG.warn("Unexpected payment result code: {} for cart: {}", resultCode, cart.getCode()); + } + } finally { + LOG.debug("Removing cart: {}", cart.getCode()); + modelService.remove(cart); + } + + LOG.warn("Returning null as OrderModel for order: {}", subscriptionOrder.getCode()); + return null; + } + + private CartModel setupCart() { + LOG.debug("Setting up cart for order: {}", subscriptionOrder.getCode()); + final CartModel cart = cartService.clone(typeService.getComposedTypeForClass(CartModel.class), + typeService.getComposedTypeForClass(CartEntryModel.class), subscriptionOrder, (String) keyGenerator.generate()); + + modelService.save(cart); + LOG.debug("Cart setup completed with code: {}", cart.getCode()); + return cart; + } + + protected OrderModel placeOrder(final CartModel cartModel) throws InvalidCartException { + LOG.debug("Placing order for cart: {}", cartModel.getCode()); + final CommerceCheckoutParameter parameter = createCommerceCheckoutParameter(cartModel, true); + parameter.setSalesApplication(SalesApplication.WEB); + return getCommerceCheckoutService().placeOrder(parameter).getOrder(); + } + + protected CommerceCheckoutParameter createCommerceCheckoutParameter(final CartModel cart, final boolean enableHooks) + { + final CommerceCheckoutParameter parameter = new CommerceCheckoutParameter(); + parameter.setEnableHooks(enableHooks); + parameter.setCart(cart); + return parameter; + } + + + public void setCartService(CartService cartService) { + this.cartService = cartService; + } + + public void setTypeService(TypeService typeService) { + this.typeService = typeService; + } + + public AdyenPaymentServiceFactory getAdyenPaymentServiceFactory() { + return adyenPaymentServiceFactory; + } + + public void setAdyenPaymentServiceFactory(AdyenPaymentServiceFactory adyenPaymentServiceFactory) { + this.adyenPaymentServiceFactory = adyenPaymentServiceFactory; + } + + public void setAdyenTransactionService(AdyenTransactionService adyenTransactionService) { + this.adyenTransactionService = adyenTransactionService; + } + + public void setCartConverter(Converter cartConverter) { + this.cartConverter = cartConverter; + } + + public CommerceCheckoutService getCommerceCheckoutService() { + return commerceCheckoutService; + } + + public void setCommerceCheckoutService(CommerceCheckoutService commerceCheckoutService) { + this.commerceCheckoutService = commerceCheckoutService; + } + public BaseStoreService getBaseStoreService() { + return baseStoreService; + } + + public void setBaseStoreService(BaseStoreService baseStoreService) { + this.baseStoreService = baseStoreService; + } + + public void setModelService(ModelService modelService) { + this.modelService = modelService; + } + + public void setKeyGenerator(KeyGenerator keyGenerator) { + this.keyGenerator = keyGenerator; + } + + public void setSubscriptionAdyenPaymentServiceFactory(SubscriptionAdyenPaymentServiceFactory subscriptionAdyenPaymentServiceFactory) { + this.subscriptionAdyenPaymentServiceFactory = subscriptionAdyenPaymentServiceFactory; + } +} diff --git a/adyenv6subscription/src/com/adyen/v6/utils/SubscriptionsUtils.java b/adyenv6subscription/src/com/adyen/v6/utils/SubscriptionsUtils.java index 2f9a7d981..ad585730b 100644 --- a/adyenv6subscription/src/com/adyen/v6/utils/SubscriptionsUtils.java +++ b/adyenv6subscription/src/com/adyen/v6/utils/SubscriptionsUtils.java @@ -1,7 +1,19 @@ package com.adyen.v6.utils; +import com.adyen.model.checkout.CreateCheckoutSessionRequest; +import com.adyen.model.checkout.PaymentsRequest; import de.hybris.platform.commercefacades.order.data.CartData; public class SubscriptionsUtils { public static boolean containsSubscription(CartData cartData){ return cartData.getEntries().stream().anyMatch(orderEntryData -> orderEntryData.getProduct().getSubscriptionTerm()!=null); } + + public static PaymentsRequest.RecurringProcessingModelEnum findRecurringProcessingModel(CartData cartData){ + if(cartData.getEntries().stream() + .filter(orderEntryData->orderEntryData.getProduct().getSubscriptionTerm()!=null) + .allMatch(orderEntryData -> orderEntryData.getProduct().getSubscriptionTerm().getBillingPlan()!=null)) { + return PaymentsRequest.RecurringProcessingModelEnum.SUBSCRIPTION; + } + + return PaymentsRequest.RecurringProcessingModelEnum.UNSCHEDULEDCARDONFILE; + } } diff --git a/adyenv6subscription/testsrc/com/adyen/v6/factory/SubscriptionPaymentRequestFactoryTest.java b/adyenv6subscription/testsrc/com/adyen/v6/factory/SubscriptionPaymentRequestFactoryTest.java new file mode 100644 index 000000000..d9930db4e --- /dev/null +++ b/adyenv6subscription/testsrc/com/adyen/v6/factory/SubscriptionPaymentRequestFactoryTest.java @@ -0,0 +1,62 @@ +package com.adyen.v6.factory; + +import com.adyen.model.checkout.PaymentsRequest; +import com.adyen.v6.enums.RecurringContractMode; +import com.adyen.v6.model.RequestInfo; +import com.adyen.v6.paymentmethoddetails.executors.AdyenPaymentMethodDetailsBuilderExecutor; +import de.hybris.platform.commercefacades.order.CartFacade; +import de.hybris.platform.commercefacades.order.data.CartData; +import de.hybris.platform.core.model.user.CustomerModel; +import de.hybris.platform.servicelayer.config.ConfigurationService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SubscriptionPaymentRequestFactoryTest { + + + @Mock + private ConfigurationService configurationService; + + @Mock + private AdyenPaymentMethodDetailsBuilderExecutor adyenPaymentMethodDetailsBuilderExecutor; + + @Mock + private CartFacade cartFacade; + + @InjectMocks + private SubscriptionPaymentRequestFactory subscriptionPaymentRequestFactory; + + @Before + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testCreatePaymentsRequest() { + // Mocking necessary objects and methods + String merchantAccount = "merchantAccount"; + CartData cartData = mock(CartData.class); + RequestInfo requestInfo = mock(RequestInfo.class); + CustomerModel customerModel = mock(CustomerModel.class); + RecurringContractMode recurringContractMode = mock(RecurringContractMode.class); + Boolean guestUserTokenizationEnabled = Boolean.TRUE; + + when(cartData.getSubscriptionOrder()).thenReturn(Boolean.FALSE); + + // Call the method to be tested + PaymentsRequest paymentsRequest = subscriptionPaymentRequestFactory.createPaymentsRequest(merchantAccount, + cartData, requestInfo, customerModel, recurringContractMode, guestUserTokenizationEnabled); + + // Verifications and assertions + assertNotNull(paymentsRequest); + assertEquals(PaymentsRequest.ShopperInteractionEnum.CONTAUTH, paymentsRequest.getShopperInteraction()); + } +} diff --git a/adyenv6subscription/testsrc/com/adyen/v6/hooks/AdyenRecurringOrdersCloneAbstractOrderHookTest.java b/adyenv6subscription/testsrc/com/adyen/v6/hooks/AdyenRecurringOrdersCloneAbstractOrderHookTest.java new file mode 100644 index 000000000..0c5522dd2 --- /dev/null +++ b/adyenv6subscription/testsrc/com/adyen/v6/hooks/AdyenRecurringOrdersCloneAbstractOrderHookTest.java @@ -0,0 +1,67 @@ +package com.adyen.v6.hooks; + +import de.hybris.platform.core.model.order.AbstractOrderModel; +import de.hybris.platform.core.model.order.OrderEntryModel; +import de.hybris.platform.core.model.user.AddressModel; +import de.hybris.platform.core.model.order.payment.PaymentInfoModel; +import de.hybris.platform.servicelayer.internal.model.impl.ItemModelCloneCreator; +import de.hybris.platform.servicelayer.model.ModelService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; +import java.util.List; + +import static org.mockito.Mockito.*; + +public class AdyenRecurringOrdersCloneAbstractOrderHookTest { + + @Mock + private ItemModelCloneCreator itemModelCloneCreator; + + @Mock + private ModelService modelService; + + @InjectMocks + private AdyenRecurringOrdersCloneAbstractOrderHook adyenRecurringOrdersCloneAbstractOrderHook; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testAfterClone() { + // Create mock objects + AbstractOrderModel original = mock(AbstractOrderModel.class); + AbstractOrderModel clone = mock(AbstractOrderModel.class); + AddressModel deliveryAddress = mock(AddressModel.class); + AddressModel paymentAddress = mock(AddressModel.class); + PaymentInfoModel paymentInfo = mock(PaymentInfoModel.class); + OrderEntryModel orderEntry = mock(OrderEntryModel.class); + + // Setup mock behavior + when(original.getDeliveryAddress()).thenReturn(deliveryAddress); + when(original.getPaymentAddress()).thenReturn(paymentAddress); + when(original.getPaymentInfo()).thenReturn(paymentInfo); + when(original.getEntries()).thenReturn(Collections.singletonList(orderEntry)); + when(clone.getEntries()).thenReturn(Collections.singletonList(orderEntry)); + when(itemModelCloneCreator.copy(deliveryAddress)).thenReturn(deliveryAddress); + when(itemModelCloneCreator.copy(paymentAddress)).thenReturn(paymentAddress); + when(itemModelCloneCreator.copy(paymentInfo)).thenReturn(paymentInfo); + + // Call the method to be tested + adyenRecurringOrdersCloneAbstractOrderHook.afterClone(original, clone, AbstractOrderModel.class); + + // Verify interactions + verify(clone).setSubscriptionOrder(Boolean.TRUE); + verify(clone).setDeliveryAddress(any()); + verify(clone).setPaymentAddress(any()); + verify(clone).setPaymentInfo(any()); + verify(modelService, times(4)).save(any()); // Three individual saves and one saveAll + } + +} diff --git a/adyenv6subscription/testsrc/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHookTest.java b/adyenv6subscription/testsrc/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHookTest.java new file mode 100644 index 000000000..02a03287d --- /dev/null +++ b/adyenv6subscription/testsrc/com/adyen/v6/hooks/AdyenSubscriptionCommercePlaceOrderMethodHookTest.java @@ -0,0 +1,83 @@ +package com.adyen.v6.hooks; + +import de.hybris.platform.commerceservices.service.data.CommerceCheckoutParameter; +import de.hybris.platform.commerceservices.service.data.CommerceOrderResult; +import de.hybris.platform.core.model.order.OrderModel; +import de.hybris.platform.core.model.order.AbstractOrderEntryModel; +import de.hybris.platform.core.model.user.UserModel; +import de.hybris.platform.servicelayer.model.ModelService; +import de.hybris.platform.subscriptionservices.model.SubscriptionModel; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; +import java.util.Date; + +import static org.mockito.Mockito.*; + +public class AdyenSubscriptionCommercePlaceOrderMethodHookTest { + + @Mock + private ModelService modelService; + + @InjectMocks + private AdyenSubscriptionCommercePlaceOrderMethodHook adyenSubscriptionCommercePlaceOrderMethodHook; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testAfterPlaceOrder() { + // Create mock objects + CommerceCheckoutParameter parameter = mock(CommerceCheckoutParameter.class); + CommerceOrderResult result = mock(CommerceOrderResult.class); + OrderModel order = mock(OrderModel.class); + AbstractOrderEntryModel orderEntry = mock(AbstractOrderEntryModel.class); + + // Setup mock behavior + when(result.getOrder()).thenReturn(order); + when(order.getChildren()).thenReturn(Collections.singleton(order)); + when(order.getEntries()).thenReturn(Collections.singletonList(orderEntry)); + when(orderEntry.getChildEntries()).thenReturn(Collections.emptyList()); + when(orderEntry.getEntryGroupNumbers()).thenReturn(Collections.singleton(Integer.valueOf(0))); + when(orderEntry.getOrder()).thenReturn(order); + + // Call the method to be tested + adyenSubscriptionCommercePlaceOrderMethodHook.afterPlaceOrder(parameter, result); + + // Verify interactions + verify(modelService, never()).save(any(SubscriptionModel.class)); // No subscription should be created due to empty child entries and entry group numbers + } + + @Test + public void testCreateSubscriptionFromOrder() { + // Create mock objects + OrderModel order = mock(OrderModel.class); + SubscriptionModel subscription = mock(SubscriptionModel.class); + UserModel userModel = mock(UserModel.class); + when(userModel.getUid()).thenReturn("userId"); + when(order.getUser()).thenReturn(userModel); + // Setup mock behavior + when(modelService.create(SubscriptionModel.class)).thenReturn(subscription); + when(order.getCode()).thenReturn("orderCode"); + when(order.getDate()).thenReturn(new Date()); + + // Call the method to be tested + //adyenSubscriptionCommercePlaceOrderMethodHook.createSubscriptionFromOrder(order); + + // Verify interactions + verify(subscription).setOrderNumber("orderCode"); + verify(subscription).setPlacedOn(any(Date.class)); + verify(subscription).setNextChargeDate(any(Date.class)); + verify(subscription).setSubscriptionStatus(anyString()); + verify(subscription).setSubscriptionOrder(order); + verify(subscription).setCustomerId("userId"); + verify(modelService).save(subscription); + } + +} diff --git a/adyenv6subscription/testsrc/com/adyen/v6/job/SubscriptionCronJobTest.java b/adyenv6subscription/testsrc/com/adyen/v6/job/SubscriptionCronJobTest.java new file mode 100644 index 000000000..5908e913b --- /dev/null +++ b/adyenv6subscription/testsrc/com/adyen/v6/job/SubscriptionCronJobTest.java @@ -0,0 +1,69 @@ +package com.adyen.v6.job; + +import com.adyen.v6.repository.SubscriptionRepository; +import com.adyen.v6.service.impl.DefaultRecurringOrderService; +import de.hybris.platform.cronjob.enums.CronJobResult; +import de.hybris.platform.cronjob.enums.CronJobStatus; +import de.hybris.platform.cronjob.model.CronJobModel; +import de.hybris.platform.servicelayer.cronjob.PerformResult; +import de.hybris.platform.subscriptionservices.model.SubscriptionModel; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SubscriptionCronJobTest { + + @InjectMocks + private SubscriptionCronJob subscriptionCronJob; + + + @Mock + private SubscriptionRepository subscriptionRepository; + + @Mock + private DefaultRecurringOrderService recurringOrderService; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void testPerform_noSubscriptions_shouldReturnSuccess() { + // Arrange + when(subscriptionRepository.getActiveSubscriptionByNextChargeDay()).thenReturn(Collections.emptyList()); + CronJobModel cronJobModel = new CronJobModel(); + + // Act + PerformResult result = subscriptionCronJob.perform(cronJobModel); + + // Assert + assertEquals(CronJobResult.SUCCESS, result.getResult()); + assertEquals(CronJobStatus.FINISHED, result.getStatus()); + } + + @Test + public void testPerform_withSubscriptions_shouldReturnSuccess() { + // Arrange + SubscriptionModel subscriptionModel = mock(SubscriptionModel.class); + when(subscriptionRepository.getActiveSubscriptionByNextChargeDay()).thenReturn(List.of(subscriptionModel)); + CronJobModel cronJobModel = new CronJobModel(); + + // Act + PerformResult result = subscriptionCronJob.perform(cronJobModel); + + // Assert + assertEquals(CronJobResult.SUCCESS, result.getResult()); + assertEquals(CronJobStatus.FINISHED, result.getStatus()); + } + +}