Passed
Push — main ( cb0dfb...09414e )
by Miaad
10:52
created

crypto::getOrder()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 10
rs 10
1
<?php
2
3
namespace BPT\pay;
4
5
use BPT\BPT;
6
use BPT\constants\callbackTypes;
7
use BPT\constants\cryptoCallbackActionTypes;
8
use BPT\constants\cryptoCallbackStatus;
9
use BPT\constants\cryptoStatus;
10
use BPT\constants\fields;
11
use BPT\constants\loggerTypes;
12
use BPT\database\mysql;
13
use BPT\exception\bptException;
14
use BPT\logger;
15
use BPT\pay\crypto\errorResponseInterface;
16
use BPT\pay\crypto\estimatePriceInterface;
17
use BPT\pay\crypto\estimateUpdateInterface;
18
use BPT\pay\crypto\invoicePaymentInterface;
19
use BPT\pay\crypto\invoiceResponseInterface;
20
use BPT\pay\crypto\ipnDataInterface;
21
use BPT\pay\crypto\paymentInterface;
22
use BPT\receiver\callback;
23
use BPT\settings;
24
use BPT\telegram\request;
25
use BPT\tools\tools;
26
use BPT\types\cryptoCallback;
27
use CurlHandle;
28
use function BPT\object;
29
30
class crypto {
31
    private static string $api_key = '';
32
33
    private static string $ipn_secret = '';
34
35
    private static int $round_decimal = 4;
36
37
    const API_BASE = 'https://api.nowpayments.io/v1/';
38
39
    private static CurlHandle $session;
40
41
    public static function init (string $api_key = '', string $ipn_secret = '', int $round_decimal = 4): void {
42
        self::$api_key = settings::$pay['crypto']['api_key'] ?? $api_key;
43
        self::$ipn_secret = settings::$pay['crypto']['ipn_secret'] ?? $ipn_secret;
44
        self::$round_decimal = settings::$pay['crypto']['round_decimal'] ?? $round_decimal;
45
        self::$session = curl_init();
46
        curl_setopt(self::$session, CURLOPT_RETURNTRANSFER, true);
47
        curl_setopt(self::$session, CURLOPT_SSL_VERIFYPEER, 1);
48
        curl_setopt(self::$session, CURLOPT_SSL_VERIFYHOST, 2);
49
        curl_setopt(self::$session, CURLOPT_HTTPHEADER, [
50
            'X-API-KEY: ' . self::$api_key,
51
            'Content-Type: application/json'
52
        ]);
53
    }
54
55
    private static function execute (string $method, string $endpoint, string|array $data = '') {
56
        if (is_array($data)) {
57
            foreach ($data as $key => $value) {
58
                if (empty($value)) {
59
                    unset($data[$key]);
60
                }
61
            }
62
        }
63
64
        $session = self::$session;
65
66
        switch ($method) {
67
            case 'GET':
68
                curl_setopt($session, CURLOPT_URL, self::API_BASE . $endpoint . (!empty($data) && is_array($data) ? ('?' . http_build_query($data)) : ''));
69
                break;
70
            case 'POST':
71
                curl_setopt($session, CURLOPT_POST, true);
72
                curl_setopt($session, CURLOPT_POSTFIELDS, json_encode($data));
73
                curl_setopt($session, CURLOPT_URL, self::API_BASE . $endpoint);
74
                break;
75
            default:
76
                return false;
77
        }
78
        return json_decode(curl_exec($session));
0 ignored issues
show
Bug introduced by
It seems like curl_exec($session) can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

78
        return json_decode(/** @scrutinizer ignore-type */ curl_exec($session));
Loading history...
79
    }
80
81
    /**
82
     * This is a method to get information about the current state of the API. Receive true if its ok and false in otherwise
83
     * @return bool
84
     */
85
    public static function status (): bool {
86
        return self::execute('GET', 'status')->message === 'OK';
87
    }
88
89
    /**
90
     * This is a method for calculating the approximate price in cryptocurrency for a given value in Fiat currency.
91
     * You will need to provide the initial cost in the Fiat currency (amount, currency_from) and the necessary
92
     * cryptocurrency (currency_to) Currently following fiat currencies are available: usd, eur, nzd, brl, gbp.
93
     *
94
     * @param int|float $amount
95
     * @param string    $currency_from
96
     * @param string    $currency_to
97
     *
98
     * @return estimatePriceInterface|errorResponseInterface|mixed
99
     */
100
    public static function getEstimatePrice (int|float $amount, string $currency_from, string $currency_to) {
101
        return self::execute('GET', 'estimate', [
102
            'amount'        => $amount,
103
            'currency_from' => $currency_from,
104
            'currency_to'   => $currency_to
105
        ]);
106
    }
107
108
    /**
109
     * Creates payment. With this method, your customer will be able to complete the payment without leaving your website.
110
     *
111
     * @param int|float      $price_amount        the fiat equivalent of the price to be paid in crypto
112
     * @param string         $price_currency      the fiat currency in which the price_amount is specified (usd, eur, etc).
113
     * @param string         $pay_currency        the cryptocurrency in which the pay_amount is specified (btc, eth, etc).
114
     * @param int|float|null $pay_amount          the amount that users have to pay for the order stated in crypto
115
     * @param string|null    $ipn_callback_url    url to receive callbacks, should contain "http" or "https"
116
     * @param string|null    $order_id            inner store order ID
117
     * @param string|null    $order_description   inner store order description
118
     * @param string|null    $purchase_id         id of purchase for which you want to create aother payment, only used for several payments for one order
119
     * @param string|null    $payout_address      usually the funds will go to the address you specify in your Personal account. In case you want to receive funds on another address, you can specify it in this parameter.
120
     * @param string|null    $payout_currency     currency of your external payout_address, required when payout_adress is specified.
121
     * @param string|null    $payout_extra_id     extra id or memo or tag for external payout_address.
122
     * @param bool|null      $fixed_rate          boolean, can be true or false. Required for fixed-rate exchanges.
123
     * @param bool|null      $is_fee_paid_by_user boolean, can be true or false. Required for fixed-rate exchanges with all fees paid by users.
124
     *
125
     * @return invoicePaymentInterface|errorResponseInterface|mixed
126
     */
127
    public static function createPayment (int|float $price_amount, string $price_currency, string $pay_currency, int|float $pay_amount = null, string $ipn_callback_url = null, string $order_id = null, string $order_description = null, string $purchase_id = null, string $payout_address = null, string $payout_currency = null, string $payout_extra_id = null, bool $fixed_rate = null, bool $is_fee_paid_by_user = null) {
128
        return self::execute('POST', 'payment', [
129
            'price_amount'        => $price_amount,
130
            'price_currency'      => $price_currency,
131
            'pay_currency'        => $pay_currency,
132
            'pay_amount'          => $pay_amount,
133
            'ipn_callback_url'    => $ipn_callback_url,
134
            'order_id'            => $order_id,
135
            'order_description'   => $order_description,
136
            'purchase_id'         => $purchase_id,
137
            'payout_address'      => $payout_address,
138
            'payout_currency'     => $payout_currency,
139
            'payout_extra_id'     => $payout_extra_id,
140
            'fixed_rate'          => $fixed_rate,
141
            'is_fee_paid_by_user' => $is_fee_paid_by_user,
142
        ]);
143
    }
144
145
    /**
146
     * Creates payment by invoice. With this method, your customer will be able to complete the payment without leaving your website.
147
     *
148
     * @param string      $iid invoice id
149
     * @param string      $pay_currency the cryptocurrency in which the pay_amount is specified (btc, eth, etc).
150
     * @param string|null $purchase_id id of purchase for which you want to create aother payment, only used for several payments for one order
151
     * @param string|null $order_description inner store order description
152
     * @param string|null $customer_email user email to which a notification about the successful completion of the payment will be sent
153
     * @param string|null $payout_address usually the funds will go to the address you specify in your Personal account.
154
     * @param string|null $payout_extra_id extra id or memo or tag for external payout_address.
155
     * @param string|null $payout_currency currency of your external payout_address, required when payout_adress is specified.
156
     *
157
     * @return invoicePaymentInterface|errorResponseInterface|mixed
158
     */
159
    public static function createInvoicePayment (string $iid, string $pay_currency, string $purchase_id = null, string $order_description = null, string $customer_email = null, string $payout_address = null, string $payout_extra_id = null, string $payout_currency = null) {
160
        return self::execute('POST', 'invoice-payment', [
161
            'iid'               => $iid,
162
            'pay_currency'      => $pay_currency,
163
            'purchase_id'       => $purchase_id,
164
            'order_description' => $order_description,
165
            'customer_email'    => $customer_email,
166
            'payout_address'    => $payout_address,
167
            'payout_extra_id'   => $payout_extra_id,
168
            'payout_currency'   => $payout_currency
169
        ]);
170
    }
171
172
    /**
173
     * This endpoint is required to get the current estimate on the payment, and update the current estimate.
174
     * Please note! Calling this estimate before expiration_estimate_date will return the current estimate, it won’t be updated.
175
     *
176
     * @param int $paymentID payment ID, for which you want to get the estimate
177
     *
178
     * @return estimateUpdateInterface|errorResponseInterface|mixed
179
     */
180
    public static function updateEstimatePrice (int $paymentID) {
181
        return self::execute('POST', 'payment/' . $paymentID . '/update-merchant-estimate');
182
    }
183
184
    /**
185
     * Get the actual information about the payment.
186
     *
187
     * @param int $paymentID payment ID, for which you want to get the status
188
     *
189
     * @return paymentInterface|errorResponseInterface|mixed
190
     */
191
    public static function getPaymentStatus (int $paymentID) {
192
        return self::execute('GET', 'payment/' . $paymentID);
193
    }
194
195
    /**
196
     * Get the minimum payment amount for a specific pair.
197
     *
198
     * @param string $currency_from
199
     * @param string $currency_to
200
     *
201
     * @return float
202
     */
203
    public static function getMinimumPaymentAmount (string $currency_from, string $currency_to): float {
204
        return self::execute('GET', 'min-amount', [
205
            'currency_from' => $currency_from,
206
            'currency_to'   => $currency_to
207
        ])->min_amount;
208
    }
209
210
    /**
211
     * Creates an invoice. With this method, the customer is required to follow the generated url to complete the payment.
212
     *
213
     * @param int|float   $price_amount
214
     * @param string      $price_currency
215
     * @param string|null $pay_currency
216
     * @param string|null $ipn_callback_url
217
     * @param string|null $order_id
218
     * @param string|null $order_description
219
     * @param string|null $success_url
220
     * @param string|null $cancel_url
221
     *
222
     * @return invoiceResponseInterface|errorResponseInterface|mixed
223
     */
224
    public static function createInvoice (int|float $price_amount, string $price_currency, string $pay_currency = null, string $ipn_callback_url = null, string $order_id = null, string $order_description = null, string $success_url = null, string $cancel_url = null) {
225
        return self::execute('POST', 'invoice', [
226
            'price_amount'      => $price_amount,
227
            'price_currency'    => $price_currency,
228
            'pay_currency'      => $pay_currency,
229
            'ipn_callback_url'  => $ipn_callback_url,
230
            'order_id'          => $order_id,
231
            'order_description' => $order_description,
232
            'success_url'       => $success_url,
233
            'cancel_url'        => $cancel_url
234
        ]);
235
    }
236
237
    /**
238
     * This is a method for obtaining information about all cryptocurrencies available for payments.
239
     *
240
     * @return array
241
     */
242
    public static function getCurrencies (): array {
243
        return self::execute('GET', 'currencies')->currencies;
244
    }
245
246
    /**
247
     * This is a method to obtain detailed information about all cryptocurrencies available for payments.
248
     *
249
     * @return array
250
     */
251
    public static function getFullCurrencies (): array {
252
        return self::execute('GET', 'full-currencies')->currencies;
253
    }
254
255
    /**
256
     * This is a method for obtaining information about the cryptocurrencies available for payments.
257
     * Shows the coins you set as available for payments in the "coins settings" tab on your personal account.
258
     *
259
     * @return array
260
     */
261
    public static function getAvailableCheckedCurrencies (): array {
262
        return self::execute('GET', 'merchant/coins')->currencies;
263
    }
264
265
    /**
266
     * Check remote ip with nowPayments IPN ip
267
     *
268
     * @return bool
269
     */
270
    public static function isNowPayments(): bool {
271
        return tools::remoteIP() === '144.76.201.30';
272
    }
273
274
    /**
275
     * Check is IPN valid or not
276
     *
277
     * @return bool
278
     */
279
    public static function isIPNRequestValid (): bool {
280
        if (empty($_SERVER['HTTP_X_NOWPAYMENTS_SIG'])) {
281
            return false;
282
        }
283
        if (!self::isNowPayments()) {
284
            return false;
285
        }
286
        $request_json = file_get_contents('php://input');
287
        if (empty($request_json)) {
288
            return false;
289
        }
290
        $request_data = json_decode($request_json, true);
291
        ksort($request_data);
292
        $hmac = hash_hmac("sha512", json_encode($request_data, JSON_UNESCAPED_SLASHES), trim(self::$ipn_secret));
293
        return $hmac == $_SERVER['HTTP_X_NOWPAYMENTS_SIG'];
294
    }
295
296
    /**
297
     * First it will check if IPN is valid or not, if its valid , then it will return IPN data
298
     *
299
     * @return ipnDataInterface|mixed
300
     */
301
    public static function getIPN () {
302
        if (!self::isIPNRequestValid()) {
303
            return false;
304
        }
305
        return json_decode(file_get_contents('php://input'));
306
    }
307
308
    protected static function createOrder (float|int $amount, int $user_id, string $description): int|string {
309
        if (!mysql::getMysqli()) {
310
            logger::write("crypto::ezPay function used\ncreating order needed mysql connection in our mysql class", loggerTypes::ERROR);
311
            throw new bptException('MYSQL_CONNECTION_NEEDED');
312
        }
313
314
        mysql::insert('orders', ['user_id', 'type', 'amount', 'description'], [$user_id, callbackTypes::CRYPTO, $amount, $description]);
315
316
        return mysql::insertId();
317
    }
318
319
    protected static function getOrder (int $order_id): bool|object {
320
        if (!mysql::getMysqli()) {
321
            logger::write("crypto::ezPay function used\ncreating order needed mysql connection in our mysql class", loggerTypes::ERROR);
322
            throw new bptException('MYSQL_CONNECTION_NEEDED');
323
        }
324
        $order = mysql::select('orders', '*', ['id' => $order_id], 1);
325
        if ($order->num_rows < 1) {
326
            return false;
327
        }
328
        return $order->fetch_object();
329
    }
330
331
    /**
332
     * An easy way to create invoice
333
     *
334
     * Processing and authorization of ipn callbacks will be done by library
335
     *
336
     * Note : You must activate ipn in your nowPayment account, and you must set ipn_secret in the settings
337
     *
338
     * The related callback will be sent to your cryptoCallback method in handler class
339
     *
340
     * @param float|int $amount
341
     * @param int|null  $user_id
342
     * @param string    $currency
343
     * @param string    $description
344
     * @param bool      $direct_url
345
     * @param bool      $one_time_url
346
     *
347
     * @return bool|string
348
     * @throws bptException
349
     */
350
    public static function ezPay (float|int $amount, int $user_id = null, string $currency = 'usd', string $description = 'Invoice created by BPT library', bool $direct_url = false, bool $one_time_url = true): bool|string {
351
        if (empty(self::$ipn_secret)) {
352
            logger::write("crypto::ezPay function used\nyou must set ipn_secret to use this", loggerTypes::ERROR);
353
            return false;
354
        }
355
        if ($amount < 0) {
356
            logger::write("crypto::ezPay function used\namount must be bigger then 0", loggerTypes::ERROR);
357
            return false;
358
        }
359
        $user_id = $user_id ?? request::catchFields(fields::USER_ID);
360
        $order_id = self::createOrder($amount, $user_id, $description);
361
        $data = ['type' => callbackTypes::CRYPTO, 'action_type' => cryptoCallbackActionTypes::CALLBACK, 'amount' => $amount, 'currency' => $currency, 'user_id' => $user_id, 'order_id' => $order_id];
362
        $callback_url = 'https://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] . '?data='.urlencode(callback::encodeData($data));
363
364
        $data = ['type' => callbackTypes::CRYPTO, 'action_type' => cryptoCallbackActionTypes::SUCCESS, 'amount' => $amount, 'currency' => $currency, 'user_id' => $user_id, 'order_id' => $order_id];
365
        $success_url = 'https://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] . '?data='.urlencode(callback::encodeData($data));
366
367
        $invoice_id = self::createInvoice($amount, $currency, null, $callback_url, $order_id, $description, $success_url)->id;
368
369
        $extra_info = [
370
            'invoice_id'       => $invoice_id,
371
            'currency'         => $currency,
372
            'related_payments' => [],
373
            'total_paid'      => 0,
374
        ];
375
        if (!$direct_url && $one_time_url) {
376
            $extra_info['redirected'] = false;
377
        }
378
379
        mysql::update('orders', [
380
            'extra_info' => json_encode($extra_info),
381
        ], ['id' => $order_id], 1);
382
383
        if ($direct_url) {
384
            return 'https://nowpayments.io/payment/?iid='. $invoice_id;
385
        }
386
387
        $data = ['type' => callbackTypes::CRYPTO, 'action_type' => cryptoCallbackActionTypes::REDIRECT, 'amount' => $amount, 'currency' => $currency, 'user_id' => $user_id, 'order_id' => $order_id];
388
        return 'https://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] . '?data='.urlencode(callback::encodeData($data));
389
    }
390
391
    /**
392
     * @internal Only for BPT self usage , Don't use it in your source!
393
     */
394
    public static function callbackProcess (array $data) {
395
        if (!isset($data['action_type'])) {
396
            return false;
397
        }
398
399
        if (!isset($data['order_id'])) {
400
            return false;
401
        }
402
403
        $action_type = $data['action_type'];
404
        $order_id = $data['order_id'];
405
406
        if ($action_type === cryptoCallbackActionTypes::REDIRECT) {
407
            $order = self::getOrder($order_id);
408
            $extra_info = json_decode($order->extra_info);
409
            if (isset($extra_info->redirected)) {
410
                if ($extra_info->redirected) {
411
                    BPT::exit('This link is one time only, Receive another link');
412
                }
413
            }
414
            $url = 'https://nowpayments.io/payment/?iid='. $extra_info->invoice_id;
415
416
            @header('Location: ' . $url);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

416
            /** @scrutinizer ignore-unhandled */ @header('Location: ' . $url);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Bug introduced by
Are you sure the usage of header('Location: ' . $url) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
417
            die("<meta http-equiv='refresh' content='0; url=$url' /><script>window.location.href = '$url';</script>");
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
418
        }
419
420
        if ($action_type === cryptoCallbackActionTypes::CALLBACK) {
421
            $ipn = self::getIPN();
422
423
            if ($ipn->payment_status !== cryptoStatus::FINISHED && $ipn->payment_status !== cryptoStatus::PARTIALLY_PAID ) {
424
                die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
425
            }
426
427
            $payment_id = $ipn->payment_id;
428
429
            $payment = self::getPaymentStatus($payment_id);
430
431
            if (isset($payment->status) && !$payment->status) {
432
                die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
433
            }
434
435
            if ($payment->payment_status !== cryptoStatus::FINISHED && $payment->payment_status !== cryptoStatus::PARTIALLY_PAID) {
436
                die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
437
            }
438
439
            $order = self::getOrder($order_id);
440
            $extra_info = json_decode($order->extra_info, true);
441
            if (isset($extra_info['related_payments'][$payment_id])) {
442
                die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
443
            }
444
445
            $paid = round(isset($payment->actually_paid_at_fiat) && $payment->actually_paid_at_fiat > 0 ? $payment->actually_paid_at_fiat : $payment->actually_paid/$payment->pay_amount*$payment->price_amount, self::$round_decimal);
446
            $extra_info['related_payments'][$payment_id] = $paid;
447
            $extra_info['total_paid'] += $paid;
448
            mysql::update('orders', ['extra_info' => json_encode($extra_info)], ['id' => $order_id], 1);
449
450
            $callback_data = [
451
                'status' => 'unknown',
452
                'order_id' => $order_id,
453
                'user_id' => $order->user_id,
454
                'description' => $order->description,
455
                'real_amount' => $order->amount,
456
                'currency' => $extra_info['currency'],
457
                'paid_amount' => $paid,
458
                'total_paid' => $extra_info['total_paid']
459
            ];
460
461
            if ($payment->payment_status === cryptoStatus::PARTIALLY_PAID) {
462
                $callback_data['status'] = $extra_info['total_paid'] > $order->amount ? cryptoCallbackStatus::EXTRA_PAID : ($extra_info['total_paid'] == $order->amount ? cryptoCallbackStatus::FINISHED : cryptoCallbackStatus::PARTIALLY_PAID);
463
            }
464
465
            if ($payment->payment_status === cryptoStatus::FINISHED) {
466
                $callback_data['status'] = $extra_info['total_paid'] <= $order->amount ? cryptoCallbackStatus::FINISHED : cryptoCallbackStatus::EXTRA_PAID;
467
            }
468
469
            $callback_data = object(...$callback_data);
470
            $callback_data = new cryptoCallback($callback_data);
471
472
            callback::callHandler('cryptoCallback', $callback_data);
473
            return true;
474
        }
475
476
        if ($action_type === cryptoCallbackActionTypes::SUCCESS) {
477
            if (!isset($_GET['NP_id'])) {
478
                return false;
479
            }
480
481
            if (!is_numeric($_GET['NP_id'])) {
482
                return false;
483
            }
484
485
            $payment_id = $_GET['NP_id'];
486
487
            $payment = self::getPaymentStatus($payment_id);
488
489
            if (isset($payment->status) && !$payment->status) {
490
                return false;
491
            }
492
493
            if ($payment->payment_status !== cryptoStatus::FINISHED) {
494
                return false;
495
            }
496
497
            $order = self::getOrder($order_id);
498
            $extra_info = json_decode($order->extra_info);
499
500
            $callback_data = [
501
                'status' => cryptoCallbackStatus::SUCCESS,
502
                'order_id' => $order_id,
503
                'user_id' => $order->user_id,
504
                'description' => $order->description,
505
                'real_amount' => $order->amount,
506
                'currency' => $extra_info->currency,
507
                'paid_amount' => round(isset($payment->actually_paid_at_fiat) && $payment->actually_paid_at_fiat > 0 ? $payment->actually_paid_at_fiat : $payment->actually_paid/$payment->pay_amount*$payment->price_amount, self::$round_decimal),
508
                'total_paid' => $extra_info->total_paid
509
            ];
510
            $callback_data = object(...$callback_data);
511
            $callback_data = new cryptoCallback($callback_data);
512
513
            callback::callHandler('cryptoCallback', $callback_data);
514
            return true;
515
        }
516
    }
517
}