feat(payments): Express-Kauf aus dem Warenkorb (PayPal v2): Start ohne Bestellung, Adresse von PayPal, Kasse vorbelegt, Capture ohne zweiten Besuch
This commit is contained in:
@@ -76,7 +76,20 @@ class Paypal_v2PaymentMethod
|
||||
if (!$provider) {
|
||||
throw new \RuntimeException('Provider paypal_v2 nicht installiert');
|
||||
}
|
||||
$url = $provider->start((int) $this->order_id, $amount);
|
||||
|
||||
// Express aus dem Warenkorb: der Kunde war schon bei PayPal, Adresse liegt vor.
|
||||
// Betrag nachziehen und einziehen -- ohne zweiten PayPal-Besuch.
|
||||
$express = $_SESSION['easyshop']['payment_express'] ?? null;
|
||||
if (\is_array($express) && ($express['module'] ?? '') === self::MODULE && !empty($express['reference'])) {
|
||||
$url = $provider->finishExpress((int) $this->order_id, $amount, (string) $express['reference']);
|
||||
unset($_SESSION['easyshop']['payment_express']);
|
||||
if ($url === '') {
|
||||
\header('Location: /Kasse/?success=1&order=' . \rawurlencode($this->resolve_order_number((int) $this->order_id)));
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
$url = $provider->start((int) $this->order_id, $amount);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
\error_log('PayPal v2 after_order (Order ' . $this->order_id . '): ' . $e->getMessage());
|
||||
if (\function_exists('shop_event')) {
|
||||
@@ -108,4 +121,11 @@ class Paypal_v2PaymentMethod
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolve_order_number(int $orderId): string
|
||||
{
|
||||
$res = $this->base_object->db->query('SELECT order_number FROM orders WHERE id=' . $orderId . ' LIMIT 1');
|
||||
$row = $res ? $res->fetch_object() : null;
|
||||
return $row ? (string) $row->order_number : (string) $orderId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,4 +426,56 @@ abstract class AbstractProvider implements ProviderInterface
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// ------------------------------------------------------- Express-Defaults
|
||||
|
||||
public function supportsExpress(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function startExpress(float $amount): string
|
||||
{
|
||||
throw new \RuntimeException($this->label() . ': kein Express-Weg');
|
||||
}
|
||||
|
||||
public function onExpressReturn(array $query): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function finishExpress(int $orderId, float $amount, string $reference): string
|
||||
{
|
||||
throw new \RuntimeException($this->label() . ': kein Express-Weg');
|
||||
}
|
||||
|
||||
/** Express-Rueckkehr laeuft ueber den Cashdesk, ohne Bestellung. */
|
||||
protected function expressReturnUrl(): string
|
||||
{
|
||||
return $this->shopBaseUrl() . '/Kasse/?payment_express=' . $this->module();
|
||||
}
|
||||
|
||||
protected function expressCancelUrl(): string
|
||||
{
|
||||
return $this->shopBaseUrl() . '/Warenkorb/?payment_express_cancel=' . $this->module();
|
||||
}
|
||||
|
||||
/** countries.id zu einem ISO-3166-Alpha-2-Code, 0 wenn das Land im Shop nicht lieferbar ist. */
|
||||
public function countryIdByIso(string $iso): int
|
||||
{
|
||||
$iso = $this->db->real_escape_string(\strtoupper($iso));
|
||||
$res = $this->db->query("SELECT id FROM countries WHERE iso_code_2='$iso' ORDER BY rang DESC, id LIMIT 1");
|
||||
$r = $res ? $res->fetch_object() : null;
|
||||
return $r ? (int) $r->id : 0;
|
||||
}
|
||||
|
||||
/** "Neuer Weg 5a" -> ["Neuer Weg", "5a"]; ohne erkennbare Nummer bleibt alles Strasse. */
|
||||
public static function splitStreet(string $line): array
|
||||
{
|
||||
$line = \trim($line);
|
||||
if (\preg_match('/^(.*?)[\s,]+(\d+[\s\-\/]?[a-zA-Z0-9\-\/]*)$/u', $line, $m)) {
|
||||
return [\trim($m[1]), \trim($m[2])];
|
||||
}
|
||||
return [$line, ''];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,38 @@ interface ProviderInterface
|
||||
*/
|
||||
public function reconcile(bool $dryRun = true): array;
|
||||
|
||||
// ------------------------------------------------------------ Express
|
||||
// "Direkt bezahlen" aus dem Warenkorb: der Kunde geht OHNE Adresse zum Anbieter,
|
||||
// der liefert Adresse + E-Mail, die Kasse ist damit vorbelegt, ein Klick auf
|
||||
// "zahlungspflichtig bestellen" zieht das Geld ein -- kein zweiter Besuch beim Anbieter.
|
||||
|
||||
/** Bietet der Anbieter den Express-Weg an (und ist er eingeschaltet)? */
|
||||
public function supportsExpress(): bool;
|
||||
|
||||
/**
|
||||
* Express-Vorgang fuer den aktuellen Warenkorb anlegen (noch keine Bestellung!).
|
||||
* @param float $amount Warenwert brutto ohne Versand (Land noch unbekannt)
|
||||
* @return string URL zum Anbieter
|
||||
*/
|
||||
public function startExpress(float $amount): string;
|
||||
|
||||
/**
|
||||
* Rueckkehr vom Anbieter im Express-Weg: Adresse und E-Mail des Kunden abholen.
|
||||
* @param array<string,mixed> $query $_GET der Rueckkehr-URL
|
||||
* @return array<string,mixed>|null Kundendaten fuer die Kasse (Schluessel: reference,
|
||||
* email, firstname, surname, company, street, house_number, zip_code, city,
|
||||
* country_iso, approve_url) oder null, wenn der Vorgang nicht zugeordnet werden kann
|
||||
*/
|
||||
public function onExpressReturn(array $query): ?array;
|
||||
|
||||
/**
|
||||
* Express abschliessen, nachdem die Bestellung angelegt ist: Betrag beim Anbieter auf
|
||||
* den Endbetrag (inkl. Versand) setzen und einziehen.
|
||||
* @return string '' = bezahlt; sonst eine URL, zu der der Kunde noch einmal muss
|
||||
* (Anbieter verlangt erneute Freigabe, z.B. nach Betragsaenderung)
|
||||
*/
|
||||
public function finishExpress(int $orderId, float $amount, string $reference): string;
|
||||
|
||||
/**
|
||||
* Felder fuer den Zahlart-Editor im Backend, die NICHT in `configuration` liegen,
|
||||
* sondern verschluesselt (Secrets). name => Beschriftung.
|
||||
|
||||
@@ -43,7 +43,8 @@ INSERT IGNORE INTO `configuration` (`name`, `text`, `group_id`, `setting`, `type
|
||||
('paypal_v2_mode', '', 'paypal_v2', 'sandbox', 1, 'sandbox oder live'),
|
||||
('paypal_v2_brand_name', '', 'paypal_v2', 'intelectra', 1, 'Anzeigename auf der PayPal-Seite'),
|
||||
('paypal_v2_send_address', '', 'paypal_v2', '1', 1, '1 = Lieferadresse an PayPal uebergeben (Verkaeuferschutz), 0 = nicht'),
|
||||
('paypal_v2_currency', '', 'paypal_v2', 'EUR', 1, 'Waehrung (ISO 4217)');
|
||||
('paypal_v2_currency', '', 'paypal_v2', 'EUR', 1, 'Waehrung (ISO 4217)'),
|
||||
('paypal_v2_express', '', 'paypal_v2', '1', 1, '1 = Button "Direkt mit PayPal bezahlen" im Warenkorb, 0 = aus');
|
||||
|
||||
-- 2b) Zahlart anlegen. active=1, aber noch KEINER Kundengruppe zugeordnet => im Shop unsichtbar.
|
||||
-- helperjoinkey 'pp' = derselbe PayPal-Aufschlag wie die alte Zahlart (shipping_area.add_pp).
|
||||
|
||||
@@ -350,6 +350,159 @@ final class Provider extends AbstractProvider
|
||||
return $pem;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- express
|
||||
|
||||
public function supportsExpress(): bool
|
||||
{
|
||||
return $this->setting('express', '1') === '1';
|
||||
}
|
||||
|
||||
/** Order ohne Adresse anlegen; PayPal liefert sie bei der Rueckkehr (GET_FROM_FILE). */
|
||||
public function startExpress(float $amount): string
|
||||
{
|
||||
$value = \number_format($amount, 2, '.', '');
|
||||
$payload = [
|
||||
'intent' => 'CAPTURE',
|
||||
'purchase_units' => [[
|
||||
'reference_id' => 'default',
|
||||
'custom_id' => 'express',
|
||||
'description' => 'Warenkorb ' . $this->setting('brand_name', 'intelectra'),
|
||||
'amount' => ['currency_code' => $this->currency(), 'value' => $value],
|
||||
]],
|
||||
'application_context' => [
|
||||
'brand_name' => $this->setting('brand_name', 'intelectra'),
|
||||
'locale' => 'de-DE',
|
||||
'landing_page' => 'NO_PREFERENCE',
|
||||
'user_action' => 'CONTINUE', // "Weiter" statt "Jetzt bezahlen": eingezogen wird erst in der Kasse
|
||||
'shipping_preference' => 'GET_FROM_FILE',
|
||||
'return_url' => $this->expressReturnUrl(),
|
||||
'cancel_url' => $this->expressCancelUrl(),
|
||||
],
|
||||
];
|
||||
[$status, $body] = $this->client()->request('POST', '/v2/checkout/orders', $payload,
|
||||
['PayPal-Request-Id: express-' . \session_id() . '-' . \substr(\hash('sha256', $value . '|' . \time()), 0, 12)]);
|
||||
Log::write($this->module(), 'express_create', null, null, (string) ($body['id'] ?? ''), (string) ($body['status'] ?? ''), $status, $body);
|
||||
if ($status !== 201 || empty($body['id'])) {
|
||||
throw new \RuntimeException('PayPal Express: Order-Anlage fehlgeschlagen (HTTP ' . $status . ')');
|
||||
}
|
||||
$approve = '';
|
||||
foreach (($body['links'] ?? []) as $link) {
|
||||
if (\in_array($link['rel'] ?? '', ['approve', 'payer-action'], true)) {
|
||||
$approve = (string) $link['href'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($approve === '') {
|
||||
throw new \RuntimeException('PayPal Express: kein Approve-Link.');
|
||||
}
|
||||
$this->event('payment_started', null, null, $amount, (string) $body['id'], null, null, 'express aus dem Warenkorb');
|
||||
return $approve;
|
||||
}
|
||||
|
||||
public function onExpressReturn(array $query): ?array
|
||||
{
|
||||
$ref = (string) ($query['token'] ?? '');
|
||||
if ($ref === '') {
|
||||
return null;
|
||||
}
|
||||
[$status, $body] = $this->client()->request('GET', '/v2/checkout/orders/' . \rawurlencode($ref));
|
||||
Log::write($this->module(), 'express_get', null, null, $ref, (string) ($body['status'] ?? ''), $status, $body);
|
||||
if ($status !== 200 || ($body['purchase_units'][0]['custom_id'] ?? '') !== 'express') {
|
||||
return null;
|
||||
}
|
||||
if (($body['status'] ?? '') !== 'APPROVED') {
|
||||
return null; // Kunde hat nicht freigegeben
|
||||
}
|
||||
$payer = $body['payer'] ?? [];
|
||||
$ship = $body['purchase_units'][0]['shipping'] ?? [];
|
||||
$addr = $ship['address'] ?? [];
|
||||
$full = \trim((string) ($ship['name']['full_name'] ?? ''));
|
||||
$first = (string) ($payer['name']['given_name'] ?? '');
|
||||
$last = (string) ($payer['name']['surname'] ?? '');
|
||||
if ($full !== '' && $full !== \trim($first . ' ' . $last)) {
|
||||
// Lieferadresse lautet auf einen anderen Namen (z.B. Packstation, Partner)
|
||||
$parts = \explode(' ', $full);
|
||||
$last = \array_pop($parts);
|
||||
$first = \implode(' ', $parts);
|
||||
}
|
||||
[$street, $number] = self::splitStreet((string) ($addr['address_line_1'] ?? ''));
|
||||
$approve = '';
|
||||
foreach (($body['links'] ?? []) as $link) {
|
||||
if (\in_array($link['rel'] ?? '', ['approve', 'payer-action'], true)) {
|
||||
$approve = (string) $link['href'];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'reference' => $ref,
|
||||
'email' => (string) ($payer['email_address'] ?? ''),
|
||||
'firstname' => $first,
|
||||
'surname' => $last,
|
||||
'company' => (string) ($addr['address_line_2'] ?? ''),
|
||||
'street' => $street,
|
||||
'house_number' => $number,
|
||||
'zip_code' => (string) ($addr['postal_code'] ?? ''),
|
||||
'city' => (string) ($addr['admin_area_2'] ?? ''),
|
||||
'country_iso' => (string) ($addr['country_code'] ?? ''),
|
||||
'approve_url' => $approve,
|
||||
'payer_id' => (string) ($payer['payer_id'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Betrag auf den Endbetrag setzen (PATCH ist im Status APPROVED erlaubt), Vorgang an
|
||||
* die Bestellung haengen, einziehen. Verlangt PayPal nach der Aenderung eine erneute
|
||||
* Freigabe, kommt die Approve-URL zurueck und der Kunde geht noch einmal hin.
|
||||
*/
|
||||
public function finishExpress(int $orderId, float $amount, string $reference): string
|
||||
{
|
||||
$order = $this->orderById($orderId);
|
||||
$number = (string) $order->order_number;
|
||||
$value = \number_format($amount, 2, '.', '');
|
||||
|
||||
$patch = [
|
||||
['op' => 'replace', 'path' => "/purchase_units/@reference_id=='default'/amount",
|
||||
'value' => ['currency_code' => $this->currency(), 'value' => $value]],
|
||||
['op' => 'replace', 'path' => "/purchase_units/@reference_id=='default'/custom_id", 'value' => (string) $orderId],
|
||||
['op' => 'add', 'path' => "/purchase_units/@reference_id=='default'/invoice_id", 'value' => $number . '-' . $orderId],
|
||||
];
|
||||
[$status, $body] = $this->client()->request('PATCH', '/v2/checkout/orders/' . \rawurlencode($reference), $patch);
|
||||
Log::write($this->module(), 'express_patch', $orderId, $number, $reference, (string) $status, $status, $body ?: ['amount' => $value]);
|
||||
if ($status !== 204) {
|
||||
throw new \RuntimeException('PayPal Express: Betrag konnte nicht gesetzt werden (HTTP ' . $status . '): ' . \json_encode($body['details'] ?? $body));
|
||||
}
|
||||
|
||||
// Approve-URL fuer den Fall, dass PayPal eine erneute Freigabe verlangt
|
||||
[$s2, $b2] = $this->client()->request('GET', '/v2/checkout/orders/' . \rawurlencode($reference));
|
||||
$approveUrl = '';
|
||||
foreach (($b2['links'] ?? []) as $link) {
|
||||
if (\in_array($link['rel'] ?? '', ['approve', 'payer-action'], true)) {
|
||||
$approveUrl = (string) $link['href'];
|
||||
}
|
||||
}
|
||||
$this->openTransaction($orderId, $reference, $amount, $approveUrl);
|
||||
|
||||
try {
|
||||
$cap = $this->capture($reference, $orderId, $number);
|
||||
} catch (\RuntimeException $e) {
|
||||
if (\str_contains($e->getMessage(), 'ORDER_NOT_APPROVED') || \str_contains($e->getMessage(), 'PAYER_ACTION_REQUIRED')) {
|
||||
Log::write($this->module(), 'express_reapprove', $orderId, $number, $reference, 'needs approval');
|
||||
return $approveUrl !== '' ? $approveUrl : 'https://www.' . ($this->mode() === 'live' ? '' : 'sandbox.') . 'paypal.com/checkoutnow?token=' . \rawurlencode($reference);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
if ($cap['capture_status'] === 'COMPLETED') {
|
||||
if ($this->markPaid($reference, $cap['amount'], $cap['currency'], $cap['capture_id'], $cap['payer_email'], $cap['payer_id'], 'express')) {
|
||||
return '';
|
||||
}
|
||||
throw new \RuntimeException('Zahlung eingezogen, aber Zuordnung fehlgeschlagen (Bestellung ' . $number . ')');
|
||||
}
|
||||
if ($cap['capture_status'] === 'PENDING') {
|
||||
$this->setTransactionStatus($reference, 'pending', 'capture PENDING: ' . ($cap['pending_reason'] ?? ''));
|
||||
return '';
|
||||
}
|
||||
throw new \RuntimeException('PayPal hat die Zahlung nicht abgeschlossen (' . ($cap['capture_status'] ?: $cap['order_status']) . ')');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- reconcile
|
||||
|
||||
public function reconcile(bool $dryRun = true): array
|
||||
|
||||
@@ -80,6 +80,17 @@ class website_cashdesk {
|
||||
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.
|
||||
@@ -109,6 +120,7 @@ class website_cashdesk {
|
||||
$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) {
|
||||
@@ -361,6 +373,119 @@ class website_cashdesk {
|
||||
|
||||
// end run
|
||||
|
||||
// ------------------------------------------------------------ Express
|
||||
|
||||
/** Start aus dem Warenkorb: /Kasse/?payment_express_start=<modul> */
|
||||
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(round((float)$cart['price_sum_gross'], 2));
|
||||
} 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;
|
||||
}
|
||||
|
||||
/** Rueckkehr im Express-Weg: /Kasse/?payment_express=<modul>&token=... */
|
||||
private function payment_express_return() {
|
||||
$module = strtolower(trim((string)$_GET['payment_express']));
|
||||
$provider = $this->payment_provider($module);
|
||||
$data = null;
|
||||
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');
|
||||
}
|
||||
$country_id = $provider->countryIdByIso((string)$data['country_iso']);
|
||||
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);
|
||||
$this->layout_object->assign('express_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.
|
||||
*
|
||||
|
||||
@@ -56,14 +56,44 @@ class website_shoppingcart {
|
||||
}
|
||||
|
||||
$data = $this->shoppingcart_object->get_data();
|
||||
|
||||
|
||||
$this->layout_object->assign('shoppingcart', $data);
|
||||
$this->layout_object->assign('shoppingcartVATUsage', $this->base_object->customer_group->show_tax);
|
||||
|
||||
// 14.09.2026: Express-Buttons der Zahlungsanbieter ("Direkt mit PayPal bezahlen").
|
||||
// Angezeigt, wenn der Anbieter Express kann und seine Zahlart aktiv ist.
|
||||
$this->layout_object->assign('express_providers', $this->express_providers());
|
||||
|
||||
return $this->layout_object->_fetch('content_shoppingcart.tpl');
|
||||
} // end show_shoppingcart
|
||||
|
||||
|
||||
/** @return array<int,array{module:string,label:string}> */
|
||||
private function express_providers() {
|
||||
$out = array();
|
||||
$file = $_SERVER['DOCUMENT_ROOT'].'/core/payments/Registry.php';
|
||||
if (!is_file($file)) {
|
||||
return $out;
|
||||
}
|
||||
include_once $file;
|
||||
$res = $this->base_object->db->query("SELECT module FROM payment_methods WHERE active=1 AND module<>''");
|
||||
while ($res && ($row = $res->fetch_object())) {
|
||||
$module = (string)$row->module;
|
||||
if (!\Payments\Registry::has($module)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$p = \Payments\Registry::get($module, $this->base_object);
|
||||
if ($p && $p->supportsExpress()) {
|
||||
$out[] = array('module' => $module, 'label' => $p->label());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
error_log('Payments express button '.$module.': '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function update_shoppingcart() {
|
||||
foreach ($_POST['update_item'] as $item => $quantity) {
|
||||
if ($quantity > 0) {
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
|
||||
<div class="cashdesk">
|
||||
{if isset($guest_order) && $guest_order == 1}<div class="dropdown_title"><span class="info" style="font-size:22px;">Gastbestellung</span></div><br /><br />{/if}
|
||||
{if isset($express_active) && $express_active}
|
||||
<div class="info_box" style="border:1px solid #b8d6a8; background:#eef8e8; padding:10px 14px; margin-bottom:14px;">
|
||||
<strong>Express-Kauf:</strong> Adresse, E-Mail und Zahlart wurden von {$express_label|escape} übernommen.
|
||||
Bitte prüfen Sie die Angaben und schließen Sie die Bestellung unten ab – die Zahlung wird erst dann eingezogen.
|
||||
</div>
|
||||
{/if}
|
||||
<form id="order_form" method="post" action="">
|
||||
{include file='content_cash_desk/address_invoice.tpl'}
|
||||
<br />
|
||||
|
||||
@@ -144,8 +144,23 @@
|
||||
*Um Artikel zu entfernen, Anzahl <strong>0</strong> eingeben
|
||||
</div>
|
||||
</div>
|
||||
<div style="float:right;">
|
||||
<div style="float:right; text-align:right;">
|
||||
<input id="cashdesk_button" type="button" class="button" value=">> Zur Kasse" />
|
||||
{if isset($express_providers) && $express_providers}
|
||||
{foreach from=$express_providers item=xp}
|
||||
{if $xp.module == 'paypal_v2'}
|
||||
<div style="margin-top:10px;">
|
||||
<a href="/Kasse/?payment_express_start={$xp.module}" class="button express_button express_paypal" title="Direkt mit PayPal bezahlen – Adresse kommt von PayPal, Sie prüfen die Bestellung danach in der Kasse">
|
||||
<img src="https://www.paypalobjects.com/webstatic/de_DE/i/de-pp-logo-100px.png" alt="PayPal" style="height:22px; vertical-align:middle; margin-right:6px;">Direkt bezahlen
|
||||
</a>
|
||||
</div>
|
||||
{else}
|
||||
<div style="margin-top:10px;">
|
||||
<a href="/Kasse/?payment_express_start={$xp.module}" class="button express_button">Direkt mit {$xp.label|escape} bezahlen</a>
|
||||
</div>
|
||||
{/if}
|
||||
{/foreach}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<br style="clear:both;" />
|
||||
|
||||
Reference in New Issue
Block a user