Skip to content

Commit

Permalink
Merge pull request #201 from Adyen/develop
Browse files Browse the repository at this point in the history
Release 7.0.0
  • Loading branch information
rkewlani authored Aug 14, 2020
2 parents b171ebe + f12129a commit 931c229
Show file tree
Hide file tree
Showing 54 changed files with 2,104 additions and 478 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ It returns an instance of OrderWSDTO obtained from OrderData of the placed order
For Boleto, it will contain the pdf url, the base64 encoded data, expiration date and due date
https://docs.adyen.com/developers/payment-methods/boleto-bancario/boleto-payment-request

## 3DS2 configuration
By default 3DS2 is enabled (Except for OCC). If you want to disable 3DS2 in your system, please set following property in local.properties file, build your environment and restart the server.
## 3DS2 configuration
By default 3DS2 is enabled (Except for OCC). If you want to disable 3DS2 in your system, please set following property in local.properties file, build your environment and restart the server.
```
is3DS2allowed = false
```
Expand All @@ -141,6 +141,24 @@ POS timeout (time calculated since initiating a payment) is max time to keep ter
pos.totaltimeout = 130
```

## Pending Order Timeout configuration
By default, an order remains in PAYMENT_PENDING status in order management for 1 hour and it is configured in dynamic order process defintiion file.
Based on which extension you are using (fulfillment or ordermanangement) timeout value can be updated in corresponding order-process.xml file.

For example, following 2 files have 60 mins configuration under waitForAdyenPendingPayment process with delay value=PT60M

Fulfillment extension file - resources/adyenv6fulfilmentprocess/process/order-process.xml

OrderManagement extension file - resources/adyenv6ordermanagement/process/order-process.xml
```
<wait id="waitForAdyenPendingPayment" then="checkPendingOrder">
<event>AdyenPaymentResult</event>
<timeout delay="PT60M" then="checkPendingOrder"/>
</wait>
```

## PayPal configuration
This plugin uses Adyen's Checkout Component for PayPal payments. To use that in a live environment, a PayPal Merchant Id is required [(check here how to get one)](https://docs.adyen.com/payment-methods/paypal/web-component#get-your-paypal-merchant-id). This id has to be provided when adding your Adyen credentials to the BaseStore via the backoffice [(installation step 5)](#installation).

## Documentation
https://docs.adyen.com/developers/plugins/hybris
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
public interface AdyenControllerConstants
{
String ADDON_PREFIX = "addon:/adyenv6b2ccheckoutaddon/";
String SELECT_PAYMENT_METHOD_PREFIX = "/checkout/multi/adyen/select-payment-method";
String SUMMARY_CHECKOUT_PREFIX = "/checkout/multi/adyen/summary";
String PAYPAL_ECS_PREFIX = "/adyen/paypal-ecs";
String COMPONENT_PREFIX = "/adyen/component";

/**
* Class with view name constants
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Hybris Extension
*
* Copyright (c) 2020 Adyen B.V.
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*/
package com.adyen.v6.controllers.pages;

import com.adyen.model.checkout.PaymentsResponse;
import com.adyen.model.checkout.details.PayPalDetails;
import com.adyen.service.exception.ApiException;
import com.adyen.v6.exceptions.AdyenComponentException;
import com.adyen.v6.exceptions.AdyenNonAuthorizedPaymentException;
import com.adyen.v6.facades.AdyenCheckoutFacade;
import com.adyen.v6.forms.AdyenPaymentForm;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import de.hybris.platform.acceleratorfacades.flow.CheckoutFlowFacade;
import de.hybris.platform.acceleratorfacades.order.AcceleratorCheckoutFacade;
import de.hybris.platform.commercefacades.order.data.CartData;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.order.InvalidCartException;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static com.adyen.v6.constants.AdyenControllerConstants.COMPONENT_PREFIX;

@Controller
@RequestMapping(COMPONENT_PREFIX)
public class AdyenComponentController {
private static final Logger LOGGER = Logger.getLogger(AdyenComponentController.class);

@Resource(name = "adyenCheckoutFacade")
private AdyenCheckoutFacade adyenCheckoutFacade;

@Resource(name = "checkoutFlowFacade")
private CheckoutFlowFacade checkoutFlowFacade;

@Resource(name = "acceleratorCheckoutFacade")
private AcceleratorCheckoutFacade checkoutFacade;

@RequestMapping(value = "/payment", method = RequestMethod.POST)
@ResponseBody
public String componentPayment(final Model model,
final HttpServletRequest request,
@Valid final AdyenPaymentForm adyenPaymentForm,
final BindingResult bindingResult) throws AdyenComponentException {
LOGGER.debug("Component paymentForm: " + adyenPaymentForm);

if(!isValidateSessionCart())
{
if (adyenPaymentForm.getBillingAddress() != null) {
adyenPaymentForm.resetFormExceptBillingAddress();
}
throw new AdyenComponentException("checkout.error.paymentethod.formentry.invalid");
}

//Save payment information
getAdyenCheckoutFacade().handlePaymentForm(adyenPaymentForm, bindingResult);
if (bindingResult.hasGlobalErrors() || bindingResult.hasErrors()) {
LOGGER.debug(bindingResult.getAllErrors().stream().map(DefaultMessageSourceResolvable::getCode).reduce((x, y) -> (x = x + y)));
if (adyenPaymentForm.getBillingAddress() != null) {
adyenPaymentForm.resetFormExceptBillingAddress();
}
throw new AdyenComponentException("checkout.error.paymentethod.formentry.invalid");
}

try {
validateOrderForm();

//Make payment request
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
String payPalDetailsJson = adyenPaymentForm.getComponentData();
PayPalDetails payPalDetails = gson.fromJson(payPalDetailsJson, PayPalDetails.class);

final CartData cartData = getCheckoutFlowFacade().getCheckoutCart();

PaymentsResponse paymentsResponse = getAdyenCheckoutFacade().componentPayment(request, cartData, payPalDetails);
return gson.toJson(paymentsResponse);
} catch (InvalidCartException e) {
LOGGER.error("InvalidCartException", e);
throw new AdyenComponentException(e.getMessage());
}
catch ( ApiException e) {
LOGGER.error("ApiException", e);
throw new AdyenComponentException("checkout.error.authorization.payment.refused");
} catch (AdyenNonAuthorizedPaymentException e) {
LOGGER.debug("AdyenNonAuthorizedPaymentException occurred. Payment is refused.");
throw new AdyenComponentException("checkout.error.authorization.payment.refused");
} catch (Exception e) {
LOGGER.error("Exception", e);
throw new AdyenComponentException("checkout.error.authorization.payment.error");
}
}

@RequestMapping(value = "/submit-details", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String submitDetails(final HttpServletRequest request) throws AdyenComponentException {
try {
String requestJsonString = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
JsonObject requestJson = new JsonParser().parse(requestJsonString).getAsJsonObject();

Gson gson = new GsonBuilder().disableHtmlEscaping().create();
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
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);
return gson.toJson(paymentsResponse);
} catch (ApiException e) {
LOGGER.error("ApiException", e);
throw new AdyenComponentException("checkout.error.authorization.payment.refused");
} catch (Exception e) {
LOGGER.error("Exception", e);
throw new AdyenComponentException("checkout.error.authorization.payment.error");
}
}

@ExceptionHandler(value = AdyenComponentException.class)
public AdyenComponentException adyenComponentExceptionHandler(AdyenComponentException e, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.getWriter().write(e.getMessage());
response.getWriter().flush();
response.getWriter().close();
return e;
}

/**
* Validates the order form before to filter out invalid order states
*
* @return True if the order form is invalid and false if everything is valid.
*/
protected void validateOrderForm() throws InvalidCartException {
if (getCheckoutFlowFacade().hasNoDeliveryAddress()) {
throw new InvalidCartException("checkout.deliveryAddress.notSelected");
}

if (getCheckoutFlowFacade().hasNoDeliveryMode()) {
throw new InvalidCartException("checkout.deliveryMethod.notSelected");
}

if (getCheckoutFlowFacade().hasNoPaymentInfo()) {
throw new InvalidCartException("checkout.paymentMethod.notSelected");
}

final CartData cartData = getCheckoutFacade().getCheckoutCart();

if (! getCheckoutFacade().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()));
throw new InvalidCartException("checkout.error.tax.missing");
}

if (! cartData.isCalculated()) {
LOGGER.error(String.format("Cart %s has a calculated flag of FALSE, placement of order can't continue", cartData.getCode()));
throw new InvalidCartException("checkout.error.cart.notcalculated");
}
}

public AdyenCheckoutFacade getAdyenCheckoutFacade() {
return adyenCheckoutFacade;
}

public void setAdyenCheckoutFacade(AdyenCheckoutFacade adyenCheckoutFacade) {
this.adyenCheckoutFacade = adyenCheckoutFacade;
}

public CheckoutFlowFacade getCheckoutFlowFacade() {
return checkoutFlowFacade;
}

public void setCheckoutFlowFacade(CheckoutFlowFacade checkoutFlowFacade) {
this.checkoutFlowFacade = checkoutFlowFacade;
}

public AcceleratorCheckoutFacade getCheckoutFacade() {
return checkoutFacade;
}

public void setCheckoutFacade(AcceleratorCheckoutFacade checkoutFacade) {
this.checkoutFacade = checkoutFacade;
}

private boolean isValidateSessionCart() {
CartData cart = getCheckoutFacade().getCheckoutCart();
final AddressData deliveryAddress = cart.getDeliveryAddress();
if (deliveryAddress == null || deliveryAddress.getCountry() == null || deliveryAddress.getCountry().getIsocode() == null) {
return false;
}
return true;

}
}
Loading

0 comments on commit 931c229

Please sign in to comment.