diff --git a/core/paymentmethods/paypal_v2paymentmethod.class.php b/core/paymentmethods/paypal_v2paymentmethod.class.php index 551870f..81489bf 100644 --- a/core/paymentmethods/paypal_v2paymentmethod.class.php +++ b/core/paymentmethods/paypal_v2paymentmethod.class.php @@ -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; + } } diff --git a/core/payments/AbstractProvider.php b/core/payments/AbstractProvider.php index 86cda75..3308d2c 100644 --- a/core/payments/AbstractProvider.php +++ b/core/payments/AbstractProvider.php @@ -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, '']; + } } diff --git a/core/payments/ProviderInterface.php b/core/payments/ProviderInterface.php index dbe6f28..aa61757 100644 --- a/core/payments/ProviderInterface.php +++ b/core/payments/ProviderInterface.php @@ -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 $query $_GET der Rueckkehr-URL + * @return array|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. diff --git a/core/payments/install.sql b/core/payments/install.sql index b6c441f..0e0bba7 100644 --- a/core/payments/install.sql +++ b/core/payments/install.sql @@ -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). diff --git a/core/payments/providers/paypal_v2/Provider.php b/core/payments/providers/paypal_v2/Provider.php index ad4cb9a..195355d 100644 --- a/core/payments/providers/paypal_v2/Provider.php +++ b/core/payments/providers/paypal_v2/Provider.php @@ -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 diff --git a/modules/website_cashdesk.php b/modules/website_cashdesk.php index 0089b3b..b10d1c6 100644 --- a/modules/website_cashdesk.php +++ b/modules/website_cashdesk.php @@ -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= */ + 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=&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. * diff --git a/modules/website_shoppingcart.php b/modules/website_shoppingcart.php index d4d863a..335df12 100644 --- a/modules/website_shoppingcart.php +++ b/modules/website_shoppingcart.php @@ -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 */ + 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) { diff --git a/themes/easyshop_advanced/templates/content_cash_desk.tpl b/themes/easyshop_advanced/templates/content_cash_desk.tpl index 6ae90ff..f521d25 100644 --- a/themes/easyshop_advanced/templates/content_cash_desk.tpl +++ b/themes/easyshop_advanced/templates/content_cash_desk.tpl @@ -18,6 +18,12 @@
{if isset($guest_order) && $guest_order == 1}

{/if} + {if isset($express_active) && $express_active} +
+ Express-Kauf: 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. +
+ {/if}
{include file='content_cash_desk/address_invoice.tpl'}
diff --git a/web/intelectra_shop/themes/theme2/templates/content_shoppingcart.tpl b/web/intelectra_shop/themes/theme2/templates/content_shoppingcart.tpl index b37cdda..fe6156b 100644 --- a/web/intelectra_shop/themes/theme2/templates/content_shoppingcart.tpl +++ b/web/intelectra_shop/themes/theme2/templates/content_shoppingcart.tpl @@ -144,8 +144,23 @@ *Um Artikel zu entfernen, Anzahl 0 eingeben
-
+
+ {if isset($express_providers) && $express_providers} + {foreach from=$express_providers item=xp} + {if $xp.module == 'paypal_v2'} + + {else} + + {/if} + {/foreach} + {/if}