Issues (56)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Bankly.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace WeDevBr\Bankly;
4
5
use Illuminate\Http\Client\RequestException;
6
use Illuminate\Support\Facades\Http;
7
use Ramsey\Uuid\Uuid;
8
use TypeError;
9
use WeDevBr\Bankly\Inputs\Customer;
10
use WeDevBr\Bankly\Inputs\DocumentAnalysis;
11
use WeDevBr\Bankly\Support\Contracts\CustomerInterface;
12
use WeDevBr\Bankly\Support\Contracts\DocumentInterface;
13
use WeDevBr\Bankly\Types\Billet\DepositBillet;
14
use WeDevBr\Bankly\Types\Pix\PixEntries;
15
use WeDevBr\Bankly\Types\Card\Card;
16
use WeDevBr\Bankly\Contracts\Pix\PixCashoutInterface;
17
18
/**
19
 * Class Bankly
20
 * @author Adeildo Amorim <[email protected]>
21
 * @package WeDevBr\Bankly
22
 */
23
class Bankly
24
{
25
    public $api_url;
26
    public $login_url;
27
    private $client_id;
28
    private $client_secret;
29
    private $token_expiry = 0;
30
    private $token = null;
31
    private $api_version = '1.0';
32
    private $headers;
33
34
    /**
35
     * Bankly constructor.
36
     * @param null|string $client_secret provided by Bankly Staff
37
     * @param null|string $client_id provided by Bankly Staff
38
     */
39
    public function __construct($client_secret = null, $client_id = null)
40
    {
41
        $this->api_url = config('bankly')['api_url'];
42
        $this->login_url = config('bankly')['login_url'];
43
        $this->setClientCredentials(['client_secret' => $client_secret, 'client_id' => $client_id]);
44
        $this->headers = ['API-Version' => $this->api_version];
45
    }
46
47
    /**
48
     * @param array|null $credentials
49
     * @return $this
50
     */
51
    public function setClientCredentials(array $credentials = null)
52
    {
53
        $this->client_secret = $credentials['client_secret'] ?? config('bankly')['client_secret'];
54
        $this->client_id = $credentials['client_id'] ?? config('bankly')['client_id'];
55
        return $this;
56
    }
57
58
    /**
59
     * @return array|mixed
60
     * @throws RequestException
61
     */
62
    public function getBankList()
63
    {
64
        return $this->get('/banklist');
65
    }
66
67
    /**
68
     * Retrieve your balance account
69
     * @param string $branch
70
     * @param string $account
71
     * @return array|mixed
72
     * @throws RequestException
73
     * @note If you have a RequestException on this endpoint in staging environment, please use getAccount() method instead.
74
     */
75
    public function getBalance(string $branch, string $account)
76
77
    {
78
        return $this->get('/account/balance', [
79
            'branch' => $branch,
80
            'account' => $account
81
        ]);
82
    }
83
84
    /**
85
     * @param string $account
86
     * @param string $includeBalance
87
     * @return array|mixed
88
     * @throws RequestException
89
     * @note This method on this date (2020-10-21) works only on staging environment. Contact Bankly/Acesso for more details
90
     */
91
    public function getAccount(string $account, string $includeBalance = 'true')
92
    {
93
        return $this->get('/accounts/' . $account, [
94
            'includeBalance' => $includeBalance,
95
        ]);
96
    }
97
98
    /**
99
     * @param $branch
100
     * @param $account
101
     * @param int $offset
102
     * @param int $limit
103
     * @param string $details
104
     * @param string $detailsLevelBasic
105
     * @return array|mixed
106
     * @throws RequestException
107
     */
108
    public function getStatement(
109
        $branch,
110
        $account,
111
        $offset = 1,
112
        $limit = 20,
113
        string $details = 'true',
114
        string $detailsLevelBasic = 'true'
115
    ) {
116
        return $this->get('/account/statement', array(
117
            'branch' => $branch,
118
            'account' => $account,
119
            'offset' => $offset,
120
            'limit' => $limit,
121
            'details' => $details,
122
            'detailsLevelBasic' => $detailsLevelBasic
123
        ));
124
    }
125
126
    /**
127
     * @param string $branch
128
     * @param string $account
129
     * @param int $page
130
     * @param int $pagesize
131
     * @param string $include_details
132
     * @param string[] $cardProxy
133
     * @param string|null $begin_date
134
     * @param string|null $end_date
135
     * @return array|mixed
136
     * @throws RequestException
137
     * @note This endpoint has been deprecated for some clients.
138
     * You need to check with Acesso/Bankly if your environment has different parameters also.
139
     * The response of this request does not have a default interface between environments.
140
     * Pay attention when use this in your project.
141
     */
142
    public function getEvents(
143
        string $branch,
144
        string $account,
145
        int $page = 1,
146
        int $pagesize = 20,
147
        string $include_details = 'true',
148
        array $cardProxy = [],
149
        string $begin_date = null,
150
        string $end_date = null
151
    ) {
152
        $query = [
153
            'branch' => $branch,
154
            'account' => $account,
155
            'page' => $page,
156
            'pageSize' => $pagesize,
157
            'includeDetails' => $include_details
158
        ];
159
160
        if (!empty($cardProxy)) {
161
            $query['cardProxy'] = $cardProxy;
162
        }
163
164
        if ($begin_date) {
165
            $query['beginDateTime'] = $begin_date;
166
        }
167
168
        if ($end_date) {
169
            $query['endDateTime'] = $end_date;
170
        }
171
172
        return $this->get(
173
            '/events',
174
            $query
175
        );
176
    }
177
178
    /**
179
     * @param int $amount
180
     * @param string $description
181
     * @param array $sender
182
     * @param array $recipient
183
     * @param string|null $correlation_id
184
     * @return array|mixed
185
     * @throws RequestException
186
     */
187
    public function transfer(
188
        int $amount,
189
        string $description,
190
        array $sender,
191
        array $recipient,
192
        string $correlation_id = null
193
    ) {
194
        if ($sender['bankCode']) {
195
            unset($sender['bankCode']);
196
        }
197
198
        return $this->post(
199
            '/fund-transfers',
200
            [
201
                'amount' => $amount,
202
                'description' => $description,
203
                'sender' => $sender,
204
                'recipient' => $recipient
205
            ],
206
            $correlation_id,
207
            true
208
        );
209
    }
210
211
    /**
212
     * Get transfer funds from an account
213
     * @param string $branch
214
     * @param string $account
215
     * @param int $pageSize
216
     * @param string|null $nextPage
217
     * @return array|mixed
218
     * @throws RequestException
219
     */
220
    public function getTransferFunds(string $branch, string $account, int $pageSize = 10, string $nextPage = null)
221
    {
222
        $queryParams = [
223
            'branch' => $branch,
224
            'account' => $account,
225
            'pageSize' => $pageSize
226
        ];
227
        if ($nextPage) {
228
            $queryParams['nextPage'] = $nextPage;
229
        }
230
        return $this->get('/fund-transfers', $queryParams);
231
    }
232
233
    /**
234
     * Get Transfer Funds By Authentication Code
235
     * @param string $branch
236
     * @param string $account
237
     * @param string $authenticationCode
238
     * @return array|mixed
239
     * @throws RequestException
240
     */
241
    public function findTransferFundByAuthCode(string $branch, string $account, string $authenticationCode)
242
    {
243
        $queryParams = [
244
            'branch' => $branch,
245
            'account' => $account
246
        ];
247
        return $this->get('/fund-transfers/' . $authenticationCode, $queryParams);
248
    }
249
250
    /**
251
     * @param string $branch
252
     * @param string $account
253
     * @param string $authentication_id
254
     * @return array|mixed
255
     * @throws RequestException
256
     */
257
    public function getTransferStatus(string $branch, string $account, string $authentication_id)
258
    {
259
        return $this->get('/fund-transfers/' . $authentication_id . '/status', [
260
            'branch' => $branch,
261
            'account' => $account
262
        ]);
263
    }
264
265
    /**
266
     * @param string $documentNumber
267
     * @param DocumentAnalysis $document
268
     * @param string $correlationId
269
     * @return array|mixed
270
     * @throws RequestException
271
     */
272
    public function documentAnalysis(
273
        string $documentNumber,
274
        $document,
275
        string $correlationId = null
276
    ) {
277
        if (!$document instanceof DocumentInterface) {
278
            throw new TypeError('The document must be an instance of DocumentInterface');
0 ignored issues
show
The call to TypeError::__construct() has too many arguments starting with 'The document must be an...e of DocumentInterface'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
279
        }
280
281
        return $this->put(
282
            "/document-analysis/{$documentNumber}",
283
            [
284
                'documentType' => $document->getDocumentType(),
285
                'documentSide' => $document->getDocumentSide(),
286
            ],
287
            $correlationId,
288
            true,
289
            true,
290
            $document
291
        );
292
    }
293
294
    /**
295
     * @param string $documentNumber
296
     * @param array $tokens
297
     * @param string $resultLevel
298
     * @param string $correlationId
299
     * @return array|mixed
300
     */
301
    public function getDocumentAnalysis(
302
        string $documentNumber,
303
        array $tokens = [],
304
        string $resultLevel = 'ONLY_STATUS',
305
        string $correlationId = null
306
    ) {
307
        $query = collect($tokens)
308
            ->map(function ($token) {
309
                return "token={$token}";
310
            })
311
            ->concat(["resultLevel={$resultLevel}"])
312
            ->implode('&');
313
314
        return $this->get(
315
            "/document-analysis/{$documentNumber}",
316
            $query,
317
            $correlationId
0 ignored issues
show
It seems like $correlationId defined by parameter $correlationId on line 305 can also be of type string; however, WeDevBr\Bankly\Bankly::get() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
318
        );
319
    }
320
321
    /**
322
     * @param string $documentNumber
323
     * @param Customer $customer
324
     * @param string $correlationId
325
     * @return array|mixed
326
     * @throws TypeError|RequestException
327
     */
328
    public function customer(
329
        string $documentNumber,
330
        $customer,
331
        string $correlationId = null
332
    ) {
333
        if (!$customer instanceof CustomerInterface) {
334
            throw new TypeError('The customer must be an instance of CustomerInterface');
0 ignored issues
show
The call to TypeError::__construct() has too many arguments starting with 'The customer must be an...e of CustomerInterface'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
335
        }
336
337
        return $this->put("/customers/{$documentNumber}", $customer->toArray(), $correlationId, true);
338
    }
339
340
    /**
341
     * @param string $documentNumber
342
     * @param string $resultLevel
343
     * @return array|mixed
344
     */
345
    public function getCustomer(string $documentNumber, string $resultLevel = 'DETAILED')
346
    {
347
        return $this->get("/customers/{$documentNumber}?resultLevel={$resultLevel}");
348
    }
349
350
    /**
351
     * @param string $documentNumber
352
     * @return array|mixed
353
     */
354
    public function getCustomerAccounts(string $documentNumber)
355
    {
356
        return $this->get("/customers/{$documentNumber}/accounts");
357
    }
358
359
    /**
360
     * Validate of boleto or dealership
361
     *
362
     * @param string $code - Digitable line
363
     * @param string $correlationId
364
     * @return array|mixed
365
     * @throws RequestException
366
     */
367
    public function paymentValidate(string $code, string $correlationId)
368
    {
369
        return $this->post('/bill-payment/validate', ['code' => $code], $correlationId, true);
370
    }
371
372
    /**
373
     * Confirmation of payment of boleto or dealership
374
     *
375
     * @param BillPayment $billPayment
376
     * @param string $correlationId
377
     * @return array|mixed
378
     */
379
    public function paymentConfirm(
380
        BillPayment $billPayment,
381
        string $correlationId
382
    ) {
383
        return $this->post('/bill-payment/confirm', $billPayment->toArray(), $correlationId, true);
384
    }
385
386
    /**
387
     * @param DepositBillet $depositBillet
388
     * @return array|mixed
389
     */
390
    public function depositBillet(DepositBillet $depositBillet)
391
    {
392
        return $this->post('/bankslip', $depositBillet->toArray(), null, true);
393
    }
394
395
    /**
396
     * @param string $authenticationCode
397
     * @return mixed
398
     */
399
    public function printBillet(string $authenticationCode)
400
    {
401
        return $this->get("/bankslip/{$authenticationCode}/pdf");
402
    }
403
404
    /**
405
     * @param string $branch
406
     * @param string $accountNumber
407
     * @param string $authenticationCode
408
     * @return array|mixed
409
     */
410
    public function getBillet(string $branch, string $accountNumber, string $authenticationCode)
411
    {
412
        return $this->get("/bankslip/branch/{$branch}/number/{$accountNumber}/{$authenticationCode}");
413
    }
414
415
    /**
416
     * @param string $datetime
417
     * @return array|mixed
418
     */
419
    public function getBilletByDate(string $datetime)
420
    {
421
        return $this->get("/bankslip/searchstatus/{$datetime}");
422
    }
423
424
    /**
425
     * @param string $barcode
426
     * @return array|mixed
427
     */
428
    public function getBilletByBarcode(string $barcode)
429
    {
430
        return $this->get("/bankslip/{$barcode}");
431
    }
432
433
    /**
434
     * Create a new PIX key link with account.
435
     *
436
     * @param PixEntries $pixEntries
437
     * @return array|mixed
438
     */
439
    public function registerPixKey(PixEntries $pixEntries)
440
    {
441
        return $this->post('/pix/entries', [
442
            'addressingKey' => $pixEntries->addressingKey->toArray(),
443
            'account' => $pixEntries->account->toArray(),
444
        ], null, true);
445
    }
446
447
    /**
448
     * Gets the list of address keys linked to an account.
449
     *
450
     * @param string $accountNumber
451
     * @return array|mixed
452
     */
453
    public function getPixAddressingKeys(string $accountNumber)
454
    {
455
        return $this->get("/accounts/$accountNumber/addressing-keys");
456
    }
457
458
    /**
459
     * Gets details of the account linked to an addressing key.
460
     *
461
     * @param string $documentNumber
462
     * @param string $addressinKeyValue
463
     * @return array|mixed
464
     */
465
    public function getPixAddressingKeyValue(string $documentNumber, string $addressinKeyValue)
466
    {
467
        $this->setHeaders(['x-bkly-pix-user-id' => $documentNumber]);
468
        return $this->get("/pix/entries/$addressinKeyValue");
469
    }
470
471
    /**
472
     * Delete a key link with account.
473
     *
474
     * @param string $addressingKeyValue
475
     * @return array|mixed
476
     */
477
    public function deletePixAddressingKeyValue(string $addressingKeyValue)
478
    {
479
        return $this->delete("/pix/entries/$addressingKeyValue");
480
    }
481
482
    /**
483
     * @param PixCashoutInterface $pixCashout
484
     * @param string $correlationId
485
     * @return array|mixed
486
     */
487
    public function pixCashout(PixCashoutInterface $pixCashout, string $correlationId)
488
    {
489
        return $this->post('/pix/cash-out', $pixCashout->toArray(), $correlationId, true);
490
    }
491
492
    /**
493
     * @param string $endpoint
494
     * @param array|string|null $query
495
     * @param null $correlation_id
496
     * @return array|mixed
497
     * @throws RequestException
498
     */
499
    private function get(string $endpoint, $query = null, $correlation_id = null)
500
    {
501
        if (now()->unix() > $this->token_expiry || !$this->token) {
502
            $this->auth();
503
        }
504
505
        if (is_null($correlation_id) && $this->requireCorrelationId($endpoint)) {
506
            $correlation_id = Uuid::uuid4()->toString();
507
        }
508
509
        return Http::withToken($this->token)
510
            ->withHeaders($this->getHeaders(['x-correlation-id' => $correlation_id]))
511
            ->get($this->getFinalUrl($endpoint), $query)
512
            ->throw()
513
            ->json();
514
    }
515
516
    /**
517
     * Create a new virtual card
518
     *
519
     * @param Card $virtualCard
520
     * @return array|mixed
521
     * @throws RequestException
522
     */
523
    public function virtualCard(Card $virtualCard)
524
    {
525
        return $this->post('/cards/virtual', $virtualCard->toArray(), null, true);
526
    }
527
528
    /**
529
     * Create a new physical card
530
     *
531
     * @param Card $physicalCard
532
     * @return array|mixed
533
     * @throws RequestException
534
     */
535
    public function physicalCard(Card $physicalCard)
536
    {
537
        return $this->post('/cards/physical', $physicalCard->toArray(), null, true);
538
    }
539
540
    /**
541
     * @param string $endpoint
542
     * @param array|null $body
543
     * @param string|null $correlation_id
544
     * @param bool $asJson
545
     * @return array|mixed
546
     * @throws RequestException
547
     */
548
    private function post(string $endpoint, array $body = null, string $correlation_id = null, bool $asJson = false)
549
    {
550
        if (now()->unix() > $this->token_expiry || !$this->token) {
551
            $this->auth();
552
        }
553
554
        if (is_null($correlation_id) && $this->requireCorrelationId($endpoint)) {
555
            $correlation_id = Uuid::uuid4()->toString();
556
        }
557
558
        $body_format = $asJson ? 'json' : 'form_params';
559
560
        return Http
561
            ::withToken($this->token)
562
            ->withHeaders($this->getHeaders(['x-correlation-id' => $correlation_id]))
563
            ->bodyFormat($body_format)
564
            ->post($this->getFinalUrl($endpoint), $body)
565
            ->throw()
566
            ->json();
567
    }
568
569
    /**
570
     * @param string $endpoint
571
     * @param array|null $body
572
     * @param string|null $correlation_id
573
     * @param bool $asJson
574
     * @param bool $attachment
575
     * @param DocumentAnalysis $document
576
     * @param string $fieldName
0 ignored issues
show
There is no parameter named $fieldName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
577
     * @return array|mixed
578
     * @throws RequestException
579
     */
580
    private function put(
581
        string $endpoint,
582
        array $body = [],
583
        string $correlation_id = null,
584
        bool $asJson = false,
585
        bool $attachment = false,
586
        DocumentAnalysis $document = null
587
    ) {
588
        if (now()->unix() > $this->token_expiry || !$this->token) {
589
            $this->auth();
590
        }
591
592
        if (is_null($correlation_id) && $this->requireCorrelationId($endpoint)) {
593
            $correlation_id = Uuid::uuid4()->toString();
594
        }
595
596
        $body_format = $asJson ? 'json' : 'form_params';
597
598
        $request = Http
599
            ::withToken($this->token)
600
            ->withHeaders($this->getHeaders(['x-correlation-id' => $correlation_id]))
601
            ->bodyFormat($body_format);
602
603
        if ($attachment) {
604
            $request->attach($document->getFieldName(), $document->getFileContents(), $document->getFileName());
0 ignored issues
show
It seems like $document is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
605
        }
606
607
        return $request->put($this->getFinalUrl($endpoint), $body)
608
            ->throw()
609
            ->json();
610
    }
611
612
    /**
613
     * Http delete method.
614
     *
615
     * @param string $endpoint
616
     * @return array|mixed
617
     * @throws RequestException
618
     */
619
    private function delete(string $endpoint)
620
    {
621
        if (now()->unix() > $this->token_expiry || !$this->token) {
622
            $this->auth();
623
        }
624
625
        $request = Http::withToken($this->token)
626
            ->withHeaders($this->getHeaders($this->headers));
627
628
        return $request->delete($this->getFinalUrl($endpoint))
629
            ->throw()
630
            ->json();
631
    }
632
633
    /**
634
     * @param string $version API version
635
     * @return $this
636
     */
637
    private function setApiVersion($version = '1.0')
638
    {
639
        $this->api_version = $version;
640
        return $this;
641
    }
642
643
    /**
644
     * @param array $headers
645
     * @return array|string[]
646
     */
647
    private function getHeaders($headers = [])
648
    {
649
        $default_headers = $this->headers;
650
651
        if (count($headers) > 0) {
652
            $default_headers = array_merge($headers, $default_headers);
653
        }
654
655
        return $default_headers;
656
    }
657
658
    /**
659
     * @param array $header
660
     * @return void
661
     */
662
    private function setHeaders($header)
663
    {
664
        $this->headers = array_merge($this->headers, $header);
665
    }
666
667
    /**
668
     * @param string $endpoint
669
     * @return bool
670
     */
671
    private function requireCorrelationId(string $endpoint)
672
    {
673
        $not_required_endpoints = [
674
            '/banklist',
675
            '/connect/token'
676
        ];
677
678
        return !in_array($endpoint, $not_required_endpoints);
679
    }
680
681
    /**
682
     * @param string $endpoint
683
     * @return string
684
     */
685
    private function getFinalUrl(string $endpoint)
686
    {
687
        return $this->api_url . $endpoint;
688
    }
689
690
    /**
691
     * Do authentication
692
     * @param string $grant_type Default sets to 'client_credentials'
693
     * @throws RequestException
694
     */
695 View Code Duplication
    private function auth($grant_type = 'client_credentials'): void
696
    {
697
        //TODO: Add auth for username and password
698
        $body = [
699
            'grant_type' => $grant_type,
700
            'client_secret' => $this->client_secret,
701
            'client_id' => $this->client_id
702
        ];
703
704
        $response = Http::asForm()->post($this->login_url, $body)->throw()->json();
705
        $this->token = $response['access_token'];
706
        $this->token_expiry = now()->addSeconds($response['expires_in'])->unix();
707
    }
708
}
709