Skip to content

Commit

Permalink
Merge pull request #420 from Adyen/feature/AD-252
Browse files Browse the repository at this point in the history
AD-252 Override "Place an Order" Endpoint to Include PaymentRequest a…
  • Loading branch information
kpieloch authored Jun 24, 2024
2 parents 5b25c2d + d21198b commit 9e4155d
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ private AdyencheckoutaddonapiWebConstants()
//empty to avoid instantiating this constant class
}

public static final String ADYEN_CHECKOUT_API_PREFIX = "/api/checkout";
public static final String ADYEN_CHECKOUT_PAGE_PREFIX = "/checkout/multi";
public static final String ADYEN_CHECKOUT_ORDER_CONFIRMATION = "/adyen/order-confirmation";
public static final String ADYEN_CHECKOUT_SELECT_PAYMENT = "/adyen/payment-method";
public static final String AUTHORISE_3D_SECURE_PAYMENT_URL = "/authorise-3d-adyen-response";

public static final String CART_PREFIX = "/cart";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,30 @@
package com.adyen.commerce.controllers.api;

import com.adyen.commerce.controllerbase.PlaceOrderControllerBase;
import com.adyen.commerce.exception.AdyenControllerException;
import com.adyen.commerce.facades.AdyenCheckoutApiFacade;
import com.adyen.commerce.request.PlaceOrderRequest;
import com.adyen.commerce.response.PlaceOrderResponse;
import com.adyen.commerce.validators.PaymentRequestValidator;
import com.adyen.model.checkout.PaymentDetailsRequest;
import com.adyen.model.checkout.PaymentResponse;
import com.adyen.service.exception.ApiException;
import com.adyen.v6.exceptions.AdyenNonAuthorizedPaymentException;
import de.hybris.platform.acceleratorfacades.flow.CheckoutFlowFacade;
import de.hybris.platform.acceleratorservices.urlresolver.SiteBaseUrlResolutionService;
import de.hybris.platform.acceleratorstorefrontcommons.annotations.RequireHardLogIn;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.commercefacades.order.CartFacade;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.servicelayer.config.ConfigurationService;
import de.hybris.platform.site.BaseSiteService;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;

import static com.adyen.commerce.constants.AdyencheckoutaddonapiWebConstants.ADYEN_CHECKOUT_API_PREFIX;
import static com.adyen.commerce.constants.AdyencheckoutaddonapiWebConstants.AUTHORISE_3D_SECURE_PAYMENT_URL;
import static com.adyen.commerce.constants.AdyenwebcommonsConstants.CHECKOUT_ERROR_AUTHORIZATION_FAILED;
import static com.adyen.commerce.util.ErrorMessageUtil.getErrorMessageByRefusalReason;
import static com.adyen.commerce.util.FieldValidationUtil.getFieldCodesFromValidation;
import static com.adyen.model.checkout.PaymentResponse.ResultCodeEnum.CHALLENGESHOPPER;
import static com.adyen.model.checkout.PaymentResponse.ResultCodeEnum.IDENTIFYSHOPPER;
import static com.adyen.model.checkout.PaymentResponse.ResultCodeEnum.PENDING;
import static com.adyen.model.checkout.PaymentResponse.ResultCodeEnum.PRESENTTOSHOPPER;
import static com.adyen.model.checkout.PaymentResponse.ResultCodeEnum.REDIRECTSHOPPER;
import static com.adyen.model.checkout.PaymentResponse.ResultCodeEnum.REFUSED;


@RequestMapping("/api/checkout")
@Controller
public class AdyenPlaceOrderController extends PlaceOrderControllerBase {
private static final Logger LOGGER = Logger.getLogger(AdyenPlaceOrderController.class);

private static final String CHECKOUT_ERROR_FORM_ENTRY_INVALID = "checkout.error.paymentethod.formentry.invalid";
public static final String GET_TYPE = "getType";


@Autowired
private CheckoutFlowFacade checkoutFlowFacade;
Expand All @@ -66,9 +35,6 @@ public class AdyenPlaceOrderController extends PlaceOrderControllerBase {
@Autowired
private AdyenCheckoutApiFacade adyenCheckoutApiFacade;

@Autowired
private ConfigurationService configurationService;

@Resource(name = "siteBaseUrlResolutionService")
private SiteBaseUrlResolutionService siteBaseUrlResolutionService;

Expand All @@ -77,163 +43,43 @@ public class AdyenPlaceOrderController extends PlaceOrderControllerBase {

@RequireHardLogIn
@PostMapping("/place-order")
public ResponseEntity<PlaceOrderResponse> placeOrder(@RequestBody PlaceOrderRequest placeOrderRequest, HttpServletRequest request) throws Exception {
public ResponseEntity<PlaceOrderResponse> onPlaceOrder(@RequestBody PlaceOrderRequest placeOrderRequest, HttpServletRequest request) throws Exception {
PlaceOrderResponse placeOrderResponse = super.placeOrder(placeOrderRequest, request);

String adyenPaymentMethodType = extractPaymentMethodType(placeOrderRequest);

preHandleAndValidateRequest(placeOrderRequest, adyenPaymentMethodType);

if (!isCartValid()) {
LOGGER.warn("Cart is invalid.");
throw new AdyenControllerException(CHECKOUT_ERROR_AUTHORIZATION_FAILED);
}

return handlePayment(request, placeOrderRequest, adyenPaymentMethodType);
return ResponseEntity.ok(placeOrderResponse);
}

@RequireHardLogIn
@PostMapping("/additional-details")
public ResponseEntity<PlaceOrderResponse> onAdditionalDetails(@RequestBody PaymentDetailsRequest detailsRequest, HttpServletRequest request) throws Exception {
try {
OrderData orderData = adyenCheckoutApiFacade.placeOrderWithAdditionalDetails(detailsRequest);
PlaceOrderResponse placeOrderResponse = new PlaceOrderResponse();
placeOrderResponse.setOrderNumber(orderData.getCode());
return ResponseEntity.status(HttpStatus.OK).body(placeOrderResponse);
} catch (ApiException e) {
LOGGER.error("ApiException: " + e);
throw new AdyenControllerException(CHECKOUT_ERROR_AUTHORIZATION_FAILED);
} catch (Exception e) {
LOGGER.error("Exception", e);
throw new AdyenControllerException(CHECKOUT_ERROR_AUTHORIZATION_FAILED);
}
}
PlaceOrderResponse placeOrderResponse = super.handleAdditionalDetails(detailsRequest);

private static String extractPaymentMethodType(PlaceOrderRequest placeOrderRequest) throws AdyenControllerException {
if (placeOrderRequest == null || placeOrderRequest.getPaymentRequest() == null || placeOrderRequest.getPaymentRequest().getPaymentMethod() == null) {
throw new AdyenControllerException(CHECKOUT_ERROR_AUTHORIZATION_FAILED);
}
Object actualInstance = placeOrderRequest.getPaymentRequest().getPaymentMethod().getActualInstance();
if (actualInstance == null) {
throw new AdyenControllerException(CHECKOUT_ERROR_AUTHORIZATION_FAILED);
}
Class<?> aClass = actualInstance.getClass();
try {
return aClass.getMethod(GET_TYPE).invoke(actualInstance).toString();
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new AdyenControllerException(CHECKOUT_ERROR_AUTHORIZATION_FAILED);
}
}

private ResponseEntity<PlaceOrderResponse> handlePayment(HttpServletRequest request, PlaceOrderRequest placeOrderRequest, String adyenPaymentMethod) {
final CartData cartData = cartFacade.getSessionCart();

String errorMessage = CHECKOUT_ERROR_AUTHORIZATION_FAILED;

try {
cartData.setAdyenReturnUrl(getPaymentRedirectReturnUrl());
OrderData orderData = adyenCheckoutApiFacade.placeOrderWithPayment(request, cartData, placeOrderRequest.getPaymentRequest());

PlaceOrderResponse placeOrderResponse = new PlaceOrderResponse();
placeOrderResponse.setOrderNumber(orderData.getCode());
return ResponseEntity.status(HttpStatus.OK).body(placeOrderResponse);

} catch (ApiException e) {
LOGGER.error("API exception: ", e);
} catch (AdyenNonAuthorizedPaymentException e) {
LOGGER.info("Handling AdyenNonAuthorizedPaymentException. Checking PaymentResponse.");
PaymentResponse paymentsResponse = e.getPaymentsResponse();
if (REDIRECTSHOPPER == paymentsResponse.getResultCode() || CHALLENGESHOPPER == paymentsResponse.getResultCode() ||
IDENTIFYSHOPPER == paymentsResponse.getResultCode() || PENDING == paymentsResponse.getResultCode() ||
PRESENTTOSHOPPER == paymentsResponse.getResultCode()) {
LOGGER.debug("PaymentResponse is " + paymentsResponse.getResultCode() + ", executing action for pspReference: " + paymentsResponse.getPspReference());
return executeAction(paymentsResponse);
} else if (REFUSED == paymentsResponse.getResultCode()) {
LOGGER.info("PaymentResponse is REFUSED, pspReference: " + paymentsResponse.getPspReference());
errorMessage = getErrorMessageByRefusalReason(paymentsResponse.getRefusalReason());
} else if (PaymentResponse.ResultCodeEnum.ERROR == paymentsResponse.getResultCode()) {
LOGGER.error("PaymentResponse is ERROR, reason: " + paymentsResponse.getRefusalReason() + " pspReference: " + paymentsResponse.getPspReference());
}
} catch (Exception e) {
LOGGER.error(ExceptionUtils.getStackTrace(e));
}

throw new AdyenControllerException(errorMessage);
return ResponseEntity.ok(placeOrderResponse);
}

private void preHandleAndValidateRequest(PlaceOrderRequest placeOrderRequest, String adyenPaymentMethod) {
final BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(placeOrderRequest, "placeOrderRequest");

boolean showRememberDetails = adyenCheckoutApiFacade.showRememberDetails();
boolean holderNameRequired = adyenCheckoutApiFacade.getHolderNameRequired();

PaymentRequestValidator paymentRequestValidator = new PaymentRequestValidator(adyenCheckoutApiFacade.getStoredCards(), showRememberDetails, holderNameRequired);
paymentRequestValidator.validate(placeOrderRequest, bindingResult);

if (bindingResult.hasErrors()) {
LOGGER.warn("Payment form is invalid.");
LOGGER.warn(bindingResult.getAllErrors().stream().map(DefaultMessageSourceResolvable::getCode).reduce((x, y) -> (x + " " + y)));
throw new AdyenControllerException(CHECKOUT_ERROR_FORM_ENTRY_INVALID, getFieldCodesFromValidation(bindingResult));
}

adyenCheckoutApiFacade.preHandlePlaceOrder(placeOrderRequest.getPaymentRequest(),adyenPaymentMethod,
placeOrderRequest.getBillingAddress(), placeOrderRequest.isUseAdyenDeliveryAddress());
@Override
public AdyenCheckoutApiFacade getAdyenCheckoutApiFacade() {
return adyenCheckoutApiFacade;
}

private ResponseEntity<PlaceOrderResponse> executeAction(PaymentResponse paymentsResponse) {
PlaceOrderResponse placeOrderResponse = new PlaceOrderResponse();
placeOrderResponse.setPaymentsResponse(paymentsResponse);
placeOrderResponse.setExecuteAction(true);
placeOrderResponse.setPaymentsAction(paymentsResponse.getAction());
return ResponseEntity.ok(placeOrderResponse);
@Override
public CheckoutFlowFacade getCheckoutFlowFacade() {
return checkoutFlowFacade;
}

private boolean isCartValid() {

if (checkoutFlowFacade.hasNoDeliveryAddress()) {
LOGGER.error("No delivery address.");
return false;
}

if (checkoutFlowFacade.hasNoDeliveryMode()) {
LOGGER.error("No delivery mode.");
return false;
}

if (checkoutFlowFacade.hasNoPaymentInfo()) {
LOGGER.error("No payment info.");
return false;
}

final CartData cartData = checkoutFlowFacade.getCheckoutCart();

if (!checkoutFlowFacade.containsTaxValues()) {
LOGGER.error(String.format("Cart %s does not have any tax values, which means the tax cacluation was not properly done, placement of order can't continue", cartData.getCode()));
LOGGER.error("Tax missing.");
return false;

}

if (!cartData.isCalculated()) {
LOGGER.error(String.format("Cart %s has a calculated flag of FALSE, placement of order can't continue", cartData.getCode()));
LOGGER.error("Cart not calculated.");
return false;

}

return true;
@Override
public CartFacade getCartFacade() {
return cartFacade;
}

private String getPaymentRedirectReturnUrl() {
String url = ADYEN_CHECKOUT_API_PREFIX + AUTHORISE_3D_SECURE_PAYMENT_URL;

BaseSiteModel currentBaseSite = baseSiteService.getCurrentBaseSite();

return siteBaseUrlResolutionService.getWebsiteUrlForSite(currentBaseSite, true, url);
@Override
public BaseSiteService getBaseSiteService() {
return baseSiteService;
}


@Override
public AdyenCheckoutApiFacade getAdyenCheckoutApiFacade() {
return adyenCheckoutApiFacade;
public SiteBaseUrlResolutionService getSiteBaseUrlResolutionService() {
return siteBaseUrlResolutionService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.util.Base64;

import static com.adyen.commerce.constants.AdyencheckoutaddonapiWebConstants.*;
import static com.adyen.commerce.constants.AdyenwebcommonsConstants.ADYEN_CHECKOUT_API_PREFIX;
import static com.adyen.commerce.constants.AdyenwebcommonsConstants.AUTHORISE_3D_SECURE_PAYMENT_URL;
import static de.hybris.platform.acceleratorstorefrontcommons.controllers.AbstractController.REDIRECT_PREFIX;

@Controller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
import com.adyen.commerce.constants.AdyenoccConstants;
import com.adyen.commerce.controllerbase.PlaceOrderControllerBase;
import com.adyen.commerce.facades.AdyenCheckoutApiFacade;
import com.adyen.commerce.request.PlaceOrderRequest;
import com.adyen.commerce.response.PlaceOrderResponse;
import com.adyen.model.checkout.PaymentDetailsRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import de.hybris.platform.acceleratorfacades.flow.CheckoutFlowFacade;
import de.hybris.platform.acceleratorservices.urlresolver.SiteBaseUrlResolutionService;
import de.hybris.platform.commercefacades.order.CartFacade;
import de.hybris.platform.commerceservices.request.mapping.annotation.ApiVersion;
import de.hybris.platform.site.BaseSiteService;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdUserIdAndCartIdParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -19,6 +24,9 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping(value = AdyenoccConstants.ADYEN_USER_CART_PREFIX)
@ApiVersion("v2")
Expand All @@ -28,9 +36,31 @@ public class PlaceOrderController extends PlaceOrderControllerBase {
@Autowired
private AdyenCheckoutApiFacade adyenCheckoutApiFacade;

@Autowired
private CheckoutFlowFacade checkoutFlowFacade;

@Autowired
private CartFacade cartFacade;

@Resource(name = "siteBaseUrlResolutionService")
private SiteBaseUrlResolutionService siteBaseUrlResolutionService;

@Resource(name = "baseSiteService")
private BaseSiteService baseSiteService;

@Secured({ "ROLE_CUSTOMERGROUP", "ROLE_CLIENT", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT" })
@PostMapping(value = "/additional-details", produces= MediaType.APPLICATION_JSON_VALUE)
@Secured({"ROLE_CUSTOMERGROUP", "ROLE_CLIENT", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT"})
@PostMapping(value = "/place-order", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(operationId = "placeOrder", summary = "Handle place order request", description =
"Places order based on request data")
@ApiBaseSiteIdUserIdAndCartIdParam
public ResponseEntity<PlaceOrderResponse> onPlaceOrder(@RequestBody PlaceOrderRequest placeOrderRequest, HttpServletRequest request) throws Exception {
PlaceOrderResponse placeOrderResponse = super.placeOrder(placeOrderRequest, request);

return ResponseEntity.ok(placeOrderResponse);
}

@Secured({"ROLE_CUSTOMERGROUP", "ROLE_CLIENT", "ROLE_CUSTOMERMANAGERGROUP", "ROLE_TRUSTED_CLIENT"})
@PostMapping(value = "/additional-details", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(operationId = "additionalDetails", summary = "Handle additional details action", description =
"Places pending order based on additional details request")
@ApiBaseSiteIdUserIdAndCartIdParam
Expand All @@ -45,4 +75,24 @@ public ResponseEntity<String> onAdditionalDetails(@RequestBody PaymentDetailsReq
public AdyenCheckoutApiFacade getAdyenCheckoutApiFacade() {
return adyenCheckoutApiFacade;
}

@Override
public CheckoutFlowFacade getCheckoutFlowFacade() {
return checkoutFlowFacade;
}

@Override
public CartFacade getCartFacade() {
return cartFacade;
}

@Override
public BaseSiteService getBaseSiteService() {
return baseSiteService;
}

@Override
public SiteBaseUrlResolutionService getSiteBaseUrlResolutionService() {
return siteBaseUrlResolutionService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public final class AdyenwebcommonsConstants extends GeneratedAdyenwebcommonsCons

public static final String CHECKOUT_ERROR_AUTHORIZATION_FAILED = "checkout.error.authorization.failed";

public static final String ADYEN_CHECKOUT_API_PREFIX = "/api/checkout";
public static final String AUTHORISE_3D_SECURE_PAYMENT_URL = "/authorise-3d-adyen-response";



private AdyenwebcommonsConstants()
{
Expand Down
Loading

0 comments on commit 9e4155d

Please sign in to comment.