PayPal-Zahlungen auch ueber die Mailadresse zuordnen
Bisher verglich die Zuordnung ausschliesslich Namen: der Parser las die Adresse des Zahlers gar nicht aus, und die Suche kannte nur paypal_name und display_name. Eine Zahlung von genau der Adresse, die beim Mitglied hinterlegt ist, blieb deshalb liegen. Die Adresse wird jetzt ausgelesen (neue Spalte paypal_payments.payer_email), in der Warteschlange angezeigt und zuerst geprueft - sie ist im Mandanten eindeutig und aendert sich nicht, wenn jemand bei PayPal anders heisst. Der heikle Teil ist das Aussortieren: In einer weitergeleiteten Mail stehen mehrere Adressen. Die falsche zu nehmen wuerde fremdes Geld dem Mitglied hinter dieser Adresse gutschreiben - typischerweise dem Kassenwart. Deshalb fallen paypal.*-Adressen und die eigene Eingangsadresse raus (geprueft gegen die tatsaechliche Empfaengeradresse, Plus-Adressierung ignoriert), und es zaehlt nur eine Adresse in unmittelbarer Naehe des Zahlernamens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -103,11 +103,69 @@ function paypal_extract_labeled_amount(string $text, string $labelRegex): ?int
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailadresse des Zahlers aus der Benachrichtigung.
|
||||
*
|
||||
* Bewusst eng gefasst: In einer weitergeleiteten Mail stehen mehrere
|
||||
* Adressen - die von PayPal selbst, die Eingangsadresse der Kaffeeliste und
|
||||
* je nach Weiterleitung auch die des urspruenglichen Empfaengers. Wuerde
|
||||
* einfach die erste beste genommen, koennte eine Zahlung beim Kassenwart
|
||||
* statt beim Zahler landen. Genommen wird deshalb nur eine Adresse, die
|
||||
* nahe am Namen des Zahlers steht (PayPal schreibt sie dort hin), und auch
|
||||
* die erst nach Abzug aller bekannten Fremdadressen.
|
||||
*
|
||||
* @param string $text Bereits per paypal_mail_to_text() normalisierter Text
|
||||
* @param string $payer Erkannter Name des Zahlers
|
||||
*/
|
||||
function paypal_extract_payer_email(string $text, string $payer): ?string
|
||||
{
|
||||
if (!preg_match_all('/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/u', $text, $treffer, PREG_OFFSET_CAPTURE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Eingangsadresse der Kaffeeliste (zahlungen+<token>@…) und alles von
|
||||
// PayPal selbst scheiden aus.
|
||||
// Der Parser wird auch aus Testskripten ohne geladene Umgebung benutzt.
|
||||
$inboxBase = function_exists('app_env') ? (string) (app_env('PAYPAL_INBOX_BASE') ?? '') : '';
|
||||
$inboxLokalteil = $inboxBase !== '' ? strtolower(explode('@', $inboxBase)[0]) : '';
|
||||
|
||||
$namePosition = stripos($text, $payer);
|
||||
|
||||
$kandidaten = [];
|
||||
foreach ($treffer[0] as [$adresse, $position]) {
|
||||
$adresse = strtolower(rtrim($adresse, '.'));
|
||||
$domain = substr($adresse, strpos($adresse, '@') + 1);
|
||||
$lokalteil = substr($adresse, 0, strpos($adresse, '@'));
|
||||
|
||||
if (preg_match('/(^|\.)paypal\.[a-z.]+$/', $domain)) {
|
||||
continue;
|
||||
}
|
||||
if ($inboxLokalteil !== '' && str_starts_with($lokalteil, $inboxLokalteil)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$abstand = $namePosition === false ? PHP_INT_MAX : abs($position - $namePosition);
|
||||
$kandidaten[$adresse] = min($kandidaten[$adresse] ?? PHP_INT_MAX, $abstand);
|
||||
}
|
||||
|
||||
if ($kandidaten === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
asort($kandidaten);
|
||||
$beste = array_key_first($kandidaten);
|
||||
|
||||
// Steht keine Adresse in der Naehe des Zahlernamens, ist die Zuordnung zu
|
||||
// unsicher - dann lieber keine liefern als die falsche.
|
||||
return $kandidaten[$beste] <= 400 ? $beste : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parst eine PayPal-Zahlungseingangs-Mail.
|
||||
*
|
||||
* @return array{
|
||||
* payer_name: string,
|
||||
* payer_email: ?string,
|
||||
* transaction_code: ?string,
|
||||
* gross_cents: int,
|
||||
* fee_cents: ?int,
|
||||
@@ -163,6 +221,7 @@ function paypal_parse_notification(string $body): ?array
|
||||
|
||||
return [
|
||||
'payer_name' => $payer,
|
||||
'payer_email' => paypal_extract_payer_email($text, $payer),
|
||||
'transaction_code' => $code,
|
||||
'gross_cents' => $grossCents,
|
||||
'fee_cents' => $feeCents,
|
||||
|
||||
Reference in New Issue
Block a user