Mandanten leiten ihre PayPal-Benachrichtigungsmails an ein zentrales IMAP-Postfach weiter. Die Zuordnung zum Mandanten erfolgt ueber Plus-Adressierung (zahlungen+<token>@...), der Token wird pro Mandant erzeugt und im Backend angezeigt. - Parser fuer PayPal-Eingangsmails, tolerant gegenueber falsch kodierten Waehrungssymbolen und HTML-Struktur weitergeleiteter Mails - Gutgeschrieben wird der Nettobetrag, also was tatsaechlich ankam (bei Waren & Dienstleistungen nach Abzug der PayPal-Gebuehr) - Deduplizierung ueber den Transaktionscode, damit doppelt weitergeleitete Mails nicht doppelt buchen - Eindeutiger Namens-Match bucht automatisch, alles andere landet in einer Warteschlange zur manuellen Zuordnung - Absenderpruefung gegen paypal.de/.com, da weitergeleitete Mails keine gueltige SPF/DKIM-Signatur mehr haben - IMAP-Zugang ausschliesslich ueber Server-/Env-Einstellungen, nicht pro Mandant konfigurierbar Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
143 lines
5.3 KiB
PHP
143 lines
5.3 KiB
PHP
<?php
|
||
|
||
include "functions.php";
|
||
require_once __DIR__ . "/app/ledger.php";
|
||
require_once __DIR__ . "/app/paypal-inbox.php";
|
||
app_require_csrf();
|
||
|
||
$pdo = app_db_pdo();
|
||
$saasUser = saas_current_user($pdo);
|
||
$tenantId = 0;
|
||
$hasAccess = false;
|
||
|
||
if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $saasUser)) {
|
||
$tenantId = (int)$saasUser['tenant_id'];
|
||
$hasAccess = true;
|
||
}
|
||
|
||
if (!$hasAccess && $saasUser === null && checkKaffeelisteAdmin($conn, $mailadress)) {
|
||
$tenant = ledger_fetch_default_tenant($pdo);
|
||
if ($tenant !== null) {
|
||
$tenantId = (int)$tenant['id'];
|
||
$hasAccess = true;
|
||
}
|
||
}
|
||
|
||
$flash = null;
|
||
if ($hasAccess && isset($_SESSION['flash_paypal'])) {
|
||
$flash = $_SESSION['flash_paypal'];
|
||
unset($_SESSION['flash_paypal']);
|
||
}
|
||
|
||
if ($hasAccess && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$aktion = (string)($_POST['aktion'] ?? '');
|
||
$paymentId = (int)($_POST['payment_id'] ?? 0);
|
||
$actorUserId = $saasUser['user_id'] ?? null;
|
||
|
||
if ($aktion === 'zuordnen') {
|
||
$participantId = (int)($_POST['participant_id'] ?? 0);
|
||
if ($participantId <= 0) {
|
||
$flash = ['type' => 'error', 'text' => 'Bitte ein Mitglied auswählen.'];
|
||
} else {
|
||
$result = paypal_assign_payment($pdo, $tenantId, $paymentId, $participantId, $actorUserId);
|
||
$flash = $result['ok']
|
||
? ['type' => 'success', 'text' => 'Zahlung wurde zugeordnet und als Einzahlung gebucht.']
|
||
: ['type' => 'error', 'text' => $result['error'] ?? 'Die Zuordnung ist fehlgeschlagen.'];
|
||
}
|
||
} elseif ($aktion === 'ignorieren') {
|
||
$result = paypal_ignore_payment($pdo, $tenantId, $paymentId, $actorUserId);
|
||
$flash = $result['ok']
|
||
? ['type' => 'success', 'text' => 'Zahlung wurde als erledigt markiert.']
|
||
: ['type' => 'error', 'text' => $result['error'] ?? 'Die Aktion ist fehlgeschlagen.'];
|
||
}
|
||
|
||
// Post-Redirect-Get gegen Doppelbuchung per Refresh.
|
||
$_SESSION['flash_paypal'] = $flash;
|
||
header('Location: paypal-zuordnung.php');
|
||
exit;
|
||
}
|
||
|
||
$inboxAddress = null;
|
||
$offene = [];
|
||
$mitglieder = [];
|
||
if ($hasAccess) {
|
||
$token = paypal_inbox_ensure_token($pdo, $tenantId);
|
||
$inboxAddress = paypal_inbox_address_for_token($token);
|
||
$offene = paypal_fetch_unmatched($pdo, $tenantId);
|
||
$mitglieder = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
||
}
|
||
|
||
include "header.php";
|
||
include "headerline.php";
|
||
include "nav.php";
|
||
?>
|
||
|
||
<section id="banner">
|
||
<div class="content">
|
||
|
||
<?php if (!$hasAccess): ?>
|
||
<h2>Kein Zugriff</h2>
|
||
<?php else: ?>
|
||
|
||
<h2>PayPal-Zahlungen zuordnen</h2>
|
||
|
||
<?php if ($flash !== null): ?>
|
||
<div class="hint-box <?php echo $flash['type'] === 'success' ? 'success' : 'error'; ?>"><p><?php echo saas_html($flash['text']); ?></p></div>
|
||
<?php endif; ?>
|
||
|
||
<h3>So richtet ihr die automatische Verbuchung ein</h3>
|
||
<?php if ($inboxAddress !== null): ?>
|
||
<p>Leitet die PayPal-Benachrichtigungsmails („Du hast eine Zahlung erhalten") automatisch an eure persönliche Eingangsadresse weiter:</p>
|
||
<p><b><?php echo saas_html($inboxAddress); ?></b></p>
|
||
<p>Eingehende Zahlungen werden dann automatisch dem passenden Mitglied gutgeschrieben (gebuchter Betrag = tatsächlich eingegangener Betrag nach PayPal-Gebühr). Zahlungen, die sich nicht eindeutig zuordnen lassen, erscheinen unten zur manuellen Zuordnung.</p>
|
||
<p><small>Tipp: Am zuverlässigsten trefft ihr die Zuordnung, wenn beim Mitglied der PayPal-Name hinterlegt ist (unter „Mitglieder verwalten").</small></p>
|
||
<?php else: ?>
|
||
<div class="hint-box error"><p>Die zentrale Eingangsadresse ist noch nicht konfiguriert. Bitte wende dich an den Betreiber (Server-Einstellung <code>PAYPAL_INBOX_BASE</code>).</p></div>
|
||
<?php endif; ?>
|
||
|
||
<h3>Offene Zahlungen (<?php echo count($offene); ?>)</h3>
|
||
<?php if ($offene === []): ?>
|
||
<p>Keine offenen Zahlungen. Alles automatisch zugeordnet. 🎉</p>
|
||
<?php else: ?>
|
||
<table>
|
||
<tr><th>Eingang</th><th>Zahler (PayPal)</th><th>Mitteilung</th><th>Betrag</th><th>Zuordnen</th><th></th></tr>
|
||
<?php foreach ($offene as $z): ?>
|
||
<tr>
|
||
<td><?php echo saas_html((string)($z['paid_at'] ?? $z['created_at'])); ?></td>
|
||
<td><?php echo saas_html($z['payer_name']); ?></td>
|
||
<td><?php echo saas_html((string)($z['note'] ?? '')); ?></td>
|
||
<td><?php echo saas_html(saas_format_money_cents((int)$z['net_cents'])); ?> €</td>
|
||
<td>
|
||
<form method="post" action="paypal-zuordnung.php" style="display:flex;gap:0.5em;align-items:center;margin:0">
|
||
<?php echo app_csrf_field(); ?>
|
||
<input type="hidden" name="aktion" value="zuordnen">
|
||
<input type="hidden" name="payment_id" value="<?php echo (int)$z['id']; ?>">
|
||
<select name="participant_id" required>
|
||
<option value="">– Mitglied wählen –</option>
|
||
<?php foreach ($mitglieder as $m): ?>
|
||
<option value="<?php echo (int)$m['participant_id']; ?>"><?php echo saas_html($m['display_name']); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<button type="submit">Buchen</button>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<form method="post" action="paypal-zuordnung.php" style="margin:0" onsubmit="return confirm('Diese Zahlung als erledigt/ignoriert markieren?');">
|
||
<?php echo app_csrf_field(); ?>
|
||
<input type="hidden" name="aktion" value="ignorieren">
|
||
<input type="hidden" name="payment_id" value="<?php echo (int)$z['id']; ?>">
|
||
<button type="submit" class="alt">Ignorieren</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</table>
|
||
<?php endif; ?>
|
||
|
||
<?php endif; ?>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<?php include "footer.php"; ?>
|