Offene PayPal-Zahlungen bleiben nach dem Abschalten erreichbar

Wird PayPal in den Mandant-Einstellungen abgeschaltet, waehrend noch
Zahlungen unzugeordnet in der Warteschlange liegen, kaeme ohne diese
Ausnahme niemand mehr an sie heran - weder ueber das Menue noch ueber die
URL.

paypal_inbox_accessible() haelt Menuepunkt und Seite deshalb offen, solange
paypal_count_unmatched() etwas findet; die Seite weist per Hinweis darauf
hin, dass sie nach dem Abarbeiten verschwindet. Eine Sperre durch den
Betreiber sticht die Ausnahme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 23:42:52 +02:00
co-authored by Claude Opus 5
parent 3f6866e075
commit 6cd7fcc079
4 changed files with 85 additions and 4 deletions
+51
View File
@@ -328,3 +328,54 @@ function paypal_ignore_payment(PDO $pdo, int $tenantId, int $paymentId, ?int $ac
return ['ok' => false, 'error' => 'Diese Zahlung ist nicht (mehr) offen.']; return ['ok' => false, 'error' => 'Diese Zahlung ist nicht (mehr) offen.'];
} }
/**
* Anzahl der offenen Zahlungen in der Warteschlange. Bewusst als eigene
* Zaehl-Abfrage neben paypal_fetch_unmatched(): die Navigation braucht nur
* die Information "gibt es noch etwas zu tun", nicht die Datensaetze.
*/
function paypal_count_unmatched(PDO $pdo, int $tenantId): int
{
static $cache = [];
if (array_key_exists($tenantId, $cache)) {
return $cache[$tenantId];
}
try {
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM paypal_payments WHERE tenant_id = ? AND status = 'unmatched'"
);
$stmt->execute([$tenantId]);
$cache[$tenantId] = (int)$stmt->fetchColumn();
} catch (Throwable $e) {
// Fehlt die Tabelle, gibt es auch keine Warteschlange - die
// Navigation darf daran nicht scheitern.
$cache[$tenantId] = 0;
}
return $cache[$tenantId];
}
/**
* Darf die Zuordnungsseite geoeffnet werden? Die Funktion kennt drei Faelle:
* der Betreiber hat gesperrt (nein), der Mandant bietet PayPal an (ja) - und
* dazwischen der Fall, dass der Mandant PayPal gerade abgeschaltet hat,
* aber noch Zahlungen unzugeordnet in der Warteschlange liegen. Die waeren
* sonst weder ueber das Menue noch ueber die URL erreichbar; die Seite bleibt
* deshalb offen, bis die Warteschlange leer ist.
*/
function paypal_inbox_accessible(PDO $pdo, int $tenantId): bool
{
require_once __DIR__ . '/features.php';
if (!app_feature_enabled($pdo, $tenantId, 'paypal_inbox')) {
return false;
}
if (app_feature_available($pdo, $tenantId, 'paypal_inbox')) {
return true;
}
return paypal_count_unmatched($pdo, $tenantId) > 0;
}
+7
View File
@@ -105,6 +105,13 @@ Design-Entscheidungen:
Aufruf erscheint statt des Betreiber-Hinweises Aufruf erscheint statt des Betreiber-Hinweises
`app_feature_tenant_notice_html()` mit dem Weg zurück in die `app_feature_tenant_notice_html()` mit dem Weg zurück in die
Mandant-Einstellungen. Mandant-Einstellungen.
- **Ausnahme: offene Warteschlange.** Schaltet ein Mandant PayPal ab,
während noch nicht zugeordnete Zahlungen liegen, wären diese weder über
Menü noch über URL erreichbar. `paypal_inbox_accessible()`
(`app/paypal-inbox.php`) hält Menüpunkt und Seite deshalb offen, solange
`paypal_count_unmatched()` etwas findet mit einem Hinweis auf der
Seite. Ist die Warteschlange abgearbeitet, schließt sie sich von selbst.
Eine Sperre durch den Betreiber sticht auch diese Ausnahme.
- Jede Änderung landet als `backoffice.features_updated` im Audit-Log des - Jede Änderung landet als `backoffice.features_updated` im Audit-Log des
betroffenen Mandanten wie jeder andere Back-Office-Zugriff auch. betroffenen Mandanten wie jeder andere Back-Office-Zugriff auch.
+10 -1
View File
@@ -45,6 +45,15 @@ $saasNavFeature = static function (string $key) use ($saasNavTenantId): bool {
return app_feature_available(app_db_pdo(), $saasNavTenantId, $key); return app_feature_available(app_db_pdo(), $saasNavTenantId, $key);
}; };
// Sonderfall PayPal: abgeschaltet heisst normalerweise unsichtbar - solange
// aber noch Zahlungen unzugeordnet in der Warteschlange liegen, bleibt der
// Menuepunkt stehen, sonst kaeme niemand mehr an sie heran.
$saasNavPaypalInbox = true;
if ($saasNavTenantId > 0) {
require_once __DIR__ . '/app/paypal-inbox.php';
$saasNavPaypalInbox = paypal_inbox_accessible(app_db_pdo(), $saasNavTenantId);
}
// Das Back-Office war bisher nur ueber die direkte URL erreichbar. Es ist die // Das Back-Office war bisher nur ueber die direkte URL erreichbar. Es ist die
// Zentralstelle des Betreibers und gehoert deshalb ins Menue - aber nur fuer // Zentralstelle des Betreibers und gehoert deshalb ins Menue - aber nur fuer
// Platform-Admins, nicht fuer Mandanten-Rollen. // Platform-Admins, nicht fuer Mandanten-Rollen.
@@ -74,7 +83,7 @@ if ($saasNavUser !== null) {
<span class="menu-group-label">Verwaltung</span> <span class="menu-group-label">Verwaltung</span>
<ul class="menu-group-items"> <ul class="menu-group-items">
<li><a href="einzahlung.php">Einzahlung eintragen</a></li> <li><a href="einzahlung.php">Einzahlung eintragen</a></li>
<?php if ($saasNavFeature('paypal_inbox')): ?> <?php if ($saasNavPaypalInbox): ?>
<li><a href="paypal-zuordnung.php">PayPal-Zahlungen</a></li> <li><a href="paypal-zuordnung.php">PayPal-Zahlungen</a></li>
<?php endif; ?> <?php endif; ?>
<li><a href="stricheintragen.php">Striche eintragen</a></li> <li><a href="stricheintragen.php">Striche eintragen</a></li>
+15 -1
View File
@@ -21,14 +21,21 @@ if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $s
// diesem Fall ausgeblendet - die Seite bleibt aber ueber die direkte URL // diesem Fall ausgeblendet - die Seite bleibt aber ueber die direkte URL
// erreichbar und muss deshalb selbst pruefen. // erreichbar und muss deshalb selbst pruefen.
$sperrHinweis = null; $sperrHinweis = null;
$restarbeitHinweis = false;
if ($hasAccess && !app_feature_enabled($pdo, $tenantId, 'paypal_inbox')) { if ($hasAccess && !app_feature_enabled($pdo, $tenantId, 'paypal_inbox')) {
// Der Betreiber hat die Funktion fuer diesen Mandanten nicht freigeschaltet. // Der Betreiber hat die Funktion fuer diesen Mandanten nicht freigeschaltet.
$sperrHinweis = app_feature_notice_html('paypal_inbox'); $sperrHinweis = app_feature_notice_html('paypal_inbox');
$hasAccess = false; $hasAccess = false;
} elseif ($hasAccess && !app_feature_available($pdo, $tenantId, 'paypal_inbox')) { } elseif ($hasAccess && !app_feature_available($pdo, $tenantId, 'paypal_inbox')) {
// Der Mandant selbst bietet PayPal nicht als Zahlungsweg an. // Der Mandant selbst bietet PayPal nicht mehr als Zahlungsweg an. Offene
// Zahlungen muessen trotzdem noch zugeordnet werden koennen - erst mit
// leerer Warteschlange schliesst die Seite.
if (paypal_count_unmatched($pdo, $tenantId) > 0) {
$restarbeitHinweis = true;
} else {
$sperrHinweis = app_feature_tenant_notice_html('paypal_inbox'); $sperrHinweis = app_feature_tenant_notice_html('paypal_inbox');
$hasAccess = false; $hasAccess = false;
}
} }
$flash = null; $flash = null;
@@ -145,6 +152,13 @@ include "nav.php";
<h2>PayPal-Zahlungen zuordnen</h2> <h2>PayPal-Zahlungen zuordnen</h2>
<?php if ($restarbeitHinweis): ?>
<div class="hint-box warning"><p><b>PayPal ist in euren Mandant-Einstellungen deaktiviert.</b><br>
Diese Seite bleibt geöffnet, bis die unten stehenden Zahlungen zugeordnet oder abgehakt sind.
Danach verschwindet sie aus dem Menü bis PayPal unter
<a href="mandant-einstellungen.php">Mandant-Einstellungen</a> wieder eingeschaltet wird.</p></div>
<?php endif; ?>
<?php if ($flash !== null): ?> <?php if ($flash !== null): ?>
<div class="hint-box <?php echo $flash['type'] === 'success' ? 'success' : 'error'; ?>"><p><?php echo saas_html($flash['text']); ?></p></div> <div class="hint-box <?php echo $flash['type'] === 'success' ? 'success' : 'error'; ?>"><p><?php echo saas_html($flash['text']); ?></p></div>
<?php endif; ?> <?php endif; ?>