From 63f77f3fce84f40587e967f71072fd0d621d2ad5 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Tue, 10 Nov 2020 09:35:11 +0100
Subject: [PATCH] OO-5059: implement an alternative Paypal payment with Smart
 Buttons

---
 .../method/AccessMethodHandler.java           |   6 +
 .../provider/auto/AutoAccessHandler.java      |   5 +
 .../provider/free/FreeAccessHandler.java      |   5 +
 .../provider/paypal/PaypalAccessHandler.java  |   5 +
 .../paypal/manager/PaypalManagerImpl.java     |  10 +-
 .../PaypalCheckoutAccessHandler.java          |  19 ++-
 .../paypalcheckout/PaypalCheckoutManager.java |  29 +++++
 .../paypalcheckout/PaypalCheckoutModule.java  |  13 ++
 .../manager/CheckoutV2Provider.java           |  91 ++++++++++---
 .../manager/PaypalCSPDirectiveProvider.java   |  84 ++++++++++++
 .../manager/PaypalCheckoutManagerImpl.java    |  73 ++++++++++-
 .../manager/PaypalCheckoutTransactionDAO.java |  12 ++
 .../model/CreateSmartOrder.java               |  46 +++++++
 .../model/PaypalCheckoutTransactionImpl.java  |  34 +++++
 ...heckoutAccountConfigurationController.java |  16 ++-
 .../ui/PaypalSmartButtonAccessController.java | 105 +++++++++++++++
 .../ui/PaypalSmartButtonMapper.java           | 123 ++++++++++++++++++
 .../ui/_content/paypal_smart_buttons.html     |  99 ++++++++++++++
 .../ui/_i18n/LocalStrings_de.properties       |  10 +-
 .../ui/_i18n/LocalStrings_en.properties       |   8 ++
 .../ui/_i18n/LocalStrings_fr.properties       |   3 +
 .../ui/_i18n/LocalStrings_it.properties       |   2 +
 .../provider/token/TokenAccessHandler.java    |   5 +
 .../ui/AccessConfigurationController.java     |  36 ++---
 .../ui/OrderDetailController.java             |   3 +-
 .../accesscontrol/ui/PriceFormat.java         |  11 +-
 .../ui/_content/configuration_list.html       |   4 +-
 .../ui/_i18n/LocalStrings_de.properties       |   1 +
 .../ui/_i18n/LocalStrings_en.properties       |   1 +
 .../manager/ShibbolethAutoAccessHandler.java  |   2 +
 .../accesscontrol/ACFrontendManagerTest.java  |  10 +-
 .../accesscontrol/ACOrderManagerTest.java     |  13 +-
 .../PaypalCheckoutTransactionDAOTest.java     |  34 +++++
 33 files changed, 849 insertions(+), 69 deletions(-)
 create mode 100644 src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCSPDirectiveProvider.java
 create mode 100644 src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/CreateSmartOrder.java
 create mode 100644 src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonAccessController.java
 create mode 100644 src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonMapper.java
 create mode 100644 src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_content/paypal_smart_buttons.html

diff --git a/src/main/java/org/olat/resource/accesscontrol/method/AccessMethodHandler.java b/src/main/java/org/olat/resource/accesscontrol/method/AccessMethodHandler.java
index 24bef22c53e..8a75d1f6ee0 100644
--- a/src/main/java/org/olat/resource/accesscontrol/method/AccessMethodHandler.java
+++ b/src/main/java/org/olat/resource/accesscontrol/method/AccessMethodHandler.java
@@ -50,6 +50,12 @@ public interface AccessMethodHandler {
 	
 	public boolean isPaymentMethod();
 	
+	/**
+	 * 
+	 * @return true if it is technically possible to have 2 methods overlap
+	 */
+	public boolean isOverlapAllowed(AccessMethodHandler handler);
+	
 	public String getType();
 	
 	public String getMethodName(Locale locale);
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/auto/AutoAccessHandler.java b/src/main/java/org/olat/resource/accesscontrol/provider/auto/AutoAccessHandler.java
index 8b03cd15204..202bfb90f21 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/auto/AutoAccessHandler.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/auto/AutoAccessHandler.java
@@ -58,6 +58,11 @@ public abstract class AutoAccessHandler implements AccessMethodHandler {
 		return false;
 	}
 
+	@Override
+	public boolean isOverlapAllowed(AccessMethodHandler handler) {
+		return true;
+	}
+
 	@Override
 	public AccessMethodSecurityCallback getSecurityCallback(Identity identity, Roles roles) {
 		return SYSTEM_AC_SECURITY_CALLBACK;
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/free/FreeAccessHandler.java b/src/main/java/org/olat/resource/accesscontrol/provider/free/FreeAccessHandler.java
index 745dbf9e426..98c9e20a507 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/free/FreeAccessHandler.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/free/FreeAccessHandler.java
@@ -63,6 +63,11 @@ public class FreeAccessHandler implements AccessMethodHandler {
 		return false;
 	}
 
+	@Override
+	public boolean isOverlapAllowed(AccessMethodHandler handler) {
+		return true;
+	}
+
 	@Override
 	public String getType() {
 		return METHOD_TYPE;
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypal/PaypalAccessHandler.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypal/PaypalAccessHandler.java
index 56374246ea3..d0d63310f7b 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypal/PaypalAccessHandler.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypal/PaypalAccessHandler.java
@@ -65,6 +65,11 @@ public class PaypalAccessHandler implements AccessMethodHandler {
 	public boolean isPaymentMethod() {
 		return true;
 	}
+	
+	@Override
+	public boolean isOverlapAllowed(AccessMethodHandler handler) {
+		return true;
+	}
 
 	@Override
 	public String getType() {
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypal/manager/PaypalManagerImpl.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypal/manager/PaypalManagerImpl.java
index 98914efe730..5ef5d6a8d4e 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypal/manager/PaypalManagerImpl.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypal/manager/PaypalManagerImpl.java
@@ -180,7 +180,7 @@ public class PaypalManagerImpl  implements PaypalManager {
 	private PaypalAccessMethod getMethodSecure(Long key) {
 		PaypalAccessMethod smethod = null;
 		List<PaypalAccessMethod> methods = getPaypalMethods();
-		if(methods.size() > 0) {
+		if(!methods.isEmpty()) {
 			smethod = methods.get(0);
 		} else {
 			smethod = new PaypalAccessMethod();
@@ -199,11 +199,9 @@ public class PaypalManagerImpl  implements PaypalManager {
 	private List<PaypalAccessMethod> getPaypalMethods() {
 		StringBuilder sb = new StringBuilder();
 		sb.append("select method from ").append(PaypalAccessMethod.class.getName()).append(" method");
-		
-		List<PaypalAccessMethod> methods = dbInstance.getCurrentEntityManager()
+		return dbInstance.getCurrentEntityManager()
 				.createQuery(sb.toString(), PaypalAccessMethod.class)
 				.getResultList();
-		return methods;
 	}
 	
 	@Override
@@ -284,8 +282,7 @@ public class PaypalManagerImpl  implements PaypalManager {
 			query.setParameter("transactionId", transactionId);
 		}
 
-		List<PaypalTransaction> transactions = query.getResultList();
-		return transactions;
+		return query.getResultList();
 	}
 	
 	private boolean appendAnd(StringBuilder sb, boolean where) {
@@ -643,6 +640,7 @@ public class PaypalManagerImpl  implements PaypalManager {
 		}
 	}
 
+	@Override
 	public PayResponse request(Identity delivery, OfferAccess offerAccess, String mapperUri, String sessionId) {
 		StringBuilder url = new StringBuilder();
 		url.append(Settings.createServerURI()).append(mapperUri);
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutAccessHandler.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutAccessHandler.java
index 4f78cc27aa9..dea26af850d 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutAccessHandler.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutAccessHandler.java
@@ -41,6 +41,7 @@ import org.olat.resource.accesscontrol.model.PSPTransaction;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.ui.PaypalCheckoutAccessConfigurationController;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.ui.PaypalCheckoutAccessController;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.ui.PaypalCheckoutTransactionDetailsController;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.ui.PaypalSmartButtonAccessController;
 import org.olat.resource.accesscontrol.ui.AbstractConfigurationMethodController;
 import org.olat.resource.accesscontrol.ui.FormController;
 
@@ -60,6 +61,15 @@ public class PaypalCheckoutAccessHandler implements AccessMethodHandler {
 	public boolean isPaymentMethod() {
 		return true;
 	}
+	
+	@Override
+	public boolean isOverlapAllowed(AccessMethodHandler handler) {
+		if(handler instanceof PaypalCheckoutAccessHandler) {
+			PaypalCheckoutModule paypalModule = CoreSpringFactory.getImpl(PaypalCheckoutModule.class);
+			return !paypalModule.isSmartButtons();
+		}
+		return true;
+	}
 
 	@Override
 	public String getType() {
@@ -79,7 +89,14 @@ public class PaypalCheckoutAccessHandler implements AccessMethodHandler {
 
 	@Override
 	public FormController createAccessController(UserRequest ureq, WindowControl wControl, OfferAccess link, Form form) {
-		if(form == null) {
+		PaypalCheckoutModule paypalModule = CoreSpringFactory.getImpl(PaypalCheckoutModule.class);
+		if(paypalModule.isSmartButtons()) {
+			if(form == null) {
+				return new PaypalSmartButtonAccessController(ureq, wControl, link);
+			} else {
+				return new PaypalSmartButtonAccessController(ureq, wControl, link, form);
+			}
+		} else if(form == null) {
 			return new PaypalCheckoutAccessController(ureq, wControl, link);
 		} else {
 			return new PaypalCheckoutAccessController(ureq, wControl, link, form);
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutManager.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutManager.java
index 1ad7059d7b5..b4b551e344b 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutManager.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutManager.java
@@ -27,6 +27,7 @@ import org.olat.resource.accesscontrol.Order;
 import org.olat.resource.accesscontrol.OrderPart;
 import org.olat.resource.accesscontrol.model.PSPTransaction;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.model.CheckoutRequest;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.model.CreateSmartOrder;
 
 /**
  * 
@@ -40,6 +41,34 @@ public interface PaypalCheckoutManager {
 	
 	public void updateTransaction(String uuid);
 	
+	/**
+	 * Create an order in OpenOlat and in Paypal, reserve the access
+	 * if needed.
+	 * 
+	 * @param delivery The identity which buy the access
+	 * @param offerAccess The offer
+	 * @return The order id or null if the reservation fails
+	 */
+	public CreateSmartOrder createOrder(Identity delivery, OfferAccess offerAccess);
+	
+	/**
+	 * 
+	 * @param paypalOrderId The order id of the Paypal order.
+	 */
+	public void approveTransaction(String paypalOrderId);
+	
+	/**
+	 * 
+	 * @param paypalOrderId The order id of the Paypal order.
+	 */
+	public void cancelTransaction(String paypalOrderId);
+	
+	/**
+	 * 
+	 * @param paypalOrderId The order id of the Paypal order.
+	 */
+	public void errorTransaction(String paypalOrderId);
+	
 
 	public PaypalCheckoutTransaction loadTransaction(Order order, OrderPart part);
 	
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutModule.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutModule.java
index d472aaa430d..6b417248dfa 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutModule.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/PaypalCheckoutModule.java
@@ -40,6 +40,7 @@ public class PaypalCheckoutModule extends AbstractSpringModule {
 	private static final String PAYPAL_CLIENT_ID = "paypal.checkout.v2.client.id";
 	private static final String PAYPAL_CLIENT_SECRET = "paypal.checkout.v2.client.secret";
 	private static final String PAYPAL_CURRENCY = "paypal.checkout.v2.currency";
+	private static final String PAYPAL_SMART_BUTTONS = "paypal.checkout.v2.smart.buttons";
 	
 	private static final String[] currencies = new String[] {
 			"AUD",
@@ -74,6 +75,8 @@ public class PaypalCheckoutModule extends AbstractSpringModule {
 	private boolean sandbox;
 	@Value("${paypal.checkout.v2.currency:CHF}")
 	private String paypalCurrency;
+	@Value("${paypal.checkout.v2.smart.buttons:true}")
+	private boolean smartButtons;
 	
 	@Autowired
 	public PaypalCheckoutModule(CoordinatorManager coordinatorManager) {
@@ -90,6 +93,7 @@ public class PaypalCheckoutModule extends AbstractSpringModule {
 		clientId = getStringPropertyValue(PAYPAL_CLIENT_ID, clientId);
 		clientSecret = getStringPropertyValue(PAYPAL_CLIENT_SECRET, clientSecret);
 		paypalCurrency = getStringPropertyValue(PAYPAL_CURRENCY, paypalCurrency);
+		smartButtons = "true".equals(getStringPropertyValue(PAYPAL_SMART_BUTTONS, Boolean.toString(smartButtons)));
 	}
 
 	public String getClientId() {
@@ -122,7 +126,16 @@ public class PaypalCheckoutModule extends AbstractSpringModule {
 		this.paypalCurrency = currency;
 		setStringProperty(PAYPAL_CURRENCY, currency, true);
 	}
+	
+	public boolean isSmartButtons() {
+		return smartButtons;
+	}
 
+	public void setSmartButtons(boolean smartButtons) {
+		this.smartButtons = smartButtons;
+		setStringProperty(PAYPAL_SMART_BUTTONS, Boolean.toString(smartButtons), true);
+	}
+	
 	public boolean isSandbox() {
 		return sandbox;
 	}
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/CheckoutV2Provider.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/CheckoutV2Provider.java
index 10f162e574d..56ae1cb5595 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/CheckoutV2Provider.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/CheckoutV2Provider.java
@@ -77,33 +77,54 @@ public class CheckoutV2Provider {
 	@Autowired
 	private PaypalCheckoutTransactionDAO transactionDao;
 	
+	public PaypalCheckoutTransaction createOrder(org.olat.resource.accesscontrol.Order order, PaypalCheckoutTransaction trx) {
+		ApplicationContext applicationContext = new ApplicationContext();
+		OrderRequest orderRequest = buildOrderRequest(order, "AUTHORIZE", applicationContext);
+		OrdersCreateRequest request = buildOrdersCreateRequest(orderRequest);
+
+		try {
+			HttpResponse<Order> orderResponse = client().execute(request);
+			if (orderResponse.statusCode() == 201) {
+				Order paypalOrder = orderResponse.result();
+				trx.setPaypalOrderId(paypalOrder.id());
+				trx.setPaypalOrderStatus(paypalOrder.status());
+				trx.setStatus(PaypalCheckoutStatus.INPROCESS);
+				log.info(Tracing.M_AUDIT, "Create Paypal order: id:{} status:{}", paypalOrder.id(), paypalOrder.status());
+				for (LinkDescription link : orderResponse.result().links()) {
+					log.debug("Create Paypal link: rel:{} href:{}", link.rel(), link.href());
+					log.debug("Create Paypal link: rel:{} media:{} for schema:{}", link.rel(), link.mediaType(),
+							link.schema());
+				}
+			} else {
+				log.error(Tracing.M_AUDIT, "Create Paypal order status:{}", orderResponse.statusCode());
+			}
+		} catch (HttpException e) {
+            JSONObject message = new JSONObject(e.getMessage());
+            log.error(Tracing.M_AUDIT, prettyPrint(message, ""));
+			log.error(Tracing.M_AUDIT, "Create Paypal order", e);
+			trx.setStatus(PaypalCheckoutStatus.ERROR);
+			trx.setPaypalOrderStatus(PaypalCheckoutStatus.ERROR.name());
+		} catch (IOException e) {
+			log.error(Tracing.M_AUDIT, "Create Paypal order", e);
+			trx.setStatus(PaypalCheckoutStatus.ERROR);
+			trx.setPaypalOrderStatus(PaypalCheckoutStatus.ERROR.name());
+		}
+		trx = transactionDao.update(trx);
+		dbInstance.commit();
+        return trx;
+	}
+	
 	public CheckoutRequest paymentUrl(String url, org.olat.resource.accesscontrol.Order order, PaypalCheckoutTransaction trx, String sessionId) {
 		
 		String returnURL = url + "/" + trx.getSecureSuccessUUID() + ".html;jsessionid=" + sessionId + "?status=success";
 		String cancelURL = url + "/" + trx.getSecureCancelUUID() + ".html;jsessionid=" + sessionId + "?status=cancel";
+		ApplicationContext applicationContext = new ApplicationContext()
+				.cancelUrl(cancelURL)
+				.returnUrl(returnURL);
 
-		OrderRequest orderRequest = new OrderRequest();
-		orderRequest.checkoutPaymentIntent("AUTHORIZE");
-		ApplicationContext applicationContext = new ApplicationContext().cancelUrl(cancelURL).returnUrl(returnURL);
-		orderRequest.applicationContext(applicationContext);
-
-		String price = PriceFormat.format(order.getTotal());
-		String currencyCode = order.getCurrencyCode();
-		
-		AmountWithBreakdown amount = new AmountWithBreakdown()
-				.currencyCode(currencyCode).value(price)
-				.amountBreakdown(new AmountBreakdown().itemTotal(new com.paypal.orders.Money().currencyCode(currencyCode).value(price)));
-
-		List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<>();
-		PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest()
-				.amountWithBreakdown(amount);
-		purchaseUnitRequests.add(purchaseUnitRequest);
-		orderRequest.purchaseUnits(purchaseUnitRequests);
+		OrderRequest orderRequest = buildOrderRequest(order, "AUTHORIZE", applicationContext);
+		OrdersCreateRequest request = buildOrdersCreateRequest(orderRequest);
 
-		OrdersCreateRequest request = new OrdersCreateRequest();
-		request.header("prefer", "return=representation");
-		request.requestBody(orderRequest);
-		
 		CheckoutRequest checkoutRequest = new CheckoutRequest();
 		try {
 			HttpResponse<Order> orderResponse = client().execute(request);
@@ -144,6 +165,34 @@ public class CheckoutV2Provider {
         return checkoutRequest;
 	}
 	
+	private OrdersCreateRequest buildOrdersCreateRequest(OrderRequest orderRequest) {
+		OrdersCreateRequest request = new OrdersCreateRequest();
+		request.header("prefer", "return=representation");
+		request.requestBody(orderRequest);
+		return request;
+	}
+	
+	private OrderRequest buildOrderRequest(org.olat.resource.accesscontrol.Order order, String intent, ApplicationContext applicationContext) {
+		OrderRequest orderRequest = new OrderRequest();
+		orderRequest.checkoutPaymentIntent(intent);
+		orderRequest.applicationContext(applicationContext);
+
+		String price = PriceFormat.format(order.getTotal());
+		String currencyCode = order.getCurrencyCode();
+		
+		AmountWithBreakdown amount = new AmountWithBreakdown()
+				.currencyCode(currencyCode).value(price)
+				.amountBreakdown(new AmountBreakdown().itemTotal(new com.paypal.orders.Money().currencyCode(currencyCode).value(price)));
+
+		List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<>();
+		PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest()
+				.amountWithBreakdown(amount);
+		purchaseUnitRequests.add(purchaseUnitRequest);
+		orderRequest.purchaseUnits(purchaseUnitRequests);
+
+		return orderRequest;
+	}
+	
 	public PaypalCheckoutTransaction authorizeUrl(PaypalCheckoutTransaction trx)  {
 		OrdersAuthorizeRequest request = new OrdersAuthorizeRequest(trx.getPaypalOrderId());
 		request.requestBody(new OrderRequest());
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCSPDirectiveProvider.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCSPDirectiveProvider.java
new file mode 100644
index 00000000000..f235048d065
--- /dev/null
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCSPDirectiveProvider.java
@@ -0,0 +1,84 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.resource.accesscontrol.provider.paypalcheckout.manager;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.olat.core.commons.services.csp.CSPDirectiveProvider;
+import org.olat.resource.accesscontrol.AccessControlModule;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.PaypalCheckoutModule;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 9 nov. 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class PaypalCSPDirectiveProvider implements CSPDirectiveProvider {
+	
+	private static final List<String> sandboxedPaypal = List.of("https://www.paypal.com", "https://www.sandbox.paypal.com");
+	private static final List<String> productionPaypal = List.of("https://www.paypal.com");
+
+	@Autowired
+	private AccessControlModule acModule;
+	@Autowired
+	private PaypalCheckoutModule paypalCheckoutModule;
+
+	@Override
+	public Collection<String> getScriptSrcUrls() {
+		return urls();
+	}
+
+	@Override
+	public Collection<String> getImgSrcUrls() {
+		return null;
+	}
+
+	@Override
+	public Collection<String> getFontSrcUrls() {
+		return null;
+	}
+
+	@Override
+	public Collection<String> getConnectSrcUrls() {
+		return urls();
+	}
+
+	@Override
+	public Collection<String> getFrameSrcUrls() {
+		return urls();
+	}
+
+	@Override
+	public Collection<String> getMediaSrcUrls() {
+		return null;
+	}
+	
+	private Collection<String> urls() {
+		if(acModule.isPaypalCheckoutEnabled()) {
+			return paypalCheckoutModule.isSandbox() ? sandboxedPaypal : productionPaypal;
+		}
+		return null;
+	}
+}
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutManagerImpl.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutManagerImpl.java
index 741de5781d9..0eb7b5e7f4e 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutManagerImpl.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutManagerImpl.java
@@ -48,6 +48,7 @@ import org.olat.resource.accesscontrol.provider.paypalcheckout.PaypalCheckoutMan
 import org.olat.resource.accesscontrol.provider.paypalcheckout.PaypalCheckoutStatus;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.PaypalCheckoutTransaction;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.model.CheckoutRequest;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.model.CreateSmartOrder;
 import org.olat.resource.accesscontrol.provider.paypalcheckout.model.PaypalCheckoutAccessMethod;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -92,6 +93,35 @@ public class PaypalCheckoutManagerImpl implements PaypalCheckoutManager {
 		return checkoutProvider.paymentUrl(url.toString(), order, trx, sessionId);
 	}
 
+	@Override
+	public CreateSmartOrder createOrder(Identity delivery, OfferAccess offerAccess) {
+		Offer offer = offerAccess.getOffer();
+		Price amount = offer.getPrice();
+
+		if(acService.reserveAccessToResource(delivery, offerAccess)) {
+			Order order = orderManager.saveOneClick(delivery, offerAccess, OrderStatus.PREPAYMENT);
+			PaypalCheckoutTransaction trx = transactionDao.createTransaction(amount, order, order.getParts().get(0), offerAccess.getMethod());
+			trx = checkoutProvider.createOrder(order, trx);
+			return new CreateSmartOrder(trx.getPaypalOrderId(), true);
+		}
+		log.info(Tracing.M_AUDIT, "Can reserve: {}", delivery);
+		return new CreateSmartOrder(null, false);
+	}
+	
+	public PaypalCheckoutTransaction approveOrder(PaypalCheckoutTransaction trx) {
+		try {
+			trx = checkoutProvider.captureOrder(trx);
+			if(PaypalCheckoutStatus.COMPLETED.name().equals(trx.getPaypalOrderStatus())) {
+				completeTransactionSucessfully(trx);
+			} else {
+				completeDeniedTransaction(trx);
+			}
+		} catch (IOException e) {
+			log.error("", e);
+		}
+		return trx;
+	}
+
 	@Override
 	public void updateTransaction(String uuid) {
 		PaypalCheckoutTransaction trx = loadTransactionByUUID(uuid);
@@ -106,6 +136,39 @@ public class PaypalCheckoutManagerImpl implements PaypalCheckoutManager {
 		}
 	}
 	
+	@Override
+	public void approveTransaction(String paypalOrderId) {
+		PaypalCheckoutTransaction trx = transactionDao.loadTransactionByPaypalOrderId(paypalOrderId);
+		if(trx != null) {
+			log.info(Tracing.M_AUDIT, "Paypal Checkout transaction approved: {}", trx);
+			completeTransaction(trx);
+		} else {
+			log.error("Paypal Checkout transaction not found for approval: {} (Paypal order id)", paypalOrderId);
+		}
+	}
+
+	@Override
+	public void cancelTransaction(String paypalOrderId) {
+		PaypalCheckoutTransaction trx = transactionDao.loadTransactionByPaypalOrderId(paypalOrderId);
+		if(trx != null) {
+			log.info(Tracing.M_AUDIT, "Paypal Checkout transaction cancelled: {}", trx);
+			cancelTransaction(trx);
+		} else {
+			log.error("Paypal Checkout transaction not found for cancellation: {} (Paypal order id)", paypalOrderId);
+		}
+	}
+
+	@Override
+	public void errorTransaction(String paypalOrderId) {
+		PaypalCheckoutTransaction trx = transactionDao.loadTransactionByPaypalOrderId(paypalOrderId);
+		if(trx != null) {
+			log.info(Tracing.M_AUDIT, "Paypal Checkout transaction error: {}", trx);
+			completeDeniedTransaction(trx);
+		} else {
+			log.error("Paypal Checkout transaction not found for error: {} (Paypal order id)", paypalOrderId);
+		}
+	}
+
 	private PaypalCheckoutTransaction completeTransaction(PaypalCheckoutTransaction trx) {
 		try {
 			trx = checkoutProvider.authorizeUrl(trx);
@@ -150,7 +213,7 @@ public class PaypalCheckoutManagerImpl implements PaypalCheckoutManager {
 					ResourceReservation reservation = acService.getReservation(identity, resource);
 					if(reservation != null) {
 						acService.removeReservation(identity, identity, reservation);
-						log.info(Tracing.M_AUDIT, "Remove reservation after cancellation for: " + reservation + " to " + identity);
+						log.info(Tracing.M_AUDIT, "Remove reservation after cancellation for: {} to {}", reservation, identity);
 					}
 				}
 			}
@@ -171,12 +234,12 @@ public class PaypalCheckoutManagerImpl implements PaypalCheckoutManager {
 					transactionManager.update(transaction, AccessTransactionStatus.ERROR);
 					for(OrderLine line:part.getOrderLines()) {
 						acService.denyAccesToResource(identity, line.getOffer());
-						log.info(Tracing.M_AUDIT, "Paypal payed access revoked for: " + buildLogMessage(line, method) + " to " + identity);
+						log.info(Tracing.M_AUDIT, "Paypal payed access revoked for: {} to {}",  buildLogMessage(line, method), identity);
 
 						ResourceReservation reservation = reservationDao.loadReservation(identity, line.getOffer().getResource());
 						if(reservation != null) {
 							acService.removeReservation(identity, identity, reservation);
-							log.info(Tracing.M_AUDIT, "Remove reservation after cancellation for: " + reservation + " to " + identity);
+							log.info(Tracing.M_AUDIT, "Remove reservation after cancellation for: {} to {}", reservation, identity);
 						}
 					}
 				}
@@ -201,10 +264,10 @@ public class PaypalCheckoutManagerImpl implements PaypalCheckoutManager {
 					transactionManager.save(transaction);
 					for(OrderLine line:part.getOrderLines()) {
 						if(acService.allowAccesToResource(identity, line.getOffer())) {
-							log.info(Tracing.M_AUDIT, "Paypal Checkout payed access granted for: " + buildLogMessage(line, method) + " to " + identity);
+							log.info(Tracing.M_AUDIT, "Paypal Checkout payed access granted for: {} to {}", buildLogMessage(line, method), identity);
 							transaction = transactionManager.update(transaction, AccessTransactionStatus.SUCCESS);
 						} else {
-							log.error("Paypal Checkout payed access refused for: " + buildLogMessage(line, method) + " to " + identity);
+							log.error("Paypal Checkout payed access refused for: {} to {}", buildLogMessage(line, method), identity);
 							transaction = transactionManager.update(transaction, AccessTransactionStatus.ERROR);
 						}
 					}
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAO.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAO.java
index fcf4780add5..2eb970050eb 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAO.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAO.java
@@ -95,6 +95,18 @@ public class PaypalCheckoutTransactionDAO {
 		return transactions.isEmpty() ? null : transactions.get(0);
 	}
 	
+	public PaypalCheckoutTransaction loadTransactionByPaypalOrderId(String paypalOrderId) {
+		StringBuilder sb = new StringBuilder(128);
+		sb.append("select trx from paypalcheckouttransaction as trx")
+		  .append(" where trx.paypalOrderId=:paypalOrderId");
+		
+		List<PaypalCheckoutTransaction> transactions = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), PaypalCheckoutTransaction.class)
+				.setParameter("paypalOrderId", paypalOrderId)
+				.getResultList();
+		return transactions.isEmpty() ? null : transactions.get(0);
+	}
+	
 	public List<PSPTransaction> loadTransactionBy(List<Order> orders) {
 		if(orders == null || orders.isEmpty()) return new ArrayList<>();
 		
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/CreateSmartOrder.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/CreateSmartOrder.java
new file mode 100644
index 00000000000..20bd9498f23
--- /dev/null
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/CreateSmartOrder.java
@@ -0,0 +1,46 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.resource.accesscontrol.provider.paypalcheckout.model;
+
+/**
+ * 
+ * Initial date: 9 nov. 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CreateSmartOrder {
+	
+	private final String paypalOrderId;
+	private final boolean reservationOk;
+	
+	public CreateSmartOrder(String paypalOrderId, boolean reservationOk) {
+		this.paypalOrderId = paypalOrderId;
+		this.reservationOk = reservationOk;
+	}
+	
+	public boolean isReservationOk() {
+		return reservationOk;
+	}
+	
+	public String getPaypalOrderId() {
+		return paypalOrderId;
+	}
+
+}
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/PaypalCheckoutTransactionImpl.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/PaypalCheckoutTransactionImpl.java
index bf0155ea9ed..b8a1f5adb74 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/PaypalCheckoutTransactionImpl.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/model/PaypalCheckoutTransactionImpl.java
@@ -34,6 +34,7 @@ import javax.persistence.TemporalType;
 
 import org.hibernate.annotations.Target;
 import org.olat.core.id.Persistable;
+import org.olat.core.util.StringHelper;
 import org.olat.resource.accesscontrol.Price;
 import org.olat.resource.accesscontrol.model.PSPTransactionStatus;
 import org.olat.resource.accesscontrol.model.PriceImpl;
@@ -306,4 +307,37 @@ public class PaypalCheckoutTransactionImpl implements Persistable, PaypalCheckou
 	public boolean equalsByPersistableKey(Persistable persistable) {
 		return equals(persistable);
 	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("PaypalCheckoutTransaction[key=").append(getKey()).append("]")
+			.append("[price=").append(securePrice).append("]")
+			//order
+			.append("[orderId=").append(orderId).append("]")
+			.append("[orderPartId=").append(orderPartId).append("]")
+			.append("[methodId=").append(methodId).append("]");
+		if(StringHelper.containsNonWhitespace(paypalOrderId)) {
+			sb.append("[paypalOrderId=").append(paypalOrderId).append("]");
+		}
+		if(StringHelper.containsNonWhitespace(paypalOrderStatus)) {
+			sb.append("[paypalOrderStatus=").append(paypalOrderStatus).append("]");
+		}
+		if(StringHelper.containsNonWhitespace(paypalOrderStatusReason)) {
+			sb.append("[paypalOrderStatusReason=").append(paypalOrderStatusReason).append("]");
+		}
+		if(StringHelper.containsNonWhitespace(paypalAuthorizationId)) {
+			sb.append("[paypalAuthorizationId=").append(paypalAuthorizationId).append("]");
+		}
+		if(StringHelper.containsNonWhitespace(paypalCaptureId)) {
+			sb.append("[paypalCaptureId=").append(paypalCaptureId).append("]");
+		}
+		if(StringHelper.containsNonWhitespace(secureSuccessUUID)) {
+			sb.append("[successUUID=").append(secureSuccessUUID ).append("]");
+		}
+		if(StringHelper.containsNonWhitespace(secureCancelUUID)) {
+			sb.append("[cancelUUID=").append(secureCancelUUID).append("]");
+		}
+		return sb.toString();
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalCheckoutAccountConfigurationController.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalCheckoutAccountConfigurationController.java
index 7f3ca48fa09..2cf4ce25806 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalCheckoutAccountConfigurationController.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalCheckoutAccountConfigurationController.java
@@ -48,10 +48,12 @@ import org.springframework.beans.factory.annotation.Autowired;
 public class PaypalCheckoutAccountConfigurationController extends FormBasicController {
 	
 	private static final String[] onKeys = new String[] { "on" };
+	private static final String[] smartButtonsKeys = new String[] { "smartbuttons", "standard" };
 	
 	private TextElement clientIdEl;
 	private TextElement clientSecretEl;
 	private SingleSelection currencyEl;
+	private SingleSelection smartButtonsEl;
 	private MultipleSelectionElement enableEl;
 	
 	private final List<String> paypalCurrencies;
@@ -83,6 +85,16 @@ public class PaypalCheckoutAccountConfigurationController extends FormBasicContr
 		enableEl.select(onKeys[0], acModule.isPaypalCheckoutEnabled());
 		enableEl.addActionListener(FormEvent.ONCHANGE);
 		
+		KeyValues smartButtons = new KeyValues();
+		smartButtons.add(KeyValues.entry(smartButtonsKeys[0], translate("checkout.smart.buttons.enabled")));
+		smartButtons.add(KeyValues.entry(smartButtonsKeys[1], translate("checkout.standard")));
+		smartButtonsEl = uifactory.addRadiosVertical("checkout.smart.buttons", "checkout.smart.buttons", formLayout, smartButtons.keys(), smartButtons.values());
+		if(paypalModule.isSmartButtons()) {
+			smartButtonsEl.select(smartButtonsKeys[0], true);
+		} else {
+			smartButtonsEl.select(smartButtonsKeys[1], true);
+		}
+		
 		KeyValues currencies = new KeyValues();
 		paypalCurrencies.forEach(currency -> currencies.add(KeyValues.entry(currency, currency)));
 		currencyEl = uifactory.addDropdownSingleselect("currency", "currency", formLayout, currencies.keys(), currencies.values(), null);
@@ -99,7 +111,6 @@ public class PaypalCheckoutAccountConfigurationController extends FormBasicContr
 
 		final FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator());
 		formLayout.add(buttonGroupLayout);
-		
 		uifactory.addFormSubmitButton("save", buttonGroupLayout);
 	}
 	
@@ -108,6 +119,7 @@ public class PaypalCheckoutAccountConfigurationController extends FormBasicContr
 		currencyEl.setVisible(enabled);
 		clientIdEl.setVisible(enabled);
 		clientSecretEl.setVisible(enabled);
+		smartButtonsEl.setVisible(enabled);
 	}
 
 	@Override
@@ -133,7 +145,6 @@ public class PaypalCheckoutAccountConfigurationController extends FormBasicContr
 				&& !StringHelper.containsNonWhitespace(element.getValue())) {
 			element.setErrorKey("form.legende.mandatory", null);
 			allOk &= false;
-			
 		}
 		
 		return allOk;
@@ -157,6 +168,7 @@ public class PaypalCheckoutAccountConfigurationController extends FormBasicContr
 			if(currencyEl.isOneSelected() && paypalCurrencies.contains(currencyEl.getSelectedKey())) {
 				paypalModule.setPaypalCurrency(currencyEl.getSelectedKey());
 			}
+			paypalModule.setSmartButtons(smartButtonsEl.isOneSelected() && smartButtonsEl.isSelected(0));
 		} else {
 			paypalModule.setClientId(null);
 			paypalModule.setClientSecret(null);
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonAccessController.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonAccessController.java
new file mode 100644
index 00000000000..ad58d806403
--- /dev/null
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonAccessController.java
@@ -0,0 +1,105 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.resource.accesscontrol.provider.paypalcheckout.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSFormItem;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.resource.accesscontrol.OfferAccess;
+import org.olat.resource.accesscontrol.Price;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.PaypalCheckoutModule;
+import org.olat.resource.accesscontrol.ui.FormController;
+import org.olat.resource.accesscontrol.ui.PriceFormat;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 nov. 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class PaypalSmartButtonAccessController extends FormBasicController implements FormController {
+	
+	private OfferAccess link;
+	
+	@Autowired
+	private PaypalCheckoutModule paypalModule;
+	
+	public PaypalSmartButtonAccessController(UserRequest ureq, WindowControl wControl, OfferAccess link) {
+		super(ureq, wControl, "paypal_smart_buttons");
+		this.link = link;
+		initForm(ureq);
+	}
+	
+	public PaypalSmartButtonAccessController(UserRequest ureq, WindowControl wControl, OfferAccess link, Form form) {
+		super(ureq, wControl, LAYOUT_CUSTOM, "paypal_smart_buttons", form);
+		this.link = link;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			String appId = paypalModule.getClientId();
+			layoutCont.contextPut("clientId", appId);
+			String currency = link.getOffer().getPrice().getCurrencyCode();
+			layoutCont.contextPut("currency", currency);
+			
+			String description = link.getOffer().getDescription();
+			if(StringHelper.containsNonWhitespace(description)) {
+				description = StringHelper.xssScan(description);
+				layoutCont.contextPut("description", description);
+			}
+			
+			Price price = link.getOffer().getPrice();
+			String priceStr = PriceFormat.fullFormat(price);
+			layoutCont.contextPut("price", priceStr);
+			
+			String mapperUri = registerMapper(ureq, new PaypalSmartButtonMapper(getIdentity(), link, this));
+			layoutCont.contextPut("mapperUri", mapperUri);
+		}
+		
+		JSAndCSSFormItem js = new JSAndCSSFormItem("js", new String[] { "https://www.paypal.com/sdk/js?client-id=" + paypalModule.getClientId() + "&currency=CHF&intent=authorize" });
+		formLayout.add(js);
+	}
+	
+	@Override
+	protected void fireEvent(UserRequest ureq, Event event) {
+		super.fireEvent(ureq, event);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+}
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonMapper.java b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonMapper.java
new file mode 100644
index 00000000000..2b4b9090da4
--- /dev/null
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/PaypalSmartButtonMapper.java
@@ -0,0 +1,123 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.resource.accesscontrol.provider.paypalcheckout.ui;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.json.JSONObject;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.dispatcher.mapper.Mapper;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.UserRequestImpl;
+import org.olat.core.gui.media.JSONMediaResource;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
+import org.olat.resource.accesscontrol.OfferAccess;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.PaypalCheckoutManager;
+import org.olat.resource.accesscontrol.provider.paypalcheckout.model.CreateSmartOrder;
+import org.olat.resource.accesscontrol.ui.AccessEvent;
+
+/**
+ * 
+ * Initial date: 6 nov. 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class PaypalSmartButtonMapper implements Mapper {
+	
+	private CreateSmartOrder order;
+	private final OfferAccess link;
+	private final Identity identity;
+	private final PaypalCheckoutManager paypalManager;
+	private final PaypalSmartButtonAccessController controller;
+	
+	public PaypalSmartButtonMapper(Identity identity, OfferAccess link, PaypalSmartButtonAccessController controller) {
+		this.link = link;
+		this.identity = identity;
+		this.controller = controller;
+		paypalManager = CoreSpringFactory.getImpl(PaypalCheckoutManager.class);
+	}
+
+	@Override
+	public MediaResource handle(String relPath, HttpServletRequest request) {
+		MediaResource resource = null;
+		if(relPath.contains("create-paypal-transaction")) {
+			resource = createPaypalTransaction();
+		} else if(relPath.contains("approve-paypal-transaction")) {
+			resource = approvePaypalTransaction(request);
+		} else if(relPath.contains("cancel-paypal-transaction")) {
+			resource = cancelPaypalTransaction();
+		} else if(relPath.contains("error-paypal-transaction")) {
+			resource = errorPaypalTransaction();
+		}
+		return resource;
+	}
+	
+	private MediaResource createPaypalTransaction() {
+		if(order == null) {
+			order = paypalManager.createOrder(identity, link);
+		}
+		if(order != null) {
+			if(StringHelper.containsNonWhitespace(order.getPaypalOrderId())) {
+				return buildOrderResource(order.getPaypalOrderId());
+			} else if(!order.isReservationOk()) {
+				JSONObject obj = new JSONObject();
+				obj.put("reservation", false);
+				return new JSONMediaResource(obj, "UTF-8");
+			}
+		}
+		return null;
+	}
+	
+	private MediaResource approvePaypalTransaction(HttpServletRequest request) {
+		if(order != null && StringHelper.containsNonWhitespace(order.getPaypalOrderId())) {
+			paypalManager.approveTransaction(order.getPaypalOrderId());
+
+			UserRequest ureq = new UserRequestImpl("m", request, null);
+			controller.fireEvent(ureq, AccessEvent.ACCESS_OK_EVENT);
+			return buildOrderResource(order.getPaypalOrderId());
+		}
+		return null;
+	}
+	
+	private MediaResource cancelPaypalTransaction() {
+		if(order != null && StringHelper.containsNonWhitespace(order.getPaypalOrderId())) {
+			paypalManager.cancelTransaction(order.getPaypalOrderId());
+			return buildOrderResource(order.getPaypalOrderId());
+		}
+		return null;
+	}
+	
+	private MediaResource errorPaypalTransaction() {
+		if(order != null && StringHelper.containsNonWhitespace(order.getPaypalOrderId())) {
+			paypalManager.errorTransaction(order.getPaypalOrderId());
+			return buildOrderResource(order.getPaypalOrderId());
+		}
+		return null;
+	}
+	
+	private JSONMediaResource buildOrderResource(String paypalOrderId) {
+		JSONObject obj = new JSONObject();
+		obj.put("orderID", paypalOrderId);
+		obj.put("reservation", true);
+		return new JSONMediaResource(obj, "UTF-8");
+	}
+}
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_content/paypal_smart_buttons.html b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_content/paypal_smart_buttons.html
new file mode 100644
index 00000000000..c04a1244032
--- /dev/null
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_content/paypal_smart_buttons.html
@@ -0,0 +1,99 @@
+<div class="o_paypal">
+	<fieldset class=" o_form form-horizontal clearfix">
+		<legend><i class="o_icon o_icon-fw o_ac_paypal_icon"> </i> $r.translate("access.paypal.title")</legend>
+		<div id="paypal_desc_${r.getCId()}" class="o_desc">$r.translate("access.paypal.desc.smart.buttons")</div>
+		<div id="paypal_processing_${r.getCId()}" class="o_desc" style="display: none;"><i class="o_icon o_icon-lg o_icon_pending o_icon-spin"> </i>  $r.translate("paypal.process.transaction")</div>
+		
+		<div id="paypal_wrapper_${r.getCId()}">
+	
+			#if($r.isNotEmpty($description))
+			<div class="form-group clearfix">
+				<label class="control-label col-sm-3" for-id="paypal_desc_${r.getCId()}">$r.translate("offer.description")</label>
+				<div class="col-sm-9"><p id="paypal_desc_${r.getCId()}" class="form-control-static">$description</p></div>
+			</div>
+			#end
+			<div class="form-group clearfix">
+				<label class="control-label col-sm-3" for-id="paypal_price_${r.getCId()}">$r.translate("offer.price")</label>
+				<div class="col-sm-9"><p id="paypal_price_${r.getCId()}" class="form-control-static">$price</p></div>
+			</div>
+			
+			<div id="paypal_buttons_${r.getCId()}">
+				<script defer>
+				jQuery(function() {	
+					jQuery.ajax({
+						url: 'https://www.paypal.com/sdk/js?client-id=$clientId&currency=$currency&intent=authorize',
+						cache: true,
+						dataType: "script",
+						success: loadButtons
+					});
+	
+					function loadButtons() {
+						paypal.Buttons({
+							createOrder: function(data, actions) {
+								return fetch('$mapperUri/create-paypal-transaction', {
+									method: 'post',
+									headers: { 'content-type': 'application/json' }
+								}).then(function(res) {
+									return res.json();
+								}).then(function(resData) {
+									// Use the same key name for order ID on the client and server
+									if(resData.reservation) {
+										return resData.orderID;
+									}
+									document.getElementById("paypal_wrapper_${r.getCId()}").style.display = "none";
+									document.getElementById("paypal_reservation_${r.getCId()}").style.display = "block";
+									return null; 
+								});
+							},
+							onApprove: function(data, actions) {
+								document.getElementById("paypal_wrapper_${r.getCId()}").style.display = "none";
+								document.getElementById("paypal_processing_${r.getCId()}").style.display = "block";
+								return fetch('$mapperUri/approve-paypal-transaction', {
+									method: 'post',
+									headers: { 'content-type': 'application/json' },
+									body: JSON.stringify({
+										orderID: data.orderID
+									})
+								}).then(function(res) {
+									return res.json();
+								}).then(function(resData) {
+									setTimeout(function() {
+										location.reload();
+									}, 100);
+								});
+							},
+							onCancel: function (data, actions) {
+								return fetch('$mapperUri/cancel-paypal-transaction', {
+									method: 'post',
+									headers: { 'content-type': 'application/json' },
+									body: JSON.stringify({
+										orderID: data.orderID
+									})
+								}).then(function(res) {
+									document.getElementById("paypal_wrapper_${r.getCId()}").style.display = "none";
+									document.getElementById("paypal_cancel_${r.getCId()}").style.display = "block";
+								});
+							},
+							onError: function (err) {
+								return fetch('$mapperUri/error-paypal-transaction', {
+									method: 'post',
+									headers: { 'content-type': 'application/json' },
+									body: JSON.stringify({
+										orderID: data.orderID
+									})
+								}).then(function(res) {
+									document.getElementById("paypal_wrapper_${r.getCId()}").style.display = "none";
+									document.getElementById("paypal_error_${r.getCId()}").style.display = "block";
+								});	
+				  			}
+						}).render('#paypal_buttons_${r.getCId()}');
+					}
+				});
+				</script>
+			</div>
+		</div>
+	</fieldset>
+	<div id="paypal_reservation_${r.getCId()}" class="o_error" style="display: none;" role="alert">$r.translate("paypal.reservation.failed") </div>
+	<div id="paypal_cancel_${r.getCId()}" class="o_warning" style="display: none;" role="alert">$r.translate("paypal.cancelled.transaction")</div>
+	<div id="paypal_error_${r.getCId()}" class="o_error" style="display: none;" role="alert">$r.translate("paypal.error.transaction")</div>
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_de.properties
index c6818d2064b..3598376e347 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_de.properties
@@ -1,11 +1,15 @@
 #Mon Aug 26 14:09:25 CEST 2019
 access.button=Zur Zahlung
 access.paypal.desc=Diese Ressource kann mit einem PayPal Konto gekauft werden. Halten Sie Ihre PayPal Kontoinformationen und w\u00E4hlen Sie die Schaltfl\u00E4che "Kaufen" um sich f\u00FCr den Zugang zu registrieren. 
+access.paypal.desc.smart.buttons=$\:access.paypal.desc
 access.paypal.title=Bezahlen mit PayPal
 checkout.client.id=Client ID
 checkout.client.secret=Client secret
 checkout.config.description=Konfigurieren Sie die PayPal API-Berechtigung f\u00FCr den PayPal Zugang mit den Elementen Client ID und Client Secret. Diese zwei Sicherheitselemente m\u00FCssen Sie zuerst in Ihrem PayPal Businesskonto erstellen. <strong>OpenOlat unterst\u00FCtzt nicht nachtr\u00E4gliche Anderungen am Bestellungen von Ihrem Paypal Konto.</strong>
 checkout.config.title=Paypal Checkout konfigurieren
+checkout.smart.buttons=Integration
+checkout.smart.buttons.enabled=Paypal Smart Buttons
+checkout.standard=Standard (Paypal Konto erforderlich)
 config.disabled.warning=Das PayPal Bezahlungsmodul ist f\u00FCr dieses System nicht freigeschaltet. Um Buchungen \u00FCber PayPal abwickeln zu k\u00F6nnen, nehmen Sie bitte Kontakt auf mit <a href="mailto:{0}">{0}</a>.
 currency=W\u00E4hrung
 currency.error=Fehler in der W\u00E4hrungsauswahl
@@ -22,15 +26,19 @@ offer.description=$org.olat.resource.accesscontrol.ui\:offer.description
 offer.price=Preis
 oo.order.nr=Bestellung ID (in OpenOlat)
 paypal.before.redirect.error=Unerwarteter Fehler
+paypal.cancelled.transaction=Sie haben die Transaktion abgebrochen. Die Buchung ist nicht ausgef\u00FChrt worden und Ihre Kreditkarte wurde nicht belastet. 
 paypal.capture.id=Capture ID
 paypal.checkout.method=Paypal Checkout v2
 paypal.enable=Paypal einschalten
+paypal.error.transaction=Ein Fehler ist aufgetreten.
 paypal.invoice.id=Invoice ID
 paypal.menu.title=Paypal
 paypal.menu.title.alt=Paypal Checkout konfigurieren
 paypal.order.id=Bestellung ID (in Paypal)
 paypal.order.status=Status
 paypal.order.status.reason=Status Erkl\u00E4rung
+paypal.process.transaction=Transkation ist erfolgreich und wird verarbeitet.
+paypal.reservation.failed=Es gibt momentan keinen freien Platz.
 paypal.segment.account=Konto
 paypal.segment.transactions=Transactionen
 paypal.transaction.amount=Preis (in Paypal)
@@ -39,7 +47,7 @@ paypal.transactions.empty=Keine Transaktion gefunden
 price=Preis
 price.error=Fehler in der Preisangabe
 show.all=Show all
-status.canceled=Abgebrochenn
+status.canceled=Abgebrochen
 status.error=Unerwartete Fehler
 status.new=Neu, noch nicht abgeschlossen
 status.success=Erfolgreich abgeschlossen
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_en.properties
index df3ff08f467..de1d2d5dadc 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_en.properties
@@ -1,11 +1,15 @@
 #Mon Aug 26 17:17:13 CEST 2019
 access.button=Go to Payment
 access.paypal.desc=PayPal description
+access.paypal.desc.smart.buttons=$\:access.paypal.desc
 access.paypal.title=Pay with PayPal
 checkout.client.id=Client ID
 checkout.client.secret=Client secret
 checkout.config.description=Configure your authorisation to use your Paypal count with the Client ID and Client Secret. These two security elements must be created in your Paypal Business Account. <strong>OpenOlat doesn't support subsequent changes in order from your Paypal Account (yet).</strong>
 checkout.config.title=Configure Paypal Checkout
+checkout.smart.buttons=Integration
+checkout.smart.buttons.enabled=Paypal Smart Buttons
+checkout.standard=Standard (Paypal account mandatory)
 config.disabled.warning=The PayPal payment module is not activated for this system. To handle bookings via PayPal can you please contact <a href="mailto:{0}">{0}</a>.
 currency=Currency
 currency.error=Currency error
@@ -22,15 +26,19 @@ offer.description=$org.olat.resource.accesscontrol.ui\:offer.description
 offer.price=Price
 oo.order.nr=Order ID (in OpenOlat)
 paypal.before.redirect.error=Unexpected error
+paypal.cancelled.transaction=You have canceled a transaction.
 paypal.capture.id=Capture ID
 paypal.checkout.method=Paypal Checkout v2
 paypal.enable=Enable Paypal
+paypal.error.transaction=An error occured.
 paypal.invoice.id=Invoice ID
 paypal.menu.title=Paypal
 paypal.menu.title.alt=Configure Paypal Checkout
 paypal.order.id=Order ID (in Paypal)
 paypal.order.status=Status
 paypal.order.status.reason=Status reason
+paypal.process.transaction=Your transaction is approved and is currently processed.
+paypal.reservation.failed=There isn't currently an available place.
 paypal.segment.account=Account
 paypal.segment.transactions=Transactions
 paypal.transaction.amount=Price (Paypal)
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_fr.properties
index 9f27010e779..cba735ce290 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_fr.properties
@@ -1,6 +1,7 @@
 #Mon May 04 19:54:01 CEST 2020
 access.button=Payer
 access.paypal.desc=Cet objet didactique peut \u00EAtre achet\u00E9 avec un compte Paypal. Cliquez sur le bouton "Payer" pour enregistrer votre acc\u00E8s.
+access.paypal.desc.smart.buttons=$\:access.paypal.desc
 access.paypal.title=Payer avec Paypal
 checkout.client.id=Client ID
 checkout.client.secret=Client secret
@@ -22,10 +23,12 @@ offer.description=$org.olat.resource.accesscontrol.ui\:offer.description
 offer.price=Prix
 oo.order.nr=ID commande (OpenOlat)
 paypal.before.redirect.error=Erreur inattendue
+paypal.cancelled.transaction=Vous avez annulez la transaction. La r\u00E9servation n'a pas pu \u00EAtre conclue et votre carte-de-cr\u00E9dit ne sera pas d\u00E9bit\u00E9e.
 paypal.capture.id=Capture ID
 paypal.checkout.method=Paypal Checkout v2
 paypal.config.description=Configurez l'autorisation d'acc\u00E8s \u00E0 l'API PayPal\: Client ID et Client Secret. Ce sont les deux \u00E9l\u00E9ments de s\u00E9curit\u00E9 dont vous avez besoin pour cr\u00E9er votre compte PayPal Business.
 paypal.enable=Activ\u00E8 Paypal
+paypal.error.transaction=Une erreur s'est produite.
 paypal.invoice.id=Facture ID
 paypal.menu.title=Paypal
 paypal.menu.title.alt=Configurer Paypal Checkout
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_it.properties
index b8f2aa936d2..64570a270c9 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_it.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/paypalcheckout/ui/_i18n/LocalStrings_it.properties
@@ -22,9 +22,11 @@ offer.description=$org.olat.resource.accesscontrol.ui\:offer.description
 offer.price=Prezzo
 oo.order.nr=ID ordine (in OpenOlat)
 paypal.before.redirect.error=Errore inaspettato
+paypal.cancelled.transaction=Hai annullato la transazione. La registrazione non \u00E8 stata conclusa e la carta di credito non sar\u00E0 addebitata.
 paypal.capture.id=Capture ID
 paypal.checkout.method=Paypal Checkout v2
 paypal.enable=Abilitare Paypal
+paypal.error.transaction=Si \u00E8 verificato un errore.
 paypal.invoice.id=ID della fattura
 paypal.menu.title=Paypal
 paypal.menu.title.alt=Configurare Paypal Checkout
diff --git a/src/main/java/org/olat/resource/accesscontrol/provider/token/TokenAccessHandler.java b/src/main/java/org/olat/resource/accesscontrol/provider/token/TokenAccessHandler.java
index bc7c9770a53..c3b3581dae4 100644
--- a/src/main/java/org/olat/resource/accesscontrol/provider/token/TokenAccessHandler.java
+++ b/src/main/java/org/olat/resource/accesscontrol/provider/token/TokenAccessHandler.java
@@ -61,6 +61,11 @@ public class TokenAccessHandler implements AccessMethodHandler {
 		return false;
 	}
 
+	@Override
+	public boolean isOverlapAllowed(AccessMethodHandler handler) {
+		return true;
+	}
+
 	@Override
 	public String getType() {
 		return METHOD_TYPE;
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/AccessConfigurationController.java b/src/main/java/org/olat/resource/accesscontrol/ui/AccessConfigurationController.java
index 491850f55b5..10d40542712 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/AccessConfigurationController.java
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/AccessConfigurationController.java
@@ -21,6 +21,7 @@
 package org.olat.resource.accesscontrol.ui;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -290,7 +291,7 @@ public class AccessConfigurationController extends FormBasicController {
 
 	protected void addConfiguration(OfferAccess link) {
 		AccessMethodHandler handler = acModule.getAccessMethodHandler(link.getMethod().getType());
-		AccessInfo infos = new AccessInfo(handler.getMethodName(getLocale()), handler.isPaymentMethod(), null, link);
+		AccessInfo infos = new AccessInfo(handler.getMethodName(getLocale()), null, link, handler);
 		confControllers.add(infos);
 
 		if(editable) {
@@ -407,7 +408,8 @@ public class AccessConfigurationController extends FormBasicController {
 	
 	private void checkOverlap() {
 		boolean overlap = false;
-		
+		boolean overlapAllowed = true;
+
 		// Take a controller from the list
 		for (AccessInfo confControllerA : confControllers) {
 			// Compare it to every other from the list
@@ -421,27 +423,26 @@ public class AccessConfigurationController extends FormBasicController {
 
 					// One unlimited booking method and another
 					if (aFrom == null && aTo == null) {
-						overlap = true;
-						break;
+						overlap |= true;
+						overlapAllowed &= confControllerA.isOverlapAllowed(confControllerB);
 					} 
 					// Start and end overlap
 					else if (aTo != null && bFrom != null && aTo.compareTo(bFrom) >= 0){
 						// Exclude not overlapping methods
 						// Negate condition for no overlap => condition for overlap
 						if (!(aFrom != null && bTo != null && aFrom.compareTo(bTo) > 0)) {
-							overlap = true; 
-							break;
+							overlap |= true; 
 						}
 					} 
 					// Two booking methods without start date
 					else if (aFrom == null && bFrom == null) {
-						overlap = true;
-						break;
+						overlap |= true;
+						overlapAllowed &= confControllerA.isOverlapAllowed(confControllerB);
 					} 
 					// Two booking methods without end date
 					else if (aTo == null && bTo == null) {
-						overlap = true;
-						break;
+						overlap |= true;
+						overlapAllowed &= confControllerA.isOverlapAllowed(confControllerB);
 					}
 				}
 			}
@@ -453,6 +454,7 @@ public class AccessConfigurationController extends FormBasicController {
 		
 		// Display a warning
 		confControllerContainer.contextPut("overlappingConfigs", overlap);
+		confControllerContainer.contextPut("overlappingErrorConfigs", !overlapAllowed);
 		confControllerContainer.setDirty(true);
 	}
 
@@ -461,16 +463,16 @@ public class AccessConfigurationController extends FormBasicController {
 		private String infos;
 		private String dates;
 		private OfferAccess link;
-		private final boolean paymentMethod;
+		private final AccessMethodHandler handler;
 		
 		private FormLink editButton;
 		private FormLink deleteButton;
 
-		public AccessInfo(String name, boolean paymentMethod, String infos, OfferAccess link) {
+		public AccessInfo(String name, String infos, OfferAccess link, AccessMethodHandler handler) {
 			this.name = name;
-			this.paymentMethod = paymentMethod;
 			this.infos = infos;
 			this.link = link;
+			this.handler = handler;
 		}
 
 		public String getName() {
@@ -482,7 +484,11 @@ public class AccessConfigurationController extends FormBasicController {
 		}
 
 		public boolean isPaymentMethod() {
-			return paymentMethod;
+			return handler.isPaymentMethod();
+		}
+		
+		public boolean isOverlapAllowed(AccessInfo info) {
+			return handler.isOverlapAllowed(info.handler);
 		}
 
 		public String getDates() {
@@ -510,7 +516,7 @@ public class AccessConfigurationController extends FormBasicController {
 					String price = PriceFormat.fullFormat(link.getOffer().getPrice());
 					if(acModule.isVatEnabled()) {
 						BigDecimal vat = acModule.getVat();
-						String vatStr = vat == null ? "" : vat.setScale(3, BigDecimal.ROUND_HALF_EVEN).toPlainString();
+						String vatStr = vat == null ? "" : vat.setScale(3, RoundingMode.HALF_EVEN).toPlainString();
 						return translate("access.info.price.vat", new String[]{price, vatStr});
 
 					} else {
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/OrderDetailController.java b/src/main/java/org/olat/resource/accesscontrol/ui/OrderDetailController.java
index 78381f8f271..57f0596e5b2 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/OrderDetailController.java
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/OrderDetailController.java
@@ -20,6 +20,7 @@
 package org.olat.resource.accesscontrol.ui;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -119,7 +120,7 @@ public class OrderDetailController extends FormBasicController {
 		String orderTotalStr;
 		if(acModule.isVatEnabled()) {
 			BigDecimal vat = acModule.getVat();
-			String vatStr = vat == null ? "" : vat.setScale(3, BigDecimal.ROUND_HALF_EVEN).toPlainString();
+			String vatStr = vat == null ? "" : vat.setScale(3, RoundingMode.HALF_EVEN).toPlainString();
 			orderTotalStr = translate("access.info.price.vat", new String[]{orderTotal, vatStr});
 		} else {
 			orderTotalStr = translate("access.info.price.noVat", new String[]{orderTotal});
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/PriceFormat.java b/src/main/java/org/olat/resource/accesscontrol/ui/PriceFormat.java
index 5cf7774cd47..d235445baf4 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/PriceFormat.java
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/PriceFormat.java
@@ -20,6 +20,7 @@
 package org.olat.resource.accesscontrol.ui;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 
 import org.apache.logging.log4j.Logger;
 import org.olat.core.logging.Tracing;
@@ -49,12 +50,12 @@ public class PriceFormat {
 			if(value != null && value.length() > 0) {
 				double val = Double.parseDouble(value);
 				BigDecimal bd = new BigDecimal(val);
-				price.setAmount(bd.setScale(2, BigDecimal.ROUND_HALF_UP));
+				price.setAmount(bd.setScale(2, RoundingMode.HALF_UP));
 			} else {
 				price.setAmount(BigDecimal.ZERO);
 			}
 		} catch (NumberFormatException e) {
-			log.error("Cannot format this value: " + value);
+			log.error("Cannot format this value: {}", value);
 		}  
 	}
 	
@@ -64,13 +65,13 @@ public class PriceFormat {
 			
 			double val = Double.parseDouble(value);
 			BigDecimal bd = new BigDecimal(val);
-			return bd.setScale(2, BigDecimal.ROUND_HALF_UP);
+			return bd.setScale(2, RoundingMode.HALF_UP);
 		}
 		return BigDecimal.ZERO;
 	}
 	
 	private static String extractNumbers(String value) {
-		StringBuffer buffer = new StringBuffer();
+		StringBuilder buffer = new StringBuilder();
 		for(int i=0; i<value.length(); i++) {
 			char ch = value.charAt(i);
 			if(Character.isDigit(ch)) {
@@ -108,6 +109,6 @@ public class PriceFormat {
 			isoCurrencyCode = "CHF";
 		}
 
-		return isoCurrencyCode + '\u00A0' + price.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN).toString();
+		return isoCurrencyCode + '\u00A0' + price.getAmount().setScale(2, RoundingMode.HALF_EVEN).toString();
 	}
 }
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_content/configuration_list.html b/src/main/java/org/olat/resource/accesscontrol/ui/_content/configuration_list.html
index 925f0a8a42c..292b613d58b 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/_content/configuration_list.html
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_content/configuration_list.html
@@ -1,4 +1,6 @@
-#if($overlappingConfigs)
+#if($r.isTrue($overlappingErrorConfigs))
+	<div class="o_block"><div class="o_error" role="alert"><i class="o_icon o_icon_important"> </i> $r.translate("accesscontrol.overlap.error")</div></div>
+#elseif($r.isTrue($overlappingConfigs))
 	<div class="o_important"><i class="o_icon o_icon_important"> </i> $r.translate("accesscontrol.overlap.found")</div>
 #end
 
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties
index 45ff7203382..fe103e45481 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties
@@ -24,6 +24,7 @@ accesscontrol.desc.end=W\u00E4hlen Sie die Schaltfl\u00E4che "$\:add.accesscontr
 accesscontrol.no.methods.full.access=Es ist noch keine Buchungsmethode konfiguriert.<br />Der Zugang zu dieser Ressource steht allen Systembenutzern ohne Buchung offen. W\u00E4hlen Sie die Schaltfl\u00E4che "$\:add.accesscontrol" um den Zugang einzuschr\u00E4nken und/oder eine Buchung zu erzwingen.
 accesscontrol.no.methods.no.access=Es ist noch keine Buchungsmethode konfiguriert.<br />Diese Ressource kann nur von den Teilnehmer der Ressource verwendet werden. W\u00E4hlen Sie die Schaltfl\u00E4che "$\:add.accesscontrol" um eine Buchungsmethode zu w\u00E4hlen und die Ressource zu ver\u00F6ffentlichen.
 accesscontrol.overlap.found=Es wurden \u00FCberlappende Buchungsmethoden gefunden. Bitte stellen Sie sicher, dass dies beabsichtig und korrekt ist.
+accesscontrol.overlap.error=Es wurden \u00FCberlappende Buchungsmethoden gefunden, die es nicht unterstützen. Bitte, ben\u00FCtzen Sie nur eine Methode gleichseitig.
 accesscontrol.table.from=g\u00FCltig von
 accesscontrol.table.method=Buchungsmethode
 accesscontrol.table.to=bis
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties
index d00aff49e98..3d63ea907d7 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties
@@ -24,6 +24,7 @@ accesscontrol.desc.end=Select the button "$\:add.accesscontrol" to select one or
 accesscontrol.no.methods.full.access=There is no booking method configured to restrict access. This resource can be accessed by all known users without an explicit booking. Select the button "$\:add.accesscontrol"  to restrict access and/or to require an explicit booking.
 accesscontrol.no.methods.no.access=There is no booking method configured. This resource can only be accessed by explicit members of this resource. Select the button "$\:add.accesscontrol" to add a booking method and to publish the resource.
 accesscontrol.overlap.found=There are overlapping booking methods. Please make sure this is intended and correct.
+accesscontrol.overlap.error=There are overlapping booking methods which don't support it. Please make sure you only one method at the same time.
 accesscontrol.table.from=valid from
 accesscontrol.table.method=Booking method
 accesscontrol.table.to=to
diff --git a/src/main/java/org/olat/shibboleth/manager/ShibbolethAutoAccessHandler.java b/src/main/java/org/olat/shibboleth/manager/ShibbolethAutoAccessHandler.java
index 81d39a87a77..069bdf92ff8 100644
--- a/src/main/java/org/olat/shibboleth/manager/ShibbolethAutoAccessHandler.java
+++ b/src/main/java/org/olat/shibboleth/manager/ShibbolethAutoAccessHandler.java
@@ -41,6 +41,8 @@ public class ShibbolethAutoAccessHandler extends AutoAccessHandler {
 	public String getType() {
 		return METHOD_TYPE;
 	}
+	
+	
 
 	@Override
 	public String getMethodName(Locale locale) {
diff --git a/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java b/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java
index 21b3f2faefc..ce082acd0c7 100644
--- a/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java
+++ b/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java
@@ -169,7 +169,7 @@ public class ACFrontendManagerTest extends OlatTestCase {
 		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("agp-" + UUID.randomUUID().toString());
 		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("agp-" + UUID.randomUUID().toString());
 		Identity id3 = JunitTestHelper.createAndPersistIdentityAsUser("agp-" + UUID.randomUUID().toString());
-		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", new Integer(0), new Integer(2), false, false, null);
+		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", Integer.valueOf(0), Integer.valueOf(2), false, false, null);
 		businessGroupRelationDao.addRole(id1, group, GroupRoles.participant.name());
 		businessGroupRelationDao.addRole(id2, group, GroupRoles.participant.name());
 
@@ -200,7 +200,7 @@ public class ACFrontendManagerTest extends OlatTestCase {
 	public void testFreeAccesToBusinessGroupWithWaitingList_enoughPlace() {
 		//create a group with a free offer
 		Identity id = JunitTestHelper.createAndPersistIdentityAsUser("agp-" + UUID.randomUUID().toString());
-		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", new Integer(0), new Integer(10), true, false, null);
+		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", Integer.valueOf(0), Integer.valueOf(10), true, false, null);
 		Offer offer = acService.createOffer(group.getResource(), "Free group (waiting)");
 		offer = acService.save(offer);
 		List<AccessMethod> freeMethods = acMethodManager.getAvailableMethodsByType(FreeAccessMethod.class);
@@ -230,7 +230,7 @@ public class ACFrontendManagerTest extends OlatTestCase {
 		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("agp-1");
 		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("agp-2");
 		Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("agp-3");
-		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", new Integer(0), new Integer(2), true, false, null);
+		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", Integer.valueOf(0), Integer.valueOf(2), true, false, null);
 		businessGroupRelationDao.addRole(id1, group, GroupRoles.participant.name());
 		businessGroupRelationDao.addRole(id2, group, GroupRoles.participant.name());
 
@@ -272,7 +272,7 @@ public class ACFrontendManagerTest extends OlatTestCase {
 		Identity id2 = JunitTestHelper.createAndPersistIdentityAsUser("agp-" + UUID.randomUUID().toString());
 		Identity id3 = JunitTestHelper.createAndPersistIdentityAsUser("agp-" + UUID.randomUUID().toString());
 
-		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", new Integer(0), new Integer(2), true, false, null);
+		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", Integer.valueOf(0), Integer.valueOf(2), true, false, null);
 		Offer offer = acService.createOffer(group.getResource(), "Free group (waiting)");
 		offer = acService.save(offer);
 		List<AccessMethod> methods = acMethodManager.getAvailableMethodsByType(PaypalAccessMethod.class);
@@ -321,7 +321,7 @@ public class ACFrontendManagerTest extends OlatTestCase {
 		Identity id2 = JunitTestHelper.createAndPersistIdentityAsUser("agp-" + UUID.randomUUID().toString());
 		Identity id3 = JunitTestHelper.createAndPersistIdentityAsUser("agp-" + UUID.randomUUID().toString());
 
-		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", new Integer(0), new Integer(2), false, false, null);
+		BusinessGroup group = businessGroupService.createBusinessGroup(null, "Free group", "But you must wait", Integer.valueOf(0), Integer.valueOf(2), false, false, null);
 		Offer offer = acService.createOffer(group.getResource(), "Free group (waiting)");
 		offer = acService.save(offer);
 		List<AccessMethod> methods = acMethodManager.getAvailableMethodsByType(PaypalAccessMethod.class);
diff --git a/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java b/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java
index d36430c2af0..fdafd681897 100644
--- a/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java
+++ b/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.List;
 import java.util.UUID;
 
@@ -373,9 +374,9 @@ public class ACOrderManagerTest extends OlatTestCase {
 		assertEquals(ident7, retrivedOrder.getDelivery());
 		assertEquals(1, retrivedOrder.getParts().size());
 		
-		assertEquals(price1.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN), retrivedOrder.getTotalOrderLines().getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN));
+		assertEquals(price1.getAmount().setScale(2, RoundingMode.HALF_EVEN), retrivedOrder.getTotalOrderLines().getAmount().setScale(2, RoundingMode.HALF_EVEN));
 		assertEquals(price1.getCurrencyCode(), retrivedOrder.getTotalOrderLines().getCurrencyCode());
-		assertEquals(price1.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN), retrivedOrder.getTotal().getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN));
+		assertEquals(price1.getAmount().setScale(2, RoundingMode.HALF_EVEN), retrivedOrder.getTotal().getAmount().setScale(2, RoundingMode.HALF_EVEN));
 		assertEquals(price1.getCurrencyCode(), retrivedOrder.getTotal().getCurrencyCode());	
 		
 		//check order part
@@ -384,9 +385,9 @@ public class ACOrderManagerTest extends OlatTestCase {
 		assertNotNull(orderPart.getTotal());
 		assertNotNull(orderPart.getTotalOrderLines());
 		assertEquals(1, orderPart.getOrderLines().size());
-		assertEquals(price1.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN), orderPart.getTotalOrderLines().getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN));
+		assertEquals(price1.getAmount().setScale(2, RoundingMode.HALF_EVEN), orderPart.getTotalOrderLines().getAmount().setScale(2, RoundingMode.HALF_EVEN));
 		assertEquals(price1.getCurrencyCode(), orderPart.getTotalOrderLines().getCurrencyCode());
-		assertEquals(price1.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN), orderPart.getTotal().getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN));
+		assertEquals(price1.getAmount().setScale(2, RoundingMode.HALF_EVEN), orderPart.getTotal().getAmount().setScale(2, RoundingMode.HALF_EVEN));
 		assertEquals(price1.getCurrencyCode(), orderPart.getTotal().getCurrencyCode());	
 		
 		//check order line
@@ -396,9 +397,9 @@ public class ACOrderManagerTest extends OlatTestCase {
 		Assert.assertNotNull(line.getUnitPrice());
 		Assert.assertNotNull(line.getTotal());
 		Assert.assertEquals(offer1, line.getOffer());
-		Assert.assertEquals(price1.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN), line.getUnitPrice().getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN));
+		Assert.assertEquals(price1.getAmount().setScale(2, RoundingMode.HALF_EVEN), line.getUnitPrice().getAmount().setScale(2, RoundingMode.HALF_EVEN));
 		Assert.assertEquals(price1.getCurrencyCode(), line.getUnitPrice().getCurrencyCode());
-		Assert.assertEquals(price1.getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN), line.getTotal().getAmount().setScale(2, BigDecimal.ROUND_HALF_EVEN));
+		Assert.assertEquals(price1.getAmount().setScale(2, RoundingMode.HALF_EVEN), line.getTotal().getAmount().setScale(2, RoundingMode.HALF_EVEN));
 		Assert.assertEquals(price1.getCurrencyCode(), line.getTotal().getCurrencyCode());	
 	}
 	
diff --git a/src/test/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAOTest.java b/src/test/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAOTest.java
index 4bebb1a909e..05c427376b1 100644
--- a/src/test/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAOTest.java
+++ b/src/test/java/org/olat/resource/accesscontrol/provider/paypalcheckout/manager/PaypalCheckoutTransactionDAOTest.java
@@ -22,6 +22,7 @@ package org.olat.resource.accesscontrol.provider.paypalcheckout.manager;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -146,6 +147,39 @@ public class PaypalCheckoutTransactionDAOTest extends OlatTestCase {
 		PaypalCheckoutTransaction unkownTrx = transactionDao.loadTransactionBySecureUuid("something-uuid-but-not-real");
 		Assert.assertNull(unkownTrx);
 	}
+	
+	@Test
+	public void loadTransactionByPaypalOrderId() {
+		List<AccessMethod> methods = acMethodManager.getAvailableMethodsByType(PaypalCheckoutAccessMethod.class);
+		AccessMethod checkoutMethod = methods.get(0);
+
+		//create an offer to buy
+		Identity author = JunitTestHelper.createAndPersistIdentityAsRndUser("customer-1");
+		RepositoryEntry entry = JunitTestHelper.createRandomRepositoryEntry(author);
+		Offer offer = acService.createOffer(entry.getOlatResource(), "TestSaveTransaction");
+		offer = acService.save(offer);
+		
+		Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("customer-1");
+		dbInstance.commit();
+		
+		//create and save an order
+		Order order = acOrderManager.createOrder(identity);
+		OrderPart orderPart = acOrderManager.addOrderPart(order);
+		OrderLine item = acOrderManager.addOrderLine(orderPart, offer);
+		order = acOrderManager.save(order);
+		dbInstance.commit();
+		Assert.assertNotNull(item);
+		
+		String paypalOrderId = UUID.randomUUID().toString();
+		Price amount = new PriceImpl(new BigDecimal("5.00"), "CHF");
+		PaypalCheckoutTransaction trx = transactionDao.createTransaction(amount, order, orderPart, checkoutMethod);
+		trx.setPaypalOrderId(paypalOrderId);
+		dbInstance.commitAndCloseSession();
+
+		PaypalCheckoutTransaction orderedTrx = transactionDao.loadTransactionByPaypalOrderId(paypalOrderId);
+		Assert.assertNotNull(orderedTrx);
+		Assert.assertEquals(trx, orderedTrx);
+	}
 
 	@Test
 	public void loadTransactionByOrder() {
-- 
GitLab