base_object = $base_object; $this->layout_object = $layout_object; $this->errors = array(); } // end __construct public function run() { // SUCCESS-Seite nach REDIRECT - verhindert Doppel-Bestellungen if (isset($_GET['success']) && $_GET['success'] == '1') { // Bestellnummer aus URL für Template verfügbar machen if (isset($_GET['order'])) { $this->layout_object->assign('order_number', $_GET['order']); } // Success-Template anzeigen - auch bei Reload! return $this->layout_object->_fetch('content_cash_desk/completed.tpl'); } // 13.09.2026 -- Rueckkehr von einem Zahlungsanbieter aus core/payments // (?payment_return= bzw. ?payment_cancel=). Laeuft hier im Cashdesk, // weil die Session gebraucht wird (Pending-Riegel, Warenkorb). Steht vor dem // Kunden-/Login-Check: nach dem Sprung ueber den Anbieter ist kein ?guest_order=1 // mehr in der URL, ein Gast haette sonst keine customer_id. if (isset($_GET['payment_return']) || isset($_GET['payment_cancel'])) { return $this->payment_provider_return(); } // 14.09.2026 -- Express aus dem Warenkorb ("Direkt mit PayPal bezahlen"). // Start: Vorgang beim Anbieter ohne Bestellung, Kunde geht hin und gibt frei. // Rueckkehr: Adresse/E-Mail vom Anbieter in die Session, weiter in die Gast-Kasse, // dort ist alles vorbelegt -- ein Klick auf "zahlungspflichtig bestellen" zieht ein. if (isset($_GET['payment_express_start'])) { return $this->payment_express_start(); } if (isset($_GET['payment_express'])) { return $this->payment_express_return(); } // 24.08.2026 -- Zielseite des Post/Redirect/Get aus PaypalPaymentMethod::after_order(). // Reines GET: hier steht der Link zu PayPal, ein Reload laedt nur noch ihn neu und // legt keine zweite Bestellung mehr an. // Steht bewusst vor dem Kunden-/Login-Check: nach dem Redirect ist kein // ?guest_order=1 mehr in der URL, ein Gast haette hier sonst keine customer_id. if (isset($_GET['pp_open'])) { $pp_token = trim((string)$_GET['pp_open']); if (preg_match('/^EC-[A-Z0-9]{10,30}$/i', $pp_token)) { $pp_sandbox = (class_exists('Config') && Config::get_value('paypal_dev_mode') == 1); $pp_host = $pp_sandbox ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com'; $this->layout_object->assign('call', 'payment_open'); $this->layout_object->assign('paypal_link', $pp_host.'/cgi-bin/webscr?cmd=_express-checkout&token='.urlencode($pp_token)); } else { $this->layout_object->assign('call', 'payment_error'); $this->layout_object->assign('message', 'Der PayPal-Vorgang konnte nicht zugeordnet werden. Ihr Warenkorb ist noch da – bitte starten Sie die Bestellung neu.'); } return $this->layout_object->_fetch('content_cash_desk/paypal.tpl'); } if (isset($_GET['guest_order']) && $_GET['guest_order'] == 1) { // guest order $this->base_object->customer->customer_id = $this->base_object->config->shopConfiguration['guest_order_customer_id']; $this->layout_object->assign('guest_order', 1); } $this->payment_express_prefill(); $action = ''; if ($this->base_object->customer->customer_id) { if (isset($_POST['cashdesk_action'])) { $action = $_POST['cashdesk_action']; } else if (isset($_POST['save_address'])) { $action = 'save_address'; } else if (isset($_GET['token'])) { $action = 'change_order'; $param = 'paypal'; } $this->personal_data = $_POST; if ($action == 'ready2fly') { // MAKE ORDER $this->setup_countries(); $this->setup_customer_data(); if (isset($_GET['guest_order']) && $_GET['guest_order'] == 1) { $this->manage_guest_order_address(); } else { $this->manage_address(); } $this->manage_shoppingcart(); $this->manage_payment_method(); $this->manage_shipping_method(); $this->manage_gift_certificate(); $this->managa_conditions(); $this->before_saving_order(); if (count($this->errors) == 0) { if (isset($_GET['guest_order']) && $_GET['guest_order'] == 1) { $this->setup_customer_data(); // check customer already exists by email $customer_id = 0;//$this->base_object->customer->check_email($_POST['billing']['email']); if (!$customer_id) { // create new customer for guest order $customer_data = array ( 'honorific' => $_POST['billing']['honorific'], 'company' => $_POST['billing']['company'], 'firstname' => $_POST['billing']['firstname'], 'surname' => $_POST['billing']['surname'], 'phone' => $_POST['billing']['phone'], 'group_id' => $this->base_object->config->shopConfiguration['guest_order_customer_group'], 'email' => $_POST['billing']['email'] ); $result = $this->base_object->customer->create($customer_data); } else { $result = $customer_id; } if ($result) { $this->customer_data->id = $customer_id = $this->base_object->customer->customer_id = $result; include_once './core/customeraddress.class.php'; $customer_address_object = new CustomerAddress($this->base_object); $billing_address = array ( 'honorific' => $_POST['billing']['honorific'], 'company' => $_POST['billing']['company'], 'firstname' => $_POST['billing']['firstname'], 'surname' => $_POST['billing']['surname'], 'street' => $_POST['billing']['street'], 'house_number' => $_POST['billing']['house_number'], 'city' => $_POST['billing']['city'], 'zip_code' => $_POST['billing']['zip_code'], 'country' => $_POST['billing']['country'], 'customer_id' => $customer_id ); $res = $customer_address_object->create($billing_address); $this->billing_address['address'] = $res; $shipping_address = array ( 'honorific' => $_POST['shipping']['honorific'], 'company' => $_POST['shipping']['company'], 'firstname' => $_POST['shipping']['firstname'], 'surname' => $_POST['shipping']['surname'], 'street' => $_POST['shipping']['street'], 'house_number' => $_POST['shipping']['house_number'], 'city' => $_POST['shipping']['city'], 'zip_code' => $_POST['shipping']['zip_code'], 'country' => $_POST['shipping']['country'], 'customer_id' => $customer_id ); $res = $customer_address_object->create($shipping_address); $this->shipping_address['address'] = $res; } } if (isset($_POST['billing']['email'])) { $data = array( 'id' => $this->base_object->customer->customer_id, 'email' => $_POST['billing']['email'] ); $this->base_object->customer->save($data); } // 25.08.2026 -- Stufe 4: ein Bestellvorgang, eine Order. // Traegt dieser POST einen Nonce, der schon zu einer Bestellung gefuehrt // hat, ist es derselbe Vorgang -- egal ob eine Sekunde oder eine Stunde // vergangen ist. Dann zurueck auf das Ergebnis von damals, statt ein // zweites Mal zu bestellen. $erledigt = $this->bestellvorgang_bereits_erledigt(); if ($erledigt) { $offen = isset($_SESSION['easyshop']['paypal_pending']) ? $_SESSION['easyshop']['paypal_pending'] : null; // Laeuft zu dieser Order noch ein unbezahlter PayPal-Vorgang, // gehoert der Kunde dorthin und nicht auf die Erfolgsseite. if ($offen && !empty($offen['token']) && (int)$offen['order_id'] === (int)$erledigt['order_id'] && !$this->paypal_order_is_paid($erledigt['order_id'])) { error_log('Checkout: Vorgang '.$erledigt['order_number'] . ' bereits bestellt, zurueck auf offenen PayPal-Vorgang ' . $offen['token']); header('Location: /Kasse/?pp_open='.urlencode($offen['token'])); exit; } // 13.09.2026: dasselbe fuer Anbieter aus core/payments -- zurueck zur // Approve-URL des offenen Vorgangs statt einer zweiten Bestellung. $offen_pv = $this->payment_provider_pending(); if ($offen_pv && (int)$offen_pv['order_id'] === (int)$erledigt['order_id']) { error_log('Checkout: Vorgang '.$erledigt['order_number'] . ' bereits bestellt, zurueck auf offenen '.$offen_pv['module'].'-Vorgang'); header('Location: '.$offen_pv['approve_url']); exit; } error_log('Checkout: Vorgang bereits bestellt (Order ' . $erledigt['order_number'].') -- keine zweite Order angelegt'); if (function_exists('shop_event')) { shop_event('duplicate_blocked', array( 'order_id' => $erledigt['order_id'], 'order_number' => $erledigt['order_number'], 'betrag' => $erledigt['total'], 'detail' => 'zweiter Bestell-POST desselben Vorgangs' )); } header('Location: /Kasse/?success=1&order='.urlencode($erledigt['order_number'])); exit; } // 24.08.2026 -- zweiter Riegel gegen Doppelbestellungen bei PayPal. // Kommt trotz PRG noch einmal ein ready2fly-POST (Zurueck-Taste, // zweiter Tab, ungeduldiges Klicken) und liegt zu dieser Session // bereits ein PayPal-Vorgang ueber denselben Betrag offen, der noch // nicht bezahlt ist: zurueck auf dessen Link, statt eine zweite Order // anzulegen. Ist der Vorgang bezahlt oder aelter als eine Stunde, // laeuft alles normal weiter -- dann ist es ein neuer Einkauf. $pending = isset($_SESSION['easyshop']['paypal_pending']) ? $_SESSION['easyshop']['paypal_pending'] : null; $pending_module = isset($this->payment_methods[$this->payment_method_id]->module) ? strtolower((string)$this->payment_methods[$this->payment_method_id]->module) : ''; if ($pending && !empty($pending['token']) && $pending_module === 'paypal' && abs(round((float)$this->shoppingcart_data['order_total'], 2) - (float)$pending['total']) < 0.01 && (time() - (int)(isset($pending['time']) ? $pending['time'] : 0)) < 3600 && !$this->paypal_order_is_paid($pending['order_id'])) { error_log("PayPal Fix: doppelter ready2fly-POST abgefangen, zurueck auf offenen Vorgang {$pending['token']}"); header('Location: /Kasse/?pp_open='.urlencode($pending['token'])); exit; } // 13.09.2026: derselbe Riegel fuer Anbieter aus core/payments. Der offene // Vorgang liegt in $_SESSION['easyshop']['payment_pending'] (gesetzt vom // Provider beim Start) -- gleiche Zahlart, gleicher Betrag, juenger als // eine Stunde, nicht bezahlt: zurueck zur Approve-URL. $pending_pv = $this->payment_provider_pending(); if ($pending_pv && $pending_pv['module'] === $pending_module && abs(round((float)$this->shoppingcart_data['order_total'], 2) - (float)$pending_pv['total']) < 0.01) { error_log('Payments: doppelter ready2fly-POST abgefangen, zurueck auf offenen ' . $pending_pv['module'].'-Vorgang '.$pending_pv['reference']); header('Location: '.$pending_pv['approve_url']); exit; } $is_saved = $this->save_order(); if ($is_saved) { return $this->after_saving_order(); } } } else if ($action == 'change_order') { return $this->change_order($param); } else if ($action == 'save_address') { // SAVE ADDRESS $this->setup_customer_data(); $this->save_address(); } else { // SHOW ORDER $this->setup_countries(); $this->setup_customer_data(); if (isset($_GET['guest_order']) && $_GET['guest_order'] == 1) { $this->manage_guest_order_address(); } else { $this->manage_address(); } $this->manage_shoppingcart(); $this->manage_payment_method(); $this->manage_shipping_method(); $this->manage_gift_certificate(); $this->managa_conditions(); } //echo var_dump($this->personal_data); $this->manage_errors(); $this->manage_data_persistence(); // customer info -- 18.09.2026: in der Kasse editierbar, ein POST (auch bei // Fehlern im Formular) aktualisiert die Session, die Bestellung nimmt $_POST (s.u.) if (isset($_POST['customer_info'])) { $_SESSION['customer_info'] = trim((string)$_POST['customer_info']); } if (isset($_SESSION['customer_info']) && $_SESSION['customer_info']) { $this->layout_object->assign('customer_info', $_SESSION['customer_info']); } if (!$this->base_object->config->shopConfiguration['menu_in_cashdesk']) { $this->layout_object->assign('site_type', 'full_width'); } // 25.08.2026: Nonce fuers Kassenformular -- macht einen zweiten Klick auf // "Bestellen" als denselben Vorgang erkennbar (siehe save_order()). $this->layout_object->assign('checkout_nonce', $this->checkout_nonce_holen()); // 18.09.2026 -- Entwurf der neuen Kassenansicht zum Anschauen: /Kasse/?ansicht=neu if (isset($_GET['ansicht']) && $_GET['ansicht'] === 'neu') { return $this->layout_object->_fetch('content_cash_desk_neu.tpl'); } return $this->layout_object->_fetch('content_cash_desk.tpl'); } else if (isset($_GET['token']) && $_GET['token']) { return $this->change_order('paypal'); } else { // login $_SESSION['easyshop']['shopAction'] = 'cash_desk_customer_registration'; $this->layout_object->assign('go_to_cash_desk', 1); return $this->layout_object->_fetch('content_login.tpl'); } } // end run // ------------------------------------------------------------ Express /** Start aus dem Warenkorb: /Kasse/?payment_express_start= */ private function payment_express_start() { $module = strtolower(trim((string)$_GET['payment_express_start'])); $provider = $this->payment_provider($module); if (!$provider || !$provider->supportsExpress()) { header('Location: /Warenkorb/'); exit; } $shoppingcart_object = new Shoppingcart($this->base_object); $cart = $shoppingcart_object->get_data(); if (empty($cart['items']) || (float)$cart['price_sum_gross'] <= 0) { header('Location: /Warenkorb/'); exit; } try { $url = $provider->startExpress($this->payment_express_amount($cart, $provider)); } catch (Throwable $e) { error_log('Payments express start '.$module.': '.$e->getMessage()); $this->layout_object->assign('call', 'payment_error'); $this->layout_object->assign('provider_label', $provider->label()); $this->layout_object->assign('message', $provider->label().' konnte nicht gestartet werden. Ihr Warenkorb ist noch da – bitte gehen Sie normal zur Kasse.'); return $this->layout_object->_fetch('content_cash_desk/payment_result.tpl'); } header('Location: '.$url); exit; } /** * 18.09.2026 -- Endbetrag fuer den Express-Start vorab rechnen: Warenwert + Versand + * Zahlartaufschlag, genau so, wie die Kasse ihn spaeter vorbelegt (erste aktive Versandart * des Gebiets, Gast: Gebiet 1 wie in manage_guest_payment_method). PayPal Live verlangt * bei einer Betragsaenderung nach der Freigabe eine zweite Freigabe (422 * PAYER_ACTION_REQUIRED); stimmt der Betrag schon, entfaellt der PATCH und damit der * zweite PayPal-Besuch. Land: erstes erlaubtes Express-Land (Setting, Standard DE). * Im Zweifel (Fehler, keine Versandart) bleibt es beim Warenwert -- dann greift der * Rueckfall mit zweiter Freigabe wie bisher. */ private function payment_express_amount($cart, $provider) { $amount = round((float)$cart['price_sum_gross'], 2); try { $guest_group = (int)$this->base_object->config->shopConfiguration['guest_order_customer_group']; $guest_id = (int)$this->base_object->config->shopConfiguration['guest_order_customer_id']; $cid = (int)$this->base_object->customer->customer_id; $logged_in = $cid > 0 && $cid !== $guest_id; $group_id = $guest_group; $area_id = 1; if ($logged_in) { $customer_object = new Customer($this->base_object); $customer_data = $customer_object->get_by_id($cid); $group_id = (int)$customer_data->group_id; $allowed = array_filter(array_map('trim', explode(',', strtoupper($provider->expressCountries())))); $iso = $allowed ? reset($allowed) : 'DE'; $country_id = $provider->countryIdByIso($iso); $area_id = $country_id > 0 ? (int)Country::getShippingAreaById($country_id, $group_id) : 0; if ($area_id <= 0) { return $amount; } } $deliverer = new Deliverer($this->base_object); $methods = $logged_in ? $deliverer->getAllActive($area_id, $cart['standard_shipping_items'], $cart['bulk_goods'], $cart['item_weight'], $cart['price_sum_gross']) : $deliverer->getAllActive($area_id, $cart['standard_shipping_items'], $cart['bulk_goods'], $cart['item_weight']); if (!is_array($methods) || !$methods) { return $amount; } $first = current($methods); $shipping = round((float)(isset($first->price_add) ? $first->price_add : 0), 2); $payment_add = 0.0; $payment_method_object = new Payment_method($this->base_object); $pms = $payment_method_object->getMethodsForGroup($group_id, $cart['price_sum_gross'], $area_id); foreach ((array)$pms as $pm) { if (isset($pm->module) && $pm->module === $provider->module()) { $payment_add = round((float)(isset($pm->paymentAdd) ? $pm->paymentAdd : 0), 2); break; } } return round($amount + $shipping + $payment_add, 2); } catch (Throwable $e) { error_log('Payments express amount: '.$e->getMessage()); return $amount; } } /** Rueckkehr im Express-Weg: /Kasse/?payment_express=&token=... */ private function payment_express_return() { $module = strtolower(trim((string)$_GET['payment_express'])); $provider = $this->payment_provider($module); $data = null; // 18.09.2026 -- zweite Freigabe: PayPal Live verlangt nach dem Betrags-PATCH (Versand) // eine erneute Freigabe, der Kunde kommt danach ueber DIESE URL zurueck. Die Bestellung // und der Vorgang existieren dann schon -> normaler Rueckweg mit Capture, nicht noch // einmal die Kasse vorbelegen (custom_id ist da auch nicht mehr 'express'). if ($provider && $provider->knownReference((string)($_GET['token'] ?? ''))) { $_GET['payment_return'] = $module; return $this->payment_provider_return(); } if ($provider && $provider->supportsExpress()) { try { $data = $provider->onExpressReturn($_GET); } catch (Throwable $e) { error_log('Payments express return '.$module.': '.$e->getMessage()); } } if (!$data) { $this->layout_object->assign('call', 'payment_error'); $this->layout_object->assign('provider_label', $provider ? $provider->label() : 'Zahlung'); $this->layout_object->assign('message', 'Der Express-Vorgang konnte nicht übernommen werden. Ihr Warenkorb ist noch da – bitte gehen Sie normal zur Kasse.'); return $this->layout_object->_fetch('content_cash_desk/payment_result.tpl'); } // Erlaubte Laender fuer Express (Setting _express_countries, Standard DE): // PayPal-Konten gibt es weltweit, geliefert wird aber nur, wohin der Shop liefert. $allowed = array_filter(array_map('trim', explode(',', strtoupper($provider->expressCountries())))); $iso = strtoupper((string)$data['country_iso']); $country_id = in_array($iso, $allowed, true) ? $provider->countryIdByIso($iso) : 0; if ($country_id <= 0) { $this->layout_object->assign('call', 'payment_error'); $this->layout_object->assign('provider_label', $provider->label()); $this->layout_object->assign('message', 'In das bei '.$provider->label().' hinterlegte Land ('.htmlspecialchars((string)$data['country_iso']).') liefern wir leider nicht. Bitte gehen Sie normal zur Kasse und wählen Sie eine Lieferadresse.'); return $this->layout_object->_fetch('content_cash_desk/payment_result.tpl'); } $data['module'] = $module; $data['country_id'] = $country_id; $data['time'] = time(); $_SESSION['easyshop']['payment_express'] = $data; // Eingeloggter Kunde: eigene Kasse mit eigenen Adressen, nur Zahlart vorbelegt. $logged_in = $this->base_object->customer->customer_id && (int)$this->base_object->customer->customer_id !== (int)$this->base_object->config->shopConfiguration['guest_order_customer_id']; header('Location: /Kasse/'.($logged_in ? '' : '?guest_order=1')); exit; } /** * Kasse mit den Express-Daten vorbelegen (Gast: Adresse + E-Mail + Zahlart, * eingeloggt: nur Zahlart). Nur beim reinen Anzeigen -- ein POST des Kunden gewinnt. */ private function payment_express_prefill() { $e = isset($_SESSION['easyshop']['payment_express']) ? $_SESSION['easyshop']['payment_express'] : null; if (!is_array($e) || empty($e['module']) || empty($e['reference'])) { return; } if ((time() - (int)$e['time']) > 3600) { unset($_SESSION['easyshop']['payment_express']); return; } $pm_id = 0; $res = $this->base_object->db->query("SELECT id FROM payment_methods WHERE module='".$this->base_object->db->real_escape_string($e['module'])."' AND active=1 LIMIT 1"); if ($res && ($r = $res->fetch_object())) { $pm_id = (int)$r->id; } $this->layout_object->assign('express_active', 1); $express_provider = $this->payment_provider($e['module']); $this->layout_object->assign('express_label', $express_provider ? $express_provider->label() : ucfirst(str_replace('_', ' ', $e['module']))); if (isset($_POST['cashdesk_action']) || isset($_POST['save_address'])) { return; // Kunde hat das Formular selbst abgeschickt } if ($pm_id) { $_POST['payment_method'] = $pm_id; } $is_guest = isset($_GET['guest_order']) && $_GET['guest_order'] == 1; if (!$is_guest) { return; } $addr = array( 'honorific' => '', 'company' => (string)$e['company'], 'firstname' => (string)$e['firstname'], 'surname' => (string)$e['surname'], 'street' => (string)$e['street'], 'house_number' => (string)$e['house_number'], 'zip_code' => (string)$e['zip_code'], 'city' => (string)$e['city'], 'country' => (int)$e['country_id'], ); $_POST['billing'] = $addr + array('email' => (string)$e['email'], 'address' => '', 'billing_address' => ''); $_POST['shipping'] = $addr + array('address' => -1); // -1 = wie Rechnungsadresse $this->personal_data = $_POST; } /** * 13.09.2026 -- Zahlungsanbieter aus core/payments. * * Bei diesen Zahlarten (und beim alten PayPal) ist die Bestellung erst nach der * Rueckkehr vom Anbieter bezahlt: Bestellbestaetigung und Warenkorb-Leerung warten * bis dahin. Alle anderen Zahlarten sind mit dem Abschicken verbindlich. */ private function zahlung_erst_spaeter($module) { $module = strtolower((string)$module); if ($module === 'paypal') { return true; // Legacy-NVP-PayPal, raeumt in PaypalPaymentMethod::change_order() selbst auf } $provider = $this->payment_provider($module); return $provider ? $provider->paysAfterRedirect() : false; } /** @return \Payments\ProviderInterface|null */ private function payment_provider($module) { if (!class_exists('\Payments\Registry') || !\Payments\Registry::has((string)$module)) { return null; } try { return \Payments\Registry::get((string)$module, $this->base_object); } catch (Throwable $e) { error_log('Payments: Provider '.$module.' nicht ladbar: '.$e->getMessage()); return null; } } /** * Offener, unbezahlter Vorgang eines Anbieters in dieser Session (juenger als 1 h), * sonst null. Grundlage der Riegel gegen Doppelbestellungen. */ private function payment_provider_pending() { $p = isset($_SESSION['easyshop']['payment_pending']) ? $_SESSION['easyshop']['payment_pending'] : null; if (!is_array($p) || empty($p['module']) || empty($p['approve_url']) || empty($p['order_id'])) { return null; } if ((time() - (int)(isset($p['time']) ? $p['time'] : 0)) >= 3600) { unset($_SESSION['easyshop']['payment_pending']); return null; } $provider = $this->payment_provider($p['module']); if (!$provider || $provider->isPaid((int)$p['order_id'])) { unset($_SESSION['easyshop']['payment_pending']); return null; } return $p; } /** * Rueckkehr vom Anbieter (?payment_return= | ?payment_cancel=). * Bezahlt -> Redirect auf die Erfolgsseite (F5-sicher). Sonst Hinweisseite, der * Warenkorb bleibt in jedem Fall stehen. */ private function payment_provider_return() { $is_cancel = isset($_GET['payment_cancel']); $module = strtolower(trim((string)($is_cancel ? $_GET['payment_cancel'] : $_GET['payment_return']))); $provider = $this->payment_provider($module); if (!$provider) { $this->layout_object->assign('call', 'payment_error'); $this->layout_object->assign('message', 'Die Zahlungsart konnte nicht zugeordnet werden. Ihr Warenkorb ist noch da.'); return $this->layout_object->_fetch('content_cash_desk/payment_result.tpl'); } try { $result = $is_cancel ? $provider->onCancel($_GET) : $provider->onReturn($_GET); } catch (Throwable $e) { error_log('Payments: '.$module.' return/cancel: '.$e->getMessage()); $result = \Payments\Result::error('Bei der Rueckmeldung des Zahlungsanbieters ist ein Fehler aufgetreten. Ihr Warenkorb ist noch da.'); } if ($result->status === \Payments\Result::PAID) { header('Location: /Kasse/?success=1&order='.urlencode($result->orderNumber)); exit; } $this->layout_object->assign('provider_label', $provider->label()); $this->layout_object->assign('order_number', $result->orderNumber); $this->layout_object->assign('message', $result->message); $this->layout_object->assign('call', $result->status === \Payments\Result::CANCELLED ? 'payment_cancelled' : ($result->status === \Payments\Result::PENDING ? 'payment_pending' : 'payment_error')); return $this->layout_object->_fetch('content_cash_desk/payment_result.tpl'); } private function setup_countries() { $country_object = new Country($this->base_object); $this->countries = $country_object->get_all(); $this->layout_object->assign('countries', $this->countries); } // end setup_countries private function setup_customer_data() { $customer_id = $this->base_object->customer->customer_id; $customer_object = new Customer($this->base_object); $this->customer_data = $customer_object->get_by_id($customer_id); // customer data is assigned in manage_address to take care of potential missing default address data $this->layout_object->assign('shoppingcartVATUsage', $this->base_object->customer_group->show_tax); $this->layout_object->assign('show_tax', $this->base_object->customer_group->show_tax); $this->layout_object->assign('customer_data', $this->customer_data); } // end setup_customer_data private function manage_guest_order_address() { $address_object = new CustomerAddress($this->base_object); $address_object->set_customer_id($this->customer_data->id); $this->customer_addresses = $address_object->get_all(); if (!isset($this->customer_addresses[$this->customer_data->default_address])) { $new_default = current($this->customer_addresses); $this->customer_data->default_address_country_id = $new_default->country; $this->customer_data->default_address_countryname = $this->countries[$new_default->country]->name; } if (isset($_POST['shipping']) && $_POST['shipping']['address'] == -1) { $this->shipping_address = $_POST['billing']; $_POST['shipping'] = $_POST['billing']; } else if (isset($_POST['shipping'])) { if (!$this->shipping_address['country']) { $_POST['shipping']['country'] = $this->customer_data->default_address_country_id; $this->layout_object->assign('default_address_country_id', $this->customer_data->default_address_country_id); } $this->shipping_address = $_POST['shipping']; } else { $this->shipping_address['country'] = $this->customer_data->default_address_country_id; } $this->layout_object->assign('shipping_address', $this->shipping_address); if (isset($_POST['billing'])) { $this->billing_address = $_POST['billing']; } else { $this->billing_address['country'] = $this->customer_data->default_address_country_id; } $this->layout_object->assign('billing_address', $this->billing_address); $this->layout_object->assign('customer', $this->customer_data); $shipping_country_id = $this->shipping_address['country']; $this->shipping_area_id = Country::getShippingAreaById($shipping_country_id, $this->customer_data->group_id); $this->layout_object->assign('addresses', $this->customer_addresses); } // end manage_address private function manage_address() { $address_object = new CustomerAddress($this->base_object); $address_object->set_customer_id($this->customer_data->id); $this->customer_addresses = $address_object->get_all(); // hack for missing default address if (is_array($this->customer_addresses) && !isset($this->customer_addresses[$this->customer_data->default_address])) { $new_default = current($this->customer_addresses); $this->customer_data->default_address = $new_default->id; $this->customer_data->default_address_company = $new_default->company; $this->customer_data->default_address_honorific = $new_default->honorific; $this->customer_data->default_address_firstname = $new_default->firstname; $this->customer_data->default_address_surname = $new_default->surname; $this->customer_data->default_address_street = $new_default->street; $this->customer_data->default_address_house_number = $new_default->house_number; $this->customer_data->default_address_zip_code = $new_default->zip_code; $this->customer_data->default_address_city = $new_default->city; $this->customer_data->default_address_country_id = $new_default->country; $this->customer_data->default_address_countryname = $this->countries[$new_default->country]->name; } if (isset($_POST['shipping']['address']) && $_POST['shipping']['address'] == -1) { $this->shipping_address = $_POST['billing']; } else if (isset($_POST['shipping'])) { $this->shipping_address = $_POST['shipping']; } else { $this->shipping_address['address'] = $this->customer_data->default_address; $this->shipping_address['honorific'] = $this->customer_data->default_address_honorific; $this->shipping_address['firstname'] = $this->customer_data->default_address_firstname; $this->shipping_address['surname'] = $this->customer_data->default_address_surname; $this->shipping_address['company'] = $this->customer_data->default_address_company; $this->shipping_address['street'] = $this->customer_data->default_address_street; $this->shipping_address['house_number'] = $this->customer_data->default_address_house_number; $this->shipping_address['zip_code'] = $this->customer_data->default_address_zip_code; $this->shipping_address['city'] = $this->customer_data->default_address_city; $this->shipping_address['country'] = $this->customer_data->default_address_country_id; } if (isset($_POST['billing'])) { $this->billing_address = $_POST['billing']; } else { $this->billing_address['address'] = $this->customer_data->default_address; $this->billing_address['honorific'] = $this->customer_data->default_address_honorific; $this->billing_address['firstname'] = $this->customer_data->default_address_firstname; $this->billing_address['surname'] = $this->customer_data->default_address_surname; $this->billing_address['company'] = $this->customer_data->default_address_company; $this->billing_address['street'] = $this->customer_data->default_address_street; $this->billing_address['house_number'] = $this->customer_data->default_address_house_number; $this->billing_address['zip_code'] = $this->customer_data->default_address_zip_code; $this->billing_address['city'] = $this->customer_data->default_address_city; $this->billing_address['country'] = $this->customer_data->default_address_country_id; } $billing_country_id = $this->billing_address['country']; $this->layout_object->assign('customer', $this->customer_data); $shipping_country_id = $this->shipping_address['country']; $this->shipping_area_id = Country::getShippingAreaById($shipping_country_id, $this->customer_data->group_id); $this->layout_object->assign('addresses', $this->customer_addresses); } // end manage_address private function manage_shoppingcart() { $shoppingcart_object = new Shoppingcart($this->base_object); $this->shoppingcart_data = $shoppingcart_object->get_data(); // can't show shoppingcart data here, gift certificates can change the data } // end manage_shoppingcart private function manage_payment_method() { $payment_method_object = new Payment_method($this->base_object); $this->payment_methods = $payment_method_object->getMethodsForGroup($this->customer_data->group_id, $this->shoppingcart_data['price_sum_gross'], $this->shipping_area_id); if (isset($_POST['payment_method']) && $_POST['payment_method'] > 0) { $this->payment_method_id = $_POST['payment_method']; } else { //$this->personal_data['payment_method'] = $this->payment_method_id = current($this->payment_methods)->id; foreach ($this->payment_methods as $pm) { if ($pm->activeType > 0) { $this->personal_data['payment_method'] = $this->payment_method_id = $pm->id; break; } } } $this->layout_object->assign('payment_methods', $this->payment_methods); } // end manage_payment_method private function manage_shipping_method() { $shipping_method_object = new Deliverer($this->base_object); $shoppingcart_object = new Shoppingcart($this->base_object); $this->shoppingcart_data = $shoppingcart_object->get_data(); $this->shipping_methods = $shipping_method_object->getAllActive( $this->shipping_area_id, $this->shoppingcart_data['standard_shipping_items'], $this->shoppingcart_data['bulk_goods'], $this->shoppingcart_data['item_weight'], $this->shoppingcart_data['price_sum_gross'] ); if (isset($_POST['shipping_method']) && $_POST['shipping_method'] > 0) { foreach ($this->shipping_methods as $sm) { if ($sm->id == $_POST['shipping_method']) { $this->shipping_method_id = $_POST['shipping_method']; } } } if (!$this->shipping_method_id) { $methods = $this->shipping_methods; $method = array_shift($methods); $this->personal_data['shipping_method'] = $this->shipping_method_id = $method->id; } $this->layout_object->assign('deliverers', $this->shipping_methods); } // end manage_shipping_method private function manage_gift_certificate() { $this->shoppingcart_data['order_total'] = $this->shoppingcart_data['price_sum_gross'] + $this->payment_methods[$this->payment_method_id]->paymentAdd + $this->shipping_methods[$this->shipping_method_id]->price_add; $this->shoppingcart_data['payment_method_vat'] = $this->payment_methods[$this->payment_method_id]->paymentAddVat; $this->shoppingcart_data['shipping_method_vat'] = $this->shipping_methods[$this->shipping_method_id]->price_add_vat; if (isset($_POST['giftcertificate']['gift_certificat_code']) && $_POST['giftcertificate']['gift_certificat_code'] != '') { $this->has_gift_certificate = true; $this->gift_certificate_code = $_POST['giftcertificate']['gift_certificat_code']; $this->layout_object->assign('giftcode', $this->gift_certificate_code); $this->gift_certificate_is_valid = GiftCertificateHelper::code_is_valide($this->customer_data->id, $this->gift_certificate_code, $this->shoppingcart_data['price_sum_gross']); if ($this->gift_certificate_is_valid) { $this->shoppingcart_data['gift_certificate_code'] = $this->gift_certificate_code; $this->gift_certificate = GiftCertificateHelper::get_certificate_by_code($this->gift_certificate_code); $this->gift_certificate_value = GiftCertificateHelper::get_certificate_value($this->gift_certificate_code, $this->shoppingcart_data['price_sum_gross'], $this->customer_data->group_id); $this->layout_object->assign('giftcertificatevalue', $this->gift_certificate_value->with_vat); $this->layout_object->assign('giftcertificatmessage', "Code akzeptiert."); // calculate the part of the gift certificate for each tax rate $part = $this->gift_certificate_value->with_vat / $this->shoppingcart_data['price_sum_gross']; } else { $this->gift_certificate_error = GiftCertificateHelper::get_error_text(); $this->layout_object->assign('gftcertificateerrormessage', $this->gift_certificate_error); $this->layout_object->assign('giftcertificatmessage', 'Code wird nicht akzeptiert.'); } } else { $this->has_gift_certificate = false; } foreach ($this->shoppingcart_data['tax_values'] as $key => $value) { $this->shoppingcart_data['tax_values'][$key]['gift']['percent'] = $percent = round(($value['gross'] / $this->shoppingcart_data['price_sum_gross'] * 100), 2); $this->shoppingcart_data['tax_values'][$key]['gift']['gross'] = $new_gross = $this->shoppingcart_data['price_sum_gross'] / 100 * $percent; $this->shoppingcart_data['tax_values'][$key]['gift']['net'] = $new_net = $new_gross / (100 + $key) * 100; $this->shoppingcart_data['tax_values'][$key]['gift']['vat'] = $new_gross - $new_net; if ($this->base_object->customer_group->show_tax) { $this->shoppingcart_data['tax_values'][$key]['gift']['show'] = $new_gross; } else { $this->shoppingcart_data['tax_values'][$key]['gift']['show'] = $new_net; } } // TODO: HACK, generalize this if (isset($this->payment_methods[9])) { if ((isset($this->base_object->config->shopConfiguration['klarna_eid']) && $this->base_object->config->shopConfiguration['klarna_eid'] != '') && (isset($this->base_object->config->shopConfiguration['klarna_secret']) && $this->base_object->config->shopConfiguration['klarna_secret'] != '')) { include_once('./core/shop_klarna.class.php'); // klarna installments $klarna_object = new Shop_klarna($this->base_object); if ($this->has_gift_certificate && $this->gift_certificate_is_valid) { $klarna_installments = $klarna_object->get_installments($this->shoppingcart_data['order_total'] - $this->gift_certificate_value->with_vat); } else { $klarna_installments = $klarna_object->get_installments($this->shoppingcart_data['order_total']); } $this->layout_object->assign('klarna_installments', $klarna_installments); } } // since the gift certificate can change the cart data this must be done here $this->layout_object->assign('shoppingcart', $this->shoppingcart_data); } // end manage_gift_certificate private function managa_conditions() { $site_content_object = new SiteContent($this->base_object); $agb_data = array(); // Datenschutz: aktuelle Version aus struct 10737 (DSGVO-Update). Alte struct 55 (id 50) ist veraltet. $agb_data['datenschutz'] = $site_content_object->get_textbox_data_by_id(10737); $agb_data['agbs'] = $site_content_object->get_textbox_data_by_id(56); $agb_data['agbs_b2b'] = $site_content_object->get_textbox_data_by_id(10632); $agb_data['widerruf'] = $site_content_object->get_textbox_data_by_id(60); $this->layout_object->assign('agb_data', $agb_data); } // end manage_conditions private function manage_errors() { $this->layout_object->assign('error_message', $this->errors); } // end manage_errors private function manage_data_persistence() { // ugly hack for data persistence $this->layout_object->assign('personal_data', $this->personal_data); } // end manage_data_persistence private function save_address() { $address = $_POST; unset($address['save_address']); $address_id = false; if ($address['address'] != '') { $address_id = $address['address']; } unset($address['address']); $address['customer_id'] = $this->customer_data->id; $customer_address_object = new CustomerAddress($this->base_object); $ret = array(); if ($customer_address_object->validate($address)) { if ($address_id) { $customer_address_object->update($address, $address_id); } else { $address_id = $customer_address_object->create($address); } $ret['id'] = $address_id; $ret['stat'] = 'OK'; } else { $ret['stat'] = 'ERR'; $ret['data'] = $customer_address_object->get_validation_errors(); } echo json_encode($ret); exit(); } // end save_address private function before_saving_order() { // check order if (!isset($_GET['guest_order']) || $_GET['guest_order'] != 1) { if (!is_numeric($_POST['billing']['address'])) { $this->errors[] = 'Bitte wählen Sie eine Rechnungsadresse'; } if (!is_numeric($_POST['shipping']['address'])) { $this->errors[] = 'Bitte wählen Sie eine Lieferadresse'; } } if (!is_numeric($_POST['payment_method'])) { $this->errors[] = 'Bitte wählen Sie eine Zahlungsart'; } else { $payment_method_id = $_POST['payment_method']; if ($this->payment_methods[$payment_method_id]->module != '') { $path = $_SERVER['DOCUMENT_ROOT'].'/core/paymentmethods/'; $module_name = ucfirst($this->payment_methods[$payment_method_id]->module).'PaymentMethod'; $file_name = strtolower($module_name).'.class.php'; if (file_exists($path.$file_name)) { include_once('./core/paymentmethods/'.$file_name); $payment_method_module = new $module_name($this->base_object, $this->layout_object); $payment_method_module->set_data($this->customer_data, $this->shipping_address, $this->billing_address, $this->shoppingcart_data, $this->shipping_methods[$this->shipping_method_id], $this->payment_methods[$this->payment_method_id]); if ($this->has_gift_certificate && $this->gift_certificate_is_valid) { $payment_method_module->set_gift_certificate($this->gift_certificate_value); } if ($this->payment_methods[$payment_method_id]->argument != '') { $return = $payment_method_module->before_order($this->payment_methods[$payment_method_id]->argument); } else { $return = $payment_method_module->before_order(); } if ($return) { $this->errors[] = $return; } } } } if (!is_numeric($_POST['shipping_method'])) { $this->errors[] = 'Bitte wählen Sie eine Versandart'; } if (!is_numeric($_POST['agreements_accepted'])) { $this->errors[] = 'Bitte akzeptieren Sie unsere Bedingungen'; } } // end before_saving_order private function save_order() { // 25.08.2026 -- FIX 1 ersetzt (Stufe 4). // // Hier stand eine Duplikatspruefung, die auf // order_date >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) // beruhte. `orders.order_date` ist aber Typ DATE, nicht DATETIME -- MySQL macht aus // dem Datum '2026-08-25 00:00:00', und damit war die Bedingung fuer den GANZEN TAG // wahr statt fuer fuenf Minuten. Wer am selben Tag ein zweites Mal bestellte, bekam // seine ERSTE Bestellnummer als "erfolgreich" angezeigt, und der neue Warenkorb // wurde nie bestellt. Ein stiller Verlust, der niemandem auffiel. // // Reparieren liess sich das nicht: die Tabelle `orders` hat ueberhaupt keine Spalte // mit Uhrzeit (alle Datumsfelder sind DATE), und `order_history.timestamp` wird bei // der Bestellanlage nicht gefuellt. Es gibt also keinen Zeitstempel, auf den man // eine Fuenf-Minuten-Regel stuetzen koennte. // // Statt der Uhr entscheidet jetzt der Vorgang: ein checkout_nonce pro Kassenformular // (siehe run() und checkout_nonce_holen()). Wird derselbe Nonce ein zweites Mal // abgeschickt, ist es derselbe Bestellvorgang -- unabhaengig davon, wie viel Zeit // vergangen ist. Als Netz fuer Absendungen ohne Nonce dient das Zeitfenster in // $_SESSION['easyshop']['last_order'], das eine echte Uhrzeit fuehrt. // // Der Empty-Cart-Schutz weiter unten (FIX 2) bleibt unveraendert. // 🚀 FIX 2: Verhindere 0€ Bestellungen ohne Items (Race Condition / Session Loss) if (empty($this->shoppingcart_data) || !isset($this->shoppingcart_data['items']) || empty($this->shoppingcart_data['items']) || !isset($this->shoppingcart_data['amount_sum']) || intval($this->shoppingcart_data['amount_sum']) <= 0) { error_log("ORDER PREVENTED: Empty cart or 0€ order attempted - Customer: " . ($this->customer_data->id ?? 'unknown') . ", Cart Items: " . (count($this->shoppingcart_data['items'] ?? [])) . ", Amount Sum: " . ($this->shoppingcart_data['amount_sum'] ?? '0')); $this->errors[] = 'Ihr Warenkorb ist leer. Bitte fügen Sie Artikel hinzu.'; return false; } if ($this->has_gift_certificate && $this->gift_certificate_is_valid) { $orderDataObject = OrderHelper::createOrderObject(array( 'customerId' => $this->customer_data->id, 'cartId' => session_id(), 'billingAddressId' => $this->billing_address['address'], 'shippingAddressId' => $this->shipping_address['address'], 'paymentMethodId' => $this->payment_method_id, 'shippingMethodId' => $this->shipping_method_id, 'giftCertificate' => $this->gift_certificate_code ), $this->base_object); } else { $orderDataObject = OrderHelper::createOrderObject(array( 'customerId' => $this->base_object->customer->customer_id, 'cartId' => session_id(), 'billingAddressId' => $this->billing_address['address'], 'shippingAddressId' => $this->shipping_address['address'], 'paymentMethodId' => $this->payment_method_id, 'shippingMethodId' => $this->shipping_method_id ), $this->base_object); } // TODO : HACK // fix up paymemt method with ALL the data $orderDataObject->payment->methodId = $this->payment_method_id; $orderDataObject->payment->areaId = $this->shipping_area_id; $orderDataObject->payment->priceGross = $this->payment_methods[$this->payment_method_id]->paymentAdd; $orderDataObject->payment->priceNet = $this->payment_methods[$this->payment_method_id]->paymentAddNet; $orderDataObject->payment->priceVat = $this->payment_methods[$this->payment_method_id]->paymentAddVat; $orderDataObject->payment->vatRate = '19.00'; // TODO : HACK // fix up shipping method with ALL the data $orderDataObject->shipping->methodId = $this->shipping_method_id; $orderDataObject->shipping->areaId = $this->shipping_area_id; $orderDataObject->shipping->priceGross = $this->shipping_methods[$this->shipping_method_id]->price_add; $orderDataObject->shipping->priceNet = $this->shipping_methods[$this->shipping_method_id]->price_add_net; $orderDataObject->shipping->priceVat = $this->shipping_methods[$this->shipping_method_id]->price_add_vat; $orderDataObject->shipping->vatRate = '19.00'; if (isset($_POST['customer_info']) && $_POST['customer_info'] != '') { $orderDataObject->customer_info = $_POST['customer_info']; } if ($this->payment_methods[$this->payment_method_id]->module != '') { $path = $_SERVER['DOCUMENT_ROOT'].'/core/paymentmethods/'; $module_name = ucfirst($this->payment_methods[$this->payment_method_id]->module).'PaymentMethod'; $file_name = strtolower($module_name).'.class.php'; if (file_exists($path.$file_name)) { include_once('./core/paymentmethods/'.$file_name); $payment_method_module = new $module_name($this->base_object, $this->layout_object); $payment_method_module->set_data($this->customer_data, $this->shipping_address, $this->billing_address, $this->shoppingcart_data, $this->shipping_methods[$this->shipping_method_id], $this->payment_methods[$this->payment_method_id]); if ($this->has_gift_certificate && $this->gift_certificate_is_valid) { $payment_method_module->set_gift_certificate($this->gift_certificate_value); } if ($this->payment_methods[$this->payment_method_id]->argument != '') { $return = $payment_method_module->during_order($this->payment_methods[$this->payment_method_id]->argument); } else { $return = $payment_method_module->during_order(); } if ($return) { foreach ($return as $key => $value) { $orderDataObject->$key = $value; } } } } $order_object = new Order($this->base_object); $this->order_id = $order_object->create($orderDataObject); $this->order_number = OrderHelper::get_order_number($this->order_id); unset($_SESSION['customer_info']); if ($this->order_id) { // 24.08.2026: Bei PayPal wandert die Bestellbestaetigung ans Ende des // Bezahlvorgangs (PaypalPaymentMethod::bestellbestaetigung_senden). Hier waere // sie zu frueh -- die Order existiert an dieser Stelle zwar, aber der Kunde war // noch nicht einmal bei PayPal. Beim Testkauf am 24.08. lagen zwischen dieser // Mail (23:25:09) und der eingezogenen Zahlung (23:26:45) anderthalb Minuten, // und wer gar nicht bezahlt, bekam trotzdem eine Bestaetigung. // Alle anderen Zahlarten sind mit dem Abschicken verbindlich -- dort bleibt der // Versand genau hier. $mail_modul = isset($this->payment_methods[$this->payment_method_id]->module) ? strtolower((string)$this->payment_methods[$this->payment_method_id]->module) : ''; if (!$this->zahlung_erst_spaeter($mail_modul)) { // send email $ticket_object = new Cs_ticket($this->base_object); $ticket_object->event(3, $this->customer_data->id, $this->order_id); } else { error_log('PayPal Fix: Bestellbestaetigung fuer Order '.$this->order_id . ' zurueckgestellt bis zur eingezogenen Zahlung'); } // EU-RL 2026/2673: elektronischer Widerruf (B2C) – silent solange Flag aus try { include_once './core/revocation.class.php'; $revocation_obj = new Revocation($this->base_object); $revocation_obj->create_for_order($this->order_id); } catch (Throwable $e) { error_log('Revocation hook failed for order '.$this->order_id.': '.$e->getMessage()); } if ($this->order_number) { // 25.08.2026: Vorgang als erledigt vermerken, damit ein zweiter Klick auf // denselben Bestellbutton keine weitere Order erzeugt. $this->bestellvorgang_vermerken(); // Ereignisprotokoll in PG -- der einzige Ort mit einer Uhrzeit zur Bestellung. if (function_exists('shop_event')) { shop_event('order_created', array( 'order_id' => $this->order_id, 'order_number' => $this->order_number, 'payment_method' => $this->payment_method_id, 'betrag' => isset($this->shoppingcart_data['order_total']) ? $this->shoppingcart_data['order_total'] : null, 'customer_id' => isset($this->customer_data->id) ? $this->customer_data->id : null, 'detail' => $this->zahlung_erst_spaeter($mail_modul) ? $mail_modul.': noch nicht bezahlt' : 'sofort verbindlich' )); } return true; } } return false; } // end save_order private function after_saving_order() { $this->layout_object->assign('order_number', $this->order_number); $this->layout_object->assign('order_amount', $this->shoppingcart_data['order_total']); // get trusted shop payment id $trusted_shop_payment = 'OTHER'; if ($this->payment_method_id == 1) { $trusted_shop_payment = 'PREPAYMENT'; } else if ($this->payment_method_id == 2) { $trusted_shop_payment = 'CASH_ON_DELIVERY'; } else if ($this->payment_method_id == 3) { $trusted_shop_payment = 'INVOICE'; } else if ($this->payment_method_id == 4) { $trusted_shop_payment = 'PAYPAL'; } else if ($this->payment_method_id == 6) { $trusted_shop_payment = 'DIRECT_DEBIT'; } $this->layout_object->assign('trusted_shop_payment', $trusted_shop_payment); // 24.08.2026: Der Warenkorb wird nur noch geleert, wenn die Bestellung mit dieser // Antwort wirklich abgeschlossen ist. Bei PayPal faengt das Bezahlen hier erst an // -- vorher flog der Korb schon raus, und wer abbrach, neu lud oder zurueckging, // stand ohne Warenkorb da ("ORDER PREVENTED: Empty cart"). PayPal leert jetzt // selbst, sobald das Geld eingezogen ist (PaypalPaymentMethod::change_order). // Bewusst nur PayPal: die anderen Module raeumen nicht selbst auf. $payment_module = isset($this->payment_methods[$this->payment_method_id]->module) ? strtolower((string)$this->payment_methods[$this->payment_method_id]->module) : ''; if (!$this->zahlung_erst_spaeter($payment_module)) { $shoppingcart_object = new Shoppingcart($this->base_object); $shoppingcart_object->clear(1); } if ($this->payment_methods[$this->payment_method_id]->module != '') { $path = $_SERVER['DOCUMENT_ROOT'].'/core/paymentmethods/'; $module_name = ucfirst($this->payment_methods[$this->payment_method_id]->module).'PaymentMethod'; $file_name = strtolower($module_name).'.class.php'; if (file_exists($path.$file_name)) { include_once('./core/paymentmethods/'.$file_name); $payment_method_module = new $module_name($this->base_object, $this->layout_object); $payment_method_module->set_data($this->customer_data, $this->shipping_address, $this->billing_address, $this->shoppingcart_data, $this->shipping_methods[$this->shipping_method_id], $this->payment_methods[$this->payment_method_id], $this->order_id); if ($this->has_gift_certificate && $this->gift_certificate_is_valid) { $payment_method_module->set_gift_certificate($this->gift_certificate_value); } return $payment_method_module->after_order($this->shoppingcart_data['order_total']); } } // REDIRECT statt Template - verhindert Doppel-Bestellungen bei F5 $order_num = $this->order_number ? $this->order_number : 'unbekannt'; header('Location: /Kasse/?success=1&order=' . urlencode($order_num)); exit; } // end after_saving_order /** * Nonce fuer das Kassenformular (25.08.2026). * * Ein Wert pro Bestellvorgang, der als hidden field im Formular mitgeht. Nach einer * erfolgreichen Bestellung wird er verbraucht und ein neuer erzeugt -- ein erneut * abgeschickter alter Nonce ist damit erkennbar derselbe Vorgang. Das ersetzt die * Zeitfenster-Pruefung, die auf einer DATE-Spalte nicht funktionieren konnte. * * @return string */ private function checkout_nonce_holen() { if (empty($_SESSION['easyshop']['checkout_nonce'])) { if (function_exists('random_bytes')) { try { $_SESSION['easyshop']['checkout_nonce'] = bin2hex(random_bytes(16)); } catch (Exception $e) { $_SESSION['easyshop']['checkout_nonce'] = md5(uniqid(mt_rand(), true)); } } else { $_SESSION['easyshop']['checkout_nonce'] = md5(uniqid(mt_rand(), true)); } } return $_SESSION['easyshop']['checkout_nonce']; } // end checkout_nonce_holen /** * Wurde dieser Bestellvorgang schon abgeschlossen? (25.08.2026) * * @return array|false Daten der bereits angelegten Order, sonst false */ private function bestellvorgang_bereits_erledigt() { $nonce = isset($_POST['checkout_nonce']) ? trim((string)$_POST['checkout_nonce']) : ''; if ($nonce !== '' && isset($_SESSION['easyshop']['checkout_done'][$nonce])) { return $_SESSION['easyshop']['checkout_done'][$nonce]; } // Netz fuer Absendungen ohne Nonce -- alte Seite aus dem Cache, JS abgeschaltet, // Formular von Hand nachgebaut. Gleiche Session, gleicher Betrag, innerhalb von // fuenf Minuten. Die Uhrzeit kommt aus der Session und ist damit eine echte // Uhrzeit, anders als beim alten Guard auf der DATE-Spalte. if ($nonce === '' && !empty($_SESSION['easyshop']['last_order'])) { $letzte = $_SESSION['easyshop']['last_order']; $summe = round((float)(isset($this->shoppingcart_data['order_total']) ? $this->shoppingcart_data['order_total'] : 0), 2); if (abs($summe - (float)$letzte['total']) < 0.01 && (time() - (int)$letzte['time']) < 300) { return $letzte; } } return false; } // end bestellvorgang_bereits_erledigt /** * Bestellvorgang als erledigt vermerken (25.08.2026). * * Laeuft direkt nach der Order-Anlage. Der verbrauchte Nonce bleibt in der Session * stehen, damit ein spaeter nachgereichter POST mit demselben Wert erkannt wird; fuer * die naechste Bestellung wird beim Rendern des Formulars ein frischer erzeugt. */ private function bestellvorgang_vermerken() { $nonce = isset($_POST['checkout_nonce']) ? trim((string)$_POST['checkout_nonce']) : ''; $eintrag = array( 'order_id' => $this->order_id, 'order_number' => $this->order_number, 'total' => round((float)(isset($this->shoppingcart_data['order_total']) ? $this->shoppingcart_data['order_total'] : 0), 2), 'time' => time() ); if ($nonce !== '') { if (!isset($_SESSION['easyshop']['checkout_done']) || !is_array($_SESSION['easyshop']['checkout_done'])) { $_SESSION['easyshop']['checkout_done'] = array(); } $_SESSION['easyshop']['checkout_done'][$nonce] = $eintrag; // Nicht unbegrenzt mitschleppen -- die letzten fuenf reichen. if (count($_SESSION['easyshop']['checkout_done']) > 5) { $_SESSION['easyshop']['checkout_done'] = array_slice($_SESSION['easyshop']['checkout_done'], -5, 5, true); } unset($_SESSION['easyshop']['checkout_nonce']); } $_SESSION['easyshop']['last_order'] = $eintrag; } // end bestellvorgang_vermerken /** * Ist zu dieser Order bereits Geld eingezogen? (24.08.2026) * * Entscheidet, ob ein wiederholter Bestell-POST auf den offenen PayPal-Vorgang * zurueckgefuehrt wird oder als neuer Einkauf durchlaeuft. * * @param int $order_id * @return bool */ private function paypal_order_is_paid($order_id) { $order_id = (int)$order_id; if ($order_id <= 0) { return false; } $result = $this->base_object->db->query(" SELECT paypal_transaction_id FROM orders WHERE id = ".$this->base_object->db->real_escape_string($order_id)." LIMIT 1 "); if ($result && $result->num_rows > 0) { $row = $result->fetch_object(); return !empty($row->paypal_transaction_id); } // Order nicht gefunden -> nicht blockieren, normal weiterbestellen lassen. return false; } // end paypal_order_is_paid private function change_order($param) { $path = $_SERVER['DOCUMENT_ROOT'].'/core/paymentmethods/'; $module_name = ucfirst($param).'PaymentMethod'; $file_name = strtolower($module_name).'.class.php'; if (file_exists($path.$file_name)) { include_once('./core/paymentmethods/'.$file_name); $payment_method_module = new $module_name($this->base_object, $this->layout_object); return $payment_method_module->change_order(); } } // end change_order private function manage_guest_payment_method() { $payment_method_object = new Payment_method($this->base_object); /* temp: */ $this->shipping_area_id = 1; $this->payment_methods = $payment_method_object->getMethodsForGroup($this->base_object->config->shopConfiguration['guest_order_customer_group'], $this->shoppingcart_data['price_sum_gross'], $this->shipping_area_id); if (isset($_POST['payment_method']) && $_POST['payment_method'] > 0) { $this->payment_method_id = $_POST['payment_method']; } else { $this->personal_data['payment_method'] = $this->payment_method_id = current($this->payment_methods)->id; } $this->layout_object->assign('payment_methods', $this->payment_methods); } // end manage_payment_method private function manage_guest_shipping_method() { $shipping_method_object = new Deliverer($this->base_object); $shoppingcart_object = new Shoppingcart($this->base_object); $this->shoppingcart_data = $shoppingcart_object->get_data(); $this->shipping_methods = $shipping_method_object->getAllActive( $this->shipping_area_id, $this->shoppingcart_data['standard_shipping_items'], $this->shoppingcart_data['bulk_goods'], $this->shoppingcart_data['item_weight'] ); if (isset($_POST['shipping_method']) && $_POST['shipping_method'] > 0) { $this->shipping_method_id = $_POST['shipping_method']; } else { $this->personal_data['shipping_method'] = $this->shipping_method_id = current($this->shipping_methods)->id; } $this->layout_object->assign('deliverers', $this->shipping_methods); } // end manage_shipping_method } // end website_cashdesk ?>