PaymentList::getPaymentList()   A
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 38
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 26
c 3
b 0
f 0
dl 0
loc 38
rs 9.1928
cc 5
nc 6
nop 0
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * PaymentList.php
6
 * Copyright (c) 2020 [email protected].
7
 *
8
 * This file is part of the Firefly III bunq importer
9
 * (https://github.com/firefly-iii/bunq-importer).
10
 *
11
 * This program is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License as
13
 * published by the Free Software Foundation, either version 3 of the
14
 * License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License
22
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
 */
24
25
namespace App\Bunq\Requests;
26
27
use App\Bunq\ApiContext\ApiContextManager;
28
use App\Exceptions\ImportException;
29
use App\Services\Configuration\Configuration;
30
use App\Services\Sync\JobStatus\ProgressInformation;
31
use bunq\Model\Generated\Endpoint\Payment as BunqPayment;
32
use Carbon\Carbon;
33
use Illuminate\Contracts\Filesystem\FileNotFoundException;
34
use Illuminate\Support\Facades\Storage;
35
36
/**
37
 * Class PaymentList.
38
 */
39
class PaymentList
40
{
41
    use ProgressInformation;
42
43
    /** @var Configuration */
44
    private $configuration;
45
    /** @var int */
46
    private $count;
47
    /** @var string */
48
    private $downloadIdentifier;
49
    /** @var Carbon */
50
    private $notAfter;
51
    /** @var Carbon */
52
    private $notBefore;
53
54
    /**
55
     * PaymentList constructor.
56
     *
57
     * @param Configuration $configuration
58
     */
59
    public function __construct(Configuration $configuration)
60
    {
61
        $this->configuration = $configuration;
62
        $this->count         = 0;
63
        $this->notBefore     = null === $configuration->getDateNotBefore() ? null : Carbon::createFromFormat('Y-m-d', $configuration->getDateNotBefore());
64
        $this->notAfter      = null === $configuration->getDateNotAfter() ? null : Carbon::createFromFormat('Y-m-d', $configuration->getDateNotAfter());
65
        if (null !== $this->notBefore) {
66
            $this->notBefore->startOfDay();
67
        }
68
        if (null !== $this->notAfter) {
69
            $this->notAfter->endOfDay();
70
        }
71
        app('log')->debug('Created Requests\\PaymentList');
72
    }
73
74
    public function getPaymentList(): array
75
    {
76
        app('log')->debug('Start of PaymentList::getPaymentList()');
77
78
        if ($this->hasDownload()) {
79
            app('log')->info('Already downloaded content for this job. Return it.');
80
            $this->addMessage(0, 'Already had data. This is not a problem though.');
81
82
            return $this->getDownload();
83
        }
84
85
        $return = [];
86
        try {
87
            ApiContextManager::getApiContext();
88
        } catch (ImportException $e) {
89
            app('log')->error($e->getMessage());
90
            app('log')->error($e->getTraceAsString());
91
            $this->addError(0, $e->getMessage());
92
93
            return [];
94
        }
95
        $totalCount = 0;
96
        foreach (array_keys($this->configuration->getAccounts()) as $bunqAccountId) {
97
            $bunqAccountId = (int) $bunqAccountId;
98
            try {
99
                $return[$bunqAccountId] = $this->getForAccount($bunqAccountId);
100
                $totalCount             += count($return[$bunqAccountId]);
101
            } catch (ImportException $e) {
102
                app('log')->error($e->getMessage());
103
                app('log')->error($e->getTraceAsString());
104
                $this->addError(0, $e->getMessage());
105
            }
106
        }
107
        app('log')->notice(sprintf('Downloaded a total of %d transactions from bunq.', $totalCount));
108
        // store the result somewhere so it can be processed easily.
109
        $this->storeDownload($return);
110
111
        return $return;
112
    }
113
114
    /**
115
     * @param string $downloadIdentifier
116
     */
117
    public function setDownloadIdentifier(string $downloadIdentifier): void
118
    {
119
        $this->downloadIdentifier = $downloadIdentifier;
120
        $this->identifier         = $downloadIdentifier;
121
    }
122
123
    /**
124
     * @return array
125
     */
126
    private function getDownload(): array
127
    {
128
        $disk = Storage::disk('downloads');
129
        try {
130
            $content = (string) $disk->get($this->downloadIdentifier);
131
        } catch (FileNotFoundException $e) {
132
            app('log')->error('Could not store download');
133
            app('log')->error($e->getMessage());
134
            app('log')->error($e->getTraceAsString());
135
            $content = '[]';
136
        }
137
138
        return json_decode($content, true, 512, JSON_THROW_ON_ERROR);
139
    }
140
141
    /**
142
     * @param int $bunqAccountId
143
     *
144
     * @throws ImportException
145
     * @return array
146
     */
147
    private function getForAccount(int $bunqAccountId): array
148
    {
149
        app('log')->debug(sprintf('Now in getForAccount(%d)', $bunqAccountId));
150
        $hasMoreTransactions = true;
151
        $olderId             = null;
152
        $return              = [];
153
154
        /*
155
         * Do a loop during which we run:
156
         */
157
        while ($hasMoreTransactions && $this->count <= 45) {
158
            app('log')->debug(sprintf('Now in loop #%d for account %d', $this->count, $bunqAccountId));
159
            $previousLoop = count($return);
160
            /*
161
             * Send request to bunq.
162
             */
163
            /** @var Payment $paymentRequest */
164
            $paymentRequest = app(Payment::class);
165
            $params         = ['count' => 197, 'older_id' => $olderId];
166
            $response       = $paymentRequest->listing($bunqAccountId, $params);
167
            $pagination     = $response->getPagination();
168
            app('log')->debug('Params for the request to bunq are: ', $params);
169
170
            /*
171
             * If pagination is not null, we can go back even further.
172
             */
173
            if (null !== $pagination) {
174
                $olderId = $pagination->getOlderId();
175
                app('log')->debug(sprintf('Pagination object is not null, new olderID is "%s"', $olderId));
176
            }
177
178
            /*
179
             * Loop the results from bunq
180
             */
181
            app('log')->debug('Now looping results from bunq...');
182
            /** @var BunqPayment $payment */
183
            foreach ($response->getValue() as $index => $payment) {
184
                app('log')->debug(sprintf('Going to process payment on index #%d', $index));
185
                $array = $this->processBunqPayment($index, $payment);
186
                if (null !== $array) {
187
                    $return[] = $array;
188
                }
189
            }
190
191
            /*
192
             * After the loop, check if must loop again.
193
             */
194
            app('log')->debug(sprintf('Count of return is now %d', count($return)));
195
            $this->count++;
196
            if (null === $olderId) {
197
                app('log')->debug('Older ID is NULL, so stop looping cause we are done!');
198
                $hasMoreTransactions = false;
199
            }
200
201
            if (null === $pagination) {
202
                app('log')->debug('No pagination object, stop looping.');
203
            }
204
            if ($previousLoop === count($return)) {
205
                app('log')->info('No new transactions were added to the array.');
206
                $hasMoreTransactions = false;
207
            }
208
209
            // sleep 2 seconds to prevent hammering bunq.
210
            sleep(2);
211
        }
212
        // store newest and oldest tranasction ID to be used later:
213
        app('log')->info(sprintf('Downloaded and parsed %d transactions from bunq (from this account).', count($return)));
214
215
        return $return;
216
    }
217
218
    /**
219
     * @return bool
220
     */
221
    private function hasDownload(): bool
222
    {
223
        $disk = Storage::disk('downloads');
224
225
        return $disk->exists($this->downloadIdentifier);
226
    }
227
228
    /**
229
     * @param int         $index
230
     * @param BunqPayment $payment
231
     *
232
     * @return array
233
     */
234
    private function processBunqPayment(int $index, BunqPayment $payment): ?array
0 ignored issues
show
Unused Code introduced by
The parameter $index is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
    private function processBunqPayment(/** @scrutinizer ignore-unused */ int $index, BunqPayment $payment): ?array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
    {
236
        $created = Carbon::createFromFormat('Y-m-d H:i:s.u', $payment->getCreated());
237
        if (null !== $this->notBefore && $created->lte($this->notBefore)) {
238
            app('log')->info(
239
                sprintf(
240
                    'Skip transaction because %s is before %s',
241
                    $created->format('Y-m-d H:i:s'),
242
                    $this->notBefore->format('Y-m-d H:i:s')
243
                )
244
            );
245
246
            return null;
247
        }
248
        if (null !== $this->notAfter && $created->gte($this->notAfter)) {
249
            app('log')->info(
250
                sprintf(
251
                    'Skip transaction because %s is after %s',
252
                    $created->format('Y-m-d H:i:s'),
253
                    $this->notAfter->format('Y-m-d H:i:s')
254
                )
255
            );
256
257
            return null;
258
        }
259
260
        $transaction                                  = [
261
            // TODO country, bunqMe, isLight, swiftBic, swiftAccountNumber, transferwiseAccountNumber, transferwiseBankCode
262
            // TODO merchantCategoryCode, bunqtoStatus, bunqtoSubStatus, bunqtoExpiry, bunqtoTimeResponded
263
            // TODO merchantReference, batchId, scheduledId, addressShipping, addressBilling, geolocation, allowChat,
264
            // TODO requestReferenceSplitTheBill
265
            'id'              => $payment->getId(),
266
            'created'         => $created,
267
            'updated'         => Carbon::createFromFormat('Y-m-d H:i:s.u', $payment->getUpdated()),
268
            'bunq_account_id' => $payment->getMonetaryAccountId(),
269
            'amount'          => $payment->getAmount()->getValue(),
270
            'currency_code'   => $payment->getAmount()->getCurrency(),
271
            'counter_party'   => [
272
                'iban'         => null,
273
                'display_name' => null,
274
                'nick_name'    => null,
275
                'country'      => null,
276
            ],
277
            'description'     => trim($payment->getDescription()),
278
            'type'            => $payment->getType(),
279
            'sub_type'        => $payment->getSubType(),
280
            'balance_after'   => $payment->getBalanceAfterMutation()->getValue(),
281
        ];
282
        $counterParty                                 = $payment->getCounterpartyAlias();
283
        $transaction['counter_party']['iban']         = $counterParty->getIban();
284
        $transaction['counter_party']['display_name'] = $counterParty->getDisplayName();
285
        $transaction['counter_party']['nick_name']    = $counterParty->getLabelUser()->getDisplayName();
286
        $transaction['counter_party']['country']      = $counterParty->getCountry();
287
        if ('' === $transaction['description']) {
288
            $transaction['description'] = '(empty description)';
289
        }
290
291
        app('log')->debug(
292
            sprintf(
293
                'Downloaded and parsed transaction #%d (%s) "%s" (%s %s).',
294
                $transaction['id'], $transaction['created']->format('Y-m-d'),
295
                $transaction['description'], $transaction['currency_code'], $transaction['amount']
296
            )
297
        );
298
299
        return $transaction;
300
    }
301
302
    /**
303
     * @param array $data
304
     */
305
    private function storeDownload(array $data): void
306
    {
307
        $disk = Storage::disk('downloads');
308
        $disk->put($this->downloadIdentifier, json_encode($data, JSON_THROW_ON_ERROR, 512));
309
    }
310
}
311