cVorname} {$Kunde->cNachname}, vielen Dank für Ihre Bestellung bei {$Einstellungen.global.global_shopname}.Leider konnten wir für die Bestellung {$oPluginMail->Bestellung->cBestellNr} bisher keinen Zahlungseingang feststellen.
Sollte es bei der Zahlung zu Problemen gekommen sein, können Sie diese über den folgenden Link einfach erneut versuchen.
Zu zahlender Betrag: {$oPluginMail->Amount}
Jetzt bezahlen: {$oPluginMail->PayURL}
{includeMailTemplate template=footer type=html}]]>
- cVorname} {$Kunde->cNachname},
vielen Dank für Ihre Bestellung bei {$Einstellungen.global.global_shopname}.
diff --git a/version/100/adminmenu/info.php b/version/100/adminmenu/info.php
index cbffae6..f6e4e4a 100644
--- a/version/100/adminmenu/info.php
+++ b/version/100/adminmenu/info.php
@@ -1,6 +1,7 @@
getBestellung()->oKunde->nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
- )
+ array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
+ )
)
&& isset($customer)
) {
diff --git a/version/200/class/Checkout/Payment/Address.php b/version/200/class/Checkout/Payment/Address.php
index c23e53e..468a08a 100644
--- a/version/200/class/Checkout/Payment/Address.php
+++ b/version/200/class/Checkout/Payment/Address.php
@@ -1,6 +1,7 @@
getBestellung()->oKunde->nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
- )
+ array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
+ )
)
&& isset($customer)
) {
diff --git a/version/200/class/ExclusiveLock.php b/version/200/class/ExclusiveLock.php
index 8a2cd2a..1d596e4 100644
--- a/version/200/class/ExclusiveLock.php
+++ b/version/200/class/ExclusiveLock.php
@@ -1,6 +1,7 @@
cType))) {
+ if (([$type, $id] = explode(':', $todo->cType))) {
try {
switch ($type) {
case 'webhook':
diff --git a/version/200/class/Shipment.php b/version/200/class/Shipment.php
index c01090a..31e6729 100644
--- a/version/200/class/Shipment.php
+++ b/version/200/class/Shipment.php
@@ -1,6 +1,7 @@
getBestellung()->oKunde->nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
- )
+ array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
+ )
)
&& isset($customer)
) {
diff --git a/version/201/class/Checkout/Payment/Address.php b/version/201/class/Checkout/Payment/Address.php
index c23e53e..468a08a 100644
--- a/version/201/class/Checkout/Payment/Address.php
+++ b/version/201/class/Checkout/Payment/Address.php
@@ -1,6 +1,7 @@
getBestellung()->oKunde->nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
- )
+ array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
+ )
)
&& isset($customer)
) {
diff --git a/version/201/class/ExclusiveLock.php b/version/201/class/ExclusiveLock.php
index 8a2cd2a..1d596e4 100644
--- a/version/201/class/ExclusiveLock.php
+++ b/version/201/class/ExclusiveLock.php
@@ -1,6 +1,7 @@
cType))) {
+ if (([$type, $id] = explode(':', $todo->cType))) {
try {
switch ($type) {
case 'webhook':
diff --git a/version/201/class/Shipment.php b/version/201/class/Shipment.php
index c01090a..31e6729 100644
--- a/version/201/class/Shipment.php
+++ b/version/201/class/Shipment.php
@@ -1,6 +1,7 @@
getBestellung()->oKunde->nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
- )
+ array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
+ )
)
&& isset($customer)
) {
diff --git a/version/202/class/Checkout/Payment/Address.php b/version/202/class/Checkout/Payment/Address.php
index c23e53e..468a08a 100644
--- a/version/202/class/Checkout/Payment/Address.php
+++ b/version/202/class/Checkout/Payment/Address.php
@@ -1,6 +1,7 @@
getBestellung()->oKunde->nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
- )
+ array_key_exists('mollie_create_customer', $_SESSION['cPost_arr'] ?: []) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y'
+ )
)
&& isset($customer)
) {
diff --git a/version/202/class/ExclusiveLock.php b/version/202/class/ExclusiveLock.php
index 8a2cd2a..1d596e4 100644
--- a/version/202/class/ExclusiveLock.php
+++ b/version/202/class/ExclusiveLock.php
@@ -1,6 +1,7 @@
cType))) {
+ if (([$type, $id] = explode(':', $todo->cType))) {
try {
switch ($type) {
case 'webhook':
diff --git a/version/202/class/Shipment.php b/version/202/class/Shipment.php
index c01090a..31e6729 100644
--- a/version/202/class/Shipment.php
+++ b/version/202/class/Shipment.php
@@ -1,6 +1,7 @@
nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists(
- 'mollie_create_customer',
- $_SESSION['cPost_arr'] ?: []
- ) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y',
- $oKunde
- )
+ array_key_exists(
+ 'mollie_create_customer',
+ $_SESSION['cPost_arr'] ?: []
+ ) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y',
+ $oKunde
+ )
)
&& isset($customer)
) {
diff --git a/version/203/class/Checkout/AbstractResource.php b/version/203/class/Checkout/AbstractResource.php
index c49adb7..7c4439e 100644
--- a/version/203/class/Checkout/AbstractResource.php
+++ b/version/203/class/Checkout/AbstractResource.php
@@ -1,6 +1,7 @@
cType))) {
+ if (([$type, $id] = explode(':', $todo->cType))) {
try {
switch ($type) {
case 'webhook':
diff --git a/version/203/class/Shipment.php b/version/203/class/Shipment.php
index b829f0a..31d16a3 100644
--- a/version/203/class/Shipment.php
+++ b/version/203/class/Shipment.php
@@ -1,6 +1,7 @@
nRegistriert
&& (
$customer = $this->getCustomer(
- array_key_exists(
- 'mollie_create_customer',
- $_SESSION['cPost_arr'] ?: []
- ) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y',
- $oKunde
- )
+ array_key_exists(
+ 'mollie_create_customer',
+ $_SESSION['cPost_arr'] ?: []
+ ) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y',
+ $oKunde
+ )
)
&& isset($customer)
) {
diff --git a/version/204/class/Checkout/AbstractResource.php b/version/204/class/Checkout/AbstractResource.php
index c49adb7..7c4439e 100644
--- a/version/204/class/Checkout/AbstractResource.php
+++ b/version/204/class/Checkout/AbstractResource.php
@@ -1,6 +1,7 @@
cType))) {
+ if (([$type, $id] = explode(':', $todo->cType))) {
try {
switch ($type) {
case 'webhook':
diff --git a/version/204/class/Shipment.php b/version/204/class/Shipment.php
index c807e7f..a22a8d3 100644
--- a/version/204/class/Shipment.php
+++ b/version/204/class/Shipment.php
@@ -1,6 +1,7 @@
title = substr(trim(($address->cAnrede === 'm' ? Shop::Lang()->get('mr') : Shop::Lang()->get('mrs')) . ' ' . $address->cTitel) ?: null, 0 , 20);
+ $resource->title = substr(trim(($address->cAnrede === 'm' ? Shop::Lang()->get('mr') : Shop::Lang()->get('mrs')) . ' ' . $address->cTitel) ?: null, 0, 20);
$resource->givenName = $address->cVorname;
$resource->familyName = $address->cNachname;
$resource->email = $address->cMail ?: null;
diff --git a/version/205/class/Checkout/Order/OrderLine.php b/version/205/class/Checkout/Order/OrderLine.php
index 878d8da..757edf7 100644
--- a/version/205/class/Checkout/Order/OrderLine.php
+++ b/version/205/class/Checkout/Order/OrderLine.php
@@ -1,7 +1,7 @@
cType))) {
+ if (([$type, $id] = explode(':', $todo->cType))) {
try {
switch ($type) {
case 'webhook':
@@ -109,7 +109,6 @@ public static function run($limit = 10)
*/
private static function getOpen($limit)
{
- // TODO: DOKU!
if (!defined('MOLLIE_HOOK_DELAY')) {
define('MOLLIE_HOOK_DELAY', 3);
}
@@ -167,6 +166,7 @@ protected static function handleHook($hook, QueueModel $todo)
switch ($hook) {
case HOOK_BESTELLUNGEN_XML_BESTELLSTATUS:
if ((int)$data['kBestellung']) {
+ // TODO: #158 What happens when API requests fail?
$checkout = AbstractCheckout::fromBestellung($data['kBestellung']);
$status = array_key_exists('status', $data) ? (int)$data['status'] : 0;
@@ -212,7 +212,7 @@ protected static function handleHook($hook, QueueModel $todo)
$result = $e->getMessage() . "\n" . $e->getFile() . ':' . $e->getLine() . "\n" . $e->getTraceAsString();
}
} else {
- $result = sprintf('Unerwarteter Mollie Status "%s" für %s', $checkout->getMollie()->status, $checkout->getBestellung()->cBestellNr);
+ $result = sprintf('Unerwarteter Mollie Status "%s" für %s', $checkout->getMollie()->status, $checkout->getBestellung()->cBestellNr);
}
} else {
$result = 'Nothing to do.';
diff --git a/version/205/class/Shipment.php b/version/205/class/Shipment.php
index 3cf7a91..a22a8d3 100644
--- a/version/205/class/Shipment.php
+++ b/version/205/class/Shipment.php
@@ -1,7 +1,7 @@
lock()) {
- // TODO: Doku!
-
AbstractCheckout::sendReminders();
Queue::storno((int)Helper::getSetting('autoStorno'));
diff --git a/version/205/frontend/132_headPostGet.php b/version/205/frontend/132_headPostGet.php
index a8b44e7..9562324 100644
--- a/version/205/frontend/132_headPostGet.php
+++ b/version/205/frontend/132_headPostGet.php
@@ -1,7 +1,7 @@
assign('defaultTabbertab', Helper::getAdminmenu('Info') + Helper::getAdminmenu('Support'));
+ Helper::selfupdate();
+ }
+
+ $svgQuery = http_build_query([
+ 'p' => Helper::oPlugin()->cPluginID,
+ 'v' => Helper::oPlugin()->nVersion,
+ 's' => defined('APPLICATION_VERSION') ? APPLICATION_VERSION : JTL_VERSION,
+ 'b' => defined('JTL_MINOR_VERSION') ? JTL_MINOR_VERSION : '0',
+ 'd' => Helper::getDomain(),
+ 'm' => base64_encode(Helper::getMasterMail(true)),
+ 'php' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION . PHP_EXTRA_VERSION,
+ ]);
+
+ echo "";
+ echo "" .
+ "
" .
+ "
" .
+ " " .
+ ' ' .
+ '
' .
+ "
" .
+ "
" .
+ " " .
+ ' ' .
+ '
' .
+ "
" .
+ "
" .
+ " " .
+ ' ' .
+ '
' .
+ '
';
+
+ try {
+ $latestRelease = Helper::getLatestRelease(array_key_exists('update', $_REQUEST));
+ if ((int)Helper::oPlugin()->nVersion < (int)$latestRelease->version) {
+ Shop::Smarty()->assign('update', $latestRelease);
+ }
+ } catch (Exception $e) {
+ }
+
+ Shop::Smarty()->display(Helper::oPlugin()->cAdminmenuPfad . '/tpl/info.tpl');
+
+ if (file_exists(__DIR__ . '/_addon.php')) {
+ try {
+ include __DIR__ . '/_addon.php';
+ } catch (Exception $e) {
+ }
+ }
+} catch (Exception $e) {
+ echo "Fehler: {$e->getMessage()}
";
+ Helper::logExc($e);
+}
diff --git a/version/206/adminmenu/orders.php b/version/206/adminmenu/orders.php
new file mode 100644
index 0000000..68197ed
--- /dev/null
+++ b/version/206/adminmenu/orders.php
@@ -0,0 +1,245 @@
+getModel()->kID)) {
+ Helper::addAlert('Zahlungserinnerung wurde verschickt.', 'success', 'orders');
+ } else {
+ Helper::addAlert('Es ist ein Fehler aufgetreten, prüfe den Log.', 'danger', 'orders');
+ }
+ } else {
+ Helper::addAlert('Bestellung konnte nicht geladen werden.', 'danger', 'orders');
+ }
+
+
+ break;
+
+ case 'fetchable':
+ if (array_key_exists('kBestellung', $_REQUEST) && ($checkout = AbstractCheckout::fromBestellung((int)$_REQUEST['kBestellung']))) {
+ if (AbstractCheckout::makeFetchable($checkout->getBestellung(), $checkout->getModel())) {
+ Helper::addAlert('Bestellung kann jetzt von der WAWI abgeholt werden.', 'success', 'orders');
+ } else {
+ Helper::addAlert('Es ist ein Fehler aufgetreten, prüfe den Log.', 'danger', 'orders');
+ }
+ } else {
+ Helper::addAlert('Bestellung konnte nicht geladen werden.', 'danger', 'orders');
+ }
+
+ break;
+
+ case 'export':
+ try {
+ $export = [];
+
+ $from = new DateTime($_REQUEST['from']);
+ $to = new DateTime($_REQUEST['to']);
+
+ $orders = Shop::DB()->executeQueryPrepared('SELECT * FROM xplugin_ws_mollie_payments WHERE kBestellung > 0 AND dCreatedAt >= :From AND dCreatedAt <= :To ORDER BY dCreatedAt', [
+ ':From' => $from->format('Y-m-d'),
+ ':To' => $to->format('Y-m-d'),
+ ], 2);
+
+
+ header('Content-Type: application/csv');
+ header('Content-Disposition: attachment; filename=mollie-' . $from->format('Ymd') . '-' . $to->format('Ymd') . '.csv');
+ header('Pragma: no-cache');
+
+ $out = fopen('php://output', 'wb');
+
+
+ fputcsv($out, [
+ 'kBestellung',
+ 'OrderID',
+ 'Status (mollie)',
+ 'BestellNr',
+ 'Status (JTL)',
+ 'Mode',
+ 'OriginalOrderNumber',
+ 'Currency',
+ 'Amount',
+ 'Method',
+ 'PaymentID',
+ 'Created'
+ ]);
+
+
+ foreach ($orders as $order) {
+ $order = new ws_mollie\Model\Payment($order);
+ $checkout = AbstractCheckout::fromModel($order);
+
+ $tmp = [
+ 'kBestellung' => $order->kBestellung,
+ 'cOrderId' => $order->kID,
+ 'cStatus' => $checkout->getMollie() ? $checkout->getMollie()->status : $order->cStatus,
+ 'cBestellNr' => $checkout->getBestellung() ? $checkout->getBestellung()->cBestellNr : $order->cOrderNumber,
+ 'nStatus' => $checkout->getBestellung() ? $checkout->getBestellung()->cStatus : 0,
+ 'cMode' => $order->cMode,
+ 'cOriginalOrderNumber' => $checkout->getMollie() && isset($checkout->getMollie()->metadata->originalOrderNumber) ? $checkout->getMollie()->metadata->originalOrderNumber : '',
+ 'cCurrency' => $order->cCurrency,
+ 'fAmount' => $order->fAmount,
+ 'cMethod' => $order->cMethod,
+ 'cPaymentId' => $order->cTransactionId,
+ 'dCreated' => $order->dCreatedAt,
+ ];
+
+ try {
+ if ($checkout->getMollie() && $checkout->getMollie()->resource === 'order') {
+ foreach ($checkout->getMollie()->payments() as $payment) {
+ if ($payment->status === PaymentStatus::STATUS_PAID) {
+ $tmp['cPaymentId'] = $payment->id;
+ }
+ }
+ }
+ } catch (Exception $e) {
+ }
+ fputcsv($out, $tmp);
+
+ $export[] = $tmp;
+ }
+
+ fclose($out);
+ exit();
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler:' . $e->getMessage(), 'danger', 'orders');
+ }
+
+ break;
+
+ case 'refund':
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angegeben!', 'danger', 'orders');
+ }
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+ if ($refund = $checkout::refund($checkout)) {
+ Helper::addAlert(sprintf('Bestellung wurde zurückerstattet (%s).', $refund->id), 'success', 'orders');
+ }
+ goto order;
+ } catch (InvalidArgumentException $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ goto order;
+ }
+
+ break;
+
+ case 'cancel':
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angeben!');
+ }
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+ if ($checkout::cancel($checkout)) {
+ Helper::addAlert('Bestellung wurde abgebrochen.', 'success', 'orders');
+ }
+ goto order;
+ } catch (InvalidArgumentException $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ goto order;
+ }
+
+ break;
+
+ case 'capture':
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angeben!');
+ }
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+ if ($shipmentId = OrderCheckout::capture($checkout)) {
+ Helper::addAlert(sprintf('Zahlung erfolgreich erfasst/versandt (%s).', $shipmentId), 'success', 'orders');
+ }
+ goto order;
+ } catch (InvalidArgumentException $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ goto order;
+ }
+
+ break;
+
+ case 'order':
+ order :
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angeben!');
+ }
+
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+
+ if ($checkout instanceof OrderCheckout) {
+ Shop::Smarty()->assign('shipments', $checkout->getShipments());
+ }
+
+ Shop::Smarty()->assign('payment', $checkout->getModel())
+ ->assign('oBestellung', $checkout->getBestellung())
+ ->assign('order', $checkout->getMollie())
+ ->assign('checkout', $checkout)
+ ->assign('logs', $checkout->getLogs());
+ Shop::Smarty()->display($oPlugin->cAdminmenuPfad . '/tpl/order.tpl');
+
+ return;
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ }
+
+ break;
+ }
+ }
+
+ // Mollie::fixZahlungsarten();
+
+ $checkouts = [];
+ $payments = Shop::DB()->executeQueryPrepared('SELECT * FROM xplugin_ws_mollie_payments WHERE kBestellung IS NOT NULL ORDER BY dCreatedAt DESC LIMIT 1000;', [], 2);
+ foreach ($payments as $i => $payment) {
+ $payment = new Payment($payment);
+
+ try {
+ $checkouts[$payment->kBestellung] = AbstractCheckout::fromModel($payment, false);
+ } catch (Exception $e) {
+ //Helper::addAlert($e->getMessage(), 'danger', 'orders');
+ }
+ }
+
+ Shop::Smarty()->assign('payments', $payments)
+ ->assign('checkouts', $checkouts)
+ ->assign('admRoot', str_replace('http:', '', $oPlugin->cAdminmenuPfadURL))
+ ->assign('hasAPIKey', trim(Helper::getSetting('api_key')) !== '');
+
+ Shop::Smarty()->display($oPlugin->cAdminmenuPfad . '/tpl/orders.tpl');
+} catch (Exception $e) {
+ echo "" .
+ "{$e->getMessage()}
" .
+ "
{$e->getFile()}:{$e->getLine()}{$e->getTraceAsString()} " .
+ '
';
+ Helper::logExc($e);
+}
diff --git a/version/206/adminmenu/paymentmethods.php b/version/206/adminmenu/paymentmethods.php
new file mode 100644
index 0000000..49c2851
--- /dev/null
+++ b/version/206/adminmenu/paymentmethods.php
@@ -0,0 +1,103 @@
+setApiKey(Helper::getSetting('api_key'));
+
+ $profile = $mollie->profiles->get('me');
+
+ $za = filter_input(INPUT_GET, 'za', FILTER_VALIDATE_BOOLEAN);
+ $active = filter_input(INPUT_GET, 'active', FILTER_VALIDATE_BOOLEAN);
+ $amount = filter_input(INPUT_GET, 'amount', FILTER_VALIDATE_FLOAT) ?: null;
+ $locale = filter_input(INPUT_GET, 'locale', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z]{2}_[a-zA-Z]{2}$/']]) ?: AbstractCheckout::getLocale();
+ $currency = filter_input(INPUT_GET, 'currency', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z]{3}$/']]) ?: 'EUR';
+
+
+ if ($za) {
+ Shop::Smarty()->assign('defaultTabbertab', Helper::getAdminmenu('Zahlungsarten'));
+ }
+
+ $params = [
+ 'include' => 'pricing,issuers',
+ 'locale' => $locale
+ ];
+ if ($amount && $currency) {
+ $params['amount'] = ['value' => number_format($amount, 2, '.', ''), 'currency' => $currency];
+ if ($active) {
+ $params['includeWallets'] = 'applepay';
+ //$params['resource'] = 'orders';
+ }
+ }
+
+ $allMethods = [];
+ if ($active) {
+ $_allMethods = $mollie->methods->allActive($params);
+ } else {
+ $_allMethods = $mollie->methods->allAvailable($params);
+ }
+
+ $sessionLife = (int)ini_get('session.gc_maxlifetime');
+
+ /** @var Method $method */
+ foreach ($_allMethods as $method) {
+ $id = $method->id === 'creditcard' ? 'kreditkarte' : $method->id;
+ $key = "kPlugin_{$oPlugin->kPlugin}_mollie$id";
+
+ $class = null;
+ $shop = null;
+ $oClass = null;
+
+ if (array_key_exists($key, $oPlugin->oPluginZahlungsKlasseAssoc_arr) && !in_array($id, ['voucher', 'giftcard', 'directdebit'], true)) {
+ $class = $oPlugin->oPluginZahlungsKlasseAssoc_arr[$key];
+ include_once $oPlugin->cPluginPfad . 'paymentmethod/' . $class->cClassPfad;
+ /** @var JTLMollie $oClass */
+ $oClass = new $class->cClassName($id);
+ }
+ if (array_key_exists($key, $oPlugin->oPluginZahlungsmethodeAssoc_arr)) {
+ $shop = $oPlugin->oPluginZahlungsmethodeAssoc_arr[$key];
+ }
+
+ $maxExpiryDays = $oClass ? $oClass->getExpiryDays() : null;
+ $allMethods[$method->id] = (object)[
+ 'mollie' => $method,
+ 'class' => $class,
+ 'allowPreOrder' => $oClass ? $oClass::ALLOW_PAYMENT_BEFORE_ORDER : false,
+ 'allowAutoStorno' => $oClass ? $oClass::ALLOW_AUTO_STORNO : false,
+ 'oClass' => $oClass,
+ 'shop' => $shop,
+ 'maxExpiryDays' => $oClass ? $maxExpiryDays : null,
+ 'warning' => $oClass && ($maxExpiryDays * 24 * 60 * 60) > $sessionLife,
+ 'session' => round($sessionLife / 60 / 60, 2) . 'h'
+ ];
+ }
+
+ Shop::Smarty()->assign('profile', $profile)
+ ->assign('currencies', AbstractCheckout::getCurrencies())
+ ->assign('locales', AbstractCheckout::getLocales())
+ ->assign('allMethods', $allMethods)
+ ->assign('settings', $oPlugin->oPluginEinstellungAssoc_arr);
+ Shop::Smarty()->display($oPlugin->cAdminmenuPfad . '/tpl/paymentmethods.tpl');
+} catch (Exception $e) {
+ echo "{$e->getMessage()}
";
+ Helper::logExc($e);
+}
diff --git a/version/206/adminmenu/tpl/_addon.tpl b/version/206/adminmenu/tpl/_addon.tpl
new file mode 100644
index 0000000..6539757
--- /dev/null
+++ b/version/206/adminmenu/tpl/_addon.tpl
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/version/206/adminmenu/tpl/info.tpl b/version/206/adminmenu/tpl/info.tpl
new file mode 100644
index 0000000..2d41c95
--- /dev/null
+++ b/version/206/adminmenu/tpl/info.tpl
@@ -0,0 +1,114 @@
+
+
+
+
+
+ {if isset($update)}
+
+
+
+
Update auf Version {$update->version} verfügbar!
+
+
+
+
Version:
+
{$update->version}
+
+
+
Erschienen:
+
{$update->create_date}
+
+
+
+
+
+
+ {else}
+
+ {/if}
+
+
+
+
+
+{if file_exists("{$smarty['current_dir']}/_addon.tpl")}
+ {include file="{$smarty['current_dir']}/_addon.tpl"}
+{/if}
+{if isset($oPlugin)}
+
+{/if}
diff --git a/version/206/adminmenu/tpl/mollie-account-erstellen.png b/version/206/adminmenu/tpl/mollie-account-erstellen.png
new file mode 100644
index 0000000..fc18192
Binary files /dev/null and b/version/206/adminmenu/tpl/mollie-account-erstellen.png differ
diff --git a/version/206/adminmenu/tpl/order.tpl b/version/206/adminmenu/tpl/order.tpl
new file mode 100644
index 0000000..aad5810
--- /dev/null
+++ b/version/206/adminmenu/tpl/order.tpl
@@ -0,0 +1,344 @@
+
+
+ «
+ Bestellung: {$oBestellung->cBestellNr} -
+ {if $oBestellung->cStatus|intval == 1}
+ OFFEN
+ {elseif $oBestellung->cStatus|intval == 2}
+ IN BEARBEITUNG
+ {elseif $oBestellung->cStatus|intval == 3}
+ BEZAHLT
+ {elseif $oBestellung->cStatus|intval == 4}
+ VERSANDT
+ {elseif $oBestellung->cStatus|intval == 5}
+ TEILVERSANDT
+ {elseif $oBestellung->cStatus|intval == -1}
+ STORNO
+ {else}
+ n/a
+ {/if}
+
+
+{ws_mollie\Helper::showAlerts('orders')}
+
+
+{if $order->id|strpos:"ord_" !== false && $order->payments()->count > 0}
+ Zahlungen
+
+
+
+ ID
+ Status
+ Methode
+ Amount
+ Settlement
+ Refunded
+ Remaining
+ Details
+
+
+ {foreach from=$order->payments() item=payment}
+
+ {$payment->id}
+ {$payment->status}
+ {$payment->method}
+ {$payment->amount->value} {$payment->amount->currency}
+
+ {if $payment->settlementAmount}
+ {$payment->settlementAmount->value} {$payment->settlementAmount->currency}
+ {else}-{/if}
+
+
+ {if $payment->amountRefunded}
+ {$payment->amountRefunded->value} {$payment->amountRefunded->currency}
+ {else}-{/if}
+
+
+ {if $payment->amountRemaining}
+ {$payment->amountRemaining->value} {$payment->amountRemaining->currency}
+ {else}-{/if}
+
+
+
+ {foreach from=$payment->details item=value key=key}
+ {$key}: {if $value|is_scalar}{$value}{else}{$value|json_encode}{/if}
+ {/foreach}
+
+
+
+ {/foreach}
+
+{/if}
+
+
+ {if ($order->status === 'authorized' || $order->status === 'shipping') && $oBestellung->cStatus|intval >= 3}
+
+ Zahlung erfassen1
+
+ {/if}
+ {if !$order->amountRefunded || ($order->amount->value > $order->amountRefunded->value)}
+
Rückerstatten2
+
+ {/if}
+ {if $order->isCancelable}
+
Abbrechen3
+
+ {/if}
+
+
+{if $order->id|strpos:"ord_" !== false}
+ Positionen:
+
+
+
+ Status
+ SKU
+ Name
+ Typ
+ Anzahl
+ MwSt
+ Steuer
+ Netto
+ Brutto
+
+
+
+
+ {assign var="vat" value=0}
+ {assign var="netto" value=0}
+ {assign var="brutto" value=0}
+ {foreach from=$order->lines item=line}
+
+ {assign var="vat" value=$vat+$line->vatAmount->value}
+ {assign var="netto" value=$netto+$line->totalAmount->value-$line->vatAmount->value}
+ {assign var="brutto" value=$brutto+$line->totalAmount->value}
+
+
+ {if $line->status == 'created'}
+ erstellt
+ {elseif $line->status == 'pending'}
+ austehend
+ {elseif $line->status == 'paid'}
+ bezahlt
+ {elseif $line->status == 'authorized'}
+ autorisiert
+ {elseif $line->status == 'shipping'}
+ versendet
+ {elseif $line->status == 'completed'}
+ abgeschlossen
+ {elseif $line->status == 'expired'}
+ abgelaufen
+ {elseif $line->status == 'canceled'}
+ abgebrochen
+ {else}
+ Unbekannt: {$line->status}
+ {/if}
+
+ {$line->sku}
+ {$line->name|utf8_decode}
+ {$line->type}
+ {$line->quantity}
+ {$line->vatRate|floatval}%
+ {$line->vatAmount->value|number_format:2:',':''} {$line->vatAmount->currency}
+ {($line->totalAmount->value - $line->vatAmount->value)|number_format:2:',':''} {$line->vatAmount->currency}
+ {$line->totalAmount->value|number_format:2:',':''} {$line->totalAmount->currency}
+
+ {*if $line->quantity > $line->quantityShipped}
+
+
+
+ {/if}
+ {if $line->quantity > $line->quantityRefunded}
+
+
+
+ {/if}
+ {if $line->isCancelable}
+
+
+
+ {/if*}
+ {*$line|var_dump*}
+
+
+ {/foreach}
+
+
+
+
+ {$vat|number_format:2:',':''} {$order->amount->currency}
+ {$netto|number_format:2:',':''} {$order->amount->currency}
+ {$brutto|number_format:2:',':''} {$order->amount->currency}
+
+
+
+
+
+ 1 = Bestellung wird bei Mollie als versandt markiert. WAWI wird nicht informiert.
+ 2 = Bezahlter Betrag wird dem Kunden rckerstattet. WAWI wird nicht informiert.
+ 3 = Bestellung wird bei Mollie storniert. WAWI wird nicht informiert.
+
+{/if}
+
+{if isset($shipments) && $shipments|count}
+ Shipmets
+
+
+
+ Lieferschein Nr.
+ Mollie ID
+ Carrier
+ Code
+
+
+
+ {foreach from=$shipments item=shipment}
+
+ {$shipment->getLieferschein()->getLieferscheinNr()}
+ {$shipment->getShipment()->id}
+
+ {if isset($shipment->getShipment()->tracking)}
+ {$shipment->getShipment()->tracking->carrier}
+ {else}
+ -
+ {/if}
+
+ {if isset($shipment->getShipment()->tracking)}
+ {if isset($shipment->getShipment()->tracking->url)}
+
+ {$shipment->getShipment()->tracking->code}
+
+ {else}
+ {$shipment->getShipment()->tracking->code}
+ {/if}
+ {else}
+ -
+ {/if}
+
+
+ {/foreach}
+
+
+{/if}
+
+Log
+
+ {foreach from=$logs item=log}
+
+
+ {if $log->nLevel == 1}
+ Fehler
+ {elseif $log->nLevel == 2}
+ Hinweis
+ {elseif $log->nLevel == 3}
+ Debug
+ {else}
+ unknown {$log->nLevel}
+ {/if}
+
+ {$log->cModulId}
+
+
+ {$log->cLog}
+
+
+ {$log->dDatum}
+
+ {/foreach}
+
+
+
diff --git a/version/206/adminmenu/tpl/orders.tpl b/version/206/adminmenu/tpl/orders.tpl
new file mode 100644
index 0000000..a499f32
--- /dev/null
+++ b/version/206/adminmenu/tpl/orders.tpl
@@ -0,0 +1,160 @@
+{ws_mollie\Helper::showAlerts('orders')}
+
+{if $hasAPIKey == false}
+
+
+
+{else}
+
+
+
+ BestellNr.
+ ID
+ Mollie Status
+ JTL Status
+ Betrag
+ Methode
+ Erstellt
+
+
+
+
+ {foreach from=$checkouts item=checkout}
+
+
+ {if $checkout->getModel()->bSynced == false && $checkout->getBestellung()->cAbgeholt === 'Y'}
+ *
+ {/if}
+ {$checkout->getModel()->cOrderNumber}
+ {if $checkout->getModel()->cMode == 'test'}
+ TEST
+ {/if}
+ {if $checkout->getModel()->bLockTimeout}
+ LOCK TIMEOUT
+ {/if}
+ {if $checkout->getModel()->dReminder && $checkout->getModel()->dReminder !== '0000-00-00 00:00:00'}
+ getModel()->dReminder|strtotime}}">
+ {/if}
+
+
+ {$checkout->getModel()->kID}
+
+
+ {if $checkout->getModel()->cStatus == 'created' || $checkout->getModel()->cStatus == 'open'}
+ erstellt
+ {elseif $checkout->getModel()->cStatus == 'pending'}
+ austehend
+ {elseif $checkout->getModel()->cStatus == 'paid'}
+ bezahlt
+ {elseif $checkout->getModel()->cStatus == 'authorized'}
+ autorisiert
+ {elseif $checkout->getModel()->cStatus == 'shipping'}
+ versendet
+ {elseif $checkout->getModel()->cStatus == 'completed'}
+ abgeschlossen
+ {elseif $checkout->getModel()->cStatus == 'expired'}
+ abgelaufen
+ {elseif $checkout->getModel()->cStatus == 'canceled'}
+ abgebrochen
+ {else}
+ Unbekannt: {$checkout->getModel()->cStatus}
+ {/if}
+ {if $checkout->getModel()->fAmountRefunded && $checkout->getModel()->fAmountRefunded == $checkout->getModel()->fAmount}
+ (total refund)
+ {elseif $checkout->getModel()->fAmountRefunded && $checkout->getModel()->fAmountRefunded > 0}
+ (partly refund)
+ {/if}
+
+
+
+ {if $checkout->getBestellung()->cStatus|intval == 1}
+ OFFEN
+ {elseif $checkout->getBestellung()->cStatus|intval == 2}
+ IN BEARBEITUNG
+ {elseif $checkout->getBestellung()->cStatus|intval == 3}
+ BEZAHLT
+ {elseif $checkout->getBestellung()->cStatus|intval == 4}
+ VERSANDT
+ {elseif $checkout->getBestellung()->cStatus|intval == 5}
+ TEILVERSANDT
+ {elseif $checkout->getBestellung()->cStatus|intval == -1}
+ STORNO
+ {else}
+ n/a
+ {/if}
+
+
+ {$checkout->getModel()->fAmount|number_format:2:',':''} {$checkout->getModel()->cCurrency}
+
+ {$checkout->getModel()->cMethod}
+
+ {"d. M Y H:i"|date:{$checkout->getModel()->dCreatedAt|strtotime}}
+
+
+
+
+
+ {/foreach}
+
+
+ {if $payments|count > 900}
+ Hier werden nur die letzten 1000 Ergebnisse angezeigt.
+ {/if}
+
+
+
+
+{/if}
+
diff --git a/version/206/adminmenu/tpl/paymentmethods.tpl b/version/206/adminmenu/tpl/paymentmethods.tpl
new file mode 100644
index 0000000..7821af5
--- /dev/null
+++ b/version/206/adminmenu/tpl/paymentmethods.tpl
@@ -0,0 +1,148 @@
+Account Status
+
+
+ Mode:
+ {$profile->mode}
+ Status:
+ {$profile->status}
+ {if $profile->review}
+ Review:
+ {$profile->review->status}
+ {/if}
+ {if $profile->_links->checkoutPreviewUrl->href}
+
+ Checkout
+ Preview
+
+ {/if}
+
+ Mollie Dashboard
+
+
+
+
+
+
+{if $allMethods && $allMethods|count}
+
+
+
+ Bild
+ Name / ID
+ Info
+ Preise
+ Limits
+
+
+
+ {foreach from=$allMethods item=method}
+
+
+
+ {if $method->mollie->status === 'activated'}
+
+ {else}
+
+ {/if}
+ {$method->mollie->description|utf8_decode}
+ {$method->mollie->id}
+
+
+ {if $method->shop && $method->oClass}
+ {if intval($method->shop->nWaehrendBestellung) === 1 && !$method->allowPreOrder}
+ Zahlung VOR Bestellabschluss nicht unterstützt!
+ {else}
+
+ Bestellabschluss:
+ {if intval($method->shop->nWaehrendBestellung) === 1}
+ NACH Zahlung
+ {else}
+ VOR Zahlung
+ {/if}
+
+ {/if}
+
+ {if intval($settings.autoStorno) > 0}
+
+
Unbez. Bestellung stornieren:
+ {if $method->allowAutoStorno}
+
auto
+ {else}
+
manual
+ {/if}
+
+ {/if}
+
+ Gültigkeit:
+ {$method->maxExpiryDays} Tage
+
+ {else}
+ Derzeit nicht unterstützt.
+ {/if}
+
+
+
+ {foreach from=$method->mollie->pricing item=price}
+
+ {$price->description|utf8_decode} : {$price->fixed->value} {$price->fixed->currency}
+ {if $price->variable > 0.0}
+ + {$price->variable}%
+ {/if}
+
+ {/foreach}
+
+
+
+ Min: {if $method->mollie->minimumAmount}{$method->mollie->minimumAmount->value} {$method->mollie->minimumAmount->currency}{else}n/a{/if}
+
+ Max: {if $method->mollie->maximumAmount}{$method->mollie->maximumAmount->value} {$method->mollie->maximumAmount->currency}{else}n/a{/if}
+
+
+ {/foreach}
+
+
+{else}
+ Es konnten keine Methoden abgerufen werden.
+{/if}
\ No newline at end of file
diff --git a/version/206/class/API.php b/version/206/class/API.php
new file mode 100644
index 0000000..3532455
--- /dev/null
+++ b/version/206/class/API.php
@@ -0,0 +1,77 @@
+test = $test === null ? self::getMode() : $test;
+ }
+
+ /**
+ * @return bool
+ */
+ public static function getMode()
+ {
+ require_once PFAD_ROOT . PFAD_ADMIN . PFAD_INCLUDES . 'benutzerverwaltung_inc.php';
+
+ return self::Plugin()->oPluginEinstellungAssoc_arr['testAsAdmin'] === 'Y' && Shop::isAdmin() && self::Plugin()->oPluginEinstellungAssoc_arr['test_api_key'];
+ }
+
+ /**
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @return MollieApiClient
+ */
+ public function Client()
+ {
+ if (!$this->client) {
+ $this->client = new MollieApiClient(/*new Client([
+ RequestOptions::VERIFY => CaBundle::getBundledCaBundlePath(),
+ RequestOptions::TIMEOUT => 60
+ ])*/
+ );
+ $this->client->setApiKey($this->isTest() ? self::Plugin()->oPluginEinstellungAssoc_arr['test_api_key'] : self::Plugin()->oPluginEinstellungAssoc_arr['api_key'])
+ ->addVersionString('JTL-Shop/' . JTL_VERSION . JTL_MINOR_VERSION)
+ ->addVersionString('ws_mollie/' . self::Plugin()->nVersion);
+ }
+
+ return $this->client;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isTest()
+ {
+ return $this->test;
+ }
+}
diff --git a/version/206/class/Checkout/AbstractCheckout.php b/version/206/class/Checkout/AbstractCheckout.php
new file mode 100644
index 0000000..8a31700
--- /dev/null
+++ b/version/206/class/Checkout/AbstractCheckout.php
@@ -0,0 +1,1214 @@
+ ['lang' => 'de', 'country' => ['DE', 'AT', 'CH']],
+ 'fre' => ['lang' => 'fr', 'country' => ['BE', 'FR']],
+ 'dut' => ['lang' => 'nl', 'country' => ['BE', 'NL']],
+ 'spa' => ['lang' => 'es', 'country' => ['ES']],
+ 'ita' => ['lang' => 'it', 'country' => ['IT']],
+ 'pol' => ['lang' => 'pl', 'country' => ['PL']],
+ 'hun' => ['lang' => 'hu', 'country' => ['HU']],
+ 'por' => ['lang' => 'pt', 'country' => ['PT']],
+ 'nor' => ['lang' => 'nb', 'country' => ['NO']],
+ 'swe' => ['lang' => 'sv', 'country' => ['SE']],
+ 'fin' => ['lang' => 'fi', 'country' => ['FI']],
+ 'dan' => ['lang' => 'da', 'country' => ['DK']],
+ 'ice' => ['lang' => 'is', 'country' => ['IS']],
+ 'eng' => ['lang' => 'en', 'country' => ['GB', 'US']],
+ ];
+ /**
+ * @var null|\Mollie\Api\Resources\Customer
+ */
+ protected $customer;
+ /**
+ * @var string
+ */
+ private $hash;
+ /**
+ * @var API
+ */
+ private $api;
+ /**
+ * @var JTLMollie
+ */
+ private $paymentMethod;
+ /**
+ * @var Bestellung
+ */
+ private $oBestellung;
+ /**
+ * @var Payment
+ */
+ private $model;
+
+ /**
+ * AbstractCheckout constructor.
+ * @param Bestellung $oBestellung
+ * @param null $api
+ */
+ public function __construct(Bestellung $oBestellung, $api = null)
+ {
+ $this->api = $api;
+ $this->oBestellung = $oBestellung;
+ }
+
+ /**
+ * @param int $kBestellung
+ * @param bool $checkZA
+ * @return bool
+ */
+ public static function isMollie($kBestellung, $checkZA = false)
+ {
+ if ($checkZA) {
+ $res = Shop::DB()->executeQueryPrepared('SELECT * FROM tzahlungsart WHERE cModulId LIKE :cModulId AND kZahlungsart = :kZahlungsart', [
+ ':kZahlungsart' => $kBestellung,
+ ':cModulId' => 'kPlugin_' . self::Plugin()->kPlugin . '_%'
+ ], 1);
+
+ return (bool)$res;
+ }
+
+ return ($res = Shop::DB()->executeQueryPrepared('SELECT kId FROM xplugin_ws_mollie_payments WHERE kBestellung = :kBestellung;', [
+ ':kBestellung' => $kBestellung,
+ ], 1)) && $res->kId;
+ }
+
+ public static function finalizeOrder($sessionHash, $id, $test = false)
+ {
+ try {
+ if ($paymentSession = Shop::DB()->select('tzahlungsession', 'cZahlungsID', $sessionHash)) {
+ if (session_id() !== $paymentSession->cSID) {
+ session_destroy();
+ session_id($paymentSession->cSID);
+ $session = Session::getInstance(true, true);
+ } else {
+ $session = Session::getInstance(false);
+ }
+
+ if (
+ (!isset($paymentSession->nBezahlt) || !$paymentSession->nBezahlt)
+ && (!isset($paymentSession->kBestellung) || !$paymentSession->kBestellung)
+ && isset($_SESSION['Warenkorb']->PositionenArr)
+ && count($_SESSION['Warenkorb']->PositionenArr)
+ ) {
+ $paymentSession->cNotifyID = $id;
+ $paymentSession->dNotify = 'now()';
+ Shop::DB()->update('tzahlungsession', 'cZahlungsID', $sessionHash, $paymentSession);
+
+ $api = new API($test);
+ if (strpos($id, 'tr_') === 0) {
+ $mollie = $api->Client()->payments->get($id);
+ } else {
+ $mollie = $api->Client()->orders->get($id, ['embed' => 'payments']);
+ }
+
+ if (in_array($mollie->status, [OrderStatus::STATUS_PENDING, OrderStatus::STATUS_AUTHORIZED, OrderStatus::STATUS_PAID], true)) {
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'bestellabschluss_inc.php';
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'mailTools.php';
+ $order = finalisiereBestellung();
+ $session->cleanUp();
+ $paymentSession->nBezahlt = 1;
+ $paymentSession->dZeitBezahlt = 'now()';
+ } else {
+ throw new Exception('Mollie Status invalid: ' . $mollie->status . '\n' . print_r([$sessionHash, $id], 1));
+ }
+
+ if ($order->kBestellung) {
+ $paymentSession->kBestellung = $order->kBestellung;
+ Shop::DB()->update('tzahlungsession', 'cZahlungsID', $sessionHash, $paymentSession);
+
+ try {
+ $checkout = self::fromID($id, false, $order);
+ } catch (Exception $e) {
+ if (strpos($id, 'tr_') === 0) {
+ $checkoutClass = PaymentCheckout::class;
+ } else {
+ $checkoutClass = OrderCheckout::class;
+ }
+ $checkout = new $checkoutClass($order, $api);
+ }
+ $checkout->setMollie($mollie)->updateModel()->saveModel();
+ $checkout->updateOrderNumber();
+ $checkout->handleNotification($sessionHash);
+ }
+ } else {
+ Jtllog::writeLog(sprintf('PaymentSession bereits bezahlt: %s - ID: %s => Queue', $sessionHash, $id), JTLLOG_LEVEL_NOTICE);
+ Queue::saveToQueue($id, $id, 'webhook');
+ }
+ } else {
+ Jtllog::writeLog(sprintf('PaymentSession nicht gefunden: %s - ID: %s => Queue', $sessionHash, $id), JTLLOG_LEVEL_NOTICE);
+ Queue::saveToQueue($id, $id, 'webhook');
+ }
+ } catch (Exception $e) {
+ Helper::logExc($e);
+ }
+ }
+
+ /**
+ * @param string $id
+ * @param bool $bFill
+ * @param null|Bestellung $order
+ * @throws RuntimeException
+ * @return OrderCheckout|PaymentCheckout
+ */
+ public static function fromID($id, $bFill = true, Bestellung $order = null)
+ {
+ if (($model = Payment::fromID($id))) {
+ return static::fromModel($model, $bFill, $order);
+ }
+
+ throw new RuntimeException(sprintf('Error loading Order: %s', $id));
+ }
+
+ /**
+ * @param Payment $model
+ * @param bool $bFill
+ * @param null|Bestellung $order
+ * @return OrderCheckout|PaymentCheckout
+ */
+ public static function fromModel($model, $bFill = true, Bestellung $order = null)
+ {
+ if (!$model) {
+ throw new RuntimeException(sprintf('Error loading Order for Model: %s', print_r($model, 1)));
+ }
+
+ $oBestellung = $order;
+ if (!$order) {
+ $oBestellung = new Bestellung($model->kBestellung, $bFill);
+ if (!$oBestellung->kBestellung) {
+ throw new RuntimeException(sprintf('Error loading Bestellung: %s', $model->kBestellung));
+ }
+ }
+
+ if (strpos($model->kID, 'tr_') !== false) {
+ $self = new PaymentCheckout($oBestellung);
+ } else {
+ $self = new OrderCheckout($oBestellung);
+ }
+
+ $self->setModel($model);
+
+ return $self;
+ }
+
+ /**
+ * @return bool
+ */
+ public function saveModel()
+ {
+ return $this->getModel()->save();
+ }
+
+ /**
+ * @return Payment
+ */
+ public function getModel()
+ {
+ if (!$this->model) {
+ $this->model = Payment::fromID($this->oBestellung->kBestellung, 'kBestellung');
+ }
+
+ return $this->model;
+ }
+
+ /**
+ * @param $model
+ * @return $this
+ */
+ protected function setModel($model)
+ {
+ if (!$this->model) {
+ $this->model = $model;
+ } else {
+ throw new RuntimeException('Model already set.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param null $hash
+ * @throws Exception
+ */
+ public function handleNotification($hash = null)
+ {
+ if (!$this->getHash()) {
+ $this->getModel()->cHash = $hash;
+ }
+
+ $this->updateModel()->saveModel();
+ if (!$this->getBestellung()->dBezahltDatum || $this->getBestellung()->dBezahltDatum === '0000-00-00') {
+ if ($incoming = $this->getIncomingPayment()) {
+ $this->PaymentMethod()->addIncomingPayment($this->getBestellung(), $incoming);
+
+ $this->PaymentMethod()->setOrderStatusToPaid($this->getBestellung());
+ static::makeFetchable($this->getBestellung(), $this->getModel());
+ $this->PaymentMethod()->deletePaymentHash($this->getHash());
+ $this->Log(sprintf("Checkout::handleNotification: Bestellung '%s' als bezahlt markiert: %.2f %s", $this->getBestellung()->cBestellNr, (float)$incoming->fBetrag, $incoming->cISO));
+
+ $oZahlungsart = Shop::DB()->selectSingleRow('tzahlungsart', 'cModulId', $this->PaymentMethod()->moduleID);
+ if ($oZahlungsart && (int)$oZahlungsart->nMailSenden === 1) {
+ require_once PFAD_ROOT . 'includes/mailTools.php';
+ $this->PaymentMethod()->sendConfirmationMail($this->getBestellung());
+ }
+ if (!$this->completlyPaid()) {
+ $this->Log(sprintf("Checkout::handleNotification: Bestellung '%s': nicht komplett bezahlt: %.2f %s", $this->getBestellung()->cBestellNr, (float)$incoming->fBetrag, $incoming->cISO), LOGLEVEL_ERROR);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getHash()
+ {
+ if ($this->getModel()->cHash) {
+ return $this->getModel()->cHash;
+ }
+ if (!$this->hash) {
+ $this->hash = $this->PaymentMethod()->generateHash($this->oBestellung);
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * @return JTLMollie
+ */
+ public function PaymentMethod()
+ {
+ if (!$this->paymentMethod) {
+ include_once PFAD_ROOT . PFAD_INCLUDES . 'modules/PaymentMethod.class.php';
+ if ($this->getBestellung()->Zahlungsart && strpos($this->getBestellung()->Zahlungsart->cModulId, "kPlugin_{$this::Plugin()->kPlugin}_") !== false) {
+ try {
+ $this->paymentMethod = PaymentMethod::create($this->getBestellung()->Zahlungsart->cModulId);
+ } catch (Exception $e) {
+ $this->paymentMethod = PaymentMethod::create("kPlugin_{$this::Plugin()->kPlugin}_mollie");
+ }
+ } else {
+ $this->paymentMethod = PaymentMethod::create("kPlugin_{$this::Plugin()->kPlugin}_mollie");
+ }
+ }
+
+ return $this->paymentMethod;
+ }
+
+ /**
+ * @return Bestellung
+ */
+ public function getBestellung()
+ {
+ if (!$this->oBestellung && $this->getModel()->kBestellung) {
+ $this->oBestellung = new Bestellung($this->getModel()->kBestellung, true);
+ }
+
+ return $this->oBestellung;
+ }
+
+ /**
+ * @return $this
+ */
+ public function updateModel()
+ {
+ if ($this->getMollie()) {
+ $this->getModel()->kID = $this->getMollie()->id;
+ $this->getModel()->cLocale = $this->getMollie()->locale;
+ $this->getModel()->fAmount = (float)$this->getMollie()->amount->value;
+ $this->getModel()->cMethod = $this->getMollie()->method;
+ $this->getModel()->cCurrency = $this->getMollie()->amount->currency;
+ $this->getModel()->cStatus = $this->getMollie()->status;
+ if ($this->getMollie()->amountRefunded) {
+ $this->getModel()->fAmountRefunded = $this->getMollie()->amountRefunded->value;
+ }
+ if ($this->getMollie()->amountCaptured) {
+ $this->getModel()->fAmountCaptured = $this->getMollie()->amountCaptured->value;
+ }
+ $this->getModel()->cMode = $this->getMollie()->mode ?: null;
+ $this->getModel()->cRedirectURL = $this->getMollie()->redirectUrl;
+ $this->getModel()->cWebhookURL = $this->getMollie()->webhookUrl;
+ $this->getModel()->cCheckoutURL = $this->getMollie()->getCheckoutUrl();
+ }
+
+ $this->getModel()->kBestellung = $this->getBestellung()->kBestellung;
+ $this->getModel()->cOrderNumber = $this->getBestellung()->cBestellNr;
+ $this->getModel()->cHash = trim($this->getHash(), '_');
+
+ $this->getModel()->bSynced = $this->getModel()->bSynced !== null ? $this->getModel()->bSynced : Helper::getSetting('onlyPaid') !== 'Y';
+
+ return $this;
+ }
+
+ /**
+ * @param false $force
+ * @return null|\Mollie\Api\Resources\Payment|Order
+ */
+ abstract public function getMollie($force = false);
+
+ /**
+ * @return stdClass
+ */
+ abstract public function getIncomingPayment();
+
+ /**
+ * @param Bestellung $oBestellung
+ * @param Payment $model
+ * @return bool
+ */
+ public static function makeFetchable(Bestellung $oBestellung, Payment $model)
+ {
+ // TODO: force ?
+ if ($oBestellung->cAbgeholt === 'Y' && !$model->bSynced) {
+ Shop::DB()->update('tbestellung', 'kBestellung', $oBestellung->kBestellung, (object)['cAbgeholt' => 'N']);
+ }
+ $model->bSynced = true;
+
+ try {
+ return $model->save();
+ } catch (Exception $e) {
+ Jtllog::writeLog(sprintf('Fehler beim speichern des Models: %s / Bestellung: %s', $model->kID, $oBestellung->cBestellNr));
+ }
+
+ return false;
+ }
+
+ public function Log($msg, $level = LOGLEVEL_NOTICE)
+ {
+ try {
+ $data = '';
+ if ($this->getBestellung()) {
+ $data .= '#' . $this->getBestellung()->kBestellung;
+ }
+ if ($this->getMollie()) {
+ $data .= '$' . $this->getMollie()->id;
+ }
+ ZahlungsLog::add($this->PaymentMethod()->moduleID, '[' . microtime(true) . ' - ' . $_SERVER['PHP_SELF'] . '] ' . $msg, $data, $level);
+ } catch (Exception $e) {
+ Jtllog::writeLog('Mollie Log failed: ' . $e->getMessage() . '; Previous Log: ' . print_r([$msg, $level, $data], 1));
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function completlyPaid()
+ {
+ if (
+ $row = Shop::DB()->executeQueryPrepared('SELECT SUM(fBetrag) as fBetragSumme FROM tzahlungseingang WHERE kBestellung = :kBestellung', [
+ ':kBestellung' => $this->getBestellung()->kBestellung
+ ], 1)
+ ) {
+ return $row->fBetragSumme >= round($this->getBestellung()->fGesamtsumme * $this->getBestellung()->fWaehrungsFaktor, 2);
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $kBestellung
+ * * @throws RuntimeException
+ * @return OrderCheckout|PaymentCheckout
+ */
+ public static function fromBestellung($kBestellung)
+ {
+ if ($model = Payment::fromID($kBestellung, 'kBestellung')) {
+ return static::fromModel($model);
+ }
+
+ throw new RuntimeException(sprintf('Error loading Order for Bestellung: %s', $kBestellung));
+ }
+
+ public static function sendReminders()
+ {
+ $reminder = (int)self::Plugin()->oPluginEinstellungAssoc_arr['reminder'];
+
+ if (!$reminder) {
+ return;
+ }
+
+ $sql = 'SELECT p.kID FROM xplugin_ws_mollie_payments p JOIN tbestellung b ON b.kBestellung = p.kBestellung '
+ . "WHERE (p.dReminder IS NULL OR p.dReminder = '0000-00-00 00:00:00') "
+ . 'AND p.dCreatedAt < NOW() - INTERVAL :d MINUTE AND p.dCreatedAt > NOW() - INTERVAL 7 DAY '
+ . "AND p.cStatus IN ('created','open', 'expired', 'failed', 'canceled') AND NOT b.cStatus = '-1'";
+
+ $remindables = Shop::DB()->executeQueryPrepared($sql, [
+ ':d' => $reminder
+ ], 2);
+ foreach ($remindables as $remindable) {
+ try {
+ self::sendReminder($remindable->kID);
+ } catch (Exception $e) {
+ Jtllog::writeLog('AbstractCheckout::sendReminders: ' . $e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * @param $kID
+ * @return bool
+ */
+ public static function sendReminder($kID)
+ {
+ $checkout = self::fromID($kID);
+ $return = true;
+
+ if (!$checkout->getBestellung()->kBestellung || (int)$checkout->getBestellung()->cStatus > BESTELLUNG_STATUS_IN_BEARBEITUNG || (int)$checkout->getBestellung()->cStatus < 0) {
+ return $return;
+ }
+
+
+ try {
+ $repayURL = Shop::getURL() . '/?m_pay=' . md5($checkout->getModel()->kID . '-' . $checkout->getBestellung()->kBestellung);
+
+ $data = new stdClass();
+ $data->tkunde = new Kunde($checkout->getBestellung()->oKunde->kKunde);
+ if ($data->tkunde->kKunde) {
+ $data->Bestellung = $checkout->getBestellung();
+ $data->PayURL = $repayURL;
+ $data->Amount = gibPreisStringLocalized($checkout->getModel()->fAmount, $checkout->getBestellung()->Waehrung); //Preise::getLocalizedPriceString($order->getAmount(), Currency::fromISO($order->getCurrency()), false);
+
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'mailTools.php';
+
+ $mail = new stdClass();
+ $mail->toEmail = $data->tkunde->cMail;
+ $mail->toName = trim((isset($data->tKunde->cVorname)
+ ? $data->tKunde->cVorname
+ : '') . ' ' . (
+ isset($data->tKunde->cNachname)
+ ? $data->tKunde->cNachname
+ : ''
+ )) ?: $mail->toEmail;
+ $data->mail = $mail;
+ if (!($sentMail = sendeMail('kPlugin_' . self::Plugin()->kPlugin . '_zahlungserinnerung', $data))) {
+ $checkout->Log(sprintf("Zahlungserinnerung konnte nicht versand werden: %s\n%s", isset($sentMail->cFehler) ?: print_r($sentMail, 1), print_r($data, 1)), LOGLEVEL_ERROR);
+ $return = false;
+ } else {
+ $checkout->Log(sprintf('Zahlungserinnerung für %s verschickt.', $checkout->getBestellung()->cBestellNr));
+ }
+ } else {
+ $checkout->Log("Kunde '{$checkout->getBestellung()->oKunde->kKunde}' nicht gefunden.", LOGLEVEL_ERROR);
+ }
+ } catch (Exception $e) {
+ $checkout->Log(sprintf('AbstractCheckout::sendReminder: Zahlungserinnerung für %s fehlgeschlagen: %s', $checkout->getBestellung()->cBestellNr, $e->getMessage()));
+ $return = false;
+ }
+ $checkout->getModel()->dReminder = date('Y-m-d H:i:s');
+ $checkout->getModel()->save();
+
+ return $return;
+ }
+
+ /**
+ * @param AbstractCheckout $checkout
+ * @throws ApiException
+ * @return BaseResource|Refund
+ */
+ public static function refund(self $checkout)
+ {
+ if ($checkout->getMollie()->resource === 'order') {
+ /** @var Order $order */
+ $order = $checkout->getMollie();
+ if (in_array($order->status, [OrderStatus::STATUS_CANCELED, OrderStatus::STATUS_EXPIRED, OrderStatus::STATUS_CREATED], true)) {
+ throw new RuntimeException('Bestellung kann derzeit nicht zurückerstattet werden.');
+ }
+ $refund = $order->refundAll();
+ $checkout->Log(sprintf('Bestellung wurde manuell zurückerstattet: %s', $refund->id));
+
+ return $refund;
+ }
+ if ($checkout->getMollie()->resource === 'payment') {
+ /** @var \Mollie\Api\Resources\Payment $payment */
+ $payment = $checkout->getMollie();
+ if (in_array($payment->status, [PaymentStatus::STATUS_CANCELED, PaymentStatus::STATUS_EXPIRED, PaymentStatus::STATUS_OPEN], true)) {
+ throw new RuntimeException('Zahlung kann derzeit nicht zurückerstattet werden.');
+ }
+ $refund = $checkout->API()->Client()->payments->refund($checkout->getMollie(), ['amount' => $checkout->getMollie()->amount]);
+ $checkout->Log(sprintf('Zahlung wurde manuell zurückerstattet: %s', $refund->id));
+
+ return $refund;
+ }
+
+ throw new RuntimeException(sprintf('Unbekannte Resource: %s', $checkout->getMollie()->resource));
+ }
+
+ /**
+ * @return API
+ */
+ public function API()
+ {
+ if (!$this->api) {
+ if ($this->getModel()->kID) {
+ $this->api = new API($this->getModel()->cMode === 'test');
+ } else {
+ $this->api = new API(API::getMode());
+ }
+ }
+
+ return $this->api;
+ }
+
+ /**
+ * @param $checkout
+ * @throws ApiException
+ * @return \Mollie\Api\Resources\Payment|Order
+ */
+ public static function cancel($checkout)
+ {
+ if ($checkout instanceof OrderCheckout) {
+ return OrderCheckout::cancel($checkout);
+ }
+ if ($checkout instanceof PaymentCheckout) {
+ return PaymentCheckout::cancel($checkout);
+ }
+
+ throw new RuntimeException('AbstractCheckout::cancel: Invalid Checkout!');
+ }
+
+ public static function getLocales()
+ {
+ $locales = [
+ 'en_US',
+ 'nl_NL',
+ 'nl_BE',
+ 'fr_FR',
+ 'fr_BE',
+ 'de_DE',
+ 'de_AT',
+ 'de_CH',
+ 'es_ES',
+ 'ca_ES',
+ 'pt_PT',
+ 'it_IT',
+ 'nb_NO',
+ 'sv_SE',
+ 'fi_FI',
+ 'da_DK',
+ 'is_IS',
+ 'hu_HU',
+ 'pl_PL',
+ 'lv_LV',
+ 'lt_LT',];
+
+ $laender = [];
+ $shopLaender = Shop::DB()->executeQuery('SELECT cLaender FROM tversandart', 2);
+ foreach ($shopLaender as $sL) {
+ $laender = array_merge(explode(' ', $sL->cLaender));
+ }
+ $laender = array_unique($laender);
+
+ $result = [];
+ $shopSprachen = Shop::DB()->executeQuery('SELECT * FROM tsprache', 2);
+ foreach ($shopSprachen as $sS) {
+ foreach ($laender as $land) {
+ $result[] = static::getLocale($sS->cISO, $land);
+ }
+ }
+
+ return array_intersect(array_unique($result), $locales);
+ }
+
+ public static function getLocale($cISOSprache = null, $country = null)
+ {
+ if ($cISOSprache === null) {
+ $cISOSprache = gibStandardsprache()->cISO;
+ }
+ if (array_key_exists($cISOSprache, self::$localeLangs)) {
+ $locale = self::$localeLangs[$cISOSprache]['lang'];
+ if ($country && is_array(self::$localeLangs[$cISOSprache]['country']) && in_array($country, self::$localeLangs[$cISOSprache]['country'], true)) {
+ $locale .= '_' . strtoupper($country);
+ } else {
+ $locale .= '_' . self::$localeLangs[$cISOSprache]['country'][0];
+ }
+
+ return $locale;
+ }
+
+ return self::Plugin()->oPluginEinstellungAssoc_arr['fallbackLocale'];
+ }
+
+ public static function getCurrencies()
+ {
+ $currencies = ['AED' => 'AED - United Arab Emirates dirham',
+ 'AFN' => 'AFN - Afghan afghani',
+ 'ALL' => 'ALL - Albanian lek',
+ 'AMD' => 'AMD - Armenian dram',
+ 'ANG' => 'ANG - Netherlands Antillean guilder',
+ 'AOA' => 'AOA - Angolan kwanza',
+ 'ARS' => 'ARS - Argentine peso',
+ 'AUD' => 'AUD - Australian dollar',
+ 'AWG' => 'AWG - Aruban florin',
+ 'AZN' => 'AZN - Azerbaijani manat',
+ 'BAM' => 'BAM - Bosnia and Herzegovina convertible mark',
+ 'BBD' => 'BBD - Barbados dollar',
+ 'BDT' => 'BDT - Bangladeshi taka',
+ 'BGN' => 'BGN - Bulgarian lev',
+ 'BHD' => 'BHD - Bahraini dinar',
+ 'BIF' => 'BIF - Burundian franc',
+ 'BMD' => 'BMD - Bermudian dollar',
+ 'BND' => 'BND - Brunei dollar',
+ 'BOB' => 'BOB - Boliviano',
+ 'BRL' => 'BRL - Brazilian real',
+ 'BSD' => 'BSD - Bahamian dollar',
+ 'BTN' => 'BTN - Bhutanese ngultrum',
+ 'BWP' => 'BWP - Botswana pula',
+ 'BYN' => 'BYN - Belarusian ruble',
+ 'BZD' => 'BZD - Belize dollar',
+ 'CAD' => 'CAD - Canadian dollar',
+ 'CDF' => 'CDF - Congolese franc',
+ 'CHF' => 'CHF - Swiss franc',
+ 'CLP' => 'CLP - Chilean peso',
+ 'CNY' => 'CNY - Renminbi (Chinese) yuan',
+ 'COP' => 'COP - Colombian peso',
+ 'COU' => 'COU - Unidad de Valor Real (UVR)',
+ 'CRC' => 'CRC - Costa Rican colon',
+ 'CUC' => 'CUC - Cuban convertible peso',
+ 'CUP' => 'CUP - Cuban peso',
+ 'CVE' => 'CVE - Cape Verde escudo',
+ 'CZK' => 'CZK - Czech koruna',
+ 'DJF' => 'DJF - Djiboutian franc',
+ 'DKK' => 'DKK - Danish krone',
+ 'DOP' => 'DOP - Dominican peso',
+ 'DZD' => 'DZD - Algerian dinar',
+ 'EGP' => 'EGP - Egyptian pound',
+ 'ERN' => 'ERN - Eritrean nakfa',
+ 'ETB' => 'ETB - Ethiopian birr',
+ 'EUR' => 'EUR - Euro',
+ 'FJD' => 'FJD - Fiji dollar',
+ 'FKP' => 'FKP - Falkland Islands pound',
+ 'GBP' => 'GBP - Pound sterling',
+ 'GEL' => 'GEL - Georgian lari',
+ 'GHS' => 'GHS - Ghanaian cedi',
+ 'GIP' => 'GIP - Gibraltar pound',
+ 'GMD' => 'GMD - Gambian dalasi',
+ 'GNF' => 'GNF - Guinean franc',
+ 'GTQ' => 'GTQ - Guatemalan quetzal',
+ 'GYD' => 'GYD - Guyanese dollar',
+ 'HKD' => 'HKD - Hong Kong dollar',
+ 'HNL' => 'HNL - Honduran lempira',
+ 'HRK' => 'HRK - Croatian kuna',
+ 'HTG' => 'HTG - Haitian gourde',
+ 'HUF' => 'HUF - Hungarian forint',
+ 'IDR' => 'IDR - Indonesian rupiah',
+ 'ILS' => 'ILS - Israeli new shekel',
+ 'INR' => 'INR - Indian rupee',
+ 'IQD' => 'IQD - Iraqi dinar',
+ 'IRR' => 'IRR - Iranian rial',
+ 'ISK' => 'ISK - Icelandic krÛna',
+ 'JMD' => 'JMD - Jamaican dollar',
+ 'JOD' => 'JOD - Jordanian dinar',
+ 'JPY' => 'JPY - Japanese yen',
+ 'KES' => 'KES - Kenyan shilling',
+ 'KGS' => 'KGS - Kyrgyzstani som',
+ 'KHR' => 'KHR - Cambodian riel',
+ 'KMF' => 'KMF - Comoro franc',
+ 'KPW' => 'KPW - North Korean won',
+ 'KRW' => 'KRW - South Korean won',
+ 'KWD' => 'KWD - Kuwaiti dinar',
+ 'KYD' => 'KYD - Cayman Islands dollar',
+ 'KZT' => 'KZT - Kazakhstani tenge',
+ 'LAK' => 'LAK - Lao kip',
+ 'LBP' => 'LBP - Lebanese pound',
+ 'LKR' => 'LKR - Sri Lankan rupee',
+ 'LRD' => 'LRD - Liberian dollar',
+ 'LSL' => 'LSL - Lesotho loti',
+ 'LYD' => 'LYD - Libyan dinar',
+ 'MAD' => 'MAD - Moroccan dirham',
+ 'MDL' => 'MDL - Moldovan leu',
+ 'MGA' => 'MGA - Malagasy ariary',
+ 'MKD' => 'MKD - Macedonian denar',
+ 'MMK' => 'MMK - Myanmar kyat',
+ 'MNT' => 'MNT - Mongolian t?gr?g',
+ 'MOP' => 'MOP - Macanese pataca',
+ 'MRU' => 'MRU - Mauritanian ouguiya',
+ 'MUR' => 'MUR - Mauritian rupee',
+ 'MVR' => 'MVR - Maldivian rufiyaa',
+ 'MWK' => 'MWK - Malawian kwacha',
+ 'MXN' => 'MXN - Mexican peso',
+ 'MXV' => 'MXV - Mexican Unidad de Inversion (UDI)',
+ 'MYR' => 'MYR - Malaysian ringgit',
+ 'MZN' => 'MZN - Mozambican metical',
+ 'NAD' => 'NAD - Namibian dollar',
+ 'NGN' => 'NGN - Nigerian naira',
+ 'NIO' => 'NIO - Nicaraguan cÛrdoba',
+ 'NOK' => 'NOK - Norwegian krone',
+ 'NPR' => 'NPR - Nepalese rupee',
+ 'NZD' => 'NZD - New Zealand dollar',
+ 'OMR' => 'OMR - Omani rial',
+ 'PAB' => 'PAB - Panamanian balboa',
+ 'PEN' => 'PEN - Peruvian sol',
+ 'PGK' => 'PGK - Papua New Guinean kina',
+ 'PHP' => 'PHP - Philippine peso',
+ 'PKR' => 'PKR - Pakistani rupee',
+ 'PLN' => 'PLN - Polish z?oty',
+ 'PYG' => 'PYG - Paraguayan guaranÌ',
+ 'QAR' => 'QAR - Qatari riyal',
+ 'RON' => 'RON - Romanian leu',
+ 'RSD' => 'RSD - Serbian dinar',
+ 'RUB' => 'RUB - Russian ruble',
+ 'RWF' => 'RWF - Rwandan franc',
+ 'SAR' => 'SAR - Saudi riyal',
+ 'SBD' => 'SBD - Solomon Islands dollar',
+ 'SCR' => 'SCR - Seychelles rupee',
+ 'SDG' => 'SDG - Sudanese pound',
+ 'SEK' => 'SEK - Swedish krona/kronor',
+ 'SGD' => 'SGD - Singapore dollar',
+ 'SHP' => 'SHP - Saint Helena pound',
+ 'SLL' => 'SLL - Sierra Leonean leone',
+ 'SOS' => 'SOS - Somali shilling',
+ 'SRD' => 'SRD - Surinamese dollar',
+ 'SSP' => 'SSP - South Sudanese pound',
+ 'STN' => 'STN - S?o TomÈ and PrÌncipe dobra',
+ 'SVC' => 'SVC - Salvadoran colÛn',
+ 'SYP' => 'SYP - Syrian pound',
+ 'SZL' => 'SZL - Swazi lilangeni',
+ 'THB' => 'THB - Thai baht',
+ 'TJS' => 'TJS - Tajikistani somoni',
+ 'TMT' => 'TMT - Turkmenistan manat',
+ 'TND' => 'TND - Tunisian dinar',
+ 'TOP' => 'TOP - Tongan pa?anga',
+ 'TRY' => 'TRY - Turkish lira',
+ 'TTD' => 'TTD - Trinidad and Tobago dollar',
+ 'TWD' => 'TWD - New Taiwan dollar',
+ 'TZS' => 'TZS - Tanzanian shilling',
+ 'UAH' => 'UAH - Ukrainian hryvnia',
+ 'UGX' => 'UGX - Ugandan shilling',
+ 'USD' => 'USD - United States dollar',
+ 'UYI' => 'UYI - Uruguay Peso en Unidades Indexadas',
+ 'UYU' => 'UYU - Uruguayan peso',
+ 'UYW' => 'UYW - Unidad previsional',
+ 'UZS' => 'UZS - Uzbekistan som',
+ 'VES' => 'VES - Venezuelan bolÌvar soberano',
+ 'VND' => 'VND - Vietnamese ??ng',
+ 'VUV' => 'VUV - Vanuatu vatu',
+ 'WST' => 'WST - Samoan tala',
+ 'YER' => 'YER - Yemeni rial',
+ 'ZAR' => 'ZAR - South African rand',
+ 'ZMW' => 'ZMW - Zambian kwacha',
+ 'ZWL' => 'ZWL - Zimbabwean dollar'];
+
+ $shopCurrencies = Shop::DB()->executeQuery('SELECT * FROM twaehrung', 2);
+
+ $result = [];
+
+ foreach ($shopCurrencies as $sC) {
+ if (array_key_exists($sC->cISO, $currencies)) {
+ $result[$sC->cISO] = $currencies[$sC->cISO];
+ }
+ }
+
+ return $result;
+ }
+
+ public function loadRequest(&$options = [])
+ {
+ $oKunde = !$this->getBestellung()->oKunde && $this->PaymentMethod()->duringCheckout ? $_SESSION['Kunde'] : $this->getBestellung()->oKunde;
+ if ($this->getBestellung()) {
+ if (
+ $oKunde->nRegistriert
+ && (
+ $customer = $this->getCustomer(
+ array_key_exists(
+ 'mollie_create_customer',
+ $_SESSION['cPost_arr'] ?: []
+ ) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y',
+ $oKunde
+ )
+ )
+ && isset($customer)
+ ) {
+ $options['customerId'] = $customer->id;
+ }
+ $this->amount = Amount::factory($this->getBestellung()->fGesamtsummeKundenwaehrung, $this->getBestellung()->Waehrung->cISO, true);
+ $this->redirectUrl = $this->PaymentMethod()->duringCheckout ? Shop::getURL() . '/bestellabschluss.php?' . http_build_query(['hash' => $this->getHash()]) : $this->PaymentMethod()->getReturnURL($this->getBestellung());
+ $this->metadata = [
+ 'kBestellung' => $this->getBestellung()->kBestellung ?: $this->getBestellung()->cBestellNr,
+ 'kKunde' => $this->getBestellung()->kKunde,
+ 'kKundengruppe' => Session::getInstance()->CustomerGroup()->kKundengruppe,
+ 'cHash' => $this->getHash(),
+ ];
+ }
+
+ $this->locale = self::getLocale($_SESSION['cISOSprache'], Session::getInstance()->Customer()->cLand);
+ $this->webhookUrl = Shop::getURL(true) . '/?' . http_build_query([
+ 'mollie' => 1,
+ 'hash' => $this->getHash(),
+ 'test' => $this->API()->isTest() ?: null,
+ ]);
+
+ $pm = $this->PaymentMethod();
+ $isPayAgain = strpos($_SERVER['PHP_SELF'], 'bestellab_again') !== false;
+ if ($pm::METHOD !== '' && (self::Plugin()->oPluginEinstellungAssoc_arr['resetMethod'] !== 'Y' || !$isPayAgain)) {
+ $this->method = $pm::METHOD;
+ }
+ }
+
+ /**
+ * @todo: Kunde wieder löschbar machen ?!
+ * @param mixed $createOrUpdate
+ * @param null|mixed $oKunde
+ * @return null|\Mollie\Api\Resources\Customer
+ */
+ public function getCustomer($createOrUpdate = false, $oKunde = null)
+ {
+ if (!$oKunde) {
+ $oKunde = $this->getBestellung()->oKunde;
+ }
+
+ if (!$this->customer) {
+ $customerModel = Customer::fromID($oKunde->kKunde, 'kKunde');
+ if ($customerModel->customerId) {
+ try {
+ $this->customer = $this->API()->Client()->customers->get($customerModel->customerId);
+ } catch (ApiException $e) {
+ $this->Log(sprintf('Fehler beim laden des Mollie Customers %s (kKunde: %d): %s', $customerModel->customerId, $customerModel->kKunde, $e->getMessage()), LOGLEVEL_ERROR);
+ }
+ }
+
+ if ($createOrUpdate) {
+ $customer = [
+ 'name' => utf8_encode(trim($oKunde->cVorname . ' ' . $oKunde->cNachname)),
+ 'email' => utf8_encode($oKunde->cMail),
+ 'locale' => self::getLocale($_SESSION['cISOSprache'], $oKunde->cLand),
+ 'metadata' => (object)[
+ 'kKunde' => $oKunde->kKunde,
+ 'kKundengruppe' => $oKunde->kKundengruppe,
+ 'cKundenNr' => utf8_encode($oKunde->cKundenNr),
+ ],
+ ];
+
+ if ($this->customer) { // UPDATE
+ $this->customer->name = $customer['name'];
+ $this->customer->email = $customer['email'];
+ $this->customer->locale = $customer['locale'];
+ $this->customer->metadata = $customer['metadata'];
+
+ try {
+ $this->customer->update();
+ } catch (Exception $e) {
+ $this->Log(sprintf("Fehler beim aktualisieren des Mollie Customers %s: %s\n%s", $this->customer->id, $e->getMessage(), print_r($customer, 1)), LOGLEVEL_ERROR);
+ }
+ } else { // create
+ try {
+ $this->customer = $this->API()->Client()->customers->create($customer);
+ $customerModel->kKunde = $oKunde->kKunde;
+ $customerModel->customerId = $this->customer->id;
+ $customerModel->save();
+ $this->Log(sprintf("Customer '%s' für Kunde %s (%d) bei Mollie angelegt.", $this->customer->id, $this->customer->name, $this->getBestellung()->kKunde));
+ } catch (Exception $e) {
+ $this->Log(sprintf("Fehler beim anlegen eines Mollie Customers: %s\n%s", $e->getMessage(), print_r($customer, 1)), LOGLEVEL_ERROR);
+ }
+ }
+ }
+ }
+
+ return $this->customer;
+ }
+
+ /**
+ * Storno Order
+ */
+ public function storno()
+ {
+ if (in_array((int)$this->getBestellung()->cStatus, [BESTELLUNG_STATUS_OFFEN, BESTELLUNG_STATUS_IN_BEARBEITUNG], true)) {
+ $log = [];
+
+ $conf = Shop::getSettings([CONF_GLOBAL, CONF_TRUSTEDSHOPS]);
+ $nArtikelAnzeigefilter = (int)$conf['global']['artikel_artikelanzeigefilter'];
+
+ foreach ($this->getBestellung()->Positionen as $pos) {
+ if ($pos->kArtikel && $pos->Artikel && $pos->Artikel->cLagerBeachten === 'Y') {
+ $log[] = sprintf('Reset stock of "%s" by %d', $pos->Artikel->cArtNr, -1 * $pos->nAnzahl);
+ self::aktualisiereLagerbestand($pos->Artikel, -1 * $pos->nAnzahl, $pos->WarenkorbPosEigenschaftArr, $nArtikelAnzeigefilter);
+ }
+ }
+ $log[] = sprintf("Cancel order '%s'.", $this->getBestellung()->cBestellNr);
+
+ if (Shop::DB()->executeQueryPrepared('UPDATE tbestellung SET cAbgeholt = \'Y\', cStatus = :cStatus WHERE kBestellung = :kBestellung', [':cStatus' => '-1', ':kBestellung' => $this->getBestellung()->kBestellung], 3)) {
+ $this->Log(implode('\n', $log));
+ }
+ }
+ }
+
+ protected static function aktualisiereLagerbestand($Artikel, $nAnzahl, $WarenkorbPosEigenschaftArr, $nArtikelAnzeigefilter = 1)
+ {
+ $artikelBestand = (float)$Artikel->fLagerbestand;
+
+ if (isset($Artikel->cLagerBeachten) && $Artikel->cLagerBeachten === 'Y') {
+ if (
+ $Artikel->cLagerVariation === 'Y' && is_array($WarenkorbPosEigenschaftArr) && count($WarenkorbPosEigenschaftArr) > 0
+ ) {
+ foreach ($WarenkorbPosEigenschaftArr as $eWert) {
+ $EigenschaftWert = new EigenschaftWert($eWert->kEigenschaftWert);
+ if ($EigenschaftWert->fPackeinheit === .0) {
+ $EigenschaftWert->fPackeinheit = 1;
+ }
+ Shop::DB()->query(
+ 'UPDATE teigenschaftwert
+ SET fLagerbestand = fLagerbestand - ' . ($nAnzahl * $EigenschaftWert->fPackeinheit) . '
+ WHERE kEigenschaftWert = ' . (int)$eWert->kEigenschaftWert,
+ 4
+ );
+ }
+ } elseif ($Artikel->fPackeinheit > 0) {
+ // Stückliste
+ if ($Artikel->kStueckliste > 0) {
+ $artikelBestand = self::aktualisiereStuecklistenLagerbestand($Artikel, $nAnzahl);
+ } else {
+ Shop::DB()->query(
+ 'UPDATE tartikel
+ SET fLagerbestand = IF (fLagerbestand >= ' . ($nAnzahl * $Artikel->fPackeinheit) . ',
+ (fLagerbestand - ' . ($nAnzahl * $Artikel->fPackeinheit) . '), fLagerbestand)
+ WHERE kArtikel = ' . (int)$Artikel->kArtikel,
+ 4
+ );
+ $tmpArtikel = Shop::DB()->select('tartikel', 'kArtikel', (int)$Artikel->kArtikel, null, null, null, null, false, 'fLagerbestand');
+ if ($tmpArtikel !== null) {
+ $artikelBestand = (float)$tmpArtikel->fLagerbestand;
+ }
+ // Stücklisten Komponente
+ if (ArtikelHelper::isStuecklisteKomponente($Artikel->kArtikel)) {
+ self::aktualisiereKomponenteLagerbestand($Artikel->kArtikel, $artikelBestand, isset($Artikel->cLagerKleinerNull) && $Artikel->cLagerKleinerNull === 'Y');
+ }
+ }
+ // Aktualisiere Merkmale in tartikelmerkmal vom Vaterartikel
+ if ($Artikel->kVaterArtikel > 0) {
+ Artikel::beachteVarikombiMerkmalLagerbestand($Artikel->kVaterArtikel, $nArtikelAnzeigefilter);
+ }
+ }
+ }
+
+ return $artikelBestand;
+ }
+
+ protected static function aktualisiereStuecklistenLagerbestand($oStueckListeArtikel, $nAnzahl)
+ {
+ $nAnzahl = (float)$nAnzahl;
+ $kStueckListe = (int)$oStueckListeArtikel->kStueckliste;
+ $bestandAlt = (float)$oStueckListeArtikel->fLagerbestand;
+ $bestandNeu = $bestandAlt;
+ $bestandUeberverkauf = $bestandAlt;
+
+ if ($nAnzahl > 0) {
+ // Gibt es lagerrelevante Komponenten in der Stückliste?
+ $oKomponente_arr = Shop::DB()->query(
+ "SELECT tstueckliste.kArtikel, tstueckliste.fAnzahl
+ FROM tstueckliste
+ JOIN tartikel
+ ON tartikel.kArtikel = tstueckliste.kArtikel
+ WHERE tstueckliste.kStueckliste = $kStueckListe
+ AND tartikel.cLagerBeachten = 'Y'",
+ 2
+ );
+
+ if (is_array($oKomponente_arr) && count($oKomponente_arr) > 0) {
+ // wenn ja, dann wird für diese auch der Bestand aktualisiert
+ $options = Artikel::getDefaultOptions();
+
+ $options->nKeineSichtbarkeitBeachten = 1;
+
+ foreach ($oKomponente_arr as $oKomponente) {
+ $tmpArtikel = new Artikel();
+ $tmpArtikel->fuelleArtikel($oKomponente->kArtikel, $options);
+
+ $komponenteBestand = floor(self::aktualisiereLagerbestand($tmpArtikel, $nAnzahl * $oKomponente->fAnzahl, null) / $oKomponente->fAnzahl);
+
+ if ($komponenteBestand < $bestandNeu && $tmpArtikel->cLagerKleinerNull !== 'Y') {
+ // Neuer Bestand ist der Kleinste Komponententbestand aller Artikel ohne Überverkauf
+ $bestandNeu = $komponenteBestand;
+ } elseif ($komponenteBestand < $bestandUeberverkauf) {
+ // Für Komponenten mit Überverkauf wird der kleinste Bestand ermittelt.
+ $bestandUeberverkauf = $komponenteBestand;
+ }
+ }
+ }
+
+ // Ist der alte gleich dem neuen Bestand?
+ if ($bestandAlt === $bestandNeu) {
+ // Es sind keine lagerrelevanten Komponenten vorhanden, die den Bestand der Stückliste herabsetzen.
+ if ($bestandUeberverkauf === $bestandNeu) {
+ // Es gibt auch keine Komponenten mit Überverkäufen, die den Bestand verringern, deshalb wird
+ // der Bestand des Stücklistenartikels anhand des Verkaufs verringert
+ $bestandNeu = $bestandNeu - $nAnzahl * $oStueckListeArtikel->fPackeinheit;
+ } else {
+ // Da keine lagerrelevanten Komponenten vorhanden sind, wird der kleinste Bestand der
+ // Komponentent mit Überverkauf verwendet.
+ $bestandNeu = $bestandUeberverkauf;
+ }
+
+ Shop::DB()->update('tartikel', 'kArtikel', (int)$oStueckListeArtikel->kArtikel, (object)[
+ 'fLagerbestand' => $bestandNeu,
+ ]);
+ }
+ // Kein Lagerbestands-Update für die Stückliste notwendig! Dies erfolgte bereits über die Komponentenabfrage und
+ // die dortige Lagerbestandsaktualisierung!
+ }
+
+ return $bestandNeu;
+ }
+
+ protected static function aktualisiereKomponenteLagerbestand($kKomponenteArtikel, $fLagerbestand, $bLagerKleinerNull)
+ {
+ $kKomponenteArtikel = (int)$kKomponenteArtikel;
+ $fLagerbestand = (float)$fLagerbestand;
+
+ $oStueckliste_arr = Shop::DB()->query(
+ "SELECT tstueckliste.kStueckliste, tstueckliste.fAnzahl,
+ tartikel.kArtikel, tartikel.fLagerbestand, tartikel.cLagerKleinerNull
+ FROM tstueckliste
+ JOIN tartikel
+ ON tartikel.kStueckliste = tstueckliste.kStueckliste
+ WHERE tstueckliste.kArtikel = $kKomponenteArtikel
+ AND tartikel.cLagerBeachten = 'Y'",
+ 2
+ );
+
+ if (is_array($oStueckliste_arr) && count($oStueckliste_arr) > 0) {
+ foreach ($oStueckliste_arr as $oStueckliste) {
+ // Ist der aktuelle Bestand der Stückliste größer als dies mit dem Bestand der Komponente möglich wäre?
+ $maxAnzahl = floor($fLagerbestand / $oStueckliste->fAnzahl);
+ if ($maxAnzahl < (float)$oStueckliste->fLagerbestand && (!$bLagerKleinerNull || $oStueckliste->cLagerKleinerNull === 'Y')) {
+ // wenn ja, dann den Bestand der Stückliste entsprechend verringern, aber nur wenn die Komponente nicht
+ // überberkaufbar ist oder die gesamte Stückliste Überverkäufe zulässt
+ Shop::DB()->update('tartikel', 'kArtikel', (int)$oStueckliste->kArtikel, (object)[
+ 'fLagerbestand' => $maxAnzahl,
+ ]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return null|array|bool|int|object
+ */
+ public function getLogs()
+ {
+ return Shop::DB()->executeQueryPrepared('SELECT * FROM tzahlungslog WHERE cLogData LIKE :kBestellung OR cLogData LIKE :cBestellNr OR cLogData LIKE :MollieID ORDER BY dDatum DESC, cLog DESC', [
+ ':kBestellung' => '%#' . ($this->getBestellung()->kBestellung ?: '##') . '%',
+ ':cBestellNr' => '%§' . ($this->getBestellung()->cBestellNr ?: '§§') . '%',
+ ':MollieID' => '%$' . ($this->getMollie()->id ?: '$$') . '%',
+ ], 2);
+ }
+
+ /**
+ * @return bool
+ */
+ public function remindable()
+ {
+ return (int)$this->getBestellung()->cStatus !== BESTELLUNG_STATUS_STORNO && !in_array($this->getModel()->cStatus, [PaymentStatus::STATUS_PAID, PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PENDING, OrderStatus::STATUS_COMPLETED, OrderStatus::STATUS_SHIPPING], true);
+ }
+
+ /**
+ * @return string
+ */
+ public function LogData()
+ {
+ $data = '';
+ if ($this->getBestellung()->kBestellung) {
+ $data .= '#' . $this->getBestellung()->kBestellung;
+ }
+ if ($this->getMollie()) {
+ $data .= '$' . $this->getMollie()->id;
+ }
+
+ return $data;
+ }
+
+ abstract public function cancelOrRefund($force = false);
+
+ /**
+ * @param array $paymentOptions
+ * @return Order|Payment
+ */
+ abstract public function create(array $paymentOptions = []);
+
+ /**
+ * @return string
+ */
+ public function getRepayURL()
+ {
+ return Shop::getURL(true) . '/?m_pay=' . md5($this->getModel()->kID . '-' . $this->getBestellung()->kBestellung);
+ }
+
+ public function getDescription()
+ {
+ $descTemplate = trim(Helper::getSetting('paymentDescTpl')) ?: 'Order {orderNumber}';
+ $oKunde = $this->getBestellung()->oKunde ?: $_SESSION['Kunde'];
+
+ return str_replace([
+ '{orderNumber}',
+ '{storeName}',
+ '{customer.firstname}',
+ '{customer.lastname}',
+ '{customer.company}',
+ ], [
+ $this->getBestellung()->cBestellNr,
+ Shop::getSettings([CONF_GLOBAL])['global']['global_shopname'],
+ $oKunde->cVorname,
+ $oKunde->cNachname,
+ $oKunde->cFirma
+
+ ], $descTemplate);
+ }
+
+ abstract protected function updateOrderNumber();
+
+ /**
+ * @param Bestellung $oBestellung
+ * @return $this
+ */
+ protected function setBestellung(Bestellung $oBestellung)
+ {
+ $this->oBestellung = $oBestellung;
+
+ return $this;
+ }
+
+ /**
+ * @param \Mollie\Api\Resources\Payment|Order $model
+ * @return self
+ */
+ abstract protected function setMollie($model);
+}
diff --git a/version/206/class/Checkout/AbstractResource.php b/version/206/class/Checkout/AbstractResource.php
new file mode 100644
index 0000000..7c4439e
--- /dev/null
+++ b/version/206/class/Checkout/AbstractResource.php
@@ -0,0 +1,18 @@
+title = substr(trim(($address->cAnrede === 'm' ? Shop::Lang()->get('mr') : Shop::Lang()->get('mrs')) . ' ' . $address->cTitel) ?: null, 0, 20);
+ $resource->givenName = $address->cVorname;
+ $resource->familyName = $address->cNachname;
+ $resource->email = $address->cMail ?: null;
+
+ if ($organizationName = trim($address->cFirma)) {
+ $resource->organizationName = $organizationName;
+ }
+
+ // Validity-Check
+ // TODO: Phone, with E.164 check
+ // TODO: Is Email-Format Check needed?
+ if (!$resource->givenName || !$resource->familyName || !$resource->email) {
+ throw ResourceValidityException::trigger(ResourceValidityException::ERROR_REQUIRED, ['givenName', 'familyName', 'email'], $resource);
+ }
+
+ return $resource;
+ }
+}
diff --git a/version/206/class/Checkout/Order/OrderLine.php b/version/206/class/Checkout/Order/OrderLine.php
new file mode 100644
index 0000000..757edf7
--- /dev/null
+++ b/version/206/class/Checkout/Order/OrderLine.php
@@ -0,0 +1,237 @@
+totalAmount->value;
+ }
+ if (abs($sum - (float)$amount->value) > 0) {
+ $diff = (round((float)$amount->value - $sum, 2));
+ if ($diff !== 0.0) {
+ $line = new self();
+ $line->type = $diff > 0 ? OrderLineType::TYPE_SURCHARGE : OrderLineType::TYPE_DISCOUNT;
+ // TODO: Translation needed?
+ $line->name = 'Rundungsausgleich';
+ $line->quantity = 1;
+ $line->unitPrice = Amount::factory($diff, $currency);
+ $line->totalAmount = Amount::factory($diff, $currency);
+ $line->vatRate = '0.00';
+ $line->vatAmount = Amount::factory(0, $currency);
+
+ return $line;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Bestellung $oBestellung
+ * @return OrderLine
+ */
+ public static function getCredit(Bestellung $oBestellung)
+ {
+ $line = new self();
+ $line->type = OrderLineType::TYPE_STORE_CREDIT;
+ $line->name = 'Guthaben';
+ $line->quantity = 1;
+ // TODO: check currency of Guthaben
+ $line->unitPrice = Amount::factory($oBestellung->fGuthaben, $oBestellung->Waehrung->cISO);
+ $line->totalAmount = $line->unitPrice;
+ $line->vatRate = '0.00';
+ $line->vatAmount = Amount::factory(0, $oBestellung->Waehrung->cISO);
+
+ return $line;
+ }
+
+ /**
+ * @param stdClass|WarenkorbPos $oPosition
+ * @param null $currency
+ * @return OrderLine
+ */
+ public static function factory($oPosition, $currency = null)
+ {
+ if (!$oPosition) {
+ throw new RuntimeException('$oPosition invalid:', print_r($oPosition, 1));
+ }
+
+ $resource = new static();
+
+ $resource->fill($oPosition, $currency);
+
+ // Validity Check
+ if (
+ !$resource->name || !$resource->quantity || !$resource->unitPrice || !$resource->totalAmount
+ || !$resource->vatRate || !$resource->vatAmount
+ ) {
+ throw ResourceValidityException::trigger(
+ ResourceValidityException::ERROR_REQUIRED,
+ ['name', 'quantity', 'unitPrice', 'totalAmount', 'vatRate', 'vatAmount'],
+ $resource
+ );
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @param stdClass|WarenkorbPos $oPosition
+ * @param null|stdClass $currency
+ * @return $this
+ * @todo Setting for Fraction handling needed?
+ */
+ protected function fill($oPosition, $currency = null)
+ {
+ if (!$currency) {
+ $currency = Amount::FallbackCurrency();
+ }
+
+ $isKupon = (int)$oPosition->nPosTyp === (int)C_WARENKORBPOS_TYP_KUPON;
+ $isFrac = fmod($oPosition->nAnzahl, 1) !== 0.0;
+
+ // Kupon? set vatRate to 0 and adjust netto
+ $vatRate = $isKupon ? 0 : (float)$oPosition->fMwSt / 100;
+ $netto = $isKupon ? round($oPosition->fPreis * (1 + $vatRate), 4) : round($oPosition->fPreis, 4);
+
+ // Fraction? transform, as it were 1, and set quantity to 1
+ $netto = round(($isFrac ? $netto * (float)$oPosition->nAnzahl : $netto) * $currency->fFaktor, 4);
+ $this->quantity = $isFrac ? 1 : (int)$oPosition->nAnzahl;
+
+ // Fraction? include quantity and unit in name
+ $this->name = $isFrac ? sprintf('%s (%.2f %s)', $oPosition->cName, (float)$oPosition->nAnzahl, $oPosition->cEinheit) : $oPosition->cName;
+ if (!$this->name) {
+ $this->name = '(null)';
+ }
+ $this->mapType($oPosition->nPosTyp, $netto > 0);
+
+ //$unitPriceNetto = round(($currency->fFaktor * $netto), 4);
+
+ $this->unitPrice = Amount::factory(round($netto * (1 + $vatRate), 2), $currency->cISO, false);
+ $this->totalAmount = Amount::factory(round($this->quantity * $this->unitPrice->value, 2), $currency->cISO, false);
+
+ $this->vatRate = number_format($vatRate * 100, 2);
+ $this->vatAmount = Amount::factory(round($this->totalAmount->value - ($this->totalAmount->value / (1 + $vatRate)), 2), $currency->cISO, false);
+
+ $metadata = [];
+
+ // Is Artikel ?
+ if (isset($oPosition->Artikel)) {
+ $this->sku = $oPosition->Artikel->cArtNr;
+ $metadata['kArtikel'] = $oPosition->kArtikel;
+ if ($oPosition->cUnique !== '') {
+ $metadata['cUnique'] = utf8_encode($oPosition->cUnique);
+ }
+ }
+
+ if (isset($oPosition->WarenkorbPosEigenschaftArr) && is_array($oPosition->WarenkorbPosEigenschaftArr) && count($oPosition->WarenkorbPosEigenschaftArr)) {
+ $metadata['properties'] = [];
+ /** @var WarenkorbPosEigenschaft $warenkorbPosEigenschaft */
+ foreach ($oPosition->WarenkorbPosEigenschaftArr as $warenkorbPosEigenschaft) {
+ $metadata['properties'][] = [
+ 'kEigenschaft' => $warenkorbPosEigenschaft->kEigenschaft,
+ 'kEigenschaftWert' => $warenkorbPosEigenschaft->kEigenschaftWert,
+ 'name' => utf8_encode($warenkorbPosEigenschaft->cEigenschaftName),
+ 'value' => utf8_encode($warenkorbPosEigenschaft->cEigenschaftWertName),
+ ];
+ if (strlen(json_encode($metadata)) > 1000) {
+ array_pop($metadata['properties']);
+
+ break;
+ }
+ }
+ }
+ if (json_encode($metadata) !== false) {
+ $this->metadata = $metadata;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $nPosTyp
+ * @param $positive
+ * @return OrderLine
+ */
+ protected function mapType($nPosTyp, $positive)
+ {
+ switch ($nPosTyp) {
+ case C_WARENKORBPOS_TYP_ARTIKEL:
+ case C_WARENKORBPOS_TYP_GRATISGESCHENK:
+ // TODO: digital / Download Artikel?
+ $this->type = OrderLineType::TYPE_PHYSICAL;
+
+ return $this;
+
+ case C_WARENKORBPOS_TYP_VERSANDPOS:
+ $this->type = OrderLineType::TYPE_SHIPPING_FEE;
+
+ return $this;
+
+ case C_WARENKORBPOS_TYP_GUTSCHEIN:
+ case C_WARENKORBPOS_TYP_KUPON:
+ case C_WARENKORBPOS_TYP_NEUKUNDENKUPON:
+ $this->type = OrderLineType::TYPE_DISCOUNT;
+
+ return $this;
+
+ case C_WARENKORBPOS_TYP_VERPACKUNG:
+ case C_WARENKORBPOS_TYP_VERSANDZUSCHLAG:
+ case C_WARENKORBPOS_TYP_ZAHLUNGSART:
+ case C_WARENKORBPOS_TYP_VERSAND_ARTIKELABHAENGIG:
+ case C_WARENKORBPOS_TYP_NACHNAHMEGEBUEHR:
+ $this->type = OrderLineType::TYPE_SURCHARGE;
+
+ return $this;
+
+ default:
+ $this->type = $positive ? OrderLineType::TYPE_SURCHARGE : OrderLineType::TYPE_DISCOUNT;
+
+ return $this;
+ }
+ }
+}
diff --git a/version/206/class/Checkout/OrderCheckout.php b/version/206/class/Checkout/OrderCheckout.php
new file mode 100644
index 0000000..1d61b6d
--- /dev/null
+++ b/version/206/class/Checkout/OrderCheckout.php
@@ -0,0 +1,406 @@
+getMollie()->status !== OrderStatus::STATUS_AUTHORIZED && $checkout->getMollie()->status !== OrderStatus::STATUS_SHIPPING) {
+ throw new RuntimeException('Nur autorisierte Zahlungen können erfasst werden!');
+ }
+ $shipment = $checkout->API()->Client()->shipments->createFor($checkout->getMollie(), ['lines' => []]);
+ $checkout->Log(sprintf('Bestellung wurde manuell erfasst/versandt: %s', $shipment->id));
+
+ return $shipment->id;
+ }
+
+ /**
+ * @param false $force
+ * @return null|Order
+ */
+ public function getMollie($force = false)
+ {
+ if ($force || (!$this->order && $this->getModel()->kID)) {
+ try {
+ $this->order = $this->API()->Client()->orders->get($this->getModel()->kID, ['embed' => 'payments,shipments,refunds']);
+ } catch (Exception $e) {
+ throw new RuntimeException(sprintf('Mollie-Order \'%s\' konnte nicht geladen werden: %s', $this->getModel()->kID, $e->getMessage()));
+ }
+ }
+
+ return $this->order;
+ }
+
+ /**
+ * @param OrderCheckout $checkout
+ * @throws ApiException
+ * @return Order
+ */
+ public static function cancel($checkout)
+ {
+ if (!$checkout->getMollie() || !$checkout->getMollie()->isCancelable) {
+ throw new RuntimeException('Bestellung kann nicht abgebrochen werden.');
+ }
+ $order = $checkout->getMollie()->cancel();
+ $checkout->Log('Bestellung wurde manuell abgebrochen.');
+
+ return $order;
+ }
+
+ /**
+ * @throws Exception
+ * @return array
+ */
+ public function getShipments()
+ {
+ $shipments = [];
+ $lieferschien_arr = Shop::DB()->executeQueryPrepared('SELECT kLieferschein FROM tlieferschein WHERE kInetBestellung = :kBestellung', [
+ ':kBestellung' => $this->getBestellung()->kBestellung
+ ], 2);
+
+ foreach ($lieferschien_arr as $lieferschein) {
+ $shipments[] = new Shipment($lieferschein->kLieferschein, $this);
+ }
+
+ return $shipments;
+ }
+
+ /**
+ * @param mixed $force
+ * @throws ApiException
+ * @return string
+ */
+ public function cancelOrRefund($force = false)
+ {
+ if (!$this->getMollie()) {
+ throw new RuntimeException('Mollie-Order konnte nicht geladen werden: ' . $this->getModel()->kID);
+ }
+ if ($force || (int)$this->getBestellung()->cStatus === BESTELLUNG_STATUS_STORNO) {
+ if ($this->getMollie()->isCancelable) {
+ $res = $this->getMollie()->cancel();
+ $result = 'Order cancelled, Status: ' . $res->status;
+ } else {
+ $res = $this->getMollie()->refundAll();
+ $result = 'Order Refund initiiert, Status: ' . $res->status;
+ }
+ $this->PaymentMethod()->Log('OrderCheckout::cancelOrRefund: ' . $result, $this->LogData());
+
+ return $result;
+ }
+
+ throw new RuntimeException('Bestellung ist derzeit nicht storniert, Status: ' . $this->getBestellung()->cStatus);
+ }
+
+ /**
+ * @param array $paymentOptions
+ * @return Order
+ */
+ public function create(array $paymentOptions = [])
+ {
+ if ($this->getModel()->kID) {
+ try {
+ $this->order = $this->API()->Client()->orders->get($this->getModel()->kID, ['embed' => 'payments']);
+ if (in_array($this->order->status, [OrderStatus::STATUS_COMPLETED, OrderStatus::STATUS_PAID, OrderStatus::STATUS_AUTHORIZED, OrderStatus::STATUS_PENDING], true)) {
+ $this->handleNotification();
+
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errAlreadyPaid']);
+ }
+ if ($this->order->status === OrderStatus::STATUS_CREATED) {
+ if ($this->order->payments()) {
+ /** @var Payment $payment */
+ foreach ($this->order->payments() as $payment) {
+ if ($payment->status === PaymentStatus::STATUS_OPEN) {
+ $this->setPayment($payment);
+
+ break;
+ }
+ }
+ }
+ if (!$this->getPayment()) {
+ $this->setPayment($this->API()->Client()->orderPayments->createForId($this->getModel()->kID, $paymentOptions));
+ }
+ $this->updateModel()->saveModel();
+
+ return $this->getMollie(true);
+ }
+ } catch (RuntimeException $e) {
+ throw $e;
+ } catch (Exception $e) {
+ $this->Log(sprintf("OrderCheckout::create: Letzte Order '%s' konnte nicht geladen werden: %s, versuche neue zu erstellen.", $this->getModel()->kID, $e->getMessage()), LOGLEVEL_ERROR);
+ }
+ }
+
+ try {
+ $req = $this->loadRequest($paymentOptions)->jsonSerialize();
+ $this->order = $this->API()->Client()->orders->create($req);
+ $this->Log(sprintf("Order für '%s' wurde erfolgreich angelegt: %s", $this->getBestellung()->cBestellNr, $this->order->id));
+ $this->updateModel()->saveModel();
+
+ return $this->order;
+ } catch (Exception $e) {
+ $this->Log(sprintf("OrderCheckout::create: Neue Order '%s' konnte nicht erstellt werden: %s.", $this->getBestellung()->cBestellNr, $e->getMessage()), LOGLEVEL_ERROR);
+
+ throw new RuntimeException(sprintf("Order für '%s' konnte nicht angelegt werden: %s", $this->getBestellung()->cBestellNr, $e->getMessage()));
+ }
+ }
+
+ /**
+ * @param mixed $search
+ * @return null|Payment
+ */
+ public function getPayment($search = false)
+ {
+ if (!$this->_payment && $search && $this->getMollie()) {
+ foreach ($this->getMollie()->payments() as $payment) {
+ if (
+ in_array($payment->status, [
+ PaymentStatus::STATUS_AUTHORIZED,
+ PaymentStatus::STATUS_PAID,
+ PaymentStatus::STATUS_PENDING,
+ ], true)
+ ) {
+ $this->_payment = $payment;
+
+ break;
+ }
+ }
+ }
+
+ return $this->_payment;
+ }
+
+ /**
+ * @param $payment
+ * @return $this
+ */
+ public function setPayment($payment)
+ {
+ $this->_payment = $payment;
+
+ return $this;
+ }
+
+ /**
+ * @return $this|OrderCheckout
+ */
+ public function updateModel()
+ {
+ parent::updateModel();
+
+ if (!$this->getPayment() && $this->getMollie() && $this->getMollie()->payments()) {
+ /** @var Payment $payment */
+ foreach ($this->getMollie()->payments() as $payment) {
+ if (in_array($payment->status, [PaymentStatus::STATUS_OPEN, PaymentStatus::STATUS_PENDING, PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PAID], true)) {
+ $this->setPayment($payment);
+
+ break;
+ }
+ }
+ }
+ if ($this->getPayment()) {
+ $this->getModel()->cTransactionId = $this->getPayment()->id;
+ }
+ if ($this->getMollie()) {
+ $this->getModel()->cCheckoutURL = $this->getMollie()->getCheckoutUrl();
+ $this->getModel()->cWebhookURL = $this->getMollie()->webhookUrl;
+ $this->getModel()->cRedirectURL = $this->getMollie()->redirectUrl;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @return $this|OrderCheckout
+ */
+ public function loadRequest(&$options = [])
+ {
+ parent::loadRequest($options);
+
+ $this->orderNumber = $this->getBestellung()->cBestellNr;
+
+ $this->billingAddress = Address::factory($this->getBestellung()->oRechnungsadresse);
+ if ($this->getBestellung()->Lieferadresse !== null) {
+ if (!$this->getBestellung()->Lieferadresse->cMail) {
+ $this->getBestellung()->Lieferadresse->cMail = $this->getBestellung()->oRechnungsadresse->cMail;
+ }
+ $this->shippingAddress = Address::factory($this->getBestellung()->Lieferadresse);
+ }
+
+ if (
+ !empty(Session::getInstance()->Customer()->dGeburtstag)
+ && Session::getInstance()->Customer()->dGeburtstag !== '0000-00-00'
+ && preg_match('/^\d{4}-\d{2}-\d{2}$/', trim(Session::getInstance()->Customer()->dGeburtstag))
+ ) {
+ $this->consumerDateOfBirth = trim(Session::getInstance()->Customer()->dGeburtstag);
+ }
+
+ $lines = [];
+ foreach ($this->getBestellung()->Positionen as $oPosition) {
+ $lines[] = OrderLine::factory($oPosition, $this->getBestellung()->Waehrung);
+ }
+
+ if ($this->getBestellung()->GuthabenNutzen && $this->getBestellung()->fGuthaben > 0) {
+ $lines[] = OrderLine::getCredit($this->getBestellung());
+ }
+
+ if ($comp = OrderLine::getRoundingCompensation($lines, $this->amount, $this->getBestellung()->Waehrung->cISO)) {
+ $lines[] = $comp;
+ }
+ $this->lines = $lines;
+
+ if ($dueDays = $this->PaymentMethod()->getExpiryDays()) {
+ try {
+ // #145
+ //$max = $this->method && strpos($this->method, 'klarna') !== false ? 28 : 100;
+ $date = new DateTime(sprintf('+%d DAYS', $dueDays), new DateTimeZone('UTC'));
+ $this->expiresAt = $date->format('Y-m-d');
+ } catch (Exception $e) {
+ $this->Log($e->getMessage(), LOGLEVEL_ERROR);
+ }
+ }
+
+ $this->payment = $options;
+
+ return $this;
+ }
+
+ /**
+ * @return null|object
+ */
+ public function getIncomingPayment()
+ {
+ if (!$this->getMollie(true)) {
+ return null;
+ }
+
+ $cHinweis = sprintf('%s / %s', $this->getMollie()->id, $this->getPayment(true)->id);
+ if (Helper::getSetting('wawiPaymentID') === 'ord') {
+ $cHinweis = $this->getMollie()->id;
+ } elseif (Helper::getSetting('wawiPaymentID') === 'tr') {
+ $cHinweis = $this->getPayment(true)->id;
+ }
+
+
+ /** @var Payment $payment */
+ /** @noinspection NullPointerExceptionInspection */
+ foreach ($this->getMollie()->payments() as $payment) {
+ if (
+ in_array(
+ $payment->status,
+ [PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PAID],
+ true
+ )
+ ) {
+ $this->setPayment($payment);
+ $data = (object)[
+ 'fBetrag' => (float)$payment->amount->value,
+ 'cISO' => $payment->amount->currency,
+ 'cZahler' => $payment->details->paypalPayerId ?: $payment->customerId,
+ 'cHinweis' => $payment->details->paypalReference ?: $cHinweis,
+ ];
+ if (isset($payment->details, $payment->details->paypalFee)) {
+ $data->fZahlungsgebuehr = $payment->details->paypalFee->value;
+ }
+
+ return $data;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function updateOrderNumber()
+ {
+ try {
+ if ($this->getMollie()) {
+ $this->getMollie()->orderNumber = $this->getBestellung()->cBestellNr;
+ $this->getMollie()->webhookUrl = Shop::getURL() . '/?mollie=1';
+ $this->getMollie()->update();
+ }
+ if ($this->getModel()->cTransactionId) {
+ $this->API()->Client()->payments->update($this->getModel()->cTransactionId, [
+ 'description' => $this->getDescription()
+ ]);
+ }
+ } catch (Exception $e) {
+ $this->Log('OrderCheckout::updateOrderNumber:' . $e->getMessage(), LOGLEVEL_ERROR);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param Order $model
+ * @return $this|AbstractCheckout
+ */
+ protected function setMollie($model)
+ {
+ if ($model instanceof Order) {
+ $this->order = $model;
+ }
+
+ return $this;
+ }
+}
diff --git a/version/206/class/Checkout/Payment/Address.php b/version/206/class/Checkout/Payment/Address.php
new file mode 100644
index 0000000..468a08a
--- /dev/null
+++ b/version/206/class/Checkout/Payment/Address.php
@@ -0,0 +1,53 @@
+streetAndNumber = $address->cStrasse . ' ' . $address->cHausnummer;
+ $resource->postalCode = $address->cPLZ;
+ $resource->city = $address->cOrt;
+ $resource->country = $address->cLand;
+
+ if (
+ isset($address->cAdressZusatz)
+ && trim($address->cAdressZusatz) !== ''
+ ) {
+ $resource->streetAdditional = trim($address->cAdressZusatz);
+ }
+
+ // Validity-Check
+ // TODO: Check for valid Country Code?
+ // TODO: Check PostalCode requirement Country?
+ if (!$resource->streetAndNumber || !$resource->city || !$resource->country) {
+ throw ResourceValidityException::trigger(ResourceValidityException::ERROR_REQUIRED, ['streetAndNumber', 'city', 'country'], $resource);
+ }
+
+ return $resource;
+ }
+}
diff --git a/version/206/class/Checkout/Payment/Amount.php b/version/206/class/Checkout/Payment/Amount.php
new file mode 100644
index 0000000..c11c64c
--- /dev/null
+++ b/version/206/class/Checkout/Payment/Amount.php
@@ -0,0 +1,75 @@
+ true [5 Rappen Rounding])
+ * @return Amount
+ */
+ public static function factory($value, $currency = null, $useRounding = false)
+ {
+ if (!$currency) {
+ $currency = static::FallbackCurrency()->cISO;
+ }
+
+ $resource = new static();
+
+ $resource->currency = $currency;
+ //$resource->value = number_format(round($useRounding ? $resource->round($value * (float)$currency->fFaktor) : $value * (float)$currency->fFaktor, 2), 2, '.', '');
+ $resource->value = number_format(round($useRounding ? $resource->round($value) : $value, 2), 2, '.', '');
+
+ // Validity Check
+ // TODO: Check ISO Code?
+ // TODO: Check Value
+ if (!$resource->currency || !$resource->value) {
+ throw ResourceValidityException::trigger(ResourceValidityException::ERROR_REQUIRED, ['currency', 'value'], $resource);
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @return stdClass
+ */
+ public static function FallbackCurrency()
+ {
+ return isset($_SESSION['Waehrung']) ? $_SESSION['Waehrung'] : Shop::DB()->select('twaehrung', 'cStandard', 'Y');
+ }
+
+ /**
+ * Check if 5 Rappen rounding is necessary
+ *
+ * @param mixed $value
+ * @return float
+ */
+ protected function round($value)
+ {
+ $conf = Shop::getSettings([CONF_KAUFABWICKLUNG]);
+ if (isset($conf['kaufabwicklung']['bestellabschluss_runden5']) && (int)$conf['kaufabwicklung']['bestellabschluss_runden5'] === 1) {
+ $value = round($value * 20) / 20;
+ }
+
+ return $value;
+ }
+}
diff --git a/version/206/class/Checkout/PaymentCheckout.php b/version/206/class/Checkout/PaymentCheckout.php
new file mode 100644
index 0000000..568be4e
--- /dev/null
+++ b/version/206/class/Checkout/PaymentCheckout.php
@@ -0,0 +1,235 @@
+getMollie()->isCancelable) {
+ throw new RuntimeException('Zahlung kann nicht abgebrochen werden.');
+ }
+ $payment = $checkout->API()->Client()->payments->cancel($checkout->getMollie()->id);
+ $checkout->Log('Zahlung wurde manuell abgebrochen.');
+
+ return $payment;
+ }
+
+ /**
+ * @param false $force
+ * @return null|Payment
+ */
+ public function getMollie($force = false)
+ {
+ if ($force || (!$this->getPayment() && $this->getModel()->kID)) {
+ try {
+ $this->setPayment($this->API()->Client()->payments->get($this->getModel()->kID, ['embed' => 'refunds']));
+ } catch (Exception $e) {
+ throw new RuntimeException('Mollie-Payment konnte nicht geladen werden: ' . $e->getMessage());
+ }
+ }
+
+ return $this->getPayment();
+ }
+
+ /**
+ * @return null|Payment
+ */
+ public function getPayment()
+ {
+ return $this->_payment;
+ }
+
+ /**
+ * @param $payment
+ * @return $this
+ */
+ public function setPayment($payment)
+ {
+ $this->_payment = $payment;
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $force
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @return string
+ */
+ public function cancelOrRefund($force = false)
+ {
+ if (!$this->getMollie()) {
+ throw new RuntimeException('Mollie-Order konnte nicht geladen werden: ' . $this->getModel()->kID);
+ }
+ if ($force || (int)$this->getBestellung()->cStatus === BESTELLUNG_STATUS_STORNO) {
+ if ($this->getMollie()->isCancelable) {
+ $res = $this->API()->Client()->payments->cancel($this->getMollie()->id);
+ $result = 'Payment cancelled, Status: ' . $res->status;
+ } else {
+ $res = $this->API()->Client()->payments->refund($this->getMollie(), ['amount' => $this->getMollie()->amount]);
+ $result = 'Payment Refund initiiert, Status: ' . $res->status;
+ }
+ $this->PaymentMethod()->Log('PaymentCheckout::cancelOrRefund: ' . $result, $this->LogData());
+
+ return $result;
+ }
+
+ throw new RuntimeException('Bestellung ist derzeit nicht storniert, Status: ' . $this->getBestellung()->cStatus);
+ }
+
+ /**
+ * @param array $paymentOptions
+ * @return Payment
+ */
+ public function create(array $paymentOptions = [])
+ {
+ if ($this->getModel()->kID) {
+ try {
+ $this->setPayment($this->API()->Client()->payments->get($this->getModel()->kID));
+ if ($this->getPayment()->status === PaymentStatus::STATUS_PAID) {
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errAlreadyPaid']);
+ }
+ if ($this->getPayment()->status === PaymentStatus::STATUS_OPEN) {
+ $this->updateModel()->saveModel();
+
+ return $this->getPayment();
+ }
+ } catch (RuntimeException $e) {
+ //$this->Log(sprintf("PaymentCheckout::create: Letzte Transaktion '%s' konnte nicht geladen werden: %s", $this->getModel()->kID, $e->getMessage()), LOGLEVEL_ERROR);
+ throw $e;
+ } catch (Exception $e) {
+ $this->Log(sprintf("PaymentCheckout::create: Letzte Transaktion '%s' konnte nicht geladen werden: %s, versuche neue zu erstellen.", $this->getModel()->kID, $e->getMessage()), LOGLEVEL_ERROR);
+ }
+ }
+
+ try {
+ $req = $this->loadRequest($paymentOptions)->jsonSerialize();
+ $this->setPayment($this->API()->Client()->payments->create($req));
+ $this->Log(sprintf("Payment für '%s' wurde erfolgreich angelegt: %s", $this->getBestellung()->cBestellNr, $this->getPayment()->id));
+ $this->updateModel()->saveModel();
+
+ return $this->getPayment();
+ } catch (Exception $e) {
+ $this->Log(sprintf("PaymentCheckout::create: Neue Transaktion für '%s' konnte nicht erstellt werden: %s.", $this->getBestellung()->cBestellNr, $e->getMessage()), LOGLEVEL_ERROR);
+
+ throw new RuntimeException(sprintf('Mollie-Payment \'%s\' konnte nicht geladen werden: %s', $this->getBestellung()->cBestellNr, $e->getMessage()));
+ }
+ }
+
+ /**
+ * @param array $options
+ * @return $this|PaymentCheckout
+ */
+ public function loadRequest(&$options = [])
+ {
+ parent::loadRequest($options);
+
+ foreach ($options as $key => $value) {
+ $this->$key = $value;
+ }
+
+
+ $this->description = $this->getDescription();
+
+ return $this;
+ }
+
+
+
+ /**
+ * @return null|object
+ */
+ public function getIncomingPayment()
+ {
+ if (!$this->getMollie()) {
+ return null;
+ }
+
+ if (in_array($this->getMollie()->status, [PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PAID], true)) {
+ $data = [];
+ $data['fBetrag'] = (float)$this->getMollie()->amount->value;
+ $data['cISO'] = $this->getMollie()->amount->currency;
+ $data['cZahler'] = $this->getMollie()->details->paypalPayerId ?: $this->getMollie()->customerId;
+ $data['cHinweis'] = $this->getMollie()->details->paypalReference ?: $this->getMollie()->id;
+ if (isset($this->getMollie()->details, $this->getMollie()->details->paypalFee)) {
+ $data['fZahlungsgebuehr'] = $this->getMollie()->details->paypalFee->value;
+ }
+
+ return (object)$data;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Payment $model
+ * @return $this|AbstractCheckout
+ */
+ protected function setMollie($model)
+ {
+ if ($model instanceof Payment) {
+ $this->setPayment($model);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function updateOrderNumber()
+ {
+ try {
+ if ($this->getMollie()) {
+ $this->getMollie()->description = $this->getDescription();
+ $this->getMollie()->webhookUrl = Shop::getURL() . '/?mollie=1';
+ $this->getMollie()->update();
+ }
+ } catch (Exception $e) {
+ $this->Log('OrderCheckout::updateOrderNumber:' . $e->getMessage(), LOGLEVEL_ERROR);
+ }
+
+ return $this;
+ }
+}
diff --git a/version/206/class/ExclusiveLock.php b/version/206/class/ExclusiveLock.php
new file mode 100644
index 0000000..c7b834d
--- /dev/null
+++ b/version/206/class/ExclusiveLock.php
@@ -0,0 +1,70 @@
+key = $key;
+ $this->path = rtrim(realpath($path), '/') . '/';
+ if (!is_dir($path) || !is_writable($path)) {
+ throw new RuntimeException("Lock Path '$path' doesn't exist, or is not writable!");
+ }
+ //create a new resource or get exisitng with same key
+ $this->file = fopen($this->path . "$key.lockfile", 'wb+');
+ }
+
+
+ public function __destruct()
+ {
+ if ($this->own === true) {
+ $this->unlock();
+ }
+ }
+
+ /** @noinspection ForgottenDebugOutputInspection */
+ public function unlock()
+ {
+ $key = $this->key;
+ if ($this->own === true) {
+ if (!flock($this->file, LOCK_UN)) { //failed
+ error_log("ExclusiveLock::lock FAILED to release lock [$key]");
+
+ return false;
+ }
+ fwrite($this->file, 'Unlocked - ' . microtime(true) . "\n");
+ fflush($this->file);
+ $this->own = false;
+ } else {
+ error_log("ExclusiveLock::unlock called on [$key] but its not acquired by caller");
+ }
+
+ return true; // success
+ }
+
+ public function lock()
+ {
+ if (!flock($this->file, LOCK_EX | LOCK_NB)) { //failed
+ return false;
+ }
+ fwrite($this->file, 'Locked - ' . microtime(true) . "\n");
+ fflush($this->file);
+
+ $this->own = true;
+
+ return true; // success
+ }
+}
diff --git a/version/206/class/Helper.php b/version/206/class/Helper.php
new file mode 100644
index 0000000..b03874f
--- /dev/null
+++ b/version/206/class/Helper.php
@@ -0,0 +1,363 @@
+short_url != '' ? $release->short_url : $release->full_url;
+ $filename = basename($release->full_url);
+ $tmpDir = PFAD_ROOT . PFAD_COMPILEDIR;
+ $pluginsDir = PFAD_ROOT . PFAD_PLUGIN;
+
+ // 1. PRE-CHECKS
+ if (file_exists($pluginsDir . self::oPlugin()->cVerzeichnis . '/.git') && is_dir($pluginsDir . self::oPlugin()->cVerzeichnis . '/.git')) {
+ throw new Exception('Pluginordner enthält ein GIT Repository, kein Update möglich!');
+ }
+
+ if (!function_exists('curl_exec')) {
+ throw new Exception('cURL ist nicht verfügbar!!');
+ }
+ if (!is_writable($tmpDir)) {
+ throw new Exception("Temporäres Verzeichnis_'{$tmpDir}' ist nicht beschreibbar!");
+ }
+ if (!is_writable($pluginsDir . self::oPlugin()->cVerzeichnis)) {
+ throw new Exception("Plugin Verzeichnis_'" . $pluginsDir . self::oPlugin()->cVerzeichnis . "' ist nicht beschreibbar!");
+ }
+ if (file_exists($tmpDir . $filename) && !unlink($tmpDir . $filename)) {
+ throw new Exception("Temporäre Datei '" . $tmpDir . $filename . "' konnte nicht gelöscht werden!");
+ }
+
+ // 2. DOWNLOAD
+ $fp = fopen($tmpDir . $filename, 'w+');
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 50);
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_exec($ch);
+ $info = curl_getinfo($ch);
+ curl_close($ch);
+ fclose($fp);
+ if ($info['http_code'] !== 200) {
+ throw new Exception("Unerwarteter Status Code '" . $info['http_code'] . "'!");
+ }
+ if ($info['download_content_length'] <= 0) {
+ throw new Exception("Unerwartete Downloadgröße '" . $info['download_content_length'] . "'!");
+ }
+
+ // 3. UNZIP
+ require_once PFAD_ROOT . PFAD_PCLZIP . 'pclzip.lib.php';
+ $zip = new PclZip($tmpDir . $filename);
+ $content = $zip->listContent();
+
+ if (!is_array($content) || !isset($content[0]['filename']) || strpos($content[0]['filename'], '.') !== false) {
+ throw new Exception('Das Zip-Archiv ist leider ungültig!');
+ }
+ $unzipPath = PFAD_ROOT . PFAD_PLUGIN;
+ $res = $zip->extract(PCLZIP_OPT_PATH, $unzipPath, PCLZIP_OPT_REPLACE_NEWER);
+ if ($res !== 0) {
+ header('Location: ' . Shop::getURL() . DIRECTORY_SEPARATOR . PFAD_ADMIN . 'pluginverwaltung.php', true);
+ } else {
+ throw new Exception('Entpacken fehlgeschlagen: ' . $zip->errorCode());
+ }
+ }
+
+ /**
+ * @param bool $force
+ * @throws Exception
+ * @return mixed
+ */
+ public static function getLatestRelease($force = false)
+ {
+ $lastCheck = (int)self::getSetting(__NAMESPACE__ . '_upd');
+ $lastRelease = file_exists(PFAD_ROOT . PFAD_COMPILEDIR . __NAMESPACE__ . '_upd') ? file_get_contents(PFAD_ROOT . PFAD_COMPILEDIR . __NAMESPACE__ . '_upd') : false;
+ if ($force || !$lastCheck || !$lastRelease || ($lastCheck + 12 * 60 * 60) < time()) {
+ $curl = curl_init('https://api.dash.bar/release/' . __NAMESPACE__);
+ @curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
+ @curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+ @curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ @curl_setopt($curl, CURLOPT_HEADER, 0);
+ @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+ @curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+
+ $data = curl_exec($curl);
+ $statusCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ @curl_close($curl);
+ if ($statusCode !== 200) {
+ throw new Exception(__NAMESPACE__ . ': Could not fetch release info: ' . $statusCode);
+ }
+ $json = json_decode($data);
+ if (json_last_error() || $json->status != 'ok') {
+ throw new Exception(__NAMESPACE__ . ': Could not decode release info: ' . $data);
+ }
+ self::setSetting(__NAMESPACE__ . '_upd', time());
+ file_put_contents(PFAD_ROOT . PFAD_COMPILEDIR . __NAMESPACE__ . '_upd', json_encode($json->data));
+
+ return $json->data;
+ }
+
+ return json_decode($lastRelease);
+ }
+
+ /**
+ * Register PSR-4 autoloader
+ * Licence-Check
+ * @return bool
+ */
+ public static function init()
+ {
+ ini_set('xdebug.default_enable', defined('WS_XDEBUG_ENABLED'));
+
+ return self::autoload();
+ }
+
+ /**
+ * @var stdClass[]
+ */
+ public static $alerts = [];
+
+ /**
+ * Usage:
+ *
+ * Helper::addAlert('Success Message', 'success', 'namespace');
+ *
+ * @param $content
+ * @param $type
+ * @param $namespace
+ */
+ public static function addAlert($content, $type, $namespace)
+ {
+ if (!array_key_exists($namespace, self::$alerts)) {
+ self::$alerts[$namespace] = new stdClass();
+ }
+
+ self::$alerts[$namespace]->{$type . '_' . microtime(true)} = $content;
+ }
+
+ /**
+ * Usage in Smarty:
+ *
+ * {ws_mollie\Helper::showAlerts('namespace')}
+ *
+ * @param $namespace
+ * @throws SmartyException
+ * @return string
+ */
+ public static function showAlerts($namespace)
+ {
+ if (array_key_exists($namespace, self::$alerts) && file_exists(self::oPlugin()->cAdminmenuPfad . '../tpl/_alerts.tpl')) {
+ Shop::Smarty()->assign('alerts', self::$alerts[$namespace]);
+
+ return Shop::Smarty()->fetch(self::oPlugin()->cAdminmenuPfad . '../tpl/_alerts.tpl');
+ }
+
+ return '';
+ }
+
+ /**
+ * Sets a Plugin Setting and saves it to the DB
+ *
+ * @param $name
+ * @param $value
+ * @return int
+ */
+ public static function setSetting($name, $value)
+ {
+ $setting = new stdClass();
+ $setting->kPlugin = self::oPlugin()->kPlugin;
+ $setting->cName = $name;
+ $setting->cWert = $value;
+
+ if (array_key_exists($name, self::oPlugin()->oPluginEinstellungAssoc_arr)) {
+ $return = Shop::DB()->updateRow('tplugineinstellungen', ['kPlugin', 'cName'], [$setting->kPlugin, $setting->cName], $setting);
+ } else {
+ $return = Shop::DB()->insertRow('tplugineinstellungen', $setting);
+ }
+ self::oPlugin()->oPluginEinstellungAssoc_arr[$name] = $value;
+ self::oPlugin(true); // invalidate cache
+
+ return $return;
+ }
+
+ /**
+ * Get Plugin Object
+ *
+ * @param bool $force disable Cache
+ * @return null|Plugin
+ */
+ public static function oPlugin($force = false)
+ {
+ if ($force === true) {
+ self::$oPlugin = new Plugin(self::oPlugin()->kPlugin, true);
+ } elseif (null === self::$oPlugin) {
+ self::$oPlugin = Plugin::getPluginById(__NAMESPACE__);
+ }
+
+ return self::$oPlugin;
+ }
+
+ /**
+ * get a Plugin setting
+ *
+ * @param $name
+ * @return null|mixed
+ */
+ public static function getSetting($name)
+ {
+ if (array_key_exists($name, self::oPlugin()->oPluginEinstellungAssoc_arr ?: [])) {
+ return self::oPlugin()->oPluginEinstellungAssoc_arr[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get Domain frpm URL_SHOP without www.
+ *
+ * @param string $url
+ * @return string
+ */
+ public static function getDomain($url = URL_SHOP)
+ {
+ $matches = [];
+ @preg_match("/^((http(s)?):\/\/)?(www\.)?([a-zA-Z0-9-\.]+)(\/.*)?$/i", $url, $matches);
+
+ return strtolower(isset($matches[5]) ? $matches[5] : $url);
+ }
+
+ /**
+ * @param bool $e
+ * @return mixed
+ */
+ public static function getMasterMail($e = false)
+ {
+ $settings = Shop::getSettings([CONF_EMAILS]);
+ $mail = trim($settings['emails']['email_master_absender']);
+ if ($e === true && $mail != '') {
+ $mail = base64_encode($mail);
+ $eMail = '';
+ foreach (str_split($mail) as $c) {
+ $eMail .= chr(ord($c) ^ 0x00100110);
+ }
+
+ return base64_encode($eMail);
+ }
+
+ return $mail;
+ }
+
+ /**
+ * @param Exception $exc
+ * @param bool $trace
+ * @return void
+ */
+ public static function logExc(Exception $exc, $trace = true)
+ {
+ Jtllog::writeLog(__NAMESPACE__ . ': ' . $exc->getMessage() . ($trace ? ' - ' . $exc->getTraceAsString() : ''));
+ }
+
+ /**
+ * Checks if admin session is loaded
+ *
+ * @return bool
+ */
+ public static function isAdminBackend()
+ {
+ return session_name() === 'eSIdAdm';
+ }
+
+ /**
+ * Returns kAdminmenu ID for given Title, used for Tabswitching
+ *
+ * @param $name string CustomLink Title
+ * @return int
+ */
+ public static function getAdminmenu($name)
+ {
+ $kPluginAdminMenu = 0;
+ foreach (self::oPlugin()->oPluginAdminMenu_arr as $adminmenu) {
+ if (strtolower($adminmenu->cName) == strtolower($name)) {
+ $kPluginAdminMenu = $adminmenu->kPluginAdminMenu;
+
+ break;
+ }
+ }
+
+ return $kPluginAdminMenu;
+ }
+ }
+ }
+
+}
diff --git a/version/206/class/Hook/AbstractHook.php b/version/206/class/Hook/AbstractHook.php
new file mode 100644
index 0000000..561e401
--- /dev/null
+++ b/version/206/class/Hook/AbstractHook.php
@@ -0,0 +1,15 @@
+kPlugin))
+ && array_key_exists($key, $_SESSION) && !array_key_exists('Zahlungsart', $_SESSION)
+ ) {
+ unset($_SESSION[$key]);
+ }
+
+ if (!array_key_exists('ws_mollie_applepay_available', $_SESSION)) {
+ // TODO DOKU
+ if (defined('MOLLIE_APPLEPAY_TPL') && MOLLIE_APPLEPAY_TPL) {
+ Shop::Smarty()->assign('applePayCheckURL', json_encode(self::Plugin()->cFrontendPfadURLSSL . 'applepay.php'));
+ pq('body')->append(Shop::Smarty()->fetch(self::Plugin()->cFrontendPfad . 'tpl/applepay.tpl'));
+ } else {
+ $checkUrl = self::Plugin()->cFrontendPfadURLSSL . 'applepay.php';
+ pq('head')->append("");
+ }
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public static function isAvailable()
+ {
+ if (array_key_exists('ws_mollie_applepay_available', $_SESSION)) {
+ return $_SESSION['ws_mollie_applepay_available'];
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $status bool
+ */
+ public static function setAvailable($status)
+ {
+ $_SESSION['ws_mollie_applepay_available'] = $status;
+ }
+}
diff --git a/version/206/class/Hook/Checkbox.php b/version/206/class/Hook/Checkbox.php
new file mode 100644
index 0000000..87c4334
--- /dev/null
+++ b/version/206/class/Hook/Checkbox.php
@@ -0,0 +1,60 @@
+cModulId, 'kPlugin_' . self::Plugin()->kPlugin . '_') === false) {
+ return;
+ }
+
+ if (array_key_exists('nAnzeigeOrt', $args_arr) && $args_arr['nAnzeigeOrt'] === CHECKBOX_ORT_BESTELLABSCHLUSS && Session::getInstance()->Customer()->nRegistriert) {
+ $mCustomer = Customer::fromID(Session::getInstance()->Customer()->kKunde, 'kKunde');
+
+ if ($mCustomer->customerId) {
+ return;
+ }
+
+ $checkbox = new \CheckBox();
+ $checkbox->kLink = 0;
+ $checkbox->kCheckBox = -1;
+ $checkbox->kCheckBoxFunktion = 0;
+ $checkbox->cName = 'MOLLIE SAVE CUSTOMER';
+ $checkbox->cKundengruppe = ';1;';
+ $checkbox->cAnzeigeOrt = ';2;';
+ $checkbox->nAktiv = 1;
+ $checkbox->nPflicht = 0;
+ $checkbox->nLogging = 0;
+ $checkbox->nSort = 999;
+ $checkbox->dErstellt = date('Y-m-d H:i:s');
+ $checkbox->oCheckBoxSprache_arr = [];
+
+ $langs = gibAlleSprachen();
+ foreach ($langs as $lang) {
+ $checkbox->oCheckBoxSprache_arr[$lang->kSprache] = (object)[
+ 'cText' => self::Plugin()->oPluginSprachvariableAssoc_arr['checkboxText'],
+ 'cBeschreibung' => self::Plugin()->oPluginSprachvariableAssoc_arr['checkboxDescr'],
+ 'kSprache' => $lang->kSprache,
+ 'kCheckbox' => -1
+ ];
+ }
+
+ $checkbox->kKundengruppe_arr = [Session::getInstance()->Customer()->kKundengruppe];
+ $checkbox->kAnzeigeOrt_arr = [CHECKBOX_ORT_BESTELLABSCHLUSS];
+ $checkbox->cID = 'mollie_create_customer';
+ $checkbox->cLink = '';
+
+ $args_arr['oCheckBox_arr'][] = $checkbox;
+ }
+ }
+}
diff --git a/version/206/class/Hook/Queue.php b/version/206/class/Hook/Queue.php
new file mode 100644
index 0000000..06149b7
--- /dev/null
+++ b/version/206/class/Hook/Queue.php
@@ -0,0 +1,140 @@
+nWaehrendBestellung === 0
+ && $args_arr['oBestellung']->fGesamtsumme > 0
+ && self::Plugin()->oPluginEinstellungAssoc_arr['onlyPaid'] === 'Y'
+ && AbstractCheckout::isMollie((int)$args_arr['oBestellung']->kZahlungsart, true)
+ ) {
+ $args_arr['oBestellung']->cAbgeholt = 'Y';
+ Jtllog::writeLog('Switch cAbgeholt for kBestellung: ' . print_r($args_arr['oBestellung']->kBestellung, 1), JTLLOG_LEVEL_NOTICE);
+ }
+ }
+
+ /**
+ * @param array $args_arr
+ */
+ public static function xmlBestellStatus(array $args_arr)
+ {
+ if (AbstractCheckout::isMollie((int)$args_arr['oBestellung']->kBestellung)) {
+ self::saveToQueue(HOOK_BESTELLUNGEN_XML_BESTELLSTATUS . ':' . (int)$args_arr['oBestellung']->kBestellung, [
+ 'kBestellung' => $args_arr['oBestellung']->kBestellung,
+ 'status' => (int)$args_arr['status']
+ ]);
+ }
+ }
+
+ /**
+ * @param $hook
+ * @param $args_arr
+ * @param string $type
+ * @return bool
+ */
+ public static function saveToQueue($hook, $args_arr, $type = 'hook')
+ {
+ $mQueue = new QueueModel();
+ $mQueue->cType = $type . ':' . $hook;
+ $mQueue->cData = serialize($args_arr);
+
+ try {
+ return $mQueue->save();
+ } catch (Exception $e) {
+ Jtllog::writeLog('mollie::saveToQueue: ' . $e->getMessage() . ' - ' . print_r($args_arr, 1));
+
+ return false;
+ }
+ }
+
+ /**
+ * @param array $args_arr
+ */
+ public static function xmlBearbeiteStorno(array $args_arr)
+ {
+ if (AbstractCheckout::isMollie((int)$args_arr['oBestellung']->kBestellung)) {
+ self::saveToQueue(HOOK_BESTELLUNGEN_XML_BEARBEITESTORNO . ':' . $args_arr['oBestellung']->kBestellung, ['kBestellung' => $args_arr['oBestellung']->kBestellung]);
+ }
+ }
+
+ /**
+ *
+ */
+ public static function headPostGet()
+ {
+ if (array_key_exists('mollie', $_REQUEST) && (int)$_REQUEST['mollie'] === 1 && array_key_exists('id', $_REQUEST)) {
+ if (array_key_exists('hash', $_REQUEST) && $hash = trim(StringHandler::htmlentities(StringHandler::filterXSS($_REQUEST['hash'])), '_')) {
+ AbstractCheckout::finalizeOrder($hash, $_REQUEST['id'], array_key_exists('test', $_REQUEST));
+ } else {
+ self::saveToQueue($_REQUEST['id'], $_REQUEST['id'], 'webhook');
+ }
+ exit();
+ }
+ if (array_key_exists('m_pay', $_REQUEST)) {
+ try {
+ $raw = Shop::DB()->executeQueryPrepared('SELECT kID FROM `xplugin_ws_mollie_payments` WHERE dReminder IS NOT NULL AND MD5(CONCAT(kID, "-", kBestellung)) = :md5', [
+ ':md5' => $_REQUEST['m_pay']
+ ], 1);
+
+ if (!$raw) {
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errOrderNotFound']);
+ }
+
+ if (strpos($raw->cOrderId, 'tr_') === 0) {
+ $checkout = PaymentCheckout::fromID($raw->kID);
+ } else {
+ $checkout = OrderCheckout::fromID($raw->kID);
+ }
+ $checkout->getMollie(true);
+ $checkout->updateModel()->saveModel();
+
+ if (
+ ($checkout->getBestellung()->dBezahltDatum !== null && $checkout->getBestellung()->dBezahltDatum !== '0000-00-00')
+ || in_array($checkout->getModel()->cStatus, ['completed', 'paid', 'authorized', 'pending'])
+ ) {
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errAlreadyPaid']);
+ }
+
+ $options = [];
+ if (self::Plugin()->oPluginEinstellungAssoc_arr['resetMethod'] !== 'Y') {
+ $options['method'] = $checkout->getModel()->cMethod;
+ }
+
+ $mollie = $checkout->create($options); // Order::repayOrder($orderModel->getOrderId(), $options, $api);
+ $url = $mollie->getCheckoutUrl();
+
+ header('Location: ' . $url);
+ exit();
+ } catch (RuntimeException $e) {
+ // TODO Workaround?
+ //$alertHelper = Shop::Container()->getAlertService();
+ //$alertHelper->addAlert(Alert::TYPE_ERROR, $e->getMessage(), 'mollie_repay', ['dismissable' => true]);
+ } catch (Exception $e) {
+ Jtllog::writeLog('mollie:repay:error: ' . $e->getMessage() . "\n" . print_r($_REQUEST, 1));
+ }
+ }
+ }
+}
diff --git a/version/206/class/Model/AbstractModel.php b/version/206/class/Model/AbstractModel.php
new file mode 100644
index 0000000..38a4ab5
--- /dev/null
+++ b/version/206/class/Model/AbstractModel.php
@@ -0,0 +1,110 @@
+data = $data;
+ if (!$data) {
+ $this->new = true;
+ }
+ }
+
+ /**
+ * @param $id
+ * @param string $col
+ * @param false $failIfNotExists
+ * @return static
+ */
+ public static function fromID($id, $col = 'kID', $failIfNotExists = false)
+ {
+ if (
+ $payment = Shop::DB()->executeQueryPrepared(
+ 'SELECT * FROM ' . static::TABLE . " WHERE `$col` = :id",
+ [':id' => $id],
+ 1
+ )
+ ) {
+ return new static($payment);
+ }
+ if ($failIfNotExists) {
+ throw new RuntimeException(sprintf('Model %s in %s nicht gefunden!', $id, static::TABLE));
+ }
+
+ return new static();
+ }
+
+ /**
+ * @return null|mixed|stdClass
+ */
+ public function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function __get($name)
+ {
+ if (isset($this->data->$name)) {
+ return $this->data->$name;
+ }
+
+ return null;
+ }
+
+ public function __set($name, $value)
+ {
+ if (!$this->data) {
+ $this->data = new stdClass();
+ }
+ $this->data->$name = $value;
+ }
+
+ public function __isset($name)
+ {
+ return isset($this->data->$name);
+ }
+
+ /**
+ * @return bool
+ */
+ public function save()
+ {
+ if (!$this->data) {
+ throw new RuntimeException('No Data to save!');
+ }
+
+ if ($this->new) {
+ Shop::DB()->insertRow(static::TABLE, $this->data);
+ $this->new = false;
+
+ return true;
+ }
+ Shop::DB()->updateRow(static::TABLE, static::PRIMARY, $this->data->{static::PRIMARY}, $this->data);
+
+ return true;
+ }
+}
diff --git a/version/206/class/Model/Customer.php b/version/206/class/Model/Customer.php
new file mode 100644
index 0000000..3d02409
--- /dev/null
+++ b/version/206/class/Model/Customer.php
@@ -0,0 +1,21 @@
+dCreatedAt) {
+ $this->dCreatedAt = date('Y-m-d H:i:s');
+ }
+ if ($this->new) {
+ $this->dReminder = self::NULL;
+ }
+
+ return parent::save();
+ }
+}
diff --git a/version/206/class/Model/Queue.php b/version/206/class/Model/Queue.php
new file mode 100644
index 0000000..1509e50
--- /dev/null
+++ b/version/206/class/Model/Queue.php
@@ -0,0 +1,46 @@
+cResult = $result;
+ $this->dDone = $date ?: date('Y-m-d H:i:s');
+
+ return $this->save();
+ }
+
+ public function save()
+ {
+ if (!$this->dCreated) {
+ $this->dCreated = date('Y-m-d H:i:s');
+ }
+ $this->dModified = date('Y-m-d H:i:s');
+
+ return parent::save();
+ }
+}
diff --git a/version/206/class/Model/Shipment.php b/version/206/class/Model/Shipment.php
new file mode 100644
index 0000000..91d93be
--- /dev/null
+++ b/version/206/class/Model/Shipment.php
@@ -0,0 +1,29 @@
+executeQueryPrepared(
+ 'SELECT p.kBestellung, b.cStatus FROM xplugin_ws_mollie_payments p '
+ . 'JOIN tbestellung b ON b.kBestellung = p.kBestellung '
+ . "WHERE b.cAbgeholt = 'Y' AND NOT p.bSynced AND b.cStatus IN ('1', '2') AND p.dCreatedAt < NOW() - INTERVAL :d HOUR",
+ [':d' => $delay],
+ 2
+ );
+
+ foreach ($open as $o) {
+ try {
+ $checkout = AbstractCheckout::fromBestellung($o->kBestellung);
+ $pm = $checkout->PaymentMethod();
+ if ($pm::ALLOW_AUTO_STORNO && $pm::METHOD === $checkout->getMollie()->method) {
+ if ($checkout->getBestellung()->cAbgeholt === 'Y' && (bool)$checkout->getModel()->bSynced === false) {
+ if (!in_array($checkout->getMollie()->status, [OrderStatus::STATUS_PAID, OrderStatus::STATUS_COMPLETED, OrderStatus::STATUS_AUTHORIZED], true)) {
+ $checkout->storno();
+ } else {
+ $checkout->Log(sprintf('AutoStorno: Bestellung bezahlt? %s - Method: %s', $checkout->getMollie()->status, $checkout->getMollie()->method), LOGLEVEL_ERROR);
+ }
+ } else {
+ $checkout->Log('AutoStorno: bereits zur WAWI synchronisiert.', LOGLEVEL_ERROR);
+ }
+ }
+ } catch (Exception $e) {
+ Helper::logExc($e);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $limit
+ */
+ public static function run($limit = 10)
+ {
+ foreach (self::getOpen($limit) as $todo) {
+ if (!self::lock($todo)) {
+ Jtllog::writeLog(sprintf('%s already locked since %s', $todo->kId, $todo->bLock ?: 'just now'));
+
+ continue;
+ }
+
+ if ((list($type, $id) = explode(':', $todo->cType))) {
+ try {
+ switch ($type) {
+ case 'webhook':
+ self::handleWebhook($id, $todo);
+
+ break;
+
+ case 'hook':
+ self::handleHook((int)$id, $todo);
+
+ break;
+ }
+ } catch (Exception $e) {
+ Jtllog::writeLog($e->getMessage() . " ($type, $id)");
+ $todo->done("{$e->getMessage()}\n{$e->getFile()}:{$e->getLine()}\n{$e->getTraceAsString()}");
+ }
+ }
+
+ self::unlock($todo);
+ }
+ }
+
+ /**
+ * @param $limit
+ * @return Generator|QueueModel[]
+ * @noinspection PhpReturnDocTypeMismatchInspection
+ * @noinspection SqlResolve
+ */
+ private static function getOpen($limit)
+ {
+ if (!defined('MOLLIE_HOOK_DELAY')) {
+ define('MOLLIE_HOOK_DELAY', 3);
+ }
+ $open = Shop::DB()->executeQueryPrepared(sprintf("SELECT * FROM %s WHERE (dDone IS NULL OR dDone = '0000-00-00 00:00:00') AND `bLock` IS NULL AND (cType LIKE 'webhook:%%' OR (cType LIKE 'hook:%%') AND dCreated < DATE_SUB(NOW(), INTERVAL " . (int)MOLLIE_HOOK_DELAY . ' MINUTE)) ORDER BY dCreated DESC LIMIT 0, :LIMIT;', QueueModel::TABLE), [
+ ':LIMIT' => $limit
+ ], 2);
+
+ foreach ($open as $_raw) {
+ yield new QueueModel($_raw);
+ }
+ }
+
+ /**
+ * @param $todo
+ * @return bool
+ * @noinspection SqlResolve
+ */
+ protected static function lock($todo)
+ {
+ return $todo->kId && Shop::DB()->executeQueryPrepared(sprintf('UPDATE %s SET `bLock` = NOW() WHERE `bLock` IS NULL AND kId = :kId', QueueModel::TABLE), [
+ 'kId' => $todo->kId
+ ], 3) >= 1;
+ }
+
+ /**
+ * @param $id
+ * @param QueueModel $todo
+ * @throws Exception
+ * @return bool
+ */
+ protected static function handleWebhook($id, QueueModel $todo)
+ {
+ $checkout = AbstractCheckout::fromID($id);
+ if ($checkout->getBestellung()->kBestellung && $checkout->PaymentMethod()) {
+ $checkout->handleNotification();
+
+ return $todo->done('Status: ' . $checkout->getMollie()->status);
+ }
+
+ throw new RuntimeException("Bestellung oder Zahlungsart konnte nicht geladen werden: $id");
+ }
+
+ /**
+ * @param $hook
+ * @param QueueModel $todo
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @throws Exception
+ * @return bool
+ */
+ protected static function handleHook($hook, QueueModel $todo)
+ {
+ $data = unserialize($todo->cData);
+ if (array_key_exists('kBestellung', $data)) {
+ switch ($hook) {
+ case HOOK_BESTELLUNGEN_XML_BESTELLSTATUS:
+ if ((int)$data['kBestellung']) {
+ // TODO: #158 What happens when API requests fail?
+ $checkout = AbstractCheckout::fromBestellung($data['kBestellung']);
+
+ $status = array_key_exists('status', $data) ? (int)$data['status'] : 0;
+ $result = '';
+ if (!$status || $status < BESTELLUNG_STATUS_VERSANDT) {
+ return $todo->done("Bestellung noch nicht versendet: {$checkout->getBestellung()->cStatus}");
+ }
+ if (!count($checkout->getBestellung()->oLieferschein_arr)) {
+ $todo->dCreated = date('Y-m-d H:i:s', strtotime('+3 MINUTES'));
+ $todo->cResult = 'Noch keine Lieferscheine, delay...';
+
+ return $todo->save();
+ }
+
+ /** @var $method JTLMollie */
+ if (
+ (strpos($checkout->getModel()->kID, 'tr_') === false)
+ && $checkout->PaymentMethod()
+ && $checkout->getMollie()
+ ) {
+ /** @var OrderCheckout $checkout */
+ $checkout->handleNotification();
+ if ($checkout->getMollie()->status === OrderStatus::STATUS_COMPLETED) {
+ $result = 'Mollie Status already ' . $checkout->getMollie()->status;
+ } elseif ($checkout->getMollie()->isCreated() || $checkout->getMollie()->isPaid() || $checkout->getMollie()->isAuthorized() || $checkout->getMollie()->isShipping() || $checkout->getMollie()->isPending()) {
+ try {
+ if ($shipments = Shipment::syncBestellung($checkout)) {
+ foreach ($shipments as $shipment) {
+ if (is_string($shipment)) {
+ $checkout->Log("Shipping-Error: $shipment");
+ $result .= "Shipping-Error: $shipment;\n";
+ } else {
+ $checkout->Log("Order shipped: {$shipment->id}");
+ $result .= "Order shipped: $shipment->id;\n";
+ }
+ }
+ } else {
+ $result = 'No Shipments ready!';
+ }
+ } catch (RuntimeException $e) {
+ $result = $e->getMessage();
+ } catch (Exception $e) {
+ $result = $e->getMessage() . "\n" . $e->getFile() . ':' . $e->getLine() . "\n" . $e->getTraceAsString();
+ }
+ } else {
+ $result = sprintf('Unerwarteter Mollie Status "%s" für %s', $checkout->getMollie()->status, $checkout->getBestellung()->cBestellNr);
+ }
+ } else {
+ $result = 'Nothing to do.';
+ }
+ $checkout->Log('Queue::handleHook: ' . $result);
+ } else {
+ $result = 'kBestellung missing';
+ }
+
+ return $todo->done($result);
+
+ case HOOK_BESTELLUNGEN_XML_BEARBEITESTORNO:
+ if (self::Plugin()->oPluginEinstellungAssoc_arr['autoRefund'] !== 'Y') {
+ throw new RuntimeException('Auto-Refund disabled');
+ }
+
+ $checkout = AbstractCheckout::fromBestellung((int)$data['kBestellung']);
+
+ return $todo->done($checkout->cancelOrRefund());
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $todo
+ * @return bool
+ */
+ protected static function unlock($todo)
+ {
+ return $todo->kId && Shop::DB()->executeQueryPrepared(sprintf('UPDATE %s SET `bLock` = NULL WHERE kId = :kId OR bLock < DATE_SUB(NOW(), INTERVAL 15 MINUTE)', QueueModel::TABLE), [
+ 'kId' => $todo->kId
+ ], 3) >= 1;
+ }
+}
diff --git a/version/206/class/Shipment.php b/version/206/class/Shipment.php
new file mode 100644
index 0000000..a22a8d3
--- /dev/null
+++ b/version/206/class/Shipment.php
@@ -0,0 +1,325 @@
+kLieferschein = $kLieferschein;
+ if ($checkout) {
+ $this->checkout = $checkout;
+ }
+
+ if (!$this->getLieferschein() || !$this->getLieferschein()->getLieferschein()) {
+ throw new RuntimeException('Lieferschein konnte nicht geladen werden');
+ }
+
+ if (!count($this->getLieferschein()->oVersand_arr)) {
+ throw new RuntimeException('Kein Versand gefunden!');
+ }
+
+ if (!$this->getCheckout()->getBestellung()->oKunde->nRegistriert) {
+ $this->isGuest = true;
+ }
+ }
+
+ public function getLieferschein()
+ {
+ if (!$this->oLieferschein && $this->kLieferschein) {
+ $this->oLieferschein = new Lieferschein($this->kLieferschein);
+ }
+
+ return $this->oLieferschein;
+ }
+
+ /**
+ * @throws Exception
+ * @return OrderCheckout
+ */
+ public function getCheckout()
+ {
+ if (!$this->checkout) {
+ //TODO evtl. load by lieferschien
+ throw new Exception('Should not happen, but it did!');
+ }
+
+ return $this->checkout;
+ }
+
+ public static function syncBestellung(OrderCheckout $checkout)
+ {
+ $shipments = [];
+ if ($checkout->getBestellung()->kBestellung) {
+ $oKunde = $checkout->getBestellung()->oKunde ?: new Kunde($checkout->getBestellung()->kKunde);
+
+ $shippingActive = Helper::getSetting('shippingActive');
+ if ($shippingActive === 'N') {
+ throw new RuntimeException('Shipping deaktiviert');
+ }
+
+ if ($shippingActive === 'K' && !$oKunde->nRegistriert && (int)$checkout->getBestellung()->cStatus !== BESTELLUNG_STATUS_VERSANDT) {
+ throw new RuntimeException('Shipping für Gast-Bestellungen und Teilversand deaktiviert');
+ }
+
+ /** @var Lieferschein $oLieferschein */
+ foreach ($checkout->getBestellung()->oLieferschein_arr as $oLieferschein) {
+ try {
+ $shipment = new self($oLieferschein->getLieferschein(), $checkout);
+ $mode = self::Plugin()->oPluginEinstellungAssoc_arr['shippingMode'];
+ switch ($mode) {
+ case 'A':
+ // ship directly
+ if (!$shipment->send() && !$shipment->getShipment()) {
+ throw new RuntimeException('Shipment konnte nicht gespeichert werden.');
+ }
+ $shipments[] = $shipment->getShipment();
+
+ break;
+
+ case 'B':
+ // only ship if complete shipping
+ if ($oKunde->nRegistriert || (int)$checkout->getBestellung()->cStatus === BESTELLUNG_STATUS_VERSANDT) {
+ if (!$shipment->send() && !$shipment->getShipment()) {
+ throw new RuntimeException('Shipment konnte nicht gespeichert werden.');
+ }
+ $shipments[] = $shipment->getShipment();
+
+ break;
+ }
+
+ throw new RuntimeException('Gastbestellung noch nicht komplett versendet!');
+ }
+ } catch (RuntimeException $e) {
+ $shipments[] = $e->getMessage();
+ } catch (Exception $e) {
+ $shipments[] = $e->getMessage();
+ $checkout->Log("mollie: Shipment::syncBestellung (BestellNr. {$checkout->getBestellung()->cBestellNr}, Lieferschein: {$oLieferschein->getLieferscheinNr()}) - " . $e->getMessage(), LOGLEVEL_ERROR);
+ }
+ }
+ }
+
+ return $shipments;
+ }
+
+ /**
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @throws Exception
+ * @return bool
+ */
+ public function send()
+ {
+ if ($this->getShipment()) {
+ throw new RuntimeException('Lieferschien bereits an Mollie übertragen: ' . $this->getShipment()->id);
+ }
+
+ if ($this->getCheckout()->getMollie(true)->status === OrderStatus::STATUS_COMPLETED) {
+ throw new RuntimeException('Bestellung bei Mollie bereits abgeschlossen!');
+ }
+
+ $api = $this->getCheckout()->API()->Client();
+ $this->shipment = $api->shipments->createForId($this->checkout->getModel()->kID, $this->loadRequest()->jsonSerialize());
+
+ return $this->updateModel()->saveModel();
+ }
+
+ /**
+ * @param false $force
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @throws Exception
+ * @return BaseResource|\Mollie\Api\Resources\Shipment
+ */
+ public function getShipment($force = false)
+ {
+ if (($force || !$this->shipment) && $this->getModel() && $this->getModel()->cShipmentId) {
+ $this->shipment = $this->getCheckout()->API()->Client()->shipments->getForId($this->getModel()->cOrderId, $this->getModel()->cShipmentId);
+ }
+
+ return $this->shipment;
+ }
+
+ /**
+ * @throws Exception
+ * @return ShipmentModel
+ */
+ public function getModel()
+ {
+ if (!$this->model && $this->kLieferschein) {
+ $this->model = ShipmentModel::fromID($this->kLieferschein, 'kLieferschein');
+
+ if (!$this->model->dCreated) {
+ $this->model->dCreated = date('Y-m-d H:i:s');
+ }
+ $this->updateModel();
+ }
+
+ return $this->model;
+ }
+
+ /**
+ * @throws Exception
+ * @return $this
+ */
+ public function updateModel()
+ {
+ $this->getModel()->kLieferschein = $this->kLieferschein;
+ if ($this->getCheckout()) {
+ $this->getModel()->cOrderId = $this->getCheckout()->getModel()->kID;
+ $this->getModel()->kBestellung = $this->getCheckout()->getModel()->kBestellung;
+ }
+ if ($this->getShipment()) {
+ $this->getModel()->cShipmentId = $this->getShipment()->id;
+ $this->getModel()->cUrl = $this->getShipment()->getTrackingUrl() ?: '';
+ }
+ if (isset($this->tracking)) {
+ $this->getModel()->cCarrier = $this->tracking['carrier'] ?: '';
+ $this->getModel()->cCode = $this->tracking['code'] ?: '';
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @throws Exception
+ * @return $this
+ */
+ public function loadRequest(&$options = [])
+ {
+ /** @var Versand $oVersand */
+ $oVersand = $this->getLieferschein()->oVersand_arr[0];
+ if ($oVersand->getIdentCode() && $oVersand->getLogistik()) {
+ $tracking = [
+ 'carrier' => utf8_encode($oVersand->getLogistik()),
+ 'code' => utf8_encode($oVersand->getIdentCode()),
+ ];
+ if ($oVersand->getLogistikVarUrl()) {
+ $tracking['url'] = utf8_encode($oVersand->getLogistikURL());
+ }
+ $this->tracking = $tracking;
+ }
+
+ // TODO: Wenn alle Lieferschiene in der WAWI erstellt wurden, aber nicht im Shop, kommt status 4.
+ if ($this->isGuest || (int)$this->getCheckout()->getBestellung()->cStatus === BESTELLUNG_STATUS_VERSANDT) {
+ $this->lines = [];
+ } else {
+ $this->lines = $this->getOrderLines();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @throws Exception
+ * @return array
+ */
+ protected function getOrderLines()
+ {
+ $lines = [];
+
+ if (!count($this->getLieferschein()->oLieferscheinPos_arr)) {
+ return $lines;
+ }
+
+ // Bei Stücklisten, sonst gibt es mehrere OrderLines für die selbe ID
+ $shippedOrderLines = [];
+
+ /** @var Lieferscheinpos $oLieferscheinPos */
+ foreach ($this->getLieferschein()->oLieferscheinPos_arr as $oLieferscheinPos) {
+ $wkpos = Shop::DB()->executeQueryPrepared('SELECT * FROM twarenkorbpos WHERE kBestellpos = :kBestellpos', [
+ ':kBestellpos' => $oLieferscheinPos->getBestellPos()
+ ], 1);
+
+ /** @var OrderLine $orderLine */
+ foreach ($this->getCheckout()->getMollie()->lines as $orderLine) {
+ if ($orderLine->sku === $wkpos->cArtNr && !in_array($orderLine->id, $shippedOrderLines, true)) {
+ if ($quantity = min($oLieferscheinPos->getAnzahl(), $orderLine->shippableQuantity)) {
+ $lines[] = [
+ 'id' => $orderLine->id,
+ 'quantity' => $quantity
+ ];
+ }
+ $shippedOrderLines[] = $orderLine->id;
+
+ break;
+ }
+ }
+ }
+
+ return $lines;
+ }
+
+ /**
+ * @throws Exception
+ * @return bool
+ */
+ public function saveModel()
+ {
+ return $this->getModel()->save();
+ }
+}
diff --git a/version/206/class/Traits/Jsonable.php b/version/206/class/Traits/Jsonable.php
new file mode 100644
index 0000000..8ba9b19
--- /dev/null
+++ b/version/206/class/Traits/Jsonable.php
@@ -0,0 +1,23 @@
+jsonSerialize();
+ }
+
+ public function jsonSerialize()
+ {
+ return array_filter(get_object_vars($this), static function ($value) {
+ return !($value === null || (is_string($value) && $value === ''));
+ });
+ }
+}
diff --git a/version/206/class/Traits/Plugin.php b/version/206/class/Traits/Plugin.php
new file mode 100644
index 0000000..08069cd
--- /dev/null
+++ b/version/206/class/Traits/Plugin.php
@@ -0,0 +1,30 @@
+requestData) === false) {
+ throw new \RuntimeException(sprintf("JSON Encode Error: %s\n%s", json_last_error_msg(), print_r($this->requestData, 1)));
+ }
+
+ return $this->requestData;
+ }
+
+ public function __get($name)
+ {
+ if (!$this->requestData) {
+ $this->loadRequest();
+ }
+
+ return is_string($this->requestData[$name]) ? utf8_decode($this->requestData[$name]) : $this->requestData[$name];
+ }
+
+ public function __set($name, $value)
+ {
+ if (!$this->requestData) {
+ $this->requestData = [];
+ }
+
+ $this->requestData[$name] = is_string($value) ? utf8_encode($value) : $value;
+
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @return $this
+ */
+ public function loadRequest(&$options = [])
+ {
+ return $this;
+ }
+
+
+ public function __serialize()
+ {
+ return $this->requestData ?: [];
+ }
+
+ public function __isset($name)
+ {
+ return $this->requestData[$name] !== null;
+ }
+}
diff --git a/version/206/frontend/.htaccess b/version/206/frontend/.htaccess
new file mode 100644
index 0000000..df6c074
--- /dev/null
+++ b/version/206/frontend/.htaccess
@@ -0,0 +1,9 @@
+
+
+ Require all granted
+
+
+ Order Allow,Deny
+ Allow from all
+
+
diff --git a/version/206/frontend/131_globalinclude.php b/version/206/frontend/131_globalinclude.php
new file mode 100644
index 0000000..962b5ad
--- /dev/null
+++ b/version/206/frontend/131_globalinclude.php
@@ -0,0 +1,61 @@
+select('tzahlungsession', 'cZahlungsID', $sessionHash);
+ if ($paymentSession && $paymentSession->kBestellung) {
+ $oBestellung = new Bestellung($paymentSession->kBestellung);
+
+ if (Shop::getConfig([CONF_KAUFABWICKLUNG])['kaufabwicklung']['bestellabschluss_abschlussseite'] === 'A') {
+ $oZahlungsID = Shop::DB()->query(
+ '
+ SELECT cId
+ FROM tbestellid
+ WHERE kBestellung = ' . (int)$paymentSession->kBestellung,
+ 1
+ );
+ if (is_object($oZahlungsID)) {
+ header('Location: ' . Shop::getURL() . '/bestellabschluss.php?i=' . $oZahlungsID->cId);
+ exit();
+ }
+ }
+ $bestellstatus = Shop::DB()->select('tbestellstatus', 'kBestellung', (int)$paymentSession->kBestellung);
+ header('Location: ' . Shop::getURL() . '/status.php?uid=' . $bestellstatus->cUID);
+ exit();
+ }
+ }
+
+ ifndef('MOLLIE_REMINDER_PROP', 10);
+ if (mt_rand(1, MOLLIE_REMINDER_PROP) % MOLLIE_REMINDER_PROP === 0) {
+ $lock = new \ws_mollie\ExclusiveLock('mollie_reminder', PFAD_ROOT . PFAD_COMPILEDIR);
+ if ($lock->lock()) {
+ AbstractCheckout::sendReminders();
+ Queue::storno((int)Helper::getSetting('autoStorno'));
+
+ $lock->unlock();
+ }
+ }
+} catch (Exception $e) {
+ Helper::logExc($e);
+}
diff --git a/version/206/frontend/132_headPostGet.php b/version/206/frontend/132_headPostGet.php
new file mode 100644
index 0000000..9562324
--- /dev/null
+++ b/version/206/frontend/132_headPostGet.php
@@ -0,0 +1,20 @@
+append(
+ <<
+ /* MOLLIE CHECKOUT STYLES*/
+ #fieldset-payment .form-group > div:hover, #checkout-shipping-payment .form-group > div:hover {
+ background-color: #eee;
+ color: black;
+ }
+ $selector label {
+ $border
+ }
+
+ $selector label::after {
+ clear: both;
+ content: ' ';
+ display: block;
+ }
+
+ $selector label span small {
+ line-height: 48px;
+ }
+
+ $selector label img {
+ float: right;
+ }
+
+HTML
+ );
+} catch (Exception $e) {
+ Helper::logExc($e);
+}
diff --git a/version/206/frontend/180_checkbox.php b/version/206/frontend/180_checkbox.php
new file mode 100644
index 0000000..530a9dc
--- /dev/null
+++ b/version/206/frontend/180_checkbox.php
@@ -0,0 +1,21 @@
+oPluginEinstellungAssoc_arr['useCustomerAPI'] === 'C') {
+ \ws_mollie\Hook\Checkbox::execute($args_arr);
+ }
+} catch (Exception $e) {
+ Helper::logExc($e);
+}
diff --git a/version/206/frontend/181_sync.php b/version/206/frontend/181_sync.php
new file mode 100644
index 0000000..46fcf31
--- /dev/null
+++ b/version/206/frontend/181_sync.php
@@ -0,0 +1,18 @@
+
diff --git a/version/206/frontend/tpl/applepay.tpl b/version/206/frontend/tpl/applepay.tpl
new file mode 100644
index 0000000..57c2e75
--- /dev/null
+++ b/version/206/frontend/tpl/applepay.tpl
@@ -0,0 +1,19 @@
+
+
diff --git a/version/206/paymentmethod/JTLMollie.php b/version/206/paymentmethod/JTLMollie.php
new file mode 100644
index 0000000..ee887a8
--- /dev/null
+++ b/version/206/paymentmethod/JTLMollie.php
@@ -0,0 +1,306 @@
+cModulId = 'kPlugin_' . self::Plugin()->kPlugin . '_mollie' . $moduleID;
+ }
+
+ /**
+ * @param Bestellung $order
+ * @return PaymentMethod
+ */
+ public function setOrderStatusToPaid($order)
+ {
+ // If paid already, do nothing
+ if ((int)$order->cStatus >= BESTELLUNG_STATUS_BEZAHLT) {
+ return $this;
+ }
+
+ return parent::setOrderStatusToPaid($order);
+ }
+
+
+ /**
+ * @param $kBestellung
+ * @param bool $redirect
+ * @return bool|string
+ */
+ public static function getOrderCompletedRedirect($kBestellung, $redirect = true)
+ {
+ $mode = Shopsetting::getInstance()->getValue(CONF_KAUFABWICKLUNG, 'bestellabschluss_abschlussseite');
+
+ $bestellid = Shop::DB()->select('tbestellid ', 'kBestellung', (int)$kBestellung);
+ $url = Shop::getURL() . '/bestellabschluss.php?i=' . $bestellid->cId;
+
+
+ if ($mode === 'S' || !$bestellid) { // Statusseite
+ $bestellstatus = Shop::DB()->select('tbestellstatus', 'kBestellung', (int)$kBestellung);
+ $url = Shop::getURL() . '/status.php?uid=' . $bestellstatus->cUID;
+ }
+
+ if ($redirect) {
+ if (!headers_sent()) {
+ header('Location: ' . $url);
+ }
+ echo "redirect ... ";
+ exit();
+ }
+
+ return $url;
+ }
+
+
+ /**
+ * Prepares everything so that the Customer can start the Payment Process.
+ * Tells Template Engine.
+ *
+ * @param Bestellung $order
+ * @return void
+ */
+ public function preparePaymentProcess($order)
+ {
+ parent::preparePaymentProcess($order);
+
+ try {
+ if ($this->duringCheckout && !static::ALLOW_PAYMENT_BEFORE_ORDER) {
+ $this->Log(sprintf('Zahlung vor Bestellabschluss nicht unterstützt (%s)!', $order->cBestellNr), sprintf('#%s', $order->kBestellung), LOGLEVEL_ERROR);
+
+ return;
+ }
+
+ $payable = (float)$order->fGesamtsumme > 0;
+ if (!$payable) {
+ $this->Log(sprintf("Bestellung '%s': Gesamtsumme %.2f, keine Zahlung notwendig!", $order->cBestellNr, $order->fGesamtsumme), sprintf('#%s', $order->kBestellung));
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'bestellabschluss_inc.php';
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'mailTools.php';
+ $finalized = finalisiereBestellung();
+ Session::getInstance()->cleanUp();
+ self::getOrderCompletedRedirect($finalized->kBestellung);
+
+ return;
+ }
+
+ $paymentOptions = [];
+
+ $api = array_key_exists($this->moduleID . '_api', self::Plugin()->oPluginEinstellungAssoc_arr) ? self::Plugin()->oPluginEinstellungAssoc_arr[$this->moduleID . '_api'] : 'order';
+
+ $paymentOptions = array_merge($paymentOptions, $this->getPaymentOptions($order, $api));
+
+ if ($api === 'payment') {
+ $checkout = new PaymentCheckout($order);
+ $payment = $checkout->create($paymentOptions);
+ /** @noinspection NullPointerExceptionInspection */
+ $url = $payment->getCheckoutUrl();
+ } else {
+ $checkout = new OrderCheckout($order);
+ $mOrder = $checkout->create($paymentOptions);
+ /** @noinspection NullPointerExceptionInspection */
+ $url = $mOrder->getCheckoutUrl();
+ }
+
+ ifndef('MOLLIE_REDIRECT_DELAY', 3);
+ $checkoutMode = self::Plugin()->oPluginEinstellungAssoc_arr['checkoutMode'];
+ Shop::Smarty()->assign('redirect', $url)
+ ->assign('checkoutMode', $checkoutMode);
+ if ($checkoutMode === 'Y' && !headers_sent()) {
+ header('Location: ' . $url);
+ }
+ } catch (Exception $e) {
+ $this->Log('mollie::preparePaymentProcess: ' . $e->getMessage() . ' - ' . print_r(['cBestellNr' => $order->cBestellNr], 1), '#' . $order->kBestellung, LOGLEVEL_ERROR);
+ Shop::Smarty()->assign('oMollieException', $e)
+ ->assign('tryAgain', Shop::getURL() . '/bestellab_again.php?kBestellung=' . $order->kBestellung);
+ }
+ }
+
+ public function Log($msg, $data = null, $level = LOGLEVEL_NOTICE)
+ {
+ ZahlungsLog::add($this->moduleID, '[' . microtime(true) . ' - ' . $_SERVER['PHP_SELF'] . '] ' . $msg, $data, $level);
+
+ return $this;
+ }
+
+ public function getPaymentOptions(Bestellung $order, $apiType)
+ {
+ return [];
+ }
+
+ /**
+ * @param Bestellung $order
+ * @param string $hash
+ * @param array $args
+ */
+ public function handleNotification($order, $hash, $args)
+ {
+ parent::handleNotification($order, $hash, $args);
+
+ try {
+ $orderId = $args['id'];
+ $checkout = null;
+ if (strpos($orderId, 'tr_') === 0) {
+ $checkout = new PaymentCheckout($order);
+ } else {
+ $checkout = new OrderCheckout($order);
+ }
+ $checkout->handleNotification($hash);
+ } catch (Exception $e) {
+ $checkout->Log(sprintf("mollie::handleNotification: Fehler bei Bestellung '%s': %s\n%s", $order->cBestellNr, $e->getMessage(), print_r($args, 1)), LOGLEVEL_ERROR);
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function canPayAgain()
+ {
+ return true;
+ }
+
+ /**
+ * determines, if the payment method can be selected in the checkout process
+ *
+ * @return bool
+ */
+ public function isSelectable()
+ {
+ if (API::getMode()) {
+ $selectable = trim(self::Plugin()->oPluginEinstellungAssoc_arr['test_api_key']) !== '';
+ } else {
+ $selectable = trim(self::Plugin()->oPluginEinstellungAssoc_arr['api_key']) !== '';
+ if (!$selectable) {
+ Jtllog::writeLog('Live API Key missing!');
+ }
+ }
+ if ($selectable) {
+ try {
+ $locale = AbstractCheckout::getLocale($_SESSION['cISOSprache'], Session::getInstance()->Customer()->cLand);
+ $amount = Session::getInstance()->Basket()->gibGesamtsummeWarenExt([
+ C_WARENKORBPOS_TYP_ARTIKEL,
+ C_WARENKORBPOS_TYP_KUPON,
+ C_WARENKORBPOS_TYP_GUTSCHEIN,
+ C_WARENKORBPOS_TYP_NEUKUNDENKUPON,
+ ], true) * Session::getInstance()->Currency()->fFaktor;
+ if ($amount <= 0) {
+ $amount = 0.01;
+ }
+ $selectable = self::isMethodPossible(
+ static::METHOD,
+ $locale,
+ Session::getInstance()->Customer()->cLand,
+ Session::getInstance()->Currency()->cISO,
+ $amount
+ );
+ } catch (Exception $e) {
+ Helper::logExc($e);
+ $selectable = false;
+ }
+ }
+
+ return $selectable && parent::isSelectable();
+ }
+
+ /**
+ * @param $method
+ * @param $locale
+ * @param $billingCountry
+ * @param $currency
+ * @param $amount
+ * @throws ApiException
+ * @return bool
+ */
+ protected static function isMethodPossible($method, $locale, $billingCountry, $currency, $amount)
+ {
+ $api = new API(API::getMode());
+
+ if (!array_key_exists('mollie_possibleMethods', $_SESSION)) {
+ $_SESSION['mollie_possibleMethods'] = [];
+ }
+
+ $key = md5(serialize([$locale, $billingCountry, $currency, $amount]));
+ if (!array_key_exists($key, $_SESSION['mollie_possibleMethods'])) {
+ $active = $api->Client()->methods->allActive([
+ 'locale' => $locale,
+ 'amount' => [
+ 'currency' => $currency,
+ 'value' => number_format($amount, 2, '.', '')
+ ],
+ 'billingCountry' => $billingCountry,
+ 'resource' => 'orders',
+ 'includeWallets' => 'applepay',
+ ]);
+ foreach ($active as $a) {
+ $_SESSION['mollie_possibleMethods'][$key][] = (object)['id' => $a->id];
+ }
+ }
+
+ if ($method !== '') {
+ foreach ($_SESSION['mollie_possibleMethods'][$key] as $m) {
+ if ($m->id === $method) {
+ return true;
+ }
+ }
+ } else {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param array $args_arr
+ * @return bool
+ */
+ public function isValidIntern($args_arr = [])
+ {
+ return $this->duringCheckout
+ ? static::ALLOW_PAYMENT_BEFORE_ORDER && parent::isValidIntern($args_arr)
+ : parent::isValidIntern($args_arr);
+ }
+
+ /**
+ * @return int
+ */
+ public function getExpiryDays()
+ {
+ return (int)min(abs((int)Helper::oPlugin()->oPluginEinstellungAssoc_arr[$this->cModulId . '_dueDays']), static::MAX_EXPIRY_DAYS) ?: static::MAX_EXPIRY_DAYS;
+ }
+}
diff --git a/version/206/paymentmethod/JTLMollieApplePay.php b/version/206/paymentmethod/JTLMollieApplePay.php
new file mode 100644
index 0000000..ca0b375
--- /dev/null
+++ b/version/206/paymentmethod/JTLMollieApplePay.php
@@ -0,0 +1,20 @@
+oRechnungsadresse->cMail;
+ $paymentOptions['locale'] = AbstractCheckout::getLocale($_SESSION['cISOSprache'], $order->oRechnungsadresse->cLand);
+ }
+
+ $dueDays = $this->getExpiryDays();
+ if ($dueDays > 3) {
+ $paymentOptions['dueDate'] = date('Y-m-d', strtotime("+$dueDays DAYS"));
+ }
+
+ return $paymentOptions;
+ }
+}
diff --git a/version/206/paymentmethod/JTLMollieBelfius.php b/version/206/paymentmethod/JTLMollieBelfius.php
new file mode 100644
index 0000000..b7cac71
--- /dev/null
+++ b/version/206/paymentmethod/JTLMollieBelfius.php
@@ -0,0 +1,17 @@
+clearToken();
+ }
+
+ protected function clearToken()
+ {
+ $this->unsetCache(self::CACHE_TOKEN)
+ ->unsetCache(self::CACHE_TOKEN_TIMESTAMP);
+
+ return true;
+ }
+
+ public function handleAdditional($aPost_arr)
+ {
+ $components = self::Plugin()->oPluginEinstellungAssoc_arr[$this->moduleID . '_components'];
+ $profileId = self::Plugin()->oPluginEinstellungAssoc_arr['profileId'];
+
+ if ($components === 'N' || !$profileId || trim($profileId) === '') {
+ return parent::handleAdditional($aPost_arr);
+ }
+
+ $cleared = false;
+ if (array_key_exists('clear', $aPost_arr) && (int)$aPost_arr['clear']) {
+ $cleared = $this->clearToken();
+ }
+
+ if ($components === 'S' && array_key_exists('skip', $aPost_arr) && (int)$aPost_arr['skip']) {
+ return parent::handleAdditional($aPost_arr);
+ }
+
+ try {
+ $trustBadge = (bool)self::Plugin()->oPluginEinstellungAssoc_arr[$this->moduleID . '_loadTrust'];
+ $locale = AbstractCheckout::getLocale($_SESSION['cISOSprache'], Session::getInstance()->Customer() ? Session::getInstance()->Customer()->cLand : null);
+ $mode = API::getMode();
+ $errorMessage = json_encode(self::Plugin()->oPluginSprachvariableAssoc_arr['mcErrorMessage']);
+ } catch (Exception $e) {
+ Jtllog::writeLog($e->getMessage() . "\n" . print_r(['e' => $e], 1));
+
+ return parent::handleAdditional($aPost_arr);
+ }
+
+ if (!$cleared && array_key_exists('cardToken', $aPost_arr) && ($token = trim($aPost_arr['cardToken']))) {
+ return $this->setToken($token) && parent::handleAdditional($aPost_arr);
+ }
+
+ $token = false;
+ if (($ctTS = (int)$this->getCache(self::CACHE_TOKEN_TIMESTAMP)) && $ctTS > time()) {
+ $token = $this->getCache(self::CACHE_TOKEN);
+ }
+
+ Shop::Smarty()->assign('profileId', $profileId)
+ ->assign('trustBadge', $trustBadge ? self::Plugin()->cFrontendPfadURLSSL . 'img/trust_' . $_SESSION['cISOSprache'] . '.png' : false)
+ ->assign('components', $components)
+ ->assign('locale', $locale ?: 'de_DE')
+ ->assign('token', $token ?: false)
+ ->assign('testMode', $mode ?: false)
+ ->assign('errorMessage', $errorMessage ?: null)
+ ->assign('mollieLang', self::Plugin()->oPluginSprachvariableAssoc_arr);
+
+ return false;
+ }
+
+ protected function setToken($token)
+ {
+ $this->addCache(self::CACHE_TOKEN, $token)
+ ->addCache(self::CACHE_TOKEN_TIMESTAMP, time() + 3600);
+
+ return true;
+ }
+
+ public function getPaymentOptions(Bestellung $order, $apiType)
+ {
+ $paymentOptions = [];
+
+ if ($apiType === 'payment') {
+ if ($order->Lieferadresse !== null) {
+ if (!$order->Lieferadresse->cMail) {
+ $order->Lieferadresse->cMail = $order->oRechnungsadresse->cMail;
+ }
+ $paymentOptions['shippingAddress'] = Address::factory($order->Lieferadresse);
+ }
+
+ $paymentOptions['billingAddress'] = Address::factory($order->oRechnungsadresse);
+ }
+ if ((int)$this->getCache(self::CACHE_TOKEN_TIMESTAMP) > time() && ($token = trim($this->getCache(self::CACHE_TOKEN)))) {
+ $paymentOptions['cardToken'] = $token;
+ }
+
+ return $paymentOptions;
+ }
+}
diff --git a/version/206/paymentmethod/JTLMollieEPS.php b/version/206/paymentmethod/JTLMollieEPS.php
new file mode 100644
index 0000000..5120b55
--- /dev/null
+++ b/version/206/paymentmethod/JTLMollieEPS.php
@@ -0,0 +1,17 @@
+ substr($order->cBestellNr, 0, 13)];
+ }
+}
diff --git a/version/206/paymentmethod/JTLMollieKlarnaPayLater.php b/version/206/paymentmethod/JTLMollieKlarnaPayLater.php
new file mode 100644
index 0000000..3474053
--- /dev/null
+++ b/version/206/paymentmethod/JTLMollieKlarnaPayLater.php
@@ -0,0 +1,24 @@
+Lieferadresse !== null) {
+ if (!$order->Lieferadresse->cMail) {
+ $order->Lieferadresse->cMail = $order->oRechnungsadresse->cMail;
+ }
+ $paymentOptions['shippingAddress'] = Address::factory($order->Lieferadresse);
+ }
+ }
+
+
+ return $paymentOptions;
+ }
+}
diff --git a/version/206/paymentmethod/JTLMolliePaysafecard.php b/version/206/paymentmethod/JTLMolliePaysafecard.php
new file mode 100644
index 0000000..29be769
--- /dev/null
+++ b/version/206/paymentmethod/JTLMolliePaysafecard.php
@@ -0,0 +1,23 @@
+ $order->oKunde->kKunde] : [];
+ }
+}
diff --git a/version/206/paymentmethod/JTLMolliePrzelewy24.php b/version/206/paymentmethod/JTLMolliePrzelewy24.php
new file mode 100644
index 0000000..62a55d5
--- /dev/null
+++ b/version/206/paymentmethod/JTLMolliePrzelewy24.php
@@ -0,0 +1,23 @@
+ $order->oRechnungsadresse->cMail] : [];
+ }
+}
diff --git a/version/206/paymentmethod/JTLMollieSofort.php b/version/206/paymentmethod/JTLMollieSofort.php
new file mode 100644
index 0000000..892adc3
--- /dev/null
+++ b/version/206/paymentmethod/JTLMollieSofort.php
@@ -0,0 +1,18 @@
+
+ {$oMollieException->getMessage()}
+
+
+{/if}
+
+{if isset($redirect) && $redirect != ''}
+
+ {if $checkoutMode == 'D'}
+
+ {/if}
+{/if}
+
diff --git a/version/206/paymentmethod/tpl/mollieComponents.tpl b/version/206/paymentmethod/tpl/mollieComponents.tpl
new file mode 100644
index 0000000..987abf1
--- /dev/null
+++ b/version/206/paymentmethod/tpl/mollieComponents.tpl
@@ -0,0 +1,148 @@
+{$mollieLang.cctitle}
+
+
+
+{/if}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version/206/tpl/_alerts.tpl b/version/206/tpl/_alerts.tpl
new file mode 100644
index 0000000..5279fa3
--- /dev/null
+++ b/version/206/tpl/_alerts.tpl
@@ -0,0 +1,10 @@
+{if $alerts}
+ {assign var=alert_arr value=$alerts|get_object_vars}
+ {if $alert_arr|count}
+
+ {foreach from=$alert_arr item=alert key=id}
+
{$alert}
+ {/foreach}
+
+ {/if}
+{/if}
diff --git a/version/207/adminmenu/info.php b/version/207/adminmenu/info.php
new file mode 100644
index 0000000..4ef2525
--- /dev/null
+++ b/version/207/adminmenu/info.php
@@ -0,0 +1,68 @@
+assign('defaultTabbertab', Helper::getAdminmenu('Info') + Helper::getAdminmenu('Support'));
+ Helper::selfupdate();
+ }
+
+ $svgQuery = http_build_query([
+ 'p' => Helper::oPlugin()->cPluginID,
+ 'v' => Helper::oPlugin()->nVersion,
+ 's' => defined('APPLICATION_VERSION') ? APPLICATION_VERSION : JTL_VERSION,
+ 'b' => defined('JTL_MINOR_VERSION') ? JTL_MINOR_VERSION : '0',
+ 'd' => Helper::getDomain(),
+ 'm' => base64_encode(Helper::getMasterMail(true)),
+ 'php' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION . PHP_EXTRA_VERSION,
+ ]);
+
+ echo "";
+ echo "" .
+ "
" .
+ "
" .
+ " " .
+ ' ' .
+ '
' .
+ "
" .
+ "
" .
+ " " .
+ ' ' .
+ '
' .
+ "
" .
+ "
" .
+ " " .
+ ' ' .
+ '
' .
+ '
';
+
+ try {
+ $latestRelease = Helper::getLatestRelease(array_key_exists('update', $_REQUEST));
+ if ((int)Helper::oPlugin()->nVersion < (int)$latestRelease->version) {
+ Shop::Smarty()->assign('update', $latestRelease);
+ }
+ } catch (Exception $e) {
+ }
+
+ Shop::Smarty()->display(Helper::oPlugin()->cAdminmenuPfad . '/tpl/info.tpl');
+
+ if (file_exists(__DIR__ . '/_addon.php')) {
+ try {
+ include __DIR__ . '/_addon.php';
+ } catch (Exception $e) {
+ }
+ }
+} catch (Exception $e) {
+ echo "Fehler: {$e->getMessage()}
";
+ Helper::logExc($e);
+}
diff --git a/version/207/adminmenu/orders.php b/version/207/adminmenu/orders.php
new file mode 100644
index 0000000..68197ed
--- /dev/null
+++ b/version/207/adminmenu/orders.php
@@ -0,0 +1,245 @@
+getModel()->kID)) {
+ Helper::addAlert('Zahlungserinnerung wurde verschickt.', 'success', 'orders');
+ } else {
+ Helper::addAlert('Es ist ein Fehler aufgetreten, prüfe den Log.', 'danger', 'orders');
+ }
+ } else {
+ Helper::addAlert('Bestellung konnte nicht geladen werden.', 'danger', 'orders');
+ }
+
+
+ break;
+
+ case 'fetchable':
+ if (array_key_exists('kBestellung', $_REQUEST) && ($checkout = AbstractCheckout::fromBestellung((int)$_REQUEST['kBestellung']))) {
+ if (AbstractCheckout::makeFetchable($checkout->getBestellung(), $checkout->getModel())) {
+ Helper::addAlert('Bestellung kann jetzt von der WAWI abgeholt werden.', 'success', 'orders');
+ } else {
+ Helper::addAlert('Es ist ein Fehler aufgetreten, prüfe den Log.', 'danger', 'orders');
+ }
+ } else {
+ Helper::addAlert('Bestellung konnte nicht geladen werden.', 'danger', 'orders');
+ }
+
+ break;
+
+ case 'export':
+ try {
+ $export = [];
+
+ $from = new DateTime($_REQUEST['from']);
+ $to = new DateTime($_REQUEST['to']);
+
+ $orders = Shop::DB()->executeQueryPrepared('SELECT * FROM xplugin_ws_mollie_payments WHERE kBestellung > 0 AND dCreatedAt >= :From AND dCreatedAt <= :To ORDER BY dCreatedAt', [
+ ':From' => $from->format('Y-m-d'),
+ ':To' => $to->format('Y-m-d'),
+ ], 2);
+
+
+ header('Content-Type: application/csv');
+ header('Content-Disposition: attachment; filename=mollie-' . $from->format('Ymd') . '-' . $to->format('Ymd') . '.csv');
+ header('Pragma: no-cache');
+
+ $out = fopen('php://output', 'wb');
+
+
+ fputcsv($out, [
+ 'kBestellung',
+ 'OrderID',
+ 'Status (mollie)',
+ 'BestellNr',
+ 'Status (JTL)',
+ 'Mode',
+ 'OriginalOrderNumber',
+ 'Currency',
+ 'Amount',
+ 'Method',
+ 'PaymentID',
+ 'Created'
+ ]);
+
+
+ foreach ($orders as $order) {
+ $order = new ws_mollie\Model\Payment($order);
+ $checkout = AbstractCheckout::fromModel($order);
+
+ $tmp = [
+ 'kBestellung' => $order->kBestellung,
+ 'cOrderId' => $order->kID,
+ 'cStatus' => $checkout->getMollie() ? $checkout->getMollie()->status : $order->cStatus,
+ 'cBestellNr' => $checkout->getBestellung() ? $checkout->getBestellung()->cBestellNr : $order->cOrderNumber,
+ 'nStatus' => $checkout->getBestellung() ? $checkout->getBestellung()->cStatus : 0,
+ 'cMode' => $order->cMode,
+ 'cOriginalOrderNumber' => $checkout->getMollie() && isset($checkout->getMollie()->metadata->originalOrderNumber) ? $checkout->getMollie()->metadata->originalOrderNumber : '',
+ 'cCurrency' => $order->cCurrency,
+ 'fAmount' => $order->fAmount,
+ 'cMethod' => $order->cMethod,
+ 'cPaymentId' => $order->cTransactionId,
+ 'dCreated' => $order->dCreatedAt,
+ ];
+
+ try {
+ if ($checkout->getMollie() && $checkout->getMollie()->resource === 'order') {
+ foreach ($checkout->getMollie()->payments() as $payment) {
+ if ($payment->status === PaymentStatus::STATUS_PAID) {
+ $tmp['cPaymentId'] = $payment->id;
+ }
+ }
+ }
+ } catch (Exception $e) {
+ }
+ fputcsv($out, $tmp);
+
+ $export[] = $tmp;
+ }
+
+ fclose($out);
+ exit();
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler:' . $e->getMessage(), 'danger', 'orders');
+ }
+
+ break;
+
+ case 'refund':
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angegeben!', 'danger', 'orders');
+ }
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+ if ($refund = $checkout::refund($checkout)) {
+ Helper::addAlert(sprintf('Bestellung wurde zurückerstattet (%s).', $refund->id), 'success', 'orders');
+ }
+ goto order;
+ } catch (InvalidArgumentException $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ goto order;
+ }
+
+ break;
+
+ case 'cancel':
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angeben!');
+ }
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+ if ($checkout::cancel($checkout)) {
+ Helper::addAlert('Bestellung wurde abgebrochen.', 'success', 'orders');
+ }
+ goto order;
+ } catch (InvalidArgumentException $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ goto order;
+ }
+
+ break;
+
+ case 'capture':
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angeben!');
+ }
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+ if ($shipmentId = OrderCheckout::capture($checkout)) {
+ Helper::addAlert(sprintf('Zahlung erfolgreich erfasst/versandt (%s).', $shipmentId), 'success', 'orders');
+ }
+ goto order;
+ } catch (InvalidArgumentException $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ goto order;
+ }
+
+ break;
+
+ case 'order':
+ order :
+ try {
+ if (!array_key_exists('id', $_REQUEST)) {
+ throw new InvalidArgumentException('Keine ID angeben!');
+ }
+
+ $checkout = AbstractCheckout::fromID($_REQUEST['id']);
+
+ if ($checkout instanceof OrderCheckout) {
+ Shop::Smarty()->assign('shipments', $checkout->getShipments());
+ }
+
+ Shop::Smarty()->assign('payment', $checkout->getModel())
+ ->assign('oBestellung', $checkout->getBestellung())
+ ->assign('order', $checkout->getMollie())
+ ->assign('checkout', $checkout)
+ ->assign('logs', $checkout->getLogs());
+ Shop::Smarty()->display($oPlugin->cAdminmenuPfad . '/tpl/order.tpl');
+
+ return;
+ } catch (Exception $e) {
+ Helper::addAlert('Fehler: ' . $e->getMessage(), 'danger', 'orders');
+ }
+
+ break;
+ }
+ }
+
+ // Mollie::fixZahlungsarten();
+
+ $checkouts = [];
+ $payments = Shop::DB()->executeQueryPrepared('SELECT * FROM xplugin_ws_mollie_payments WHERE kBestellung IS NOT NULL ORDER BY dCreatedAt DESC LIMIT 1000;', [], 2);
+ foreach ($payments as $i => $payment) {
+ $payment = new Payment($payment);
+
+ try {
+ $checkouts[$payment->kBestellung] = AbstractCheckout::fromModel($payment, false);
+ } catch (Exception $e) {
+ //Helper::addAlert($e->getMessage(), 'danger', 'orders');
+ }
+ }
+
+ Shop::Smarty()->assign('payments', $payments)
+ ->assign('checkouts', $checkouts)
+ ->assign('admRoot', str_replace('http:', '', $oPlugin->cAdminmenuPfadURL))
+ ->assign('hasAPIKey', trim(Helper::getSetting('api_key')) !== '');
+
+ Shop::Smarty()->display($oPlugin->cAdminmenuPfad . '/tpl/orders.tpl');
+} catch (Exception $e) {
+ echo "" .
+ "{$e->getMessage()}
" .
+ "
{$e->getFile()}:{$e->getLine()}{$e->getTraceAsString()} " .
+ '
';
+ Helper::logExc($e);
+}
diff --git a/version/207/adminmenu/paymentmethods.php b/version/207/adminmenu/paymentmethods.php
new file mode 100644
index 0000000..49c2851
--- /dev/null
+++ b/version/207/adminmenu/paymentmethods.php
@@ -0,0 +1,103 @@
+setApiKey(Helper::getSetting('api_key'));
+
+ $profile = $mollie->profiles->get('me');
+
+ $za = filter_input(INPUT_GET, 'za', FILTER_VALIDATE_BOOLEAN);
+ $active = filter_input(INPUT_GET, 'active', FILTER_VALIDATE_BOOLEAN);
+ $amount = filter_input(INPUT_GET, 'amount', FILTER_VALIDATE_FLOAT) ?: null;
+ $locale = filter_input(INPUT_GET, 'locale', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z]{2}_[a-zA-Z]{2}$/']]) ?: AbstractCheckout::getLocale();
+ $currency = filter_input(INPUT_GET, 'currency', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z]{3}$/']]) ?: 'EUR';
+
+
+ if ($za) {
+ Shop::Smarty()->assign('defaultTabbertab', Helper::getAdminmenu('Zahlungsarten'));
+ }
+
+ $params = [
+ 'include' => 'pricing,issuers',
+ 'locale' => $locale
+ ];
+ if ($amount && $currency) {
+ $params['amount'] = ['value' => number_format($amount, 2, '.', ''), 'currency' => $currency];
+ if ($active) {
+ $params['includeWallets'] = 'applepay';
+ //$params['resource'] = 'orders';
+ }
+ }
+
+ $allMethods = [];
+ if ($active) {
+ $_allMethods = $mollie->methods->allActive($params);
+ } else {
+ $_allMethods = $mollie->methods->allAvailable($params);
+ }
+
+ $sessionLife = (int)ini_get('session.gc_maxlifetime');
+
+ /** @var Method $method */
+ foreach ($_allMethods as $method) {
+ $id = $method->id === 'creditcard' ? 'kreditkarte' : $method->id;
+ $key = "kPlugin_{$oPlugin->kPlugin}_mollie$id";
+
+ $class = null;
+ $shop = null;
+ $oClass = null;
+
+ if (array_key_exists($key, $oPlugin->oPluginZahlungsKlasseAssoc_arr) && !in_array($id, ['voucher', 'giftcard', 'directdebit'], true)) {
+ $class = $oPlugin->oPluginZahlungsKlasseAssoc_arr[$key];
+ include_once $oPlugin->cPluginPfad . 'paymentmethod/' . $class->cClassPfad;
+ /** @var JTLMollie $oClass */
+ $oClass = new $class->cClassName($id);
+ }
+ if (array_key_exists($key, $oPlugin->oPluginZahlungsmethodeAssoc_arr)) {
+ $shop = $oPlugin->oPluginZahlungsmethodeAssoc_arr[$key];
+ }
+
+ $maxExpiryDays = $oClass ? $oClass->getExpiryDays() : null;
+ $allMethods[$method->id] = (object)[
+ 'mollie' => $method,
+ 'class' => $class,
+ 'allowPreOrder' => $oClass ? $oClass::ALLOW_PAYMENT_BEFORE_ORDER : false,
+ 'allowAutoStorno' => $oClass ? $oClass::ALLOW_AUTO_STORNO : false,
+ 'oClass' => $oClass,
+ 'shop' => $shop,
+ 'maxExpiryDays' => $oClass ? $maxExpiryDays : null,
+ 'warning' => $oClass && ($maxExpiryDays * 24 * 60 * 60) > $sessionLife,
+ 'session' => round($sessionLife / 60 / 60, 2) . 'h'
+ ];
+ }
+
+ Shop::Smarty()->assign('profile', $profile)
+ ->assign('currencies', AbstractCheckout::getCurrencies())
+ ->assign('locales', AbstractCheckout::getLocales())
+ ->assign('allMethods', $allMethods)
+ ->assign('settings', $oPlugin->oPluginEinstellungAssoc_arr);
+ Shop::Smarty()->display($oPlugin->cAdminmenuPfad . '/tpl/paymentmethods.tpl');
+} catch (Exception $e) {
+ echo "{$e->getMessage()}
";
+ Helper::logExc($e);
+}
diff --git a/version/207/adminmenu/tpl/_addon.tpl b/version/207/adminmenu/tpl/_addon.tpl
new file mode 100644
index 0000000..6539757
--- /dev/null
+++ b/version/207/adminmenu/tpl/_addon.tpl
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/version/207/adminmenu/tpl/info.tpl b/version/207/adminmenu/tpl/info.tpl
new file mode 100644
index 0000000..2d41c95
--- /dev/null
+++ b/version/207/adminmenu/tpl/info.tpl
@@ -0,0 +1,114 @@
+
+
+
+
+
+ {if isset($update)}
+
+
+
+
Update auf Version {$update->version} verfügbar!
+
+
+
+
Version:
+
{$update->version}
+
+
+
Erschienen:
+
{$update->create_date}
+
+
+
+
+
+
+ {else}
+
+ {/if}
+
+
+
+
+
+{if file_exists("{$smarty['current_dir']}/_addon.tpl")}
+ {include file="{$smarty['current_dir']}/_addon.tpl"}
+{/if}
+{if isset($oPlugin)}
+
+{/if}
diff --git a/version/207/adminmenu/tpl/mollie-account-erstellen.png b/version/207/adminmenu/tpl/mollie-account-erstellen.png
new file mode 100644
index 0000000..fc18192
Binary files /dev/null and b/version/207/adminmenu/tpl/mollie-account-erstellen.png differ
diff --git a/version/207/adminmenu/tpl/order.tpl b/version/207/adminmenu/tpl/order.tpl
new file mode 100644
index 0000000..aad5810
--- /dev/null
+++ b/version/207/adminmenu/tpl/order.tpl
@@ -0,0 +1,344 @@
+
+
+ «
+ Bestellung: {$oBestellung->cBestellNr} -
+ {if $oBestellung->cStatus|intval == 1}
+ OFFEN
+ {elseif $oBestellung->cStatus|intval == 2}
+ IN BEARBEITUNG
+ {elseif $oBestellung->cStatus|intval == 3}
+ BEZAHLT
+ {elseif $oBestellung->cStatus|intval == 4}
+ VERSANDT
+ {elseif $oBestellung->cStatus|intval == 5}
+ TEILVERSANDT
+ {elseif $oBestellung->cStatus|intval == -1}
+ STORNO
+ {else}
+ n/a
+ {/if}
+
+
+{ws_mollie\Helper::showAlerts('orders')}
+
+
+{if $order->id|strpos:"ord_" !== false && $order->payments()->count > 0}
+ Zahlungen
+
+
+
+ ID
+ Status
+ Methode
+ Amount
+ Settlement
+ Refunded
+ Remaining
+ Details
+
+
+ {foreach from=$order->payments() item=payment}
+
+ {$payment->id}
+ {$payment->status}
+ {$payment->method}
+ {$payment->amount->value} {$payment->amount->currency}
+
+ {if $payment->settlementAmount}
+ {$payment->settlementAmount->value} {$payment->settlementAmount->currency}
+ {else}-{/if}
+
+
+ {if $payment->amountRefunded}
+ {$payment->amountRefunded->value} {$payment->amountRefunded->currency}
+ {else}-{/if}
+
+
+ {if $payment->amountRemaining}
+ {$payment->amountRemaining->value} {$payment->amountRemaining->currency}
+ {else}-{/if}
+
+
+
+ {foreach from=$payment->details item=value key=key}
+ {$key}: {if $value|is_scalar}{$value}{else}{$value|json_encode}{/if}
+ {/foreach}
+
+
+
+ {/foreach}
+
+{/if}
+
+
+ {if ($order->status === 'authorized' || $order->status === 'shipping') && $oBestellung->cStatus|intval >= 3}
+
+ Zahlung erfassen1
+
+ {/if}
+ {if !$order->amountRefunded || ($order->amount->value > $order->amountRefunded->value)}
+
Rückerstatten2
+
+ {/if}
+ {if $order->isCancelable}
+
Abbrechen3
+
+ {/if}
+
+
+{if $order->id|strpos:"ord_" !== false}
+ Positionen:
+
+
+
+ Status
+ SKU
+ Name
+ Typ
+ Anzahl
+ MwSt
+ Steuer
+ Netto
+ Brutto
+
+
+
+
+ {assign var="vat" value=0}
+ {assign var="netto" value=0}
+ {assign var="brutto" value=0}
+ {foreach from=$order->lines item=line}
+
+ {assign var="vat" value=$vat+$line->vatAmount->value}
+ {assign var="netto" value=$netto+$line->totalAmount->value-$line->vatAmount->value}
+ {assign var="brutto" value=$brutto+$line->totalAmount->value}
+
+
+ {if $line->status == 'created'}
+ erstellt
+ {elseif $line->status == 'pending'}
+ austehend
+ {elseif $line->status == 'paid'}
+ bezahlt
+ {elseif $line->status == 'authorized'}
+ autorisiert
+ {elseif $line->status == 'shipping'}
+ versendet
+ {elseif $line->status == 'completed'}
+ abgeschlossen
+ {elseif $line->status == 'expired'}
+ abgelaufen
+ {elseif $line->status == 'canceled'}
+ abgebrochen
+ {else}
+ Unbekannt: {$line->status}
+ {/if}
+
+ {$line->sku}
+ {$line->name|utf8_decode}
+ {$line->type}
+ {$line->quantity}
+ {$line->vatRate|floatval}%
+ {$line->vatAmount->value|number_format:2:',':''} {$line->vatAmount->currency}
+ {($line->totalAmount->value - $line->vatAmount->value)|number_format:2:',':''} {$line->vatAmount->currency}
+ {$line->totalAmount->value|number_format:2:',':''} {$line->totalAmount->currency}
+
+ {*if $line->quantity > $line->quantityShipped}
+
+
+
+ {/if}
+ {if $line->quantity > $line->quantityRefunded}
+
+
+
+ {/if}
+ {if $line->isCancelable}
+
+
+
+ {/if*}
+ {*$line|var_dump*}
+
+
+ {/foreach}
+
+
+
+
+ {$vat|number_format:2:',':''} {$order->amount->currency}
+ {$netto|number_format:2:',':''} {$order->amount->currency}
+ {$brutto|number_format:2:',':''} {$order->amount->currency}
+
+
+
+
+
+ 1 = Bestellung wird bei Mollie als versandt markiert. WAWI wird nicht informiert.
+ 2 = Bezahlter Betrag wird dem Kunden rckerstattet. WAWI wird nicht informiert.
+ 3 = Bestellung wird bei Mollie storniert. WAWI wird nicht informiert.
+
+{/if}
+
+{if isset($shipments) && $shipments|count}
+ Shipmets
+
+
+
+ Lieferschein Nr.
+ Mollie ID
+ Carrier
+ Code
+
+
+
+ {foreach from=$shipments item=shipment}
+
+ {$shipment->getLieferschein()->getLieferscheinNr()}
+ {$shipment->getShipment()->id}
+
+ {if isset($shipment->getShipment()->tracking)}
+ {$shipment->getShipment()->tracking->carrier}
+ {else}
+ -
+ {/if}
+
+ {if isset($shipment->getShipment()->tracking)}
+ {if isset($shipment->getShipment()->tracking->url)}
+
+ {$shipment->getShipment()->tracking->code}
+
+ {else}
+ {$shipment->getShipment()->tracking->code}
+ {/if}
+ {else}
+ -
+ {/if}
+
+
+ {/foreach}
+
+
+{/if}
+
+Log
+
+ {foreach from=$logs item=log}
+
+
+ {if $log->nLevel == 1}
+ Fehler
+ {elseif $log->nLevel == 2}
+ Hinweis
+ {elseif $log->nLevel == 3}
+ Debug
+ {else}
+ unknown {$log->nLevel}
+ {/if}
+
+ {$log->cModulId}
+
+
+ {$log->cLog}
+
+
+ {$log->dDatum}
+
+ {/foreach}
+
+
+
diff --git a/version/207/adminmenu/tpl/orders.tpl b/version/207/adminmenu/tpl/orders.tpl
new file mode 100644
index 0000000..a499f32
--- /dev/null
+++ b/version/207/adminmenu/tpl/orders.tpl
@@ -0,0 +1,160 @@
+{ws_mollie\Helper::showAlerts('orders')}
+
+{if $hasAPIKey == false}
+
+
+
+{else}
+
+
+
+ BestellNr.
+ ID
+ Mollie Status
+ JTL Status
+ Betrag
+ Methode
+ Erstellt
+
+
+
+
+ {foreach from=$checkouts item=checkout}
+
+
+ {if $checkout->getModel()->bSynced == false && $checkout->getBestellung()->cAbgeholt === 'Y'}
+ *
+ {/if}
+ {$checkout->getModel()->cOrderNumber}
+ {if $checkout->getModel()->cMode == 'test'}
+ TEST
+ {/if}
+ {if $checkout->getModel()->bLockTimeout}
+ LOCK TIMEOUT
+ {/if}
+ {if $checkout->getModel()->dReminder && $checkout->getModel()->dReminder !== '0000-00-00 00:00:00'}
+ getModel()->dReminder|strtotime}}">
+ {/if}
+
+
+ {$checkout->getModel()->kID}
+
+
+ {if $checkout->getModel()->cStatus == 'created' || $checkout->getModel()->cStatus == 'open'}
+ erstellt
+ {elseif $checkout->getModel()->cStatus == 'pending'}
+ austehend
+ {elseif $checkout->getModel()->cStatus == 'paid'}
+ bezahlt
+ {elseif $checkout->getModel()->cStatus == 'authorized'}
+ autorisiert
+ {elseif $checkout->getModel()->cStatus == 'shipping'}
+ versendet
+ {elseif $checkout->getModel()->cStatus == 'completed'}
+ abgeschlossen
+ {elseif $checkout->getModel()->cStatus == 'expired'}
+ abgelaufen
+ {elseif $checkout->getModel()->cStatus == 'canceled'}
+ abgebrochen
+ {else}
+ Unbekannt: {$checkout->getModel()->cStatus}
+ {/if}
+ {if $checkout->getModel()->fAmountRefunded && $checkout->getModel()->fAmountRefunded == $checkout->getModel()->fAmount}
+ (total refund)
+ {elseif $checkout->getModel()->fAmountRefunded && $checkout->getModel()->fAmountRefunded > 0}
+ (partly refund)
+ {/if}
+
+
+
+ {if $checkout->getBestellung()->cStatus|intval == 1}
+ OFFEN
+ {elseif $checkout->getBestellung()->cStatus|intval == 2}
+ IN BEARBEITUNG
+ {elseif $checkout->getBestellung()->cStatus|intval == 3}
+ BEZAHLT
+ {elseif $checkout->getBestellung()->cStatus|intval == 4}
+ VERSANDT
+ {elseif $checkout->getBestellung()->cStatus|intval == 5}
+ TEILVERSANDT
+ {elseif $checkout->getBestellung()->cStatus|intval == -1}
+ STORNO
+ {else}
+ n/a
+ {/if}
+
+
+ {$checkout->getModel()->fAmount|number_format:2:',':''} {$checkout->getModel()->cCurrency}
+
+ {$checkout->getModel()->cMethod}
+
+ {"d. M Y H:i"|date:{$checkout->getModel()->dCreatedAt|strtotime}}
+
+
+
+
+
+ {/foreach}
+
+
+ {if $payments|count > 900}
+ Hier werden nur die letzten 1000 Ergebnisse angezeigt.
+ {/if}
+
+
+
+
+{/if}
+
diff --git a/version/207/adminmenu/tpl/paymentmethods.tpl b/version/207/adminmenu/tpl/paymentmethods.tpl
new file mode 100644
index 0000000..7821af5
--- /dev/null
+++ b/version/207/adminmenu/tpl/paymentmethods.tpl
@@ -0,0 +1,148 @@
+Account Status
+
+
+ Mode:
+ {$profile->mode}
+ Status:
+ {$profile->status}
+ {if $profile->review}
+ Review:
+ {$profile->review->status}
+ {/if}
+ {if $profile->_links->checkoutPreviewUrl->href}
+
+ Checkout
+ Preview
+
+ {/if}
+
+ Mollie Dashboard
+
+
+
+
+
+
+{if $allMethods && $allMethods|count}
+
+
+
+ Bild
+ Name / ID
+ Info
+ Preise
+ Limits
+
+
+
+ {foreach from=$allMethods item=method}
+
+
+
+ {if $method->mollie->status === 'activated'}
+
+ {else}
+
+ {/if}
+ {$method->mollie->description|utf8_decode}
+ {$method->mollie->id}
+
+
+ {if $method->shop && $method->oClass}
+ {if intval($method->shop->nWaehrendBestellung) === 1 && !$method->allowPreOrder}
+ Zahlung VOR Bestellabschluss nicht unterstützt!
+ {else}
+
+ Bestellabschluss:
+ {if intval($method->shop->nWaehrendBestellung) === 1}
+ NACH Zahlung
+ {else}
+ VOR Zahlung
+ {/if}
+
+ {/if}
+
+ {if intval($settings.autoStorno) > 0}
+
+
Unbez. Bestellung stornieren:
+ {if $method->allowAutoStorno}
+
auto
+ {else}
+
manual
+ {/if}
+
+ {/if}
+
+ Gültigkeit:
+ {$method->maxExpiryDays} Tage
+
+ {else}
+ Derzeit nicht unterstützt.
+ {/if}
+
+
+
+ {foreach from=$method->mollie->pricing item=price}
+
+ {$price->description|utf8_decode} : {$price->fixed->value} {$price->fixed->currency}
+ {if $price->variable > 0.0}
+ + {$price->variable}%
+ {/if}
+
+ {/foreach}
+
+
+
+ Min: {if $method->mollie->minimumAmount}{$method->mollie->minimumAmount->value} {$method->mollie->minimumAmount->currency}{else}n/a{/if}
+
+ Max: {if $method->mollie->maximumAmount}{$method->mollie->maximumAmount->value} {$method->mollie->maximumAmount->currency}{else}n/a{/if}
+
+
+ {/foreach}
+
+
+{else}
+ Es konnten keine Methoden abgerufen werden.
+{/if}
\ No newline at end of file
diff --git a/version/207/class/API.php b/version/207/class/API.php
new file mode 100644
index 0000000..3532455
--- /dev/null
+++ b/version/207/class/API.php
@@ -0,0 +1,77 @@
+test = $test === null ? self::getMode() : $test;
+ }
+
+ /**
+ * @return bool
+ */
+ public static function getMode()
+ {
+ require_once PFAD_ROOT . PFAD_ADMIN . PFAD_INCLUDES . 'benutzerverwaltung_inc.php';
+
+ return self::Plugin()->oPluginEinstellungAssoc_arr['testAsAdmin'] === 'Y' && Shop::isAdmin() && self::Plugin()->oPluginEinstellungAssoc_arr['test_api_key'];
+ }
+
+ /**
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @return MollieApiClient
+ */
+ public function Client()
+ {
+ if (!$this->client) {
+ $this->client = new MollieApiClient(/*new Client([
+ RequestOptions::VERIFY => CaBundle::getBundledCaBundlePath(),
+ RequestOptions::TIMEOUT => 60
+ ])*/
+ );
+ $this->client->setApiKey($this->isTest() ? self::Plugin()->oPluginEinstellungAssoc_arr['test_api_key'] : self::Plugin()->oPluginEinstellungAssoc_arr['api_key'])
+ ->addVersionString('JTL-Shop/' . JTL_VERSION . JTL_MINOR_VERSION)
+ ->addVersionString('ws_mollie/' . self::Plugin()->nVersion);
+ }
+
+ return $this->client;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isTest()
+ {
+ return $this->test;
+ }
+}
diff --git a/version/207/class/Checkout/AbstractCheckout.php b/version/207/class/Checkout/AbstractCheckout.php
new file mode 100644
index 0000000..8a31700
--- /dev/null
+++ b/version/207/class/Checkout/AbstractCheckout.php
@@ -0,0 +1,1214 @@
+ ['lang' => 'de', 'country' => ['DE', 'AT', 'CH']],
+ 'fre' => ['lang' => 'fr', 'country' => ['BE', 'FR']],
+ 'dut' => ['lang' => 'nl', 'country' => ['BE', 'NL']],
+ 'spa' => ['lang' => 'es', 'country' => ['ES']],
+ 'ita' => ['lang' => 'it', 'country' => ['IT']],
+ 'pol' => ['lang' => 'pl', 'country' => ['PL']],
+ 'hun' => ['lang' => 'hu', 'country' => ['HU']],
+ 'por' => ['lang' => 'pt', 'country' => ['PT']],
+ 'nor' => ['lang' => 'nb', 'country' => ['NO']],
+ 'swe' => ['lang' => 'sv', 'country' => ['SE']],
+ 'fin' => ['lang' => 'fi', 'country' => ['FI']],
+ 'dan' => ['lang' => 'da', 'country' => ['DK']],
+ 'ice' => ['lang' => 'is', 'country' => ['IS']],
+ 'eng' => ['lang' => 'en', 'country' => ['GB', 'US']],
+ ];
+ /**
+ * @var null|\Mollie\Api\Resources\Customer
+ */
+ protected $customer;
+ /**
+ * @var string
+ */
+ private $hash;
+ /**
+ * @var API
+ */
+ private $api;
+ /**
+ * @var JTLMollie
+ */
+ private $paymentMethod;
+ /**
+ * @var Bestellung
+ */
+ private $oBestellung;
+ /**
+ * @var Payment
+ */
+ private $model;
+
+ /**
+ * AbstractCheckout constructor.
+ * @param Bestellung $oBestellung
+ * @param null $api
+ */
+ public function __construct(Bestellung $oBestellung, $api = null)
+ {
+ $this->api = $api;
+ $this->oBestellung = $oBestellung;
+ }
+
+ /**
+ * @param int $kBestellung
+ * @param bool $checkZA
+ * @return bool
+ */
+ public static function isMollie($kBestellung, $checkZA = false)
+ {
+ if ($checkZA) {
+ $res = Shop::DB()->executeQueryPrepared('SELECT * FROM tzahlungsart WHERE cModulId LIKE :cModulId AND kZahlungsart = :kZahlungsart', [
+ ':kZahlungsart' => $kBestellung,
+ ':cModulId' => 'kPlugin_' . self::Plugin()->kPlugin . '_%'
+ ], 1);
+
+ return (bool)$res;
+ }
+
+ return ($res = Shop::DB()->executeQueryPrepared('SELECT kId FROM xplugin_ws_mollie_payments WHERE kBestellung = :kBestellung;', [
+ ':kBestellung' => $kBestellung,
+ ], 1)) && $res->kId;
+ }
+
+ public static function finalizeOrder($sessionHash, $id, $test = false)
+ {
+ try {
+ if ($paymentSession = Shop::DB()->select('tzahlungsession', 'cZahlungsID', $sessionHash)) {
+ if (session_id() !== $paymentSession->cSID) {
+ session_destroy();
+ session_id($paymentSession->cSID);
+ $session = Session::getInstance(true, true);
+ } else {
+ $session = Session::getInstance(false);
+ }
+
+ if (
+ (!isset($paymentSession->nBezahlt) || !$paymentSession->nBezahlt)
+ && (!isset($paymentSession->kBestellung) || !$paymentSession->kBestellung)
+ && isset($_SESSION['Warenkorb']->PositionenArr)
+ && count($_SESSION['Warenkorb']->PositionenArr)
+ ) {
+ $paymentSession->cNotifyID = $id;
+ $paymentSession->dNotify = 'now()';
+ Shop::DB()->update('tzahlungsession', 'cZahlungsID', $sessionHash, $paymentSession);
+
+ $api = new API($test);
+ if (strpos($id, 'tr_') === 0) {
+ $mollie = $api->Client()->payments->get($id);
+ } else {
+ $mollie = $api->Client()->orders->get($id, ['embed' => 'payments']);
+ }
+
+ if (in_array($mollie->status, [OrderStatus::STATUS_PENDING, OrderStatus::STATUS_AUTHORIZED, OrderStatus::STATUS_PAID], true)) {
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'bestellabschluss_inc.php';
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'mailTools.php';
+ $order = finalisiereBestellung();
+ $session->cleanUp();
+ $paymentSession->nBezahlt = 1;
+ $paymentSession->dZeitBezahlt = 'now()';
+ } else {
+ throw new Exception('Mollie Status invalid: ' . $mollie->status . '\n' . print_r([$sessionHash, $id], 1));
+ }
+
+ if ($order->kBestellung) {
+ $paymentSession->kBestellung = $order->kBestellung;
+ Shop::DB()->update('tzahlungsession', 'cZahlungsID', $sessionHash, $paymentSession);
+
+ try {
+ $checkout = self::fromID($id, false, $order);
+ } catch (Exception $e) {
+ if (strpos($id, 'tr_') === 0) {
+ $checkoutClass = PaymentCheckout::class;
+ } else {
+ $checkoutClass = OrderCheckout::class;
+ }
+ $checkout = new $checkoutClass($order, $api);
+ }
+ $checkout->setMollie($mollie)->updateModel()->saveModel();
+ $checkout->updateOrderNumber();
+ $checkout->handleNotification($sessionHash);
+ }
+ } else {
+ Jtllog::writeLog(sprintf('PaymentSession bereits bezahlt: %s - ID: %s => Queue', $sessionHash, $id), JTLLOG_LEVEL_NOTICE);
+ Queue::saveToQueue($id, $id, 'webhook');
+ }
+ } else {
+ Jtllog::writeLog(sprintf('PaymentSession nicht gefunden: %s - ID: %s => Queue', $sessionHash, $id), JTLLOG_LEVEL_NOTICE);
+ Queue::saveToQueue($id, $id, 'webhook');
+ }
+ } catch (Exception $e) {
+ Helper::logExc($e);
+ }
+ }
+
+ /**
+ * @param string $id
+ * @param bool $bFill
+ * @param null|Bestellung $order
+ * @throws RuntimeException
+ * @return OrderCheckout|PaymentCheckout
+ */
+ public static function fromID($id, $bFill = true, Bestellung $order = null)
+ {
+ if (($model = Payment::fromID($id))) {
+ return static::fromModel($model, $bFill, $order);
+ }
+
+ throw new RuntimeException(sprintf('Error loading Order: %s', $id));
+ }
+
+ /**
+ * @param Payment $model
+ * @param bool $bFill
+ * @param null|Bestellung $order
+ * @return OrderCheckout|PaymentCheckout
+ */
+ public static function fromModel($model, $bFill = true, Bestellung $order = null)
+ {
+ if (!$model) {
+ throw new RuntimeException(sprintf('Error loading Order for Model: %s', print_r($model, 1)));
+ }
+
+ $oBestellung = $order;
+ if (!$order) {
+ $oBestellung = new Bestellung($model->kBestellung, $bFill);
+ if (!$oBestellung->kBestellung) {
+ throw new RuntimeException(sprintf('Error loading Bestellung: %s', $model->kBestellung));
+ }
+ }
+
+ if (strpos($model->kID, 'tr_') !== false) {
+ $self = new PaymentCheckout($oBestellung);
+ } else {
+ $self = new OrderCheckout($oBestellung);
+ }
+
+ $self->setModel($model);
+
+ return $self;
+ }
+
+ /**
+ * @return bool
+ */
+ public function saveModel()
+ {
+ return $this->getModel()->save();
+ }
+
+ /**
+ * @return Payment
+ */
+ public function getModel()
+ {
+ if (!$this->model) {
+ $this->model = Payment::fromID($this->oBestellung->kBestellung, 'kBestellung');
+ }
+
+ return $this->model;
+ }
+
+ /**
+ * @param $model
+ * @return $this
+ */
+ protected function setModel($model)
+ {
+ if (!$this->model) {
+ $this->model = $model;
+ } else {
+ throw new RuntimeException('Model already set.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param null $hash
+ * @throws Exception
+ */
+ public function handleNotification($hash = null)
+ {
+ if (!$this->getHash()) {
+ $this->getModel()->cHash = $hash;
+ }
+
+ $this->updateModel()->saveModel();
+ if (!$this->getBestellung()->dBezahltDatum || $this->getBestellung()->dBezahltDatum === '0000-00-00') {
+ if ($incoming = $this->getIncomingPayment()) {
+ $this->PaymentMethod()->addIncomingPayment($this->getBestellung(), $incoming);
+
+ $this->PaymentMethod()->setOrderStatusToPaid($this->getBestellung());
+ static::makeFetchable($this->getBestellung(), $this->getModel());
+ $this->PaymentMethod()->deletePaymentHash($this->getHash());
+ $this->Log(sprintf("Checkout::handleNotification: Bestellung '%s' als bezahlt markiert: %.2f %s", $this->getBestellung()->cBestellNr, (float)$incoming->fBetrag, $incoming->cISO));
+
+ $oZahlungsart = Shop::DB()->selectSingleRow('tzahlungsart', 'cModulId', $this->PaymentMethod()->moduleID);
+ if ($oZahlungsart && (int)$oZahlungsart->nMailSenden === 1) {
+ require_once PFAD_ROOT . 'includes/mailTools.php';
+ $this->PaymentMethod()->sendConfirmationMail($this->getBestellung());
+ }
+ if (!$this->completlyPaid()) {
+ $this->Log(sprintf("Checkout::handleNotification: Bestellung '%s': nicht komplett bezahlt: %.2f %s", $this->getBestellung()->cBestellNr, (float)$incoming->fBetrag, $incoming->cISO), LOGLEVEL_ERROR);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getHash()
+ {
+ if ($this->getModel()->cHash) {
+ return $this->getModel()->cHash;
+ }
+ if (!$this->hash) {
+ $this->hash = $this->PaymentMethod()->generateHash($this->oBestellung);
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * @return JTLMollie
+ */
+ public function PaymentMethod()
+ {
+ if (!$this->paymentMethod) {
+ include_once PFAD_ROOT . PFAD_INCLUDES . 'modules/PaymentMethod.class.php';
+ if ($this->getBestellung()->Zahlungsart && strpos($this->getBestellung()->Zahlungsart->cModulId, "kPlugin_{$this::Plugin()->kPlugin}_") !== false) {
+ try {
+ $this->paymentMethod = PaymentMethod::create($this->getBestellung()->Zahlungsart->cModulId);
+ } catch (Exception $e) {
+ $this->paymentMethod = PaymentMethod::create("kPlugin_{$this::Plugin()->kPlugin}_mollie");
+ }
+ } else {
+ $this->paymentMethod = PaymentMethod::create("kPlugin_{$this::Plugin()->kPlugin}_mollie");
+ }
+ }
+
+ return $this->paymentMethod;
+ }
+
+ /**
+ * @return Bestellung
+ */
+ public function getBestellung()
+ {
+ if (!$this->oBestellung && $this->getModel()->kBestellung) {
+ $this->oBestellung = new Bestellung($this->getModel()->kBestellung, true);
+ }
+
+ return $this->oBestellung;
+ }
+
+ /**
+ * @return $this
+ */
+ public function updateModel()
+ {
+ if ($this->getMollie()) {
+ $this->getModel()->kID = $this->getMollie()->id;
+ $this->getModel()->cLocale = $this->getMollie()->locale;
+ $this->getModel()->fAmount = (float)$this->getMollie()->amount->value;
+ $this->getModel()->cMethod = $this->getMollie()->method;
+ $this->getModel()->cCurrency = $this->getMollie()->amount->currency;
+ $this->getModel()->cStatus = $this->getMollie()->status;
+ if ($this->getMollie()->amountRefunded) {
+ $this->getModel()->fAmountRefunded = $this->getMollie()->amountRefunded->value;
+ }
+ if ($this->getMollie()->amountCaptured) {
+ $this->getModel()->fAmountCaptured = $this->getMollie()->amountCaptured->value;
+ }
+ $this->getModel()->cMode = $this->getMollie()->mode ?: null;
+ $this->getModel()->cRedirectURL = $this->getMollie()->redirectUrl;
+ $this->getModel()->cWebhookURL = $this->getMollie()->webhookUrl;
+ $this->getModel()->cCheckoutURL = $this->getMollie()->getCheckoutUrl();
+ }
+
+ $this->getModel()->kBestellung = $this->getBestellung()->kBestellung;
+ $this->getModel()->cOrderNumber = $this->getBestellung()->cBestellNr;
+ $this->getModel()->cHash = trim($this->getHash(), '_');
+
+ $this->getModel()->bSynced = $this->getModel()->bSynced !== null ? $this->getModel()->bSynced : Helper::getSetting('onlyPaid') !== 'Y';
+
+ return $this;
+ }
+
+ /**
+ * @param false $force
+ * @return null|\Mollie\Api\Resources\Payment|Order
+ */
+ abstract public function getMollie($force = false);
+
+ /**
+ * @return stdClass
+ */
+ abstract public function getIncomingPayment();
+
+ /**
+ * @param Bestellung $oBestellung
+ * @param Payment $model
+ * @return bool
+ */
+ public static function makeFetchable(Bestellung $oBestellung, Payment $model)
+ {
+ // TODO: force ?
+ if ($oBestellung->cAbgeholt === 'Y' && !$model->bSynced) {
+ Shop::DB()->update('tbestellung', 'kBestellung', $oBestellung->kBestellung, (object)['cAbgeholt' => 'N']);
+ }
+ $model->bSynced = true;
+
+ try {
+ return $model->save();
+ } catch (Exception $e) {
+ Jtllog::writeLog(sprintf('Fehler beim speichern des Models: %s / Bestellung: %s', $model->kID, $oBestellung->cBestellNr));
+ }
+
+ return false;
+ }
+
+ public function Log($msg, $level = LOGLEVEL_NOTICE)
+ {
+ try {
+ $data = '';
+ if ($this->getBestellung()) {
+ $data .= '#' . $this->getBestellung()->kBestellung;
+ }
+ if ($this->getMollie()) {
+ $data .= '$' . $this->getMollie()->id;
+ }
+ ZahlungsLog::add($this->PaymentMethod()->moduleID, '[' . microtime(true) . ' - ' . $_SERVER['PHP_SELF'] . '] ' . $msg, $data, $level);
+ } catch (Exception $e) {
+ Jtllog::writeLog('Mollie Log failed: ' . $e->getMessage() . '; Previous Log: ' . print_r([$msg, $level, $data], 1));
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function completlyPaid()
+ {
+ if (
+ $row = Shop::DB()->executeQueryPrepared('SELECT SUM(fBetrag) as fBetragSumme FROM tzahlungseingang WHERE kBestellung = :kBestellung', [
+ ':kBestellung' => $this->getBestellung()->kBestellung
+ ], 1)
+ ) {
+ return $row->fBetragSumme >= round($this->getBestellung()->fGesamtsumme * $this->getBestellung()->fWaehrungsFaktor, 2);
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $kBestellung
+ * * @throws RuntimeException
+ * @return OrderCheckout|PaymentCheckout
+ */
+ public static function fromBestellung($kBestellung)
+ {
+ if ($model = Payment::fromID($kBestellung, 'kBestellung')) {
+ return static::fromModel($model);
+ }
+
+ throw new RuntimeException(sprintf('Error loading Order for Bestellung: %s', $kBestellung));
+ }
+
+ public static function sendReminders()
+ {
+ $reminder = (int)self::Plugin()->oPluginEinstellungAssoc_arr['reminder'];
+
+ if (!$reminder) {
+ return;
+ }
+
+ $sql = 'SELECT p.kID FROM xplugin_ws_mollie_payments p JOIN tbestellung b ON b.kBestellung = p.kBestellung '
+ . "WHERE (p.dReminder IS NULL OR p.dReminder = '0000-00-00 00:00:00') "
+ . 'AND p.dCreatedAt < NOW() - INTERVAL :d MINUTE AND p.dCreatedAt > NOW() - INTERVAL 7 DAY '
+ . "AND p.cStatus IN ('created','open', 'expired', 'failed', 'canceled') AND NOT b.cStatus = '-1'";
+
+ $remindables = Shop::DB()->executeQueryPrepared($sql, [
+ ':d' => $reminder
+ ], 2);
+ foreach ($remindables as $remindable) {
+ try {
+ self::sendReminder($remindable->kID);
+ } catch (Exception $e) {
+ Jtllog::writeLog('AbstractCheckout::sendReminders: ' . $e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * @param $kID
+ * @return bool
+ */
+ public static function sendReminder($kID)
+ {
+ $checkout = self::fromID($kID);
+ $return = true;
+
+ if (!$checkout->getBestellung()->kBestellung || (int)$checkout->getBestellung()->cStatus > BESTELLUNG_STATUS_IN_BEARBEITUNG || (int)$checkout->getBestellung()->cStatus < 0) {
+ return $return;
+ }
+
+
+ try {
+ $repayURL = Shop::getURL() . '/?m_pay=' . md5($checkout->getModel()->kID . '-' . $checkout->getBestellung()->kBestellung);
+
+ $data = new stdClass();
+ $data->tkunde = new Kunde($checkout->getBestellung()->oKunde->kKunde);
+ if ($data->tkunde->kKunde) {
+ $data->Bestellung = $checkout->getBestellung();
+ $data->PayURL = $repayURL;
+ $data->Amount = gibPreisStringLocalized($checkout->getModel()->fAmount, $checkout->getBestellung()->Waehrung); //Preise::getLocalizedPriceString($order->getAmount(), Currency::fromISO($order->getCurrency()), false);
+
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'mailTools.php';
+
+ $mail = new stdClass();
+ $mail->toEmail = $data->tkunde->cMail;
+ $mail->toName = trim((isset($data->tKunde->cVorname)
+ ? $data->tKunde->cVorname
+ : '') . ' ' . (
+ isset($data->tKunde->cNachname)
+ ? $data->tKunde->cNachname
+ : ''
+ )) ?: $mail->toEmail;
+ $data->mail = $mail;
+ if (!($sentMail = sendeMail('kPlugin_' . self::Plugin()->kPlugin . '_zahlungserinnerung', $data))) {
+ $checkout->Log(sprintf("Zahlungserinnerung konnte nicht versand werden: %s\n%s", isset($sentMail->cFehler) ?: print_r($sentMail, 1), print_r($data, 1)), LOGLEVEL_ERROR);
+ $return = false;
+ } else {
+ $checkout->Log(sprintf('Zahlungserinnerung für %s verschickt.', $checkout->getBestellung()->cBestellNr));
+ }
+ } else {
+ $checkout->Log("Kunde '{$checkout->getBestellung()->oKunde->kKunde}' nicht gefunden.", LOGLEVEL_ERROR);
+ }
+ } catch (Exception $e) {
+ $checkout->Log(sprintf('AbstractCheckout::sendReminder: Zahlungserinnerung für %s fehlgeschlagen: %s', $checkout->getBestellung()->cBestellNr, $e->getMessage()));
+ $return = false;
+ }
+ $checkout->getModel()->dReminder = date('Y-m-d H:i:s');
+ $checkout->getModel()->save();
+
+ return $return;
+ }
+
+ /**
+ * @param AbstractCheckout $checkout
+ * @throws ApiException
+ * @return BaseResource|Refund
+ */
+ public static function refund(self $checkout)
+ {
+ if ($checkout->getMollie()->resource === 'order') {
+ /** @var Order $order */
+ $order = $checkout->getMollie();
+ if (in_array($order->status, [OrderStatus::STATUS_CANCELED, OrderStatus::STATUS_EXPIRED, OrderStatus::STATUS_CREATED], true)) {
+ throw new RuntimeException('Bestellung kann derzeit nicht zurückerstattet werden.');
+ }
+ $refund = $order->refundAll();
+ $checkout->Log(sprintf('Bestellung wurde manuell zurückerstattet: %s', $refund->id));
+
+ return $refund;
+ }
+ if ($checkout->getMollie()->resource === 'payment') {
+ /** @var \Mollie\Api\Resources\Payment $payment */
+ $payment = $checkout->getMollie();
+ if (in_array($payment->status, [PaymentStatus::STATUS_CANCELED, PaymentStatus::STATUS_EXPIRED, PaymentStatus::STATUS_OPEN], true)) {
+ throw new RuntimeException('Zahlung kann derzeit nicht zurückerstattet werden.');
+ }
+ $refund = $checkout->API()->Client()->payments->refund($checkout->getMollie(), ['amount' => $checkout->getMollie()->amount]);
+ $checkout->Log(sprintf('Zahlung wurde manuell zurückerstattet: %s', $refund->id));
+
+ return $refund;
+ }
+
+ throw new RuntimeException(sprintf('Unbekannte Resource: %s', $checkout->getMollie()->resource));
+ }
+
+ /**
+ * @return API
+ */
+ public function API()
+ {
+ if (!$this->api) {
+ if ($this->getModel()->kID) {
+ $this->api = new API($this->getModel()->cMode === 'test');
+ } else {
+ $this->api = new API(API::getMode());
+ }
+ }
+
+ return $this->api;
+ }
+
+ /**
+ * @param $checkout
+ * @throws ApiException
+ * @return \Mollie\Api\Resources\Payment|Order
+ */
+ public static function cancel($checkout)
+ {
+ if ($checkout instanceof OrderCheckout) {
+ return OrderCheckout::cancel($checkout);
+ }
+ if ($checkout instanceof PaymentCheckout) {
+ return PaymentCheckout::cancel($checkout);
+ }
+
+ throw new RuntimeException('AbstractCheckout::cancel: Invalid Checkout!');
+ }
+
+ public static function getLocales()
+ {
+ $locales = [
+ 'en_US',
+ 'nl_NL',
+ 'nl_BE',
+ 'fr_FR',
+ 'fr_BE',
+ 'de_DE',
+ 'de_AT',
+ 'de_CH',
+ 'es_ES',
+ 'ca_ES',
+ 'pt_PT',
+ 'it_IT',
+ 'nb_NO',
+ 'sv_SE',
+ 'fi_FI',
+ 'da_DK',
+ 'is_IS',
+ 'hu_HU',
+ 'pl_PL',
+ 'lv_LV',
+ 'lt_LT',];
+
+ $laender = [];
+ $shopLaender = Shop::DB()->executeQuery('SELECT cLaender FROM tversandart', 2);
+ foreach ($shopLaender as $sL) {
+ $laender = array_merge(explode(' ', $sL->cLaender));
+ }
+ $laender = array_unique($laender);
+
+ $result = [];
+ $shopSprachen = Shop::DB()->executeQuery('SELECT * FROM tsprache', 2);
+ foreach ($shopSprachen as $sS) {
+ foreach ($laender as $land) {
+ $result[] = static::getLocale($sS->cISO, $land);
+ }
+ }
+
+ return array_intersect(array_unique($result), $locales);
+ }
+
+ public static function getLocale($cISOSprache = null, $country = null)
+ {
+ if ($cISOSprache === null) {
+ $cISOSprache = gibStandardsprache()->cISO;
+ }
+ if (array_key_exists($cISOSprache, self::$localeLangs)) {
+ $locale = self::$localeLangs[$cISOSprache]['lang'];
+ if ($country && is_array(self::$localeLangs[$cISOSprache]['country']) && in_array($country, self::$localeLangs[$cISOSprache]['country'], true)) {
+ $locale .= '_' . strtoupper($country);
+ } else {
+ $locale .= '_' . self::$localeLangs[$cISOSprache]['country'][0];
+ }
+
+ return $locale;
+ }
+
+ return self::Plugin()->oPluginEinstellungAssoc_arr['fallbackLocale'];
+ }
+
+ public static function getCurrencies()
+ {
+ $currencies = ['AED' => 'AED - United Arab Emirates dirham',
+ 'AFN' => 'AFN - Afghan afghani',
+ 'ALL' => 'ALL - Albanian lek',
+ 'AMD' => 'AMD - Armenian dram',
+ 'ANG' => 'ANG - Netherlands Antillean guilder',
+ 'AOA' => 'AOA - Angolan kwanza',
+ 'ARS' => 'ARS - Argentine peso',
+ 'AUD' => 'AUD - Australian dollar',
+ 'AWG' => 'AWG - Aruban florin',
+ 'AZN' => 'AZN - Azerbaijani manat',
+ 'BAM' => 'BAM - Bosnia and Herzegovina convertible mark',
+ 'BBD' => 'BBD - Barbados dollar',
+ 'BDT' => 'BDT - Bangladeshi taka',
+ 'BGN' => 'BGN - Bulgarian lev',
+ 'BHD' => 'BHD - Bahraini dinar',
+ 'BIF' => 'BIF - Burundian franc',
+ 'BMD' => 'BMD - Bermudian dollar',
+ 'BND' => 'BND - Brunei dollar',
+ 'BOB' => 'BOB - Boliviano',
+ 'BRL' => 'BRL - Brazilian real',
+ 'BSD' => 'BSD - Bahamian dollar',
+ 'BTN' => 'BTN - Bhutanese ngultrum',
+ 'BWP' => 'BWP - Botswana pula',
+ 'BYN' => 'BYN - Belarusian ruble',
+ 'BZD' => 'BZD - Belize dollar',
+ 'CAD' => 'CAD - Canadian dollar',
+ 'CDF' => 'CDF - Congolese franc',
+ 'CHF' => 'CHF - Swiss franc',
+ 'CLP' => 'CLP - Chilean peso',
+ 'CNY' => 'CNY - Renminbi (Chinese) yuan',
+ 'COP' => 'COP - Colombian peso',
+ 'COU' => 'COU - Unidad de Valor Real (UVR)',
+ 'CRC' => 'CRC - Costa Rican colon',
+ 'CUC' => 'CUC - Cuban convertible peso',
+ 'CUP' => 'CUP - Cuban peso',
+ 'CVE' => 'CVE - Cape Verde escudo',
+ 'CZK' => 'CZK - Czech koruna',
+ 'DJF' => 'DJF - Djiboutian franc',
+ 'DKK' => 'DKK - Danish krone',
+ 'DOP' => 'DOP - Dominican peso',
+ 'DZD' => 'DZD - Algerian dinar',
+ 'EGP' => 'EGP - Egyptian pound',
+ 'ERN' => 'ERN - Eritrean nakfa',
+ 'ETB' => 'ETB - Ethiopian birr',
+ 'EUR' => 'EUR - Euro',
+ 'FJD' => 'FJD - Fiji dollar',
+ 'FKP' => 'FKP - Falkland Islands pound',
+ 'GBP' => 'GBP - Pound sterling',
+ 'GEL' => 'GEL - Georgian lari',
+ 'GHS' => 'GHS - Ghanaian cedi',
+ 'GIP' => 'GIP - Gibraltar pound',
+ 'GMD' => 'GMD - Gambian dalasi',
+ 'GNF' => 'GNF - Guinean franc',
+ 'GTQ' => 'GTQ - Guatemalan quetzal',
+ 'GYD' => 'GYD - Guyanese dollar',
+ 'HKD' => 'HKD - Hong Kong dollar',
+ 'HNL' => 'HNL - Honduran lempira',
+ 'HRK' => 'HRK - Croatian kuna',
+ 'HTG' => 'HTG - Haitian gourde',
+ 'HUF' => 'HUF - Hungarian forint',
+ 'IDR' => 'IDR - Indonesian rupiah',
+ 'ILS' => 'ILS - Israeli new shekel',
+ 'INR' => 'INR - Indian rupee',
+ 'IQD' => 'IQD - Iraqi dinar',
+ 'IRR' => 'IRR - Iranian rial',
+ 'ISK' => 'ISK - Icelandic krÛna',
+ 'JMD' => 'JMD - Jamaican dollar',
+ 'JOD' => 'JOD - Jordanian dinar',
+ 'JPY' => 'JPY - Japanese yen',
+ 'KES' => 'KES - Kenyan shilling',
+ 'KGS' => 'KGS - Kyrgyzstani som',
+ 'KHR' => 'KHR - Cambodian riel',
+ 'KMF' => 'KMF - Comoro franc',
+ 'KPW' => 'KPW - North Korean won',
+ 'KRW' => 'KRW - South Korean won',
+ 'KWD' => 'KWD - Kuwaiti dinar',
+ 'KYD' => 'KYD - Cayman Islands dollar',
+ 'KZT' => 'KZT - Kazakhstani tenge',
+ 'LAK' => 'LAK - Lao kip',
+ 'LBP' => 'LBP - Lebanese pound',
+ 'LKR' => 'LKR - Sri Lankan rupee',
+ 'LRD' => 'LRD - Liberian dollar',
+ 'LSL' => 'LSL - Lesotho loti',
+ 'LYD' => 'LYD - Libyan dinar',
+ 'MAD' => 'MAD - Moroccan dirham',
+ 'MDL' => 'MDL - Moldovan leu',
+ 'MGA' => 'MGA - Malagasy ariary',
+ 'MKD' => 'MKD - Macedonian denar',
+ 'MMK' => 'MMK - Myanmar kyat',
+ 'MNT' => 'MNT - Mongolian t?gr?g',
+ 'MOP' => 'MOP - Macanese pataca',
+ 'MRU' => 'MRU - Mauritanian ouguiya',
+ 'MUR' => 'MUR - Mauritian rupee',
+ 'MVR' => 'MVR - Maldivian rufiyaa',
+ 'MWK' => 'MWK - Malawian kwacha',
+ 'MXN' => 'MXN - Mexican peso',
+ 'MXV' => 'MXV - Mexican Unidad de Inversion (UDI)',
+ 'MYR' => 'MYR - Malaysian ringgit',
+ 'MZN' => 'MZN - Mozambican metical',
+ 'NAD' => 'NAD - Namibian dollar',
+ 'NGN' => 'NGN - Nigerian naira',
+ 'NIO' => 'NIO - Nicaraguan cÛrdoba',
+ 'NOK' => 'NOK - Norwegian krone',
+ 'NPR' => 'NPR - Nepalese rupee',
+ 'NZD' => 'NZD - New Zealand dollar',
+ 'OMR' => 'OMR - Omani rial',
+ 'PAB' => 'PAB - Panamanian balboa',
+ 'PEN' => 'PEN - Peruvian sol',
+ 'PGK' => 'PGK - Papua New Guinean kina',
+ 'PHP' => 'PHP - Philippine peso',
+ 'PKR' => 'PKR - Pakistani rupee',
+ 'PLN' => 'PLN - Polish z?oty',
+ 'PYG' => 'PYG - Paraguayan guaranÌ',
+ 'QAR' => 'QAR - Qatari riyal',
+ 'RON' => 'RON - Romanian leu',
+ 'RSD' => 'RSD - Serbian dinar',
+ 'RUB' => 'RUB - Russian ruble',
+ 'RWF' => 'RWF - Rwandan franc',
+ 'SAR' => 'SAR - Saudi riyal',
+ 'SBD' => 'SBD - Solomon Islands dollar',
+ 'SCR' => 'SCR - Seychelles rupee',
+ 'SDG' => 'SDG - Sudanese pound',
+ 'SEK' => 'SEK - Swedish krona/kronor',
+ 'SGD' => 'SGD - Singapore dollar',
+ 'SHP' => 'SHP - Saint Helena pound',
+ 'SLL' => 'SLL - Sierra Leonean leone',
+ 'SOS' => 'SOS - Somali shilling',
+ 'SRD' => 'SRD - Surinamese dollar',
+ 'SSP' => 'SSP - South Sudanese pound',
+ 'STN' => 'STN - S?o TomÈ and PrÌncipe dobra',
+ 'SVC' => 'SVC - Salvadoran colÛn',
+ 'SYP' => 'SYP - Syrian pound',
+ 'SZL' => 'SZL - Swazi lilangeni',
+ 'THB' => 'THB - Thai baht',
+ 'TJS' => 'TJS - Tajikistani somoni',
+ 'TMT' => 'TMT - Turkmenistan manat',
+ 'TND' => 'TND - Tunisian dinar',
+ 'TOP' => 'TOP - Tongan pa?anga',
+ 'TRY' => 'TRY - Turkish lira',
+ 'TTD' => 'TTD - Trinidad and Tobago dollar',
+ 'TWD' => 'TWD - New Taiwan dollar',
+ 'TZS' => 'TZS - Tanzanian shilling',
+ 'UAH' => 'UAH - Ukrainian hryvnia',
+ 'UGX' => 'UGX - Ugandan shilling',
+ 'USD' => 'USD - United States dollar',
+ 'UYI' => 'UYI - Uruguay Peso en Unidades Indexadas',
+ 'UYU' => 'UYU - Uruguayan peso',
+ 'UYW' => 'UYW - Unidad previsional',
+ 'UZS' => 'UZS - Uzbekistan som',
+ 'VES' => 'VES - Venezuelan bolÌvar soberano',
+ 'VND' => 'VND - Vietnamese ??ng',
+ 'VUV' => 'VUV - Vanuatu vatu',
+ 'WST' => 'WST - Samoan tala',
+ 'YER' => 'YER - Yemeni rial',
+ 'ZAR' => 'ZAR - South African rand',
+ 'ZMW' => 'ZMW - Zambian kwacha',
+ 'ZWL' => 'ZWL - Zimbabwean dollar'];
+
+ $shopCurrencies = Shop::DB()->executeQuery('SELECT * FROM twaehrung', 2);
+
+ $result = [];
+
+ foreach ($shopCurrencies as $sC) {
+ if (array_key_exists($sC->cISO, $currencies)) {
+ $result[$sC->cISO] = $currencies[$sC->cISO];
+ }
+ }
+
+ return $result;
+ }
+
+ public function loadRequest(&$options = [])
+ {
+ $oKunde = !$this->getBestellung()->oKunde && $this->PaymentMethod()->duringCheckout ? $_SESSION['Kunde'] : $this->getBestellung()->oKunde;
+ if ($this->getBestellung()) {
+ if (
+ $oKunde->nRegistriert
+ && (
+ $customer = $this->getCustomer(
+ array_key_exists(
+ 'mollie_create_customer',
+ $_SESSION['cPost_arr'] ?: []
+ ) && $_SESSION['cPost_arr']['mollie_create_customer'] === 'Y',
+ $oKunde
+ )
+ )
+ && isset($customer)
+ ) {
+ $options['customerId'] = $customer->id;
+ }
+ $this->amount = Amount::factory($this->getBestellung()->fGesamtsummeKundenwaehrung, $this->getBestellung()->Waehrung->cISO, true);
+ $this->redirectUrl = $this->PaymentMethod()->duringCheckout ? Shop::getURL() . '/bestellabschluss.php?' . http_build_query(['hash' => $this->getHash()]) : $this->PaymentMethod()->getReturnURL($this->getBestellung());
+ $this->metadata = [
+ 'kBestellung' => $this->getBestellung()->kBestellung ?: $this->getBestellung()->cBestellNr,
+ 'kKunde' => $this->getBestellung()->kKunde,
+ 'kKundengruppe' => Session::getInstance()->CustomerGroup()->kKundengruppe,
+ 'cHash' => $this->getHash(),
+ ];
+ }
+
+ $this->locale = self::getLocale($_SESSION['cISOSprache'], Session::getInstance()->Customer()->cLand);
+ $this->webhookUrl = Shop::getURL(true) . '/?' . http_build_query([
+ 'mollie' => 1,
+ 'hash' => $this->getHash(),
+ 'test' => $this->API()->isTest() ?: null,
+ ]);
+
+ $pm = $this->PaymentMethod();
+ $isPayAgain = strpos($_SERVER['PHP_SELF'], 'bestellab_again') !== false;
+ if ($pm::METHOD !== '' && (self::Plugin()->oPluginEinstellungAssoc_arr['resetMethod'] !== 'Y' || !$isPayAgain)) {
+ $this->method = $pm::METHOD;
+ }
+ }
+
+ /**
+ * @todo: Kunde wieder löschbar machen ?!
+ * @param mixed $createOrUpdate
+ * @param null|mixed $oKunde
+ * @return null|\Mollie\Api\Resources\Customer
+ */
+ public function getCustomer($createOrUpdate = false, $oKunde = null)
+ {
+ if (!$oKunde) {
+ $oKunde = $this->getBestellung()->oKunde;
+ }
+
+ if (!$this->customer) {
+ $customerModel = Customer::fromID($oKunde->kKunde, 'kKunde');
+ if ($customerModel->customerId) {
+ try {
+ $this->customer = $this->API()->Client()->customers->get($customerModel->customerId);
+ } catch (ApiException $e) {
+ $this->Log(sprintf('Fehler beim laden des Mollie Customers %s (kKunde: %d): %s', $customerModel->customerId, $customerModel->kKunde, $e->getMessage()), LOGLEVEL_ERROR);
+ }
+ }
+
+ if ($createOrUpdate) {
+ $customer = [
+ 'name' => utf8_encode(trim($oKunde->cVorname . ' ' . $oKunde->cNachname)),
+ 'email' => utf8_encode($oKunde->cMail),
+ 'locale' => self::getLocale($_SESSION['cISOSprache'], $oKunde->cLand),
+ 'metadata' => (object)[
+ 'kKunde' => $oKunde->kKunde,
+ 'kKundengruppe' => $oKunde->kKundengruppe,
+ 'cKundenNr' => utf8_encode($oKunde->cKundenNr),
+ ],
+ ];
+
+ if ($this->customer) { // UPDATE
+ $this->customer->name = $customer['name'];
+ $this->customer->email = $customer['email'];
+ $this->customer->locale = $customer['locale'];
+ $this->customer->metadata = $customer['metadata'];
+
+ try {
+ $this->customer->update();
+ } catch (Exception $e) {
+ $this->Log(sprintf("Fehler beim aktualisieren des Mollie Customers %s: %s\n%s", $this->customer->id, $e->getMessage(), print_r($customer, 1)), LOGLEVEL_ERROR);
+ }
+ } else { // create
+ try {
+ $this->customer = $this->API()->Client()->customers->create($customer);
+ $customerModel->kKunde = $oKunde->kKunde;
+ $customerModel->customerId = $this->customer->id;
+ $customerModel->save();
+ $this->Log(sprintf("Customer '%s' für Kunde %s (%d) bei Mollie angelegt.", $this->customer->id, $this->customer->name, $this->getBestellung()->kKunde));
+ } catch (Exception $e) {
+ $this->Log(sprintf("Fehler beim anlegen eines Mollie Customers: %s\n%s", $e->getMessage(), print_r($customer, 1)), LOGLEVEL_ERROR);
+ }
+ }
+ }
+ }
+
+ return $this->customer;
+ }
+
+ /**
+ * Storno Order
+ */
+ public function storno()
+ {
+ if (in_array((int)$this->getBestellung()->cStatus, [BESTELLUNG_STATUS_OFFEN, BESTELLUNG_STATUS_IN_BEARBEITUNG], true)) {
+ $log = [];
+
+ $conf = Shop::getSettings([CONF_GLOBAL, CONF_TRUSTEDSHOPS]);
+ $nArtikelAnzeigefilter = (int)$conf['global']['artikel_artikelanzeigefilter'];
+
+ foreach ($this->getBestellung()->Positionen as $pos) {
+ if ($pos->kArtikel && $pos->Artikel && $pos->Artikel->cLagerBeachten === 'Y') {
+ $log[] = sprintf('Reset stock of "%s" by %d', $pos->Artikel->cArtNr, -1 * $pos->nAnzahl);
+ self::aktualisiereLagerbestand($pos->Artikel, -1 * $pos->nAnzahl, $pos->WarenkorbPosEigenschaftArr, $nArtikelAnzeigefilter);
+ }
+ }
+ $log[] = sprintf("Cancel order '%s'.", $this->getBestellung()->cBestellNr);
+
+ if (Shop::DB()->executeQueryPrepared('UPDATE tbestellung SET cAbgeholt = \'Y\', cStatus = :cStatus WHERE kBestellung = :kBestellung', [':cStatus' => '-1', ':kBestellung' => $this->getBestellung()->kBestellung], 3)) {
+ $this->Log(implode('\n', $log));
+ }
+ }
+ }
+
+ protected static function aktualisiereLagerbestand($Artikel, $nAnzahl, $WarenkorbPosEigenschaftArr, $nArtikelAnzeigefilter = 1)
+ {
+ $artikelBestand = (float)$Artikel->fLagerbestand;
+
+ if (isset($Artikel->cLagerBeachten) && $Artikel->cLagerBeachten === 'Y') {
+ if (
+ $Artikel->cLagerVariation === 'Y' && is_array($WarenkorbPosEigenschaftArr) && count($WarenkorbPosEigenschaftArr) > 0
+ ) {
+ foreach ($WarenkorbPosEigenschaftArr as $eWert) {
+ $EigenschaftWert = new EigenschaftWert($eWert->kEigenschaftWert);
+ if ($EigenschaftWert->fPackeinheit === .0) {
+ $EigenschaftWert->fPackeinheit = 1;
+ }
+ Shop::DB()->query(
+ 'UPDATE teigenschaftwert
+ SET fLagerbestand = fLagerbestand - ' . ($nAnzahl * $EigenschaftWert->fPackeinheit) . '
+ WHERE kEigenschaftWert = ' . (int)$eWert->kEigenschaftWert,
+ 4
+ );
+ }
+ } elseif ($Artikel->fPackeinheit > 0) {
+ // Stückliste
+ if ($Artikel->kStueckliste > 0) {
+ $artikelBestand = self::aktualisiereStuecklistenLagerbestand($Artikel, $nAnzahl);
+ } else {
+ Shop::DB()->query(
+ 'UPDATE tartikel
+ SET fLagerbestand = IF (fLagerbestand >= ' . ($nAnzahl * $Artikel->fPackeinheit) . ',
+ (fLagerbestand - ' . ($nAnzahl * $Artikel->fPackeinheit) . '), fLagerbestand)
+ WHERE kArtikel = ' . (int)$Artikel->kArtikel,
+ 4
+ );
+ $tmpArtikel = Shop::DB()->select('tartikel', 'kArtikel', (int)$Artikel->kArtikel, null, null, null, null, false, 'fLagerbestand');
+ if ($tmpArtikel !== null) {
+ $artikelBestand = (float)$tmpArtikel->fLagerbestand;
+ }
+ // Stücklisten Komponente
+ if (ArtikelHelper::isStuecklisteKomponente($Artikel->kArtikel)) {
+ self::aktualisiereKomponenteLagerbestand($Artikel->kArtikel, $artikelBestand, isset($Artikel->cLagerKleinerNull) && $Artikel->cLagerKleinerNull === 'Y');
+ }
+ }
+ // Aktualisiere Merkmale in tartikelmerkmal vom Vaterartikel
+ if ($Artikel->kVaterArtikel > 0) {
+ Artikel::beachteVarikombiMerkmalLagerbestand($Artikel->kVaterArtikel, $nArtikelAnzeigefilter);
+ }
+ }
+ }
+
+ return $artikelBestand;
+ }
+
+ protected static function aktualisiereStuecklistenLagerbestand($oStueckListeArtikel, $nAnzahl)
+ {
+ $nAnzahl = (float)$nAnzahl;
+ $kStueckListe = (int)$oStueckListeArtikel->kStueckliste;
+ $bestandAlt = (float)$oStueckListeArtikel->fLagerbestand;
+ $bestandNeu = $bestandAlt;
+ $bestandUeberverkauf = $bestandAlt;
+
+ if ($nAnzahl > 0) {
+ // Gibt es lagerrelevante Komponenten in der Stückliste?
+ $oKomponente_arr = Shop::DB()->query(
+ "SELECT tstueckliste.kArtikel, tstueckliste.fAnzahl
+ FROM tstueckliste
+ JOIN tartikel
+ ON tartikel.kArtikel = tstueckliste.kArtikel
+ WHERE tstueckliste.kStueckliste = $kStueckListe
+ AND tartikel.cLagerBeachten = 'Y'",
+ 2
+ );
+
+ if (is_array($oKomponente_arr) && count($oKomponente_arr) > 0) {
+ // wenn ja, dann wird für diese auch der Bestand aktualisiert
+ $options = Artikel::getDefaultOptions();
+
+ $options->nKeineSichtbarkeitBeachten = 1;
+
+ foreach ($oKomponente_arr as $oKomponente) {
+ $tmpArtikel = new Artikel();
+ $tmpArtikel->fuelleArtikel($oKomponente->kArtikel, $options);
+
+ $komponenteBestand = floor(self::aktualisiereLagerbestand($tmpArtikel, $nAnzahl * $oKomponente->fAnzahl, null) / $oKomponente->fAnzahl);
+
+ if ($komponenteBestand < $bestandNeu && $tmpArtikel->cLagerKleinerNull !== 'Y') {
+ // Neuer Bestand ist der Kleinste Komponententbestand aller Artikel ohne Überverkauf
+ $bestandNeu = $komponenteBestand;
+ } elseif ($komponenteBestand < $bestandUeberverkauf) {
+ // Für Komponenten mit Überverkauf wird der kleinste Bestand ermittelt.
+ $bestandUeberverkauf = $komponenteBestand;
+ }
+ }
+ }
+
+ // Ist der alte gleich dem neuen Bestand?
+ if ($bestandAlt === $bestandNeu) {
+ // Es sind keine lagerrelevanten Komponenten vorhanden, die den Bestand der Stückliste herabsetzen.
+ if ($bestandUeberverkauf === $bestandNeu) {
+ // Es gibt auch keine Komponenten mit Überverkäufen, die den Bestand verringern, deshalb wird
+ // der Bestand des Stücklistenartikels anhand des Verkaufs verringert
+ $bestandNeu = $bestandNeu - $nAnzahl * $oStueckListeArtikel->fPackeinheit;
+ } else {
+ // Da keine lagerrelevanten Komponenten vorhanden sind, wird der kleinste Bestand der
+ // Komponentent mit Überverkauf verwendet.
+ $bestandNeu = $bestandUeberverkauf;
+ }
+
+ Shop::DB()->update('tartikel', 'kArtikel', (int)$oStueckListeArtikel->kArtikel, (object)[
+ 'fLagerbestand' => $bestandNeu,
+ ]);
+ }
+ // Kein Lagerbestands-Update für die Stückliste notwendig! Dies erfolgte bereits über die Komponentenabfrage und
+ // die dortige Lagerbestandsaktualisierung!
+ }
+
+ return $bestandNeu;
+ }
+
+ protected static function aktualisiereKomponenteLagerbestand($kKomponenteArtikel, $fLagerbestand, $bLagerKleinerNull)
+ {
+ $kKomponenteArtikel = (int)$kKomponenteArtikel;
+ $fLagerbestand = (float)$fLagerbestand;
+
+ $oStueckliste_arr = Shop::DB()->query(
+ "SELECT tstueckliste.kStueckliste, tstueckliste.fAnzahl,
+ tartikel.kArtikel, tartikel.fLagerbestand, tartikel.cLagerKleinerNull
+ FROM tstueckliste
+ JOIN tartikel
+ ON tartikel.kStueckliste = tstueckliste.kStueckliste
+ WHERE tstueckliste.kArtikel = $kKomponenteArtikel
+ AND tartikel.cLagerBeachten = 'Y'",
+ 2
+ );
+
+ if (is_array($oStueckliste_arr) && count($oStueckliste_arr) > 0) {
+ foreach ($oStueckliste_arr as $oStueckliste) {
+ // Ist der aktuelle Bestand der Stückliste größer als dies mit dem Bestand der Komponente möglich wäre?
+ $maxAnzahl = floor($fLagerbestand / $oStueckliste->fAnzahl);
+ if ($maxAnzahl < (float)$oStueckliste->fLagerbestand && (!$bLagerKleinerNull || $oStueckliste->cLagerKleinerNull === 'Y')) {
+ // wenn ja, dann den Bestand der Stückliste entsprechend verringern, aber nur wenn die Komponente nicht
+ // überberkaufbar ist oder die gesamte Stückliste Überverkäufe zulässt
+ Shop::DB()->update('tartikel', 'kArtikel', (int)$oStueckliste->kArtikel, (object)[
+ 'fLagerbestand' => $maxAnzahl,
+ ]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return null|array|bool|int|object
+ */
+ public function getLogs()
+ {
+ return Shop::DB()->executeQueryPrepared('SELECT * FROM tzahlungslog WHERE cLogData LIKE :kBestellung OR cLogData LIKE :cBestellNr OR cLogData LIKE :MollieID ORDER BY dDatum DESC, cLog DESC', [
+ ':kBestellung' => '%#' . ($this->getBestellung()->kBestellung ?: '##') . '%',
+ ':cBestellNr' => '%§' . ($this->getBestellung()->cBestellNr ?: '§§') . '%',
+ ':MollieID' => '%$' . ($this->getMollie()->id ?: '$$') . '%',
+ ], 2);
+ }
+
+ /**
+ * @return bool
+ */
+ public function remindable()
+ {
+ return (int)$this->getBestellung()->cStatus !== BESTELLUNG_STATUS_STORNO && !in_array($this->getModel()->cStatus, [PaymentStatus::STATUS_PAID, PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PENDING, OrderStatus::STATUS_COMPLETED, OrderStatus::STATUS_SHIPPING], true);
+ }
+
+ /**
+ * @return string
+ */
+ public function LogData()
+ {
+ $data = '';
+ if ($this->getBestellung()->kBestellung) {
+ $data .= '#' . $this->getBestellung()->kBestellung;
+ }
+ if ($this->getMollie()) {
+ $data .= '$' . $this->getMollie()->id;
+ }
+
+ return $data;
+ }
+
+ abstract public function cancelOrRefund($force = false);
+
+ /**
+ * @param array $paymentOptions
+ * @return Order|Payment
+ */
+ abstract public function create(array $paymentOptions = []);
+
+ /**
+ * @return string
+ */
+ public function getRepayURL()
+ {
+ return Shop::getURL(true) . '/?m_pay=' . md5($this->getModel()->kID . '-' . $this->getBestellung()->kBestellung);
+ }
+
+ public function getDescription()
+ {
+ $descTemplate = trim(Helper::getSetting('paymentDescTpl')) ?: 'Order {orderNumber}';
+ $oKunde = $this->getBestellung()->oKunde ?: $_SESSION['Kunde'];
+
+ return str_replace([
+ '{orderNumber}',
+ '{storeName}',
+ '{customer.firstname}',
+ '{customer.lastname}',
+ '{customer.company}',
+ ], [
+ $this->getBestellung()->cBestellNr,
+ Shop::getSettings([CONF_GLOBAL])['global']['global_shopname'],
+ $oKunde->cVorname,
+ $oKunde->cNachname,
+ $oKunde->cFirma
+
+ ], $descTemplate);
+ }
+
+ abstract protected function updateOrderNumber();
+
+ /**
+ * @param Bestellung $oBestellung
+ * @return $this
+ */
+ protected function setBestellung(Bestellung $oBestellung)
+ {
+ $this->oBestellung = $oBestellung;
+
+ return $this;
+ }
+
+ /**
+ * @param \Mollie\Api\Resources\Payment|Order $model
+ * @return self
+ */
+ abstract protected function setMollie($model);
+}
diff --git a/version/207/class/Checkout/AbstractResource.php b/version/207/class/Checkout/AbstractResource.php
new file mode 100644
index 0000000..7c4439e
--- /dev/null
+++ b/version/207/class/Checkout/AbstractResource.php
@@ -0,0 +1,18 @@
+title = html_entity_decode(substr(trim(($address->cAnrede === 'm' ? Shop::Lang()->get('mr') : Shop::Lang()->get('mrs')) . ' ' . $address->cTitel) ?: null, 0, 20));
+ $resource->givenName = html_entity_decode($address->cVorname);
+ $resource->familyName = html_entity_decode($address->cNachname);
+ $resource->email = html_entity_decode($address->cMail) ?: null;
+
+ if ($organizationName = trim($address->cFirma)) {
+ $resource->organizationName = html_entity_decode($organizationName);
+ }
+
+ // Validity-Check
+ // TODO: Phone, with E.164 check
+ // TODO: Is Email-Format Check needed?
+ if (!$resource->givenName || !$resource->familyName || !$resource->email) {
+ throw ResourceValidityException::trigger(ResourceValidityException::ERROR_REQUIRED, ['givenName', 'familyName', 'email'], $resource);
+ }
+
+ return $resource;
+ }
+}
diff --git a/version/207/class/Checkout/Order/OrderLine.php b/version/207/class/Checkout/Order/OrderLine.php
new file mode 100644
index 0000000..757edf7
--- /dev/null
+++ b/version/207/class/Checkout/Order/OrderLine.php
@@ -0,0 +1,237 @@
+totalAmount->value;
+ }
+ if (abs($sum - (float)$amount->value) > 0) {
+ $diff = (round((float)$amount->value - $sum, 2));
+ if ($diff !== 0.0) {
+ $line = new self();
+ $line->type = $diff > 0 ? OrderLineType::TYPE_SURCHARGE : OrderLineType::TYPE_DISCOUNT;
+ // TODO: Translation needed?
+ $line->name = 'Rundungsausgleich';
+ $line->quantity = 1;
+ $line->unitPrice = Amount::factory($diff, $currency);
+ $line->totalAmount = Amount::factory($diff, $currency);
+ $line->vatRate = '0.00';
+ $line->vatAmount = Amount::factory(0, $currency);
+
+ return $line;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Bestellung $oBestellung
+ * @return OrderLine
+ */
+ public static function getCredit(Bestellung $oBestellung)
+ {
+ $line = new self();
+ $line->type = OrderLineType::TYPE_STORE_CREDIT;
+ $line->name = 'Guthaben';
+ $line->quantity = 1;
+ // TODO: check currency of Guthaben
+ $line->unitPrice = Amount::factory($oBestellung->fGuthaben, $oBestellung->Waehrung->cISO);
+ $line->totalAmount = $line->unitPrice;
+ $line->vatRate = '0.00';
+ $line->vatAmount = Amount::factory(0, $oBestellung->Waehrung->cISO);
+
+ return $line;
+ }
+
+ /**
+ * @param stdClass|WarenkorbPos $oPosition
+ * @param null $currency
+ * @return OrderLine
+ */
+ public static function factory($oPosition, $currency = null)
+ {
+ if (!$oPosition) {
+ throw new RuntimeException('$oPosition invalid:', print_r($oPosition, 1));
+ }
+
+ $resource = new static();
+
+ $resource->fill($oPosition, $currency);
+
+ // Validity Check
+ if (
+ !$resource->name || !$resource->quantity || !$resource->unitPrice || !$resource->totalAmount
+ || !$resource->vatRate || !$resource->vatAmount
+ ) {
+ throw ResourceValidityException::trigger(
+ ResourceValidityException::ERROR_REQUIRED,
+ ['name', 'quantity', 'unitPrice', 'totalAmount', 'vatRate', 'vatAmount'],
+ $resource
+ );
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @param stdClass|WarenkorbPos $oPosition
+ * @param null|stdClass $currency
+ * @return $this
+ * @todo Setting for Fraction handling needed?
+ */
+ protected function fill($oPosition, $currency = null)
+ {
+ if (!$currency) {
+ $currency = Amount::FallbackCurrency();
+ }
+
+ $isKupon = (int)$oPosition->nPosTyp === (int)C_WARENKORBPOS_TYP_KUPON;
+ $isFrac = fmod($oPosition->nAnzahl, 1) !== 0.0;
+
+ // Kupon? set vatRate to 0 and adjust netto
+ $vatRate = $isKupon ? 0 : (float)$oPosition->fMwSt / 100;
+ $netto = $isKupon ? round($oPosition->fPreis * (1 + $vatRate), 4) : round($oPosition->fPreis, 4);
+
+ // Fraction? transform, as it were 1, and set quantity to 1
+ $netto = round(($isFrac ? $netto * (float)$oPosition->nAnzahl : $netto) * $currency->fFaktor, 4);
+ $this->quantity = $isFrac ? 1 : (int)$oPosition->nAnzahl;
+
+ // Fraction? include quantity and unit in name
+ $this->name = $isFrac ? sprintf('%s (%.2f %s)', $oPosition->cName, (float)$oPosition->nAnzahl, $oPosition->cEinheit) : $oPosition->cName;
+ if (!$this->name) {
+ $this->name = '(null)';
+ }
+ $this->mapType($oPosition->nPosTyp, $netto > 0);
+
+ //$unitPriceNetto = round(($currency->fFaktor * $netto), 4);
+
+ $this->unitPrice = Amount::factory(round($netto * (1 + $vatRate), 2), $currency->cISO, false);
+ $this->totalAmount = Amount::factory(round($this->quantity * $this->unitPrice->value, 2), $currency->cISO, false);
+
+ $this->vatRate = number_format($vatRate * 100, 2);
+ $this->vatAmount = Amount::factory(round($this->totalAmount->value - ($this->totalAmount->value / (1 + $vatRate)), 2), $currency->cISO, false);
+
+ $metadata = [];
+
+ // Is Artikel ?
+ if (isset($oPosition->Artikel)) {
+ $this->sku = $oPosition->Artikel->cArtNr;
+ $metadata['kArtikel'] = $oPosition->kArtikel;
+ if ($oPosition->cUnique !== '') {
+ $metadata['cUnique'] = utf8_encode($oPosition->cUnique);
+ }
+ }
+
+ if (isset($oPosition->WarenkorbPosEigenschaftArr) && is_array($oPosition->WarenkorbPosEigenschaftArr) && count($oPosition->WarenkorbPosEigenschaftArr)) {
+ $metadata['properties'] = [];
+ /** @var WarenkorbPosEigenschaft $warenkorbPosEigenschaft */
+ foreach ($oPosition->WarenkorbPosEigenschaftArr as $warenkorbPosEigenschaft) {
+ $metadata['properties'][] = [
+ 'kEigenschaft' => $warenkorbPosEigenschaft->kEigenschaft,
+ 'kEigenschaftWert' => $warenkorbPosEigenschaft->kEigenschaftWert,
+ 'name' => utf8_encode($warenkorbPosEigenschaft->cEigenschaftName),
+ 'value' => utf8_encode($warenkorbPosEigenschaft->cEigenschaftWertName),
+ ];
+ if (strlen(json_encode($metadata)) > 1000) {
+ array_pop($metadata['properties']);
+
+ break;
+ }
+ }
+ }
+ if (json_encode($metadata) !== false) {
+ $this->metadata = $metadata;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $nPosTyp
+ * @param $positive
+ * @return OrderLine
+ */
+ protected function mapType($nPosTyp, $positive)
+ {
+ switch ($nPosTyp) {
+ case C_WARENKORBPOS_TYP_ARTIKEL:
+ case C_WARENKORBPOS_TYP_GRATISGESCHENK:
+ // TODO: digital / Download Artikel?
+ $this->type = OrderLineType::TYPE_PHYSICAL;
+
+ return $this;
+
+ case C_WARENKORBPOS_TYP_VERSANDPOS:
+ $this->type = OrderLineType::TYPE_SHIPPING_FEE;
+
+ return $this;
+
+ case C_WARENKORBPOS_TYP_GUTSCHEIN:
+ case C_WARENKORBPOS_TYP_KUPON:
+ case C_WARENKORBPOS_TYP_NEUKUNDENKUPON:
+ $this->type = OrderLineType::TYPE_DISCOUNT;
+
+ return $this;
+
+ case C_WARENKORBPOS_TYP_VERPACKUNG:
+ case C_WARENKORBPOS_TYP_VERSANDZUSCHLAG:
+ case C_WARENKORBPOS_TYP_ZAHLUNGSART:
+ case C_WARENKORBPOS_TYP_VERSAND_ARTIKELABHAENGIG:
+ case C_WARENKORBPOS_TYP_NACHNAHMEGEBUEHR:
+ $this->type = OrderLineType::TYPE_SURCHARGE;
+
+ return $this;
+
+ default:
+ $this->type = $positive ? OrderLineType::TYPE_SURCHARGE : OrderLineType::TYPE_DISCOUNT;
+
+ return $this;
+ }
+ }
+}
diff --git a/version/207/class/Checkout/OrderCheckout.php b/version/207/class/Checkout/OrderCheckout.php
new file mode 100644
index 0000000..1d61b6d
--- /dev/null
+++ b/version/207/class/Checkout/OrderCheckout.php
@@ -0,0 +1,406 @@
+getMollie()->status !== OrderStatus::STATUS_AUTHORIZED && $checkout->getMollie()->status !== OrderStatus::STATUS_SHIPPING) {
+ throw new RuntimeException('Nur autorisierte Zahlungen können erfasst werden!');
+ }
+ $shipment = $checkout->API()->Client()->shipments->createFor($checkout->getMollie(), ['lines' => []]);
+ $checkout->Log(sprintf('Bestellung wurde manuell erfasst/versandt: %s', $shipment->id));
+
+ return $shipment->id;
+ }
+
+ /**
+ * @param false $force
+ * @return null|Order
+ */
+ public function getMollie($force = false)
+ {
+ if ($force || (!$this->order && $this->getModel()->kID)) {
+ try {
+ $this->order = $this->API()->Client()->orders->get($this->getModel()->kID, ['embed' => 'payments,shipments,refunds']);
+ } catch (Exception $e) {
+ throw new RuntimeException(sprintf('Mollie-Order \'%s\' konnte nicht geladen werden: %s', $this->getModel()->kID, $e->getMessage()));
+ }
+ }
+
+ return $this->order;
+ }
+
+ /**
+ * @param OrderCheckout $checkout
+ * @throws ApiException
+ * @return Order
+ */
+ public static function cancel($checkout)
+ {
+ if (!$checkout->getMollie() || !$checkout->getMollie()->isCancelable) {
+ throw new RuntimeException('Bestellung kann nicht abgebrochen werden.');
+ }
+ $order = $checkout->getMollie()->cancel();
+ $checkout->Log('Bestellung wurde manuell abgebrochen.');
+
+ return $order;
+ }
+
+ /**
+ * @throws Exception
+ * @return array
+ */
+ public function getShipments()
+ {
+ $shipments = [];
+ $lieferschien_arr = Shop::DB()->executeQueryPrepared('SELECT kLieferschein FROM tlieferschein WHERE kInetBestellung = :kBestellung', [
+ ':kBestellung' => $this->getBestellung()->kBestellung
+ ], 2);
+
+ foreach ($lieferschien_arr as $lieferschein) {
+ $shipments[] = new Shipment($lieferschein->kLieferschein, $this);
+ }
+
+ return $shipments;
+ }
+
+ /**
+ * @param mixed $force
+ * @throws ApiException
+ * @return string
+ */
+ public function cancelOrRefund($force = false)
+ {
+ if (!$this->getMollie()) {
+ throw new RuntimeException('Mollie-Order konnte nicht geladen werden: ' . $this->getModel()->kID);
+ }
+ if ($force || (int)$this->getBestellung()->cStatus === BESTELLUNG_STATUS_STORNO) {
+ if ($this->getMollie()->isCancelable) {
+ $res = $this->getMollie()->cancel();
+ $result = 'Order cancelled, Status: ' . $res->status;
+ } else {
+ $res = $this->getMollie()->refundAll();
+ $result = 'Order Refund initiiert, Status: ' . $res->status;
+ }
+ $this->PaymentMethod()->Log('OrderCheckout::cancelOrRefund: ' . $result, $this->LogData());
+
+ return $result;
+ }
+
+ throw new RuntimeException('Bestellung ist derzeit nicht storniert, Status: ' . $this->getBestellung()->cStatus);
+ }
+
+ /**
+ * @param array $paymentOptions
+ * @return Order
+ */
+ public function create(array $paymentOptions = [])
+ {
+ if ($this->getModel()->kID) {
+ try {
+ $this->order = $this->API()->Client()->orders->get($this->getModel()->kID, ['embed' => 'payments']);
+ if (in_array($this->order->status, [OrderStatus::STATUS_COMPLETED, OrderStatus::STATUS_PAID, OrderStatus::STATUS_AUTHORIZED, OrderStatus::STATUS_PENDING], true)) {
+ $this->handleNotification();
+
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errAlreadyPaid']);
+ }
+ if ($this->order->status === OrderStatus::STATUS_CREATED) {
+ if ($this->order->payments()) {
+ /** @var Payment $payment */
+ foreach ($this->order->payments() as $payment) {
+ if ($payment->status === PaymentStatus::STATUS_OPEN) {
+ $this->setPayment($payment);
+
+ break;
+ }
+ }
+ }
+ if (!$this->getPayment()) {
+ $this->setPayment($this->API()->Client()->orderPayments->createForId($this->getModel()->kID, $paymentOptions));
+ }
+ $this->updateModel()->saveModel();
+
+ return $this->getMollie(true);
+ }
+ } catch (RuntimeException $e) {
+ throw $e;
+ } catch (Exception $e) {
+ $this->Log(sprintf("OrderCheckout::create: Letzte Order '%s' konnte nicht geladen werden: %s, versuche neue zu erstellen.", $this->getModel()->kID, $e->getMessage()), LOGLEVEL_ERROR);
+ }
+ }
+
+ try {
+ $req = $this->loadRequest($paymentOptions)->jsonSerialize();
+ $this->order = $this->API()->Client()->orders->create($req);
+ $this->Log(sprintf("Order für '%s' wurde erfolgreich angelegt: %s", $this->getBestellung()->cBestellNr, $this->order->id));
+ $this->updateModel()->saveModel();
+
+ return $this->order;
+ } catch (Exception $e) {
+ $this->Log(sprintf("OrderCheckout::create: Neue Order '%s' konnte nicht erstellt werden: %s.", $this->getBestellung()->cBestellNr, $e->getMessage()), LOGLEVEL_ERROR);
+
+ throw new RuntimeException(sprintf("Order für '%s' konnte nicht angelegt werden: %s", $this->getBestellung()->cBestellNr, $e->getMessage()));
+ }
+ }
+
+ /**
+ * @param mixed $search
+ * @return null|Payment
+ */
+ public function getPayment($search = false)
+ {
+ if (!$this->_payment && $search && $this->getMollie()) {
+ foreach ($this->getMollie()->payments() as $payment) {
+ if (
+ in_array($payment->status, [
+ PaymentStatus::STATUS_AUTHORIZED,
+ PaymentStatus::STATUS_PAID,
+ PaymentStatus::STATUS_PENDING,
+ ], true)
+ ) {
+ $this->_payment = $payment;
+
+ break;
+ }
+ }
+ }
+
+ return $this->_payment;
+ }
+
+ /**
+ * @param $payment
+ * @return $this
+ */
+ public function setPayment($payment)
+ {
+ $this->_payment = $payment;
+
+ return $this;
+ }
+
+ /**
+ * @return $this|OrderCheckout
+ */
+ public function updateModel()
+ {
+ parent::updateModel();
+
+ if (!$this->getPayment() && $this->getMollie() && $this->getMollie()->payments()) {
+ /** @var Payment $payment */
+ foreach ($this->getMollie()->payments() as $payment) {
+ if (in_array($payment->status, [PaymentStatus::STATUS_OPEN, PaymentStatus::STATUS_PENDING, PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PAID], true)) {
+ $this->setPayment($payment);
+
+ break;
+ }
+ }
+ }
+ if ($this->getPayment()) {
+ $this->getModel()->cTransactionId = $this->getPayment()->id;
+ }
+ if ($this->getMollie()) {
+ $this->getModel()->cCheckoutURL = $this->getMollie()->getCheckoutUrl();
+ $this->getModel()->cWebhookURL = $this->getMollie()->webhookUrl;
+ $this->getModel()->cRedirectURL = $this->getMollie()->redirectUrl;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @return $this|OrderCheckout
+ */
+ public function loadRequest(&$options = [])
+ {
+ parent::loadRequest($options);
+
+ $this->orderNumber = $this->getBestellung()->cBestellNr;
+
+ $this->billingAddress = Address::factory($this->getBestellung()->oRechnungsadresse);
+ if ($this->getBestellung()->Lieferadresse !== null) {
+ if (!$this->getBestellung()->Lieferadresse->cMail) {
+ $this->getBestellung()->Lieferadresse->cMail = $this->getBestellung()->oRechnungsadresse->cMail;
+ }
+ $this->shippingAddress = Address::factory($this->getBestellung()->Lieferadresse);
+ }
+
+ if (
+ !empty(Session::getInstance()->Customer()->dGeburtstag)
+ && Session::getInstance()->Customer()->dGeburtstag !== '0000-00-00'
+ && preg_match('/^\d{4}-\d{2}-\d{2}$/', trim(Session::getInstance()->Customer()->dGeburtstag))
+ ) {
+ $this->consumerDateOfBirth = trim(Session::getInstance()->Customer()->dGeburtstag);
+ }
+
+ $lines = [];
+ foreach ($this->getBestellung()->Positionen as $oPosition) {
+ $lines[] = OrderLine::factory($oPosition, $this->getBestellung()->Waehrung);
+ }
+
+ if ($this->getBestellung()->GuthabenNutzen && $this->getBestellung()->fGuthaben > 0) {
+ $lines[] = OrderLine::getCredit($this->getBestellung());
+ }
+
+ if ($comp = OrderLine::getRoundingCompensation($lines, $this->amount, $this->getBestellung()->Waehrung->cISO)) {
+ $lines[] = $comp;
+ }
+ $this->lines = $lines;
+
+ if ($dueDays = $this->PaymentMethod()->getExpiryDays()) {
+ try {
+ // #145
+ //$max = $this->method && strpos($this->method, 'klarna') !== false ? 28 : 100;
+ $date = new DateTime(sprintf('+%d DAYS', $dueDays), new DateTimeZone('UTC'));
+ $this->expiresAt = $date->format('Y-m-d');
+ } catch (Exception $e) {
+ $this->Log($e->getMessage(), LOGLEVEL_ERROR);
+ }
+ }
+
+ $this->payment = $options;
+
+ return $this;
+ }
+
+ /**
+ * @return null|object
+ */
+ public function getIncomingPayment()
+ {
+ if (!$this->getMollie(true)) {
+ return null;
+ }
+
+ $cHinweis = sprintf('%s / %s', $this->getMollie()->id, $this->getPayment(true)->id);
+ if (Helper::getSetting('wawiPaymentID') === 'ord') {
+ $cHinweis = $this->getMollie()->id;
+ } elseif (Helper::getSetting('wawiPaymentID') === 'tr') {
+ $cHinweis = $this->getPayment(true)->id;
+ }
+
+
+ /** @var Payment $payment */
+ /** @noinspection NullPointerExceptionInspection */
+ foreach ($this->getMollie()->payments() as $payment) {
+ if (
+ in_array(
+ $payment->status,
+ [PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PAID],
+ true
+ )
+ ) {
+ $this->setPayment($payment);
+ $data = (object)[
+ 'fBetrag' => (float)$payment->amount->value,
+ 'cISO' => $payment->amount->currency,
+ 'cZahler' => $payment->details->paypalPayerId ?: $payment->customerId,
+ 'cHinweis' => $payment->details->paypalReference ?: $cHinweis,
+ ];
+ if (isset($payment->details, $payment->details->paypalFee)) {
+ $data->fZahlungsgebuehr = $payment->details->paypalFee->value;
+ }
+
+ return $data;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function updateOrderNumber()
+ {
+ try {
+ if ($this->getMollie()) {
+ $this->getMollie()->orderNumber = $this->getBestellung()->cBestellNr;
+ $this->getMollie()->webhookUrl = Shop::getURL() . '/?mollie=1';
+ $this->getMollie()->update();
+ }
+ if ($this->getModel()->cTransactionId) {
+ $this->API()->Client()->payments->update($this->getModel()->cTransactionId, [
+ 'description' => $this->getDescription()
+ ]);
+ }
+ } catch (Exception $e) {
+ $this->Log('OrderCheckout::updateOrderNumber:' . $e->getMessage(), LOGLEVEL_ERROR);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param Order $model
+ * @return $this|AbstractCheckout
+ */
+ protected function setMollie($model)
+ {
+ if ($model instanceof Order) {
+ $this->order = $model;
+ }
+
+ return $this;
+ }
+}
diff --git a/version/207/class/Checkout/Payment/Address.php b/version/207/class/Checkout/Payment/Address.php
new file mode 100644
index 0000000..7e33875
--- /dev/null
+++ b/version/207/class/Checkout/Payment/Address.php
@@ -0,0 +1,53 @@
+streetAndNumber = html_entity_decode($address->cStrasse . ' ' . $address->cHausnummer);
+ $resource->postalCode = html_entity_decode($address->cPLZ);
+ $resource->city = html_entity_decode($address->cOrt);
+ $resource->country = html_entity_decode($address->cLand);
+
+ if (
+ isset($address->cAdressZusatz)
+ && trim($address->cAdressZusatz) !== ''
+ ) {
+ $resource->streetAdditional = html_entity_decode(trim($address->cAdressZusatz));
+ }
+
+ // Validity-Check
+ // TODO: Check for valid Country Code?
+ // TODO: Check PostalCode requirement Country?
+ if (!$resource->streetAndNumber || !$resource->city || !$resource->country) {
+ throw ResourceValidityException::trigger(ResourceValidityException::ERROR_REQUIRED, ['streetAndNumber', 'city', 'country'], $resource);
+ }
+
+ return $resource;
+ }
+}
diff --git a/version/207/class/Checkout/Payment/Amount.php b/version/207/class/Checkout/Payment/Amount.php
new file mode 100644
index 0000000..c11c64c
--- /dev/null
+++ b/version/207/class/Checkout/Payment/Amount.php
@@ -0,0 +1,75 @@
+ true [5 Rappen Rounding])
+ * @return Amount
+ */
+ public static function factory($value, $currency = null, $useRounding = false)
+ {
+ if (!$currency) {
+ $currency = static::FallbackCurrency()->cISO;
+ }
+
+ $resource = new static();
+
+ $resource->currency = $currency;
+ //$resource->value = number_format(round($useRounding ? $resource->round($value * (float)$currency->fFaktor) : $value * (float)$currency->fFaktor, 2), 2, '.', '');
+ $resource->value = number_format(round($useRounding ? $resource->round($value) : $value, 2), 2, '.', '');
+
+ // Validity Check
+ // TODO: Check ISO Code?
+ // TODO: Check Value
+ if (!$resource->currency || !$resource->value) {
+ throw ResourceValidityException::trigger(ResourceValidityException::ERROR_REQUIRED, ['currency', 'value'], $resource);
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @return stdClass
+ */
+ public static function FallbackCurrency()
+ {
+ return isset($_SESSION['Waehrung']) ? $_SESSION['Waehrung'] : Shop::DB()->select('twaehrung', 'cStandard', 'Y');
+ }
+
+ /**
+ * Check if 5 Rappen rounding is necessary
+ *
+ * @param mixed $value
+ * @return float
+ */
+ protected function round($value)
+ {
+ $conf = Shop::getSettings([CONF_KAUFABWICKLUNG]);
+ if (isset($conf['kaufabwicklung']['bestellabschluss_runden5']) && (int)$conf['kaufabwicklung']['bestellabschluss_runden5'] === 1) {
+ $value = round($value * 20) / 20;
+ }
+
+ return $value;
+ }
+}
diff --git a/version/207/class/Checkout/PaymentCheckout.php b/version/207/class/Checkout/PaymentCheckout.php
new file mode 100644
index 0000000..568be4e
--- /dev/null
+++ b/version/207/class/Checkout/PaymentCheckout.php
@@ -0,0 +1,235 @@
+getMollie()->isCancelable) {
+ throw new RuntimeException('Zahlung kann nicht abgebrochen werden.');
+ }
+ $payment = $checkout->API()->Client()->payments->cancel($checkout->getMollie()->id);
+ $checkout->Log('Zahlung wurde manuell abgebrochen.');
+
+ return $payment;
+ }
+
+ /**
+ * @param false $force
+ * @return null|Payment
+ */
+ public function getMollie($force = false)
+ {
+ if ($force || (!$this->getPayment() && $this->getModel()->kID)) {
+ try {
+ $this->setPayment($this->API()->Client()->payments->get($this->getModel()->kID, ['embed' => 'refunds']));
+ } catch (Exception $e) {
+ throw new RuntimeException('Mollie-Payment konnte nicht geladen werden: ' . $e->getMessage());
+ }
+ }
+
+ return $this->getPayment();
+ }
+
+ /**
+ * @return null|Payment
+ */
+ public function getPayment()
+ {
+ return $this->_payment;
+ }
+
+ /**
+ * @param $payment
+ * @return $this
+ */
+ public function setPayment($payment)
+ {
+ $this->_payment = $payment;
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $force
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @return string
+ */
+ public function cancelOrRefund($force = false)
+ {
+ if (!$this->getMollie()) {
+ throw new RuntimeException('Mollie-Order konnte nicht geladen werden: ' . $this->getModel()->kID);
+ }
+ if ($force || (int)$this->getBestellung()->cStatus === BESTELLUNG_STATUS_STORNO) {
+ if ($this->getMollie()->isCancelable) {
+ $res = $this->API()->Client()->payments->cancel($this->getMollie()->id);
+ $result = 'Payment cancelled, Status: ' . $res->status;
+ } else {
+ $res = $this->API()->Client()->payments->refund($this->getMollie(), ['amount' => $this->getMollie()->amount]);
+ $result = 'Payment Refund initiiert, Status: ' . $res->status;
+ }
+ $this->PaymentMethod()->Log('PaymentCheckout::cancelOrRefund: ' . $result, $this->LogData());
+
+ return $result;
+ }
+
+ throw new RuntimeException('Bestellung ist derzeit nicht storniert, Status: ' . $this->getBestellung()->cStatus);
+ }
+
+ /**
+ * @param array $paymentOptions
+ * @return Payment
+ */
+ public function create(array $paymentOptions = [])
+ {
+ if ($this->getModel()->kID) {
+ try {
+ $this->setPayment($this->API()->Client()->payments->get($this->getModel()->kID));
+ if ($this->getPayment()->status === PaymentStatus::STATUS_PAID) {
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errAlreadyPaid']);
+ }
+ if ($this->getPayment()->status === PaymentStatus::STATUS_OPEN) {
+ $this->updateModel()->saveModel();
+
+ return $this->getPayment();
+ }
+ } catch (RuntimeException $e) {
+ //$this->Log(sprintf("PaymentCheckout::create: Letzte Transaktion '%s' konnte nicht geladen werden: %s", $this->getModel()->kID, $e->getMessage()), LOGLEVEL_ERROR);
+ throw $e;
+ } catch (Exception $e) {
+ $this->Log(sprintf("PaymentCheckout::create: Letzte Transaktion '%s' konnte nicht geladen werden: %s, versuche neue zu erstellen.", $this->getModel()->kID, $e->getMessage()), LOGLEVEL_ERROR);
+ }
+ }
+
+ try {
+ $req = $this->loadRequest($paymentOptions)->jsonSerialize();
+ $this->setPayment($this->API()->Client()->payments->create($req));
+ $this->Log(sprintf("Payment für '%s' wurde erfolgreich angelegt: %s", $this->getBestellung()->cBestellNr, $this->getPayment()->id));
+ $this->updateModel()->saveModel();
+
+ return $this->getPayment();
+ } catch (Exception $e) {
+ $this->Log(sprintf("PaymentCheckout::create: Neue Transaktion für '%s' konnte nicht erstellt werden: %s.", $this->getBestellung()->cBestellNr, $e->getMessage()), LOGLEVEL_ERROR);
+
+ throw new RuntimeException(sprintf('Mollie-Payment \'%s\' konnte nicht geladen werden: %s', $this->getBestellung()->cBestellNr, $e->getMessage()));
+ }
+ }
+
+ /**
+ * @param array $options
+ * @return $this|PaymentCheckout
+ */
+ public function loadRequest(&$options = [])
+ {
+ parent::loadRequest($options);
+
+ foreach ($options as $key => $value) {
+ $this->$key = $value;
+ }
+
+
+ $this->description = $this->getDescription();
+
+ return $this;
+ }
+
+
+
+ /**
+ * @return null|object
+ */
+ public function getIncomingPayment()
+ {
+ if (!$this->getMollie()) {
+ return null;
+ }
+
+ if (in_array($this->getMollie()->status, [PaymentStatus::STATUS_AUTHORIZED, PaymentStatus::STATUS_PAID], true)) {
+ $data = [];
+ $data['fBetrag'] = (float)$this->getMollie()->amount->value;
+ $data['cISO'] = $this->getMollie()->amount->currency;
+ $data['cZahler'] = $this->getMollie()->details->paypalPayerId ?: $this->getMollie()->customerId;
+ $data['cHinweis'] = $this->getMollie()->details->paypalReference ?: $this->getMollie()->id;
+ if (isset($this->getMollie()->details, $this->getMollie()->details->paypalFee)) {
+ $data['fZahlungsgebuehr'] = $this->getMollie()->details->paypalFee->value;
+ }
+
+ return (object)$data;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Payment $model
+ * @return $this|AbstractCheckout
+ */
+ protected function setMollie($model)
+ {
+ if ($model instanceof Payment) {
+ $this->setPayment($model);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function updateOrderNumber()
+ {
+ try {
+ if ($this->getMollie()) {
+ $this->getMollie()->description = $this->getDescription();
+ $this->getMollie()->webhookUrl = Shop::getURL() . '/?mollie=1';
+ $this->getMollie()->update();
+ }
+ } catch (Exception $e) {
+ $this->Log('OrderCheckout::updateOrderNumber:' . $e->getMessage(), LOGLEVEL_ERROR);
+ }
+
+ return $this;
+ }
+}
diff --git a/version/207/class/ExclusiveLock.php b/version/207/class/ExclusiveLock.php
new file mode 100644
index 0000000..c7b834d
--- /dev/null
+++ b/version/207/class/ExclusiveLock.php
@@ -0,0 +1,70 @@
+key = $key;
+ $this->path = rtrim(realpath($path), '/') . '/';
+ if (!is_dir($path) || !is_writable($path)) {
+ throw new RuntimeException("Lock Path '$path' doesn't exist, or is not writable!");
+ }
+ //create a new resource or get exisitng with same key
+ $this->file = fopen($this->path . "$key.lockfile", 'wb+');
+ }
+
+
+ public function __destruct()
+ {
+ if ($this->own === true) {
+ $this->unlock();
+ }
+ }
+
+ /** @noinspection ForgottenDebugOutputInspection */
+ public function unlock()
+ {
+ $key = $this->key;
+ if ($this->own === true) {
+ if (!flock($this->file, LOCK_UN)) { //failed
+ error_log("ExclusiveLock::lock FAILED to release lock [$key]");
+
+ return false;
+ }
+ fwrite($this->file, 'Unlocked - ' . microtime(true) . "\n");
+ fflush($this->file);
+ $this->own = false;
+ } else {
+ error_log("ExclusiveLock::unlock called on [$key] but its not acquired by caller");
+ }
+
+ return true; // success
+ }
+
+ public function lock()
+ {
+ if (!flock($this->file, LOCK_EX | LOCK_NB)) { //failed
+ return false;
+ }
+ fwrite($this->file, 'Locked - ' . microtime(true) . "\n");
+ fflush($this->file);
+
+ $this->own = true;
+
+ return true; // success
+ }
+}
diff --git a/version/207/class/Helper.php b/version/207/class/Helper.php
new file mode 100644
index 0000000..b03874f
--- /dev/null
+++ b/version/207/class/Helper.php
@@ -0,0 +1,363 @@
+short_url != '' ? $release->short_url : $release->full_url;
+ $filename = basename($release->full_url);
+ $tmpDir = PFAD_ROOT . PFAD_COMPILEDIR;
+ $pluginsDir = PFAD_ROOT . PFAD_PLUGIN;
+
+ // 1. PRE-CHECKS
+ if (file_exists($pluginsDir . self::oPlugin()->cVerzeichnis . '/.git') && is_dir($pluginsDir . self::oPlugin()->cVerzeichnis . '/.git')) {
+ throw new Exception('Pluginordner enthält ein GIT Repository, kein Update möglich!');
+ }
+
+ if (!function_exists('curl_exec')) {
+ throw new Exception('cURL ist nicht verfügbar!!');
+ }
+ if (!is_writable($tmpDir)) {
+ throw new Exception("Temporäres Verzeichnis_'{$tmpDir}' ist nicht beschreibbar!");
+ }
+ if (!is_writable($pluginsDir . self::oPlugin()->cVerzeichnis)) {
+ throw new Exception("Plugin Verzeichnis_'" . $pluginsDir . self::oPlugin()->cVerzeichnis . "' ist nicht beschreibbar!");
+ }
+ if (file_exists($tmpDir . $filename) && !unlink($tmpDir . $filename)) {
+ throw new Exception("Temporäre Datei '" . $tmpDir . $filename . "' konnte nicht gelöscht werden!");
+ }
+
+ // 2. DOWNLOAD
+ $fp = fopen($tmpDir . $filename, 'w+');
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 50);
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ curl_exec($ch);
+ $info = curl_getinfo($ch);
+ curl_close($ch);
+ fclose($fp);
+ if ($info['http_code'] !== 200) {
+ throw new Exception("Unerwarteter Status Code '" . $info['http_code'] . "'!");
+ }
+ if ($info['download_content_length'] <= 0) {
+ throw new Exception("Unerwartete Downloadgröße '" . $info['download_content_length'] . "'!");
+ }
+
+ // 3. UNZIP
+ require_once PFAD_ROOT . PFAD_PCLZIP . 'pclzip.lib.php';
+ $zip = new PclZip($tmpDir . $filename);
+ $content = $zip->listContent();
+
+ if (!is_array($content) || !isset($content[0]['filename']) || strpos($content[0]['filename'], '.') !== false) {
+ throw new Exception('Das Zip-Archiv ist leider ungültig!');
+ }
+ $unzipPath = PFAD_ROOT . PFAD_PLUGIN;
+ $res = $zip->extract(PCLZIP_OPT_PATH, $unzipPath, PCLZIP_OPT_REPLACE_NEWER);
+ if ($res !== 0) {
+ header('Location: ' . Shop::getURL() . DIRECTORY_SEPARATOR . PFAD_ADMIN . 'pluginverwaltung.php', true);
+ } else {
+ throw new Exception('Entpacken fehlgeschlagen: ' . $zip->errorCode());
+ }
+ }
+
+ /**
+ * @param bool $force
+ * @throws Exception
+ * @return mixed
+ */
+ public static function getLatestRelease($force = false)
+ {
+ $lastCheck = (int)self::getSetting(__NAMESPACE__ . '_upd');
+ $lastRelease = file_exists(PFAD_ROOT . PFAD_COMPILEDIR . __NAMESPACE__ . '_upd') ? file_get_contents(PFAD_ROOT . PFAD_COMPILEDIR . __NAMESPACE__ . '_upd') : false;
+ if ($force || !$lastCheck || !$lastRelease || ($lastCheck + 12 * 60 * 60) < time()) {
+ $curl = curl_init('https://api.dash.bar/release/' . __NAMESPACE__);
+ @curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
+ @curl_setopt($curl, CURLOPT_TIMEOUT, 5);
+ @curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ @curl_setopt($curl, CURLOPT_HEADER, 0);
+ @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+ @curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+
+ $data = curl_exec($curl);
+ $statusCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ @curl_close($curl);
+ if ($statusCode !== 200) {
+ throw new Exception(__NAMESPACE__ . ': Could not fetch release info: ' . $statusCode);
+ }
+ $json = json_decode($data);
+ if (json_last_error() || $json->status != 'ok') {
+ throw new Exception(__NAMESPACE__ . ': Could not decode release info: ' . $data);
+ }
+ self::setSetting(__NAMESPACE__ . '_upd', time());
+ file_put_contents(PFAD_ROOT . PFAD_COMPILEDIR . __NAMESPACE__ . '_upd', json_encode($json->data));
+
+ return $json->data;
+ }
+
+ return json_decode($lastRelease);
+ }
+
+ /**
+ * Register PSR-4 autoloader
+ * Licence-Check
+ * @return bool
+ */
+ public static function init()
+ {
+ ini_set('xdebug.default_enable', defined('WS_XDEBUG_ENABLED'));
+
+ return self::autoload();
+ }
+
+ /**
+ * @var stdClass[]
+ */
+ public static $alerts = [];
+
+ /**
+ * Usage:
+ *
+ * Helper::addAlert('Success Message', 'success', 'namespace');
+ *
+ * @param $content
+ * @param $type
+ * @param $namespace
+ */
+ public static function addAlert($content, $type, $namespace)
+ {
+ if (!array_key_exists($namespace, self::$alerts)) {
+ self::$alerts[$namespace] = new stdClass();
+ }
+
+ self::$alerts[$namespace]->{$type . '_' . microtime(true)} = $content;
+ }
+
+ /**
+ * Usage in Smarty:
+ *
+ * {ws_mollie\Helper::showAlerts('namespace')}
+ *
+ * @param $namespace
+ * @throws SmartyException
+ * @return string
+ */
+ public static function showAlerts($namespace)
+ {
+ if (array_key_exists($namespace, self::$alerts) && file_exists(self::oPlugin()->cAdminmenuPfad . '../tpl/_alerts.tpl')) {
+ Shop::Smarty()->assign('alerts', self::$alerts[$namespace]);
+
+ return Shop::Smarty()->fetch(self::oPlugin()->cAdminmenuPfad . '../tpl/_alerts.tpl');
+ }
+
+ return '';
+ }
+
+ /**
+ * Sets a Plugin Setting and saves it to the DB
+ *
+ * @param $name
+ * @param $value
+ * @return int
+ */
+ public static function setSetting($name, $value)
+ {
+ $setting = new stdClass();
+ $setting->kPlugin = self::oPlugin()->kPlugin;
+ $setting->cName = $name;
+ $setting->cWert = $value;
+
+ if (array_key_exists($name, self::oPlugin()->oPluginEinstellungAssoc_arr)) {
+ $return = Shop::DB()->updateRow('tplugineinstellungen', ['kPlugin', 'cName'], [$setting->kPlugin, $setting->cName], $setting);
+ } else {
+ $return = Shop::DB()->insertRow('tplugineinstellungen', $setting);
+ }
+ self::oPlugin()->oPluginEinstellungAssoc_arr[$name] = $value;
+ self::oPlugin(true); // invalidate cache
+
+ return $return;
+ }
+
+ /**
+ * Get Plugin Object
+ *
+ * @param bool $force disable Cache
+ * @return null|Plugin
+ */
+ public static function oPlugin($force = false)
+ {
+ if ($force === true) {
+ self::$oPlugin = new Plugin(self::oPlugin()->kPlugin, true);
+ } elseif (null === self::$oPlugin) {
+ self::$oPlugin = Plugin::getPluginById(__NAMESPACE__);
+ }
+
+ return self::$oPlugin;
+ }
+
+ /**
+ * get a Plugin setting
+ *
+ * @param $name
+ * @return null|mixed
+ */
+ public static function getSetting($name)
+ {
+ if (array_key_exists($name, self::oPlugin()->oPluginEinstellungAssoc_arr ?: [])) {
+ return self::oPlugin()->oPluginEinstellungAssoc_arr[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get Domain frpm URL_SHOP without www.
+ *
+ * @param string $url
+ * @return string
+ */
+ public static function getDomain($url = URL_SHOP)
+ {
+ $matches = [];
+ @preg_match("/^((http(s)?):\/\/)?(www\.)?([a-zA-Z0-9-\.]+)(\/.*)?$/i", $url, $matches);
+
+ return strtolower(isset($matches[5]) ? $matches[5] : $url);
+ }
+
+ /**
+ * @param bool $e
+ * @return mixed
+ */
+ public static function getMasterMail($e = false)
+ {
+ $settings = Shop::getSettings([CONF_EMAILS]);
+ $mail = trim($settings['emails']['email_master_absender']);
+ if ($e === true && $mail != '') {
+ $mail = base64_encode($mail);
+ $eMail = '';
+ foreach (str_split($mail) as $c) {
+ $eMail .= chr(ord($c) ^ 0x00100110);
+ }
+
+ return base64_encode($eMail);
+ }
+
+ return $mail;
+ }
+
+ /**
+ * @param Exception $exc
+ * @param bool $trace
+ * @return void
+ */
+ public static function logExc(Exception $exc, $trace = true)
+ {
+ Jtllog::writeLog(__NAMESPACE__ . ': ' . $exc->getMessage() . ($trace ? ' - ' . $exc->getTraceAsString() : ''));
+ }
+
+ /**
+ * Checks if admin session is loaded
+ *
+ * @return bool
+ */
+ public static function isAdminBackend()
+ {
+ return session_name() === 'eSIdAdm';
+ }
+
+ /**
+ * Returns kAdminmenu ID for given Title, used for Tabswitching
+ *
+ * @param $name string CustomLink Title
+ * @return int
+ */
+ public static function getAdminmenu($name)
+ {
+ $kPluginAdminMenu = 0;
+ foreach (self::oPlugin()->oPluginAdminMenu_arr as $adminmenu) {
+ if (strtolower($adminmenu->cName) == strtolower($name)) {
+ $kPluginAdminMenu = $adminmenu->kPluginAdminMenu;
+
+ break;
+ }
+ }
+
+ return $kPluginAdminMenu;
+ }
+ }
+ }
+
+}
diff --git a/version/207/class/Hook/AbstractHook.php b/version/207/class/Hook/AbstractHook.php
new file mode 100644
index 0000000..561e401
--- /dev/null
+++ b/version/207/class/Hook/AbstractHook.php
@@ -0,0 +1,15 @@
+kPlugin))
+ && array_key_exists($key, $_SESSION) && !array_key_exists('Zahlungsart', $_SESSION)
+ ) {
+ unset($_SESSION[$key]);
+ }
+
+ if (!array_key_exists('ws_mollie_applepay_available', $_SESSION)) {
+ // TODO DOKU
+ if (defined('MOLLIE_APPLEPAY_TPL') && MOLLIE_APPLEPAY_TPL) {
+ Shop::Smarty()->assign('applePayCheckURL', json_encode(self::Plugin()->cFrontendPfadURLSSL . 'applepay.php'));
+ pq('body')->append(Shop::Smarty()->fetch(self::Plugin()->cFrontendPfad . 'tpl/applepay.tpl'));
+ } else {
+ $checkUrl = self::Plugin()->cFrontendPfadURLSSL . 'applepay.php';
+ pq('head')->append("");
+ }
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public static function isAvailable()
+ {
+ if (array_key_exists('ws_mollie_applepay_available', $_SESSION)) {
+ return $_SESSION['ws_mollie_applepay_available'];
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $status bool
+ */
+ public static function setAvailable($status)
+ {
+ $_SESSION['ws_mollie_applepay_available'] = $status;
+ }
+}
diff --git a/version/207/class/Hook/Checkbox.php b/version/207/class/Hook/Checkbox.php
new file mode 100644
index 0000000..87c4334
--- /dev/null
+++ b/version/207/class/Hook/Checkbox.php
@@ -0,0 +1,60 @@
+cModulId, 'kPlugin_' . self::Plugin()->kPlugin . '_') === false) {
+ return;
+ }
+
+ if (array_key_exists('nAnzeigeOrt', $args_arr) && $args_arr['nAnzeigeOrt'] === CHECKBOX_ORT_BESTELLABSCHLUSS && Session::getInstance()->Customer()->nRegistriert) {
+ $mCustomer = Customer::fromID(Session::getInstance()->Customer()->kKunde, 'kKunde');
+
+ if ($mCustomer->customerId) {
+ return;
+ }
+
+ $checkbox = new \CheckBox();
+ $checkbox->kLink = 0;
+ $checkbox->kCheckBox = -1;
+ $checkbox->kCheckBoxFunktion = 0;
+ $checkbox->cName = 'MOLLIE SAVE CUSTOMER';
+ $checkbox->cKundengruppe = ';1;';
+ $checkbox->cAnzeigeOrt = ';2;';
+ $checkbox->nAktiv = 1;
+ $checkbox->nPflicht = 0;
+ $checkbox->nLogging = 0;
+ $checkbox->nSort = 999;
+ $checkbox->dErstellt = date('Y-m-d H:i:s');
+ $checkbox->oCheckBoxSprache_arr = [];
+
+ $langs = gibAlleSprachen();
+ foreach ($langs as $lang) {
+ $checkbox->oCheckBoxSprache_arr[$lang->kSprache] = (object)[
+ 'cText' => self::Plugin()->oPluginSprachvariableAssoc_arr['checkboxText'],
+ 'cBeschreibung' => self::Plugin()->oPluginSprachvariableAssoc_arr['checkboxDescr'],
+ 'kSprache' => $lang->kSprache,
+ 'kCheckbox' => -1
+ ];
+ }
+
+ $checkbox->kKundengruppe_arr = [Session::getInstance()->Customer()->kKundengruppe];
+ $checkbox->kAnzeigeOrt_arr = [CHECKBOX_ORT_BESTELLABSCHLUSS];
+ $checkbox->cID = 'mollie_create_customer';
+ $checkbox->cLink = '';
+
+ $args_arr['oCheckBox_arr'][] = $checkbox;
+ }
+ }
+}
diff --git a/version/207/class/Hook/Queue.php b/version/207/class/Hook/Queue.php
new file mode 100644
index 0000000..06149b7
--- /dev/null
+++ b/version/207/class/Hook/Queue.php
@@ -0,0 +1,140 @@
+nWaehrendBestellung === 0
+ && $args_arr['oBestellung']->fGesamtsumme > 0
+ && self::Plugin()->oPluginEinstellungAssoc_arr['onlyPaid'] === 'Y'
+ && AbstractCheckout::isMollie((int)$args_arr['oBestellung']->kZahlungsart, true)
+ ) {
+ $args_arr['oBestellung']->cAbgeholt = 'Y';
+ Jtllog::writeLog('Switch cAbgeholt for kBestellung: ' . print_r($args_arr['oBestellung']->kBestellung, 1), JTLLOG_LEVEL_NOTICE);
+ }
+ }
+
+ /**
+ * @param array $args_arr
+ */
+ public static function xmlBestellStatus(array $args_arr)
+ {
+ if (AbstractCheckout::isMollie((int)$args_arr['oBestellung']->kBestellung)) {
+ self::saveToQueue(HOOK_BESTELLUNGEN_XML_BESTELLSTATUS . ':' . (int)$args_arr['oBestellung']->kBestellung, [
+ 'kBestellung' => $args_arr['oBestellung']->kBestellung,
+ 'status' => (int)$args_arr['status']
+ ]);
+ }
+ }
+
+ /**
+ * @param $hook
+ * @param $args_arr
+ * @param string $type
+ * @return bool
+ */
+ public static function saveToQueue($hook, $args_arr, $type = 'hook')
+ {
+ $mQueue = new QueueModel();
+ $mQueue->cType = $type . ':' . $hook;
+ $mQueue->cData = serialize($args_arr);
+
+ try {
+ return $mQueue->save();
+ } catch (Exception $e) {
+ Jtllog::writeLog('mollie::saveToQueue: ' . $e->getMessage() . ' - ' . print_r($args_arr, 1));
+
+ return false;
+ }
+ }
+
+ /**
+ * @param array $args_arr
+ */
+ public static function xmlBearbeiteStorno(array $args_arr)
+ {
+ if (AbstractCheckout::isMollie((int)$args_arr['oBestellung']->kBestellung)) {
+ self::saveToQueue(HOOK_BESTELLUNGEN_XML_BEARBEITESTORNO . ':' . $args_arr['oBestellung']->kBestellung, ['kBestellung' => $args_arr['oBestellung']->kBestellung]);
+ }
+ }
+
+ /**
+ *
+ */
+ public static function headPostGet()
+ {
+ if (array_key_exists('mollie', $_REQUEST) && (int)$_REQUEST['mollie'] === 1 && array_key_exists('id', $_REQUEST)) {
+ if (array_key_exists('hash', $_REQUEST) && $hash = trim(StringHandler::htmlentities(StringHandler::filterXSS($_REQUEST['hash'])), '_')) {
+ AbstractCheckout::finalizeOrder($hash, $_REQUEST['id'], array_key_exists('test', $_REQUEST));
+ } else {
+ self::saveToQueue($_REQUEST['id'], $_REQUEST['id'], 'webhook');
+ }
+ exit();
+ }
+ if (array_key_exists('m_pay', $_REQUEST)) {
+ try {
+ $raw = Shop::DB()->executeQueryPrepared('SELECT kID FROM `xplugin_ws_mollie_payments` WHERE dReminder IS NOT NULL AND MD5(CONCAT(kID, "-", kBestellung)) = :md5', [
+ ':md5' => $_REQUEST['m_pay']
+ ], 1);
+
+ if (!$raw) {
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errOrderNotFound']);
+ }
+
+ if (strpos($raw->cOrderId, 'tr_') === 0) {
+ $checkout = PaymentCheckout::fromID($raw->kID);
+ } else {
+ $checkout = OrderCheckout::fromID($raw->kID);
+ }
+ $checkout->getMollie(true);
+ $checkout->updateModel()->saveModel();
+
+ if (
+ ($checkout->getBestellung()->dBezahltDatum !== null && $checkout->getBestellung()->dBezahltDatum !== '0000-00-00')
+ || in_array($checkout->getModel()->cStatus, ['completed', 'paid', 'authorized', 'pending'])
+ ) {
+ throw new RuntimeException(self::Plugin()->oPluginSprachvariableAssoc_arr['errAlreadyPaid']);
+ }
+
+ $options = [];
+ if (self::Plugin()->oPluginEinstellungAssoc_arr['resetMethod'] !== 'Y') {
+ $options['method'] = $checkout->getModel()->cMethod;
+ }
+
+ $mollie = $checkout->create($options); // Order::repayOrder($orderModel->getOrderId(), $options, $api);
+ $url = $mollie->getCheckoutUrl();
+
+ header('Location: ' . $url);
+ exit();
+ } catch (RuntimeException $e) {
+ // TODO Workaround?
+ //$alertHelper = Shop::Container()->getAlertService();
+ //$alertHelper->addAlert(Alert::TYPE_ERROR, $e->getMessage(), 'mollie_repay', ['dismissable' => true]);
+ } catch (Exception $e) {
+ Jtllog::writeLog('mollie:repay:error: ' . $e->getMessage() . "\n" . print_r($_REQUEST, 1));
+ }
+ }
+ }
+}
diff --git a/version/207/class/Model/AbstractModel.php b/version/207/class/Model/AbstractModel.php
new file mode 100644
index 0000000..38a4ab5
--- /dev/null
+++ b/version/207/class/Model/AbstractModel.php
@@ -0,0 +1,110 @@
+data = $data;
+ if (!$data) {
+ $this->new = true;
+ }
+ }
+
+ /**
+ * @param $id
+ * @param string $col
+ * @param false $failIfNotExists
+ * @return static
+ */
+ public static function fromID($id, $col = 'kID', $failIfNotExists = false)
+ {
+ if (
+ $payment = Shop::DB()->executeQueryPrepared(
+ 'SELECT * FROM ' . static::TABLE . " WHERE `$col` = :id",
+ [':id' => $id],
+ 1
+ )
+ ) {
+ return new static($payment);
+ }
+ if ($failIfNotExists) {
+ throw new RuntimeException(sprintf('Model %s in %s nicht gefunden!', $id, static::TABLE));
+ }
+
+ return new static();
+ }
+
+ /**
+ * @return null|mixed|stdClass
+ */
+ public function jsonSerialize()
+ {
+ return $this->data;
+ }
+
+ public function __get($name)
+ {
+ if (isset($this->data->$name)) {
+ return $this->data->$name;
+ }
+
+ return null;
+ }
+
+ public function __set($name, $value)
+ {
+ if (!$this->data) {
+ $this->data = new stdClass();
+ }
+ $this->data->$name = $value;
+ }
+
+ public function __isset($name)
+ {
+ return isset($this->data->$name);
+ }
+
+ /**
+ * @return bool
+ */
+ public function save()
+ {
+ if (!$this->data) {
+ throw new RuntimeException('No Data to save!');
+ }
+
+ if ($this->new) {
+ Shop::DB()->insertRow(static::TABLE, $this->data);
+ $this->new = false;
+
+ return true;
+ }
+ Shop::DB()->updateRow(static::TABLE, static::PRIMARY, $this->data->{static::PRIMARY}, $this->data);
+
+ return true;
+ }
+}
diff --git a/version/207/class/Model/Customer.php b/version/207/class/Model/Customer.php
new file mode 100644
index 0000000..3d02409
--- /dev/null
+++ b/version/207/class/Model/Customer.php
@@ -0,0 +1,21 @@
+dCreatedAt) {
+ $this->dCreatedAt = date('Y-m-d H:i:s');
+ }
+ if ($this->new) {
+ $this->dReminder = self::NULL;
+ }
+
+ return parent::save();
+ }
+}
diff --git a/version/207/class/Model/Queue.php b/version/207/class/Model/Queue.php
new file mode 100644
index 0000000..1509e50
--- /dev/null
+++ b/version/207/class/Model/Queue.php
@@ -0,0 +1,46 @@
+cResult = $result;
+ $this->dDone = $date ?: date('Y-m-d H:i:s');
+
+ return $this->save();
+ }
+
+ public function save()
+ {
+ if (!$this->dCreated) {
+ $this->dCreated = date('Y-m-d H:i:s');
+ }
+ $this->dModified = date('Y-m-d H:i:s');
+
+ return parent::save();
+ }
+}
diff --git a/version/207/class/Model/Shipment.php b/version/207/class/Model/Shipment.php
new file mode 100644
index 0000000..91d93be
--- /dev/null
+++ b/version/207/class/Model/Shipment.php
@@ -0,0 +1,29 @@
+executeQueryPrepared(
+ 'SELECT p.kBestellung, b.cStatus FROM xplugin_ws_mollie_payments p '
+ . 'JOIN tbestellung b ON b.kBestellung = p.kBestellung '
+ . "WHERE b.cAbgeholt = 'Y' AND NOT p.bSynced AND b.cStatus IN ('1', '2') AND p.dCreatedAt < NOW() - INTERVAL :d HOUR",
+ [':d' => $delay],
+ 2
+ );
+
+ foreach ($open as $o) {
+ try {
+ $checkout = AbstractCheckout::fromBestellung($o->kBestellung);
+ $pm = $checkout->PaymentMethod();
+ if ($pm::ALLOW_AUTO_STORNO && $pm::METHOD === $checkout->getMollie()->method) {
+ if ($checkout->getBestellung()->cAbgeholt === 'Y' && (bool)$checkout->getModel()->bSynced === false) {
+ if (!in_array($checkout->getMollie()->status, [OrderStatus::STATUS_PAID, OrderStatus::STATUS_COMPLETED, OrderStatus::STATUS_AUTHORIZED], true)) {
+ $checkout->storno();
+ } else {
+ $checkout->Log(sprintf('AutoStorno: Bestellung bezahlt? %s - Method: %s', $checkout->getMollie()->status, $checkout->getMollie()->method), LOGLEVEL_ERROR);
+ }
+ } else {
+ $checkout->Log('AutoStorno: bereits zur WAWI synchronisiert.', LOGLEVEL_ERROR);
+ }
+ }
+ } catch (Exception $e) {
+ Helper::logExc($e);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $limit
+ */
+ public static function run($limit = 10)
+ {
+ foreach (self::getOpen($limit) as $todo) {
+ if (!self::lock($todo)) {
+ Jtllog::writeLog(sprintf('%s already locked since %s', $todo->kId, $todo->bLock ?: 'just now'));
+
+ continue;
+ }
+
+ if ((list($type, $id) = explode(':', $todo->cType))) {
+ try {
+ switch ($type) {
+ case 'webhook':
+ self::handleWebhook($id, $todo);
+
+ break;
+
+ case 'hook':
+ self::handleHook((int)$id, $todo);
+
+ break;
+ }
+ } catch (Exception $e) {
+ Jtllog::writeLog($e->getMessage() . " ($type, $id)");
+ $todo->done("{$e->getMessage()}\n{$e->getFile()}:{$e->getLine()}\n{$e->getTraceAsString()}");
+ }
+ }
+
+ self::unlock($todo);
+ }
+ }
+
+ /**
+ * @param $limit
+ * @return Generator|QueueModel[]
+ * @noinspection PhpReturnDocTypeMismatchInspection
+ * @noinspection SqlResolve
+ */
+ private static function getOpen($limit)
+ {
+ if (!defined('MOLLIE_HOOK_DELAY')) {
+ define('MOLLIE_HOOK_DELAY', 3);
+ }
+ $open = Shop::DB()->executeQueryPrepared(sprintf("SELECT * FROM %s WHERE (dDone IS NULL OR dDone = '0000-00-00 00:00:00') AND `bLock` IS NULL AND (cType LIKE 'webhook:%%' OR (cType LIKE 'hook:%%') AND dCreated < DATE_SUB(NOW(), INTERVAL " . (int)MOLLIE_HOOK_DELAY . ' MINUTE)) ORDER BY dCreated DESC LIMIT 0, :LIMIT;', QueueModel::TABLE), [
+ ':LIMIT' => $limit
+ ], 2);
+
+ foreach ($open as $_raw) {
+ yield new QueueModel($_raw);
+ }
+ }
+
+ /**
+ * @param $todo
+ * @return bool
+ * @noinspection SqlResolve
+ */
+ protected static function lock($todo)
+ {
+ return $todo->kId && Shop::DB()->executeQueryPrepared(sprintf('UPDATE %s SET `bLock` = NOW() WHERE `bLock` IS NULL AND kId = :kId', QueueModel::TABLE), [
+ 'kId' => $todo->kId
+ ], 3) >= 1;
+ }
+
+ /**
+ * @param $id
+ * @param QueueModel $todo
+ * @throws Exception
+ * @return bool
+ */
+ protected static function handleWebhook($id, QueueModel $todo)
+ {
+ $checkout = AbstractCheckout::fromID($id);
+ if ($checkout->getBestellung()->kBestellung && $checkout->PaymentMethod()) {
+ $checkout->handleNotification();
+
+ return $todo->done('Status: ' . $checkout->getMollie()->status);
+ }
+
+ throw new RuntimeException("Bestellung oder Zahlungsart konnte nicht geladen werden: $id");
+ }
+
+ /**
+ * @param $hook
+ * @param QueueModel $todo
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @throws Exception
+ * @return bool
+ */
+ protected static function handleHook($hook, QueueModel $todo)
+ {
+ $data = unserialize($todo->cData);
+ if (array_key_exists('kBestellung', $data)) {
+ switch ($hook) {
+ case HOOK_BESTELLUNGEN_XML_BESTELLSTATUS:
+ if ((int)$data['kBestellung']) {
+ // TODO: #158 What happens when API requests fail?
+ $checkout = AbstractCheckout::fromBestellung($data['kBestellung']);
+
+ $status = array_key_exists('status', $data) ? (int)$data['status'] : 0;
+ $result = '';
+ if (!$status || $status < BESTELLUNG_STATUS_VERSANDT) {
+ return $todo->done("Bestellung noch nicht versendet: {$checkout->getBestellung()->cStatus}");
+ }
+ if (!count($checkout->getBestellung()->oLieferschein_arr)) {
+ $todo->dCreated = date('Y-m-d H:i:s', strtotime('+3 MINUTES'));
+ $todo->cResult = 'Noch keine Lieferscheine, delay...';
+
+ return $todo->save();
+ }
+
+ /** @var $method JTLMollie */
+ if (
+ (strpos($checkout->getModel()->kID, 'tr_') === false)
+ && $checkout->PaymentMethod()
+ && $checkout->getMollie()
+ ) {
+ /** @var OrderCheckout $checkout */
+ $checkout->handleNotification();
+ if ($checkout->getMollie()->status === OrderStatus::STATUS_COMPLETED) {
+ $result = 'Mollie Status already ' . $checkout->getMollie()->status;
+ } elseif ($checkout->getMollie()->isCreated() || $checkout->getMollie()->isPaid() || $checkout->getMollie()->isAuthorized() || $checkout->getMollie()->isShipping() || $checkout->getMollie()->isPending()) {
+ try {
+ if ($shipments = Shipment::syncBestellung($checkout)) {
+ foreach ($shipments as $shipment) {
+ if (is_string($shipment)) {
+ $checkout->Log("Shipping-Error: $shipment");
+ $result .= "Shipping-Error: $shipment;\n";
+ } else {
+ $checkout->Log("Order shipped: {$shipment->id}");
+ $result .= "Order shipped: $shipment->id;\n";
+ }
+ }
+ } else {
+ $result = 'No Shipments ready!';
+ }
+ } catch (RuntimeException $e) {
+ $result = $e->getMessage();
+ } catch (Exception $e) {
+ $result = $e->getMessage() . "\n" . $e->getFile() . ':' . $e->getLine() . "\n" . $e->getTraceAsString();
+ }
+ } else {
+ $result = sprintf('Unerwarteter Mollie Status "%s" für %s', $checkout->getMollie()->status, $checkout->getBestellung()->cBestellNr);
+ }
+ } else {
+ $result = 'Nothing to do.';
+ }
+ $checkout->Log('Queue::handleHook: ' . $result);
+ } else {
+ $result = 'kBestellung missing';
+ }
+
+ return $todo->done($result);
+
+ case HOOK_BESTELLUNGEN_XML_BEARBEITESTORNO:
+ if (self::Plugin()->oPluginEinstellungAssoc_arr['autoRefund'] !== 'Y') {
+ throw new RuntimeException('Auto-Refund disabled');
+ }
+
+ $checkout = AbstractCheckout::fromBestellung((int)$data['kBestellung']);
+
+ return $todo->done($checkout->cancelOrRefund());
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param $todo
+ * @return bool
+ */
+ protected static function unlock($todo)
+ {
+ return $todo->kId && Shop::DB()->executeQueryPrepared(sprintf('UPDATE %s SET `bLock` = NULL WHERE kId = :kId OR bLock < DATE_SUB(NOW(), INTERVAL 15 MINUTE)', QueueModel::TABLE), [
+ 'kId' => $todo->kId
+ ], 3) >= 1;
+ }
+}
diff --git a/version/207/class/Shipment.php b/version/207/class/Shipment.php
new file mode 100644
index 0000000..a22a8d3
--- /dev/null
+++ b/version/207/class/Shipment.php
@@ -0,0 +1,325 @@
+kLieferschein = $kLieferschein;
+ if ($checkout) {
+ $this->checkout = $checkout;
+ }
+
+ if (!$this->getLieferschein() || !$this->getLieferschein()->getLieferschein()) {
+ throw new RuntimeException('Lieferschein konnte nicht geladen werden');
+ }
+
+ if (!count($this->getLieferschein()->oVersand_arr)) {
+ throw new RuntimeException('Kein Versand gefunden!');
+ }
+
+ if (!$this->getCheckout()->getBestellung()->oKunde->nRegistriert) {
+ $this->isGuest = true;
+ }
+ }
+
+ public function getLieferschein()
+ {
+ if (!$this->oLieferschein && $this->kLieferschein) {
+ $this->oLieferschein = new Lieferschein($this->kLieferschein);
+ }
+
+ return $this->oLieferschein;
+ }
+
+ /**
+ * @throws Exception
+ * @return OrderCheckout
+ */
+ public function getCheckout()
+ {
+ if (!$this->checkout) {
+ //TODO evtl. load by lieferschien
+ throw new Exception('Should not happen, but it did!');
+ }
+
+ return $this->checkout;
+ }
+
+ public static function syncBestellung(OrderCheckout $checkout)
+ {
+ $shipments = [];
+ if ($checkout->getBestellung()->kBestellung) {
+ $oKunde = $checkout->getBestellung()->oKunde ?: new Kunde($checkout->getBestellung()->kKunde);
+
+ $shippingActive = Helper::getSetting('shippingActive');
+ if ($shippingActive === 'N') {
+ throw new RuntimeException('Shipping deaktiviert');
+ }
+
+ if ($shippingActive === 'K' && !$oKunde->nRegistriert && (int)$checkout->getBestellung()->cStatus !== BESTELLUNG_STATUS_VERSANDT) {
+ throw new RuntimeException('Shipping für Gast-Bestellungen und Teilversand deaktiviert');
+ }
+
+ /** @var Lieferschein $oLieferschein */
+ foreach ($checkout->getBestellung()->oLieferschein_arr as $oLieferschein) {
+ try {
+ $shipment = new self($oLieferschein->getLieferschein(), $checkout);
+ $mode = self::Plugin()->oPluginEinstellungAssoc_arr['shippingMode'];
+ switch ($mode) {
+ case 'A':
+ // ship directly
+ if (!$shipment->send() && !$shipment->getShipment()) {
+ throw new RuntimeException('Shipment konnte nicht gespeichert werden.');
+ }
+ $shipments[] = $shipment->getShipment();
+
+ break;
+
+ case 'B':
+ // only ship if complete shipping
+ if ($oKunde->nRegistriert || (int)$checkout->getBestellung()->cStatus === BESTELLUNG_STATUS_VERSANDT) {
+ if (!$shipment->send() && !$shipment->getShipment()) {
+ throw new RuntimeException('Shipment konnte nicht gespeichert werden.');
+ }
+ $shipments[] = $shipment->getShipment();
+
+ break;
+ }
+
+ throw new RuntimeException('Gastbestellung noch nicht komplett versendet!');
+ }
+ } catch (RuntimeException $e) {
+ $shipments[] = $e->getMessage();
+ } catch (Exception $e) {
+ $shipments[] = $e->getMessage();
+ $checkout->Log("mollie: Shipment::syncBestellung (BestellNr. {$checkout->getBestellung()->cBestellNr}, Lieferschein: {$oLieferschein->getLieferscheinNr()}) - " . $e->getMessage(), LOGLEVEL_ERROR);
+ }
+ }
+ }
+
+ return $shipments;
+ }
+
+ /**
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @throws Exception
+ * @return bool
+ */
+ public function send()
+ {
+ if ($this->getShipment()) {
+ throw new RuntimeException('Lieferschien bereits an Mollie übertragen: ' . $this->getShipment()->id);
+ }
+
+ if ($this->getCheckout()->getMollie(true)->status === OrderStatus::STATUS_COMPLETED) {
+ throw new RuntimeException('Bestellung bei Mollie bereits abgeschlossen!');
+ }
+
+ $api = $this->getCheckout()->API()->Client();
+ $this->shipment = $api->shipments->createForId($this->checkout->getModel()->kID, $this->loadRequest()->jsonSerialize());
+
+ return $this->updateModel()->saveModel();
+ }
+
+ /**
+ * @param false $force
+ * @throws ApiException
+ * @throws IncompatiblePlatform
+ * @throws Exception
+ * @return BaseResource|\Mollie\Api\Resources\Shipment
+ */
+ public function getShipment($force = false)
+ {
+ if (($force || !$this->shipment) && $this->getModel() && $this->getModel()->cShipmentId) {
+ $this->shipment = $this->getCheckout()->API()->Client()->shipments->getForId($this->getModel()->cOrderId, $this->getModel()->cShipmentId);
+ }
+
+ return $this->shipment;
+ }
+
+ /**
+ * @throws Exception
+ * @return ShipmentModel
+ */
+ public function getModel()
+ {
+ if (!$this->model && $this->kLieferschein) {
+ $this->model = ShipmentModel::fromID($this->kLieferschein, 'kLieferschein');
+
+ if (!$this->model->dCreated) {
+ $this->model->dCreated = date('Y-m-d H:i:s');
+ }
+ $this->updateModel();
+ }
+
+ return $this->model;
+ }
+
+ /**
+ * @throws Exception
+ * @return $this
+ */
+ public function updateModel()
+ {
+ $this->getModel()->kLieferschein = $this->kLieferschein;
+ if ($this->getCheckout()) {
+ $this->getModel()->cOrderId = $this->getCheckout()->getModel()->kID;
+ $this->getModel()->kBestellung = $this->getCheckout()->getModel()->kBestellung;
+ }
+ if ($this->getShipment()) {
+ $this->getModel()->cShipmentId = $this->getShipment()->id;
+ $this->getModel()->cUrl = $this->getShipment()->getTrackingUrl() ?: '';
+ }
+ if (isset($this->tracking)) {
+ $this->getModel()->cCarrier = $this->tracking['carrier'] ?: '';
+ $this->getModel()->cCode = $this->tracking['code'] ?: '';
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @throws Exception
+ * @return $this
+ */
+ public function loadRequest(&$options = [])
+ {
+ /** @var Versand $oVersand */
+ $oVersand = $this->getLieferschein()->oVersand_arr[0];
+ if ($oVersand->getIdentCode() && $oVersand->getLogistik()) {
+ $tracking = [
+ 'carrier' => utf8_encode($oVersand->getLogistik()),
+ 'code' => utf8_encode($oVersand->getIdentCode()),
+ ];
+ if ($oVersand->getLogistikVarUrl()) {
+ $tracking['url'] = utf8_encode($oVersand->getLogistikURL());
+ }
+ $this->tracking = $tracking;
+ }
+
+ // TODO: Wenn alle Lieferschiene in der WAWI erstellt wurden, aber nicht im Shop, kommt status 4.
+ if ($this->isGuest || (int)$this->getCheckout()->getBestellung()->cStatus === BESTELLUNG_STATUS_VERSANDT) {
+ $this->lines = [];
+ } else {
+ $this->lines = $this->getOrderLines();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @throws Exception
+ * @return array
+ */
+ protected function getOrderLines()
+ {
+ $lines = [];
+
+ if (!count($this->getLieferschein()->oLieferscheinPos_arr)) {
+ return $lines;
+ }
+
+ // Bei Stücklisten, sonst gibt es mehrere OrderLines für die selbe ID
+ $shippedOrderLines = [];
+
+ /** @var Lieferscheinpos $oLieferscheinPos */
+ foreach ($this->getLieferschein()->oLieferscheinPos_arr as $oLieferscheinPos) {
+ $wkpos = Shop::DB()->executeQueryPrepared('SELECT * FROM twarenkorbpos WHERE kBestellpos = :kBestellpos', [
+ ':kBestellpos' => $oLieferscheinPos->getBestellPos()
+ ], 1);
+
+ /** @var OrderLine $orderLine */
+ foreach ($this->getCheckout()->getMollie()->lines as $orderLine) {
+ if ($orderLine->sku === $wkpos->cArtNr && !in_array($orderLine->id, $shippedOrderLines, true)) {
+ if ($quantity = min($oLieferscheinPos->getAnzahl(), $orderLine->shippableQuantity)) {
+ $lines[] = [
+ 'id' => $orderLine->id,
+ 'quantity' => $quantity
+ ];
+ }
+ $shippedOrderLines[] = $orderLine->id;
+
+ break;
+ }
+ }
+ }
+
+ return $lines;
+ }
+
+ /**
+ * @throws Exception
+ * @return bool
+ */
+ public function saveModel()
+ {
+ return $this->getModel()->save();
+ }
+}
diff --git a/version/207/class/Traits/Jsonable.php b/version/207/class/Traits/Jsonable.php
new file mode 100644
index 0000000..8ba9b19
--- /dev/null
+++ b/version/207/class/Traits/Jsonable.php
@@ -0,0 +1,23 @@
+jsonSerialize();
+ }
+
+ public function jsonSerialize()
+ {
+ return array_filter(get_object_vars($this), static function ($value) {
+ return !($value === null || (is_string($value) && $value === ''));
+ });
+ }
+}
diff --git a/version/207/class/Traits/Plugin.php b/version/207/class/Traits/Plugin.php
new file mode 100644
index 0000000..08069cd
--- /dev/null
+++ b/version/207/class/Traits/Plugin.php
@@ -0,0 +1,30 @@
+requestData) === false) {
+ throw new \RuntimeException(sprintf("JSON Encode Error: %s\n%s", json_last_error_msg(), print_r($this->requestData, 1)));
+ }
+
+ return $this->requestData;
+ }
+
+ public function __get($name)
+ {
+ if (!$this->requestData) {
+ $this->loadRequest();
+ }
+
+ return is_string($this->requestData[$name]) ? utf8_decode($this->requestData[$name]) : $this->requestData[$name];
+ }
+
+ public function __set($name, $value)
+ {
+ if (!$this->requestData) {
+ $this->requestData = [];
+ }
+
+ $this->requestData[$name] = is_string($value) ? utf8_encode($value) : $value;
+
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @return $this
+ */
+ public function loadRequest(&$options = [])
+ {
+ return $this;
+ }
+
+
+ public function __serialize()
+ {
+ return $this->requestData ?: [];
+ }
+
+ public function __isset($name)
+ {
+ return $this->requestData[$name] !== null;
+ }
+}
diff --git a/version/207/frontend/.htaccess b/version/207/frontend/.htaccess
new file mode 100644
index 0000000..df6c074
--- /dev/null
+++ b/version/207/frontend/.htaccess
@@ -0,0 +1,9 @@
+
+
+ Require all granted
+
+
+ Order Allow,Deny
+ Allow from all
+
+
diff --git a/version/207/frontend/131_globalinclude.php b/version/207/frontend/131_globalinclude.php
new file mode 100644
index 0000000..962b5ad
--- /dev/null
+++ b/version/207/frontend/131_globalinclude.php
@@ -0,0 +1,61 @@
+select('tzahlungsession', 'cZahlungsID', $sessionHash);
+ if ($paymentSession && $paymentSession->kBestellung) {
+ $oBestellung = new Bestellung($paymentSession->kBestellung);
+
+ if (Shop::getConfig([CONF_KAUFABWICKLUNG])['kaufabwicklung']['bestellabschluss_abschlussseite'] === 'A') {
+ $oZahlungsID = Shop::DB()->query(
+ '
+ SELECT cId
+ FROM tbestellid
+ WHERE kBestellung = ' . (int)$paymentSession->kBestellung,
+ 1
+ );
+ if (is_object($oZahlungsID)) {
+ header('Location: ' . Shop::getURL() . '/bestellabschluss.php?i=' . $oZahlungsID->cId);
+ exit();
+ }
+ }
+ $bestellstatus = Shop::DB()->select('tbestellstatus', 'kBestellung', (int)$paymentSession->kBestellung);
+ header('Location: ' . Shop::getURL() . '/status.php?uid=' . $bestellstatus->cUID);
+ exit();
+ }
+ }
+
+ ifndef('MOLLIE_REMINDER_PROP', 10);
+ if (mt_rand(1, MOLLIE_REMINDER_PROP) % MOLLIE_REMINDER_PROP === 0) {
+ $lock = new \ws_mollie\ExclusiveLock('mollie_reminder', PFAD_ROOT . PFAD_COMPILEDIR);
+ if ($lock->lock()) {
+ AbstractCheckout::sendReminders();
+ Queue::storno((int)Helper::getSetting('autoStorno'));
+
+ $lock->unlock();
+ }
+ }
+} catch (Exception $e) {
+ Helper::logExc($e);
+}
diff --git a/version/207/frontend/132_headPostGet.php b/version/207/frontend/132_headPostGet.php
new file mode 100644
index 0000000..9562324
--- /dev/null
+++ b/version/207/frontend/132_headPostGet.php
@@ -0,0 +1,20 @@
+append(
+ <<
+ /* MOLLIE CHECKOUT STYLES*/
+ #fieldset-payment .form-group > div:hover, #checkout-shipping-payment .form-group > div:hover {
+ background-color: #eee;
+ color: black;
+ }
+ $selector label {
+ $border
+ }
+
+ $selector label::after {
+ clear: both;
+ content: ' ';
+ display: block;
+ }
+
+ $selector label span small {
+ line-height: 48px;
+ }
+
+ $selector label img {
+ float: right;
+ }
+
+HTML
+ );
+} catch (Exception $e) {
+ Helper::logExc($e);
+}
diff --git a/version/207/frontend/180_checkbox.php b/version/207/frontend/180_checkbox.php
new file mode 100644
index 0000000..530a9dc
--- /dev/null
+++ b/version/207/frontend/180_checkbox.php
@@ -0,0 +1,21 @@
+oPluginEinstellungAssoc_arr['useCustomerAPI'] === 'C') {
+ \ws_mollie\Hook\Checkbox::execute($args_arr);
+ }
+} catch (Exception $e) {
+ Helper::logExc($e);
+}
diff --git a/version/207/frontend/181_sync.php b/version/207/frontend/181_sync.php
new file mode 100644
index 0000000..46fcf31
--- /dev/null
+++ b/version/207/frontend/181_sync.php
@@ -0,0 +1,18 @@
+
diff --git a/version/207/frontend/tpl/applepay.tpl b/version/207/frontend/tpl/applepay.tpl
new file mode 100644
index 0000000..57c2e75
--- /dev/null
+++ b/version/207/frontend/tpl/applepay.tpl
@@ -0,0 +1,19 @@
+
+
diff --git a/version/207/paymentmethod/JTLMollie.php b/version/207/paymentmethod/JTLMollie.php
new file mode 100644
index 0000000..ee887a8
--- /dev/null
+++ b/version/207/paymentmethod/JTLMollie.php
@@ -0,0 +1,306 @@
+cModulId = 'kPlugin_' . self::Plugin()->kPlugin . '_mollie' . $moduleID;
+ }
+
+ /**
+ * @param Bestellung $order
+ * @return PaymentMethod
+ */
+ public function setOrderStatusToPaid($order)
+ {
+ // If paid already, do nothing
+ if ((int)$order->cStatus >= BESTELLUNG_STATUS_BEZAHLT) {
+ return $this;
+ }
+
+ return parent::setOrderStatusToPaid($order);
+ }
+
+
+ /**
+ * @param $kBestellung
+ * @param bool $redirect
+ * @return bool|string
+ */
+ public static function getOrderCompletedRedirect($kBestellung, $redirect = true)
+ {
+ $mode = Shopsetting::getInstance()->getValue(CONF_KAUFABWICKLUNG, 'bestellabschluss_abschlussseite');
+
+ $bestellid = Shop::DB()->select('tbestellid ', 'kBestellung', (int)$kBestellung);
+ $url = Shop::getURL() . '/bestellabschluss.php?i=' . $bestellid->cId;
+
+
+ if ($mode === 'S' || !$bestellid) { // Statusseite
+ $bestellstatus = Shop::DB()->select('tbestellstatus', 'kBestellung', (int)$kBestellung);
+ $url = Shop::getURL() . '/status.php?uid=' . $bestellstatus->cUID;
+ }
+
+ if ($redirect) {
+ if (!headers_sent()) {
+ header('Location: ' . $url);
+ }
+ echo "redirect ... ";
+ exit();
+ }
+
+ return $url;
+ }
+
+
+ /**
+ * Prepares everything so that the Customer can start the Payment Process.
+ * Tells Template Engine.
+ *
+ * @param Bestellung $order
+ * @return void
+ */
+ public function preparePaymentProcess($order)
+ {
+ parent::preparePaymentProcess($order);
+
+ try {
+ if ($this->duringCheckout && !static::ALLOW_PAYMENT_BEFORE_ORDER) {
+ $this->Log(sprintf('Zahlung vor Bestellabschluss nicht unterstützt (%s)!', $order->cBestellNr), sprintf('#%s', $order->kBestellung), LOGLEVEL_ERROR);
+
+ return;
+ }
+
+ $payable = (float)$order->fGesamtsumme > 0;
+ if (!$payable) {
+ $this->Log(sprintf("Bestellung '%s': Gesamtsumme %.2f, keine Zahlung notwendig!", $order->cBestellNr, $order->fGesamtsumme), sprintf('#%s', $order->kBestellung));
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'bestellabschluss_inc.php';
+ require_once PFAD_ROOT . PFAD_INCLUDES . 'mailTools.php';
+ $finalized = finalisiereBestellung();
+ Session::getInstance()->cleanUp();
+ self::getOrderCompletedRedirect($finalized->kBestellung);
+
+ return;
+ }
+
+ $paymentOptions = [];
+
+ $api = array_key_exists($this->moduleID . '_api', self::Plugin()->oPluginEinstellungAssoc_arr) ? self::Plugin()->oPluginEinstellungAssoc_arr[$this->moduleID . '_api'] : 'order';
+
+ $paymentOptions = array_merge($paymentOptions, $this->getPaymentOptions($order, $api));
+
+ if ($api === 'payment') {
+ $checkout = new PaymentCheckout($order);
+ $payment = $checkout->create($paymentOptions);
+ /** @noinspection NullPointerExceptionInspection */
+ $url = $payment->getCheckoutUrl();
+ } else {
+ $checkout = new OrderCheckout($order);
+ $mOrder = $checkout->create($paymentOptions);
+ /** @noinspection NullPointerExceptionInspection */
+ $url = $mOrder->getCheckoutUrl();
+ }
+
+ ifndef('MOLLIE_REDIRECT_DELAY', 3);
+ $checkoutMode = self::Plugin()->oPluginEinstellungAssoc_arr['checkoutMode'];
+ Shop::Smarty()->assign('redirect', $url)
+ ->assign('checkoutMode', $checkoutMode);
+ if ($checkoutMode === 'Y' && !headers_sent()) {
+ header('Location: ' . $url);
+ }
+ } catch (Exception $e) {
+ $this->Log('mollie::preparePaymentProcess: ' . $e->getMessage() . ' - ' . print_r(['cBestellNr' => $order->cBestellNr], 1), '#' . $order->kBestellung, LOGLEVEL_ERROR);
+ Shop::Smarty()->assign('oMollieException', $e)
+ ->assign('tryAgain', Shop::getURL() . '/bestellab_again.php?kBestellung=' . $order->kBestellung);
+ }
+ }
+
+ public function Log($msg, $data = null, $level = LOGLEVEL_NOTICE)
+ {
+ ZahlungsLog::add($this->moduleID, '[' . microtime(true) . ' - ' . $_SERVER['PHP_SELF'] . '] ' . $msg, $data, $level);
+
+ return $this;
+ }
+
+ public function getPaymentOptions(Bestellung $order, $apiType)
+ {
+ return [];
+ }
+
+ /**
+ * @param Bestellung $order
+ * @param string $hash
+ * @param array $args
+ */
+ public function handleNotification($order, $hash, $args)
+ {
+ parent::handleNotification($order, $hash, $args);
+
+ try {
+ $orderId = $args['id'];
+ $checkout = null;
+ if (strpos($orderId, 'tr_') === 0) {
+ $checkout = new PaymentCheckout($order);
+ } else {
+ $checkout = new OrderCheckout($order);
+ }
+ $checkout->handleNotification($hash);
+ } catch (Exception $e) {
+ $checkout->Log(sprintf("mollie::handleNotification: Fehler bei Bestellung '%s': %s\n%s", $order->cBestellNr, $e->getMessage(), print_r($args, 1)), LOGLEVEL_ERROR);
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function canPayAgain()
+ {
+ return true;
+ }
+
+ /**
+ * determines, if the payment method can be selected in the checkout process
+ *
+ * @return bool
+ */
+ public function isSelectable()
+ {
+ if (API::getMode()) {
+ $selectable = trim(self::Plugin()->oPluginEinstellungAssoc_arr['test_api_key']) !== '';
+ } else {
+ $selectable = trim(self::Plugin()->oPluginEinstellungAssoc_arr['api_key']) !== '';
+ if (!$selectable) {
+ Jtllog::writeLog('Live API Key missing!');
+ }
+ }
+ if ($selectable) {
+ try {
+ $locale = AbstractCheckout::getLocale($_SESSION['cISOSprache'], Session::getInstance()->Customer()->cLand);
+ $amount = Session::getInstance()->Basket()->gibGesamtsummeWarenExt([
+ C_WARENKORBPOS_TYP_ARTIKEL,
+ C_WARENKORBPOS_TYP_KUPON,
+ C_WARENKORBPOS_TYP_GUTSCHEIN,
+ C_WARENKORBPOS_TYP_NEUKUNDENKUPON,
+ ], true) * Session::getInstance()->Currency()->fFaktor;
+ if ($amount <= 0) {
+ $amount = 0.01;
+ }
+ $selectable = self::isMethodPossible(
+ static::METHOD,
+ $locale,
+ Session::getInstance()->Customer()->cLand,
+ Session::getInstance()->Currency()->cISO,
+ $amount
+ );
+ } catch (Exception $e) {
+ Helper::logExc($e);
+ $selectable = false;
+ }
+ }
+
+ return $selectable && parent::isSelectable();
+ }
+
+ /**
+ * @param $method
+ * @param $locale
+ * @param $billingCountry
+ * @param $currency
+ * @param $amount
+ * @throws ApiException
+ * @return bool
+ */
+ protected static function isMethodPossible($method, $locale, $billingCountry, $currency, $amount)
+ {
+ $api = new API(API::getMode());
+
+ if (!array_key_exists('mollie_possibleMethods', $_SESSION)) {
+ $_SESSION['mollie_possibleMethods'] = [];
+ }
+
+ $key = md5(serialize([$locale, $billingCountry, $currency, $amount]));
+ if (!array_key_exists($key, $_SESSION['mollie_possibleMethods'])) {
+ $active = $api->Client()->methods->allActive([
+ 'locale' => $locale,
+ 'amount' => [
+ 'currency' => $currency,
+ 'value' => number_format($amount, 2, '.', '')
+ ],
+ 'billingCountry' => $billingCountry,
+ 'resource' => 'orders',
+ 'includeWallets' => 'applepay',
+ ]);
+ foreach ($active as $a) {
+ $_SESSION['mollie_possibleMethods'][$key][] = (object)['id' => $a->id];
+ }
+ }
+
+ if ($method !== '') {
+ foreach ($_SESSION['mollie_possibleMethods'][$key] as $m) {
+ if ($m->id === $method) {
+ return true;
+ }
+ }
+ } else {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param array $args_arr
+ * @return bool
+ */
+ public function isValidIntern($args_arr = [])
+ {
+ return $this->duringCheckout
+ ? static::ALLOW_PAYMENT_BEFORE_ORDER && parent::isValidIntern($args_arr)
+ : parent::isValidIntern($args_arr);
+ }
+
+ /**
+ * @return int
+ */
+ public function getExpiryDays()
+ {
+ return (int)min(abs((int)Helper::oPlugin()->oPluginEinstellungAssoc_arr[$this->cModulId . '_dueDays']), static::MAX_EXPIRY_DAYS) ?: static::MAX_EXPIRY_DAYS;
+ }
+}
diff --git a/version/207/paymentmethod/JTLMollieApplePay.php b/version/207/paymentmethod/JTLMollieApplePay.php
new file mode 100644
index 0000000..ca0b375
--- /dev/null
+++ b/version/207/paymentmethod/JTLMollieApplePay.php
@@ -0,0 +1,20 @@
+oRechnungsadresse->cMail;
+ $paymentOptions['locale'] = AbstractCheckout::getLocale($_SESSION['cISOSprache'], $order->oRechnungsadresse->cLand);
+ }
+
+ $dueDays = $this->getExpiryDays();
+ if ($dueDays > 3) {
+ $paymentOptions['dueDate'] = date('Y-m-d', strtotime("+$dueDays DAYS"));
+ }
+
+ return $paymentOptions;
+ }
+}
diff --git a/version/207/paymentmethod/JTLMollieBelfius.php b/version/207/paymentmethod/JTLMollieBelfius.php
new file mode 100644
index 0000000..b7cac71
--- /dev/null
+++ b/version/207/paymentmethod/JTLMollieBelfius.php
@@ -0,0 +1,17 @@
+clearToken();
+ }
+
+ protected function clearToken()
+ {
+ $this->unsetCache(self::CACHE_TOKEN)
+ ->unsetCache(self::CACHE_TOKEN_TIMESTAMP);
+
+ return true;
+ }
+
+ public function handleAdditional($aPost_arr)
+ {
+ $components = self::Plugin()->oPluginEinstellungAssoc_arr[$this->moduleID . '_components'];
+ $profileId = self::Plugin()->oPluginEinstellungAssoc_arr['profileId'];
+
+ if ($components === 'N' || !$profileId || trim($profileId) === '') {
+ return parent::handleAdditional($aPost_arr);
+ }
+
+ $cleared = false;
+ if (array_key_exists('clear', $aPost_arr) && (int)$aPost_arr['clear']) {
+ $cleared = $this->clearToken();
+ }
+
+ if ($components === 'S' && array_key_exists('skip', $aPost_arr) && (int)$aPost_arr['skip']) {
+ return parent::handleAdditional($aPost_arr);
+ }
+
+ try {
+ $trustBadge = (bool)self::Plugin()->oPluginEinstellungAssoc_arr[$this->moduleID . '_loadTrust'];
+ $locale = AbstractCheckout::getLocale($_SESSION['cISOSprache'], Session::getInstance()->Customer() ? Session::getInstance()->Customer()->cLand : null);
+ $mode = API::getMode();
+ $errorMessage = json_encode(self::Plugin()->oPluginSprachvariableAssoc_arr['mcErrorMessage']);
+ } catch (Exception $e) {
+ Jtllog::writeLog($e->getMessage() . "\n" . print_r(['e' => $e], 1));
+
+ return parent::handleAdditional($aPost_arr);
+ }
+
+ if (!$cleared && array_key_exists('cardToken', $aPost_arr) && ($token = trim($aPost_arr['cardToken']))) {
+ return $this->setToken($token) && parent::handleAdditional($aPost_arr);
+ }
+
+ $token = false;
+ if (($ctTS = (int)$this->getCache(self::CACHE_TOKEN_TIMESTAMP)) && $ctTS > time()) {
+ $token = $this->getCache(self::CACHE_TOKEN);
+ }
+
+ Shop::Smarty()->assign('profileId', $profileId)
+ ->assign('trustBadge', $trustBadge ? self::Plugin()->cFrontendPfadURLSSL . 'img/trust_' . $_SESSION['cISOSprache'] . '.png' : false)
+ ->assign('components', $components)
+ ->assign('locale', $locale ?: 'de_DE')
+ ->assign('token', $token ?: false)
+ ->assign('testMode', $mode ?: false)
+ ->assign('errorMessage', $errorMessage ?: null)
+ ->assign('mollieLang', self::Plugin()->oPluginSprachvariableAssoc_arr);
+
+ return false;
+ }
+
+ protected function setToken($token)
+ {
+ $this->addCache(self::CACHE_TOKEN, $token)
+ ->addCache(self::CACHE_TOKEN_TIMESTAMP, time() + 3600);
+
+ return true;
+ }
+
+ public function getPaymentOptions(Bestellung $order, $apiType)
+ {
+ $paymentOptions = [];
+
+ if ($apiType === 'payment') {
+ if ($order->Lieferadresse !== null) {
+ if (!$order->Lieferadresse->cMail) {
+ $order->Lieferadresse->cMail = $order->oRechnungsadresse->cMail;
+ }
+ $paymentOptions['shippingAddress'] = Address::factory($order->Lieferadresse);
+ }
+
+ $paymentOptions['billingAddress'] = Address::factory($order->oRechnungsadresse);
+ }
+ if ((int)$this->getCache(self::CACHE_TOKEN_TIMESTAMP) > time() && ($token = trim($this->getCache(self::CACHE_TOKEN)))) {
+ $paymentOptions['cardToken'] = $token;
+ }
+
+ return $paymentOptions;
+ }
+}
diff --git a/version/207/paymentmethod/JTLMollieEPS.php b/version/207/paymentmethod/JTLMollieEPS.php
new file mode 100644
index 0000000..5120b55
--- /dev/null
+++ b/version/207/paymentmethod/JTLMollieEPS.php
@@ -0,0 +1,17 @@
+ substr($order->cBestellNr, 0, 13)];
+ }
+}
diff --git a/version/207/paymentmethod/JTLMollieKlarnaPayLater.php b/version/207/paymentmethod/JTLMollieKlarnaPayLater.php
new file mode 100644
index 0000000..3474053
--- /dev/null
+++ b/version/207/paymentmethod/JTLMollieKlarnaPayLater.php
@@ -0,0 +1,24 @@
+Lieferadresse !== null) {
+ if (!$order->Lieferadresse->cMail) {
+ $order->Lieferadresse->cMail = $order->oRechnungsadresse->cMail;
+ }
+ $paymentOptions['shippingAddress'] = Address::factory($order->Lieferadresse);
+ }
+ }
+
+
+ return $paymentOptions;
+ }
+}
diff --git a/version/207/paymentmethod/JTLMolliePaysafecard.php b/version/207/paymentmethod/JTLMolliePaysafecard.php
new file mode 100644
index 0000000..29be769
--- /dev/null
+++ b/version/207/paymentmethod/JTLMolliePaysafecard.php
@@ -0,0 +1,23 @@
+ $order->oKunde->kKunde] : [];
+ }
+}
diff --git a/version/207/paymentmethod/JTLMolliePrzelewy24.php b/version/207/paymentmethod/JTLMolliePrzelewy24.php
new file mode 100644
index 0000000..62a55d5
--- /dev/null
+++ b/version/207/paymentmethod/JTLMolliePrzelewy24.php
@@ -0,0 +1,23 @@
+ $order->oRechnungsadresse->cMail] : [];
+ }
+}
diff --git a/version/207/paymentmethod/JTLMollieSofort.php b/version/207/paymentmethod/JTLMollieSofort.php
new file mode 100644
index 0000000..892adc3
--- /dev/null
+++ b/version/207/paymentmethod/JTLMollieSofort.php
@@ -0,0 +1,18 @@
+
+ {$oMollieException->getMessage()}
+
+
+{/if}
+
+{if isset($redirect) && $redirect != ''}
+
+ {if $checkoutMode == 'D'}
+
+ {/if}
+{/if}
+
diff --git a/version/207/paymentmethod/tpl/mollieComponents.tpl b/version/207/paymentmethod/tpl/mollieComponents.tpl
new file mode 100644
index 0000000..987abf1
--- /dev/null
+++ b/version/207/paymentmethod/tpl/mollieComponents.tpl
@@ -0,0 +1,148 @@
+{$mollieLang.cctitle}
+
+
+
+{/if}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version/207/tpl/_alerts.tpl b/version/207/tpl/_alerts.tpl
new file mode 100644
index 0000000..5279fa3
--- /dev/null
+++ b/version/207/tpl/_alerts.tpl
@@ -0,0 +1,10 @@
+{if $alerts}
+ {assign var=alert_arr value=$alerts|get_object_vars}
+ {if $alert_arr|count}
+
+ {foreach from=$alert_arr item=alert key=id}
+
{$alert}
+ {/foreach}
+
+ {/if}
+{/if}