Files
shop-old/modules/website_cashdesk.php
T

1145 lines
46 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
* @version $Id: index.php 10381 2008-06-01 03:35:53Z $
* @package Carteasy
* @copyright Copyright (C) 2005 - 2011 Wlanium
* @license proprietary
* @author Thomas Bartelt
* Carteasy is a web shop system
*/
include_once('./core/country.class.php');
include_once('./core/customer.class.php');
include_once('./core/customeraddress.class.php');
include_once('./core/shoppingcart.class.php');
include_once('./core/payment_method.class.php');
include_once('./core/deliverer.class.php'); // should be called shipping methods
include_once('./core/order.class.php');
include_once('./core/cs_ticket.class.php');
// 25.08.2026: Ereignisprotokoll in PG -- liefert die Uhrzeiten zu Bestellungen, die
// `orders` nicht hat (alle Datumsfelder dort sind Typ DATE). Faellt still aus, wenn
// Postgres nicht erreichbar ist.
include_once($_SERVER['DOCUMENT_ROOT'].'/core/postgres/shop_events.php');
include_once('./core/orderhelper.class.php');
include_once('./core/giftcertificatehelper.class.php');
class website_cashdesk {
private $base_object;
private $layout_object;
private $errors;
private $personal_data;
private $countries;
private $customer_data;
private $customer_addresses;
private $shipping_address;
private $billing_address;
private $shoppingcart_data;
private $shipping_area_id;
private $payment_methods;
private $payment_method_id;
private $shipping_methods;
private $shipping_method_id;
private $has_gift_certificate;
private $gift_certificate_code;
private $gift_certificate_is_valid;
private $gift_certificate;
private $gift_certificate_value;
private $order_number;
private $order_id;
function __construct($base_object, $layout_object) {
$this->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');
}
// 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 &ndash; 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);
}
$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;
}
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;
}
$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
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());
return $this->layout_object->_fetch('content_cash_desk.tpl');
} else if ($_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
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&auml;hlen Sie eine Rechnungsadresse';
}
if (!is_numeric($_POST['shipping']['address'])) {
$this->errors[] = 'Bitte w&auml;hlen Sie eine Lieferadresse';
}
}
if (!is_numeric($_POST['payment_method'])) {
$this->errors[] = 'Bitte w&auml;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&auml;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 ($mail_modul !== 'paypal') {
// 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' => $mail_modul === 'paypal'
? 'PayPal: 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 ($payment_module !== 'paypal') {
$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
?>