Skip to content

Commit

Permalink
Merge pull request #252 from Adyen/develop
Browse files Browse the repository at this point in the history
Release 10.0.0
  • Loading branch information
martinsrenato authored Jun 1, 2021
2 parents b626a10 + 15cb20c commit 7af6e30
Show file tree
Hide file tree
Showing 37 changed files with 484 additions and 559 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -103,14 +106,16 @@ 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);
} else {
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);
Expand All @@ -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) {
Expand All @@ -142,7 +147,7 @@ public String submitDetails(final HttpServletRequest request) throws AdyenCompon
Map<String, String> 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());
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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";
Expand Down Expand Up @@ -260,15 +262,15 @@ 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");
errorMessage = getErrorMessageByRefusalReason(paymentsResponse.getRefusalReason());
}
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));
Expand All @@ -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));
}
Expand All @@ -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<String, String> 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));
}
Expand All @@ -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<String, String> 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:
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -627,6 +608,12 @@ private boolean isValidResult(String resultData, String isResultError) {
&& StringUtils.isNotBlank(resultData);
}

private Map<String, String> parseDetailsFromComponent(String details) {
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
return gson.fromJson(details, mapType);
}

@RequestMapping(value = "/back", method = RequestMethod.GET)
@RequireHardLogIn
@Override
Expand Down
Loading

0 comments on commit 7af6e30

Please sign in to comment.