Lines 100.00% 67 / 67
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
TatraBankaMailParser 100.00% 67 / 67 100.00% 3 / 3 21 100.00% 1 / 1
 bankDomains 100.00% 1 / 1 100.00% 1 / 1 1
 supports 100.00% 8 / 8 100.00% 1 / 1 6
 parse 100.00% 58 / 58 100.00% 1 / 1 14
1<?php
2
3declare(strict_types=1);
4
5namespace Tomaj\BankMailsParser\Parser\TatraBanka;
6
7use Tomaj\BankMailsParser\Exception\InvalidEmailFormatException;
8use Tomaj\BankMailsParser\MailContent;
9use Tomaj\BankMailsParser\Parser\BankDate;
10use Tomaj\BankMailsParser\Parser\MailWhitespace;
11use Tomaj\BankMailsParser\Parser\ParserInterface;
12use Tomaj\BankMailsParser\Parser\ParsesBankMail;
13
14final readonly class TatraBankaMailParser implements ParserInterface
15{
16    use ParsesBankMail;
17
18    private const string TRANSACTION = '/^' . MailWhitespace::WS . '(\d{1,2}\.[ \t\x{00A0}\x{2009}\x{202F}]*\d{1,2}\.[ \t\x{00A0}\x{2009}\x{202F}]*\d{2,4}(?:[ \t\x{00A0}\x{2009}\x{202F}]+\d{1,2}:\d{2}(?::\d{2})?)?|\d{4}-\d{2}-\d{2}(?:[ \t\x{00A0}\x{2009}\x{202F}]+\d{1,2}:\d{2}(?::\d{2})?)?|\d{1,2}[ \t\x{00A0}\x{2009}\x{202F}]+[A-Za-z]+[ \t\x{00A0}\x{2009}\x{202F}]+\d{4}(?:[ \t\x{00A0}\x{2009}\x{202F}]+\d{1,2}:\d{2}(?::\d{2})?)?)[ \t\x{00A0}\x{2009}\x{202F}]+bol zostatok Vasho uctu ([A-Za-z0-9]+) (zvyseny|znizeny) o ([0-9 \x{00A0}\x{2009}\x{202F}\t]+,[0-9]+) ([A-Za-z]+)/mu';
19
20    /**
21     * @return list<string>
22     */
23    #[\Override]
24    public function bankDomains(): array
25    {
26        return ['tatrabanka.sk'];
27    }
28
29    #[\Override]
30    public function supports(string $content, ?string $sender = null, ?string $subject = null): bool
31    {
32        return $this->supportsQuietly(function () use ($content, $sender, $subject): bool {
33            $content = $this->withoutFreeText($this->scrubUtf8($this->withoutByteOrderMark($content)));
34            if ($this->matched(self::TRANSACTION, $content)) {
35                return true;
36            }
37
38            $senderMatches = $sender !== null && $this->senderDomain($sender, 'tatrabanka.sk');
39            $subjectMatches = $subject !== null && str_contains(strtolower($subject), 'tatra');
40
41            return ($senderMatches || $subjectMatches) && str_contains($content, 'zostatok Vasho uctu');
42        });
43    }
44
45    public function parse(string $content): MailContent
46    {
47        $original = $this->scrubUtf8($this->requireUniformLineBreaks($content));
48        $description = $this->lineValue($original, 'Popis transakcie');
49        $receiverMessage = $this->lineValue($original, 'Informacia pre prijemcu');
50        $sourceAccountNumber = null;
51        if ($description !== null) {
52            $descriptionParts = explode(' ', $description, 2);
53            $sourceAccountNumber = count($descriptionParts) === 2 ? $descriptionParts[1] : $descriptionParts[0];
54        }
55
56        $content = $this->withoutFreeText($original);
57        if (!$this->matched(self::TRANSACTION, $content, $matches)) {
58            throw InvalidEmailFormatException::malformedContent(
59                self::class,
60                'Could not find required transaction pattern',
61            );
62        }
63
64        $rawDate = trim($this->group($matches, 1));
65        $transactionDate = BankDate::parseFirst($rawDate, [
66            '!j. n. Y G:i:s',
67            '!j.n.Y G:i:s',
68            '!j. n. Y G:i',
69            '!j.n.Y G:i',
70            '!Y-m-d G:i:s',
71            '!Y-m-d H:i:s',
72            '!Y-m-d G:i',
73            '!Y-m-d H:i',
74            '!j. n. Y',
75            '!j.n.Y',
76            '!Y-m-d',
77        ], BankDate::bratislava());
78        if ($transactionDate === null && preg_match('/[A-Za-z]/', $rawDate) === 1) {
79            $transactionDate = BankDate::parseEnglish($rawDate, BankDate::bratislava());
80        }
81
82        $accountNumber = $this->group($matches, 2);
83        $amount = $this->group($matches, 4);
84        if ($this->group($matches, 3) === 'znizeny') {
85            $amount = '-' . $amount;
86        }
87
88        $money = $this->money($amount, $this->group($matches, 5), ',');
89
90        $vs = null;
91        $ss = null;
92        $ks = null;
93        if ($this->matched($this->field('Referencia platitela:', '\/VS(\d*)\/SS(\d*)\/KS(\d*)'), $original, $refMatches)) {
94            $vs = $this->paymentSymbol($this->group($refMatches, 1), null);
95            $ss = $this->paymentSymbol($this->group($refMatches, 2), null);
96            $ks = $this->paymentSymbol($this->group($refMatches, 3), null);
97        }
98
99        // 4.1.0 fallbacks. They read payer text and never the amount, account, or date.
100        if ($vs === null && $this->matched('/vs([0-9]{1,10})(?!\d)/i', $original, $prefixed)) {
101            $vs = $this->group($prefixed, 1);
102        }
103        if ($vs === null && $this->matched('/Informacia pre prijemcu:.*\b([0-9]{1,10})\b.*/i', $original, $fromMessage)) {
104            $vs = $this->group($fromMessage, 1);
105        }
106        if ($vs === null && $this->matched('/Referencia platitela:.*\b([0-9]{1,10})\b.*/i', $original, $fromReference)) {
107            $vs = $this->group($fromReference, 1);
108        }
109
110        return new MailContent(
111            money: $money,
112            transactionDate: $transactionDate,
113            accountNumber: $accountNumber,
114            sourceAccountNumber: $sourceAccountNumber,
115            vs: $vs,
116            ss: $ss,
117            ks: $ks,
118            receiverMessage: $receiverMessage,
119            description: $description,
120        );
121    }
122}