diff --git a/AdminRender.php b/AdminRender.php
index 40d8c03..d053381 100644
--- a/AdminRender.php
+++ b/AdminRender.php
@@ -55,6 +55,17 @@ public function renderPage(string $tabName, JTLSmarty $smarty, $request): string
$smarty->assign('pluginURL', $this->plugin->getPaths()->getShopURL());
+ // Add JS script to Settings page for webhooks_secret field
+ if ($tabName === 'Settings') {
+ $jsUrl = $this->plugin->getPaths()->getAdminURL() . 'js/mondu_admin.js?v=3.0.8';
+ $postUrl = Shop::getURL() . '/' . \PFAD_ADMIN . 'plugin.php?kPlugin=' . $this->plugin->getID();
+
+ pq('body')->append('
+
+
+ ');
+ }
+
if ($tabName === 'Info') {
return $smarty
->assign('postUrl', Shop::getURL() . '/' . \PFAD_ADMIN . 'plugin.php?kPlugin=' . $this->plugin->getID())
@@ -93,6 +104,7 @@ private function handleRegisterWebhooksRequest()
$response,
Response::HTTP_UNPROCESSABLE_ENTITY
);
+ exit;
}
}
diff --git a/Bootstrap.php b/Bootstrap.php
index bbeec05..dc04927 100644
--- a/Bootstrap.php
+++ b/Bootstrap.php
@@ -80,6 +80,19 @@ public function prepareFrontend(LinkInterface $link, JTLSmarty $smarty): bool
$routes = new RoutesService;
$routes->frontEndRoutes($this->getPlugin());
+ // Register CSS and JS files via Smarty
+ $pluginUrl = $this->getPlugin()->getPaths()->getBaseURL();
+ $version = $this->getPlugin()->getMeta()->getVersion();
+
+ // Add CSS to head
+ Shop::Smarty()->assign('monduPluginUrl', $pluginUrl);
+ Shop::Smarty()->assign('monduPluginVersion', $version);
+
+ // Register in page header
+ \pq('head')->append(
+ ''
+ );
+
return true;
}
diff --git a/Migrations/Migration20221130105300.php b/Migrations/Migration20221130105300.php
index f990fa2..b29cb1f 100644
--- a/Migrations/Migration20221130105300.php
+++ b/Migrations/Migration20221130105300.php
@@ -9,10 +9,11 @@ class Migration20221130105300 extends Migration implements IMigration
{
public function up()
{
+ // Clear all hint texts for Mondu payment methods instead of setting them
$this->execute("
UPDATE `tzahlungsartsprache` zs
- SET zs.`cHinweisTextShop` = 'Hinweise zur Verarbeitung Ihrer personenbezogenen Daten durch die Mondu GmbH finden Sie [url=https://www.mondu.ai/de/datenschutzgrundverordnung-kaeufer/]hier[/url].'
- WHERE zs.`cGebuehrname` = 'Mondu' and zs.`cISOSprache` IN ('ger', 'eng');
+ SET zs.`cHinweisTextShop` = '', zs.`cHinweisText` = ''
+ WHERE zs.`cGebuehrname` = 'Mondu';
");
}
@@ -21,3 +22,4 @@ public function down()
}
}
+
diff --git a/README.md b/README.md
index f0148ff..a683e9c 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ JTL 5 Integration plugin for Mondu Payment.
2. Expand **Installed plug-ins** menu item on the left side and choose Mondu Payment
3. Configure the fields:
* API Sandbox Mode: Select yes to point the plugin to the sandbox environment
- * Fill in API Secret
+ * Fill in API Secret (will be done automatically)
* Save configuration
* Click register webhooks button
@@ -35,13 +35,13 @@ JTL 5 Integration plugin for Mondu Payment.
2. Add following payment methods:
```
-Rechnungskauf - jetzt kaufen, später bezahlen
-SEPA-Lastschrift - jetzt kaufen, später per Bankeinzug bezahlen
-Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
+Ratenkauf (3, 6, 12 Monaten)
+SEPA-Lastschrift (30 Tage)
+Rechnungskauf (30 Tage)
+Echtzeitüberweisung
```
-**Note: In case Payment Method names are changed manually in the JTL Shop, please update accordingly in the JTL Wawi.**
-
+**Note: Please add or modify the net terms accordingly your contractual agreement.
In case the Mondu Payment Method names are changed manually in the JTL Shop, please update them accordingly in JTL Wawi.**
### Create Invoice Workflow
@@ -49,24 +49,38 @@ Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
2. Select **Rechnungen** tab
3. Select Rechnungen -> Erstellt -> Rechnungen_Erstellt workflow
4. Configure condition with "One condition met" (Eine Bedingung erfüllt")
- 1. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rechnungskauf - jetzt kaufen, später bezahlen
- 2. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** SEPA-Lastschrift - jetzt kaufen, später per Bankeinzug bezahlen
- 3. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
+ ### New installation:
+ Just create the workflows with the name of the payment methods which are mentioned in step 2 (add payment methods):
+ * Ratenkauf (3, 6, 12 Monaten)
+ * SEPA-Lastschrift (30 Tage)
+ * Rechnungskauf (30 Tage)
+ * Echtzeitüberweisung
+
+ ### Update from existing installation:
+ Update the conditions so that the rules will match for old AND new naming e.g.:
+ * Ratenkauf (3, 6, 12 Monaten)
+ * SEPA-Lastschrift (30 Tage)
+ * Rechnungskauf (30 Tage)
+ * Echtzeitüberweisung
OR create additional rules so that all new payment methods will be covered
+
+ 1. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rechnungskauf
+ 2. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** SEPA-Lastschrift
+ 3. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rate
+ 4. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Echtzeitüberweisung
5. Configure action
1. Web-Request POST:
- 1. URL:
- ```
- http://{SHOP-URL}/mondu-api?return=invoice-create&webhooks_secret={WEBHOOK SECRET}
- ```
- 2. Parameter:
- ```
- gross_amount_cents={{ Vorgang.Auftrag.Positionen.BruttopreisGesamt2 }}&net_amount_cents={{ Vorgang.Auftrag.Positionen.NettopreisGesamt2 }}&invoice_id={{ Vorgang.Rechnungsnummer }}&order_id={{ Vorgang.Auftrag.ExterneAuftragsnummer }}
- ```
- 3. Header:
- ```
- Content-Type: application/x-www-form-urlencoded
- ```
-
+ 1. URL:
+ ```
+ http://{SHOP-URL}/mondu-api?return=invoice-create&webhooks_secret={WEBHOOK SECRET}
+ ```
+ 2. Parameter:
+ ```
+ gross_amount_cents={{ Vorgang.Auftrag.Positionen.BruttopreisGesamt2 }}&net_amount_cents={{ Vorgang.Auftrag.Positionen.NettopreisGesamt2 }}&invoice_id={{ Vorgang.Rechnungsnummer }}&order_id={{ Vorgang.Auftrag.ExterneAuftragsnummer }}
+ ```
+ 3. Header:
+ ```
+ Content-Type: application/x-www-form-urlencoded
+ ```
### Cancel Invoice Workflow
1. Navigate to the Admin -> JTL-Workflows
@@ -74,48 +88,76 @@ Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
3. Select Rechnungen - Manuell, create new Event with "Ereignis anlegen" button
4. Create new event
4. Configure condition with "One condition met" (Eine Bedingung erfüllt")
- 1. Auftrag\Zahlungsart\Name **Enthalt** Rechnungskauf - jetzt kaufen, später bezahlen
- 2. Auftrag\Zahlungsart\Name **Enthalt** SEPA-Lastschrift - jetzt kaufen, später per Bankeinzug bezahlen
- 3. Auftrag\Zahlungsart\Name **Enthalt** Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
+ ### New installation:
+ Just create the workflows with the name of the payment methods which are mentioned in step 2 (add payment methods):
+ * Ratenkauf (3, 6, 12 Monaten)
+ * SEPA-Lastschrift (30 Tage)
+ * Rechnungskauf (30 Tage)
+ * Echtzeitüberweisung
+
+ ### Update from existing installation:
+ Update the conditions so that the rules will match for old AND new naming e.g.:
+ * Ratenkauf (3, 6, 12 Monaten)
+ * SEPA-Lastschrift (30 Tage)
+ * Rechnungskauf (30 Tage)
+ * Echtzeitüberweisung
OR create additional rules so that all new payment methods will be covered
+
+ 1. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rechnungskauf
+ 2. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** SEPA-Lastschrift
+ 3. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rate
+ 4. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Echtzeitüberweisung
6. Configure action
- 1. Web-Request POST:
- 1. URL:
- ```
- http://{SHOP-URL}/mondu-api?return=cancel-invoice&webhooks_secret={WEBHOOK SECRET}
- ```
- 2. Parameter:
- ```
- invoice_number={{ Vorgang.Rechnungsnummer }}
- ```
- 3. Header:
- ```
- Content-Type: application/x-www-form-urlencoded
- ```
-
+ 1. Web-Request POST:
+ 1. URL:
+ ```
+ http://{SHOP-URL}/mondu-api?return=cancel-invoice&webhooks_secret={WEBHOOK SECRET}
+ ```
+ 2. Parameter:
+ ```
+ invoice_number={{ Vorgang.Rechnungsnummer }}
+ ```
+ 3. Header:
+ ```
+ Content-Type: application/x-www-form-urlencoded
+ ```
### Cancel Order Workflow
1. Navigate to the Admin -> JTL-Workflows
2. Select **Auftrage** tab
3. Select Auftrag -> Storniert and create a workflow
-4. 4. Configure condition with "One condition met" (Eine Bedingung erfüllt")
- 1. Zahlungen\Zahlungsart\Name **Enthalt** Rechnungskauf - jetzt kaufen, später bezahlen
- 2. Zahlungen\Zahlungsart\Name **Enthalt** SEPA-Lastschrift - jetzt kaufen, später per Bankeinzug bezahlen
- 3. Zahlungen\Zahlungsart\Name **Enthalt** Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
+4. Configure condition with "One condition met" (Eine Bedingung erfüllt")
+ ### New installation:
+ Just create the workflows with the name of the payment methods which are mentioned in step 2 (add payment methods):
+ * Ratenkauf (3, 6, 12 Monaten)
+ * SEPA-Lastschrift (30 Tage)
+ * Rechnungskauf (30 Tage)
+ * Echtzeitüberweisung
+
+ ### Update from existing installation:
+ Update the conditions so that the rules will match for old AND new naming e.g.:
+ * Ratenkauf (3, 6, 12 Monaten)
+ * SEPA-Lastschrift (30 Tage)
+ * Rechnungskauf (30 Tage)
+ * Echtzeitüberweisung
OR create additional rules so that all new payment methods will be covered
+
+ 1. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rechnungskauf
+ 2. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** SEPA-Lastschrift
+ 3. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Rate
+ 4. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Echtzeitüberweisung
5. Configure action
- 1. Web-Request POST:
- 1. URL:
- ```
- http://{SHOP-URL}/mondu-api?return=cancel-order&webhooks_secret={WEBHOOK SECRET}
- ```
- 2. Parameter:
- ```
- order_number={{ Vorgang.Stammdaten.ExterneAuftragsnummer }}
- ```
- 3. Header:
- ```
- Content-Type: application/x-www-form-urlencoded
- ```
-
+ 1. Web-Request POST:
+ 1. URL:
+ ```
+ http://{SHOP-URL}/mondu-api?return=cancel-order&webhooks_secret={WEBHOOK SECRET}
+ ```
+ 2. Parameter:
+ ```
+ order_number={{ Vorgang.Stammdaten.ExterneAuftragsnummer }}
+ ```
+ 3. Header:
+ ```
+ Content-Type: application/x-www-form-urlencoded
+ ```

### Configure Invoice Template
diff --git a/Src/Controllers/Frontend/WebhookController.php b/Src/Controllers/Frontend/WebhookController.php
index 8d43df3..4f3f319 100644
--- a/Src/Controllers/Frontend/WebhookController.php
+++ b/Src/Controllers/Frontend/WebhookController.php
@@ -3,7 +3,6 @@
namespace Plugin\MonduPayment\Src\Controllers\Frontend;
use JTL\Shop;
-use Plugin\MonduPayment\PaymentMethod\MonduPayment;
use Plugin\MonduPayment\Src\Exceptions\DatabaseQueryException;
use Plugin\MonduPayment\Src\Helpers\Response;
use Plugin\MonduPayment\Src\Models\MonduInvoice;
@@ -13,10 +12,10 @@
class WebhookController
{
public const MONDU_JTL_MAPPING = [
- MonduPayment::STATE_CONFIRMED => \BESTELLUNG_STATUS_BEZAHLT,
- MonduPayment::STATE_PENDING => \BESTELLUNG_STATUS_IN_BEARBEITUNG,
- MonduPayment::STATE_CANCELED => \BESTELLUNG_STATUS_STORNO,
- MonduPayment::STATE_DECLINED => \BESTELLUNG_STATUS_STORNO,
+ 'confirmed' => \BESTELLUNG_STATUS_BEZAHLT,
+ 'pending' => \BESTELLUNG_STATUS_IN_BEARBEITUNG,
+ 'canceled' => \BESTELLUNG_STATUS_STORNO,
+ 'declined' => \BESTELLUNG_STATUS_STORNO,
];
private Request $request;
@@ -43,17 +42,26 @@ public function index()
*/
private function handleWebhook()
{
- $requestData = $this->request->all();
-
- switch ($requestData['topic']) {
- case 'order/confirmed':
- case 'order/declined':
- case 'order/pending':
- return $this->handleOrderStateChanged($requestData);
- case 'invoice/canceled':
- return $this->handleInvoiceStateChanged($requestData, 'canceled');
- default:
- return [['message' => 'Unregistered topic'], Response::HTTP_OK];
+ try {
+ $requestData = $this->request->all();
+
+ // Проверяем наличие topic
+ if (!isset($requestData['topic'])) {
+ return [['message' => 'Missing topic parameter', 'received_data' => $requestData], Response::HTTP_BAD_REQUEST];
+ }
+
+ switch ($requestData['topic']) {
+ case 'order/confirmed':
+ case 'order/declined':
+ case 'order/pending':
+ return $this->handleOrderStateChanged($requestData);
+ case 'invoice/canceled':
+ return $this->handleInvoiceStateChanged($requestData, 'canceled');
+ default:
+ return [['message' => 'Unregistered topic: ' . $requestData['topic'], 'available_topics' => ['order/confirmed', 'order/declined', 'order/pending', 'invoice/canceled']], Response::HTTP_OK];
+ }
+ } catch (\Exception $e) {
+ return [['message' => 'Error processing webhook', 'error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR];
}
}
@@ -75,13 +83,50 @@ public function handleOrderStateChanged($requestData)
return [['message' => 'Order not found'], Response::HTTP_NOT_FOUND];
}
- $this->monduOrder->update(['state' => $params['order_state']], $monduOrder->id);
+ // If order found via fallback (id is null), create mondu_orders record
+ if (empty($monduOrder->id)) {
+ $monduOrderData = [
+ 'order_id' => $monduOrder->order_id,
+ 'external_reference_id' => $monduOrder->external_reference_id,
+ 'order_uuid' => $requestData['order_uuid'] ?? null,
+ 'state' => $params['order_state']
+ ];
+
+ // Use direct SQL insert
+ $pdo = new \PDO(
+ 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME,
+ DB_USER,
+ DB_PASS
+ );
+
+ $stmt = $pdo->prepare("
+ INSERT INTO mondu_orders
+ (order_id, external_reference_id, order_uuid, state, created_at, updated_at)
+ VALUES (?, ?, ?, ?, NOW(), NOW())
+ ");
+
+ $stmt->execute([
+ $monduOrderData['order_id'],
+ $monduOrderData['external_reference_id'],
+ $monduOrderData['order_uuid'],
+ $monduOrderData['state']
+ ]);
+
+ $newId = $pdo->lastInsertId();
+
+ // Update monduOrder with new ID
+ $monduOrder->id = $newId;
+ $monduOrder->order_uuid = $monduOrderData['order_uuid'];
+ } else {
+ // Update existing record
+ $this->monduOrder->update(['state' => $params['order_state']], $monduOrder->id);
+ }
if (isset(self::MONDU_JTL_MAPPING[$params['order_state']])) {
$this->updateOrderStatus($monduOrder, self::MONDU_JTL_MAPPING[$params['order_state']]);
}
- if ($params['order_state'] === MonduPayment::STATE_CONFIRMED) {
+ if ($params['order_state'] === 'confirmed') {
$this->unlockOrderForWawiSync($monduOrder);
}
@@ -91,7 +136,6 @@ public function handleOrderStateChanged($requestData)
/**
* @param $requestData
* @param $state
- *
* @return array
*/
public function handleInvoiceStateChanged($requestData, $state): array
@@ -122,7 +166,55 @@ public function handleInvoiceStateChanged($requestData, $state): array
*/
private function getOrder($orderUuid)
{
- return $this->monduOrder->select('id', 'order_uuid', 'order_id')->where('external_reference_id', $orderUuid)->first()[0];
+ // Try to find in mondu_orders by external_reference_id
+ $query = $this->monduOrder->select('id', 'order_uuid', 'order_id')->where('external_reference_id', $orderUuid);
+ $result = $query->first();
+
+ if (is_array($result) && isset($result[0])) {
+ return $result[0];
+ }
+
+ // Fallback: Search in tbestellung
+ $jtlOrderId = null;
+
+ // Extract JTL order ID from external_reference_id (e.g. "JTL5-10005" -> 10005)
+ if (preg_match('/^[A-Z0-9]+-(\d+)$/', $orderUuid, $matches)) {
+ $jtlOrderId = (int)$matches[1];
+ } elseif (is_numeric($orderUuid)) {
+ $jtlOrderId = (int)$orderUuid;
+ }
+
+ // Search in tbestellung
+ try {
+ $pdo = new \PDO(
+ 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
+ DB_USER,
+ DB_PASS,
+ [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]
+ );
+
+ // Search by cBestellNr first (most reliable), then by kBestellung
+ $stmt = $pdo->prepare("SELECT kBestellung FROM tbestellung WHERE cBestellNr = ? OR kBestellung = ?");
+ $stmt->execute([$orderUuid, $jtlOrderId ?: 0]);
+
+ $jtlOrder = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if ($jtlOrder) {
+ // Return a compatible format (object with order_id property)
+ $compatibleResult = new \stdClass();
+ $compatibleResult->id = null; // No mondu_orders record yet
+ $compatibleResult->order_id = $jtlOrder['kBestellung'];
+ $compatibleResult->order_uuid = null;
+ $compatibleResult->external_reference_id = $orderUuid;
+
+ return $compatibleResult;
+ }
+
+ } catch (\Exception $e) {
+ // Silent fail
+ }
+
+ return null;
}
/**
diff --git a/Src/Services/ConfigService.php b/Src/Services/ConfigService.php
index 8d6f915..0ebd85e 100644
--- a/Src/Services/ConfigService.php
+++ b/Src/Services/ConfigService.php
@@ -72,16 +72,6 @@ public function getPaymentMethodNameVisible()
return $this->config->getValue('payment_method_name_visible');
}
- public function getNetTermTitle()
- {
- return $this->config->getValue('net_term_title');
- }
-
- public function getNetTermDescription()
- {
- return $this->config->getValue('net_term_description');
- }
-
public function getPaymentMethodByKPlugin($kPlugin)
{
return $this->config->getValue($kPlugin . '_payment_method');
diff --git a/Src/Support/HttpClients/MonduClient.php b/Src/Support/HttpClients/MonduClient.php
index 725e22b..4eb6eca 100644
--- a/Src/Support/HttpClients/MonduClient.php
+++ b/Src/Support/HttpClients/MonduClient.php
@@ -138,7 +138,14 @@ public function registerWebhooks(array $data = []): ?array
return $this->client->post('webhooks', $data);
} catch (InvalidRequestException $e) {
$this->logEvent($e);
- return ['error' => true];
+ $exceptionData = $e->getExceptionData();
+ $responseBody = json_decode($exceptionData->response_body, true);
+
+ return [
+ 'error' => true,
+ 'message' => $responseBody['detail'] ?? $responseBody['message'] ?? 'Unknown error',
+ 'status_code' => $exceptionData->response_code
+ ];
}
}
diff --git a/adminmenu/js/mondu_admin.js b/adminmenu/js/mondu_admin.js
index 87ddd43..3796709 100644
--- a/adminmenu/js/mondu_admin.js
+++ b/adminmenu/js/mondu_admin.js
@@ -1,4 +1,12 @@
jQuery(document).ready(function() {
+ // Disable webhooks_secret field so users can't manually edit it
+ jQuery('#webhooks_secret').prop('disabled', true).css('background-color', '#e9ecef');
+
+ // Enable field before form submit so value gets sent
+ jQuery('form').on('submit', function() {
+ jQuery('#webhooks_secret').prop('disabled', false);
+ });
+
if (jQuery('.mondu_webhook_button').length === 0) {
jQuery('#webhooks_secret')
.parent()
@@ -28,13 +36,36 @@ jQuery(document).ready(function() {
},
success: function (result) {
if (result.success) {
- jQuery('#webhooks_secret').val(result.webhooks_secret);
+ // Temporarily enable field to update value, then disable again
+ jQuery('#webhooks_secret')
+ .prop('disabled', false)
+ .val(result.webhooks_secret)
+ .prop('disabled', true);
+ alert('Webhook registered successfully!');
} else {
alert('Something went wrong, make sure you filled out API Secret correctly and saved the configuration.')
}
},
- error: function() {
- alert('Something went wrong, make sure you filled out API Secret correctly and saved the configuration.')
+ error: function(xhr) {
+ let errorMessage = 'Something went wrong, make sure you filled out API Secret correctly and saved the configuration.';
+
+ // Check if status code is 422 (Unprocessable Entity) - webhooks already registered
+ if (xhr.status === 422) {
+ errorMessage = 'Webhooks are already registered.';
+ } else if (xhr.responseJSON && xhr.responseJSON.message) {
+ const apiMessage = xhr.responseJSON.message.toLowerCase();
+
+ // Double-check message content for webhook duplicates
+ if (apiMessage.includes('already exists') ||
+ apiMessage.includes('already registered') ||
+ apiMessage.includes('duplicate')) {
+ errorMessage = 'Webhooks are already registered.';
+ } else {
+ errorMessage = xhr.responseJSON.message;
+ }
+ }
+
+ alert(errorMessage);
},
complete: function() {
jQuery('#mondu_webhook_register_button').prop('disabled', false);
diff --git a/adminmenu/templates/mondu_info.tpl b/adminmenu/templates/mondu_info.tpl
index 875fe18..9dc58e6 100644
--- a/adminmenu/templates/mondu_info.tpl
+++ b/adminmenu/templates/mondu_info.tpl
@@ -12,4 +12,4 @@
Visit Mondu's website, the FAQ page or contact us via our contact form to find out more about Mondu.
- + diff --git a/frontend/css/style.css b/frontend/css/style.css index 7f599d5..1aa622b 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -1,3 +1,4 @@ +/* Updated: 1762960876 - CSS Cache Bust */ #mondu-checkout-widget>div { position: fixed; top: 0; @@ -54,14 +55,6 @@ background-color: #692CCF } - .mondu-payment-method-box .custom-control-label::before { - margin-top: 10px - } - - .mondu-payment-method-box .custom-control-label::after { - margin-top: 10px - } - .mondu-payment-method-box .custom-control-label span { margin-left: 10px } @@ -171,9 +164,19 @@ background: white; } +.mondu-description { + margin-left: 20px; +} + @-moz-document url-prefix() { .mondu-paypal-plus-enabled { margin-bottom: -80px; padding-bottom: 0; } - } \ No newline at end of file +} + +/* Mondu payment method images for non-grouped mode */ +.mondu-small { + max-width: 80px; + height: auto; +} \ No newline at end of file diff --git a/frontend/hooks/CheckoutConfirmPage.php b/frontend/hooks/CheckoutConfirmPage.php index b9f88d4..9504ab5 100644 --- a/frontend/hooks/CheckoutConfirmPage.php +++ b/frontend/hooks/CheckoutConfirmPage.php @@ -14,13 +14,13 @@ public function execute(): void { if (!$this->isMonduPayment() || !$this->isConfirmStep()) return; - if ($_GET['payment'] !== 'accepted' && $_SERVER['REQUEST_METHOD'] === 'GET') { + if (isset($_GET['payment']) && $_GET['payment'] !== 'accepted' && $_SERVER['REQUEST_METHOD'] === 'GET') { header('Location: ' . Shop::Container()->getLinkService()->getStaticRoute('bestellvorgang.php') . '?editZahlungsart=1'); } - if ($_GET['payment'] == 'accepted' || !$this->isMonduOrderSessionMissing()) return; + if (isset($_GET['payment']) && $_GET['payment'] == 'accepted' || !$this->isMonduOrderSessionMissing()) return; - if ($_GET['monduCreateOrder'] === 'true') { + if (isset($_GET['monduCreateOrder']) && $_GET['monduCreateOrder'] === 'true') { $orderService = new OrderService(); $orderData = $orderService->token($_SESSION['Zahlungsart']->cModulId); header('Location: ' . $orderData['hosted_checkout_url'], true, 303); diff --git a/frontend/hooks/CheckoutPaymentMethod.php b/frontend/hooks/CheckoutPaymentMethod.php index 8382459..099dfac 100644 --- a/frontend/hooks/CheckoutPaymentMethod.php +++ b/frontend/hooks/CheckoutPaymentMethod.php @@ -56,12 +56,9 @@ public function execute(array $args_arr = []): void */ private function filterPaymentMethods(): void { - $allowedPaymentMethodsCache = $this->cache->get('mondu_payment_methods'); - $allowedPaymentMethods = $allowedPaymentMethodsCache ?: $this->getAllowedPaymentMethods(); - + $allowedPaymentMethods = $this->getAllowedPaymentMethods(); $paymentMethods = $this->smarty->getTemplateVars('Zahlungsarten'); $monduPaymentMethods = []; - $allowedNetTerms = $this->getAllowedNetTerms(); foreach ($paymentMethods as $key => $method) { @@ -74,13 +71,33 @@ private function filterPaymentMethods(): void continue; } + // Set localized image based on payment type (for non-grouped mode) + switch ($paymentMethodType) { + case 'invoice': + $method->cBild = $this->getPaymentMethodImage('invoice'); + break; + case 'direct_debit': + $method->cBild = $this->getPaymentMethodImage('direct_debit'); + break; + case 'installment': + $method->cBild = $this->getPaymentMethodImage('installment'); + break; + case 'pay_now': + $method->cBild = $this->getPaymentMethodImage('pay_now'); + break; + default: + // Keep default image from database + break; + } + $netTerm = (int) $this->configService->getPaymentMethodNetTerm($method->cModulId); - // Installments + // Installments and Pay Now don't have net terms - no filtering needed if (!$netTerm) { continue; } + // For invoice and direct_debit, check if net term is allowed if (!in_array($netTerm, $allowedNetTerms)) { unset($paymentMethods[$key]); } @@ -97,10 +114,10 @@ private function filterPaymentMethods(): void private function isPaymentGroupingEnabled(): bool { $groupEnabled = $this->configService->getPaymentMethodGroupEnabled() == '1'; - $paymentMethodNameVisible = $this->configService->getPaymentMethodNameVisible() == '1'; $this->smarty->assign('paymentMethodGroupEnabled', $groupEnabled); - $this->smarty->assign('paymentMethodNameVisible', $paymentMethodNameVisible); + // Always show payment method name - no configuration needed + $this->smarty->assign('paymentMethodNameVisible', true); return $groupEnabled; } @@ -130,56 +147,153 @@ private function createMonduGroups(): void { $availablePaymentMethods = $this->smarty->getTemplateVars('Zahlungsarten'); - $allowedNetTermsCache = $this->cache->get('mondu_net_terms'); - $netTerms = $allowedNetTermsCache ?: $this->getAllowedNetTerms(); + // Temporarily disable cache - always fetch fresh data from API + $netTerms = $this->getAllowedNetTerms(); $monduGroups = []; // Config $benefits = $this->configService->getBenefitsText(); - $netTermTitle = $this->configService->getNetTermTitle(); - $netTermDescription = $this->configService->getNetTermDescription(); - // Invoice & Direct Debit + // Collect all Invoice methods with different net terms + $invoiceMethods = []; + $invoiceNetTerms = []; + foreach ($netTerms as $netTerm) { - $paymentMethods = array_filter($availablePaymentMethods, function ($method) use ($netTerm) { - return $method->cAnbieter == 'Mondu' && str_contains( $method->cModulId, $netTerm . 'tagen' ); + $methods = array_filter($availablePaymentMethods, function ($method) use ($netTerm) { + $paymentMethodType = $this->configService->getPaymentMethodByKPlugin($method->cModulId); + return $method->cAnbieter == 'Mondu' && + $paymentMethodType === 'invoice' && + str_contains($method->cModulId, $netTerm . 'tage'); }); - - foreach ($paymentMethods as $method) { - $paymentMethodType = $this->configService->getPaymentMethodByKPlugin($method->cModulId); - $method->monduBenefits = str_replace('{net_term}', $netTerm, $this->__translate($benefits[$paymentMethodType])); + + foreach ($methods as $method) { + $method->monduBenefits = str_replace('{net_term}', $netTerm, $this->__translate($benefits['invoice'])); + $method->monduNetTerm = $netTerm; // Store net term for sorting + $method->cBild = $this->getPaymentMethodImage('invoice'); // Set localized image + $invoiceMethods[] = $method; + $invoiceNetTerms[] = $netTerm; } + } + + // Group all Invoice methods together if any exist + if (count($invoiceMethods) > 0) { + // Sort payment methods by net term (14, 30, 45, 60, 90) + usort($invoiceMethods, function($a, $b) { + return $a->monduNetTerm <=> $b->monduNetTerm; + }); + + $uniqueNetTerms = array_unique($invoiceNetTerms); + sort($uniqueNetTerms); + $netTermsText = implode(', ', $uniqueNetTerms) . ' ' . $this->__translate('Tage'); + + $monduGroups[] = [ + 'title' => $this->__translate('Rechnungskauf') . ' (' . $netTermsText . ')', + 'description' => '', + 'image' => $this->getPaymentMethodImage('invoice'), + 'payment_methods' => $invoiceMethods + ]; + } - if (count($paymentMethods) != 0) { - $monduGroups[] = [ - 'title' => str_replace('{net_term}', $netTerm, $netTermTitle), - 'description' => str_replace('{net_term}', $netTerm, $netTermDescription), - 'payment_methods' => $paymentMethods - ]; + // Collect all SEPA methods with different net terms + $sepaMethods = []; + $sepaNetTerms = []; + + foreach ($netTerms as $netTerm) { + $methods = array_filter($availablePaymentMethods, function ($method) use ($netTerm) { + $paymentMethodType = $this->configService->getPaymentMethodByKPlugin($method->cModulId); + return $method->cAnbieter == 'Mondu' && + $paymentMethodType === 'direct_debit' && + str_contains($method->cModulId, $netTerm . 'tage'); + }); + + foreach ($methods as $method) { + $method->monduBenefits = str_replace('{net_term}', $netTerm, $this->__translate($benefits['direct_debit'])); + $method->monduNetTerm = $netTerm; // Store net term for sorting + $method->cBild = $this->getPaymentMethodImage('direct_debit'); // Set localized image + $sepaMethods[] = $method; + $sepaNetTerms[] = $netTerm; } } - // Installments + // Group all SEPA methods together if any exist + if (count($sepaMethods) > 0) { + // Sort payment methods by net term (14, 30, 45, 60, 90) + usort($sepaMethods, function($a, $b) { + return $a->monduNetTerm <=> $b->monduNetTerm; + }); + + $uniqueNetTerms = array_unique($sepaNetTerms); + sort($uniqueNetTerms); + $netTermsText = implode(', ', $uniqueNetTerms) . ' ' . $this->__translate('Tage'); + + $monduGroups[] = [ + 'title' => $this->__translate('SEPA-Lastschrift') . ' (' . $netTermsText . ')', + 'description' => '', + 'image' => $this->getPaymentMethodImage('direct_debit'), + 'payment_methods' => $sepaMethods + ]; + } + + // Installments (Ratenkauf) - Collect all installment methods and show as ONE method $installmentPaymentMethods = array_filter($availablePaymentMethods, function ($method) { - return $method->cAnbieter == 'Mondu' && str_contains($method->cModulId, 'monduratenzahlung'); + // Search for 'ratenkauf' in cModulId (DB has 'kPlugin_2_ratenkauf(3,6,12monaten)') + return $method->cAnbieter == 'Mondu' && + (str_contains(strtolower($method->cModulId), 'ratenkauf') || + str_contains(strtolower($method->cModulId), 'installment')); }); + $installmentPeriods = []; foreach ($installmentPaymentMethods as $method) { - $paymentMethodType = $this->configService->getPaymentMethodByKPlugin($method->cModulId); - $method->monduBenefits = $this->__translate($benefits[$paymentMethodType]); + $paymentMethodType = $this->configService->getPaymentMethodByKPlugin($method->cModulId); + if (isset($benefits[$paymentMethodType]) && $benefits[$paymentMethodType]) { + $method->monduBenefits = $this->__translate($benefits[$paymentMethodType]); + } else { + $method->monduBenefits = ''; + } + + $method->cBild = $this->getPaymentMethodImage('installment'); // Set localized image + + // Extract all periods from module ID (e.g., "ratenkauf(3,6,12monaten)" -> "3, 6, 12") + // The DB has format like: kPlugin_2_ratenkauf(3,6,12monaten) + if (preg_match('/ratenkauf\(([\d,]+)monaten\)/i', $method->cModulId, $matches)) { + $periods = explode(',', $matches[1]); + $installmentPeriods = array_merge($installmentPeriods, $periods); + } } + // Create ONE Ratenkauf method (no grouping) with all periods in title if (count($installmentPaymentMethods) > 0) { - $installment = reset($installmentPaymentMethods); - - if (count($installmentPaymentMethods) != 0) { - $monduGroups[] = [ - 'title' => $installment->angezeigterName[$_SESSION['cISOSprache']], - 'description' => $installment->cHinweisText[$_SESSION['cISOSprache']], - 'payment_methods' => $installmentPaymentMethods - ]; - } + $uniquePeriods = array_unique($installmentPeriods); + sort($uniquePeriods, SORT_NUMERIC); + $periodsText = implode(', ', $uniquePeriods) . ' ' . $this->__translate('Monaten'); + + $monduGroups[] = [ + 'title' => $this->__translate('Ratenkauf') . ' (' . $periodsText . ')', + 'description' => '', + 'image' => $this->getPaymentMethodImage('installment'), + 'payment_methods' => $installmentPaymentMethods + ]; + } + + // Pay Now (Echtzeitüberweisung) - NO grouping, show as separate method + $payNowPaymentMethods = array_filter($availablePaymentMethods, function ($method) { + $paymentMethodType = $this->configService->getPaymentMethodByKPlugin($method->cModulId); + return $method->cAnbieter == 'Mondu' && $paymentMethodType === 'pay_now'; + }); + + foreach ($payNowPaymentMethods as $method) { + // Pay Now doesn't have benefits configured, use empty string + $method->monduBenefits = ''; + $method->cBild = $this->getPaymentMethodImage('pay_now'); // Set localized image + + // Each Pay Now method as separate group (no grouping) + $monduGroups[] = [ + 'title' => $method->angezeigterName[$_SESSION['cISOSprache']], + 'description' => '', + 'image' => $this->getPaymentMethodImage('pay_now'), + 'payment_methods' => [$method] + ]; } $this->smarty->assign('Zahlungsarten', array_filter($availablePaymentMethods, function ($method) { @@ -199,8 +313,6 @@ private function getAllowedPaymentMethods() $apiAllowedPaymentMethods = $this->monduClient->getPaymentMethods(); if (!isset($apiAllowedPaymentMethods['payment_methods'])){ - $this->debugger->log('[ERROR]: Get Payment Methods request failed.'); - $this->setMonduPaymentMethodsCache(['error']); return ['error']; } @@ -214,8 +326,6 @@ private function getAllowedPaymentMethods() return $allowedPaymentMethods; } catch (Exception $e) { - $this->debugger->log('[ERROR]: Get Allowed Payment Methods failed with exception: ' . $e->getMessage()); - $this->setMonduPaymentMethodsCache(['error']); return ['error']; } @@ -230,25 +340,23 @@ private function getAllowedNetTerms(): array $allowedNetTerms = $this->monduClient->getNetTerms(); if(!isset($allowedNetTerms['payment_terms'])){ - $this->debugger->log('[ERROR]: Get Net Terms request failed.'); - - $this->setMonduNetTermsCache([]); + $this->setMonduNetTermsCache([]); return []; } - $netTerms = array_unique(array_filter(array_map(function ($paymentMethod) { - if ($this->getBuyerCountryCode() == $paymentMethod['country_code']){ + $buyerCountry = $this->getBuyerCountryCode(); + + $netTerms = array_unique(array_filter(array_map(function ($paymentMethod) use ($buyerCountry) { + if ($buyerCountry == $paymentMethod['country_code']){ return $paymentMethod['net_term']; } }, (array) $allowedNetTerms['payment_terms']))); - $this->setMonduNetTermsCache($netTerms); + $this->setMonduNetTermsCache($netTerms); - return $netTerms; + return $netTerms; } catch (Exception $e) { - $this->debugger->log('[ERROR]: Get Allowed Net Terms failed with exception: ' . $e->getMessage()); - $this->setMonduNetTermsCache([]); return []; } @@ -283,6 +391,57 @@ public function setMonduNetTermsCache($methods): void { $this->cache->set('mondu_net_terms', $methods, ['mondu'], 3600); } + + /** + * Get payment method image based on locale and payment type + * + * @param string $paymentType (invoice, direct_debit, installment, pay_now) + * @return string Path to image + */ + private function getPaymentMethodImage(string $paymentType): string + { + // Get current language ISO code + $currentLang = Shop::Lang()->getIso(); // Returns 'ger', 'eng', etc. + + // Map ISO codes to locale folders + $localeMap = [ + 'ger' => 'de', + 'eng' => 'en', + 'dut' => 'nl', + 'fre' => 'fr' + ]; + + // Map payment types to image filenames + $imageMap = [ + 'invoice' => 'invoice_white_rectangle.png', + 'direct_debit' => 'sepa_white_rectangle.png', + 'installment' => 'installments_white_rectangle.png', + 'pay_now' => 'instant_pay_white_rectangle.png' + ]; + + // Get locale folder (default to 'de' if not mapped) + $locale = $localeMap[$currentLang] ?? 'de'; + + // Get image filename + $imageFilename = $imageMap[$paymentType] ?? null; + + if (!$imageFilename) { + // Return default image if payment type not recognized + return $this->plugin->getPaths()->getBaseURL() . 'paymentmethod/images/plugin.png'; + } + + // Build localized image path + $localizedImagePath = $this->plugin->getPaths()->getBaseURL() . 'paymentmethod/images/' . $locale . '/' . $imageFilename; + $localizedImageFile = $this->plugin->getPaths()->getBasePath() . 'paymentmethod/images/' . $locale . '/' . $imageFilename; + + // Check if localized image exists + if (file_exists($localizedImageFile)) { + return $localizedImagePath; + } + + // Fallback to default image + return $this->plugin->getPaths()->getBaseURL() . 'paymentmethod/images/plugin.png'; + } } $hook = new CheckoutPaymentMethod(); diff --git a/frontend/mondu_api.php b/frontend/mondu_api.php new file mode 100644 index 0000000..a8f31f4 --- /dev/null +++ b/frontend/mondu_api.php @@ -0,0 +1,8 @@ +frontEndRoutes($oPlugin); + diff --git a/frontend/template/checkout/inc_payment_methods.tpl b/frontend/template/checkout/inc_payment_methods.tpl index 7193607..f820cb1 100644 --- a/frontend/template/checkout/inc_payment_methods.tpl +++ b/frontend/template/checkout/inc_payment_methods.tpl @@ -1,25 +1,24 @@ extends file="{$parent_template_path}/checkout/inc_payment_methods.tpl"} -{block name='checkout-inc-payment-methods-note'} +{block name='checkout-inc-payment-methods-image-title'} {if $zahlungsart->cAnbieter == 'Mondu'} - - {$zahlungsart->cHinweisText|trans|replace: "[br]":"{$group['title']}
-{$group['description']|replace: "[br]":"
"|replace:"[b]":""|replace:"[/b]":""|replace:"[url=": ""|replace:"]":"\" >"}
-