diff --git a/Bootstrap.php b/Bootstrap.php
new file mode 100755
index 0000000..3ca8da6
--- /dev/null
+++ b/Bootstrap.php
@@ -0,0 +1,89 @@
+getPlugin());
+
+ // Custom frontend operations for the Novalnet Plugin
+ $dispatcher->listen('shop.hook.' . \HOOK_BESTELLABSCHLUSS_INC_BESTELLUNGINDB, [$novalnetHookHandler, 'orderStatusPage']);
+ $dispatcher->listen('shop.hook.' . \HOOK_SMARTY_OUTPUTFILTER, [$novalnetHookHandler, 'contentUpdate']);
+ $dispatcher->listen('shop.hook.' . \HOOK_BESTELLVORGANG_PAGE, [$novalnetHookHandler, 'removeSavedDetails']);
+ $dispatcher->listen('shop.hook.' . \HOOK_JTL_PAGE, [$novalnetHookHandler, 'accountPage']);
+ $dispatcher->listen('shop.hook.' . \HOOK_BESTELLABSCHLUSS_INC_BESTELLUNGINDB_ENDE, [$novalnetHookHandler, 'changeWawiPickupStatus']);
+ $dispatcher->listen('shop.hook.' . \HOOK_IO_HANDLE_REQUEST, function (array $args) {
+ $novalnetPostDataController = new NovalnetPostDataController($args['io'], $args['request'], $this->getPlugin());
+ $novalnetPostDataController->handlePostData();
+ });
+
+ if (isset($_REQUEST['novalnet_webhook'])) {
+
+ // When the Novalnet webhook is triggered and known through URL, we call the appropriate Novalnet webhook handler
+ $novalnetWebhookHandler = new NovalnetWebhookHandler($this->getPlugin());
+ $dispatcher->listen('shop.hook.' . \HOOK_INDEX_NAVI_HEAD_POSTGET, [$novalnetWebhookHandler, 'handleNovalnetWebhook']);
+ }
+
+ if (isset($_REQUEST['novalnet_refund'])) {
+
+ // When the Novalnet webhook is triggered and known through URL, we call the appropriate Novalnet webhook handler
+ $novalnetWebhookHandler = new NovalnetWebhookHandler($this->getPlugin());
+ $dispatcher->listen('shop.hook.' . \HOOK_INDEX_NAVI_HEAD_POSTGET, [$novalnetWebhookHandler, 'handleNovalnetRefund']);
+ }
+ }
+ }
+
+ /**
+ * @param string $tabName
+ * @param int $menuID
+ * @param JTLSmarty $smarty
+ * @return string
+ */
+ public function renderAdminMenuTab(string $tabName, int $menuID, JTLSmarty $smarty): string
+ {
+ // Render Novalnet Plugin's backend tabs and it's related functions
+ $backendRenderer = new NovalnetBackendTabRenderer($this->getPlugin(), $this->getDB());
+ return $backendRenderer->renderNovalnetTabs($tabName, $menuID, $smarty);
+ }
+}
+
diff --git a/Migrations/Migration20210608221920.php b/Migrations/Migration20210608221920.php
new file mode 100755
index 0000000..b4b1e9e
--- /dev/null
+++ b/Migrations/Migration20210608221920.php
@@ -0,0 +1,78 @@
+execute('CREATE TABLE IF NOT EXISTS `xplugin_novalnet_transaction_details` (
+ `kId` int(10) NOT NULL AUTO_INCREMENT,
+ `cNnorderid` VARCHAR(64) NOT NULL,
+ `nNntid` BIGINT(20) NOT NULL,
+ `cZahlungsmethode` VARCHAR(64) NOT NULL,
+ `cMail` VARCHAR(255) NOT NULL,
+ `cStatuswert` VARCHAR(64),
+ `nBetrag` INT(11) NOT NULL,
+ `cSaveOnetimeToken` TINYINT(1) DEFAULT 0,
+ `cTokenInfo` LONGTEXT DEFAULT NULL,
+ `cAdditionalInfo` LONGTEXT DEFAULT NULL,
+ INDEX (cNnorderid, nNntid, cZahlungsmethode),
+ PRIMARY KEY (`kId`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
+ );
+
+ $this->execute('CREATE TABLE IF NOT EXISTS `xplugin_novalnet_callback` (
+ `kId` INT(10) NOT NULL AUTO_INCREMENT,
+ `cNnorderid` VARCHAR(64) NOT NULL,
+ `nCallbackTid` BIGINT(20) NOT NULL,
+ `nReferenzTid` BIGINT(20) NOT NULL,
+ `cZahlungsmethode` VARCHAR(64) NOT NULL,
+ `dDatum` datetime NOT NULL,
+ `nCallbackAmount` INT(11) DEFAULT NULL,
+ `cWaehrung` VARCHAR(64) DEFAULT NULL,
+ INDEX (cNnorderid),
+ PRIMARY KEY (`kId`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
+ );
+ }
+
+ /**
+ * Delete Novalnet transaction details table during the novalnet plugin uninstallation
+ *
+ */
+ public function down()
+ {
+ $this->execute('DROP TABLE IF EXISTS `xplugin_novalnet_transaction_details`');
+ $this->execute('DROP TABLE IF EXISTS `xplugin_novalnet_callback`');
+ }
+}
diff --git a/NovalnetPaymentHelper.php b/NovalnetPaymentHelper.php
new file mode 100755
index 0000000..89a1932
--- /dev/null
+++ b/NovalnetPaymentHelper.php
@@ -0,0 +1,260 @@
+plugin = $this->getNovalnetPluginObject();
+ }
+
+ /**
+ * Get plugin object
+ *
+ * @return object
+ */
+ public function getNovalnetPluginObject()
+ {
+ return Helper::getPluginById('jtl_novalnet');
+ }
+
+ /**
+ * Retrieve configuration values stored under Novalnet Plugin
+ *
+ * @param string $configuration
+ * @param bool|string $paymentName
+ * @return mixed
+ */
+ public function getConfigurationValues(string $configuration, $paymentName = false)
+ {
+ $configValue = $paymentName ? $paymentName . '_' . $configuration : $configuration;
+
+ if (!empty($this->plugin->getConfig()->getValue($configValue))) {
+
+ // Only for the tariff ID field, we extract the value which is separated by tariff value and type
+ if ($configValue == 'novalnet_tariffid') {
+ $tariffValue = trim($this->plugin->getConfig()->getValue('novalnet_tariffid'));
+ $tariffId = explode('-', $tariffValue);
+ return $tariffId[0];
+ }
+ return is_string($this->plugin->getConfig()->getValue($configValue)) ? trim($this->plugin->getConfig()->getValue($configValue)) : $this->plugin->getConfig()->getValue($configValue);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returning the list of the European Union countries for checking the country code of Guaranteed consumer
+ *
+ * @return array
+ */
+ public function getEuropeanRegionCountryCodes(): array
+ {
+ return ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'UK', 'CH'];
+ }
+
+ /**
+ * Building the required billing and shipping details from customer session
+ *
+ * @return array
+ */
+ public function getRequiredBillingShippingDetails(): array
+ {
+ // Extracting the billing address from Frontend Module
+ $billingAddress = Frontend::getCustomer();
+
+ $billingShippingDetails['billing'] = $billingShippingDetails['shipping'] = [
+ 'street' => $billingAddress->cStrasse,
+ 'house_no' => $billingAddress->cHausnummer,
+ 'city' => $billingAddress->cOrt,
+ 'zip' => $billingAddress->cPLZ,
+ 'country_code' => $billingAddress->cLand
+ ];
+
+ // Extracting the shipping address from the session object
+ if (!empty($_SESSION['Lieferadresse'])) {
+
+ $shippingAddress = $_SESSION['Lieferadresse'];
+
+ $billingShippingDetails['shipping'] = [
+ 'street' => $shippingAddress->cStrasse,
+ 'house_no' => $shippingAddress->cHausnummer,
+ 'city' => $shippingAddress->cOrt,
+ 'zip' => $shippingAddress->cPLZ,
+ 'country_code' => $shippingAddress->cLand
+ ];
+ }
+ return $billingShippingDetails;
+ }
+
+ /**
+ * Retrieving the reference details for one-click shopping
+ *
+ * @param string $paymentName
+ * @return array
+ */
+ public function getPaymentReferenceValues($paymentName): ?array
+ {
+ $customerDetails = Frontend::getCustomer();
+
+ if (($customerDetails->kKunde) != '') {
+ $storedValues = Shop::Container()->getDB()->query('SELECT nNntid, cStatuswert, cTokenInfo FROM xplugin_novalnet_transaction_details WHERE cZahlungsmethode LIKE "%' . $paymentName . '" AND cMail = "' . $customerDetails->cMail . '" AND cSaveOnetimeToken = 1 AND cTokenInfo != "" ORDER BY kId DESC LIMIT 3', 2);
+ return ($storedValues != '') ? $storedValues : null;
+ }
+ return null;
+ }
+
+ /**
+ * Convert the order amount from decimal to integer
+ *
+ * @return int
+ */
+ public function getOrderAmount($order = null): int
+ {
+ if($order == null) {
+ $convertedOrderAmount = Currency::convertCurrency(Frontend::getCart()->gibGesamtsummeWaren(true), Frontend::getCurrency()->getCode());
+ if (($convertedOrderAmount) == '') {
+ $convertedOrderAmount = $_SESSION['Warenkorb']->gibGesamtsummeWaren(true);
+ }
+ $orderAmount = str_replace(',', '.', sprintf('%.2f', $convertedOrderAmount));
+ return (int) ($orderAmount * 100);
+ } else {
+ $totalAmount = $order->WarensummeLocalized[0];
+ $totalAmountValue = preg_replace('/[^0-9,.]/', '', $totalAmount);
+ $orderAmount = (float) str_replace(',', '.', $totalAmountValue);
+ return (int) strval(round($orderAmount, 2) * 100);
+ }
+ }
+
+ /**
+ * Convert the order amount from decimal to integer
+ *
+ * @return int
+ */
+ public function getTotalOrderAmount($order): int
+ {
+ $totalAmount = $order->WarensummeLocalized[0];
+ $totalAmountValue = preg_replace('/[^0-9,.]/', '', $totalAmount);
+ $orderAmount = (float) str_replace(',', '.', $totalAmountValue);
+ return (int) strval(round($orderAmount, 2) * 100);
+ }
+
+ /**
+ * Process the database update
+ *
+ * @param string $tableName
+ * @param string $keyName
+ * @param string $keyValue
+ * @param object $object
+ * @return none
+ */
+ public function performDbUpdateProcess(string $tableName, $keyName, $keyValue, $object): void
+ {
+ Shop::Container()->getDB()->update($tableName , $keyName , $keyValue, (object) $object);
+ }
+
+ /**
+ * Unsets the Novalnet payment sessions
+ *
+ * @return none
+ */
+ public function novalnetSessionCleanUp(string $paymentName): void
+ {
+ $sessionValues = array(
+ 'nn_'.$paymentName.'_request',
+ 'nn_'.$paymentName.'_payment_response',
+ 'nn_comments',
+ 'novalnetWalletResponse',
+ 'nn_order_obj',
+ 'nn_wallet_token',
+ 'nn_wallet_amount',
+ 'nn_wallet_doredirect'
+ );
+
+ foreach($sessionValues as $sessionVal) {
+ unset($_SESSION[$paymentName]);
+ unset($_SESSION[$sessionVal]);
+ }
+ }
+
+ /**
+ * Get language texts for the fields
+ *
+ * @param array $languages
+ * @return array
+ */
+ public function getNnLanguageText(array $languages, $langCode = null): array
+ {
+ foreach($languages as $lang) {
+ $languageTexts[$lang] = $this->plugin->getLocalization()->getTranslation($lang, $langCode);
+ }
+ return $languageTexts;
+ }
+
+ /**
+ * Get translated text for the provided Novalnet text key
+ *
+ * @param string $key
+ * @return string
+ */
+ public function getNnLangTranslationText(string $key, $langCode = null): string
+ {
+ return $this->plugin->getLocalization()->getTranslation($key, $langCode);
+ }
+
+ /**
+ * Get server or remote address from the global variable
+ *
+ * @param string $addressType
+ * @return mixed
+ */
+ public function getNnIpAddress($addressType)
+ {
+ if ($addressType == 'REMOTE_ADDR') {
+ # Shop's core function that fetches the remote address
+ $remoteAddress = (Request::getRealIP() != '') ? Request::getRealIP() : $_SERVER[$addressType];
+ return $remoteAddress;
+ }
+ return $_SERVER[$addressType];
+ }
+}
diff --git a/NovalnetWebhookHandler.php b/NovalnetWebhookHandler.php
new file mode 100755
index 0000000..92d40d9
--- /dev/null
+++ b/NovalnetWebhookHandler.php
@@ -0,0 +1,967 @@
+novalnetPaymentGateway = new NovalnetPaymentGateway();
+ }
+
+ public function handleNovalnetWebhook()
+ {
+ try {
+ $this->eventData = json_decode(file_get_contents('php://input'), true);
+ } catch (Exception $e) {
+ $this->displayMessage('Received data is not in the JSON format' . $e);
+ }
+
+ // validated the IP Address
+ $this->validateIpAddress();
+
+ // Validates the webhook params before processing
+ $this->validateEventParams();
+
+ // Set Event data
+ $this->eventType = $this->eventData['event']['type'];
+ $this->parentTid = (!empty($this->eventData['event']['parent_tid'])) ? $this->eventData['event']['parent_tid'] : $this->eventData['event']['tid'];
+ $this->eventTid = $this->eventData['event']['tid'];
+
+ // Retreiving the shop's order information based on the transaction
+ $this->orderDetails = $this->getOrderReference();
+
+ $this->languageCode = ($this->orderDetails->kSprache == 1) ? 'ger' : 'eng';
+
+ if ($this->eventData['result']['status'] == 'SUCCESS') {
+
+ switch ($this->eventType) {
+ case 'PAYMENT':
+ $this->displayMessage('The Payment has been received');
+ break;
+ case 'TRANSACTION_CAPTURE':
+ case 'TRANSACTION_CANCEL':
+ $this->handleNnTransactionCaptureCancel();
+ break;
+ case 'TRANSACTION_UPDATE':
+ $this->handleNnTransactionUpdate();
+ break;
+ case 'TRANSACTION_REFUND':
+ $this->handleNnTransactionRefund();
+ break;
+ case 'CREDIT':
+ $this->handleNnTransactionCredit();
+ break;
+ case 'CHARGEBACK':
+ $this->handleNnChargeback();
+ break;
+ case 'INSTALMENT':
+ $this->handleNnInstalment();
+ break;
+ case 'INSTALMENT_CANCEL':
+ $this->handleNnInstalmentCancel();
+ break;
+ default:
+ $this->displayMessage('The webhook notification has been received for the unhandled EVENT type ( ' . $this->eventType . ')' );
+ }
+ }
+ }
+
+ /**
+ * Display webhook notification message
+ *
+ * @param string $message
+ * @return none
+ */
+ public function displayMessage($message): void
+ {
+ print $message;
+ exit;
+ }
+
+ /**
+ * Validate the IP control check
+ *
+ * @return none
+ */
+ public function validateIpAddress(): void
+ {
+ $realHostIp = gethostbyname('pay-nn.de');
+
+ if (($realHostIp) == '') {
+ $this->displayMessage('Novalnet HOST IP missing');
+ }
+
+ // Condition to check whether the callback is called from authorized IP
+ if ((Request::getRealIP() != $realHostIp) && ($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('novalnet_webhook_testmode')) == '') {
+ $this->displayMessage('Unauthorised access from the IP ' . Request::getRealIP());
+ }
+ }
+
+ /**
+ * Validates the event parameters
+ *
+ * @return none
+ */
+ public function validateEventParams()
+ {
+ if(!empty( $this->eventData ['custom'] ['shop_invoked'])) {
+ $this->displayMessage('Process already handled in the shop.');
+ }
+
+ // Mandatory webhook params
+ $requiredParams = ['event' => ['type', 'checksum', 'tid'], 'result' => ['status']];
+
+ // Validate required parameters
+ foreach ($requiredParams as $category => $parameters) {
+ if (($this->eventData[$category]) == '') {
+ // Could be a possible manipulation in the notification data
+ $this->displayMessage('Required parameter category(' . $category. ') not received');
+ } elseif (($parameters) != '') {
+ foreach ($parameters as $parameter) {
+ if (($this->eventData[$category][$parameter]) == '') {
+ // Could be a possible manipulation in the notification data
+ $this->displayMessage('Required parameter(' . $parameter . ') in the category (' . $category . ') not received');
+ }
+ }
+ }
+ }
+
+ // Validate the received checksum.
+ $this->validateChecksum();
+ }
+
+ /**
+ * Validate checksum
+ *
+ * @return none
+ */
+ public function validateChecksum()
+ {
+ $accessKey = $this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('novalnet_private_key');
+ $tokenString = $this->eventData['event']['tid'] . $this->eventData['event']['type'] . $this->eventData['result']['status'];
+
+ if (isset($this->eventData['transaction']['amount'])) {
+ $tokenString .= $this->eventData['transaction']['amount'];
+ }
+
+ if (isset($this->eventData['transaction']['currency'])) {
+ $tokenString .= $this->eventData['transaction']['currency'];
+ }
+
+ if (($accessKey) != '') {
+ $tokenString .= strrev($accessKey);
+ }
+
+ $generatedChecksum = hash('sha256', $tokenString);
+ if ($generatedChecksum !== $this->eventData['event']['checksum']) {
+ $this->displayMessage('While notifying some data has been changed. The hash check failed');
+ }
+ }
+
+ /**
+ * Get order details from the shop's database
+ *
+ * @return object
+ */
+ public function getOrderReference(): object
+ {
+ // Looking into the Novalnet database if the transaction exists
+ $novalnetOrder = Shop::Container()->getDB()->query('SELECT cNnorderid, cZahlungsmethode, cStatuswert, nBetrag, cSaveOnetimeToken, cTokenInfo FROM xplugin_novalnet_transaction_details WHERE nNntid = "' . $this->parentTid . '"', ReturnType::SINGLE_OBJECT);
+
+ // If both the order number from Novalnet and in shop is missing, then something is wrong
+ if (empty($this->eventData['transaction']['order_no']) && empty($novalnetOrder->cNnorderid)) {
+ $this->displayMessage('Order reference not found for the TID ' . $this->parentTid);
+ }
+
+ $orderNumberToSearch = !empty($novalnetOrder->cNnorderid) ? $novalnetOrder->cNnorderid : $this->eventData['transaction']['order_no'];
+
+ // If the order in the Novalnet server to the order number in Novalnet database doesn't match, then there is an issue
+ if (!empty($this->eventData['transaction']['order_no']) && !empty($novalnetOrder->cNnorderid) && (($this->eventData['transaction']['order_no']) != $novalnetOrder->cNnorderid)) {
+ $this->displayMessage('Order reference not matching for the order number ' . $orderNumberToSearch);
+ }
+
+ $shopOrder = Shop::Container()->getDB()->query('SELECT kBestellung FROM tbestellung WHERE cBestellNr = "' . $orderNumberToSearch . '"', ReturnType::SINGLE_OBJECT);
+
+ // Loads order object from shop
+ $order = new Bestellung((int) $shopOrder->kBestellung);
+ $order->fuelleBestellung(true, 0, false);
+
+ // If the order is not found in Novalnet's database, it is communication failure
+ if (empty($novalnetOrder->cNnorderid)) {
+
+ // We look into the shop's core database to create the order object for further handling
+ if (!empty($shopOrder->kBestellung)) {
+ // Handles communication failure scenario
+ $this->handleCommunicationBreak($order);
+ } else {
+ $this->displayMessage('Transaction mapping failed ' . $orderNumberToSearch);
+ }
+ }
+
+ return (object) array_merge((array) $order, (array) $novalnetOrder);
+ }
+
+ /**
+ * Handling communication breakup
+ *
+ * @param object $order
+ * @return none
+ */
+ public function handleCommunicationBreak(object $order): void
+ {
+ $jtlPaymentmethod = Method::create($order->Zahlungsart->cModulId);
+
+ $orderLanguage = ($order->kSprache == 1) ? 'ger' : 'eng';
+
+ $webhookComments = '';
+
+ $webhookComments .= $order->cZahlungsartName;
+
+ $webhookComments .= \PHP_EOL . $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_transaction_tid', $orderLanguage) . $this->parentTid;
+
+ if (!empty($this->eventData['transaction']['test_mode'])) {
+ $webhookComments .= \PHP_EOL . $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_test_order', $orderLanguage);
+ }
+
+ $isPaidNow = false;
+
+ // Deactivating the WAWI synchronization
+ $updateWawi = 'Y';
+
+ // Setting the payment Name
+ if (in_array($this->eventData['transaction']['payment_type'], array('DIRECT_DEBIT_SEPA', 'GUARANTEED_DIRECT_DEBIT_SEPA', 'INSTALMENT_DIRECT_DEBIT_SEPA'))) {
+ $paymentName = str_replace('DIRECT_DEBIT_', '', $this->eventData['transaction']['payment_type']);
+ } else {
+ $paymentName = (($this->eventData['transaction']['payment_type'] == 'CREDITCARD') ? 'CC' : (($this->eventData['transaction']['payment_type'] == 'ONLINE_TRANSFER') ? 'SOFORT' : $this->eventData['transaction']['payment_type']));
+ }
+
+ if ($this->eventData['result']['status'] == 'SUCCESS') {
+
+ if ($this->eventData['transaction']['status']) {
+
+ if ($this->eventData['transaction']['status'] == 'PENDING') {
+ $orderStatus = \BESTELLUNG_STATUS_OFFEN;
+ } elseif ($this->eventData['transaction']['status'] == 'ON_HOLD') {
+ $orderStatus = constant($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('novalnet_onhold_order_status'));
+ } elseif ($this->eventData['transaction']['status'] == 'CONFIRMED') {
+ // For the successful case, we enable the WAWI synchronization
+ $updateWawi = 'N';
+ $isPaidNow = true;
+
+ $orderStatus = constant($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('order_completion_status', strtolower('NOVALNET_'.$paymentName)));
+
+ // Add the current transaction payment into db
+ $incomingPayment = new stdClass();
+ $incomingPayment->fBetrag = $order->fGesamtsummeKundenwaehrung;
+ $incomingPayment->cISO = $order->Waehrung->cISO;
+ $incomingPayment->cHinweis = $this->parentTid;
+ $jtlPaymentmethod->name = $order->cZahlungsartName;
+
+ $jtlPaymentmethod->addIncomingPayment($order, $incomingPayment);
+ } else {
+ $orderStatus = \BESTELLUNG_STATUS_STORNO;
+ $webhookComments .= \PHP_EOL . $this->eventData['result']['status_text'];
+ }
+ }
+
+ // Sending the order update mail here as due to the communcation failure, the update mail would not have reached
+ $jtlPaymentmethod->sendMail($order->kBestellung, MAILTEMPLATE_BESTELLUNG_AKTUALISIERT);
+
+ } else {
+ $orderStatus = \BESTELLUNG_STATUS_STORNO;
+ $webhookComments .= \PHP_EOL . $this->eventData['result']['status_text'];
+ }
+
+ // Entering the details into the Novalnet's transactions details for consistency and further operations
+ $this->novalnetPaymentGateway->insertOrderDetailsIntoNnDb($order, $this->eventData, strtolower('NOVALNET_'.$paymentName));
+
+ // Completing the callback notification
+ $this->webhookFinalprocess($webhookComments, $orderStatus, $updateWawi, $isPaidNow);
+ }
+
+ /**
+ * Handling the Novalnet transaction authorization process
+ *
+ * @return none
+ */
+ public function handleNnTransactionCaptureCancel(): void
+ {
+ // Capturing or cancellation of a transaction occurs only when the transaction is found as ON_HOLD in the shop
+ if (in_array($this->orderDetails->cStatuswert, array('PENDING', 'ON_HOLD'))) {
+
+ $isPaidNow = false;
+
+ // If the transaction is captured, we update necessary alterations in DB
+ if ($this->eventType == 'TRANSACTION_CAPTURE') {
+
+ // Activating the WAWI synchronization
+ $updateWawi = 'N';
+
+ $webhookComments = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_confirmation_text', $this->languageCode), date('d.m.Y'), date('H:i:s'));
+
+ $amount = (strpos($this->eventData['transaction']['payment_type'], 'INSTALMENT') !== false) ? $this->eventData['instalment']['cycle_amount'] : $this->eventData['transaction']['amount'];
+
+ if ($this->eventData['transaction']['payment_type'] == 'PAYPAL') {
+ $tokenInfo = !empty($this->orderDetails->cTokenInfo) ? json_decode($this->orderDetails->cTokenInfo, true) : '';
+ $this->eventData['transaction']['payment_data']['token'] = $tokenInfo['token'];
+ }
+
+ // Certain payment methods require their tokens to be stored during capture
+ if ($this->orderDetails->cSaveOnetimeToken == 1 && (empty($this->orderDetails->cTokenInfo) || !empty($tokenInfo))) {
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $this->orderDetails->cBestellNr, ['cTokenInfo' => json_encode($this->eventData['transaction']['payment_data'])]);
+ }
+
+ // Instalment payment methods cycle information
+ if (!empty($this->eventData['instalment'])) {
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $this->orderDetails->cBestellNr, ['cAdditionalInfo' => json_encode($this->eventData['instalment'])]);
+ }
+
+
+ // Only for the Invoice Payment type, we have to wait for the credit entry
+ if ($this->eventData['transaction']['payment_type'] != 'INVOICE') {
+ // Loads the order object
+ $order = new Bestellung((int) ($this->orderDetails->kBestellung));
+ $order->fuelleBestellung(true, 0, false);
+
+ // Add the incoming payments if the transaction was confirmed
+ $jtlPaymentmethod = Method::create($this->orderDetails->Zahlungsart->cModulId);
+
+ $incomingPayment = new stdClass();
+ $incomingPayment->fBetrag = $this->orderDetails->fGesamtsummeKundenwaehrung;
+ $incomingPayment->cISO = $this->orderDetails->Waehrung->cISO;
+ $incomingPayment->cHinweis = $this->parentTid;
+ $jtlPaymentmethod->name = $this->orderDetails->cZahlungsartName;
+
+ // Add the current transaction payment into db
+ $jtlPaymentmethod->addIncomingPayment($order, $incomingPayment);
+
+ $isPaidNow = true;
+ }
+
+ // Order status required to be changed for the payment type
+ $orderStatus = constant($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('order_completion_status', $this->orderDetails->cZahlungsmethode));
+
+
+ } elseif($this->eventType == 'TRANSACTION_CANCEL') {
+
+ $webhookComments = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_transaction_cancellation', $this->languageCode), date('d.m.Y'), date('H:i:s'));
+
+ $orderStatus = \BESTELLUNG_STATUS_STORNO;
+
+ // We do not want that cancelled orders picked up in WAWI
+ $updateWawi = 'Y';
+ }
+
+ $this->eventData['transaction']['amount'] = ($this->eventData['transaction']['payment_type'] == 'INVOICE') ? 0 : $this->eventData['transaction']['amount'];
+ $this->webhookFinalprocess($webhookComments, $orderStatus, $updateWawi, $isPaidNow);
+
+
+ } else {
+ $this->displayMessage('Transaction already captured/cancelled');
+ }
+ }
+
+ /**
+ * Handling the Novalnet transaction update process
+ *
+ * @return none
+ */
+ public function handleNnTransactionUpdate(): void
+ {
+ // Paid is set to NULL
+ $isPaidNow = false;
+
+ // orderStatus is set to empty
+ $orderStatus = '';
+
+ // Set the WAWI status
+ $updateWawi = ($this->eventData['transaction']['status'] == 'CONFIRMED') ? 'N' : 'Y';
+
+ if ($this->eventData['transaction']['update_type'] == 'STATUS') {
+
+ if ($this->eventData['transaction']['status'] == 'DEACTIVATED') {
+
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_transaction_cancellation', $this->languageCode), date('d.m.Y'), date('H:i:s'));
+
+ // Cancellation of the order status
+ $orderStatus = \BESTELLUNG_STATUS_STORNO;
+
+ } else {
+
+ if ($this->orderDetails->cStatuswert == 'PENDING' && in_array($this->eventData['transaction']['status'], ['ON_HOLD', 'CONFIRMED'])) {
+
+ $webhookComments = '';
+
+ if (in_array($this->eventData['transaction']['payment_type'], ['INVOICE', 'PREPAYMENT', 'GUARANTEED_INVOICE', 'INSTALMENT_INVOICE'])) {
+ $webhookComments = $this->novalnetPaymentGateway->getBankdetailsInformation($this->orderDetails, $this->eventData, $this->languageCode);
+ }
+
+ // For on-hold, we only update the order status
+ if ($this->eventData['transaction']['status'] == 'ON_HOLD') {
+ // Building the onhold activation text
+ $webhookComments .= \PHP_EOL . \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_pending_to_onhold_status_change', $this->languageCode), $this->parentTid, date('d.m.Y'), date('H:i:s'));
+
+ $orderStatus = constant($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('novalnet_onhold_order_status'));
+ } else {
+
+ $amount = (strpos($this->eventData['transaction']['payment_type'], 'INSTALMENT') !== false) ? $this->eventData['instalment']['cycle_amount'] : $this->eventData['transaction']['amount'];
+
+ // Building the confirmation text
+ $webhookComments .= \PHP_EOL . \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_update_confirmation_text', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor( $amount / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'));
+
+ // Instalment payment methods cycle information
+ if (!empty($this->eventData['instalment'])) {
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $this->orderDetails->cBestellNr, ['cAdditionalInfo' => json_encode($this->eventData['instalment'])]);
+ }
+
+ $orderStatus = constant($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('order_completion_status', $this->orderDetails->cZahlungsmethode));
+
+ if ($this->eventData['transaction']['payment_type'] != 'INVOICE') {
+ // Paid is set to NULL
+ $isPaidNow = true;
+ }
+ }
+ }
+
+ }
+ } else {
+
+ $webhookComments = '';
+
+ $dueDateUpdateMessage = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_duedate_update_message', $this->languageCode), Preise::getLocalizedPriceWithoutFactor( $this->eventData['transaction']['amount'] / 100, Frontend::getCurrency(), false ), $this->eventData['transaction']['due_date']);
+
+ // Amount update process
+ if($this->eventData['transaction']['amount'] != $this->orderDetails->nBetrag && !in_array($this->eventData['transaction']['payment_type'], array('INSTALMENT_INVOICE', 'INSTALMENT_SEPA'))) {
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $this->orderDetails->cBestellNr, ['nBetrag' => $this->eventData['transaction']['amount']]);
+ }
+
+ $amountUpdateMessage = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_amount_update_message', $this->languageCode), Preise::getLocalizedPriceWithoutFactor( $this->eventData['transaction']['amount'] / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'));
+
+ $webhookComments .= (($this->eventData['transaction']['update_type'] == 'AMOUNT') ? $amountUpdateMessage : (($this->eventData['transaction']['update_type'] == 'DUE_DATE') ? $dueDateUpdateMessage : $dueDateUpdateMessage . $amountUpdateMessage));
+
+ }
+
+ $this->webhookFinalprocess($webhookComments, $orderStatus, $updateWawi, $isPaidNow);
+ }
+
+ /**
+ * Handling the transaction refund process
+ *
+ * @return none
+ */
+ public function handleNnTransactionRefund(): void
+ {
+
+ if ($this->eventData['result']['status'] == 'SUCCESS') {
+
+ $webhookComments = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_refund_execution', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor($this->eventData['transaction']['refund']['amount'] / 100, Frontend::getCurrency(), false ));
+
+ if (!empty($this->eventData['transaction']['refund']['tid'])) {
+ $webhookComments .= \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_new_tid_refund_execution', $this->languageCode), $this->eventData['transaction']['refund']['tid']);
+ }
+
+ // In case of full refund, deactivation is processed
+ if ($this->eventData['transaction']['status'] == 'DEACTIVATED') {
+
+ // We do not send to webhookFinalprocess for update as it will cause problems with WAWI synchronization
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('tbestellung', 'cBestellNr', $this->orderDetails->cBestellNr, ['cStatus' => \BESTELLUNG_STATUS_STORNO]);
+ }
+
+ $txAdditonalDetails = Shop::Container()->getDB()->query('SELECT cAdditionalInfo FROM xplugin_novalnet_transaction_details WHERE cNnorderid = "' . $this->orderDetails->cBestellNr . '"', ReturnType::SINGLE_OBJECT);
+
+ $txAdditonalInfo = !empty($txAdditonalDetails->cAdditionalInfo) ? json_decode($txAdditonalDetails->cAdditionalInfo, true) : [];
+
+ $txAdditonalInfo['refunded_amount'] = $this->eventData['transaction']['refunded_amount'] + $this->eventData['transaction']['refund']['amount'];
+
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $this->orderDetails->cBestellNr, ['cAdditionalInfo' => json_encode($txAdditonalInfo)]);
+
+ }
+
+ $this->webhookFinalprocess($webhookComments);
+ }
+
+ /**
+ * Handling the credit process
+ *
+ * @return none
+ */
+ public function handleNnTransactionCredit(): void
+ {
+ $webhookComments = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_webhook_initial_execution', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor( $this->eventData['transaction']['amount'] / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'), $this->eventTid);
+
+ if (in_array($this->eventData['transaction']['payment_type'], ['INVOICE_CREDIT', 'CASHPAYMENT_CREDIT', 'ONLINE_TRANSFER_CREDIT', 'MULTIBANCO_CREDIT'])) {
+
+ $callbackDetails = Shop::Container()->getDB()->query('SELECT SUM(nCallbackAmount) AS kCallbackAmount FROM xplugin_novalnet_callback WHERE cNnorderid = "' . $this->orderDetails->cBestellNr . '" and nCallbackTid != nReferenzTid', ReturnType::SINGLE_OBJECT);
+
+ $refundDetails = Shop::Container()->getDB()->query('SELECT cAdditionalInfo FROM xplugin_novalnet_transaction_details WHERE cNnorderid = "' . $this->orderDetails->cBestellNr . '"', ReturnType::SINGLE_OBJECT);
+
+
+ $callbackDetails->kCallbackAmount = !empty($callbackDetails->kCallbackAmount) ? $callbackDetails->kCallbackAmount : 0;
+
+ $orderPaidAmount = $callbackDetails->kCallbackAmount + $this->eventData['transaction']['amount'];
+ $orderAmount = !empty($this->orderDetails->nBetrag) ? $this->orderDetails->nBetrag : ($this->orderDetails->fGesamtsumme * 100);
+
+ $refundInfo = !empty($refundDetails->cAdditionalInfo) ? json_decode($refundDetails->cAdditionalInfo, true) : 0;
+
+ $orderPaidAmount = $orderPaidAmount - (!empty($refundInfo['refunded_amount']) ? $refundInfo['refunded_amount'] : 0);
+ $orderAmount = $orderAmount - (!empty($refundInfo['refunded_amount']) ? $refundInfo['refunded_amount'] : 0);
+
+ if ($orderPaidAmount >= $orderAmount) {
+ // Loads the order object
+ $order = new Bestellung((int) ($this->orderDetails->kBestellung));
+ $order->fuelleBestellung(true, 0, false);
+
+ // Add the current transaction payment into db
+ $jtlPaymentmethod = Method::create($this->orderDetails->Zahlungsart->cModulId);
+
+ $incomingPayment = new stdClass();
+ $incomingPayment->fBetrag = $this->orderDetails->fGesamtsummeKundenwaehrung;
+ $incomingPayment->cISO = $this->orderDetails->Waehrung->cISO;
+ $incomingPayment->cHinweis = $this->parentTid;
+ $jtlPaymentmethod->name = $this->orderDetails->cZahlungsartName;
+
+ $jtlPaymentmethod->addIncomingPayment($order, $incomingPayment);
+
+ $orderStatus = $this->eventData['transaction']['payment_type'] == 'ONLINE_TRANSFER_CREDIT' ? '' : constant($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('callback_status', $this->orderDetails->cZahlungsmethode));
+
+ $this->webhookFinalprocess($webhookComments, $orderStatus, 'N', true);
+
+ } else {
+ $this->webhookFinalprocess($webhookComments);
+ }
+ } else {
+ $this->webhookFinalprocess($webhookComments);
+ }
+ }
+
+ /**
+ * Handling the chargeback process
+ *
+ * @return none
+ */
+ public function handleNnChargeback(): void
+ {
+ if ($this->eventData['transaction']['payment_type'] == 'RETURN_DEBIT_SEPA') {
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_return_debit_execution_text', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor( $this->eventData['transaction']['amount'] / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'), $this->eventTid);
+
+ } elseif ($this->eventData['transaction']['payment_type'] == 'REVERSAL') {
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_reversal_execution_text', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor( $this->eventData['transaction']['amount'] / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'), $this->eventTid);
+ } else {
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_chargeback_execution_text', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor( $this->eventData['transaction']['amount'] / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'), $this->eventTid);
+ }
+
+ $this->webhookFinalprocess($webhookComments);
+ }
+
+ /**
+ * Handling the Instalment payment execution
+ *
+ * @return none
+ */
+ public function handleNnInstalment(): void
+ {
+ if ($this->eventData['transaction']['status'] == 'CONFIRMED' && !empty($this->eventData['instalment']['cycles_executed'])) {
+ $additionalInstalmentMsg = $nextSepaInstalmentMsg = '';
+
+ $webhookComments = \PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_instalment_payment_execution', $this->languageCode), $this->parentTid, Preise::getLocalizedPriceWithoutFactor( $this->eventData['instalment']['cycle_amount'] / 100, Frontend::getCurrency(), false ), date('d.m.Y'), date('H:i:s'), $this->eventTid);
+
+ $additionalInfo = Shop::Container()->getDB()->query('SELECT cAdditionalInfo FROM xplugin_novalnet_transaction_details WHERE cNnorderid = "' . $this->orderDetails->cBestellNr . '"', ReturnType::SINGLE_OBJECT);
+
+ $instalmentCyclesInfo = json_decode($additionalInfo->cAdditionalInfo, true);
+ $insCycleCount = $this->eventData['instalment']['cycles_executed'];
+ $instalmentCyclesInfo[$insCycleCount]['tid'] = $this->eventTid;
+
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $this->orderDetails->cBestellNr, ['cAdditionalInfo' => json_encode($instalmentCyclesInfo)]);
+
+ if (empty($this->eventData['instalment']['prepaid'])) {
+ if ($this->eventData['transaction']['payment_type'] == 'INSTALMENT_INVOICE') {
+ $additionalInstalmentMsg = \PHP_EOL . $this->novalnetPaymentGateway->getBankdetailsInformation($this->orderDetails, $this->eventData, $this->languageCode);
+
+ }
+ }
+
+ $instalmentInfo = $this->novalnetPaymentGateway->getInstalmentInfoFromDb($this->orderDetails->cBestellNr, $this->languageCode);
+
+ $webhookComments .= $nextSepaInstalmentMsg;
+ // Send mail notification to customer regarding the new instalment creation
+ $this->sendInstalmentMailNotification($instalmentInfo, $additionalInstalmentMsg);
+
+ $this->webhookFinalprocess($webhookComments);
+ }
+ }
+
+ /**
+ * Handling the Instalment cancelation
+ *
+ * @return none
+ */
+ public function handleNnInstalmentCancel(): void
+ {
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_instalment_cancellation', $this->languageCode), $this->parentTid, date('d.m.Y'), date('H:i:s'));
+ if($this->eventData['instalment']['cancel_type'] == 'REMAINING_CYCLES') {
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_instalment_cancellation_remaining_cycle', $this->languageCode), $this->parentTid, date('d.m.Y'), date('H:i:s'));
+ }
+ $this->webhookFinalprocess($webhookComments, \BESTELLUNG_STATUS_STORNO, 'Y');
+ }
+
+ /**
+ * Performs final callback process
+ *
+ * @param string $webhookMessage
+ * @param string $orderStatusToUpdate
+ * @param string $updateWawiStatus
+ * @param bool $isPaidNow
+ * @return none
+ */
+ public function webhookFinalprocess($webhookMessage, $orderStatusToUpdate = '', $updateWawiStatus = 'N', $isPaidNow = false)
+ {
+ $oldTransactionComment = Shop::Container()->getDB()->query('SELECT cKommentar, kSprache FROM tbestellung WHERE cBestellNr = "' . $this->orderDetails->cBestellNr . '"', ReturnType::SINGLE_OBJECT);
+
+ if (strpos($this->eventData['transaction']['payment_type'], 'INVOICE') !== false) {
+ if($this->eventType == 'TRANSACTION_CAPTURE') {
+ if (strpos($oldTransactionComment->cKommentar, 'auf das folgende Konto') !== false && $oldTransactionComment->kSprache == 1) {
+ $oldTransactionComment->cKommentar = str_replace('auf das folgende Konto', 'spätestens bis zum ' .$this->eventData['transaction']['due_date'] . ' auf das folgende Konto', $oldTransactionComment->cKommentar);
+ } else {
+ $oldTransactionComment->cKommentar = str_replace('to the following account', 'to the following account on or before ' . $this->eventData['transaction']['due_date'] , $oldTransactionComment->cKommentar);
+ }
+ }
+
+ if(($this->eventType == 'INSTALMENT' || $this->eventType == 'TRANSACTION_UPDATE' && in_array($this->eventData['transaction']['update_type'], array('DUE_DATE', 'AMOUNT_DUE_DATE'))) && (preg_match('/before(.*)/', $oldTransactionComment->cKommentar, $matches) || preg_match('/zum(.*)/', $oldTransactionComment->cKommentar, $matches))) {
+
+ $oldTransactionComment->cKommentar = ($oldTransactionComment->kSprache == 1) ? str_replace($matches[1], $this->eventData['transaction']['due_date'] . ' auf das folgende Konto', $oldTransactionComment->cKommentar) : str_replace($matches[0], 'before ' . $this->eventData['transaction']['due_date'] , $oldTransactionComment->cKommentar);
+ }
+
+
+ if ($this->eventType == 'INSTALMENT' && preg_match("/([0-9]{17})/s", $oldTransactionComment->cKommentar, $matches)) {
+ if (strpos($oldTransactionComment->cKommentar, 'Novalnet-Transaktions-ID:') !== false && $oldTransactionComment->kSprache == 1) {
+ $oldTransactionComment->cKommentar = str_replace('Novalnet-Transaktions-ID: ' . $matches[0], 'Novalnet-Transaktions-ID: ' . $this->eventTid, $oldTransactionComment->cKommentar);
+ $oldTransactionComment->cKommentar = str_replace('Zahlungsreferenz: ' . $matches[0], 'Zahlungsreferenz: ' . $this->eventTid, $oldTransactionComment->cKommentar);
+ } else {
+ $oldTransactionComment->cKommentar = str_replace('Novalnet transaction ID: ' . $matches[0], 'Novalnet transaction ID: ' . $this->eventTid, $oldTransactionComment->cKommentar);
+ $oldTransactionComment->cKommentar = str_replace('Payment Reference: ' . $matches[0], 'Payment Reference: ' . $this->eventTid, $oldTransactionComment->cKommentar);
+ }
+ }
+
+ if(($this->eventType == 'INSTALMENT' || $this->eventType == 'TRANSACTION_UPDATE' && in_array($this->eventData['transaction']['update_type'], array('AMOUNT', 'AMOUNT_DUE_DATE'))) && (preg_match('/(.*)to the following/', $oldTransactionComment->cKommentar, $matches) || preg_match('/(.*)spätestens/', $oldTransactionComment->cKommentar, $matches))) {
+ $updatedOrderAmount = $this->eventType == 'INSTALMENT' ? $this->eventData['instalment']['cycle_amount'] :
+ $this->eventData['transaction']['amount'];
+
+ $oldTransactionComment->cKommentar = ($oldTransactionComment->kSprache == 1) ? str_replace($matches[1], 'Bitte überweisen Sie den Betrag von ' . number_format($updatedOrderAmount / 100 , 2, ',', '') . ' '. $this->orderDetails->Waehrung->htmlEntity, $oldTransactionComment->cKommentar) : str_replace($matches[1], 'Please transfer the amount of ' . number_format($updatedOrderAmount / 100 , 2, ',', '') . ' '. $this->orderDetails->Waehrung->htmlEntity , $oldTransactionComment->cKommentar);
+ }
+ }
+
+ $webhookComments = !empty($oldTransactionComment->cKommentar) ? ($oldTransactionComment->cKommentar . \PHP_EOL . $webhookMessage) : $webhookMessage;
+
+ $orderNo = !empty($this->orderDetails->cBestellNr) ? ($this->orderDetails->cBestellNr) : $this->eventData['transaction']['order_no'];
+
+ if (!empty($orderStatusToUpdate)) {
+ // Updating the necessary details into the core order table related to the webhook transaction
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('tbestellung', 'cBestellNr', $orderNo, ['cKommentar' => $webhookComments, 'cStatus' => $orderStatusToUpdate, 'cAbgeholt' => $updateWawiStatus, 'dBezahltDatum' => ($isPaidNow ? 'NOW()' : '')]);
+ } else {
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('tbestellung', 'cBestellNr', $orderNo, ['cKommentar' => $webhookComments, 'cAbgeholt' => $updateWawiStatus, 'dBezahltDatum' => ($isPaidNow ? 'NOW()' : '')]);
+ }
+
+ // Updating in the Novalnet transaction table
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $orderNo, ['cStatuswert' => $this->eventData['transaction']['status']]);
+
+ // Logging the webhook event notification for further handling and troubleshooting
+ $this->logWebhookProcess();
+
+
+ // Send the order update mail when the transaction has been confirmed or amount and due date update
+ if ($this->eventType == 'TRANSACTION_CAPTURE' || (isset($this->eventData['transaction']['update_type']) && in_array($this->eventData['transaction']['update_type'], array('AMOUNT', 'DUE_DATE', 'AMOUNT_DUE_DATE'))) || (isset($this->eventData['transaction']['update_type']) && $this->eventData['transaction']['update_type'] == 'STATUS' && $this->eventData['transaction']['status'] == 'CONFIRMED')) {
+ $jtlPaymentmethod = Method::create($this->orderDetails->Zahlungsart->cModulId);
+
+ $jtlPaymentmethod->sendMail($this->orderDetails->kBestellung, MAILTEMPLATE_BESTELLUNG_AKTUALISIERT);
+
+ }
+
+ // Send mail for merchant
+ $this->sendMailNotification($webhookMessage);
+ }
+
+ /**
+ * To log webhook process into the callback table
+ *
+ * @return none
+ */
+ public function logWebhookProcess()
+ {
+ $insertCallback = new stdClass();
+ $insertCallback->cNnorderid = !empty($this->orderDetails->cBestellNr) ? $this->orderDetails->cBestellNr : '';
+ $insertCallback->nCallbackTid = $this->parentTid;
+ $insertCallback->nReferenzTid = $this->eventTid;
+ $insertCallback->cZahlungsmethode = !empty($this->orderDetails->cZahlungsartName) ? $this->orderDetails->cZahlungsartName : '';
+ $insertCallback->dDatum = date('Y-m-d H:i:s');
+ $insertCallback->nCallbackAmount = ($this->eventType == 'TRANSACTION_REFUND') ? $this->eventData['transaction']['refund']['amount'] : (isset($this->eventData['transaction']['amount']) ? $this->eventData['transaction']['amount'] : 0);
+ $insertCallback->cWaehrung = !empty($this->orderDetails->Waehrung->code) ? $this->orderDetails->Waehrung->code : '';
+
+ Shop::Container()->getDB()->insert('xplugin_novalnet_callback', $insertCallback);
+ }
+
+ /**
+ * Triggers mail notification to the mail address specified
+ *
+ * @param array $webhookMessage
+ * @return none
+ */
+ public function sendMailNotification($webhookMessage)
+ {
+ // Looping in through the core plugin's mail templates
+ foreach ($this->novalnetPaymentGateway->novalnetPaymentHelper->plugin->getMailTemplates()->getTemplatesAssoc() as $mailTemplate) {
+
+ if ($mailTemplate->cModulId == 'novalnetwebhookmail' && $mailTemplate->cAktiv == 'Y') {
+
+ $adminDetails = Shop::Container()->getDB()->query('SELECT cMail from tadminlogin LIMIT 1', ReturnType::SINGLE_OBJECT);
+
+ // Notification is sent only when the admin login email is configured
+ if (!empty($adminDetails->cMail)) {
+
+ $data = new stdClass();
+ $data->webhookMessage = nl2br($webhookMessage);
+ $data->tkunde = $this->orderDetails->oKunde;
+
+ // Constructing the mail object
+ $mail = new Mail();
+ $mail->setToMail($adminDetails->cMail);
+
+ // Replacing the template variable with the webhook message
+ $mail = $mail->createFromTemplateID('kPlugin_' . $this->novalnetPaymentGateway->novalnetPaymentHelper->plugin->getID() . '_novalnetwebhookmail', $data);
+
+ // Preparing the shop send email function for dispatching the custom email
+ $mailer = Shop::Container()->get(Mailer::class);
+ $mailer->send($mail);
+ }
+ break;
+ }
+ }
+ $this->displayMessage($webhookMessage);
+ }
+
+ public function sendInstalmentMailNotification(array $instalmentInfo, string $additionalInfo) {
+ // Looping in through the core plugin's mail templates
+ foreach ($this->novalnetPaymentGateway->novalnetPaymentHelper->plugin->getMailTemplates()->getTemplatesAssoc() as $mailTemplate) {
+
+ if ($mailTemplate->cModulId == 'novalnetinstalmentmail' && $mailTemplate->cAktiv == 'Y') {
+ $instalmentMsg = \PHP_EOL . $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_next_instalment_message', $this->languageCode);
+
+ $instalmentMsg .= '
' . $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_invoice_payments_order_number_reference', $this->languageCode) . $this->orderDetails->cBestellNr;
+ $instalmentMsg .= '
' . $this->orderDetails->cZahlungsartName;
+
+ $instalmentMsg .= nl2br($additionalInfo);
+
+ $webhookMessage = '
'.$this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_instalment_information', $this->languageCode).'
+
+
+
+
+ '. $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_serial_no', $this->languageCode). ' |
+ '.$this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_instalment_future_date', $this->languageCode).' |
+ ' .$this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_transaction_tid', $this->languageCode).' |
+ '.$this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_instalment_amount', $this->languageCode).' |
+ Status |
+
+
+ ';
+ foreach ($instalmentInfo['insDetails'] as $key => $instalment) {
+ $status = ($instalment['tid'] != '-') ? ($this->languageCode == 'ger' ? 'Bezahlt' : 'Paid') : ($this->languageCode == 'ger' ? 'Offen' : 'Open');
+ $webhookMessage .= '
+
+ '.$key.' |
+ '.$instalment['future_instalment_date'].' |
+ '.$instalment['tid'].' |
+ '.$instalment['cycle_amount'].' |
+ '.$status.' |
+
';
+ }
+ $webhookMessage .= '
+
+
';
+
+ $data = new stdClass();
+ $data->nextInstalmentMsg = $instalmentMsg;
+ $data->instalmentMessage = $webhookMessage;
+ $data->tkunde = $this->orderDetails->oKunde;
+
+ // Constructing the mail object
+ $mail = new Mail();
+ $mail->setToMail($this->orderDetails->oKunde->cMail);
+
+ // Replacing the template variable with the webhook message
+ $mail = $mail->createFromTemplateID('kPlugin_' . $this->novalnetPaymentGateway->novalnetPaymentHelper->plugin->getID() . '_novalnetinstalmentmail', $data);
+
+ // Preparing the shop send email function for dispatching the custom email
+ $mailer = Shop::Container()->get(Mailer::class);
+ $mailer->send($mail);
+ }
+ }
+ }
+
+ public function handleNovalnetRefund() {
+
+ // Get the order number from wawi
+ $getKeyValues = $_POST;
+ $orderNumber = array_keys($getKeyValues);
+
+ // Get the amount, and TID from Database
+ $orderDetails = Shop::Container()->getDB()->query('SELECT nBetrag, nNntid, cAdditionalInfo FROM xplugin_novalnet_transaction_details WHERE cNnorderid = "' . $orderNumber[0] . '"', ReturnType::SINGLE_OBJECT);
+ $oldTransactionComment = Shop::Container()->getDB()->query('SELECT cKommentar, kSprache FROM tbestellung WHERE cBestellNr = "' . $orderNumber[0] . '"', ReturnType::SINGLE_OBJECT);
+ $amount = json_decode($orderDetails->nBetrag, true);
+ $tid = json_decode($orderDetails->nNntid, true);
+ $txAdditonalDetails = json_decode($orderDetails->cAdditionalInfo, true);
+ $lang = json_decode($oldTransactionComment->kSprache, true);
+ $this->languageCode = $lang == 2 ? 'eng' : 'ger';
+
+ // Form the refund request Parameters
+ $data = [];
+ $data['transaction'] = [
+ 'tid' => $tid,
+ 'amount' => $amount,
+ ];
+ $data['custom'] = [
+ 'lang' => $lang == 2 ? 'EN' : 'DE',
+ 'shop_invoked' => 1
+ ];
+
+ // Do the refund call to Novalnet server
+ $response = $this->novalnetPaymentGateway->performServerCall($data, 'transaction_refund');
+
+ // Create a new entry into callback table for the refund details
+ $insertCallback = new stdClass();
+ $insertCallback->cNnorderid = !empty($response['transaction']['order_no']) ? $response['transaction']['order_no'] : '';
+ $insertCallback->nCallbackTid = $response['transaction']['tid'];
+ $insertCallback->nReferenzTid = $response['transaction']['refund']['tid'];
+ $insertCallback->cZahlungsmethode = '';
+ $insertCallback->dDatum = date('Y-m-d H:i:s');
+ $insertCallback->nCallbackAmount = ($response['transaction']['refund']['amount']) ? $response['transaction']['refund']['amount'] : (isset($response['transaction']['amount']) ? $response['transaction']['amount'] : 0);
+ $insertCallback->cWaehrung = !empty($response['transaction']['currency']) ? $response['transaction']['currency'] : '';
+ Shop::Container()->getDB()->insert('xplugin_novalnet_callback', $insertCallback);
+
+ // Handling the refund success response
+ if ($response['result']['status'] == 'SUCCESS') {
+ // Form the refund new tid comments by the response
+ $webhookComments = sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_refund_execution', $this->languageCode), $response['transaction']['tid'], Preise::getLocalizedPriceWithoutFactor($response['transaction']['refund']['amount'] / 100, Frontend::getCurrency(), false ));
+ if (!empty($response['transaction']['refund']['tid'])) {
+ $webhookComments .= PHP_EOL . sprintf($this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLangTranslationText('jtl_novalnet_new_tid_refund_execution', $this->languageCode), $response['transaction']['refund']['tid']);
+ }
+
+ // Store the refunded amount details into the Database
+ $txAdditonalInfo = !empty($txAdditonalDetails) ? $txAdditonalDetails : [];
+ $txAdditonalInfo['refunded_amount'] = $response['transaction']['refunded_amount'];
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('xplugin_novalnet_transaction_details', 'cNnorderid', $orderNumber[0], ['cAdditionalInfo' => json_encode($txAdditonalInfo)]);
+
+ } else {
+ $webhookComments = PHP_EOL . $response['result']['status_text'];
+ }
+
+ // Store the refund comments into the Database
+ $oldTransactionComment = Shop::Container()->getDB()->query('SELECT cKommentar, kSprache FROM tbestellung WHERE cBestellNr = "' . $orderNumber[0] . '"', ReturnType::SINGLE_OBJECT);
+ $webhookMessage = !empty($oldTransactionComment->cKommentar) ? ($oldTransactionComment->cKommentar . \PHP_EOL . $webhookComments) : $webhookComments;
+ $cKommentar = array('cKommentar' => $webhookMessage);
+ $cStatus = [];
+ // In case of full refund, deactivation is processed
+ if ($response['transaction']['status'] == 'DEACTIVATED') {
+ // We do not send to webhookFinalprocess for update as it will cause problems with WAWI synchronization
+ $cStatus = array('cStatus' => \BESTELLUNG_STATUS_STORNO);
+ }
+
+ $updateValues = array_merge($cKommentar, $cStatus);
+ // Update comments and status into the Database
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('tbestellung', 'cBestellNr', $orderNumber[0], $updateValues);
+
+ }
+
+}
diff --git a/adminmenu/NovalnetBackendTabRenderer.php b/adminmenu/NovalnetBackendTabRenderer.php
new file mode 100755
index 0000000..0d29aed
--- /dev/null
+++ b/adminmenu/NovalnetBackendTabRenderer.php
@@ -0,0 +1,234 @@
+plugin = $plugin;
+ $this->db = $db;
+ $this->novalnetPaymentGateway = new NovalnetPaymentGateway();
+ }
+
+ /**
+ * @param string $tabName
+ * @param int $menuID
+ * @param JTLSmarty $smarty
+ * @return string
+ * @throws \SmartyException
+ */
+ public function renderNovalnetTabs(string $tabName, int $menuID, JTLSmarty $smarty): string
+ {
+ $this->smarty = $smarty;
+
+ if ($tabName == 'Info') {
+ return $this->renderNovalnetInfoPage();
+ } elseif ($tabName == 'Bestellungen') {
+ return $this->renderNovalnetOrdersPage($menuID);
+ } else {
+ throw new InvalidArgumentException('Cannot render tab ' . $tabName);
+ }
+ }
+
+ /**
+ * Display the Novalnet info template page
+ *
+ * @return string
+ */
+ private function renderNovalnetInfoPage(): string
+ {
+ $novalnetRequestType = !empty($_REQUEST['nn_request_type']) ? $_REQUEST['nn_request_type'] : null;
+ $langCode = ($_SESSION['AdminAccount']->language == 'de-DE') ? 'ger' : 'eng';
+ $novalnetWebhookUrl = !empty($this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('novalnet_webhook_url')) ? $this->novalnetPaymentGateway->novalnetPaymentHelper->getConfigurationValues('novalnet_webhook_url') : Shop::getURL() . '/?novalnet_webhook';
+
+ if (!empty($novalnetRequestType)) {
+ // Based on the request type, we either auto-configure the merchant settings or configure the webhook URL
+ if ($novalnetRequestType == 'autofill') {
+ $this->handleMerchantAutoConfig($_REQUEST);
+ } elseif ($novalnetRequestType == 'configureWebhook') {
+ $this->configureWebhookUrl($_REQUEST);
+ }
+ }
+
+ return $this->smarty->assign('pluginDetails', $this->plugin)
+ ->assign('postUrl', Shop::getURL() . '/' . \PFAD_ADMIN . 'plugin.php?kPlugin=' . $this->plugin->getID())
+ ->assign('languageTexts', $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLanguageText(['jtl_novalnet_notification_text', 'jtl_novalnet_configure_webhook', 'jtl_novalnet_webhook_alert_text', 'jtl_novalnet_webhook_notification_text', 'jtl_novalnet_webhook_error_text', 'jtl_novalnet_webhook_configuration_tooltip', 'jtl_novalnet_due_date_error_text', 'jtl_novalnet_guarantee_min_amount_error_text', 'jtl_novalnet_instalment_min_amount_error_text', 'jtl_novalnet_guarantee_condition_text', 'jtl_novalnet_instalment_condition_text', 'jtl_novalnet_webhook_notification', 'jtl_novalnet_paypal_configuration_text', 'jtl_novalnet_info_page_text', 'jtl_novalnet_multiselect_option_text', 'jtl_novalnet_google_pay_merchant_id_error'], $langCode))
+ ->assign('shopLang', $_SESSION['AdminAccount']->language)
+ ->assign('adminUrl', $this->plugin->getPaths()->getadminURL())
+ ->assign('webhookUrl', $novalnetWebhookUrl)
+ ->assign('shopName', Shop::getSettingValue(\CONF_GLOBAL, 'global_shopname'))
+ ->fetch($this->plugin->getPaths()->getAdminPath() . 'templates/novalnet_info.tpl');
+ }
+
+ /**
+ * Display the Novalnet order template
+ *
+ * @param int $menuID
+ * @return string
+ */
+ private function renderNovalnetOrdersPage(int $menuID): string
+ {
+ $novalnetRequestType = !empty($_REQUEST['nn_request_type']) ? $_REQUEST['nn_request_type'] : null;
+
+ if (!empty($novalnetRequestType)) {
+ $this->displayNovalnetorderDetails($_REQUEST, $menuID);
+ }
+
+ $orders = [];
+ $nnOrderCount = $this->db->query('SELECT cNnorderid FROM xplugin_novalnet_transaction_details', ReturnType::AFFECTED_ROWS);
+ $pagination = (new Pagination('novalnetorders'))->setItemCount($nnOrderCount)->assemble();
+ $langCode = ($_SESSION['AdminAccount']->language == 'de-DE') ? 'ger' : 'eng';
+
+ $orderArr = $this->db->query('SELECT DISTINCT ord.kBestellung FROM tbestellung ord JOIN xplugin_novalnet_transaction_details nov WHERE ord.cBestellNr = nov.cNnorderid ORDER BY ord.kBestellung DESC LIMIT ' . $pagination->getLimitSQL(), ReturnType::ARRAY_OF_OBJECTS);
+
+ foreach ($orderArr as $order) {
+ $orderId = (int) $order->kBestellung;
+ $ordObj = new Bestellung($orderId);
+ $ordObj->fuelleBestellung(true, 0, false);
+ $orders[$orderId] = $ordObj;
+ }
+
+ if ($_SESSION['AdminAccount']->language == 'de-DE') {
+ $paymentStatus = ['5' => 'teilversendet', '4' => 'versendet', '3' => 'bezahlt', '2' => 'in Bearbeitung' , '1' => 'offen' , '-1' => 'Storno'];
+ } else {
+ $paymentStatus = ['5' => 'partially shipped', '4' => 'shipped', '3' => 'paid', '2' => 'in processing' , '1' => 'open' , '-1' => 'canceled'];
+ }
+
+ return $this->smarty->assign('orders', $orders)
+ ->assign('pagination', $pagination)
+ ->assign('pluginId', $this->plugin->getID())
+ ->assign('postUrl', Shop::getURL() . '/' . \PFAD_ADMIN . 'plugin.php?kPlugin=' . $this->plugin->getID())
+ ->assign('paymentStatus', $paymentStatus)
+ ->assign('hash', 'plugin-tab-' . $menuID)
+ ->assign('languageTexts', $this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLanguageText(['jtl_novalnet_order_number', 'jtl_novalnet_customer_text', 'jtl_novalnet_payment_name_text', 'jtl_novalnet_wawi_pickup', 'jtl_novalnet_total_amount_text', 'jtl_novalnet_order_creation_date', 'jtl_novalnet_orders_not_available'], $langCode))
+ ->fetch($this->plugin->getPaths()->getAdminPath() . 'templates/novalnet_orders.tpl');
+ }
+
+ /**
+ * Handling of the merchant auto configuration process
+ *
+ * @param array $post
+ * @return none
+ */
+ private function handleMerchantAutoConfig(array $post): void
+ {
+ $autoConfigRequestParams = [];
+ $autoConfigRequestParams['merchant']['signature'] = $post['nn_public_key'];
+ $autoConfigRequestParams['custom']['lang'] = ($_SESSION['AdminAccount']->language == 'de-DE') ? 'DE' : 'EN';
+
+ $responseData = $this->novalnetPaymentGateway->performServerCall($autoConfigRequestParams, 'merchant_details', $post['nn_private_key']);
+ print json_encode($responseData);
+ exit;
+ }
+
+
+ /**
+ * Configuring webhook URL in admin portal
+ *
+ * @param array $post
+ * @return none
+ */
+ private function configureWebhookUrl(array $post): void
+ {
+ $webhookRequestParams = [];
+ $webhookRequestParams['merchant']['signature'] = $post['nn_public_key'];
+ $webhookRequestParams['webhook']['url'] = $post['nn_webhook_url'];
+ $webhookRequestParams['custom']['lang'] = ($_SESSION['AdminAccount']->language == 'de-DE') ? 'DE' : 'EN';
+
+ $responseData = $this->novalnetPaymentGateway->performServerCall($webhookRequestParams, 'webhook_configure', $post['nn_private_key']);
+
+ // Upon successful intimation in Novalnet server, we also store it in the internal DB
+ if ($responseData['result']['status'] == 'SUCCESS') {
+ $this->novalnetPaymentGateway->novalnetPaymentHelper->performDbUpdateProcess('tplugineinstellungen', 'cName', 'novalnet_webhook_url', ['cWert' => $post['nn_webhook_url']]);
+ }
+
+ print json_encode($responseData);
+ exit;
+ }
+
+ /**
+ * Display the Novalnet transaction details template
+ *
+ * @param int $menuID
+ * @return string
+ */
+ private function displayNovalnetorderDetails(array $post, int $menuID): string
+ {
+ $getOrderComment = $this->db->query('SELECT ord.cKommentar, ord.kSprache FROM tbestellung ord JOIN xplugin_novalnet_transaction_details nov ON ord.cBestellNr = nov.cNnorderid WHERE cNnorderid = "' . $post['order_no'] . '"', ReturnType::SINGLE_OBJECT);
+
+ $langCode = ($getOrderComment->kSprache == 1) ? 'ger' : 'eng';
+
+ $instalmentInfo = $this->novalnetPaymentGateway->getInstalmentInfoFromDb($post['order_no'], $langCode);
+
+ $smartyVar = $this->smarty->assign('adminUrl', $this->plugin->getPaths()->getadminURL())
+ ->assign('orderNo', $post['order_no'])
+ ->assign('languageTexts',$this->novalnetPaymentGateway->novalnetPaymentHelper->getNnLanguageText(['jtl_novalnet_invoice_payments_order_number_reference'], $langCode))
+ ->assign('orderComment', $getOrderComment)
+ ->assign('menuId', '#plugin-tab-' . $menuID)
+ ->assign('instalmentDetails', $instalmentInfo)
+ ->fetch($this->plugin->getPaths()->getAdminPath() . 'templates/novalnet_order_details.tpl');
+
+ print $smartyVar;
+ exit;
+ }
+}
+
diff --git a/adminmenu/css/novalnet_admin.css b/adminmenu/css/novalnet_admin.css
new file mode 100755
index 0000000..c5d566c
--- /dev/null
+++ b/adminmenu/css/novalnet_admin.css
@@ -0,0 +1,49 @@
+.nn_fa {
+ float: right;
+ font-size: 15px;
+}
+
+.nn_additional_content {
+ border-left: 3px solid #5cbcf6 !important;
+ padding: 5px 10px;
+ background-color: #f5f7fa;
+ border: 1px solid #f5f7fa;
+}
+
+.nn_content {
+ font-size: 13px;
+}
+
+#nn_header {
+ color :#e85b0e;
+ font-size:14px;
+ font-weight:bold;
+}
+
+.body_div {
+ padding:5px;
+ overflow-y:auto;
+ margin-bottom : 2%;
+ height:82%;
+}
+
+#overlay_window_block_body h1 {
+ font-weight:bold;
+}
+
+#nn_transaction_details {
+ line-height: 2.0;
+}
+
+.nn_back_tab {
+ color: white;
+ float: right;
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+.nn_webhook_button, .nn_webhook_notify, .nn_paypal_notify {
+ margin-left: 35% !important;
+}
+
+
diff --git a/adminmenu/js/novalnet_admin.js b/adminmenu/js/novalnet_admin.js
new file mode 100755
index 0000000..85e5fa6
--- /dev/null
+++ b/adminmenu/js/novalnet_admin.js
@@ -0,0 +1,354 @@
+jQuery(document).ready(function() {
+ // Validate the Google Pay Seller name
+ if(jQuery('#novalnet_googlepay_seller_name').val() == '') {
+ jQuery('#novalnet_googlepay_seller_name').val(jQuery('#nn_shop_name').val());
+ }
+
+ // Validate the Apple Pay Seller name
+ if(jQuery('#novalnet_applepay_seller_name').val() == '') {
+ jQuery('#novalnet_applepay_seller_name').val(jQuery('#nn_shop_name').val());
+ }
+
+ // Add minimum, maximum length and case sensitive attribute to merchant country code
+ jQuery('#novalnet_googlepay_country_code').attr({minlength: 2, maxlength: 2});
+ jQuery('#novalnet_googlepay_country_code').on('input',function ( event ) {
+ let merchantCountryCode = jQuery(this).val().replace( /[^A-Z]+/g, "" ).replace( /\s+/g, "" );
+ jQuery(this).val(merchantCountryCode);
+ });
+
+ // Select all credit card types
+ if (jQuery('#novalnet_cc_accepted_card_types').val() == '') {
+ jQuery('#novalnet_cc_accepted_card_types option').each(function() {
+ var optionVal = jQuery(this).val();
+ jQuery('#novalnet_cc_accepted_card_types option[value=' + optionVal + ']').attr('selected', true);
+
+ });
+ }
+
+ // Select all credit card types
+ jQuery.each(['#novalnet_applepay_button_display', '#novalnet_googlepay_button_display'], function (index, element) {
+ if (jQuery(element).val() == '') {
+ jQuery(element + ' option').each(function() {
+ var optionVal = jQuery(this).val();
+ jQuery(element + ' option[value=' + optionVal + ']').attr('selected', true);
+ });
+ }
+ });
+
+ // Select default instalment cycles
+ jQuery.each(['#novalnet_instalment_invoice_cycles', '#novalnet_instalment_sepa_cycles'], function (index, element) {
+ if (jQuery('#nn_shop_lang').val() != 'de-DE') {
+ jQuery(element + ' ' + 'option').each(function() {
+ jQuery(this).text(jQuery(this).html().replace(/\bZyklen\b/g, 'Cycle'));
+ });
+ }
+
+ if (jQuery(element).val() == '') {
+ jQuery(element + ' ' + 'option').each(function() {
+ if (jQuery(this).val() <= 36) {
+ jQuery(element + ' ' + 'option[value=' + jQuery(this).val() + ']').attr('selected', true);
+ }
+ });
+ }
+ });
+
+ // Alert if the multiple selection doesn' have anyne selection
+ jQuery.each(['#novalnet_cc_accepted_card_types', '#novalnet_instalment_invoice_cycles', '#novalnet_instalment_sepa_cycles'], function (index, element) {
+ jQuery(element).on('change', function () {
+ if (!jQuery(element + ' ' + "option:selected").length) {
+ handleErrorElement(jQuery(element), jQuery('#nn_multiselect_text').val());
+ }
+ });
+ });
+
+ // set the toggle for the payment settings
+ var paymentSettings = jQuery('.tab-content').children()[2];
+
+ jQuery(paymentSettings).find('[class*=subheading]').append('');
+
+ jQuery(paymentSettings).find('.mb-3').hover(function () {
+ jQuery(this).css('cursor', 'pointer');
+ });
+
+ // Show and hide the authorization amount field value
+ jQuery.each(['cc', 'sepa', 'invoice', 'paypal', 'guaranteed_invoice', 'guaranteed_sepa', 'instalment_invoice', 'instalment_sepa', 'googlepay', 'applepay'],function(index, value) {
+ if(jQuery(paymentSettings).find('#novalnet_'+value+'_payment_action').val() == 0) {
+ jQuery(paymentSettings).find('#novalnet_'+value+'_manual_check_limit').parent().parent().hide();
+ }
+ jQuery(paymentSettings).find('#novalnet_'+value+'_payment_action').on('change',function(event){
+ if(jQuery(paymentSettings).find('#novalnet_'+value+'_payment_action').val() == 0) {
+ jQuery(paymentSettings).find('#novalnet_'+value+'_manual_check_limit').parent().parent().hide();
+ } else {
+ jQuery(paymentSettings).find('#novalnet_'+value+'_manual_check_limit').parent().parent().show();
+ }
+ });
+ });
+
+ // Set the error class if the condition not met
+ jQuery('#novalnet_sepa_due_date, #novalnet_invoice_due_date, #novalnet_prepayment_due_date, #novalnet_guaranteed_invoice_min_amount, #novalnet_guaranteed_sepa_min_amount, #novalnet_instalment_invoice_min_amount, #novalnet_instalment_sepa_min_amount').parent().on('change', function() {
+ if (jQuery(this).hasClass('set_error')) jQuery(this).removeClass('set_error');
+ });
+
+ // Payment settings toggle
+ jQuery('.nn_fa').each(function(){
+ jQuery(this).parent().addClass('nn-toggle-heading');
+ jQuery(this).parent().next().next().addClass('nn-toggle-content');
+ });
+ jQuery('.nn-toggle-content').hide();
+
+ jQuery('.nn-toggle-heading').on('click',function(){
+ jQuery(this).next().next().toggle(700);
+ if( jQuery(this).children('i').hasClass('fa-chevron-circle-down') ) {
+ jQuery(this).children('i').addClass('fa-chevron-circle-up').removeClass('fa-chevron-circle-down');
+ } else {
+ jQuery(this).children('i').addClass('fa-chevron-circle-down').removeClass('fa-chevron-circle-up');
+ }
+ });
+
+ // Hide the client key field
+ jQuery('input[id=novalnet_client_key]').parent().parent('.form-group').addClass('hide_client_key');
+ jQuery('.hide_client_key').hide();
+
+ if (jQuery('#novalnet_tariffid').val() == undefined) {
+ jQuery('input[name=novalnet_tariffid]').attr('id', 'novalnet_tariffid');
+ }
+
+ // Display the alert box if the public and private key was not configured
+ if (jQuery('input[name=novalnet_public_key]').val() == '' && jQuery('input[name=novalnet_private_key]').val() == '') {
+ jQuery('.content-header').prepend('' + ' ' + jQuery('input[name=nn_lang_notification]').val() + '
');
+
+
+ }
+
+ // Autofill the merchant details
+ if (jQuery('input[name=novalnet_public_key]').val() != undefined && jQuery('input[name=novalnet_public_key]').val() != '') {
+ fillMerchantConfiguration();
+ } else if (jQuery('input[name=novalnet_public_key]').val() == '') {
+ jQuery('#novalnet_tariffid').val('');
+ }
+
+ jQuery('input[name=novalnet_public_key], input[name=novalnet_private_key]' ).on('change', function () {
+ if (jQuery('input[name=novalnet_public_key]').val() != '' && jQuery('input[name=novalnet_private_key]').val() != '') {
+ fillMerchantConfiguration();
+ } else {
+ jQuery('#novalnet_tariffid').val('');
+ }
+ });
+
+ // Set the webhook URL
+ jQuery('input[name=novalnet_webhook_url]').val(jQuery('#nn_webhook_url').val());
+ let text = jQuery('input[name=nn_webhook_change]').val();
+ jQuery('#novalnet_webhook_url').parent().parent().after(' ');
+
+ jQuery('#nn_webhook_configure_button').on('click', function() {
+ if(jQuery('#novalnet_webhook_url').val() != undefined && jQuery('#novalnet_webhook_url').val() != '') {
+ if (confirm(text) == true) {
+ configureWebhookUrlAdminPortal();
+ } else {
+ location.reload();
+ }
+ } else {
+ alert(jQuery('input[name=nn_webhook_invalid]').val());
+ }
+ });
+
+ // Backend payment configuration validation
+ jQuery('button[name=speichern]').on('click', function(event){
+ // SEPA payment due date validation
+ jQuery.each(['#novalnet_sepa_due_date'], function (index, element) {
+ if (jQuery.trim(jQuery(element).val()) != '' && (isNaN(jQuery(element).val()) || jQuery(element).val() < 2 || jQuery(element).val() > 14)) {
+ handleErrorElement(jQuery(element), jQuery('#nn_invalid_due_date').val());
+ }
+ });
+
+ // INVOICE and Prepayment due date validation
+ if (jQuery.trim(jQuery('#novalnet_invoice_due_date').val()) != '' && (isNaN(jQuery('#novalnet_invoice_due_date').val()) || jQuery('#novalnet_invoice_due_date').val() < 7 )) {
+ handleErrorElement(jQuery('#novalnet_invoice_due_date'), jQuery('#nn_invalid_due_date').val());
+ }
+
+ if (jQuery.trim(jQuery('#novalnet_prepayment_due_date').val()) != '' && (isNaN(jQuery('#novalnet_prepayment_due_date').val()) || jQuery('#novalnet_prepayment_due_date').val() < 7 || jQuery('#novalnet_prepayment_due_date').val() > 28)) {
+ handleErrorElement(jQuery('#novalnet_prepayment_due_date'), jQuery('#nn_invalid_due_date').val());
+ }
+
+ // Minimum Instalment amount validation
+ jQuery.each(['#novalnet_instalment_invoice_min_amount', '#novalnet_instalment_sepa_min_amount'], function (index, element) {
+ if (jQuery.trim(jQuery(element).val()) != '' && (isNaN(jQuery(element).val()) || jQuery(element).val() < 1998)) {
+ handleErrorElement(jQuery(element), jQuery('#nn_instalment_min_amount_error_text').val());
+ }
+ });
+
+ // Minimum guarantee payment amount validation
+ jQuery.each(['#novalnet_guaranteed_invoice_min_amount', '#novalnet_guaranteed_sepa_min_amount'], function (index, element) {
+ if (jQuery.trim(jQuery(element).val()) != '' && (isNaN(jQuery(element).val()) || jQuery(element).val() < 999)) {
+
+ handleErrorElement(jQuery(element), jQuery('#nn_guarantee_min_amount_error_text').val());
+ }
+ });
+
+ jQuery.each(['#novalnet_cc_accepted_card_types', '#novalnet_instalment_invoice_cycles', '#novalnet_instalment_sepa_cycles'], function (index, element) {
+ if (jQuery(element).val() == '') {
+ handleErrorElement(jQuery(element), jQuery('#nn_multiselect_text').val());
+ }
+ });
+ });
+
+ // Display the payment messages below the payment type
+ jQuery.each(['#novalnet_instalment_invoice_enablemode', '#novalnet_instalment_sepa_enablemode'], function (index, element) {
+ jQuery(element).closest('.nn-toggle-content').prepend(('' + jQuery('#nn_instalment_payment_conditions').val() + '
'));
+ });
+
+ jQuery.each(['#novalnet_guaranteed_invoice_enablemode', '#novalnet_guaranteed_sepa_enablemode'], function (index, element) {
+ jQuery(element).closest('.nn-toggle-content').prepend(('' + jQuery('#nn_guarantee_payment_conditions').val() + '
'));
+ });
+
+ jQuery('#novalnet_paypal_enablemode').closest('.nn-toggle-content').prepend(('' + jQuery('#nn_paypal_api_configure').val() + '
'));
+
+ jQuery('#novalnet_webhook_testmode').parent().parent().parent().parent().after(('' + jQuery('#nn_webhook_notification').val() + '
'));
+});
+
+function fillMerchantConfiguration() {
+ var autoconfigurationRequestParams = { 'nn_public_key' : jQuery('input[name=novalnet_public_key]').val(), 'nn_private_key' : jQuery('input[name=novalnet_private_key]').val(), 'nn_request_type' : 'autofill' };
+ transactionRequestHandler(autoconfigurationRequestParams);
+}
+
+function transactionRequestHandler(requestParams)
+{
+ requestParams = typeof(requestParams !== 'undefined') ? requestParams : '';
+
+ var requestUrl = jQuery('input[id=nn_post_url]').val() ;
+ if ('XDomainRequest' in window && window.XDomainRequest !== null) {
+ var xdr = new XDomainRequest();
+ var query = jQuery.param(requestParams);
+ xdr.open('GET', requestUrl + query) ;
+ xdr.onload = function () {
+ autofillMerchantDetails(this.responseText);
+ };
+ xdr.onerror = function () {
+ _result = false;
+ };
+ xdr.send();
+ } else {
+ jQuery.ajax({
+ url : requestUrl,
+ type : 'post',
+ dataType : 'html',
+ data : requestParams,
+ global : false,
+ async : false,
+ success : function (result) {
+ autofillMerchantDetails(result);
+ }
+ });
+ }
+}
+
+function autofillMerchantDetails(result)
+{
+ var fillParams = jQuery.parseJSON(result);
+
+ if (fillParams.result.status != 'SUCCESS') {
+ jQuery('input[name="novalnet_public_key"],input[name="novalnet_private_key"]').val('');
+ jQuery('.content-header').prepend('' + fillParams.result.status_text + '
');
+ jQuery('#novalnet_tariffid').val('');
+ return false;
+ }
+
+ var tariffKeys = Object.keys(fillParams.merchant.tariff);
+ var saved_tariff_id = jQuery('#novalnet_tariffid').val();
+ var tariff_id;
+
+ try {
+ var select_text = decodeURIComponent(escape('Auswählen'));
+ } catch(e) {
+ var select_text = 'Auswählen';
+ }
+
+ jQuery('#novalnet_tariffid').replaceWith('');
+
+ jQuery('#novalnet_tariffid').find('option').remove();
+
+ for (var i = 0; i < tariffKeys.length; i++)
+ {
+ if (tariffKeys[i] !== undefined) {
+ jQuery('#novalnet_tariffid').append(
+ jQuery(
+ '