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).'

+ + + + + + + + + + + + '; + foreach ($instalmentInfo['insDetails'] as $key => $instalment) { + $status = ($instalment['tid'] != '-') ? ($this->languageCode == 'ger' ? 'Bezahlt' : 'Paid') : ($this->languageCode == 'ger' ? 'Offen' : 'Open'); + $webhookMessage .= ' + + + + + + + '; + } + $webhookMessage .= ' + +
'. $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
'.$key.''.$instalment['future_instalment_date'].''.$instalment['tid'].''.$instalment['cycle_amount'].''.$status.'
'; + + $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( + '