Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AdminRender.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('
<input type="hidden" name="mondu_post_url" id="mondu_post_url" value="' . $postUrl . '">
<script type="text/javascript" src="' . $jsUrl . '"></script>
');
}

if ($tabName === 'Info') {
return $smarty
->assign('postUrl', Shop::getURL() . '/' . \PFAD_ADMIN . 'plugin.php?kPlugin=' . $this->plugin->getID())
Expand Down Expand Up @@ -93,6 +104,7 @@ private function handleRegisterWebhooksRequest()
$response,
Response::HTTP_UNPROCESSABLE_ENTITY
);
exit;
}
}

Expand Down
13 changes: 13 additions & 0 deletions Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
'<link rel="stylesheet" href="' . $pluginUrl . 'frontend/css/style.css?v=' . $version . '" type="text/css" media="all">'
);

return true;
}

Expand Down
6 changes: 4 additions & 2 deletions Migrations/Migration20221130105300.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
");
}

Expand All @@ -21,3 +22,4 @@ public function down()

}
}

75 changes: 62 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ 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.**
Expand All @@ -49,9 +50,25 @@ 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** Ratenkauf
4. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Echtzeitüberweisung
5. Configure action
1. Web-Request POST:
1. URL:
Expand All @@ -74,9 +91,25 @@ 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** Ratenkauf
4. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Echtzeitüberweisung
6. Configure action
1. Web-Request POST:
1. URL:
Expand All @@ -97,10 +130,26 @@ Ratenzahlung - Bequem in Raten per Bankeinzug zahlen
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** Ratenkauf
4. Rechnungen\Auftrag\Zahlungsart\Name **Enthalt** Echtzeitüberweisung
5. Configure action
1. Web-Request POST:
1. URL:
Expand Down
132 changes: 112 additions & 20 deletions Src/Controllers/Frontend/WebhookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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];
}
}

Expand All @@ -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);
}

Expand All @@ -91,7 +136,6 @@ public function handleOrderStateChanged($requestData)
/**
* @param $requestData
* @param $state
*
* @return array
*/
public function handleInvoiceStateChanged($requestData, $state): array
Expand Down Expand Up @@ -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;
}

/**
Expand Down
10 changes: 0 additions & 10 deletions Src/Services/ConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
9 changes: 8 additions & 1 deletion Src/Support/HttpClients/MonduClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
];
}
}

Expand Down
Loading