diff --git a/README.md b/README.md index f61c4faea..f88ddf59c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ This plugin supports SAP Commerce (Hybris) versions 6.x +The plugin is using following adyen libraries and API. +- [adyen-java-api-library](https://github.com/Adyen/adyen-java-api-library) (v14.0.0) +- [adyen-web](https://github.com/Adyen/adyen-web) (v4.3.1) +- [Adyen Checkout API](https://docs.adyen.com/api-explorer/) (v67) + ## Integration The SAP Commerce integrates Adyen Checkout for all card payments and local/redirect payment methods. diff --git a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/constants/AdyenControllerConstants.java b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/constants/AdyenControllerConstants.java index 0d1ce1bac..f36b2ac70 100644 --- a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/constants/AdyenControllerConstants.java +++ b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/constants/AdyenControllerConstants.java @@ -41,9 +41,7 @@ interface MultiStepCheckout { String CheckoutSummaryPage = ADDON_PREFIX + "pages/checkout/multi/checkoutSummaryPage"; String SelectPaymentMethod = ADDON_PREFIX + "pages/checkout/multi/selectPaymentMethodPage"; - String Validate3DSecurePaymentPage = ADDON_PREFIX + "pages/checkout/multi/3d-secure-payment-validation"; - String HppPaymentPage = ADDON_PREFIX + "pages/checkout/multi/hpp-payment"; - String Validate3DS2PaymentPage = ADDON_PREFIX + "pages/checkout/multi/3ds2_payment"; + String Validate3DSPaymentPage = ADDON_PREFIX + "pages/checkout/multi/3ds_payment"; String BillingAddressformPage = ADDON_PREFIX + "pages/checkout/multi/billingAddressForm"; } } diff --git a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenComponentController.java b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenComponentController.java index f34c9e455..ad97fac7d 100644 --- a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenComponentController.java +++ b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenComponentController.java @@ -22,8 +22,10 @@ import com.adyen.model.checkout.DefaultPaymentMethodDetails; import com.adyen.model.checkout.PaymentMethodDetails; +import com.adyen.model.checkout.PaymentsDetailsResponse; import com.adyen.model.checkout.PaymentsResponse; import com.adyen.model.checkout.details.ApplePayDetails; +import com.adyen.model.checkout.details.GooglePayDetails; import com.adyen.model.checkout.details.MbwayDetails; import com.adyen.model.checkout.details.PayPalDetails; import com.adyen.service.exception.ApiException; @@ -61,6 +63,7 @@ import java.util.Map; import static com.adyen.v6.constants.AdyenControllerConstants.COMPONENT_PREFIX; +import static com.adyen.v6.constants.AdyenControllerConstants.SUMMARY_CHECKOUT_PREFIX; import static com.adyen.v6.constants.Adyenv6coreConstants.PAYMENT_METHOD_PIX; @RestController @@ -103,6 +106,8 @@ public String componentPayment(final HttpServletRequest request) throws AdyenCom paymentMethodDetails = gson.fromJson(requestJson.get("paymentMethodDetails"), MbwayDetails.class); } else if(ApplePayDetails.APPLEPAY.equals(paymentMethod)) { paymentMethodDetails = gson.fromJson(requestJson.get("paymentMethodDetails"), ApplePayDetails.class); + } else if(GooglePayDetails.GOOGLEPAY.equals(paymentMethod)) { + paymentMethodDetails = gson.fromJson(requestJson.get("paymentMethodDetails"), GooglePayDetails.class); } else if(PAYMENT_METHOD_PIX.equals(paymentMethod)) { paymentMethodDetails = new DefaultPaymentMethodDetails(); paymentMethodDetails.setType(PAYMENT_METHOD_PIX); @@ -110,7 +115,7 @@ public String componentPayment(final HttpServletRequest request) throws AdyenCom throw new InvalidCartException("checkout.error.paymentethod.formentry.invalid"); } - cartData.setAdyenReturnUrl(getReturnUrl()); + cartData.setAdyenReturnUrl(getReturnUrl(paymentMethod)); PaymentsResponse paymentsResponse = getAdyenCheckoutFacade().componentPayment(request, cartData, paymentMethodDetails); return gson.toJson(paymentsResponse); @@ -121,7 +126,7 @@ public String componentPayment(final HttpServletRequest request) throws AdyenCom catch ( ApiException e) { LOGGER.error("ApiException: " + e.toString()); throw new AdyenComponentException("checkout.error.authorization.payment.refused"); - } catch (AdyenNonAuthorizedPaymentException e) { + } catch (AdyenNonAuthorizedPaymentException e) { LOGGER.debug("AdyenNonAuthorizedPaymentException occurred. Payment is refused."); throw new AdyenComponentException("checkout.error.authorization.payment.refused"); } catch (Exception e) { @@ -142,7 +147,7 @@ public String submitDetails(final HttpServletRequest request) throws AdyenCompon Map details = gson.fromJson(requestJson.get("details"), mapType); String paymentData = gson.fromJson(requestJson.get("paymentData"), String.class); - PaymentsResponse paymentsResponse = getAdyenCheckoutFacade().componentDetails(request, details, paymentData); + PaymentsDetailsResponse paymentsResponse = getAdyenCheckoutFacade().componentDetails(request, details, paymentData); return gson.toJson(paymentsResponse); } catch (ApiException e) { LOGGER.error("ApiException: " + e.toString()); @@ -200,8 +205,14 @@ protected void validateOrderForm(JsonObject requestJson) throws InvalidCartExcep } } - private String getReturnUrl() { - String url = COMPONENT_PREFIX + "/submit-details"; + private String getReturnUrl(String paymentMethod) { + String url; + if(GooglePayDetails.GOOGLEPAY.equals(paymentMethod)) { + //Google Pay will only use returnUrl if redirected to 3DS authentication + url = SUMMARY_CHECKOUT_PREFIX + "/authorise-3d-adyen-response"; + } else { + url = COMPONENT_PREFIX + "/submit-details"; + } BaseSiteModel currentBaseSite = baseSiteService.getCurrentBaseSite(); return siteBaseUrlResolutionService.getWebsiteUrlForSite(currentBaseSite, true, url); } diff --git a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenSummaryCheckoutStepController.java b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenSummaryCheckoutStepController.java index 0fcfa8116..b6617b48d 100644 --- a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenSummaryCheckoutStepController.java +++ b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/src/com/adyen/v6/controllers/pages/AdyenSummaryCheckoutStepController.java @@ -22,12 +22,16 @@ import com.adyen.constants.ApiConstants.RefusalReason; import com.adyen.model.PaymentResult; +import com.adyen.model.checkout.CheckoutPaymentsAction; +import com.adyen.model.checkout.PaymentsDetailsResponse; import com.adyen.model.checkout.PaymentsResponse; import com.adyen.service.exception.ApiException; import com.adyen.v6.constants.AdyenControllerConstants; import com.adyen.v6.exceptions.AdyenNonAuthorizedPaymentException; import com.adyen.v6.facades.AdyenCheckoutFacade; import com.adyen.v6.util.TerminalAPIUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import de.hybris.platform.acceleratorservices.enums.CheckoutPciOptionEnum; import de.hybris.platform.acceleratorservices.urlresolver.SiteBaseUrlResolutionService; import de.hybris.platform.acceleratorstorefrontcommons.annotations.PreValidateCheckoutStep; @@ -63,17 +67,13 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Type; import java.net.SocketTimeoutException; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; -import static com.adyen.constants.ApiConstants.Redirect.Data.MD; -import static com.adyen.constants.ApiConstants.Redirect.Data.PAREQ; -import static com.adyen.constants.ApiConstants.Redirect.Data.PAYMENT_DATA; -import static com.adyen.constants.ApiConstants.ThreeDS2Property.CHALLENGE_TOKEN; -import static com.adyen.constants.ApiConstants.ThreeDS2Property.FINGERPRINT_TOKEN; -import static com.adyen.constants.ApiConstants.ThreeDS2Property.THREEDS2_CHALLENGE_TOKEN; -import static com.adyen.constants.ApiConstants.ThreeDS2Property.THREEDS2_FINGERPRINT_TOKEN; import static com.adyen.constants.HPPConstants.Response.SHOPPER_LOCALE; import static com.adyen.model.checkout.PaymentsResponse.ResultCodeEnum.CHALLENGESHOPPER; import static com.adyen.model.checkout.PaymentsResponse.ResultCodeEnum.IDENTIFYSHOPPER; @@ -90,7 +90,9 @@ import static com.adyen.v6.constants.Adyenv6coreConstants.PAYMENT_METHOD_ONECLICK; import static com.adyen.v6.constants.Adyenv6coreConstants.PAYMENT_METHOD_POS; import static com.adyen.v6.constants.Adyenv6coreConstants.RATEPAY; +import static com.adyen.v6.facades.DefaultAdyenCheckoutFacade.DETAILS; import static com.adyen.v6.facades.DefaultAdyenCheckoutFacade.MODEL_CHECKOUT_SHOPPER_HOST; +import static com.adyen.v6.facades.DefaultAdyenCheckoutFacade.MODEL_CLIENT_KEY; import static com.adyen.v6.facades.DefaultAdyenCheckoutFacade.MODEL_ENVIRONMENT_MODE; @Controller @@ -101,8 +103,8 @@ public class AdyenSummaryCheckoutStepController extends AbstractCheckoutStepCont private final static String SUMMARY = "summary"; private static final String AUTHORISE_3D_SECURE_PAYMENT_URL = "/authorise-3d-adyen-response"; private static final String HPP_RESULT_URL = "/hpp-adyen-response"; - private static final String ADYEN_PAYLOAD = "payload"; private static final String REDIRECT_RESULT = "redirectResult"; + private static final String ACTION = "action"; private static final int POS_TOTALTIMEOUT_DEFAULT = 130; private static final String POS_TOTALTIMEOUT_KEY = "pos.totaltimeout"; @@ -260,7 +262,7 @@ public String placeOrder(@ModelAttribute("placeOrderForm") final PlaceOrderForm return REDIRECT_PREFIX + paymentsResponse.getAction().getUrl(); } LOGGER.debug("PaymentResponse resultCode is REDIRECTSHOPPER, redirecting shopper to local payment method page"); - return REDIRECT_PREFIX + paymentsResponse.getRedirect().getUrl(); + return REDIRECT_PREFIX + paymentsResponse.getAction().getUrl(); } if (REFUSED == paymentsResponse.getResultCode()) { LOGGER.debug("PaymentResponse is REFUSED"); @@ -268,7 +270,7 @@ public String placeOrder(@ModelAttribute("placeOrderForm") final PlaceOrderForm } if (CHALLENGESHOPPER == paymentsResponse.getResultCode() || IDENTIFYSHOPPER == paymentsResponse.getResultCode()) { LOGGER.debug("PaymentResponse is "+paymentsResponse.getResultCode()+", redirecting to 3DS2 flow"); - return redirectTo3DS2Validation(model, paymentsResponse); + return redirectTo3DSValidation(model, paymentsResponse); } } catch (Exception e) { LOGGER.error(ExceptionUtils.getStackTrace(e)); @@ -279,37 +281,27 @@ public String placeOrder(@ModelAttribute("placeOrderForm") final PlaceOrderForm return enterStep(model, redirectModel); } - @RequestMapping(value = "/3ds2-adyen-response", method = RequestMethod.POST) + @RequestMapping(value = AUTHORISE_3D_SECURE_PAYMENT_URL, method = RequestMethod.GET) @RequireHardLogIn - public String authorise3DS2Payment(final Model model, - final RedirectAttributes redirectModel, + public String authorise3DS1Payment(final RedirectAttributes redirectModel, final HttpServletRequest request) { - - String errorMessage = "checkout.error.authorization.failed"; + String redirectResult = request.getParameter(REDIRECT_RESULT); try { - OrderData orderData = adyenCheckoutFacade.handle3DS2Response(request); + OrderData orderData = adyenCheckoutFacade.handle3DSResponse(Collections.singletonMap(REDIRECT_RESULT, redirectResult)); LOGGER.debug("Redirecting to confirmation"); return redirectToOrderConfirmationPage(orderData); } catch (AdyenNonAuthorizedPaymentException e) { - LOGGER.debug("Handling AdyenNonAuthorizedPaymentException. Checking PaymentResponse."); - PaymentsResponse paymentsResponse = e.getPaymentsResponse(); - if(paymentsResponse != null) { - if (paymentsResponse.getResultCode() == CHALLENGESHOPPER || paymentsResponse.getResultCode() == IDENTIFYSHOPPER) { - LOGGER.debug("PaymentResponse is " + paymentsResponse.getResultCode() + ", redirecting to 3DS2 flow"); - return redirectTo3DS2Validation(model, paymentsResponse); - } - if (paymentsResponse.getResultCode() == REDIRECTSHOPPER) { - LOGGER.debug("PaymentResponse is " + paymentsResponse.getResultCode() + ", redirecting to 3DS flow"); - return redirectTo3DSValidation(model, paymentsResponse); - } - if (paymentsResponse.getResultCode() == PaymentsResponse.ResultCodeEnum.REFUSED) { - errorMessage = getErrorMessageByRefusalReason(paymentsResponse.getRefusalReason()); - LOGGER.debug("PaymentResponse is REFUSED: " + errorMessage); - } + LOGGER.debug("Handling AdyenNonAuthorizedPaymentException"); + String errorMessage = "checkout.error.authorization.failed"; + PaymentsDetailsResponse response = e.getPaymentsDetailsResponse(); + if (response != null && response.getResultCode() == PaymentsResponse.ResultCodeEnum.REFUSED) { + LOGGER.debug("PaymentResponse is REFUSED: " + response); + errorMessage = getErrorMessageByRefusalReason(response.getRefusalReason()); } + LOGGER.debug("Redirecting to select payment method.."); return redirectToSelectPaymentMethodWithError(redirectModel, errorMessage); } catch (CalculationException | InvalidCartException e) { - LOGGER.warn(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); } catch (Exception e) { LOGGER.error(ExceptionUtils.getStackTrace(e)); } @@ -320,26 +312,28 @@ public String authorise3DS2Payment(final Model model, @RequestMapping(value = AUTHORISE_3D_SECURE_PAYMENT_URL, method = RequestMethod.POST) @RequireHardLogIn - public String authorise3DSecurePayment(final RedirectAttributes redirectModel, - final HttpServletRequest request) { - String errorMessage = "checkout.error.authorization.failed"; + public String authorise3DSPayment(final RedirectAttributes redirectModel, + final HttpServletRequest request) { + String detailsJson = request.getParameter(DETAILS); try { - OrderData orderData = adyenCheckoutFacade.handle3DResponse(request); - + Map details = parseDetailsFromComponent(detailsJson); + OrderData orderData = adyenCheckoutFacade.handle3DSResponse(details); LOGGER.debug("Redirecting to confirmation"); return redirectToOrderConfirmationPage(orderData); } catch (AdyenNonAuthorizedPaymentException e) { - LOGGER.debug("Handling AdyenNonAuthorizedPaymentException"); - PaymentsResponse paymentsResponse = e.getPaymentsResponse(); - if (paymentsResponse != null && paymentsResponse.getResultCode() == PaymentsResponse.ResultCodeEnum.REFUSED) { - LOGGER.debug("PaymentResponse is REFUSED: " + paymentsResponse); - errorMessage = getErrorMessageByRefusalReason(paymentsResponse.getRefusalReason()); + LOGGER.debug("Handling AdyenNonAuthorizedPaymentException. Checking PaymentResponse."); + String errorMessage = "checkout.error.authorization.failed"; + PaymentsDetailsResponse paymentsDetailsResponse = e.getPaymentsDetailsResponse(); + if(paymentsDetailsResponse != null) { + if (paymentsDetailsResponse.getResultCode() == PaymentsResponse.ResultCodeEnum.REFUSED) { + errorMessage = getErrorMessageByRefusalReason(paymentsDetailsResponse.getRefusalReason()); + LOGGER.debug("PaymentResponse is REFUSED: " + errorMessage); + } } - LOGGER.debug("Redirecting to select payment method.."); return redirectToSelectPaymentMethodWithError(redirectModel, errorMessage); } catch (CalculationException | InvalidCartException e) { - LOGGER.error(e.getMessage(), e); + LOGGER.warn(e.getMessage(), e); } catch (Exception e) { LOGGER.error(ExceptionUtils.getStackTrace(e)); } @@ -351,19 +345,15 @@ public String authorise3DSecurePayment(final RedirectAttributes redirectModel, @RequestMapping(value = HPP_RESULT_URL, method = RequestMethod.GET) @RequireHardLogIn public String handleAdyenResponse(final HttpServletRequest request, final RedirectAttributes redirectModel) { - String payload = request.getParameter(ADYEN_PAYLOAD); String redirectResult = request.getParameter(REDIRECT_RESULT); HashMap details = new HashMap<>(); - if (payload != null && ! payload.isEmpty()) { - details.put(ADYEN_PAYLOAD, payload); - } if (redirectResult != null && ! redirectResult.isEmpty()) { details.put(REDIRECT_RESULT, redirectResult); } try { - PaymentsResponse response = adyenCheckoutFacade.handleRedirectPayload(details); + PaymentsDetailsResponse response = adyenCheckoutFacade.handleRedirectPayload(details); switch (response.getResultCode()) { case AUTHORISED: @@ -450,26 +440,14 @@ private String redirectToSelectPaymentMethodWithError(final RedirectAttributes r } private String redirectTo3DSValidation(Model model, PaymentsResponse paymentsResponse) { - if (paymentsResponse.getRedirect() != null && paymentsResponse.getRedirect().getData() != null) { - model.addAttribute("termUrl", paymentsResponse.getRedirect().getData().get("TermUrl")); - model.addAttribute("paReq", paymentsResponse.getRedirect().getData().get(PAREQ)); - model.addAttribute("md", paymentsResponse.getRedirect().getData().get(MD)); - model.addAttribute("issuerUrl", paymentsResponse.getRedirect().getUrl()); - } - return AdyenControllerConstants.Views.Pages.MultiStepCheckout.Validate3DSecurePaymentPage; - } + CheckoutPaymentsAction action = paymentsResponse.getAction(); - private String redirectTo3DS2Validation(Model model, PaymentsResponse paymentsResponse) { + model.addAttribute(MODEL_CLIENT_KEY, adyenCheckoutFacade.getClientKey()); model.addAttribute(MODEL_CHECKOUT_SHOPPER_HOST, adyenCheckoutFacade.getCheckoutShopperHost()); model.addAttribute(MODEL_ENVIRONMENT_MODE, adyenCheckoutFacade.getEnvironmentMode()); model.addAttribute(SHOPPER_LOCALE, adyenCheckoutFacade.getShopperLocale()); - model.addAttribute(PAYMENT_DATA, paymentsResponse.getPaymentData()); - if (paymentsResponse.getResultCode() == CHALLENGESHOPPER) { - model.addAttribute(CHALLENGE_TOKEN, paymentsResponse.getAuthentication().get(THREEDS2_CHALLENGE_TOKEN)); - } else { - model.addAttribute(FINGERPRINT_TOKEN, paymentsResponse.getAuthentication().get(THREEDS2_FINGERPRINT_TOKEN)); - } - return AdyenControllerConstants.Views.Pages.MultiStepCheckout.Validate3DS2PaymentPage; + model.addAttribute(ACTION, new Gson().toJson(action)); + return AdyenControllerConstants.Views.Pages.MultiStepCheckout.Validate3DSPaymentPage; } private String getReturnUrl(String adyenPaymentMethod) { @@ -583,6 +561,9 @@ public String handleComponentResult(final HttpServletRequest request, PaymentsResponse paymentsResponse = e.getPaymentsResponse(); if (paymentsResponse != null && paymentsResponse.getResultCode() != null) { switch (paymentsResponse.getResultCode()) { + case REDIRECTSHOPPER: + LOGGER.debug("Component PaymentResponse resultCode is REDIRECTSHOPPER, redirecting shopper to 3DS flow"); + return redirectTo3DSValidation(model, paymentsResponse); case REFUSED: errorMessageKey = "checkout.error.authorization.payment.refused"; break; @@ -627,6 +608,12 @@ private boolean isValidResult(String resultData, String isResultError) { && StringUtils.isNotBlank(resultData); } + private Map parseDetailsFromComponent(String details) { + Gson gson = new Gson(); + Type mapType = new TypeToken>() {}.getType(); + return gson.fromJson(details, mapType); + } + @RequestMapping(value = "/back", method = RequestMethod.GET) @RequireHardLogIn @Override diff --git a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/alternativeMethod.tag b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/alternativeMethod.tag index 42e62741d..8b7792b6e 100644 --- a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/alternativeMethod.tag +++ b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/alternativeMethod.tag @@ -19,6 +19,7 @@ ~ See the LICENSE file for more info. --%> <%@ attribute name="brandCode" required="true" type="java.lang.String" %> +<%@ attribute name="brand" required="false" type="java.lang.String" %> <%@ attribute name="name" required="true" type="java.lang.String" %> <%@ attribute name="issuers" required="false" type="java.util.List" %> <%@ attribute name="showDob" required="false" type="java.lang.Boolean" %> @@ -34,7 +35,7 @@
- + ${name} diff --git a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/checkoutOrderSummary.tag b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/checkoutOrderSummary.tag index 14b01ba4e..a7a1f5b22 100644 --- a/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/checkoutOrderSummary.tag +++ b/adyenv6b2ccheckoutaddon/acceleratoraddon/web/webroot/WEB-INF/tags/responsive/checkoutOrderSummary.tag @@ -11,40 +11,39 @@ <%-- Components --%> - - <%-- Render Paypal or Apple Pay button --%> - -
- -
+ +
+ +
+ + + + <%-- Render Pix QR code --%> + +
+
+ + <%-- Render Paypal, Apple or Google Pay button --%> +
+
+
+
- - - -
-
- -
-
-
-
- - -
- -
-
-
+ +
+ +
+
-<%-- Paypal and Apple Pay has it's own button --%> - +<%-- For components that do not have it's own button --%> +