Index: /lib/wpsg_calculation.class.php
===================================================================
--- /lib/wpsg_calculation.class.php	(revision 8419)
+++ /lib/wpsg_calculation.class.php	(revision 8420)
@@ -426,5 +426,5 @@
 			
         }
-
+				
         /**
          * Setzt die Zahlungskosten
@@ -1510,4 +1510,6 @@
 			}
 
+			$kosten = 0;
+			
 			foreach ($arKosten as $k) {
 
Index: /lib/wpsg_header.class.php
===================================================================
--- /lib/wpsg_header.class.php	(revision 8419)
+++ /lib/wpsg_header.class.php	(revision 8420)
@@ -58,9 +58,10 @@
 		} // public static function PDFPlugin($filename)
 		
-		public static function JSONData($arData)
-		{
+		public static function JSONData($arData) {
 			
 			header('Content-Type: application/json');
-			die(json_encode($arData));
+			
+			echo json_encode($arData);
+			exit;
 			
 		} // public static function JSONData()
Index: /mods/mod_paypalapi/GiroPayPalAPI.php
===================================================================
--- /mods/mod_paypalapi/GiroPayPalAPI.php	(revision 8420)
+++ /mods/mod_paypalapi/GiroPayPalAPI.php	(revision 8420)
@@ -0,0 +1,156 @@
+<?php
+	
+	declare(strict_types=1);
+	
+	/**
+	 * @author: daniel
+	 * @date: 13.06.24
+	 * @time: 15:18
+	 */
+	
+	namespace wpsg;
+	
+	class GiroPayPalAPI {
+		
+		private static ?GiroPayPalAPI $instance = null;
+		
+		private string $client_id;
+		private string $client_secret;
+		private bool $bSandbox;
+		
+		public function __construct(string $client_id, string $client_secret, bool $bSandbox) {
+		
+			$this->client_id = $client_id;
+			$this->client_secret = $client_secret;
+			$this->bSandbox = $bSandbox;
+		
+		}
+		
+		public function captureOrder(string $paypal_order_id): array {
+			
+			$r = $this->call(
+				'/v2/checkout/orders/'.$paypal_order_id.'/capture',
+				null
+			);
+			
+			return $r;
+			
+		}
+		
+		public function creeateOrder(\wpsg_order $oOrder): string {
+			
+			$r = $this->call(
+				'/v2/checkout/orders',
+				[
+					'intent' => 'CAPTURE',
+					'purchase_units' => [
+						[
+							'reference_id' => $oOrder->getId(),
+							'amount' => [
+								'currency_code' => 'EUR',
+								'value' => $oOrder->getToPay()
+							]
+						]
+					]
+				]
+			);
+			
+			if (!isset($r['id'])) die(wpsg_debug($r));
+			
+			return $r['id'];
+			
+		}
+		
+		private function call(string $path, ?array $arBodyData = null): ?array {
+		
+			if ($this->bSandbox) $url = 'https://api-m.sandbox.paypal.com'.$path;
+			else $url = 'https://api-m.paypal.com'.$path;
+			
+			$ch = curl_init();
+			
+			curl_setopt($ch, CURLOPT_URL, $url);
+			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+			curl_setopt($ch, CURLOPT_USERPWD, $this->client_id.":".$this->client_secret);
+			curl_setopt($ch, CURLOPT_HTTPHEADER, [
+				"Content-Type: application/json"
+			]);
+			
+			curl_setopt($ch, CURLOPT_POST, true);
+			
+			if ($arBodyData !== null) {
+			 
+				curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($arBodyData));
+				
+			}
+			
+			$response = curl_exec($ch);
+			
+			if (curl_errno($ch)) throw new \Exception('cURL Error: '.curl_error($ch));
+			
+			$response_data = json_decode($response, true);
+			
+			return $response_data;
+			
+		}
+		
+		public function integrateSDK() {
+			
+			if ($this->bSandbox) echo '<script src="https://sandbox.paypal.com/sdk/js?client-id='.$this->client_id.'&components=buttons,payment-fields,marks,funding-eligibility&enable-funding=giropay&currency=EUR"></script>';
+			else echo '<script src="https://www.paypal.com/sdk/js?client-id='.$this->client_id.'&components=buttons,payment-fields,marks,funding-eligibility&enable-funding=giropay&currency=EUR"></script>';
+			
+		}
+		
+		private function getToken(): string {
+		
+			$ch = curl_init();
+			
+			curl_setopt($ch, CURLOPT_URL, "https://api-m.sandbox.paypal.com/v1/oauth2/token");
+			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+			curl_setopt($ch, CURLOPT_USERPWD, "$this->client_id:$this->client_secret");
+			curl_setopt($ch, CURLOPT_POST, true);
+			curl_setopt($ch, CURLOPT_HTTPHEADER, [
+				"Content-Type: application/x-www-form-urlencoded"
+			]);
+			curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials");
+			
+			$response = curl_exec($ch);
+			
+			if (curl_errno($ch)) throw new \Exception('cURL Error: '.curl_error($ch));
+			
+			$response_data = json_decode($response, true);
+			
+			if (!isset($response_data['access_token'])) throw new \Exception('PayPalAPI Error');
+			
+			return $response_data['access_token'];
+		
+		}
+		
+		public static function getInstance(): GiroPayPalAPI {
+			
+			if (self::$instance === null) {
+			
+				if (intval(\wpsg_ShopController::getShop()->get_option('wpsg_mod_paypalapi_sandbox')) > 0) {
+				
+					self::$instance = new GiroPayPalAPI(
+						\wpsg_ShopController::getShop()->get_option('wpsg_mod_paypalapi_sandbox_clientid'),
+						\wpsg_ShopController::getShop()->get_option('wpsg_mod_paypalapi_sandbox_secret'),
+						true,
+					);
+					
+				} else {
+					
+					self::$instance = new GiroPayPalAPI(
+						\wpsg_ShopController::getShop()->get_option('wpsg_mod_paypalapi_clientid'),
+						\wpsg_ShopController::getShop()->get_option('wpsg_mod_paypalapi_secret'),
+						false,
+					);
+					
+				}
+			
+			}
+			
+			return self::$instance;
+			
+		}
+		
+	}
Index: /mods/wpsg_mod_basic.class.php
===================================================================
--- /mods/wpsg_mod_basic.class.php	(revision 8419)
+++ /mods/wpsg_mod_basic.class.php	(revision 8420)
@@ -114,5 +114,6 @@
             5503 	=> 'wpsg_mod_paymentqr',
 			5600    => 'wpsg_mod_orderstates',
-			5700    => 'wpsg_mod_payone'
+			5700    => 'wpsg_mod_payone',
+			5800    => 'wpsg_mod_quickcheckout'
 		);
 		
Index: /mods/wpsg_mod_paypalapi.class.php
===================================================================
--- /mods/wpsg_mod_paypalapi.class.php	(revision 8419)
+++ /mods/wpsg_mod_paypalapi.class.php	(revision 8420)
@@ -24,6 +24,5 @@
 		 * Costructor 
 		 */
-		public function __construct()
-		{
+		public function __construct() {
 				
 			parent::__construct();
@@ -32,4 +31,6 @@
 			$this->group = __('Zahlungsarten', 'wpsg');
 			$this->desc = __('ErmÃ¶glicht die Zahlungsart PayPal und PayPal PLUS, ÃŒber die REST API Schnittstelle und Login mit PayPal Express.', 'wpsg');
+			
+			require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'mod_paypalapi'.DIRECTORY_SEPARATOR.'GiroPayPalAPI.php';
 			
 		} // public function __construct()
@@ -58,6 +59,9 @@
 			$this->shop->checkDefault('wpsg_mod_paypalapi_experience_locale_code', 'DE');
 			
-			$this->shop->checkDefault('wpsg_mod_paypalapi_plus_aktiv', '1');		
-									
+			$this->shop->checkDefault('wpsg_mod_paypalapi_plus_aktiv', '1');
+			
+			$this->shop->checkDefault('wpsg_mod_paypalapi_giropay_name', 'GiroPay');
+			$this->shop->checkDefault('wpsg_mod_paypalapi_giropay_hint', 'Kommen Sie auf die sichere Seite und zahlen Sie mit GiroPay, was von vielen deutschen Banken unterstÃŒtz wird.');
+			
 			$this->shop->checkDefault('wpsg_mod_paypalapi_stornostate', array('400' => '1', '500' => '1'));
 							
@@ -133,4 +137,8 @@
 			$this->shop->update_option('wpsg_mod_paypalapi_experience_locale_code', $_REQUEST['wpsg_mod_paypalapi_experience_locale_code'], false, false, WPSG_SANITIZE_VALUES, ['DE', 'GB', 'FR']);
 
+			$this->shop->update_option('wpsg_mod_paypalapi_giropay_aktiv', $_REQUEST['wpsg_mod_paypalapi_giropay_aktiv'], false, false, WPSG_SANITIZE_CHECKBOX);
+			$this->shop->update_option('wpsg_mod_paypalapi_giropay_name', $_REQUEST['wpsg_mod_paypalapi_giropay_name'], false, false, WPSG_SANITIZE_TEXTFIELD);
+			$this->shop->update_option('wpsg_mod_paypalapi_giropay_hint', $_REQUEST['wpsg_mod_paypalapi_giropay_hint'], false, false, WPSG_SANITIZE_TEXTAREA);
+			
 			$this->shop->update_option('wpsg_mod_paypalapi_plus_aktiv', $_REQUEST['wpsg_mod_paypalapi_plus_aktiv'], false, false, WPSG_SANITIZE_CHECKBOX);
 			$this->shop->update_option('wpsg_mod_paypalapi_plus_basket', $_REQUEST['wpsg_mod_paypalapi_plus_basket'], false, false, WPSG_SANITIZE_CHECKBOX);
@@ -313,9 +321,29 @@
 					'logo' => $this->shop->getRessourceURL('mods/mod_paypalapi/gfx/logo_100x25.png')
 				);
-			 			
+			
+			}
+			
+			if (is_admin() || $this->shop->get_option('wpsg_mod_paypalapi_giropay_aktiv') === '1') {
+				
+				$arPayment[$this->id.'_1'] = array(
+					'id' => $this->id.'_1',
+					'name' => __($this->shop->get_option('wpsg_mod_paypalapi_giropay_name'), 'wpsg'),
+					'price' => $this->shop->get_option('wpsg_mod_paypalapi_gebuehr'),
+					'tax_key' => $this->shop->get_option('wpsg_mod_paypalapi_mwst'),
+					'mwst_null' => $this->shop->get_option('wpsg_mod_paypalapi_mwstland'),
+					'hint' => __($this->shop->get_option('wpsg_mod_paypalapi_giropay_hint'), 'wpsg').'<br /><br /><div id="giropay-container"></div>',
+					'logo' => $this->shop->getRessourceURL('mods/mod_paypalapi/gfx/giropay.svg')
+				);
+				
 			}
 			
 		} // public function addPayment(&$arPayment)
-				 
+		
+		public function checkout2_inner_prebutton(&$checkout_view): void {
+			
+			$this->render(WPSG_PATH_VIEW.'/mods/mod_paypalapi/checkout2_inner_prebutton.phtml');
+			
+		}
+		
 		public function checkout_handlePayment() {
 		
@@ -495,4 +523,23 @@
 			if ($oOrder->getToPay(WPSG_BRUTTO) <= 0) return;
 
+			if ($oOrder->getPaymentID() === '10_1') {
+				
+				return;
+				
+				/*
+				$captured = $oOrder->getMeta('wpsg_mod_giropay_captured', false, 0);
+				
+				if ($captured === 0) {
+					
+					if ($this->isSandbox()) $this->shop->view['clientId'] = $this->shop->get_option('wpsg_mod_paypalapi_sandbox_clientid');
+					else $this->shop->view['clientId'] = $this->shop->get_option('wpsg_mod_paypalapi_clientid');
+					
+				}
+				
+				$this->render(WPSG_PATH_VIEW.'/mods/mod_paypalapi/order_done_giropay.phtml');
+				*/
+				
+			}
+			
 			// Bestellungen die nicht mit PayPal API beglichen werden sollen ignorieren
 			if ($oOrder->getPaymentID() != $this->id) return;
@@ -634,11 +681,9 @@
 		} // public function order_done(&$order_id)
 
-		public function order_view_afterpayment(&$order_id)
-		{
+		public function order_view_afterpayment(&$order_id) {
 			
 			$oOrder = wpsg_order::getInstance($order_id);
 			
-			if ($oOrder->getPaymentID() == $this->id)
-			{
+			if ($oOrder->getPaymentID() == $this->id || $oOrder->getPaymentID() === '10_1') {
 				 
 				$this->shop->render(WPSG_PATH_VIEW.'/mods/mod_paypalapi/order_view_afterpayment.phtml');
@@ -749,4 +794,19 @@
 		} // public function basket_submitSuccess()
 		
+		public function handle_order() {
+			
+			if (($_SESSION['wpsg']['checkout']['payment']??'') === '10_1') {
+				
+				if ($this->isSandbox()) $this->shop->view['clientId'] = $this->shop->get_option('wpsg_mod_paypalapi_sandbox_clientid');
+			 	else $this->shop->view['clientId'] = $this->shop->get_option('wpsg_mod_paypalapi_clientid');
+				
+				$this->render(WPSG_PATH_VIEW.'/mods/mod_paypalapi/overview_button.phtml');
+				
+				return -2;
+				
+			}
+			
+		}
+		
 		/* Redirects */
 		
@@ -1047,4 +1107,107 @@
 						
 		} // public function PaymentSelectRedirect()
+		
+		/**
+		 * Wird vor Bestellabschluss stat dem Button "zahlungspflichtig bestellen" aufgerufen
+		 * @return void
+		 */
+		public function giroPayCreateOrderFromSessionRedirect(): void {
+		 
+			try {
+			
+				$this->shop->basket->initFromSession();
+				
+				$order_id = $this->shop->basket->save(false, false, true);
+				$paypal_order_id = \wpsg\GiroPayPalAPI::getInstance()->creeateOrder(wpsg_order::getInstance($order_id));
+				
+				$oOrder = wpsg_order::getInstance($order_id);
+				$oOrder->setMeta('wpsg_mod_paypalapi_paymentid', $paypal_order_id);
+				
+				wpsg_header::JSONData([
+					'id' => $paypal_order_id
+				]);
+				
+			} catch (\Throwable $e) {
+				
+				wpsg_header::JSONData([
+					'error' => $e->getMessage()
+				]);
+				
+			}
+		
+		}
+		
+		/**
+		 * Wird beim platzieren der Bestellung aufgerufen
+		 * @return void
+		 */
+		public function giroPayCreateOrderRedirect(): void {
+		
+			// wpsg_debug($_REQUEST);
+			// wpsg_debug(file_get_contents('php://input'));
+			
+			$order_id = intval($_REQUEST['order_id']);
+			$paypal_order_id = \wpsg\GiroPayPalAPI::getInstance()->creeateOrder(wpsg_order::getInstance($order_id));
+			
+			$oOrder = wpsg_order::getInstance($order_id);
+			$oOrder->setMeta('wpsg_mod_paypalapi_paymentid', $paypal_order_id);
+			
+			wpsg_header::JSONData([
+				'id' => $paypal_order_id
+			]);
+			
+			exit;
+		
+		}
+	
+		public function giroPayCancelOrderRedirect(): void {
+	
+			$this->shop->addFrontendError(__('Zahlung wurde bei/von GiroPay abgebrochen. Bitte eine andere Zahlungsart wÃ€hlen oder erneut versuchen.', 'wpsg'));
+			
+			$this->shop->redirect(
+				\wpsg_ShopController::getShop()->getURL(\wpsg_ShopController::URL_CHECKOUT2)
+			);
+			
+		}
+		
+		public function giroPayApproveOrderRedirect(): void {
+			
+			$request = json_decode(file_get_contents('php://input'), true);
+			
+			$paypal_order_id = $request['orderID'];
+			$paypal_order_json = \wpsg\GiroPayPalAPI::getInstance()->captureOrder($paypal_order_id);
+			
+			$order_id = intval($paypal_order_json['purchase_units'][0]['reference_id']);
+			
+			$oOrder = wpsg_order::getInstance($order_id);
+			$oOrder->setMeta('wpsg_mod_paypalapi_saleid', $paypal_order_json['purchase_units'][0]['payments']['captures'][0]['id']);
+			
+			$this->shop->basket->initFromDB($order_id);
+			$this->shop->basket->save(true, true, true);
+			
+			wpsg_header::JSONData([
+				'order_paypal' => $paypal_order_json,
+				'order_done_url' => $this->shop->getDoneURL($order_id)
+			]);
+			
+			exit;
+			
+		}
+		
+		/**
+		 * GehÃ¶rt zur Giropay Option vom Checkot2
+		 * @return void
+		 */
+		public function giroPaySetRedirect(): void {
+			
+			//wpsg_debug($_REQUEST);
+			//wpsg_debug(file_get_contents('php://input'));
+			//die();
+			
+			$_SESSION['wpsg']['checkout']['payment'] = '10_1';
+			
+			$this->shop->redirect($this->shop->getURL(wpsg_ShopController::URL_OVERVIEW));
+			
+		}
 		
 		/* Aktionen */
Index: /mods/wpsg_mod_productvariants.class.php
===================================================================
--- /mods/wpsg_mod_productvariants.class.php	(revision 8419)
+++ /mods/wpsg_mod_productvariants.class.php	(revision 8420)
@@ -141,5 +141,6 @@
 		    $this->shop->update_option('wpsg_vp_detailview', $_REQUEST['wpsg_vp_detailview'], false, false, WPSG_SANITIZE_CHECKBOX);
 			$this->shop->update_option('wpsg_vp_replaceanr', wpsg_xss($_REQUEST['wpsg_vp_replaceanr']));
-
+			$this->shop->update_option('wpsg_vp_product_stock_nocalc', intval($_REQUEST['wpsg_vp_product_stock_nocalc']));
+ 
 		} // public function settings_save()
 
@@ -1639,6 +1640,8 @@
 		} // private function setProductVariation($product_id, $field, $value)
 
-		private function setStockFromVariation($product_id)
-		{
+		private function setStockFromVariation($product_id) {
+			
+			// Die Funktion berechnet den Bestand auch im Backend
+			if ($this->shop->get_option('wpsg_vp_product_stock_nocalc') === '1') return;
 
 			if ($this->isVariantsProductKey($product_id)) {
@@ -2065,8 +2068,18 @@
 
 			}
-
+			
 			$this->db->unlockTables();
 
-			$this->setStockFromVariation($produkt_key);
+			if ($this->shop->get_option('wpsg_vp_product_stock_nocalc') === '1') {
+				
+				// Produktbestand reduzieren
+				$this->db->Query("UPDATE ".WPSG_TBL_PRODUCTS." SET `stock` = `stock` - '".intval($menge)."' WHERE `id` = '".wpsg_q($this->shop->getProduktID($produkt_key))."' ");
+				
+			} else {
+				
+				// Produktbestand aus Variantenbestand ermitteln
+				$this->setStockFromVariation($produkt_key);
+				
+			}
 
 			$this->checkMinStock($arVar, $produkt_key);
Index: /views/mods/mod_ordercondition/overview_top.phtml
===================================================================
--- /views/mods/mod_ordercondition/overview_top.phtml	(revision 8419)
+++ /views/mods/mod_ordercondition/overview_top.phtml	(revision 8420)
@@ -12,5 +12,5 @@
 <div class="wpsg_agb" id="wpsg_agb_<?php echo $oc['id']; ?>">
 	<label>
-		<input type="checkbox" value="1" name="wpsg_mod_ordercondition[<?php echo $oc['id']; ?>]" />
+		<input type="checkbox" value="1" name="wpsg_mod_ordercondition[<?php echo $oc['id']; ?>]" required />
 		<?php if (strlen($oc['text']) < $minLength) { ?>
 		<?php echo $oc['text']; ?>
Index: /views/mods/mod_paypalapi/checkout2_inner_prebutton.phtml
===================================================================
--- /views/mods/mod_paypalapi/checkout2_inner_prebutton.phtml	(revision 8420)
+++ /views/mods/mod_paypalapi/checkout2_inner_prebutton.phtml	(revision 8420)
@@ -0,0 +1,60 @@
+<?php
+	
+	declare(strict_types=1);
+	
+	/**
+	 * @author: daniel
+	 * @date: 09.06.24
+	 * @time: 12:04
+	 */
+	
+	namespace wpsg;
+	
+?>
+
+<?php echo GiroPayPalAPI::getInstance()->integrateSDK(); ?>
+
+<script>
+ 
+	paypal.PaymentFields({
+		fundingSource: paypal.FUNDING.GIROPAY,
+		style: {
+			variables: {},
+			rules: {},
+		},
+		onInit: (data, actions) => {
+		
+			const form = document.querySelector("form#wpsg_checkout_form");
+		 
+			form.addEventListener("submit", (e) => {
+				
+				const formData = new FormData(form);
+				const paymentSource = formData.get("wpsg[checkout][payment]");
+				
+				if (paymentSource === '10_1') {
+
+					e.preventDefault();
+
+					actions.validate().then((valid) => {
+
+						if (valid) {
+
+							window.location.href = '<?php echo $this->getUrl(\wpsg_ShopController::URL_BASKET, 'wpsg_mod_paypalapi', 'giroPaySet', [], true); ?>';
+
+						}
+
+					});
+
+				}
+
+			});
+
+		},
+		fields: {
+			name: {
+				value: "<?php echo ($_SESSION['wpsg']['checkout']['vname']??'').' '.($_SESSION['wpsg']['checkout']['name']??''); ?>"
+			},
+		},
+	}).render("#giropay-container");
+	
+</script>
Index: /views/mods/mod_paypalapi/gfx/giropay.svg
===================================================================
--- /views/mods/mod_paypalapi/gfx/giropay.svg	(revision 8420)
+++ /views/mods/mod_paypalapi/gfx/giropay.svg	(revision 8420)
@@ -0,0 +1,1 @@
+<svg width="75px" height="32px" viewBox="0 0 75 32" version="1.1" xmlns="http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg"><title>logo giropay</title><desc>Created with Sketch.</desc><defs><polygon id="path-1" points="0 0.017902439 46.0918699 0.017902439 46.0918699 19.8373984 0 19.8373984"></polygon><polygon id="path-3" points="0 19.9821138 46.0918699 19.9821138 46.0918699 0.162601626 0 0.162601626"></polygon></defs><g id="SPB_&amp;_AltPay_NewAssets" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="matrix(1.614571, 0, 0, 1.614571, -161.457123, -256.816772)"><g id="logo-giropay" transform="translate(100.000000, 159.000000)"><g id="Group-3" transform="translate(0.000000, 0.144715)"><mask id="mask-2" fill="#FFFFFF"><polygon points="0 0.017902439 46.0918699 0.017902439 46.0918699 19.8373984 0 19.8373984" transform="matrix(1, 0, 0, 1, 0, 0)"></polygon></mask><g id="Clip-2"></g><path d="M0,3.5735122 C0,1.60977236 1.5998374,0.017902439 3.57349593,0.017902439 L42.5182114,0.017902439 C44.4918699,0.017902439 46.0918699,1.60977236 46.0918699,3.5735122 L46.0918699,16.2818049 C46.0918699,18.2455447 44.4918699,19.8374146 42.5182114,19.8374146 L3.57349593,19.8374146 C1.5998374,19.8374146 0,18.2455447 0,16.2818049 L0,3.5735122 Z" id="Fill-1" fill="#003a7d" mask="url(#mask-2)"></path></g><path d="M1.46738211,3.84021138 L1.46738211,16.3065528 C1.46738211,17.5338699 2.46738211,18.5288293 3.70087805,18.5288293 L24.1036423,18.5288293 L24.1036423,1.61793496 L3.70087805,1.61793496 C2.46738211,1.61793496 1.46738211,2.61289431 1.46738211,3.84021138" id="Fill-4" fill="#ffffff"></path><path d="M5.67102439,10.0205528 C5.67102439,9.34152846 6.00582114,8.77730081 6.64663415,8.77730081 C7.42126829,8.77730081 7.74630894,9.39892683 7.74630894,9.95356098 C7.74630894,10.7187642 7.25866667,11.235187 6.64663415,11.235187 C6.13004878,11.235187 5.67102439,10.795187 5.67102439,10.0205528 Z M9.41053659,7.57226016 L7.8515122,7.57226016 L7.8515122,8.47112195 L7.8324878,8.47112195 C7.46907317,7.85908943 6.87606504,7.45746341 6.13964228,7.45746341 C4.59021138,7.45746341 3.8921626,8.56689431 3.8921626,10.0491707 C3.8921626,11.5221789 4.74321951,12.5550244 6.11102439,12.5550244 C6.79964228,12.5550244 7.37346341,12.2872195 7.78468293,11.7038049 L7.80370732,11.7038049 L7.80370732,11.9716098 C7.80370732,12.9472195 7.26826016,13.4158374 6.27362602,13.4158374 C5.55622764,13.4158374 5.11622764,13.2628293 4.59021138,13.0046179 L4.50419512,14.3626667 C4.90582114,14.5060813 5.58500813,14.6782764 6.40744715,14.6782764 C8.41590244,14.6782764 9.41053659,14.0182764 9.41053659,11.9716098 L9.41053659,7.57226016 Z" id="Fill-6" fill="#ED1C24"></path><mask id="mask-4" fill="#FFFFFF"><polygon points="0 19.9821138 46.0918699 19.9821138 46.0918699 0.162601626 0 0.162601626" transform="matrix(1, 0, 0, 1, 0, 0)"></polygon></mask><g id="Clip-9"></g><path d="M10.5985366,6.75918699 L12.32,6.75918699 L12.32,5.49674797 L10.5985366,5.49674797 L10.5985366,6.75918699 Z M10.598374,12.5549593 L12.3198374,12.5549593 L12.3198374,7.57219512 L10.598374,7.57219512 L10.598374,12.5549593 Z" id="Fill-8" fill="#ED1C24" mask="url(#mask-4)"></path><path d="M17.1133333,7.51479675 C16.9411382,7.48617886 16.7307317,7.45739837 16.5299187,7.45739837 C15.7839024,7.45739837 15.3534959,7.85918699 15.0570732,8.4904065 L15.0380488,8.4904065 L15.0380488,7.57219512 L13.4694309,7.57219512 L13.4694309,12.5549593 L15.1910569,12.5549593 L15.1910569,10.4508943 C15.1910569,9.47544715 15.6404878,8.89203252 16.4439024,8.89203252 C16.6447154,8.89203252 16.835935,8.89203252 17.0273171,8.94943089 L17.1133333,7.51479675 Z" id="Fill-10" fill="#ED1C24" mask="url(#mask-4)"></path><path d="M20.0773496,11.4647154 C19.2835285,11.4647154 18.9583252,10.804878 18.9583252,10.0682927 C18.9583252,9.32243902 19.2835285,8.66243902 20.0773496,8.66243902 C20.8711707,8.66243902 21.196374,9.32243902 21.196374,10.0682927 C21.196374,10.804878 20.8711707,11.4647154 20.0773496,11.4647154 M20.0773496,12.6697561 C21.7223902,12.6697561 22.9752358,11.7133333 22.9752358,10.0682927 C22.9752358,8.41382114 21.7223902,7.45739837 20.0773496,7.45739837 C18.4323089,7.45739837 17.1794634,8.41382114 17.1794634,10.0682927 C17.1794634,11.7133333 18.4323089,12.6697561 20.0773496,12.6697561" id="Fill-11" fill="#ED1C24" mask="url(#mask-4)"></path><path d="M29.3757073,10.0110244 C29.3757073,10.8048455 28.9836748,11.3500488 28.3332683,11.3500488 C27.7594472,11.3500488 27.2812358,10.8048455 27.2812358,10.0780163 C27.2812358,9.332 27.7020488,8.77720325 28.3332683,8.77720325 C29.0026992,8.77720325 29.3757073,9.35102439 29.3757073,10.0110244 Z M25.5597724,14.5633821 L27.2812358,14.5633821 L27.2812358,11.856878 L27.3002602,11.856878 C27.6254634,12.4498862 28.2758699,12.6697236 28.8783089,12.6697236 C30.360748,12.6697236 31.1545691,11.4456585 31.1545691,9.97265041 C31.1545691,8.76777236 30.399122,7.45736585 29.0218862,7.45736585 C28.2376585,7.45736585 27.5106667,7.77297561 27.166439,8.47118699 L27.147252,8.47118699 L27.147252,7.5721626 L25.5597724,7.5721626 L25.5597724,14.5633821 Z" id="Fill-12" fill="#ffffff" mask="url(#mask-4)"></path><path d="M33.505252,10.9769919 C33.505252,10.4987805 33.9642764,10.3169919 34.5476911,10.3169919 C34.8059024,10.3169919 35.0546829,10.3361789 35.2745203,10.3457724 C35.2745203,10.929187 34.8633008,11.5221951 34.2128943,11.5221951 C33.8112683,11.5221951 33.505252,11.3212195 33.505252,10.9769919 Z M36.9769593,12.5550407 C36.900374,12.1628455 36.8813496,11.770813 36.8813496,11.3786179 L36.8813496,9.52317073 C36.8813496,8.00252033 35.7815122,7.45747967 34.5094797,7.45747967 C33.7730569,7.45747967 33.1322439,7.56268293 32.5202114,7.81130081 L32.5488293,8.97813008 C33.0270407,8.7103252 33.5816748,8.60512195 34.1364715,8.60512195 C34.7580976,8.60512195 35.2650894,8.78674797 35.2745203,9.46577236 C35.0546829,9.42756098 34.7485041,9.39894309 34.4711057,9.39894309 C33.5530569,9.39894309 31.8984228,9.58056911 31.8984228,11.1013821 C31.8984228,12.1820325 32.7784228,12.6698374 33.7634634,12.6698374 C34.4711057,12.6698374 34.9493171,12.392439 35.3415122,11.770813 L35.3606992,11.770813 C35.3606992,12.0290244 35.3893171,12.2872358 35.3989106,12.5550407 L36.9769593,12.5550407 Z" id="Fill-13" fill="#ffffff" mask="url(#mask-4)"></path><path d="M37.750748,14.5634634 C38.1045691,14.6398862 38.4583902,14.6782602 38.8218049,14.6782602 C40.3998537,14.6782602 40.7728618,13.463626 41.2606667,12.2107805 L43.0873333,7.5722439 L41.3658699,7.5722439 L40.3426179,10.8239512 L40.3234309,10.8239512 L39.2522114,7.5722439 L37.3967642,7.5722439 L39.414813,12.6698049 C39.2904228,13.1098049 38.9653821,13.3584228 38.5445691,13.3584228 C38.3053821,13.3584228 38.0949756,13.3296423 37.8655447,13.2532195 L37.750748,14.5634634 Z" id="Fill-14" fill="#ffffff" mask="url(#mask-4)"></path></g></g></svg>
Index: /views/mods/mod_paypalapi/order_done_giropay.phtml
===================================================================
--- /views/mods/mod_paypalapi/order_done_giropay.phtml	(revision 8420)
+++ /views/mods/mod_paypalapi/order_done_giropay.phtml	(revision 8420)
@@ -0,0 +1,80 @@
+<?php
+	
+	declare(strict_types=1);
+	
+	/**
+	 * @author: daniel
+	 * @date: 13.06.24
+	 * @time: 14:59
+	 */
+	
+	namespace wpsg;
+	
+	$client_id = $this->view['clientId']??null;
+
+?>
+
+<?php if ($client_id !== null) { ?>
+	
+	<p><?php echo __('Die Bestellung ist bei uns eingegangen, bitte begleichen Sie den offenen Betrag mit giroPAy ÃŒber folgenden Button.', 'wpsg'); ?></p>
+	
+	<script src="https://sandbox.paypal.com/sdk/js?client-id=<?php echo $client_id; ?>&components=buttons,payment-fields,marks,funding-eligibility&enable-funding=giropay&currency=EUR"></script>
+	
+	<div id="giropay-btn"></div>
+	
+	<script>
+		
+		paypal.Buttons({
+			fundingSource: paypal.FUNDING.GIROPAY,
+			style: {
+				label: "pay",
+			},
+			createOrder() {
+				return fetch('<?php echo $this->getUrl(\wpsg_ShopController::URL_BASKET, 'wpsg_mod_paypalapi', 'giroPayCreateOrder', [
+					'order_id' => intval($_REQUEST['order_id'])
+				], true); ?>', {
+					method: "POST",
+					headers: {
+						"Content-Type": "application/json",
+					},
+					body: null,
+				})
+				.then((response) => response.json())
+				.then((order) => order.id);
+			},
+			onApprove(data) {
+
+				console.log('onApprove');
+				
+				return fetch('<?php echo $this->getUrl(\wpsg_ShopController::URL_BASKET, 'wpsg_mod_paypalapi', 'giroPayApproveOrder', [], true); ?>', {
+					method: "POST",
+					headers: {
+						"Content-Type": "application/json",
+					},
+					body: JSON.stringify({
+						orderID: data.orderID
+					})
+				})
+				.then((response) => response.json())
+				.then((orderData) => {
+					
+					//console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
+					//const transaction = orderData.purchase_units[0].payments.captures[0];
+					//console.log('Transaction Status:',	transaction.status);
+					//console.log('Transaction ID:', transaction.id);
+					
+					location.reload();
+					
+				});
+			},
+			onCancel(data, actions) { console.log(`Order Canceled - ID: ${data.orderID}`); },
+			onError(err) { console.error(err); }
+		}).render("#giropay-btn");
+		
+	</script>
+	
+<?php } else { ?>
+
+	<p><?php echo __('Den Betrag haben wir mittels GiroPay dankend erhalten.', 'wpsg'); ?></p>
+
+<?php }
Index: /views/mods/mod_paypalapi/overview_button.phtml
===================================================================
--- /views/mods/mod_paypalapi/overview_button.phtml	(revision 8420)
+++ /views/mods/mod_paypalapi/overview_button.phtml	(revision 8420)
@@ -0,0 +1,89 @@
+<?php
+	
+	declare(strict_types=1);
+	
+	/**
+	 * @author: daniel
+	 * @date: 13.06.24
+	 * @time: 14:48
+	 */
+	
+	namespace wpsg;
+	
+?>
+
+<?php GiroPayPalAPI::getInstance()->integrateSDK(); ?>
+
+
+<div style="width:300px; float:right;">
+	<div id="giropay-btn"></div>
+</div>
+
+<script>
+	
+	paypal.Buttons({
+		fundingSource: paypal.FUNDING.GIROPAY,
+		style: {
+			label: "pay",
+		},
+		createOrder() {
+			
+			const form = document.getElementById('wpsg_form');
+   
+			if (form.checkValidity()) {
+			
+				return fetch("<?php echo $this->getUrl(\wpsg_ShopController::URL_BASKET, 'wpsg_mod_paypalapi', 'giroPayCreateOrderFromSession', [ ], true); ?>", {
+					method: "POST",
+					headers: {
+						"Content-Type": "application/json",
+					},
+					body: { }
+				})
+				.then((response) => response.json())
+				.then((order) => order.id);
+
+			} else form.reportValidity();
+			
+		},
+		onApprove(data) {
+			
+			return fetch("<?php echo $this->getUrl(\wpsg_ShopController::URL_BASKET, 'wpsg_mod_paypalapi', 'giroPayApproveOrder', [], true); ?>", {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json",
+				},
+				body: JSON.stringify({
+					orderID: data.orderID
+				})
+			})
+				.then((response) => response.json())
+				.then((orderData) => {
+				
+					location.href = orderData.order_done_url;
+					
+					// Successful capture! For dev/demo purposes:
+					// console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
+					
+					// const transaction = orderData.order_paypal.purchase_units[0].payments.captures[0];
+					
+					//console.log('Transaction Status:',	transaction.status);
+					//console.log('Transaction ID:', transaction.id);
+					
+			});
+			
+		},
+		onCancel(data, actions) {
+ 
+			console.log(`Order Canceled - ID: ${data.orderID}`);
+			
+			location.href = '<?php echo $this->getUrl(\wpsg_ShopController::URL_BASKET, 'wpsg_mod_paypalapi', 'giroPayCancelOrder', [], true); ?>';
+			
+		},
+		onError(err) {
+			
+			<?php if ($GLOBALS['wpsg_sc']->get_option('wpsg_debugModus') === "1") { ?>console.error(err);<?php } ?>
+			
+		}
+	}).render("#giropay-btn");
+	
+</script>
Index: /views/mods/mod_paypalapi/settings_edit.phtml
===================================================================
--- /views/mods/mod_paypalapi/settings_edit.phtml	(revision 8419)
+++ /views/mods/mod_paypalapi/settings_edit.phtml	(revision 8420)
@@ -60,4 +60,10 @@
 <?php echo wpsg_drawForm_Checkbox('wpsg_mod_paypalapi_aktiv', __('PayPal fÃŒr neue Bestellungen erlauben', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_aktiv'), array('help' => 'wpsg_shippay_activ')); ?>
 <?php echo wpsg_drawForm_Checkbox('wpsg_mod_paypalapi_plus_aktiv', __('PayPal Plus aktivieren', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_plus_aktiv'), array('help' => 'wpsg_mod_paypalapi_plus_aktiv')); ?>
+<?php echo wpsg_drawForm_Checkbox('wpsg_mod_paypalapi_giropay_aktiv', __('GiroPay aktivieren', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_giropay_aktiv'), array('help' => 'wpsg_mod_paypalapi_giropay_aktiv ')); ?>
+
+<div id="wpsg_mod_paypalapi_giropay_aktiv_layer">
+	<?php echo wpsg_drawForm_Input('wpsg_mod_paypalapi_giropay_name', __('Bezeichnung (GiroPay)', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_giropay_name'), array('help' => 'wpsg_shippay_name')); ?>
+	<?php echo wpsg_drawForm_Textarea('wpsg_mod_paypalapi_giropay_hint', __('Hinweistext (GiroPay)', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_giropay_hint'), array('help' => 'wpsg_shippay_hint')); ?>
+</div>
 
 <div class="wpsg_mod_paypalapi_plus_aktiv_layer wpsg_mod_paypalapi_plus_aktiv_layer_1">
@@ -86,5 +92,7 @@
 
 <br />
+
 <?php echo wpsg_drawForm_Input('wpsg_mod_paypalapi_experience_label', __('Bezeichnung im PayPal Zahlungsinterface', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_experience_label'), array('help' => 'wpsg_mod_paypalapi_experience_label', 'hint' => __('Max. 127 Zeichen', 'wpsg'))); ?>
+
 <?php echo wpsg_drawForm_Input('wpsg_mod_paypalapi_experience_logo', __('URL fÃŒr ein Logo im PayPal Zahlungsinterface', 'wpsg'), $this->get_option('wpsg_mod_paypalapi_experience_logo'), array('help' => 'wpsg_mod_paypalapi_experience_logo', 'hint' => __('gif|png|jpg, Max. 190x60', 'wpsg'))); ?>
 <?php echo wpsg_drawForm_Select('wpsg_mod_paypalapi_experience_locale_code', __('Sprache im PayPal Zahlungsinterface', 'wpsg'), array(
@@ -175,3 +183,39 @@
 	} );
 
+	const el_wpsg_mod_paypalapi_plus_aktiv = document.getElementById('wpsg_mod_paypalapi_plus_aktiv');
+	const el_wpsg_mod_paypalapi_giropay_aktiv = document.getElementById('wpsg_mod_paypalapi_giropay_aktiv');
+	const el_wpsg_mod_paypalapi_giropay_aktiv_layer = document.getElementById('wpsg_mod_paypalapi_giropay_aktiv_layer');
+	
+	el_wpsg_mod_paypalapi_plus_aktiv.addEventListener('change', event => {
+		
+		if (el_wpsg_mod_paypalapi_plus_aktiv.checked) {
+		
+			el_wpsg_mod_paypalapi_giropay_aktiv.disabled = true;
+			el_wpsg_mod_paypalapi_giropay_aktiv.checked = false;
+		
+		} else {
+		
+			el_wpsg_mod_paypalapi_giropay_aktiv.disabled = false;
+		
+		}
+		
+	});
+	
+	el_wpsg_mod_paypalapi_giropay_aktiv.addEventListener('change', event => {
+	 
+		if (el_wpsg_mod_paypalapi_giropay_aktiv.checked) {
+		
+			el_wpsg_mod_paypalapi_giropay_aktiv_layer.style.display = 'block';
+			
+		} else {
+		
+			el_wpsg_mod_paypalapi_giropay_aktiv_layer.style.display = 'none';
+			
+		}
+		
+	});
+	
+	el_wpsg_mod_paypalapi_plus_aktiv.dispatchEvent(new Event('change'));
+	el_wpsg_mod_paypalapi_giropay_aktiv.dispatchEvent(new Event('change'));
+	
 </script>
Index: /views/mods/mod_productvariants/settings_edit.phtml
===================================================================
--- /views/mods/mod_productvariants/settings_edit.phtml	(revision 8419)
+++ /views/mods/mod_productvariants/settings_edit.phtml	(revision 8420)
@@ -23,4 +23,7 @@
 			<?php echo wpsg_drawForm_Checkbox('wpsg_vp_detailview', __('Erweiterte Variantenanzeige aktivieren', 'wpsg'), $this->get_option('wpsg_vp_detailview'), array('help' => 'wpsg_vp_detailview')); ?>
 			<?php echo wpsg_drawForm_Checkbox('wpsg_vp_replaceanr', __('Produktartikelnummer ersetzen', 'wpsg'), $this->get_option('wpsg_vp_replaceanr'), array('hint' => __('Ersetzt die Produktartikelnummer durch die zusammengesetzte Artikelnummern der Varianten.', 'wpsg'), 'help' => 'wpsg_vp_replaceanr')); ?>
+			<?php echo wpsg_drawForm_Checkbox('wpsg_vp_product_stock_nocalc', __('Produktbestand nach Kauf nicht berechnen', 'wpsg'), $this->get_option('wpsg_vp_product_stock_nocalc'), [
+				'hint' => __('Ist diese Option aktiv, so bildet sich der Bestand im Produkt bei Variantenprodukten nicht aus den Varianten neu sondern wird einfach reduziert.', 'wpsg'), 'help' => 'wpsg_vp_product_stock_nocalc'
+			]); ?>
 			
 		</div>
Index: /views/produkttemplates/standard.phtml
===================================================================
--- /views/produkttemplates/standard.phtml	(revision 8419)
+++ /views/produkttemplates/standard.phtml	(revision 8420)
@@ -292,5 +292,5 @@
 		
 		<?php $this->callMods('product_bottom', array(&$this->view['data']['product_key'], $this->getTemplateIndex())); ?>
-		
+ 
 		<div class="wpsg_product_buttonarea">			
 			<div class="wpsg_product_buttons_add_basket">
@@ -301,5 +301,5 @@
 
 					<input type="text" value="<?php echo ((wpsg_isSizedInt($this->view['data']['menge']))?$this->view['data']['menge']:'1'); ?>" name="wpsg[menge]" class="wpsg_menge" id="wpsg_menge_<?php echo $this->getTemplateIndex(); ?>" <?php echo (($this->view['data']['basket_multiple'] == 4)?' readonly="readonly" ':''); ?> />
-							
+					
 					<?php /* Urlaubsmodus aktiv */ ?>
 					<?php if ($this->hasMod('wpsg_mod_deliverytime') && ($this->get_option('wpsg_mod_deliverytime_holiday') == '1')) { ?>
Index: /views/warenkorb/overview.phtml
===================================================================
--- /views/warenkorb/overview.phtml	(revision 8419)
+++ /views/warenkorb/overview.phtml	(revision 8420)
@@ -19,5 +19,5 @@
 	<?php } ?>
 
-	<form method="post" action="<?php echo $this->getURL(wpsg_ShopController::URL_BASKET); ?>">
+	<form method="post" action="<?php echo $this->getURL(wpsg_ShopController::URL_BASKET); ?>" id="wpsg_form">
 
 		<h2><?php echo __('Zusammenfassung', 'wpsg'); ?></h2>
@@ -28,5 +28,5 @@
 		<div class="wpsg_agb">
 			<label>
-				<input type="checkbox" value="1" name="wpsg[agb]" />
+				<input type="checkbox" value="1" name="wpsg[agb]" required />
 				<?php echo wpsg_translate(__('Ich habe die #1# des Anbieters gelesen und erklÃ€re mit dem Absenden der Bestellung mein EinverstÃ€ndnis. Die #2# habe ich zur Kenntnis genommen.', 'wpsg'),
 					'<a target="_blank" href="'.$this->getURL(wpsg_ShopController::URL_AGB).'">'.__('AGB', 'wpsg').'</a>',
