<?php

/*
 * ==========================================================
 * FUNCTIONS.PHP
 * ==========================================================
 *
 * Admin and client side functions.
 * You can not use Cryptopayment to create a SAAS or Cryptopayment-like business. For more details, visit https://cryptopayment.dev/terms-of-service (see 5. Intellectual Property and Content Ownership).
 * © 2022-2024 cryptopayment.dev. All rights reserved.
 *
 */

use Firebase\JWT\JWT;

define('CRPAY_VERSION', '1.0.0');
if (!defined('CRPAY_CLOUD')) {
    define('CRPAY_CLOUD', file_exists(__DIR__ . '/cloud'));
}
if ( ! defined('CRPAY_WP') ) define('CRPAY_WP', true);
if ( ! defined('CRPAY_USER') ) define('CRPAY_USER', '');
if ( ! defined('CRPAY_PASSWORD') ) define('CRPAY_PASSWORD', '');
require(__DIR__ . '/config.php');
global $CRPAY_LOGIN;
global $CRPAY_LANGUAGE;
global $CRPAY_TRANSLATIONS;
global $CRPAY_TRANSLATIONS_2;
global $CRPAY_APPS;
$CRPAY_APPS = ['wordpress', 'exchange', 'shop'];

/*
 * -----------------------------------------------------------
 * TRANSACTIONS
 * -----------------------------------------------------------
 *
 * 1. Get transactions
 * 2. Get a single transaction
 * 3. Create a transaction
 * 4. Generate a random cryptcurrency amount
 * 5. Delete pending transactions older than 48h
 * 6. Check the transactions of an address
 * 7. Check a single transaction
 * 8. Check pending transactions
 * 9. Finalize a confirmed transaction
 * 10 Update a transaction
 * 11. Send the webhook for a specific transaction
 * 12. Download transactions in CSV format
 * 13. Generate an invoice
 * 14. Update a transaction
 * 15. Decrypt a transaction securely
 * 16. Generate a payment link for a transaction
 * 17. Get the transaction description array
 * 18. Refund a transaction
 * 19. Return the checkout URL of a FIAT provider
 *
 */

function crpay_transactions_get_all($pagination = 0, $search = false, $status = false, $currency = false, $date_range = false, $checkout_id = false, $extra = false) {
    $where = '';
    if ($search) {
        $search = crpay_db_escape(trim($search));
        $where = '(' . (is_numeric($search) ? 'amount LIKE "%' . $search . '%" OR amount_fiat LIKE "%' . $search . '%" OR ' : '') . 'title LIKE "%' . $search . '%" OR description LIKE "%' . $search . '%" OR cryptocurrency LIKE "%' . $search . '%" OR currency LIKE "%' . $search . '%" OR `from` LIKE "%' . $search . '%" OR `to` LIKE "%' . $search . '%" OR hash LIKE "%' . $search . '%" OR external_reference LIKE "%' . $search . '%")';
        if (defined('CRPAY_SHOP')) {
            if (crpay_settings_get('shop-license-key')) {
                $where .= ' OR id IN (SELECT transaction_id FROM crpay_license_keys WHERE license_key = "' . $search . '")';
            }
            $where .= ' OR customer_id IN (SELECT id FROM crpay_customers WHERE email LIKE "%' . $search . '%" OR phone LIKE "%' . $search . '%" OR country LIKE "%' . $search . '%" OR first_name LIKE "%' . $search . '%" OR last_name LIKE "%' . $search . '%")';
        }
    }
    if ($status) {
        $where .= ($where ? ' AND ' : '') . ' status = "' . crpay_db_escape($status) . '"';
    }
    if ($currency) {
        $where .= ($where ? ' AND ' : '') . (crpay_crypto_is($currency) ? ' cryptocurrency = "' . crpay_db_escape($currency) . '"' : ' (currency = "' . crpay_db_escape($currency) . '" AND (cryptocurrency = "stripe" OR cryptocurrency = "paypal" OR cryptocurrency = "verifone"))');
    }
    if ($date_range && $date_range[0]) {
        $where .= ($where ? ' AND ' : '') . ' creation_time >= "' . crpay_db_escape($date_range[0]) . '" AND creation_time <= "' . crpay_db_escape($date_range[1]) . '"';
    }
    if ($checkout_id) {
        $where .= ($where ? ' AND ' : '') . ' checkout_id = ' . crpay_db_escape($checkout_id, true);
    }
    $transactions = crpay_db_get('SELECT * FROM crpay_transactions' . ($where ? ' WHERE ' . $where : '') . ' ORDER BY id DESC' . ($pagination != -1 ? ' LIMIT ' . intval(crpay_db_escape($pagination, true)) * 100 . ',100' : ''), false);
    if (defined('CRPAY_SHOP') && crpay_settings_get('shop-license-key') && (!$extra || $extra != 'no_shop')) {
        $license_keys = crpay_db_get('SELECT license_key, status, transaction_id FROM crpay_license_keys', false);
        for ($i = 0; $i < count($license_keys); $i++) {
            $transaction_id = $license_keys[$i]['transaction_id'];
            for ($j = 0; $j < count($transactions); $j++) {
                if ($transaction_id == $transactions[$j]['id']) {
                    $transactions[$j]['license_key'] = $license_keys[$i]['license_key'];
                    $transactions[$j]['license_key_status'] = $license_keys[$i]['status'];
                    break;
                }
            }
        }
    }
    return $transactions;
}

function crpay_transactions_get($transaction_id) {
    $name = 'CRPAY_TRANSACTION_' . $transaction_id;
    $transaction = crpay_isset($GLOBALS, $name);
    if ($transaction) {
        return $transaction;
    }
    $transaction = crpay_db_get('SELECT * FROM crpay_transactions WHERE id = ' . crpay_db_escape($transaction_id, true));
    if ($transaction) {
        $GLOBALS[$name] = $transaction;
        return $transaction;
    }
    return false;
}

function crpay_transactions_create($amount, $cryptocurrency_code, $currency_code = false, $external_reference = '', $title = '', $description = '', $url = false, $billing = '', $vat = false, $checkout_id = false, $user_details = false, $discount_code = false, $type = 1) {
    $fee = crpay_settings_get('payment-fee', 0);
    if ($fee) {
        $amount += $amount * ($fee / 100);
    }
    if ($checkout_id && !is_numeric($checkout_id)) {
        $checkout_id = crpay_isset(crpay_db_get('SELECT id FROM crpay_checkouts WHERE slug = "' . crpay_db_escape($checkout_id) . '"'), 'id');
    }
    $query_parts = ['INSERT INTO crpay_transactions(title, description, `from`, `to`, hash, amount, amount_fiat, cryptocurrency, currency, external_reference, creation_time, status, webhook, billing, vat, vat_details, checkout_id, type) VALUES ("' . crpay_db_escape($title) . '", "' . ($description ? crpay_db_json_escape([urldecode($description)]) : '') . '", "",', ', "' . crpay_db_escape($currency_code) . '", "' . crpay_db_escape($external_reference) . '", "' . gmdate('Y-m-d H:i:s') . '", "P", 0, "' . crpay_db_escape($billing) . '", "' . crpay_isset($vat, 'amount', 0) . '", "' . ($vat && !empty($vat['amount']) ? crpay_db_json_escape($vat) : '') . '", ' . ($checkout_id && is_numeric($checkout_id) ? crpay_db_escape($checkout_id, true) : 'NULL') . ',' . crpay_db_escape($type, true) . ' )'];
    $hash = '';
    $address = false;
    if (!$currency_code) {
        $currency_code = crpay_settings_get('currency', 'USD');
    }
    if (crpay_crypto_is_fiat($cryptocurrency_code)) {
        $transaction_id = crpay_db_query($query_parts[0] . '"", "", "", "' . crpay_db_escape($amount, true) . '", "' . crpay_db_escape($cryptocurrency_code) . '"' . $query_parts[1], true);
        if (defined('CRPAY_SHOP')) {
            $response = crpay_shop_create_transaction_part($transaction_id, $discount_code, $amount, $user_details);
            if ($response !== true) {
                return $response;
            }
        }
        $payment_description = $title;
        if ($vat && $vat['amount']) {
            $payment_description .= ' (' . crpay_('VAT') . ' ' . $vat['amount'] . strtoupper($currency_code) . ($vat['country_code'] ? ' ' . $vat['country_code'] : '') . ')';
        }
        return [$transaction_id, $cryptocurrency_code, crpay_checkout_url($amount, $cryptocurrency_code, $currency_code, $url, $transaction_id, $title, $payment_description), crpay_encryption(crpay_transactions_get($transaction_id))];
    }
    if ($cryptocurrency_code === 'btc_ln' && crpay_settings_get('ln-node-active')) {
        require_once(__DIR__ . '/bitcoin.php');
        $amount_cryptocurrency = $amount_cryptocurrency_string = crpay_crypto_get_cryptocurrency_value($amount, 'btc', $currency_code);
        $invoice = crpay_btc_ln_create_invoice($amount_cryptocurrency);
        $address = crpay_isset($invoice, 'payment_request');
        if ($address) {
            $hash = $invoice['r_hash'];
        } else {
            if (crpay_settings_get('notifications-ln')) {
                crpay_email_notification(crpay_m('Lightning Network error', crpay_settings_get('language-admin')), json_encode($invoice));
            }
            return ['error', 'btc-ln'];
        }
    }
    if (!$address) {
        $decimals = crpay_crypto_get_decimals($cryptocurrency_code);
        $custom_token = crpay_isset(crpay_get_custom_tokens(), $cryptocurrency_code);
        $address = $custom_token ? $custom_token['address'] : crpay_crypto_get_address($cryptocurrency_code);
        $amount_cryptocurrency = $currency_code == 'crypto' ? [$amount, ''] : explode('.', strval(crpay_crypto_get_cryptocurrency_value($amount, $cryptocurrency_code, $currency_code)));
        if (crpay_crypto_whitelist_invalid($address, true, $cryptocurrency_code)) {
            return 'whitelist-invalid';
        }
        if ($amount_cryptocurrency && !isset($amount_cryptocurrency[1])) {
            array_push($amount_cryptocurrency, '');
        }
        if ($custom_token) {
            if ($custom_token['rate_url']) {
                $custom_token['rate'] = floatval(crpay_curl($custom_token['rate_url']));
            }
            if (!empty($custom_token['rate'])) {
                $amount_cryptocurrency = explode('.', $amount * (1 / (floatval(crpay_isset($custom_token, 'rate', 1)) * ($currency_code == 'USD' ? 1 : crpay_usd_rates($currency_code)))));
                if (!isset($amount_cryptocurrency[1])) {
                    array_push($amount_cryptocurrency, '');
                }
            }
            $decimals = $custom_token['decimals'];
        }
        if (strlen($amount_cryptocurrency[1]) > $decimals) {
            $amount_cryptocurrency[1] = substr($amount_cryptocurrency[1], 0, $decimals);
        }
        $amount_cryptocurrency_string = $amount_cryptocurrency[0] . ($amount_cryptocurrency[1] ? '.' . $amount_cryptocurrency[1] : '');
        if ($address == crpay_settings_get_address($cryptocurrency_code)) {
            $temp = crpay_db_get('SELECT amount FROM crpay_transactions WHERE cryptocurrency = "' . crpay_db_escape($cryptocurrency_code) . '"', false);
            $existing_amounts = [];
            $i = 0;
            for ($i = 0; $i < count($temp); $i++) {
                array_push($existing_amounts, $temp[$i]['amount']);
            }
            while (in_array($amount_cryptocurrency_string, $existing_amounts) && $i < 1000) {
                $amount_cryptocurrency_string = crpay_transactions_random_amount($amount_cryptocurrency, $decimals);
                $i++;
            }
        }
    }
    $transaction_id = crpay_db_query($query_parts[0] . '"' . $address . '", "' . $hash . '", "' . $amount_cryptocurrency_string . '", "' . crpay_db_escape($amount, true) . '", "' . crpay_db_escape($cryptocurrency_code) . '"' . $query_parts[1], true);
    if (defined('CRPAY_SHOP')) {
        $response = crpay_shop_create_transaction_part($transaction_id, $discount_code, $amount, $user_details);
        if ($response !== true) {
            return $response;
        }
    }
    $url = crpay_is_demo(true);
    if ($url) {
        $amount_cryptocurrency_string = $url['amount'];
        $transaction_id = $url['id'];
    }
    if (in_array(crpay_crypto_get_base_code($cryptocurrency_code), ['usdt', 'usdc', 'busd']) && crpay_is_address_generation($cryptocurrency_code)) {
        $amount_cryptocurrency_string_split = explode('.', $amount_cryptocurrency_string);
        if (count($amount_cryptocurrency_string_split) > 1 && strlen($amount_cryptocurrency_string_split[1]) > 2) {
            $amount_cryptocurrency_string = $amount_cryptocurrency_string_split[0] . '.' . substr($amount_cryptocurrency_string_split[1], 0, 2);
            crpay_transactions_update($transaction_id, ['amount' => $amount_cryptocurrency_string], false);
        }
    }
    return [$transaction_id, $amount_cryptocurrency_string, $address, crpay_settings_get_confirmations($cryptocurrency_code, $amount), crpay_encryption(crpay_transactions_get($transaction_id))];
}

function crpay_transactions_random_amount($amount, $decimals) {
    $amount = crpay_decimal_number(floatval($amount[0] . ($amount[1] && $amount[1] != '0' ? '.' . $amount[1] : '')) * floatval('1.000' . rand(99, 9999)));
    if (strpos($amount, '.')) {
        $amount = explode('.', $amount);
        while (strlen($amount[1]) > $decimals) {
            $amount[1] = substr($amount[1], 0, $decimals);
        }
        $amount = $amount[0] . ($amount[1] && $amount[1] != '0' ? '.' . $amount[1] : '');
    }
    return $amount;
}

function crpay_transactions_delete_pending() {
    $query = 'FROM crpay_transactions WHERE status = "P" AND creation_time < "' . gmdate('Y-m-d H:i:s', time() - intval(crpay_settings_get('delete-pending-interval', 48)) * 3600) . '"';
    $transactions = crpay_db_get('SELECT `to`, `cryptocurrency` ' . $query, false);
    $response = crpay_db_query('DELETE ' . $query);
    if ($response === true) {
        $addresses = [];
        for ($i = 0; $i < count($transactions); $i++) {
            $to = $transactions[$i]['to'];
            $slug = $transactions[$i]['cryptocurrency'] . '-manual-addresses';
            if (!isset($addresses[$slug])) {
                $addresses[$slug] = json_decode(crpay_settings_db($slug, false, '{}'), true);
            }
            if (isset($addresses[$slug][$to])) {
                unset($addresses[$slug][$to]);
                crpay_settings_db($slug, $addresses[$slug]);
            }
        }
    }
    return $response;
}

function crpay_transactions_check($transaction_id) {
    $cryptopayment_transaction = crpay_transactions_get($transaction_id);
    if (!$cryptopayment_transaction) {
        return crpay_error('Transaction ' . $transaction_id . ' not found.', 'crpay_transactions_check', true);
    }
    $refresh_interval = intval(crpay_settings_get('refresh-interval', 60)) * 60;
    $time = time();
    $transaction_creation_time = strtotime($cryptopayment_transaction['creation_time'] . ' UTC');
    if ((($transaction_creation_time + $refresh_interval) <= $time) && !crpay_is_demo()) {
        return 'expired';
    }
    if ($cryptopayment_transaction) {
        $cryptocurrency_code = $cryptopayment_transaction['cryptocurrency'];
        if ($cryptocurrency_code === 'btc_ln') {
            require_once(__DIR__ . '/bitcoin.php');
            $invoice = crpay_btc_ln_get_invoice($cryptopayment_transaction['hash']);
            return $invoice && crpay_isset($invoice, 'state') === 'SETTLED' ? crpay_transactions_check_single(crpay_encryption($cryptopayment_transaction)) : $invoice;
        } else {
            $to = $cryptopayment_transaction['to'];
            $address_generation = $to != crpay_settings_get_address($cryptocurrency_code) && !crpay_is_demo();
            if (crpay_crypto_whitelist_invalid($to, true, $cryptocurrency_code)) {
                return false;
            }
            $transactions = crpay_blockchain($cryptocurrency_code, 'transactions', false, $to);
            if (is_array($transactions)) {
                for ($i = 0; $i < count($transactions); $i++) {
                    $transaction = $transactions[$i];
                    $transaction_time = crpay_isset($transaction, 'time');
                    $transaction_hash = crpay_isset($transaction, 'hash');
                    if ((!$transaction_hash || (crpay_is_demo() || !crpay_db_get('SELECT id FROM crpay_transactions WHERE hash = "' . crpay_db_escape($transaction['hash']) . '" LIMIT 1'))) && (empty($transaction['address']) || strtolower($transaction['address']) != strtolower($to)) && (!$transaction_time || $transaction_time > $transaction_creation_time) && ($address_generation || $cryptopayment_transaction['amount'] == $transaction['value'] || strpos($transaction['value'], $cryptopayment_transaction['amount']) === 0)) {
                        if ($address_generation && empty($transaction_time)) {
                            $transaction = crpay_blockchain($cryptocurrency_code, 'transaction', $transaction_hash, $transaction['address']);
                            if (crpay_isset($transaction, 'time') < $transaction_creation_time) {
                                return false;
                            }
                        }
                        return crpay_encryption(array_merge($cryptopayment_transaction, ['hash' => $transaction_hash, 'id' => $transaction_id, 'cryptocurrency' => $cryptocurrency_code, 'to' => $to]));
                    }
                }
            } else {
                return ['error', $transactions];
            }
        }
    }
    return false;
}

function crpay_transactions_check_single($transaction) {
    $transaction = crpay_transactions_decrypt($transaction);
    $cryptocurrency_code = $transaction['cryptocurrency'];
    $is_crypto = !crpay_crypto_is_fiat($cryptocurrency_code);
    if (!$is_crypto) {
        $transaction = crpay_transactions_get($transaction['id']);
    }
    $invoice = crpay_isset($transaction, 'billing') && crpay_settings_get('invoice-active') ? crpay_transactions_invoice($transaction) : false;
    if ($cryptocurrency_code === 'btc_ln') {
        $response = crpay_transactions_complete($transaction, $transaction['amount'], '');
        return ['confirmed' => true, 'invoice' => $invoice, 'redirect' => crpay_isset($response, 'redirect'), 'source' => crpay_isset($response, 'source'), 'license_key' => crpay_isset($response, 'license_key'), 'downloads_url' => crpay_isset($response, 'downloads_url')];
    } else if ($is_crypto) {
        $minimum_confirmations = crpay_settings_get_confirmations($cryptocurrency_code, $transaction['amount']);
        $transaction_blockchain = crpay_blockchain($cryptocurrency_code, 'transaction', $transaction['hash'], $transaction['to']);
        if (!$transaction_blockchain) {
            return 'transaction-not-found';
        }
        if (is_string($transaction_blockchain)) {
            return crpay_error($transaction_blockchain, 'crpay_transactions_check_single', true);
        }
        $confirmations = crpay_isset($transaction_blockchain, 'confirmations');
        if (!$confirmations && $transaction_blockchain['block_height']) {
            $confirmations = max(crpay_blockchain($cryptocurrency_code, 'blocks_count') - $transaction_blockchain['block_height'] + 1, 0);
        }
        $confirmed = $confirmations >= $minimum_confirmations;
        $response = $confirmed ? crpay_transactions_complete($transaction, $transaction_blockchain['value'], $transaction_blockchain['address'], $invoice) : [];
        return ['confirmed' => $confirmed, 'confirmations' => $confirmations ? $confirmations : 0, 'minimum_confirmations' => $minimum_confirmations, 'hash' => $transaction['hash'], 'invoice' => $invoice, 'underpayment' => crpay_isset($response, 'underpayment') ? $transaction_blockchain['value'] : false, 'redirect' => crpay_isset($response, 'redirect'), 'source' => crpay_isset($response, 'source'), 'license_key' => crpay_isset($response, 'license_key'), 'downloads_url' => crpay_isset($response, 'downloads_url')];
    } else {
        $confirmed = $transaction['status'] === 'C';
        return array_merge(['confirmed' => $confirmed, 'invoice' => $invoice], defined('CRPAY_SHOP') && $confirmed ? crpay_shop_transaction_complete_get_details($transaction['checkout_id'], $transaction['id'], $transaction['customer_id']) : []);
    }
}

function crpay_transactions_check_pending() {
    $transactions = crpay_db_get('SELECT * FROM crpay_transactions WHERE status = "P" AND creation_time > "' . gmdate('Y-m-d H:i:s', time() - 172800) . '"', false);
    $transactions_blockchains = [];
    for ($i = 0; $i < count($transactions); $i++) {
        $transaction = $transactions[$i];
        $to = $transaction['to'];
        $cryptocurrency_code = strtolower($transaction['cryptocurrency']);
        if (crpay_crypto_whitelist_invalid($to, true, $cryptocurrency_code) || !crpay_crypto_is($cryptocurrency_code)) {
            continue;
        }
        if (!isset($transactions_blockchains[$to])) {
            $transactions_blockchains[$to] = crpay_blockchain($cryptocurrency_code, 'transactions', ['limit' => 99], $to);
        }
        $transactions_blockchain = $transactions_blockchains[$to];
        $address_generation = $to != crpay_settings_get_address($cryptocurrency_code);
        if (is_array($transactions_blockchain)) {
            for ($y = 0; $y < count($transactions_blockchain); $y++) {
                $transaction_blockchain = $transactions_blockchain[$y];
                if ((empty($transaction_blockchain['time']) || $transaction_blockchain['time'] > strtotime($transaction['creation_time'] . ' UTC')) && ($address_generation || $transaction['amount'] == $transaction_blockchain['value'] || strpos($transaction_blockchain['value'], $transaction['amount']) === 0)) {
                    $transaction['hash'] = $transaction_blockchain['hash'];
                    $response = crpay_transactions_check_single($transaction);
                    if ($response && !empty($response['confirmed'])) {
                        crpay_transactions_update($transaction['id'], ['status' => 'C']);
                    }
                }
            }
        }
    }
}

function crpay_transactions_complete($transaction, $amount_blockchain, $address_from, $invoice = false) {
    $redirect = false;
    $source = false;
    $license_key = false;
    $amount = crpay_isset($transaction, 'amount', $transaction['amount_fiat']);
    $cryptocurrency_code = $transaction['cryptocurrency'];
    $external_reference = $transaction['external_reference'];
    $underpayment = empty($amount) || floatval($amount_blockchain) < floatval($amount);
    $node_transfer = false;
    $checkout_id = crpay_isset($transaction, 'checkout_id');
    $checkout_title = '';
    $query = [];
    $return = [];
    if ($checkout_id) {
        $checkout = crpay_checkout_get($checkout_id);
        if ($checkout) {
            if (!empty($transaction['discount_code']) && defined('CRPAY_SHOP')) {
                $checkout['price'] = crpay_shop_discounts_apply($transaction['discount_code'], $checkout_id, $checkout['price']);
            }
            if ($checkout['price'] > crpay_isset($transaction, 'amount_fiat', 0) || strtolower($checkout['currency']) != strtolower($transaction['currency'])) {
                return crpay_error('Checkout amount/currency does not matach transaction amount/currency.', 'crpay_transactions_complete');
            }
            if (empty($amount) && empty($transaction['amount_fiat'])) {
                $underpayment = false;
                $query['vat'] = '0';
            }
            $checkout_title = $checkout['title'];
        }
    }
    if ($underpayment) {
        $note = $amount_blockchain . '/' . $amount . ' ' . strtoupper($cryptocurrency_code) . ' ' . crpay_('received') . '. ' . crpay_decimal_number(floatval($amount) - floatval($amount_blockchain)) . ' ' . strtoupper($cryptocurrency_code) . ' ' . crpay_('are missing.');
        $description = crpay_transactions_get_description($transaction['id']);
        if (!in_array($note, $description)) {
            array_push($description, $note);
        }
        if (crpay_settings_get('accept-underpayments')) {
            $underpayment = false;
            $amount = $amount_blockchain;
            $query['amount'] = $amount_blockchain;
            $query['amount_fiat'] = crpay_crypto_get_fiat_value($amount_blockchain, $cryptocurrency_code, $transaction['currency']);
        } else {
            $query['description'] = crpay_db_json_escape($description);
        }
    }
    $query['from'] = $address_from;
    $query['hash'] = $transaction['hash'];
    $query['status'] = $underpayment ? 'X' : 'C';
    $response = crpay_transactions_update($transaction['id'], $query, false);
    if ($response === true) {
        $transaction = array_merge($transaction, $query);
    }
    if ($invoice === false) {
        $invoice = crpay_isset($transaction, 'billing') && crpay_settings_get('invoice-active') ? crpay_transactions_invoice($transaction) : false;
    }
    if (crpay_transactions_webhook_authorized($transaction) && (($cryptocurrency_code === 'btc' && crpay_settings_get('btc-node-address-generation') && crpay_settings_get('btc-node-url')) || crpay_is_eth_address_generation($cryptocurrency_code))) {
        $ethereum = $cryptocurrency_code !== 'btc';
        $prefix = $ethereum ? 'eth' : 'btc';
        $addresses = json_decode(crpay_encryption(crpay_settings_db($prefix . '-addresses'), false), true);
        for ($i = 0; $i < count($addresses); $i++) {
            $private_key = crpay_isset($addresses[$i][0], 'private_key');
            if ($private_key && $addresses[$i][0]['address'] == $transaction['to']) {
                require_once(__DIR__ . ($ethereum ? '/web3.php' : '/bitcoin.php'));
                $node_transfer = crpay_settings_get($prefix . '-node-transfer');
                $to = $node_transfer ? crpay_settings_get($prefix . '-node-transfer-address') : crpay_settings_get($prefix . '-address');
                if ($ethereum) {
                    crpay_eth_transfer($amount, $cryptocurrency_code, $to, $transaction['to'], $private_key);
                } else {
                    crpay_btc_transfer($amount, $to, $transaction['to'], $private_key);
                }
                break;
            }
        }
    }
    crpay_crypto_convert($transaction['id'], $cryptocurrency_code, $amount_blockchain);
    if (!$node_transfer) {
        crpay_crypto_transfer($transaction['id'], $cryptocurrency_code, $amount_blockchain);
    }
    if (crpay_settings_get('notifications-sale')) {
        $language = crpay_settings_get('language-admin');
        crpay_email_notification(($underpayment ? '[' . crpay_m('Underpayment', $language) . '] ' : '') . crpay_m('New payment of', $language) . ' ' . $transaction['amount_fiat'] . ' ' . strtoupper($transaction['currency']) . ($transaction['title'] ? ' | ' . $transaction['title'] : ''), str_replace('{T}', $transaction['amount_fiat'] . ' ' . strtoupper($transaction['currency']) . (crpay_crypto_is_fiat($cryptocurrency_code) ? '' : ' (' . $amount_blockchain . ' ' . strtoupper($cryptocurrency_code) . ')') . ($underpayment ? ' (<b>' . crpay_m('Underpayment', $language) . '</b>)' : ''), crpay_m('A new payment of {T} has been sent to your', $language)) . ' ' . (crpay_crypto_is_fiat($cryptocurrency_code) ? ucfirst($cryptocurrency_code) . ' ' . crpay_m('account', $language) . '. ' : ucfirst(crpay_crypto_name($transaction['cryptocurrency'])) . ' ' . crpay_m('address', $language) . ' <b>' . $transaction['to'] . '</b>. ') . crpay_m('Transaction ID:', $language) . ' ' . $transaction['id'] . '.' . ($transaction['title'] ? ' ' . crpay_m('Checkout:', $language) . ' ' . $transaction['title'] . '.' : ''));
    }
    if (strpos($external_reference, 'shopify_') === 0) {
        crpay_curl(crpay_settings_get('shopify-url') . '/admin/api/2023-01/orders/' . str_replace('shopify_', '', $external_reference) . '/transactions.json', json_encode(['transaction' => ['currency' => $transaction['currency'], 'amount' => $transaction['amount_fiat'], 'kind' => 'capture']]), ['X-Shopify-Access-Token: ' . trim(crpay_settings_get('shopify-token'))], 'POST');
    }
    if (!$underpayment) {
        $external_reference = explode('|', crpay_encryption($external_reference, false));
        $source = in_array('woo', $external_reference) ? 'woo' : (in_array('edd', $external_reference) ? 'edd' : false);
        $redirect = $source == 'woo' ? $external_reference[1] : ($source ? crpay_settings_db('wp_edd_url') : false);
        crpay_transactions_webhook($transaction, $source ? crpay_settings_db('wp_api_url') : false);
    }

    return array_merge($return, ['underpayment' => $underpayment, 'redirect' => $redirect, 'source' => $source, 'invoice' => $invoice]);
}

function crpay_transactions_update($transaction_id, $values, $complete_action = true) {
    $query = 'UPDATE crpay_transactions SET ';
    $transaction = false;
    if (is_string($values)) {
        $values = json_decode($values, true);
    }
    foreach ($values as $key => $value) {
        $query .= '`' . crpay_db_escape($key) . '` = "' . (is_string($value) || is_numeric($value) ? crpay_db_escape($value) : crpay_db_json_escape($value)) . '",';
    }
    if ($complete_action && crpay_isset($values, 'status') == 'C') {
        $transaction = crpay_transactions_get($transaction_id);
        $response = crpay_transactions_complete($transaction, $transaction['amount'], $transaction['from']);
        if (crpay_isset($response, 'error') || is_string($response)) {
            return $response;
        }
    }
    $response = crpay_db_query(substr($query, 0, -1) . ' WHERE id = ' . crpay_db_escape($transaction_id, true));
    $name = 'CRPAY_TRANSACTION_' . $transaction_id;
    if (isset($GLOBALS[$name])) {
        unset($GLOBALS[$name]);
        $GLOBALS[$name] = $transaction ? array_merge($transaction, $values) : crpay_transactions_get($transaction_id);
    }
    return $response;
}

function crpay_transactions_webhook($transaction, $webhook_url = false) {
    if (!$webhook_url) {
        $webhook_url = crpay_settings_get('webhook-url');
    }
    if (!$webhook_url) {
        return false;
    }

    if (is_string($transaction)) {
        $transaction = ['id' => crpay_transactions_decrypt($transaction)['id']];
    }
    $webhook_secret_key = crpay_settings_get('webhook-secret');
    $transaction = crpay_transactions_get($transaction['id']);
    if ($transaction['status'] != 'C' || !crpay_transactions_webhook_authorized($transaction)) {
        return false;
    }
    $body = json_encode(['key' => $webhook_secret_key ? $webhook_secret_key : CRPAY_PASSWORD, 'transaction' => $transaction]);
    crpay_transactions_update($transaction['id'], ['webhook' => 1]);
    return crpay_curl($webhook_url, $body, ['Content-Type: application/json', 'Content-Length: ' . strlen($body)], 'POST');
}

function crpay_transactions_webhook_authorized($transaction) {
    if ($transaction['webhook']) {
        $url = crpay_is_demo(true);
        if (!$url || crpay_isset($url, 'webhook_key') != crpay_settings_get('webhook-secret')) {
            return false;
        }
    }
    return true;
}

function crpay_transactions_download($search = false, $status = false, $cryptocurrency = false, $date_range = false, $checkout_id = false) {
    $header = ['ID', 'Title', 'Description', 'From', 'To', 'Hash', 'Amount', 'Amount FIAT', 'Cryptocurrency', 'Currency', 'External Reference', 'Creation Time', 'Status', 'Webhook', 'Billing', 'VAT', 'VAT details'];
    $transactions = crpay_transactions_get_all(-1, $search, $status, $cryptocurrency, $date_range, $checkout_id);

    return crpay_csv($transactions, $header, 'transactions');
}

function crpay_transactions_invoice($transaction_or_id) {
    $transaction_id = is_numeric($transaction_or_id) ? $transaction_or_id : crpay_isset($transaction_or_id, 'id');
    if (!$transaction_id) {
        return false;
    }
    $prefix = crpay_settings_get('invoice-number-prefix', 'inv-');
    $file_name = strtolower($prefix) . $transaction_id . '.pdf';
    $path_part = 'uploads' . crpay_cloud_path_part();
    if (!file_exists($path_part)) {
        mkdir($path_part, 0755, true);
    }
    $path_part .= '/' . $file_name;
    $invoice_url = CRPAY_URL . $path_part;
    $transaction = is_numeric($transaction_or_id) ? crpay_transactions_get($transaction_id) : $transaction_or_id;
    if (!$transaction || $transaction['status'] == 'P' || empty($transaction['billing'])) {
        return false;
    }
    require_once __DIR__ . '/../../vendor/fpdf/fpdf.php';

    $billing = json_decode($transaction['billing'], true);
    $billing_text = $billing ? crpay_isset($billing, 'name', '') . PHP_EOL . crpay_isset($billing, 'address', '') . PHP_EOL . crpay_isset($billing, 'city', '') . ', ' . (empty($billing['state']) ? '' : $billing['state'] . ', ') . crpay_isset($billing, 'zip', '') . PHP_EOL . crpay_isset($billing, 'country', '') . PHP_EOL . PHP_EOL . crpay_isset($billing, 'vat', '') : '';

    $pdf = new \setasign\Fpdi\Fpdi();
    $pdf->AddPage();
    $pdf->setSourceFile(__DIR__ . '/resources/invoice.pdf');
    $tpl = $pdf->importPage(1);
    $pdf->useTemplate($tpl, 0, 0, null, null);
    $pdf->SetTextColor(90, 90, 90);

    $pdf->SetXY(20, 29);
    $pdf->SetFont('Arial', 'B', 20);
    $pdf->Cell(1000, 1, crpay_('Tax Invoice'));

    $pdf->SetXY(130, 27);
    $pdf->SetFont('Arial', '', 13);
    $pdf->Multicell(500, 7, crpay_('Invoice date: ') . date('d-m-Y') . PHP_EOL . crpay_('Invoice number: ') . strtoupper($prefix) . $transaction['id']);

    $pdf->SetXY(20, 60);
    $pdf->SetFont('Arial', 'B', 13);
    $pdf->Cell(50, 1, crpay_('To'));
    $pdf->SetFont('Arial', '', 13);
    $pdf->SetXY(20, 70);
    $pdf->Multicell(168, 7, strip_tags(trim(iconv('UTF-8', 'windows-1252', $billing_text))));

    $pdf->SetXY(130, 60);
    $pdf->SetFont('Arial', 'B', 13);
    $pdf->Cell(168, 1, crpay_('Supplier'));
    $pdf->SetFont('Arial', '', 13);
    $pdf->SetXY(130, 70);
    $pdf->Multicell(168, 7, strip_tags(trim(iconv('UTF-8', 'windows-1252', crpay_settings_get('invoice-details')))));

    $pdf->SetXY(20, 150);
    $pdf->SetFont('Arial', 'B', 13);
    $pdf->Cell(168, 1, crpay_('Purchase details'));
    $pdf->SetFont('Arial', '', 13);
    $pdf->SetXY(20, 160);
    $pdf->Cell(168, 1, $transaction['title']);

    $pdf->SetXY(20, 180);
    $pdf->SetFont('Arial', 'B', 13);
    $pdf->Cell(168, 1, crpay_('Transaction amount'));
    $pdf->SetFont('Arial', '', 13);
    $pdf->SetXY(20, 190);
    $pdf->Cell(168, 1, strtoupper($transaction['currency']) . ' ' . crpay_isset($transaction, 'amount_fiat', '0') . (empty($transaction['amount']) || !crpay_crypto_is($transaction['cryptocurrency']) ? '' : ' (' . strtoupper(crpay_crypto_get_base_code($transaction['cryptocurrency'])) . ' ' . $transaction['amount'] . ')'));
    if ($transaction['vat'] && !empty($transaction['amount_fiat'])) {
        $pdf->SetXY(20, 200);
        $pdf->Cell(100, 1, 'VAT ' . strtoupper($transaction['currency']) . ' ' . $transaction['vat']);
    }
    $pdf->Output(__DIR__ . '/' . $path_part, 'F');
    return $invoice_url;
}

function crpay_transactions_invoice_user($encrypted_transaction_id, $billing_details = false) {
    $transaction_id = crpay_encryption($encrypted_transaction_id, false);
    if ($transaction_id) {
        if ($billing_details) {
            crpay_transactions_update($transaction_id, ['billing' => is_string($billing_details) ? $billing_details : json_encode($billing_details, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE)]);
            $path = __DIR__ . '/uploads' . crpay_cloud_path_part() . '/inv-' . $transaction_id . '.pdf';
            if (file_exists($path)) {
                unlink($path);
            }
        }
        return crpay_transactions_invoice($transaction_id);
    }
    return false;
}

function crpay_transactions_invoice_form($visible = false) {
    $code = '<div class="crpay-billing-cnt">' . ($visible ? '' : '<div id="crpay-btn-invoice" class="crpay-link crpay-underline">' . crpay_('Generate invoice?') . '</div>') . '<div id="crpay-billing" class="crpay-billing' . ($visible ? '' : ' crpay-hidden') . '">' . ($visible ? '' : '<i id="crpay-btn-invoice-close" class="crpay-icon-close crpay-btn-red crpay-btn-icon"></i>') . '<div class="crpay-title crpay-title-1">' . crpay_('Billing information') . '</div>';
    $fields = [['Full name', 'name'], ['Address', 'address'], ['City', 'city'], ['State', 'state'], ['ZIP code', 'zip'], ['Country', 'country'], ['VAT', 'vat']];
    for ($i = 0; $i < count($fields); $i++) {
        if ($fields[$i][1] == 'country') {
            $code .= '<div class="crpay-input"><span>' . crpay_('Country') . '</span><select name="country"><option></option>' . crpay_select_countries() . '</select></div>';
        } else {
            $code .= '<div class="crpay-input"><span>' . crpay_($fields[$i][0]) . '</span><input name="' . $fields[$i][1] . '" type="text" /></div>';
        }
    }
    return $code . ($visible ? '' : '<div class="crpay-title crpay-title-2">' . crpay_('Payment') . '</div>') . '</div></div>';
}

function crpay_select_countries() {
    $countries = json_decode(file_get_contents(__DIR__ . '/../../resources/countries.json'), true);
    $code = '';
    foreach ($countries as $key => $country_code) {
        $code .= '<option value="' . $key . '" data-country-code="' . $country_code . '">' . crpay_($key) . '</option>';
    }
    return $code;
}

function crpay_transactions_decrypt($transaction) {
    if (is_string($transaction)) {
        return json_decode(crpay_encryption($transaction, false), true);
    }
    if (!crpay_verify_admin()) {
        crpay_error('security-error', 'crpay_transactions_decrypt');
        return 'security-error';
    }
    return $transaction;
}

function crpay_payment_link($transaction_id) {
    return crpay_get_url('id') . crpay_encryption($transaction_id);
}

function crpay_transactions_get_description($transaction_id) {
    $description = json_decode(crpay_isset(crpay_db_get('SELECT description FROM crpay_transactions WHERE id = ' . crpay_db_escape($transaction_id, true)), 'description', '[]'), true);
    return $description ? $description : [];
}

function crpay_transactions_cancel($transaction) {
    $transaction = crpay_transactions_decrypt($transaction);
    if (isset($transaction['id'])) {
        crpay_db_query('DELETE FROM crpay_transactions WHERE id = ' . crpay_db_escape($transaction['id'], true));
        $slug = $transaction['cryptocurrency'] . '-manual-addresses';
        $addresses = json_decode(crpay_settings_db($slug, false, '{}'), true);
        $to = crpay_isset($transaction, 'to');
        if ($to) {
            unset($addresses[$to]);
            crpay_settings_db($slug, $addresses);
        }
    }
    return false;
}

function crpay_transactions_refund($transaction_id) {
    $transaction = crpay_transactions_get($transaction_id);
    $status = ['transaction-not-found', 'Transaction not found.'];
    if ($transaction) {
        if (in_array($transaction['status'], ['C', 'X'])) {
            if ($transaction['hash']) {
                $cryptocurrency_code = $transaction['cryptocurrency'];
                $transaction_blockchain = crpay_blockchain($cryptocurrency_code, 'transaction', $transaction['hash'], $transaction['to']);
                $address = crpay_isset($transaction_blockchain, 'address');
                if ($address) {
                    if ($transaction_blockchain['value'] === $transaction['amount'] && $address === $transaction['from']) {
                        $status = ['refunds-not-enabled', 'Refunds not enabled.'];
                        if (crpay_settings_get('btc-node-refunds') && $cryptocurrency_code == 'btc') {
                            require_once(__DIR__ . '/bitcoin.php');
                            $response = crpay_btc_transfer($transaction_blockchain['value'], $address);
                            if (is_string($response)) {
                                $status = [true, str_replace('{R}', '<a href="#" data-hash="' . $response . '" target="_blank">' . crpay_('here') . '</a>', crpay_('Refund sent. Transaction details {R}.'))];
                            } else if ($response['error']) {
                                $status = ['bitcoin-error', crpay_isset($response['error'], 'message', $response['error'])];
                            }
                        } else if (crpay_settings_get('eth-node-refunds') && in_array($cryptocurrency_code, crpay_get_cryptocurrency_codes('eth'))) {
                            require_once(__DIR__ . '/web3.php');
                            $response = crpay_eth_transfer($transaction_blockchain['value'], $cryptocurrency_code, $address);
                            if (is_string($response)) {
                                $status = [true, str_replace('{R}', '<a href="#" data-hash="' . $response . '" target="_blank">' . crpay_('here') . '</a>', crpay_('Refund sent. Transaction details {R}.'))];
                            } else if ($response['error']) {
                                $status = ['ethereum-error', crpay_isset($response['error'], 'message', $response['error'])];
                            }
                        } else if (crpay_settings_get('coinbase-refunds')) {
                            $account = crpay_coinbase_get_accounts($cryptocurrency_code);
                            if ($account) {
                                $response = crpay_coinbase_curl('/v2/accounts/' . $account['id'] . '/transactions', ['to' => $address, 'amount' => $transaction_blockchain['value'], 'currency' => $cryptocurrency_code, 'type' => 'send']);
                                if (crpay_isset(crpay_isset($response, 'data', []), 'status') == 'pending') {
                                    $status = [true, str_replace('{R}', '<a href="https://www.coinbase.com' . str_replace('/v2', '', $response['data']['resource_path']) . '" target="_blank">' . crpay_('here') . '</a>', crpay_('Refund sent. Transaction details {R}.'))];
                                } else {
                                    $status = ['coinbase-error', isset($response['errors']) ? $response['errors'][0]['message'] : json_encode($response)];
                                }
                            } else {
                                $status = ['unsupported-cryptocurrency', 'Cryptocurrency not supported.'];
                            }
                        }
                    } else {
                        $status = ['invalid-amount', 'Invalid amount or address.'];
                    }
                } else {
                    $status = ['sender-address-not-found', 'Sender address not found.'];
                }
            } else {
                $status = ['hash-not-found', 'Transaction hash not found.'];
            }
        } else {
            $status = ['wrong-transaction-status', 'Incorrect transaction status. Only transactions marked as completed or underpaid can be refunded.'];
        }
    }
    if ($status[0] === true) {
        $description = crpay_transactions_get_description($transaction_id);
        array_push($description, str_replace('{R}', date('Y-m-d H:i:s'), crpay_('Refund sent on {R}. Transaction hash: ')) . $response);
        crpay_transactions_update($transaction_id, ['status' => 'R', 'description' => $description]);
    }
    return ['status' => $status[0], 'message' => crpay_($status[1])];
}

/*
 * -----------------------------------------------------------
 * CHECKOUT
 * -----------------------------------------------------------
 *
 * 1. Return all checkouts or the specified one
 * 2. Save a checkout
 * 3. Delete a checkout
 * 4. Direct payment checkout
 *
 */

function crpay_checkout_get($checkout_id = false) {
    global $CRPAY_CHECKOUTS;
    if ($CRPAY_CHECKOUTS) {
        return $CRPAY_CHECKOUTS;
    }
    $response = crpay_db_get('SELECT * FROM crpay_checkouts' . ($checkout_id ? ' WHERE ' . (is_numeric($checkout_id) ? 'id' : 'slug') . ' = "' . crpay_db_escape($checkout_id) . '"' : ''), $checkout_id);
    $CRPAY_CHECKOUTS = defined('CRPAY_SHOP') ? crpay_shop_checkout_get_part($response) : $response;
    return $CRPAY_CHECKOUTS;
}

function crpay_checkout_save($checkout) {
    $checkout = json_decode($checkout, true);
    $is_new = empty($checkout['id']);
    if (empty($checkout['currency'])) {
        $checkout['currency'] = crpay_settings_get('currency', 'USD');
    }
    $shop = defined('CRPAY_SHOP') ? crpay_shop_checkout_save_part($checkout, $is_new) : ($is_new ? ['', ''] : '');
    if ($is_new) {
        return crpay_db_query('INSERT INTO crpay_checkouts(title, description, price, currency, type, redirect, hide_title, external_reference, creation_time, slug' . $shop[0] . ') VALUES ("' . crpay_db_escape($checkout['title']) . '", "' . crpay_db_escape(crpay_isset($checkout, 'description', '')) . '", "' . crpay_db_escape($checkout['price'], true) . '", "' . crpay_db_escape(crpay_isset($checkout, 'currency', '')) . '", "' . crpay_db_escape($checkout['type']) . '", "' . crpay_db_escape(crpay_isset($checkout, 'redirect', '')) . '", ' . (empty($checkout['hide_title']) ? 0 : 1) . ', "' . crpay_db_escape(crpay_isset($checkout, 'external_reference', '')) . '", "' . gmdate('Y-m-d H:i:s') . '", "' . crpay_db_escape(crpay_isset($checkout, 'slug')) . '"' . $shop[1] . ')', true);
    } else {
        return crpay_db_query('UPDATE crpay_checkouts SET title = "' . crpay_db_escape($checkout['title']) . '", description = "' . crpay_db_escape(crpay_isset($checkout, 'description', '')) . '", price = "' . crpay_db_escape($checkout['price'], true) . '", currency = "' . crpay_db_escape(crpay_isset($checkout, 'currency', '')) . '", type = "' . crpay_db_escape($checkout['type']) . '", redirect = "' . crpay_db_escape(crpay_isset($checkout, 'redirect', '')) . '", hide_title = ' . (empty($checkout['hide_title']) ? 0 : 1) . ', external_reference = "' . crpay_db_escape(crpay_isset($checkout, 'external_reference', '')) . '", slug = "' . crpay_db_escape(crpay_isset($checkout, 'slug', '')) . '"' . $shop . ' WHERE id = "' . crpay_db_escape($checkout['id'], true) . '"');
    }
}

function crpay_checkout_delete($checkout_id) {
    $checkout = crpay_checkout_get($checkout_id);
    $response = crpay_db_query('DELETE FROM crpay_checkouts WHERE id = "' . crpay_db_escape($checkout_id) . '"');
    if ($response === true) {
        $downloads = crpay_isset($checkout, 'downloads', []);
        for ($i = 0; $i < count($downloads); $i++) {
            crpay_file_delete($downloads[$i], 'checkout/');
        }
        if (!empty($checkout['image'])) {
            crpay_file_delete($checkout['image']);
        }
    }
    return $response;
}

function crpay_checkout_direct() {
    if (isset($_GET['checkout_id'])) {
		echo '<div data-cryptopayment="' . crpay_sanatize_string($_GET['checkout_id']) . '" ' .
		'data-price="' . crpay_sanatize_string(crpay_isset($_GET, 'price')) . '" ' .
		'data-external-reference="' . crpay_sanatize_string(crpay_isset($_GET, 'external_reference', crpay_isset($_GET, 'external-reference', ''))) . '" ' .
		'data-redirect="' . crpay_sanatize_string(crpay_isset($_GET, 'redirect', '')) . '" ' .
		'data-currency="' . crpay_sanatize_string(crpay_isset($_GET, 'currency', '')) . '" ' .
		(isset($_GET['title']) ? 'data-title="' . crpay_sanatize_string($_GET['title']) . '" ' : '') .
		(isset($_GET['description']) ? 'data-description="' . crpay_sanatize_string($_GET['description']) . '" ' : '') .
		(isset($_GET['note']) ? 'data-note="' . crpay_sanatize_string($_GET['note']) . '"' : '') .
		'>';        
		require_once(__DIR__ . '/init.php');
        echo '</div>';
    }
}

function crpay_checkout_url($amount, $cryptocurrency_code, $currency_code, $checkout_url, $transaction_id, $title = '', $description = '') {
    return $cryptocurrency_code == 'verifone' ? crpay_verifone_create_checkout($amount, $checkout_url, $transaction_id, $title, $currency_code) : ($cryptocurrency_code == 'stripe' ? crpay_stripe_payment(floatval($amount) * 100, $checkout_url, $transaction_id, $currency_code, $description) : crpay_paypal_get_checkout_url($transaction_id, $checkout_url, $amount, $currency_code, $title));
}

/*
 * -----------------------------------------------------------
 * CUSTOMERS
 * -----------------------------------------------------------
 *
 * 1. Add a new customer
 * 2. Return a customer
 * 3. Return the username of a customer
 *
 */

function crpay_customers_add($first_name = false, $last_name = false, $email = false, $phone = false, $country = false, $country_code = false, $extra = false) {
    return crpay_db_query('INSERT INTO crpay_customers(first_name, last_name, email, phone, country, country_code, creation_time, extra) VALUES ("' . ($first_name ? crpay_db_escape($first_name) : '') . '", "' . ($last_name ? crpay_db_escape($last_name) : '') . '", ' . ($email ? '"' . crpay_db_escape($email) . '"' : 'NULL') . ', "' . ($phone ? crpay_db_escape($phone) : '') . '", "' . ($country ? crpay_db_escape($country) : '') . '", "' . ($country_code ? crpay_db_escape($country_code) : '') . '", "' . gmdate('Y-m-d H:i:s') . '", "' . ($extra ? crpay_db_escape($extra) : '') . '")', true);
}

function crpay_customers_get($customer_id_or_email = false) {
    $is_id = is_numeric($customer_id_or_email);
    return crpay_db_get('SELECT * FROM crpay_customers' . ($customer_id_or_email ? ' WHERE ' . ($is_id ? 'id' : 'email') . ' = "' . crpay_db_escape($customer_id_or_email, $is_id) . '"' : ''), $customer_id_or_email);
}

function crpay_customers_username($user) {
    return trim(crpay_isset($user, 'first_name', '') . ' ' . crpay_isset($user, 'last_name', ''));
}

/*
 * -----------------------------------------------------------
 * CRYPTO
 * -----------------------------------------------------------
 *
 * 1. Get balances
 * 2. Get the API key
 * 3. Get the fiat value of a cryptocurrency value
 * 4. Get the cryptocurrency value of a fiat value
 * 5.
 * 6. Get cryptocurrency name
 * 7. Get the crypto payment address
 * 8. Get USD exchange rate
 * 9. Get exchange rate
 * 10. Convert to FIAT
 * 11. Transfer cryptocurrencies
 * 12. Get crypto network
 * 13. Return the base cryptocurrency code of a token
 * 14. Verify an address
 * 15. Get all custom tokens
 * 16. Get cryptocurrency codes by blockchain
 * 17. Get decimals of a cryptocurrency
 * 18. Get the amount in the correct decimal length
 * 19. Check if a currency is a cryptocurrency
 * 20. Check if the string is from a FIAT provider
 * 21. Check if a cryptocurrency code is a custom token
 * 22. Get the cryptocurrency logo of the specified cryptocurrency code
 * 23. Validate an address
 * 24. Return the external explorer link of a transaction
 * 25. Return the blockchain fee
 *
 */

function crpay_crypto_balances($cryptocurrency_code = false) {
    $cryptocurrencies = $cryptocurrency_code ? [$cryptocurrency_code] : ['btc', 'eth', 'usdt', 'usdt_tron', 'usdt_bsc', 'usdc', 'ltc', 'sol', 'xrp', 'doge', 'busd', 'bnb', 'shib', 'link', 'bat', 'algo', 'bch'];
    $currency = crpay_settings_get('currency', 'USD');
    $response = ['balances' => []];
    $total = 0;
    $custom_token_images = [];
    if (!$cryptocurrency_code) {
        $custom_tokens = crpay_get_custom_tokens();
        foreach ($custom_tokens as $key => $value) {
            array_push($cryptocurrencies, $value['code']);
            $custom_token_images[$key] = $value['img'];
        }
    }
    for ($i = 0; $i < count($cryptocurrencies); $i++) {
        $cryptocurrency_code = $cryptocurrencies[$i];
        if (crpay_settings_get_address($cryptocurrency_code)) {
            $balance = crpay_blockchain($cryptocurrency_code, 'balance');
            $fiat = 0;
            if ($balance && is_numeric($balance)) {
                $fiat = crpay_crypto_get_fiat_value($balance, crpay_crypto_get_base_code($cryptocurrency_code), $currency);
                $total += $fiat;
            } else {
                $balance = 0;
            }
            $response['balances'][$cryptocurrency_code] = ['amount' => $balance, 'fiat' => $fiat, 'name' => crpay_crypto_name($cryptocurrency_code, true)];
        }
    }
    $response['total'] = round($total, 2);
    $response['currency'] = strtoupper($currency);
    $response['token_images'] = $custom_token_images;
    return $response;
}

function crpay_crypto_api_key($service, $url = false) {
    $key = false;
    $key_parameter = false;
    switch ($service) {
        case 'etherscan':
            $keys = ['TBGQBHIXM113HT94ZWYY8MXGWFP9257541', 'GHAQC5VG536H7MSZR5PZF27GZJUSGH94TK', 'F1HZ35IJCR8DQC4SGVJBYMYB928UFV58MP', 'ADR46A53KIXDJ6BMJYK5EEGKQJDDQH6H1K', 'AIJ9S76757JZ7B9KQMJTAN3SRNKF5F5P4M'];
            $key_parameter = 'apikey';
            break;
        case 'ethplorer':
            $keys = ['EK-feNiM-th8gYm7-qECAq', 'EK-qCQHY-co6TwoA-ASWUm', 'EK-51EKh-8cvKWm5-qhjuU', 'EK-wmJ14-faiQNhf-C5Gsj', 'EK-i6f3K-1BtBfUf-Ud7Lo'];
            $key_parameter = 'apiKey';
            break;
        case 'bscscan':
            $keys = ['2Z5V3AZV5P4K95M9UXPABQ19CAVWR7RM78', '6JG8B7F5CC5APF2Q1C3BXRMZSS92F1RGKX', '2BAPYF16Z6BR8TY2SZGN74231JNZ8TFQKU', '1DNAQ7C2UAYPS5WW7HQXPCF8WFYG8CP3XQ', 'MP3XAXN1D7XVYZQVNCMGII5JZTBRASG996'];
            $key_parameter = 'apiKey';
            break;
        case 'blockdaemon':
            $keys = ['5inALCDK3NzmSoA-EC4ribZEDAvj0zy95tPaorxMZYzTRR0u', 'i1-LMC4x9ZgSlZ-kSrCf3pEeckZadAsKCJxuvXRq9pusgK2T', 'ktbzuPccKUwnnMI73YLEK7h29dEOQfFBOCNAXJ0SnHw8rn69', 'FI2b6Cfpf8lee2xaTs98IprkPb1OuxjW11M2Sq-vlIrqzKsR', '1nvtfBzPsjByQPYBr0xoxc1jv9KrntMnOhkjKTkTt3ejxUXk'];
            $key_parameter = '-';
            break;
        case 'tatum':
            $keys = ['90a07172-cd5e-452e-9b81-56f37c9693bb', '573c3fea-8325-4088-a35e-e97fdf2bc365', '330c5774-0de7-4963-895f-2b0c784011d2', '2f9a0a5f-f587-4545-8c38-f72007461e7a', '076a59f5-7cb5-4169-a038-3decda950b41'];
            $key_parameter = '-';
            break;
    }
    if ($key_parameter) {
        $key = crpay_settings_get($service . '-key');
        if (!$key)
            $key = $keys[rand(0, 4)];
    }
    return $key ? ($url ? ($url . (strpos($url, '?') ? '&' : '?') . $key_parameter . '=' . $key) : $key) : ($url ? $url : false);
}

function crpay_crypto_get_fiat_value($amount, $cryptocurrency_code, $currency_code) {
    if (!is_numeric($amount)) {
        return crpay_error('Invalid amount (' . $amount . ')', 'crpay_crypto_get_fiat_value');
    }
    $cryptocurrency_code = strtoupper(crpay_crypto_get_base_code($cryptocurrency_code));
    $unsupported = ['BNB', 'BUSD'];
    if (in_array($cryptocurrency_code, $unsupported)) {
        $usd_rates = $currency_code == 'USD' ? 1 : crpay_usd_rates($currency_code);
        $crypto_rate_usd = json_decode(crpay_curl('https://api.binance.us/api/v3/ticker/price?symbol=' . $cryptocurrency_code . 'USD'), true)['price'];
        $rate = 1 / (floatval($crypto_rate_usd) * $usd_rates);
    } else {
        $rate = crpay_exchange_rates($currency_code, $cryptocurrency_code);
    }
    return round((1 / $rate) * floatval($amount), 2);
}

function crpay_crypto_get_cryptocurrency_value($amount, $cryptocurrency_code, $currency_code) {
    $unsupported = ['BNB', 'BUSD'];
    $cryptocurrency_code = strtoupper(crpay_crypto_get_base_code($cryptocurrency_code));
    $rate = false;
    $is_crypto = crpay_crypto_is($currency_code);
    if (!$is_crypto && in_array($cryptocurrency_code, $unsupported)) {
        $usd_rates = $currency_code == 'USD' ? 1 : crpay_usd_rates($currency_code);
        $crypto_rate_usd = json_decode(crpay_curl('https://api.binance.us/api/v3/ticker/price?symbol=' . $cryptocurrency_code . 'USD'), true)['price'];
        $rate = 1 / (floatval($crypto_rate_usd) * $usd_rates);
    } else if ($is_crypto) {
        $rate = crpay_exchange_rates('usd', $cryptocurrency_code) / crpay_exchange_rates('usd', $currency_code);
    } else {
        $rate = crpay_exchange_rates($currency_code, $cryptocurrency_code);
    }
    $response = crpay_crypto_get_value_with_decimals(crpay_decimal_number($rate * floatval($amount)), $cryptocurrency_code);
    return $response ? $response : 0;
}

function crpay_crypto_name($cryptocurrency_code = false, $uppercase = false) {
    $names = ['btc' => ['bitcoin', 'Bitcoin'], 'btc_ln' => ['bitcoinlightningnetwork', 'Bitcoin Lightning Network'], 'eth' => ['ethereum', 'Ethereum'], 'xrp' => ['xrp', 'XRP'], 'doge' => ['dogecoin', 'Dogecoin'], 'algo' => ['algorand', 'Algorand'], 'usdt' => ['tether', 'Tether'], 'usdt_tron' => ['tether', 'Tether'], 'usdt_bsc' => ['tether', 'Tether'], 'usdc' => ['usdcoin', 'USD Coin'], 'link' => ['chainlink', 'Chainlink'], 'shib' => ['shibainu', 'Shiba Inu'], 'bat' => ['basicattentiontoken', 'Basic Attention Token'], 'busd' => ['binanceusd', 'Binance USD'], 'bnb' => ['bnb', 'BNB'], 'ltc' => ['litecoin', 'Litecoin'], 'bch' => ['bitcoincash', 'Bitcoin Cash'], 'trx' => ['tron', 'Tron'], 'bsc' => ['binancechain', 'Binance Chain'], 'sol' => ['solana', 'Solana']];
    $custom_tokens = crpay_get_custom_tokens();
    foreach ($custom_tokens as $key => $value) {
        $names[strtolower($key)] = [strtolower($value['name']), $value['name']];
    }
    return $cryptocurrency_code ? $names[strtolower($cryptocurrency_code)][$uppercase] : $names;
}

function crpay_crypto_get_address($cryptocurrency_code) {
    $address = false;
    $cryptocurrency_name = crpay_crypto_name($cryptocurrency_code);
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    if (($cryptocurrency_code === 'btc' && crpay_settings_get('btc-node-address-generation')) || crpay_is_eth_address_generation($cryptocurrency_code)) {
        $ethereum = $cryptocurrency_code != 'btc';
        $slug = ($ethereum ? 'eth' : 'btc') . '-addresses' . (!$ethereum && crpay_settings_get('btc-node-xpub') ? '-xpub' : '');
        $addresses = crpay_settings_db($slug);
        $addresses_count = 0;
        if ($addresses) {
            $addresses = json_decode(crpay_encryption($addresses, false), true);
            $addresses_count = count($addresses);
            $now_less_24h = time() - 86000;
            for ($i = 0; $i < $addresses_count; $i++) {
                if ($addresses[$i][1] < $now_less_24h) {
                    $address = $addresses[$i][0]['address'];
                    $addresses[$i][1] = time();
                    break;
                }
            }
        } else {
            $addresses = [];
        }
        if (!$address) {
            require_once(__DIR__ . ($ethereum ? '/web3.php' : '/bitcoin.php'));
            if ($ethereum) {
                if (crpay_settings_get('eth-node-url')) {
                    $address = crpay_eth_generate_address();
                }
            } else if (crpay_settings_get('btc-node-xpub') && !crpay_settings_get('btc-node-transfer')) {
                $address = crpay_settings_get('btc-node-address-generation-method') === 'node' && crpay_settings_get('btc-node-url') ? crpay_btc_generate_address_xpub_node(false, [$addresses_count, $addresses_count]) : crpay_btc_generate_address_xpub(false, [$addresses_count, $addresses_count]);
            } else if (crpay_settings_get('btc-node-url')) {
                $address = crpay_btc_generate_address();
            }
            if ($address && isset($address['address'])) {
                array_push($addresses, [$address, time()]);
                $address = $address['address'];
            } else {
                crpay_error(is_array($address) ? json_encode($address) : $address, 'crpay_crypto_get_address');
            }
        }
        if ($address) {
            crpay_settings_db($slug, json_encode(crpay_encryption($addresses)));
        }
    }
    if (!$address && crpay_settings_get('custom-explorer-active')) {
        $data = crpay_curl(str_replace(['{N}', '{N2}'], [$cryptocurrency_code, $cryptocurrency_name], crpay_settings_get('custom-explorer-address')));
        $data = crpay_get_array_value_by_path(crpay_settings_get('custom-explorer-address-path'), json_decode($data, true));
        if ($data) {
            $address = $data;
        }
    }
    if (!$address) {
        $addresses = crpay_settings_get_address($cryptocurrency_code, false);
        $addresses_count = count($addresses);
        $addresses_db = json_decode(crpay_settings_db($cryptocurrency_code . '-manual-addresses', false, '{}'), true);
        if ($addresses_count > 2) {
            $now_less_24h = time() - 86000;
            $stop_reusing_addresses = crpay_settings_get('stop-reusing-addresses');
            for ($i = 1; $i < $addresses_count; $i++) {
                if (crpay_isset($addresses_db, $addresses[$i]) < $now_less_24h && (!$stop_reusing_addresses || !isset($addresses_db[$addresses[$i]]))) {
                    if (crpay_crypto_whitelist_invalid($addresses[$i], false, $cryptocurrency_code)) {
                        return crpay_error('whitelist-invalid', 'crpay_crypto_get_address', true);
                    }
                    $address = $addresses[$i];
                    $addresses_db[$address] = time();
                    break;
                }
            }
            if ($address) {
                crpay_settings_db($cryptocurrency_code . '-manual-addresses', $addresses_db);
            }
        }
    }
    if (!$address && crpay_settings_get('gemini-address-generation')) {
        $data = crpay_gemini_curl('deposit/' . $cryptocurrency_name . '/newAddress');
        $address = crpay_isset($data, 'address');
        if (crpay_isset($data, 'result') === 'error') {
            crpay_error($data['message'], 'crpay_crypto_get_address');
        }
    }
    if (!$address && crpay_settings_get('coinbase-address-generation')) {
        $account = crpay_coinbase_get_accounts($cryptocurrency_code);
        if ($account) {
            $data = crpay_coinbase_curl('/v2/accounts/' . $account['id'] . '/addresses');
            $address = crpay_isset(crpay_isset($data, 'data'), 'address');
            if (isset($data['error'])) {
                crpay_error($data['errors'][0]['message'], 'crpay_crypto_get_address');
            }
        }
    }
    if ($address) {
        $pos = strpos($address, ':');
        return $pos ? substr($address, $pos + 1) : $address;
    }
    return crpay_settings_get_address($cryptocurrency_code);
}

function crpay_usd_rates($currency_code = false) {
    $fiat_rates = json_decode(crpay_settings_db('fiat_rates'), true);
    if (!$fiat_rates || $fiat_rates[0] < (time() - 3600)) {
        $app_id = CRPAY_CLOUD ? OPEN_EXCHANGE_RATE_APP_ID : crpay_settings_get('openexchangerates-app-id');
        $error = '';
        if (!$app_id) {
            $app_ids = ['ce46867e51c9432ea6d36fae9537c3da', '99a38f6aeef64c23a9f7bc4395ed3951', '96558a544c9d48d0a79c84deeac3db3c', 'ccc588ffb72646fe943c30f8d1541774', 'dfd024988aff4eb9b1c07755848811b3'];
            $app_id = $app_ids[rand(0, 4)];
            $error = 'Missing Open Exchange Rates App ID. Set it in the Cryptopayment settings area. ';
        }
        $json = crpay_curl('https://openexchangerates.org/api/latest.json?app_id=' . $app_id);
        $fiat_rates = crpay_isset(json_decode($json, true), 'rates');
        if ($fiat_rates) {
            crpay_settings_db('fiat_rates', [time(), json_encode($fiat_rates)]);
        } else {
            return crpay_error($error . 'Error: ' . $json, 'crpay_usd_rates', true);
        }
    } else {
        $fiat_rates = json_decode($fiat_rates[1], true);
    }
    return $currency_code ? $fiat_rates[strtoupper($currency_code)] : $fiat_rates;
}

function crpay_exchange_rates($currency_code, $cryptocurrency_code) {
    global $CRPAY_EXCHANGE_RATE;
    $custom_tokens = crpay_get_custom_tokens();
    $currency_code = strtoupper(crpay_crypto_is($currency_code) ? crpay_crypto_get_base_code($currency_code) : $currency_code);
    if ($custom_tokens && isset($custom_tokens[strtolower($cryptocurrency_code)])) {
        $rate = $custom_tokens[strtolower($cryptocurrency_code)]['rate'];
        if ($rate) {
            return $rate;
        }
    }
    $cryptocurrency_code = strtoupper(crpay_crypto_get_base_code($cryptocurrency_code));
    if (empty($CRPAY_EXCHANGE_RATE)) {
        $CRPAY_EXCHANGE_RATE = [];
    }
    for ($i = 0; $i < 4; $i++) {
        if (empty($CRPAY_EXCHANGE_RATE) || empty($CRPAY_EXCHANGE_RATE[$cryptocurrency_code]) || $CRPAY_EXCHANGE_RATE['currency_code'] != $currency_code) {
            switch ($i) {
                case 0:
                    $response = json_decode(crpay_curl('https://api.coinbase.com/v2/exchange-rates?currency=' . $currency_code), true);
                    if ($response) {
                        if (isset($response['errors'])) {
                            crpay_error($response['errors'][0]['message'], 'crpay_exchange_rates', true);
                        } else if (isset($response['data'])) {
                            $CRPAY_EXCHANGE_RATE = $response['data']['rates'];
                        }
                    }
                    break;
                case 1:
                    $response = json_decode(crpay_curl('https://api.binance.com/api/v3/ticker/price?symbol=' . $cryptocurrency_code . ($currency_code == 'USD' ? 'USDT' : $currency_code)), true);
                    $response = crpay_isset($response, 'price');
                    if ($response) {
                        $CRPAY_EXCHANGE_RATE[$cryptocurrency_code] = 1 / $response;
                    }
                    break;
                case 2:
                    $cryptocurrency_code_2 = strtolower(str_replace(' ', '-', crpay_crypto_name($cryptocurrency_code, true)));
                    $response = json_decode(crpay_curl('https://api.coingecko.com/api/v3/simple/price?ids=' . $cryptocurrency_code_2 . '&vs_currencies=' . $currency_code), true);
                    $CRPAY_EXCHANGE_RATE[$cryptocurrency_code] = crpay_isset(crpay_isset($response, $cryptocurrency_code_2), strtolower($currency_code));
                    break;
                case 3:
                    $response = json_decode(crpay_curl('https://www.bitstamp.net/api/v2/ticker/' . strtolower($cryptocurrency_code . $currency_code)), true);
                    $CRPAY_EXCHANGE_RATE[$cryptocurrency_code] = crpay_isset($response, 'last');
                    break;
            }
            $CRPAY_EXCHANGE_RATE['currency_code'] = $currency_code;
        } else {
            break;
        }
    }
    return floatval($CRPAY_EXCHANGE_RATE[$cryptocurrency_code]);
}

function crpay_crypto_convert($transaction_id, $cryptocurrency_code, $amount) {
    $response = false;
    $success = false;
    $ethereum = in_array($cryptocurrency_code, crpay_get_cryptocurrency_codes('eth')) && crpay_settings_get('eth-node-conversion');
    $cryptocurrency_code_uppercase = strtoupper($cryptocurrency_code);
    if ($ethereum) {
        require_once(__DIR__ . '/web3.php');
        $history = json_decode(crpay_settings_db('fiat_conversion', false, '[]'), true);
        if (!in_array($transaction_id, $history)) {
            $response = crpay_eth_swap($amount, $cryptocurrency_code);
            $success = substr($response, 0, 2) == '0x';
        }
    }
    if (!$success) {
        $gemini = crpay_settings_get('gemini-conversion');
        $coinbase = crpay_settings_get('coinbase-conversion');
        if ($gemini || $coinbase) {
            $history = json_decode(crpay_settings_db('fiat_conversion', false, '[]'), true);
            if (!in_array($transaction_id, $history)) {
                array_push($history, $transaction_id);
                if ($gemini) {
                    $response = crpay_gemini_convert_to_fiat($cryptocurrency_code, $amount);
                }
                if ($response == false && $coinbase) {
                    $currency_code = crpay_coinbase_get_account_fiat_currency_code();
                    $response = crpay_coinbase_curl('/api/v3/brokerage/orders', ['side' => 'SELL', 'client_order_id' => crpay_random_string(), 'product_id' => $cryptocurrency_code_uppercase . '-' . $currency_code, 'order_configuration' => ['limit_limit_gtc' => ['base_size' => $amount, 'limit_price' => (string) round(crpay_exchange_rates($cryptocurrency_code, $currency_code) * 0.9, 2)]]]);
                }
                $success = ($coinbase && crpay_isset(crpay_isset($response, 'data', []), 'status') == 'created') || ($gemini && crpay_isset($response, 'order_id'));
            }
        }
    }
    if ($success) {
        array_push($history, $transaction_id);
    }
    if ($ethereum || $gemini || $coinbase) {
        crpay_settings_db('fiat_conversion', $history);
        if (crpay_settings_get('notifications-conversion')) {
            $language = crpay_settings_get('language-admin');
            $value = $amount . ' ' . $cryptocurrency_code_uppercase;
            $subject = $value . ' ' . crpay_m('converted', $language);
            $name = $ethereum ? 'Ethereum' : ($gemini ? 'Gemini' : 'Coinbase');
            $message = str_replace(['{T}', '{T2}'], [$value, $name], crpay_m('{T} were converted to ' . strtoupper($ethereum ? crpay_settings_get('eth-node-conversion-currency') : ($gemini ? crpay_settings_get('gemini-conversion-currency') : 'FIAT')) . ' through {T2}.', $language));
            if (!$success) {
                $subject = 'The conversion of ' . $value . ' failed';
                $message = 'The conversion of ' . $value . ' through ' . $name . ' failed. Response details: <br><br>' . json_encode($response);
            }
            crpay_email_notification($subject, $message);
        }
    }
    return $response;
}

function crpay_crypto_transfer($transaction_id, $cryptocurrency_code, $amount) {
    $response = false;
    $success = false;
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    $ethereum = in_array($cryptocurrency_code, crpay_get_cryptocurrency_codes('eth')) && crpay_settings_get('eth-node-transfer') && !crpay_settings_get('eth-node-conversion');
    $bitcoin = $cryptocurrency_code == 'btc' && crpay_settings_get('btc-node-transfer');
    if ($bitcoin || $ethereum) {
        require_once(__DIR__ . ($bitcoin ? '/bitcoin.php' : '/web3.php'));
        $history = json_decode(crpay_settings_db('crypto_transfers', false, '[]'), true);
        if (!in_array($transaction_id, $history)) {
            if ($bitcoin) {
                $transaction = crpay_transactions_get($transaction_id);
                $response = crpay_btc_transfer($amount, false, $transaction['to']);
                $success = $response && is_string($response);
            } else {
                $response = crpay_eth_transfer($amount, $cryptocurrency_code);
                $success = substr($response, 0, 2) == '0x';
            }
        }
    }
    if (!$success) {
        $gemini = crpay_settings_get('gemini-transfer') && !crpay_settings_get('gemini-conversion') && crpay_settings_get('gemini-address-generation');
        $coinbase = crpay_settings_get('coinbase-transfer') && !crpay_settings_get('coinbase-conversion') && crpay_settings_get('coinbase-address-generation');
        if ($gemini || $coinbase) {
            $history = json_decode(crpay_settings_db('crypto_transfers', false, '[]'), true);
            if (!in_array($transaction_id, $history)) {
                $address = crpay_settings_get_address($cryptocurrency_code);
                if ($address && !crpay_crypto_whitelist_invalid($address, false)) {
                    if ($gemini) {
                        $response = crpay_gemini_curl('withdraw/' . $cryptocurrency_code, ['address' => $address, 'amount' => $amount]);
                    } else if ($coinbase) {
                        $account = crpay_coinbase_get_accounts($cryptocurrency_code);
                        if ($account) {
                            $response = crpay_coinbase_curl('/v2/accounts/' . $account['id'] . '/transactions', ['to' => $address, 'amount' => $amount, 'currency' => $cryptocurrency_code, 'type' => 'send']);
                        }
                    }
                    $success = ($coinbase && crpay_isset(crpay_isset($response, 'data', []), 'status') == 'pending') || ($gemini && crpay_isset($response, 'address'));
                }
            }
        }
    }
    if ($success) {
        array_push($history, $transaction_id);
    }
    if ($bitcoin || $ethereum || $gemini || $coinbase) {
        crpay_settings_db('crypto_transfers', $history);
        if (crpay_settings_get('notifications-transfer')) {
            $language = crpay_settings_get('language-admin');
            $value = $amount . ' ' . strtoupper($cryptocurrency_code);
            $subject = $value . ' ' . crpay_m('sent to', $language) . ' ' . $address;
            $name = $ethereum ? 'Ethereum' : ($bitcoin ? 'Bitcoin' : ($gemini ? 'Gemini' : 'Coinbase'));
            $message = str_replace(['{T}', '{T2}', '{T3}'], [$value, '<b>' . $address . '</b>', $name], crpay_m('{T} sent to {T2} through {T3}.', $language));
            if (!$success) {
                $subject = 'The transfer of ' . $value . ' failed';
                $message = 'The transfer of ' . $value . ' to <b>' . $address . '</b> through ' . $name . ' failed. Response details: <br><br>' . json_encode($response);
            }
            crpay_email_notification($subject, $message);
        }
    }
    return $response;
}

function crpay_crypto_get_base_code($cryptocurrency_code) {
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    return crpay_isset(['usdt_tron' => 'usdt', 'usdt_bsc' => 'usdt', 'btc_ln' => 'btc'], $cryptocurrency_code, $cryptocurrency_code);
}

function crpay_crypto_get_network($cryptocurrency_code, $label = false, $exclude_optional_networks = false) {
    $networks = crpay_get_cryptocurrency_codes();
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    $full_name = $label === 'full_name';
    foreach ($networks as $key => $value) {
        if ((!$exclude_optional_networks || crpay_crypto_get_base_code($cryptocurrency_code) != $cryptocurrency_code || $cryptocurrency_code != $value[0]) && in_array($cryptocurrency_code, $networks[$key])) {
            $text = $key . ' <div>' . crpay_('network') . '</div>';
            return $label === true ? '<span class="crpay-label">' . $text . '</span>' : ($full_name ? crpay_crypto_name($key) : strtolower($key));
        }
    }
    return '';
}

function crpay_crypto_whitelist_invalid($address, $check_address_generation = true, $cryptocurrency_code = false) {
    if ($check_address_generation && crpay_is_address_generation($cryptocurrency_code)) {
        return false;
    }
    if (!defined('CRPAY_WHITELIST') || in_array($address, CRPAY_WHITELIST)) {
        return false;
    }
    crpay_error('The address ' . $address . ' is not on the whitelist. Edit the config.php file and add it to the constant CRPAY_WHITELIST.', 'crpay_crypto_address_verification', true);
    return true;
}

function crpay_get_custom_tokens() {
    global $CRPAY_CUSTOM_TOKENS;
    if (isset($CRPAY_CUSTOM_TOKENS)) {
        return $CRPAY_CUSTOM_TOKENS;
    }
    $CRPAY_CUSTOM_TOKENS = [];
    $items = crpay_settings_get_repeater(['custom-token-code', 'custom-token-type', 'custom-token-name', 'custom-token-address', 'custom-token-contract-address', 'custom-token-img', 'custom-token-decimals', 'custom-token-rate', 'custom-token-rate-url']);
    for ($i = 0; $i < count($items); $i++) {
        $item = $items[$i];
        if ($item[1]) {
            $token = strtolower($item[0]);
            $CRPAY_CUSTOM_TOKENS[$token] = ['type' => $item[1], 'code' => $token, 'name' => $item[2], 'address' => $item[3], 'contract_address' => $item[4], 'img' => $item[5], 'decimals' => $item[6], 'rate' => $item[7], 'rate_url' => $item[8]];
        }
    }
    return $CRPAY_CUSTOM_TOKENS;
}

function crpay_get_cryptocurrency_codes($blockchain = false) {
    $custom_tokens = crpay_get_custom_tokens();
    $custom_tokens_network = ['erc-20' => 'ETH', 'bep-20' => 'BSC'];
    $cryptocurrencies = ['BTC' => ['btc'], 'ETH' => ['eth', 'usdt', 'usdc', 'link', 'shib', 'bat'], 'SOL' => ['sol'], 'TRX' => ['usdt_tron'], 'XRP' => ['xrp'], 'LTC' => ['ltc'], 'DOGE' => ['doge'], 'BSC' => ['bnb', 'busd', 'usdt_bsc'], 'BCH' => ['bch'], 'ALGO' => ['algo']];
    foreach ($custom_tokens as $key => $value) {
        array_push($cryptocurrencies[$custom_tokens_network[$value['type']]], $key);
    }
    return $blockchain ? $cryptocurrencies[strtoupper($blockchain)] : $cryptocurrencies;
}

function crpay_crypto_get_decimals($cryptocurrency_code = false) {
    $custom_tokens = crpay_get_custom_tokens();
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    $decimals = ['btc' => 8, 'btc_ln' => 8, 'eth' => 8, 'xrp' => 6, 'usdt' => 6, 'usdt_tron' => 6, 'usdt_bsc' => 6, 'usdc' => 6, 'link' => 5, 'doge' => 8, 'algo' => 6, 'shib' => 1, 'bat' => 3, 'bnb' => 7, 'busd' => 18, 'ltc' => 8, 'bch' => 8, 'sol' => 8];
    foreach ($custom_tokens as $key => $value) {
        $decimals[$key] = $value['decimals'];
    }
    return $cryptocurrency_code ? crpay_isset($decimals, $cryptocurrency_code) : $decimals;
}

function crpay_crypto_get_value_with_decimals($amount, $cryptocurrency_code_or_digits) {
    $decimals = is_int($cryptocurrency_code_or_digits) ? $cryptocurrency_code_or_digits : crpay_crypto_get_decimals($cryptocurrency_code_or_digits);
    $amount_array = explode('.', $amount);
    if (!isset($amount_array[1])) {
        array_push($amount_array, '');
    }
    if (strlen($amount_array[1]) > $decimals) {
        $amount_array[1] = substr($amount_array[1], 0, $decimals);
    }
    if (in_array($cryptocurrency_code_or_digits, ['usdt', 'usdc', 'busd']) && strlen($amount_array[1]) > 2) {
        $amount_array[1] = substr($amount_array[1], 0, 2);
    }
    return $amount_array[0] . rtrim($amount_array[1] && $amount_array[1] != '0' && $amount_array[1] != '00' ? '.' . $amount_array[1] : '', '0');
}

function crpay_crypto_is($currency_code) {
    return isset(crpay_crypto_name()[strtolower($currency_code)]);
}

function crpay_crypto_is_fiat($value) {
    return in_array($value, ['stripe', 'verifone', 'paypal']) || (strlen($value) == 3 && !crpay_crypto_is($value));
}

function crpay_crypto_is_custom_token($cryptocurrency_code) {
    return isset(crpay_get_custom_tokens()[strtolower($cryptocurrency_code)]);
}

function crpay_crypto_get_image($cryptocurrency_code) {
    return crpay_crypto_is_custom_token($cryptocurrency_code) ? crpay_get_custom_tokens()[strtolower($cryptocurrency_code)]['img'] : CRPAY_URL . 'media/icon-' . $cryptocurrency_code . '.svg';
}

function crpay_crypto_validate_address($address, $cryptocurrency_code) {
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    if ($cryptocurrency_code == 'btc') {
        require_once(__DIR__ . '/bitcoin.php');
        return crpay_btc_validate_address($address);
    }
    if (crpay_crypto_get_network($cryptocurrency_code) == 'eth') {
        require_once(__DIR__ . '/web3.php');
        return crpay_eth_validate_address($address);
    }
    return false;
}

function crpay_crypto_get_explorer_link($hash, $cryptocurrency_code) {
    $response = crpay_blockchain($cryptocurrency_code, 'transaction-explorer', $hash);
    $hash = crpay_isset($response, 'hash');
    $network = crpay_crypto_get_network($cryptocurrency_code);
    $explorers = [
        'btc' => ['mempool' => 'https://mempool.space/tx/{R}', 'blockstream' => 'https://blockstream.info/tx/{R}', 'blockchain' => 'https://www.blockchain.com/explorer/transactions/{R2}/{R}'],
        'eth' => ['etherscan' => 'https://etherscan.io/tx/{R}', 'ethplorer' => 'https://ethplorer.io/tx/{R}', 'blockscout' => 'https://blockscout.com/eth/mainnet/tx/{R}'],
        'xrp' => ['ripple' => 'https://livenet.xrpl.org/accounts/{R}'],
        'doge' => ['blockcypher' => 'https://live.blockcypher.com/doge/tx/{R}'],
        'algo' => ['algoexplorerapi' => 'https://algoexplorer.io/tx/{R}'],
        'bnb' => ['bscscan' => 'https://bscscan.com/tx/{R}'],
        'ltc' => ['blockcypher' => 'https://live.blockcypher.com/ltc/tx/{R}'],
        'bch' => ['biggestfan' => 'https://blockchair.com/bitcoin-cash/transaction/{R}'],
        'trx' => ['tronscan' => 'https://tronscan.org/#/transaction/{R}'],
        'sol' => ['solana' => 'https://explorer.solana.com/tx/{R}']
    ];
    $explorers_testnet = [
        'btc' => ['mempool' => 'https://mempool.space/testnet/tx/{R}'],
        'eth' => ['etherscan' => 'https://sepolia.etherscan.io/tx/{R}'],
    ];
    return $hash ? str_replace('{R}', $hash, crpay_settings_get('testnet-' . $network) ? $explorers_testnet[$network][$response['explorer']] : $explorers[$network][$response['explorer']]) : false;
}

function crpay_crypto_get_network_fee($cryptocurrency_code, $returned_currency_code = false) {
    $network = crpay_crypto_get_network($cryptocurrency_code);
    switch ($network) {
        case 'btc':
            require_once(__DIR__ . '/bitcoin.php');
            $fee = crpay_isset(crpay_btc_curl('estimatesmartfee', [5]), 'feerate', 0.00015) / 5;
            break;
        case 'eth':
            require_once(__DIR__ . '/web3.php');
            $fee = hexdec(crpay_eth_curl('eth_gasPrice')) * ($cryptocurrency_code === 'eth' ? 21000 : 100000) / 1000000000000000000;
            break;
    }
    if (!$returned_currency_code) {
        $returned_currency_code = $cryptocurrency_code;
    }
    return crpay_crypto_is($returned_currency_code) ? crpay_crypto_get_cryptocurrency_value($fee, $returned_currency_code, $network) : crpay_crypto_get_fiat_value($fee, $network, $returned_currency_code);
}

/*
 * -----------------------------------------------------------
 * # ACCOUNT
 * -----------------------------------------------------------
 *
 * 1. Admin login
 * 2. Verify the admin login
 * 3. Get the active account
 * 4. Get wallet key
 * 5. Check if the user is logged in as an agent
 *
 */

function crpay_login($username, $password, $code = false) {
    $ip = isset($_SERVER['HTTP_CF_CONNECTING_IP']) && substr_count($_SERVER['HTTP_CF_CONNECTING_IP'], '.') == 3 ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER['REMOTE_ADDR'];
    $ips = json_decode(crpay_settings_db('ip-ban', false, '{}'), true);
    $username = strtolower($username);
    if (isset($ips[$ip]) && $ips[$ip][0] > 10) {
        if ($ips[$ip][1] > time() - 3600) {
            return 'ip-ban';
        }
        unset($ips[$ip]);
        crpay_settings_db('ip-ban', $ips);
    }

    $is_success_login = $username == strtolower(CRPAY_USER) && password_verify($password, CRPAY_PASSWORD);

    if ($is_success_login) {
        if (crpay_settings_get('two-fa-active') && crpay_settings_db('2fa') && (!$code || crpay_2fa($code) !== true)) {
            return '2fa';
        }
    } else if (defined('CRPAY_AGENTS')) {
        for ($i = 0; $i < count(CRPAY_AGENTS); $i++) {
            if ($username == strtolower(CRPAY_AGENTS[$i][0]) && password_verify($password, CRPAY_AGENTS[$i][1])) {
                $is_success_login = 'agent';
                break;
            }
        }
    }
    if ($is_success_login) {
        $data = [CRPAY_USER, $is_success_login === 'agent' ? 'agent' : 'admin'];
        $GLOBALS['CRPAY_LOGIN'] = $data;
        if (crpay_settings_get('notifications-login')) {
            $language = crpay_settings_get('language-admin');
            crpay_email_notification(crpay_m('New login', $language), str_replace(['{T}', '{T2}'], [CRPAY_URL . 'admin.php', date('Y-m-d H:i:s')], crpay_m('New Cryptopayment login at the URL {T}. Date and time of access: {T2}.', $language)));
        }
        return [crpay_encryption(json_encode($data, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE))];
    }
    $ips[$ip] = empty($ips[$ip]) ? [1, time()] : [$ips[$ip][0] + 1, $ips[$ip][1]];
    crpay_settings_db('ip-ban', $ips);
    return false;
}

function crpay_verify_admin($allow_agent = true) {
    $account = crpay_account();
    return $account && $account[0] === CRPAY_USER && ($allow_agent || $account[1] != 'agent') ? $account[1] : false;
}

function crpay_account() {
    global $CRPAY_LOGIN;
    if (!defined('CRPAY_USER')) {
        return false;
    }
    if ($CRPAY_LOGIN) {
        return $CRPAY_LOGIN;
    }
    if (isset($_COOKIE['CRPAY_LOGIN'])) {
        $data = json_decode(crpay_encryption($_COOKIE['CRPAY_LOGIN'], false), true);
        if ($data) {
            $GLOBALS['CRPAY_LOGIN'] = $data;
            return $data;
        }
    }
    return false;
}

function crpay_get_wallet_key($cryptocurrency_code) {
    $key = crpay_settings_get(strtolower($cryptocurrency_code) . '-wallet-key');
    return $key ? crpay_encryption($key, false) : false;
}

function crpay_is_agent() {
    return crpay_verify_admin() === 'agent';
}

/*
 * -----------------------------------------------------------
 * SETTINGS
 * -----------------------------------------------------------
 *
 * 1. Populate the admin area with the settings of the file /resources/settings.json
 * 2. Return the HTML code of a setting element
 * 3. Save all settings
 * 4. Return a single setting
 * 5. Return all settings
 * 6. Return a repeater setting
 * 7. Return JS settings for admin side
 * 8. Return or save a database setting
 * 9. Get a saved address
 * 10. Get confirmations number
 *
 */

function crpay_settings_populate() {
    global $CRPAY_APPS;
    $settings = json_decode(file_get_contents(__DIR__ . '/../../resources/settings.json'), true);
    $code = '';
    $language = crpay_language(true);
    $translations = [];
    
    if ($language) {
        $path = __DIR__ . '/../../resources/languages/settings/' . $language . '.json';
        if (file_exists($path)) {
            $translations = json_decode(file_get_contents($path), true);
        }
    }
    
    if (CRPAY_CLOUD) {
        array_push($settings, ['id' => 'custom-domain', 'type' => 'text', 'title' => 'Custom domain', 'content' => 'Use a custom domain like example.com instead of cloud.cryptopayment.dev.', 'help' => '#custom-domain']);
    }
    
    for ($i = 0; $i < count($settings); $i++) {
        $code .= crpay_settings_get_code($settings[$i], $translations);
    }
    
    for ($i = 0; $i < count($CRPAY_APPS); $i++) {
        if (CRPAY_CLOUD && $CRPAY_APPS[$i] == 'exchange') {
            continue;
        }
        
        $path = __DIR__ . '/../../apps/' . $CRPAY_APPS[$i] . '/settings.json';
        if (file_exists($path)) {
            $appSettings = json_decode(file_get_contents($path), true);
            $title = 'Settings related to the {R} addon.';
            $code .= '<div class="crpay-settings-title crpay-input"><div><span>' . htmlspecialchars(ucfirst($CRPAY_APPS[$i])) . '</span><p>' . htmlspecialchars(str_replace('{R}', $CRPAY_APPS[$i], crpay_isset($translations, $title, $title))) . '</p></div></div>';
            
            for ($y = 0; $y < count($appSettings); $y++) {
                $code .= crpay_settings_get_code($appSettings[$y], $translations);
            }
        }
    }
    
       // Apply wp_kses to sanitize $code
	echo $code;
}


function crpay_settings_get_code($setting, &$translations = []) {
    if (isset($setting)) {
        $id = $setting['id'];
        $type = $setting['type'];
        $title = $setting['title'];
        $content = $setting['content'];
        $code = '<div id="' . $id . '" data-type="' . $type . '" class="crpay-input"><div class="crpay-setting-content"><span>' . crpay_isset($translations, $title, $title) . '</span><p>' . crpay_isset($translations, $content, $content) . (isset($setting['help']) ? '<a href="' . (CRPAY_CLOUD ? CLOUD_DOCS : 'https://cryptopayment.dev/docs') . '/' . $setting['help'] . '" target="_blank" class="crpay-icon-help"></a>' : '') . '</p></div><div class="crpay-setting-input">';
        switch ($type) {
            case 'color':
            case 'text':
                $code .= '<input type="text">';
                break;
            case 'password':
                $code .= '<input type="password">';
                break;
            case 'textarea':
                $code .= '<textarea></textarea>';
                break;
            case 'select':
                $values = $setting['value'];
                $code .= '<select>';
                for ($i = 0; $i < count($values); $i++) {
                    $code .= '<option value="' . $values[$i][0] . '">' . crpay_isset($translations, $values[$i][1], $values[$i][1]) . '</option>';
                }
                $code .= '</select>';
                break;
            case 'checkbox':
                $code .= '<input type="checkbox">';
                break;
            case 'number':
                $code .= '<input type="number">';
                break;
            case 'multi-input':
                $values = $setting['value'];
                for ($i = 0; $i < count($values); $i++) {
                    $sub_type = $values[$i]['type'];
                    $sub_title = $values[$i]['title'];
                    $code .= '<div id="' . $values[$i]['id'] . '" data-type="' . $sub_type . '"><span>' . crpay_isset($translations, $sub_title, $sub_title) . (isset($values[$i]['label']) ? '<span class="crpay-label">' . $values[$i]['label'] . '</span>' : '') . '</span>';
                    switch ($sub_type) {
                        case 'color':
                        case 'text':
                            $code .= '<input type="text">';
                            break;
                        case 'password':
                            $code .= '<input type="password">';
                            break;
                        case 'number':
                            $code .= '<input type="number">';
                            break;
                        case 'textarea':
                            $code .= '<textarea></textarea>';
                            break;
                        case 'checkbox':
                            $code .= '<input type="checkbox">';
                            break;
                        case 'select':
                            $code .= '<select>';
                            $items = $values[$i]['value'];
                            for ($j = 0; $j < count($items); $j++) {
                                $code .= '<option value="' . $items[$j][0] . '">' . crpay_isset($translations, $items[$j][1], $items[$j][1]) . '</option>';
                            }
                            $code .= '</select>';
                            break;
                        case 'button':
                            $code .= '<a class="crpay-btn" href="' . $values[$i]['button-url'] . '">' . crpay_isset($translations, $values[$i]['button-text'], $values[$i]['button-text']) . '</a>';
                            break;
                    }
                    $code .= '</div>';
                }
                if (isset($setting['repeater'])) {
                    $code .= '<div class="crpay-btn crpay-btn-repater" data-index="2">' . crpay_isset($translations, $setting['repeater_button'], $setting['repeater_button']) . '</div>';
                }
                break;
        }
        return $code . '</div></div>';
    }
    return '';
}

function crpay_settings_save($settings) {
    $settings = json_decode($settings, true);
    $settings_old = crpay_settings_get_all();
    if (!$settings) {
        return false;
    }
    $encryption = ['btc-wallet-key', 'eth-wallet-key', 'ln-macaroon'];
    for ($i = 0; $i < count($encryption); $i++) {
        $key = $encryption[$i];
        if (!empty($settings[$key])) {
            $settings[$key] = $settings[$key] == '********' ? $settings_old[$key] : crpay_encryption($settings[$encryption[$i]]);
        }
    }

    $settings = str_replace(['"false"', '"true"'], ['false', 'true'], json_encode($settings, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE));
    if (json_last_error() != JSON_ERROR_NONE || !$settings) {
        return json_last_error();
    }
    $settings = crpay_encryption($settings);
    return crpay_db_query('INSERT INTO crpay_settings (name, value) VALUES (\'settings\', \'' . $settings . '\') ON DUPLICATE KEY UPDATE value = \'' . $settings . '\'');
}

function crpay_settings_get($id, $default = false) {
    global $CRPAY_SETTINGS;
    if (!$CRPAY_SETTINGS) {
        $CRPAY_SETTINGS = crpay_settings_get_all();
    }
    return crpay_isset($CRPAY_SETTINGS, $id, $default);
}

function crpay_settings_get_all() {
    global $CRPAY_SETTINGS;
    if (!$CRPAY_SETTINGS) {
        $CRPAY_SETTINGS = crpay_settings_db('settings');
        if ($CRPAY_SETTINGS) {
            if (substr($CRPAY_SETTINGS, 0, 1) !== '{') {
                $CRPAY_SETTINGS = crpay_encryption($CRPAY_SETTINGS, false); // temp, the if check will be removed soon
            }
            $CRPAY_SETTINGS = json_decode($CRPAY_SETTINGS, true);
        } else {
            $CRPAY_SETTINGS = [];
        }
    }
    return $CRPAY_SETTINGS;
}

function crpay_settings_get_repeater($ids) {
    $index = 1;
    $repeater_items = [];
    $count = count($ids);
    while ($index) {
        $suffix = $index > 1 ? '-' . $index : '';
        $repeater_item = [crpay_settings_get($ids[0] . $suffix)];
        if ($repeater_item[0]) {
            for ($i = 1; $i < $count; $i++) {
                array_push($repeater_item, crpay_settings_get($ids[$i] . $suffix));
            }
            array_push($repeater_items, $repeater_item);
            $index++;
        } else {
            $index = false;
        }
    }
    return $repeater_items;
}

function crpay_settings_js_admin() {
    global $CRPAY_APPS;
    $language = crpay_language(true);
    $code = 'var CRPAY_LANG = `' . $language . '`; ' .
        'var CRPAY_AJAX_URL = `' . CRPAY_URL . '/vendor/cryptd/ajax.php`; ' .
        'var CRPAY_TRANSLATIONS = ' . ($language ? file_get_contents(__DIR__ . '/../../resources/languages/admin/' . $language . '.json') : '{}') . '; ' .
        'var CRPAY_CURRENCY = `' . crpay_settings_get('currency', 'USD') . '`; ' .
        'var CRPAY_URL = `' . CRPAY_URL . '`; ' .
        'var CRPAY_ADMIN = true; ' .
        'var CRPAY_ADDRESS = {' .
        ' btc: `' . crpay_settings_get_address('btc') . '`, ' .
        ' eth: `' . crpay_settings_get_address('eth') . '`, ' .
        ' doge: `' . crpay_settings_get_address('doge') . '`, ' .
        ' algo: `' . crpay_settings_get_address('algo') . '`, ' .
        ' link: `' . crpay_settings_get_address('link') . '`, ' .
        ' usdt: `' . crpay_settings_get_address('usdt') . '`, ' .
        ' usdt_tron: `' . crpay_settings_get_address('usdt_tron') . '`, ' .
        ' usdt_bsc: `' . crpay_settings_get_address('usdt_bsc') . '`, ' .
        ' usdc: `' . crpay_settings_get_address('usdc') . '`, ' .
        ' bat: `' . crpay_settings_get_address('bat') . '`, ' .
        ' shib: `' . crpay_settings_get_address('shib') . '`, ' .
        ' bnb: `' . crpay_settings_get_address('bnb') . '`, ' .
        ' busd: `' . crpay_settings_get_address('busd') . '`, ' .
        ' ltc: `' . crpay_settings_get_address('ltc') . '`, ' .
        ' bch: `' . crpay_settings_get_address('bch') . '`, ' .
        ' xrp: `' . crpay_settings_get_address('xrp') . '`, ' .
        ' sol: `' . crpay_settings_get_address('sol') . '` ' .
        '};';




    $refunds = [];
    $settings = [
        'apps' => [],
        'cryptocurrencies' => crpay_get_cryptocurrency_codes(),
        'invoice' => crpay_settings_get('invoice-active'),
        'max_file_size' => crpay_server_max_file_size(),
        'url_rewrite_checkout' => crpay_settings_get('url-rewrite-checkout'),
        'url_rewrite_invoice' => crpay_settings_get('url-rewrite-invoice'),
        'testnet_btc' => crpay_settings_get('testnet-btc'),
        'testnet_eth' => crpay_settings_get('testnet-eth'),
    ];
    for ($i = 0; $i < count($CRPAY_APPS); $i++) {
        if (defined('CRPAY_' . strtoupper($CRPAY_APPS[$i]))) {
            array_push($settings['apps'], $CRPAY_APPS[$i]);
        }
    }
    if (crpay_settings_get('stripe-active') && strpos(crpay_settings_get('stripe-key'), '_test_')) {
        $settings['stripe_test_mode'] = true;
    }
    if (crpay_settings_get('coinbase-refunds')) {
        array_push($refunds, 'coinbase');
    }
    if (crpay_settings_get('btc-node-refunds')) {
        array_push($refunds, 'btc');
    }
    if (crpay_settings_get('eth-node-refunds')) {
        array_push($refunds, 'eth');
    }
    if (!CRPAY_CLOUD) {
        $code .= 'var CRPAY_CLOUD = false;';
    }
    $code .= 'var CRPAY_REFUNDS = ' . json_encode($refunds) . ';';
    $code .= 'var CRPAY_ADMIN_SETTINGS = `' . json_encode($settings, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . '`;';

    return $code;
}

function crpay_settings_db($name, $value = false, $default = false) {
    if ($value === false) {
        return crpay_isset(crpay_db_get('SELECT value FROM crpay_settings WHERE name = "' . crpay_db_escape($name) . '"'), 'value', $default);
    }
    if (is_string($value) || is_numeric($value)) {
        $value = crpay_db_escape($value);
    } else {
        $value = crpay_db_json_escape($value);
        if (json_last_error() != JSON_ERROR_NONE || !$value) {
            return json_last_error();
        }
    }
    return crpay_db_query('INSERT INTO crpay_settings (name, value) VALUES (\'' . crpay_db_escape($name) . '\', \'' . $value . '\') ON DUPLICATE KEY UPDATE value = \'' . $value . '\'');
}

function crpay_settings_get_address($cryptocurrency_code, $single = true) {
    $custom_tokens = crpay_get_custom_tokens();
    $address = $custom_tokens && isset($custom_tokens[$cryptocurrency_code]) ? $custom_tokens[$cryptocurrency_code]['address'] : crpay_settings_get('address-' . $cryptocurrency_code);
    $addresses = explode(',', str_replace(' ', '', preg_replace('/\s+/', '', $address)));
    return $addresses ? ($single ? $addresses[0] : $addresses) : false;
}

function crpay_settings_get_confirmations($cryptocurrency_code, $transaction_value = false) {
    $confirmations = crpay_settings_get('confirmations-' . $cryptocurrency_code);
    $confirmations = $confirmations ? $confirmations : crpay_settings_get('confirmations', 3);
    $threshold = crpay_settings_get('confirmations-increase-threshold');
    if ($transaction_value && $threshold && $transaction_value >= $threshold) {
        $confirmations = intval(intval($confirmations) * crpay_settings_get('confirmations-increase-percentage'));
    }
    return $confirmations;
}

/*
 * -----------------------------------------------------------
 * # LANGUAGE
 * -----------------------------------------------------------
 *
 * 1. Initialize the translations
 * 2. Get the active language
 * 3. Return the translation of a string
 * 4. Echo the translation of a string
 *
 */

function crpay_init_translations() {
    global $CRPAY_TRANSLATIONS;
    global $CRPAY_LANGUAGE;
    if (!empty($CRPAY_LANGUAGE) && $CRPAY_LANGUAGE[0] != 'en') {
        $path = __DIR__ . '/../../resources/languages/' . $CRPAY_LANGUAGE[1] . '/' . $CRPAY_LANGUAGE[0] . '.json';
        if (file_exists($path)) {
            $CRPAY_TRANSLATIONS = json_decode(file_get_contents($path), true);
        } else {
            $CRPAY_TRANSLATIONS = false;
        }
    } else if (!isset($CRPAY_LANGUAGE)) {
        $CRPAY_LANGUAGE = false;
        $CRPAY_TRANSLATIONS = false;
        $admin = crpay_verify_admin();
        $language = crpay_language($admin);
        $area = $admin ? 'admin' : 'client';
        if ($language) {
            $path = __DIR__ . '/../../resources/languages/' . $area . '/' . $language . '.json';
            if (file_exists($path)) {
                $CRPAY_TRANSLATIONS = json_decode(file_get_contents($path), true);
                $CRPAY_LANGUAGE = [$language, $area];
            } else {
                $CRPAY_TRANSLATIONS = false;
            }
        }
    }
    if ($CRPAY_LANGUAGE && $CRPAY_TRANSLATIONS && file_exists(__DIR__ . '/../../translations.json')) {
        $custom_translations = json_decode(file_get_contents(__DIR__ . '/../../translations.json'), true);
        if ($custom_translations && isset($custom_translations[$CRPAY_LANGUAGE[0]])) {
            $CRPAY_TRANSLATIONS = array_merge($CRPAY_TRANSLATIONS, $custom_translations[$CRPAY_LANGUAGE[0]]);
        }
    }
}

function crpay_language($admin = false) {
    $language = crpay_settings_get($admin ? 'language-admin' : 'language');
    if ($language == 'auto')
        $language = strtolower(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) : false);
    if (!$language)
        $language = crpay_isset($_POST, 'language');
    return $language == 'en' ? false : $language;
}

function crpay_($string) {
    global $CRPAY_TRANSLATIONS;
    if (!isset($CRPAY_TRANSLATIONS)) {
        crpay_init_translations();
    }
    return empty($CRPAY_TRANSLATIONS[$string]) ? $string : $CRPAY_TRANSLATIONS[$string];
}

function crpay_e($string) {
    echo crpay_($string);
}

function crpay_m($string, $language_code) {
    global $CRPAY_TRANSLATIONS_2;
    if (!$language_code) {
        return $string;
    }
    if (!isset($CRPAY_TRANSLATIONS_2)) {
        $path = __DIR__ . '/../../resources/languages/client/' . $language_code . '.json';
        if (file_exists($path)) {
            $CRPAY_TRANSLATIONS_2 = json_decode(file_get_contents($path), true);
        }
    }
    return empty($CRPAY_TRANSLATIONS_2[$string]) ? $string : $CRPAY_TRANSLATIONS_2[$string];
}

/*
 * -----------------------------------------------------------
 * DATABASE
 * -----------------------------------------------------------
 *
 * 1. Connection to the database
 * 2. Get database values
 * 3. Insert or update database values
 * 4. Escape and sanatize values prior to databse insertion
 * 5. Escape a JSON string prior to databse insertion
 * 6. Set default database environment settings
 *
 */

 function crpay_db_connect() {
    global $CRPAY_CONNECTION;

    // Validate required constants
    $required_constants = ['CRPAY_DB_HOST', 'CRPAY_DB_USER', 'CRPAY_DB_PASSWORD', 'CRPAY_DB_NAME'];

    foreach ($required_constants as $constant) {
        if (!defined($constant) || empty(constant($constant))) {
            return false;
        }
    }

    // Use default port if not defined or invalid
    $port = defined('CRPAY_DB_PORT') && is_numeric(CRPAY_DB_PORT) && CRPAY_DB_PORT > 0
        ? (int) CRPAY_DB_PORT
        : (int) ini_get('mysqli.default_port');

    // Use connection if already established
    if ($CRPAY_CONNECTION instanceof mysqli && !$CRPAY_CONNECTION->connect_error) {
        crpay_db_init_settings();
        return true;
    }


    $CRPAY_CONNECTION = @new mysqli(
        constant('CRPAY_DB_HOST'),
        constant('CRPAY_DB_USER'),
        constant('CRPAY_DB_PASSWORD'),
        constant('CRPAY_DB_NAME'),
        $port
    );

    if ($CRPAY_CONNECTION->connect_error) {
        $CRPAY_CONNECTION = null; 
        return false;
    }

    if (!$CRPAY_CONNECTION->set_charset('utf8mb4')) {
        $CRPAY_CONNECTION->close();
        $CRPAY_CONNECTION = null;
        return false;
    }

    crpay_db_init_settings();
    return true;
}

function crpay_db_get($query, $single = true) {
    global $CRPAY_CONNECTION;
    
    // Ensure the connection is valid
    $status = crpay_db_connect();
    
    // Default return value
    $value = ($single ? [] : []);

    // Return an empty array if the connection fails
    if (!$status || !$CRPAY_CONNECTION) {
        return [];
    }

    try {
        // Execute query
        $result = $CRPAY_CONNECTION->query($query);

        // If query execution fails, return an empty array
        if (!$result) {
            return [];
        }

        // Process the result
        if ($result->num_rows > 0) {
            while ($row = $result->fetch_assoc()) {
                if ($single) {
                    $value = $row;
                    break; // Stop after the first result if $single is true
                } else {
                    $value[] = $row;
                }
            }
        }
    } catch (mysqli_sql_exception $e) {
        // Catch missing table error and return empty array
        if (strpos($e->getMessage(), "doesn't exist") !== false) {
            return [];
        }
        return []; // Return empty array for any other SQL errors
    }

    return $value;
}


function crpay_db_query($query, $return = false) {
    global $CRPAY_CONNECTION;
    
    $status = crpay_db_connect();
    if (!$status) {
        return false; 
    }

    try {
        $result = $CRPAY_CONNECTION->query($query);
        if ($result) {
            if ($return) {
                return ($CRPAY_CONNECTION->insert_id > 0) ? $CRPAY_CONNECTION->insert_id : true;
            }
            return true;
        } else {
            return true; // Query failed but no fatal error
        }
    } catch (mysqli_sql_exception $e) {
        return true; // Return false on SQL exception (e.g., missing table)
    }
}

function crpay_db_escape($value, $numeric = -1) {
    if (is_numeric($value)) {
        return $value;
    } else if ($numeric === true) {
        return false;
    }
    if ($value === false) {
        return false;
    }
    global $CRPAY_CONNECTION;

    if (crpay_db_connect() && $CRPAY_CONNECTION) {
        $value = $CRPAY_CONNECTION->real_escape_string($value);
    }
    $value = crpay_sanatize_string($value);
    $value = str_replace('&amp;', '&', $value);
    return $value;
}

function crpay_db_json_escape($array) {
    global $CRPAY_CONNECTION;
    $value = str_replace(['"false"', '"true"'], ['false', 'true'], json_encode($array, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE));
    $value = crpay_sanatize_string($value, false);
	$status = crpay_db_connect();
    if (!$status) {
        return false; 
    }
    return $CRPAY_CONNECTION ? $CRPAY_CONNECTION->real_escape_string($value) : $value;
}

function crpay_db_check_connection($name = false, $user = false, $password = false, $host = false, $port = false) {
    global $CRPAY_CONNECTION;

    $response = true;

    // Check if MySQLi class exists
    if (!class_exists('mysqli')) {
        return false;
    }


    if ($name === false && defined('CRPAY_DB_NAME')) {
        $name = CRPAY_DB_NAME;
        $user = CRPAY_DB_USER;
        $password = CRPAY_DB_PASSWORD;
        $host = CRPAY_DB_HOST;
        $port = defined('CRPAY_DB_PORT') && CRPAY_DB_PORT ? intval(CRPAY_DB_PORT) : false;
    }

	if (!$name || !$user || !$host || !$password) {
		return false;
	}
	
	$final_port = ($port !== false && is_numeric($port)) ? (int)$port : null;

    try {
        set_error_handler(function () { }, E_ALL);
        $CRPAY_CONNECTION = new mysqli($host, $user, $password, $name, $final_port);
        restore_error_handler();
        
        // Check for connection error
        if ($CRPAY_CONNECTION->connect_error) {
            return false;
        }
    } catch (Exception $e) {
        return false;
    }

    return $response;
}


function crpay_db_init_settings() {
    global $CRPAY_CONNECTION;
    $CRPAY_CONNECTION->set_charset('utf8mb4');
    $CRPAY_CONNECTION->query("SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''))");
}

/*
 * -----------------------------------------------------------
 * MISCELLANEOUS
 * -----------------------------------------------------------
 *
 * 1. Encryption
 * 2. Check if a key is set and return it
 * 3. Update or create config file
 * 4. Installation
 * 5. Check if database connection is working
 * 6. Curl
 * 7. Cron jobs
 * 8. Scientific number to decimal number
 * 9. Get array value by path
 * 10. Updates
 * 11. Check if demo URL
 * 12. Check if RTL
 * 13. Debug
 * 14. CSV
 * 15. Apply admin colors
 * 16. Admin JS
 * 18. Load the custom .js and .css files
 * 19. Generate the payment redirect URL
 * 19. Apply version updates
 * 20. Error reporting
 * 21. Env check
 * 22. Check if address generation or not
 * 23. Vbox
 * 24. Email
 * 25. Email notifications
 * 26. Check if ETH address generation
 * 27. Get Tron contract addresses
 * 28. Get Binance contract addresses
 * 29. Get the user IP
 * 30. Slug string
 * 31. Returns the max file size for uploading
 * 32. Delete a file
 * 33. Generate a random string
 * 34. Convert text syntax to HTML tags
 * 35. Return the rewritten URL
 * 36. Sanatize a string
 * 37. Sanatize a file name
 * 38. 2FA
 *
 */

function crpay_encryption($string, $encrypt = true) {
    $output = false;
    $encrypt_method = 'AES-256-CBC';
    $secret_key = CRPAY_PASSWORD . CRPAY_USER;
    $key = hash('sha256', $secret_key);
    $iv = substr(hash('sha256', CRPAY_PASSWORD), 0, 16);
    if ($encrypt) {
        $output = openssl_encrypt(is_string($string) ? $string : json_encode($string, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE), $encrypt_method, $key, 0, $iv);
        $output = base64_encode($output);
        if (substr($output, -1) == '=')
            $output = substr($output, 0, -1);
    } else {
        $output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
    }
    return $output;
}

function crpay_isset($array, $key, $default = false) {
    return !empty($array) && isset($array[$key]) && $array[$key] !== '' ? $array[$key] : $default;
}

function crpay_config($content) {
    $file = fopen(__DIR__ . '/config.php', 'w');
    fwrite($file, $content);
    fclose($file);
    return true;
}

function crpay_installation($data) {
    if (!defined('CRPAY_USER') || !defined('CRPAY_DB_HOST')) {
		
        if (is_string($data)) {
            $data = json_decode($data, true);
        }

		$final_port = ($data['db-port'] !== false && is_numeric($data['db-port'])) ? (int)$data['db-port'] : null;

        $connection_check = crpay_db_check_connection($data['db-name'], $data['db-user'], $data['db-password'], $data['db-host'], $data['db-port']);
        if ($connection_check === true) {

			// Create the config.php file
			$code = '<?php' . PHP_EOL;

			if (empty($data['db-host'])) {
				$data['db-host'] = 'localhost';
			}

			if (empty($data['db-port'])) {
				$data['db-port'] = null;
			}

			// Hash password if not already hashed
			if (!empty($data['password']) && !password_get_info($data['password'])['algo']) {
				$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
			}

			unset($data['password-check']);

			foreach ($data as $key => $value) {
				// Generate constant name
				$constantName = 'CRPAY_' . str_replace('-', '_', strtoupper($key));

				// Check if the value is empty and define it as an empty string if needed
				$value = ($value !== false && !empty($value)) ? str_replace("'", "\\'", $value) : '';

				// Check if constant is already defined before defining it
				$code .= "if (!defined('$constantName')) {" . PHP_EOL;
				$code .= "    define('$constantName', '$value');" . PHP_EOL;
				$code .= "}" . PHP_EOL;
			}

			$filePath = __DIR__ . '/config.php';
			$file = fopen($filePath, 'w');
			fwrite($file, $code . '?>');
			fclose($file);
    		// Ensure MySQLi is available
    		if (!class_exists('mysqli')) {
    			return false;
    		}

			try {

    			// Create the database tables
                $connection = new mysqli($data['db-host'], $data['db-user'], $data['db-password'], $data['db-name'], $data['db-port']);
                $connection->set_charset('utf8mb4');
                $connection->query('CREATE TABLE IF NOT EXISTS crpay_checkouts (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(191), description TEXT, price VARCHAR(100) NOT NULL, currency VARCHAR(10) NOT NULL, type VARCHAR(1), redirect VARCHAR(191), hide_title TINYINT, external_reference VARCHAR(1000) NOT NULL DEFAULT "", creation_time DATETIME NOT NULL, slug VARCHAR(191), PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
                $connection->query('CREATE TABLE IF NOT EXISTS crpay_transactions (id INT NOT NULL AUTO_INCREMENT, `from` VARCHAR(191) NOT NULL DEFAULT "", `to` VARCHAR(191), hash VARCHAR(191) NOT NULL DEFAULT "", `title` VARCHAR(500) NOT NULL DEFAULT "", description VARCHAR(1000) NOT NULL DEFAULT "", amount VARCHAR(100) NOT NULL, amount_fiat VARCHAR(100) NOT NULL, cryptocurrency VARCHAR(10) NOT NULL, currency VARCHAR(10) NOT NULL, external_reference VARCHAR(1000) NOT NULL DEFAULT "", creation_time DATETIME NOT NULL, status VARCHAR(1) NOT NULL, webhook TINYINT NOT NULL, billing TINYTEXT, vat FLOAT, vat_details TINYTEXT, checkout_id INT, type TINYINT, PRIMARY KEY (id), FOREIGN KEY (checkout_id) REFERENCES crpay_checkouts(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
                $connection->query('CREATE TABLE IF NOT EXISTS crpay_settings (name VARCHAR(191) NOT NULL, value LONGTEXT, PRIMARY KEY (name)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
                $connection->query('CREATE TABLE IF NOT EXISTS crpay_customers (id INT NOT NULL AUTO_INCREMENT, first_name VARCHAR(191) NOT NULL DEFAULT "", last_name VARCHAR(191) NOT NULL DEFAULT "", email VARCHAR(191), phone VARCHAR(191) NOT NULL DEFAULT "", country VARCHAR(191) NOT NULL DEFAULT "", country_code VARCHAR(3) NOT NULL DEFAULT "", creation_time DATETIME NOT NULL, extra TINYTEXT, PRIMARY KEY (id), UNIQUE (email)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
                return true;
			} catch (Exception $e) {

				return false;
			}

        }
        return $connection_check;
    }
    return false;
}

function crpay_curl($url, $post_fields = '', $header = [], $type = 'GET', $timeout = false) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'CRYPTOPAYMENT');
    switch ($type) {
        case 'DELETE':
        case 'PUT':
        case 'POST':
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, is_string($post_fields) ? $post_fields : http_build_query($post_fields));
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : 7);
            if ($type != 'POST') {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
            }
            break;
        case 'GET':
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : 7);
            curl_setopt($ch, CURLOPT_HEADER, false);
            break;
        case 'DOWNLOAD':
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : 70);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            break;
        case 'FILE':
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : 400);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            if (strpos($url, '?')) {
                $url = substr($url, 0, strpos($url, '?'));
            }
            $path_part = 'uploads' . crpay_cloud_path_part() . '/' . basename($url);
            $file = fopen(__DIR__ . '/' . $path_part, 'wb');
            curl_setopt($ch, CURLOPT_FILE, $file);
            break;
    }
    if (!empty($header)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        if (crpay_isset($header, 'CURLOPT_USERPWD')) {
            curl_setopt($ch, CURLOPT_USERPWD, $header['CURLOPT_USERPWD']);
        }
    }
    $response = curl_exec($ch);
    if (curl_errno($ch) > 0) {
        $error = curl_error($ch);
        curl_close($ch);
        return $error;
    }
    curl_close($ch);
    if ($type == 'FILE' && $response === true) {
        return [__DIR__ . '/' . $path_part, CRPAY_URL . $path_part];
    }
    return $response;
}

function crpay_download($url) {
    return crpay_curl($url, '', '', 'DOWNLOAD');
}

function crpay_cron() {

    // Updates
    if (!CRPAY_CLOUD && crpay_settings_get('update-auto')) {
        crpay_update($_POST['domain']);
        crpay_version_updates();
    }

    // Invoice deletion
    if (!CRPAY_CLOUD && crpay_settings_get('invoice-active')) {
        $path = __DIR__ . '/uploads/';
        $files = scandir($path);
        if ($files) {
            $expiration = strtotime('-1 days');
            for ($i = 0; $i < count($files); $i++) {
                $file = $files[$i];
                if (strpos($file, 'inv-') === 0 && (filemtime($path . $file) < $expiration)) {
                    unlink($path . '/' . $file);
                }
            }
        }
    }

    // Delete pending transactions
    crpay_transactions_delete_pending();

    // Check payment for pending transactions
    crpay_transactions_check_pending();
}

function crpay_decimal_number($number) {
    $number = rtrim(number_format($number, 10, '.', ''), '0');
    return substr($number, -1) == '.' ? substr($number, 0, -1) : $number;
}

function crpay_get_array_value_by_path($path, $array) {
    $path = str_replace(' ', '', $path);
    if (strpos($path, ',')) {
        $response = [];
        $paths = explode(',', $path);
        for ($i = 0; $i < count($paths); $i++) {
            array_push($response, crpay_get_array_value_by_path($paths[$i], $array));
        }
        return $response;
    }
    $path = explode('>', $path);
    for ($i = 0; $i < count($path); $i++) {
        $array = $array ? crpay_isset($array, $path[$i]) : false;
    }
    return $array;
}

function crpay_update($domain) {
    if (!class_exists('ZipArchive')) {
        return 'no-zip-archive';
    }
    $latest_versions = crpay_versions();
    $response_update = [];
    foreach ($latest_versions as $name => $version) {
        $addon = $name != 'cryptopayment';
        if ($addon && !defined('CRPAY_' . strtoupper($name))) {
            continue;
        }
        $cryptopayment_purchase_code = crpay_settings_get(($addon ? $name . '-' : '') . 'cryptopayment-purchase-code');
        if (!$cryptopayment_purchase_code) {
            return ($addon ? $name . '-' : '') . 'cryptopayment-purchase-code-not-found';
        }
        if ((!$addon && $version == CRPAY_VERSION) || ($name == 'exchange' && $version == CRPAY_EXCHANGE)) {
            $response_update[$name] = 'latest-version-installed';
            continue;
        }
        $response_json = crpay_download('https://cryptopayment.dev/sync/updates.php?key=' . trim($cryptopayment_purchase_code) . '&domain=' . $domain . '&app=' . $name);
        $response = json_decode($response_json, true);
        if (empty($response[$name])) {
            return $response_json;
        }
        $zip = crpay_download('https://cryptopayment.dev/sync/temp/' . $response[$name]);
        if ($zip) {
            $file_path = __DIR__ . '/cryptopayment.zip';
            file_put_contents($file_path, $zip);
            if (file_exists($file_path)) {
                $zip = new ZipArchive;
                if ($zip->open($file_path) === true) {
                    $zip->extractTo(__DIR__ . ($addon ? '/../../apps/' : ''));
                    $zip->close();
                    unlink($file_path);
                    $response_update[$name] = true;
                } else
                    return 'zip-error';
            } else
                return 'file-not-found';
        } else
            return 'download-error';
    }
    return $response_update;
}

function crpay_versions() {
    return json_decode(crpay_download('https://cryptopayment.dev/sync/versions.json'), true);
}

function crpay_is_demo($attributes = false) {
    $url = crpay_isset($_SERVER, 'HTTP_REFERER');
    if (strpos($url, 'demo=true')) {
        if ($attributes) {
            parse_str($url, $url);
            return $url;
        }
        return true;
    }
    return false;
}

function crpay_is_rtl($language) {
    return in_array($language, ['ar', 'he', 'ku', 'fa', 'ur']);
}

function crpay_debug($value) {
    $value = is_string($value) ? $value : json_encode($value);
    if (file_exists('debug.txt')) {
        $value = file_get_contents('debug.txt') . PHP_EOL . $value;
    }
    crpay_file(__DIR__ . '/debug.txt', $value);
}

function crpay_file($path, $content) {
    try {
        $file = fopen($path, 'w');
        fwrite($file, $content);
        fclose($file);
        return true;
    } catch (Exception $e) {
        return $e->getMessage();
    }
}

function crpay_csv($rows, $header, $filename) {
    // Generate filename with a random string
    $filename .= '-' . crpay_random_string() . '.csv';

    // Define the path
    $directory = __DIR__ . '/uploads' . crpay_cloud_path_part();
    $path_part = $directory . '/' . $filename;

    // Ensure the directory exists
    if (!is_dir($directory)) {
        if (!mkdir($directory, 0755, true) && !is_dir($directory)) {
            return false; // Failed to create directory
        }
    }

    // Open the file for writing
    $file = fopen($path_part, 'w');
    if (!$file) {
        return false; // Failed to open file
    }

    // Write header if provided
    if (!empty($header)) {
        fputcsv($file, $header);
    }

    // Write rows to CSV
    foreach ($rows as $row) {
        fputcsv($file, $row);
    }

    // Close file
    fclose($file);

    // Return the file URL
    return CRPAY_URL . '/uploads' . crpay_cloud_path_part() . '/' . $filename;
}


function crpay_colors_admin() {
    $color_1 = crpay_settings_get('color-admin-1');
    $color_2 = crpay_settings_get('color-admin-2');
    $code = '';
    if ($color_1) {
        $code = '.crpay-btn,.datepicker-cell.range-end:not(.selected), .datepicker-cell.range-start:not(.selected), .datepicker-cell.selected, .datepicker-cell.selected:hover,.crpay-select ul li:hover,.crpay-underline:hover:after { background-color: ' . $color_1 . '; }';
        $code .= '.crpay-nav>div:hover, .crpay-nav>div.crpay-active,.crpay-btn-icon:hover,.crpay-btn.crpay-btn-border:hover, .crpay-btn.crpay-btn-border:active,[data-type="multi-input"] .crpay-btn:hover { border-color: ' . $color_1 . ' !important; color: ' . $color_1 . '; }';
        $code .= '.crpay-link:hover, .crpay-link:active,.crpay-input input[type="checkbox"]:checked:before,.crpay-loading:before, [data-cryptopayment]:empty:before,.crpay-search input:focus+input+i,.crpay-select p:hover { color: ' . $color_1 . '; }';
        $code .= '.crpay-input input:focus, .crpay-input input.crpay-focus, .crpay-input select:focus, .crpay-input select.crpay-focus, .crpay-input textarea:focus, .crpay-input textarea.crpay-focus { border-color: ' . $color_1 . '; }';
        $code .= '.datepicker-cell.range,.crpay-btn-icon:hover,.crpay-input input:focus, .crpay-input input.crpay-focus, .crpay-input select:focus, .crpay-input select.crpay-focus, .crpay-input textarea:focus, .crpay-input textarea.crpay-focus,.crpay-table tr:hover td { background-color: rgb(105 105 105 / 5%); }';
        $code .= '.crpay-input input, .crpay-input select, .crpay-input textarea, .crpay-input input[type="checkbox"] { background-color: #fafafa; }';
    }
    if ($color_2) {
        $code .= '.crpay-btn:hover, .crpay-btn:active { background-color: ' . $color_2 . '; }';
    }
    if ($code) {
        echo '<style>' . $code  . '</style>';
    }
}

function crpay_load_custom_js_css() {
    $js = CRPAY_CLOUD ? false : crpay_settings_get('js-admin');
    $css = crpay_settings_get('css-admin');
    if ($js)
        echo '<script src="' . ($js) . '?v=' . (CRPAY_VERSION) . '"></script>';
    if ($css)
        echo '<link rel="stylesheet" href="' . ($css) . '?v=' . (CRPAY_VERSION) . '" media="all" />';
}

function crpay_payment_redirect_url($url, $client_reference_id, $encode = true) {
    parse_str(crpay_isset(parse_url($url), 'query'), $attributes);
    $url = str_replace(['payment_status=cancelled', isset($attributes['cc']) ? 'cc=' . $attributes['cc'] : '', isset($attributes['pay']) ? 'pay=' . $attributes['pay'] : ''], '', $url);
    $url = $url . (strpos($url, '?') ? '&' : '?') . 'cc=' . crpay_encryption(json_encode(['id' => $client_reference_id]));
    return $encode ? urlencode($url) : $url;
}

function crpay_version_updates() {
    if (crpay_settings_db('version') != CRPAY_VERSION) {
        try {
            // 11-23
            crpay_db_query('ALTER TABLE crpay_checkouts ADD COLUMN slug VARCHAR(191)');

            // 09-23
            crpay_db_query('CREATE TABLE IF NOT EXISTS crpay_customers (id INT NOT NULL AUTO_INCREMENT, first_name VARCHAR(191) NOT NULL DEFAULT "", last_name VARCHAR(191) NOT NULL DEFAULT "", email VARCHAR(191), phone VARCHAR(191) NOT NULL DEFAULT "", country VARCHAR(191) NOT NULL DEFAULT "", country_code VARCHAR(3) NOT NULL DEFAULT "", creation_time DATETIME NOT NULL, extra TINYTEXT, PRIMARY KEY (id), UNIQUE (email)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci');
            crpay_db_query('ALTER TABLE crpay_transactions ADD COLUMN type TINYINT');

            // 08-23
            crpay_db_query('ALTER TABLE crpay_transactions ADD COLUMN checkout_id INT, ADD CONSTRAINT FOREIGN KEY (checkout_id) REFERENCES crpay_checkouts(id) ON DELETE CASCADE');

        } catch (Exception $e) {
        }
        crpay_settings_db('version', CRPAY_VERSION);
    }
}

function crpay_error($message, $function_name, $force = false) {
    $message = 'Cryptopayment error [' . ($function_name) . ']: ' . (is_string($message) ? ($message) : json_encode($message));
    
    if ($force || crpay_isset($_GET, 'debug') || strpos(crpay_isset($_SERVER, 'HTTP_REFERER'), 'debug') !== false) {
        if (crpay_verify_admin()) {
            crpay_debug($message);
        }
        error_log($message);
    }
    
    return $message;
}

function crpay_is_address_generation($cryptocurrency_code = false) {
    return crpay_settings_get('gemini-address-generation') || crpay_settings_get('coinbase-address-generation') || (strtolower($cryptocurrency_code) === 'btc' && crpay_settings_get('btc-node-address-generation')) || crpay_is_eth_address_generation($cryptocurrency_code) || (crpay_settings_get('custom-explorer-active') && crpay_settings_get('custom-explorer-address')) || ($cryptocurrency_code && count(crpay_settings_get_address($cryptocurrency_code, false)) > 2);
}



function crpay_ve_box() {
    $main = !isset($_COOKIE['TR_' . 'VUU' . 'KMILO']) || !password_verify('YTYFUJG', $_COOKIE['TR_' . 'VUU' . 'K' . 'MILO']);
    $exchange = defined('CRP_EXCH' . 'ANGE') && (!isset($_COOKIE['EX_' . 'HH' . 'U' . 'V' . 'AR']) || !password_verify('EXC' . 'JKYU', $_COOKIE['EX_' . 'H' . 'H' . 'U' . 'V' . 'AR']));
    $shop = defined('CRP_S' . 'HOP') && (!isset($_COOKIE['SH_' . 'UI' . 'X' . 'U' . 'JJ']) || !password_verify('SHP' . 'UIOO', $_COOKIE['SH_' . 'U' . 'I' . 'X' . 'U' . 'JJ']));
    if ($main || $exchange || $shop) {
        echo '<' . 'scr' . 'ipt' . '>' . 'va' . 'r CRP' . 'EV = ' . ($main ? 'false' : ($exchange ? '"Exc' . 'hange"' : ($shop ? '"S' . 'hop"' : 'false'))) . ';<' . '/scri' . 'pt' . '>';
		$path = dirname(__DIR__, 2) . '/resources/epc.html';
		echo file_get_contents($path);       
        return false; // Return false to prevent admin interface from loading
    }
    return true; // Return true to allow admin interface to load
}

function crpay_ve($code, $domain, $app = false) {
    $app = strtolower($app);
    if ($code === 'au' . 'to') {
        $code = crpay_settings_get(($app ? $app . '-' : '') . 'env' . 'ato-' . 'purcha' . 'se-c' . 'ode');
    }

    if (empty($code)) {
        return [false, ''];
    }

    $result = crpay_api_check($code);

    $result = json_decode( $result,true);

    $response = 'Inv' . 'alid L' . 'ic' . 'ense K' . 'ey';
    if (isset($result['valid'])) {
        if( $result['valid'] == 1 ) {
            $seed = $app ? ($app === 'exchange' ? 'EXC' . 'JKYU' : 'SHPUI' . 'OO') : 'YTYF' . 'UJG';
            crpay_settings_update_one(($app ? $app . '-' : '') . 'env' . 'ato-pu' . 'rchase-code', $code);
            return [true, password_hash($seed, PASSWORD_DEFAULT)];
        } else {
           return [false, $response];
        }
    }

    return [false, $response];
}

function crpay_api_check($code){
    $url = 'https://pan'.'el.cryp'.'topayment.netw'.'ork/ver'.'ify.php'; 

    $data = [
        'type' => 'val' . 'idation',
        'key' => $code,
        'resource' => 'cryp' . 'topay', //Here you should enter the name of your resource. Preferably, by obfuscating the codes, this will prevent it from being changed. However, for added security, we place the resource's obligation in the already inserted key.
        'client' => '', //Leave this empty if you don't want the user to enter their account's secret key for more secure validation.
        'custom_ip' => crpay_get_client_ip(), 
        'custom_hwid' => crpay_get_serial_number(), 
        'version' => '1.0', 
    ];
    $jsonData = json_encode($data);

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);

    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: Bearer 1744080756cn0UyoSIfi0SOWxOWIBuv4LVXmDJFyMuac9A2T' 
    ]);

    $response = curl_exec($ch);
    return $response;
}
/**
 * Update a single setting key in the encrypted "settings" row.
 *
 * @param string $id      The setting key to update (e.g. 'color-admin-1')
 * @param mixed  $value   The new value. Pass null to remove the key.
 * @return bool|string    true on success, or an error string on failure.
 */
function crpay_settings_update_one($id, $value) {
    global $CRP_SETTINGS;

    // Load current settings (already decrypted as array)
    $settings = crpay_settings_get_all();
    if (!is_array($settings)) {
        $settings = [];
    }

    // Handle sensitive keys like in crpay_settings_save()
    $encryption = ['btc-wallet-key', 'eth-wallet-key', 'ln-macaroon'];
    $is_sensitive = in_array($id, $encryption, true);

    if ($value === null) {
        // Remove key
        if (isset($settings[$id])) {
            unset($settings[$id]);
        }
    } else if ($is_sensitive) {
        if ($value === '********') {
        } else {
            $settings[$id] = crpay_encryption($value);
        }
    } else {
        // Normal key
        $settings[$id] = $value;
    }

    $json = json_encode($settings, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE);
    if (json_last_error() !== JSON_ERROR_NONE || !$json) {
        return json_last_error_msg();
    }
    $json = str_replace(['"false"', '"true"'], ['false', 'true'], $json);

    $enc = crpay_encryption($json);
    $sql = 'INSERT INTO crpay_settings (name, value) VALUES (\'settings\', \'' . $enc .
           '\') ON DUPLICATE KEY UPDATE value = \'' . $enc . '\'';
    $res = crpay_db_query($sql);

    if ($res === true) {
        $CRP_SETTINGS = $settings;
    }

    return $res;
}
function crpay_get_serial_number() {
    $hostName = php_uname('n');
    $uniqueId = $hostName;
    return $uniqueId;
}
// This will get the IP of the logged in user. If it is a JAVA plugin or something running on the computer, it mostly knows how to get the IP of the machine and not the user.
function crpay_get_client_ip() {
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ipList[0]);
    } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    } else {
        return $_SERVER['REMOTE_ADDR'];
    }
}


function crpay_email_send($to, $subject, $body, $is_user = false) {
    $settings = CRPAY_CLOUD ? ['smtp-host' => CLOUD_SMTP_HOST, 'smtp-user' => CLOUD_SMTP_USERNAME, 'smtp-password' => CLOUD_SMTP_PASSWORD, 'smtp-from' => CLOUD_SMTP_SENDER, 'email-sender-name' => CLOUD_SMTP_SENDER_NAME, 'smtp-port' => CLOUD_SMTP_PORT] : ['smtp-host' => crpay_settings_get('smtp-host'), 'smtp-user' => crpay_settings_get('smtp-user'), 'smtp-password' => crpay_settings_get('smtp-password'), 'smtp-from' => crpay_settings_get('smtp-from'), 'email-sender-name' => crpay_settings_get('smtp-name'), 'smtp-port' => crpay_settings_get('smtp-port')];
    if (empty($to)) {
        return false;
    }
    if (!is_string($body)) {
        $code = file_get_contents(__DIR__ . '/resources/email.html');
        $code = str_replace('{message}', $body['message'], $code);
        $code = str_replace('{title}', isset($body['title']) ? '<h1 style="text-align:left;font-size: 25px;line-height: 40px;font-weight: 500;color: #283c49;text-decoration: none;">' . $body['title'] . '</h1>' : '', $code);
        $code = str_replace('{tagline}', isset($body['title']) ? $body['title'] : substr($body['message'], 0, 100), $code);
        $code = str_replace('{image}', CRPAY_CLOUD ? CLOUD_LOGO_PNG : (crpay_settings_get('logo-admin') ? crpay_settings_get('logo-url-png', crpay_settings_get('logo-url', CRPAY_URL . 'media/logo.png')) : CRPAY_URL . 'media/logo.png'), $code);
        $code = str_replace('{link}', CRPAY_CLOUD ? CLOUD_URL : ($is_user ? '#' : CRPAY_URL . '/vendor/cryptd/admin.php'), $code);
        $code = str_replace('{footer}', CRPAY_CLOUD ? CLOUD_EMAIL : crpay_settings_get('notifications-footer', ''), $code);
        $body = $code;
    } else {
        $body = nl2br(trim($body));
    }
    if ($settings['smtp-host']) {
        require_once __DIR__ . '/../../vendor/phpmailer/PHPMailerAutoload.php';
        $port = $settings['smtp-port'];
        $mail = new PHPMailer;
        $mail->CharSet = 'UTF-8';
        $mail->Encoding = 'base64';
        $mail->isSMTP();
        $mail->Host = $settings['smtp-host'];
        $mail->SMTPAuth = true;
        $mail->Username = $settings['smtp-user'];
        $mail->Password = $settings['smtp-password'];
        $mail->SMTPSecure = $port == 25 ? '' : ($port == 465 ? 'ssl' : 'tls');
        $mail->Port = $port;
        $mail->setFrom($settings['smtp-from'], crpay_isset($settings, 'email-sender-name', ''));
        $mail->isHTML(true);
        $mail->Subject = trim($subject);
        $mail->Body = $body;
        $mail->AltBody = $body;
        $mail->SMTPOptions = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]];
        if (strpos($to, ',') > 0) {
            $emails = explode(',', $to);
            for ($i = 0; $i < count($emails); $i++) {
                $mail->addAddress($emails[$i]);
            }
        } else {
            $mail->addAddress($to);
        }
        return $mail->send() ? true : crpay_error($mail->ErrorInfo, 'crpay_email_send');
    } else {
        return mail($to, $subject, $body);
    }
}

function crpay_email_notification($subject, $message, $to = false) {
    if (!$to) {
        $to = crpay_settings_get('notifications-email');
        if (!$to) {
            return crpay_error('Missing recipient email.', 'crpay_email_notification');
        }
    }
    return crpay_email_send($to, $subject, ['message' => $message]);
}

function crpay_is_eth_address_generation($cryptocurrency_code) {
    $cryptocurrency_code = strtolower($cryptocurrency_code);
    return in_array($cryptocurrency_code, crpay_get_cryptocurrency_codes('eth')) && crpay_settings_get('eth-node-address-generation');
}

function crpay_tron_get_contract_address($cryptocurrency_code) {
    return crpay_isset(['usdt' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'], strtolower($cryptocurrency_code));
}

function crpay_binance_get_contract_address($cryptocurrency_code) {
    return crpay_isset(['busd' => '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56', 'usdt' => '0x55d398326f99059ff775485246999027b3197955'], strtolower($cryptocurrency_code));
}

function crpay_ip_info($fields, $ip = false) {
    $ip = $ip ? $ip : (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && substr_count($_SERVER['HTTP_CF_CONNECTING_IP'], '.') == 3 ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER['REMOTE_ADDR']);
    return strlen($ip) > 6 ? json_decode(crpay_download('http://ip-api.com/json/' . $ip . '?fields=' . $fields), true) : false;
}

function crpay_string_slug($string, $action = 'slug') {
    $string = trim($string);
    if ($action == 'slug') {
        return strtolower(str_replace([' ', '\'', '"'], ['-', '', ''], $string));
    } else if ($action == 'string') {
        return ucfirst(strtolower(str_replace(['-', '_'], ' ', $string)));
    }
    return $string;
}

function crpay_server_max_file_size() {
    $size = ini_get('post_max_size');
    if (empty($size)) {
        return 9999;
    }
    $suffix = strtoupper(substr($size, -1));
    $size = substr($size, 0, -1);
    if ($size === 0) {
        return 9999;
    }
    switch ($suffix) {
        case 'P':
            $size /= 1024;
        case 'T':
            $size /= 1024;
        case 'G':
            $size /= 1024;
        case 'K':
            $iValue *= 1024;
            break;
    }
    return $size;
}

function crpay_file_delete($file_name, $folder = '') {
    $path = __DIR__ . '/uploads' . crpay_cloud_path_part() . '/' . $folder . $file_name;
    if (file_exists($path)) {
        return unlink($path);
    } else if (CRPAY_CLOUD && defined('CLOUD_AWS_S3')) {
        return crpay_cloud_aws_s3(crpay_cloud_aws_s3_url($file_name), 'DELETE');
    }
    return false;
}

function crpay_random_string() {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $characters_length = strlen($characters);
    $random_string = '';
    for ($i = 0; $i < 15; $i++) {
        $random_string .= $characters[rand(0, $characters_length - 1)];
    }
    return $random_string;
}

function crpay_render_text($text) {
    $text = trim(preg_replace('@(http)?(s)?(://)?(([a-zA-Z])([-\w]+\.)+([^\s\.]+[^\s]*)+[^,.\s])@', '<a href="$0" target="_blank" class="crpay-link">$0</a>', str_replace('&amp;', '&', crpay_($text))));
    $regex = [['/\*(.*?)\*/', '<b>', '</b>'], ['/__(.*?)__/', '<em>', '</em>'], ['/~(.*?)~/', '<del>', '</del>'], ['/`(.*?)`/', '<code>', '</code>']];
    for ($i = 0; $i < count($regex); $i++) {
        $values = [];
        if (preg_match_all($regex[$i][0], $text, $values, PREG_SET_ORDER)) {
            for ($j = 0; $j < count($values); $j++) {
                $text = str_replace($values[$j][0], $regex[$i][1] . $values[$j][1] . $regex[$i][2], $text);
            }
        }
    }
    return $text;
}

function crpay_get_url($type, $full_url = true) {
    $custom_domain = CRPAY_CLOUD ? crpay_settings_get('custom-domain') : false;
    $cloud = CRPAY_CLOUD && !$custom_domain ? '&cloud=' . crpay_cloud_get_data() : '';
    $url_part = $custom_domain ? str_replace('//' . $type, '/' . $type, $custom_domain . '/' . $type . '/') : CRPAY_URL . (CRPAY_CLOUD ? $type . '/' : (crpay_settings_get('url-rewrite-' . $type) ? crpay_settings_get('url-rewrite-' . $type) : '/vendor/cryptd/pay.php?' . ($type == 'checkout' ? 'checkout_id' : $type) . '='));
    return $full_url ? $url_part . $cloud : [$url_part, $cloud];
}

function crpay_sanatize_string($value, $is_htmlspecialchars = true) {
    $value = str_ireplace(['<script', '</script'], ['&lt;script', '&lt;/script'], $value);
    if ($is_htmlspecialchars) {
        $value = htmlspecialchars($value, ENT_NOQUOTES | ENT_SUBSTITUTE, 'utf-8');
    }
    return str_ireplace(['onload', 'javascript:', 'onclick', 'onerror', 'onmouseover', 'oncontextmenu', 'ondblclick', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseup', 'ontoggle'], '', $value);
}

function crpay_sanatize_file_name($value) {
    return htmlspecialchars(str_ireplace(['\\', '/', ':', '?', '"', '*', '<', '>', '|'], '', crpay_sanatize_string($value)), ENT_NOQUOTES | ENT_SUBSTITUTE, 'utf-8');
}

function crpay_2fa($pin = false) {
    $secret_code = crpay_settings_db('2fa');
    if ($pin) {
        if (!$secret_code) {
            return crpay_error('2FA secret code not found.', 'crpay_2fa');
        }
        $response = crpay_curl('https://www.authenticatorApi.com/Validate.aspx?Pin=' . $pin . '&SecretCode=' . $secret_code);
        return $response == 'True' ? true : $response;
    } else {
        if (!$secret_code) {
            $secret_code = crpay_random_string();
            crpay_settings_db('2fa', $secret_code);
        }
        return 'https://www.authenticatorApi.com/pair.aspx?AppName=' . (CRPAY_CLOUD ? CLOUD_NAME : 'Cryptopayment') . '&AppInfo=' . CRPAY_USER . '&SecretCode=' . $secret_code;
    }
}

/*
 * -----------------------------------------------------------
 * BLOCKCHAIN
 * -----------------------------------------------------------
 *
 */

function crpay_blockchain($cryptocurrency_code, $action, $extra = false, $address = false) {
    $services = [
        'btc' => [['https://mempool.space/api/', 'address/{R}', 'address/{R}/txs', 'tx/{R}', 'blocks/tip/height', 'mempool'], ['https://blockstream.info/api/', 'address/{R}', 'address/{R}/txs', 'tx/{R}', 'blocks/tip/height', 'blockstream'], ['https://blockchain.info/', 'q/addressbalance/{R}', 'rawaddr/{R}?limit=30', 'rawtx/{R}', 'q/getblockcount', 'blockchain'], 'blockdaemon'],
        'eth' => [['https://api.etherscan.io/api?', 'module=account&action=balance&address={R}', 'module=account&action=txlist&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', 'module=account&action=txlist&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', false, 'etherscan'], ['https://api.ethplorer.io/', 'getAddressInfo/{R}', 'getAddressTransactions/{R}?limit=99&showZeroValues=false', 'getTxInfo/{R}', 'getLastBlock', 'ethplorer'], ['https://blockscout.com/eth/mainnet/api?', 'module=account&action=balance&address={R}', 'module=account&action=txlist&address={R}', 'module=transaction&action=gettxinfo&txhash={R}', false, 'blockscout'], 'blockdaemon'],
        'xrp' => [['https://data.ripple.com/v2/', 'accounts/{R}/balances', 'accounts/{R}/transactions', 'transactions/{R}', false, 'ripple'], 'blockdaemon'],
        'doge' => ['blockcypher', 'blockdaemon'],
        'algo' => [['https://algoindexer.algoexplorerapi.io/v2/', 'accounts/{R}', 'accounts/{R}/transactions?limit=99', 'transactions/{R}', 'accounts/{R}', 'algoexplorerapi'], 'blockdaemon'],
        'bnb' => [['https://api.bscscan.com/api?', 'module=account&action=balance&address={R}', 'module=account&action=txlist&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', 'module=account&action=txlist&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', false, 'bscscan']],
        'ltc' => ['tatum', 'blockdaemon', 'blockcypher'],
        'bch' => [['https://rest1.biggestfan.net/v2/address/', 'details/{R}', 'transactions/{R}', 'transactions/{R}', false, 'biggestfan'], 'blockdaemon'],
        'trx' => [['https://apilist.tronscan.org/api/', 'account?address={R}', 'transaction?sort=-timestamp&count=true&limit=99&start=0&address={R}', 'transaction-info?hash={R}', false, 'tronscan'], 'tatum'],
        'sol' => ['blockdaemon']
    ];
    $services_testnet = [
        'btc' => [['https://mempool.space/testnet/api/', 'address/{R}', 'address/{R}/txs', 'tx/{R}', 'blocks/tip/height', 'mempool']],
        'eth' => [['https://api-sepolia.etherscan.io/api?', 'module=account&action=balance&address={R}', 'module=account&action=txlist&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', 'module=account&action=txlist&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', false, 'etherscan']],
    ];
    if (crpay_settings_get('testnet-' . crpay_crypto_get_network($cryptocurrency_code)) && isset($services_testnet[$cryptocurrency_code])) {
        $services = $services_testnet;
    }
    $address = $address ? $address : crpay_settings_get_address($cryptocurrency_code);
    $address_lowercase = strtolower($address);
    $cryptocurrency_code_base = crpay_crypto_get_base_code($cryptocurrency_code);
    $return_explorer = $action == 'transaction-explorer';
    if ($return_explorer) {
        $action = 'transaction';
    }

    // Tokens
    $custom_token_code = ['eth' => false, 'trx' => false, 'bsc' => false];
    $custom_token = crpay_isset(crpay_get_custom_tokens(), $cryptocurrency_code);
    $is_token = (in_array($cryptocurrency_code, crpay_get_cryptocurrency_codes('eth')) && $cryptocurrency_code != 'eth') || ($custom_token && $custom_token['type'] == 'erc-20') ? 'eth' : (in_array($cryptocurrency_code, ['usdt_tron']) ? 'trx' : (in_array($cryptocurrency_code, ['busd', 'usdt_bsc']) || $custom_token && $custom_token['type'] == 'bep-20' ? 'bsc' : false));
    if ($is_token) {
        switch ($is_token) {
            case 'eth':
                require_once(__DIR__ . '/web3.php');
                $services = [['https://api.etherscan.io/api?', 'module=account&action=tokenbalance&contractaddress={A}&address={R}&tag=latest', 'module=account&action=tokentx&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', 'module=account&action=tokentx&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', false, 'etherscan', 'module=account&action=tokentx&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc'], ['https://api.ethplorer.io/', 'getAddressInfo/{R}', 'getAddressHistory/{R}?limit=99&showZeroValues=false', 'getTxInfo/{R}', false, 'ethplorer', 'getAddressHistory/{R}?limit=99&showZeroValues=false'], ['https://blockscout.com/eth/mainnet/api?', 'module=account&action=tokenbalance&contractaddress={A}&address={R}', 'module=account&action=tokentx&address={R}&offset=99', 'module=account&action=tokentx&address={R}&offset=99', false, 'blockscout', 'module=account&action=tokenlist&address={R}']];
                $contract_address = crpay_eth_get_contract($cryptocurrency_code_base);
                $contract_address = $contract_address ? $contract_address[0] : false;
                break;
            case 'trx':
                $services = $services['trx'];
                $services[0][2] = 'contract/events?address={R}&start=0&limit=30';
                $contract_address = crpay_tron_get_contract_address($cryptocurrency_code_base);
                break;
            case 'bsc':
                $services = [['https://api.bscscan.com/api?', 'module=account&action=tokenbalance&contractaddress={A}&address={R}&tag=latest', 'module=account&action=tokentx&contractaddress={A}&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', 'module=account&action=tokentx&contractaddress={A}&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc', false, 'bscscan', 'module=account&action=tokentx&address={R}&startblock=0&endblock=99999999&offset=99&sort=asc']];
                $contract_address = crpay_binance_get_contract_address($cryptocurrency_code_base);
                break;
        }
        $custom_token_code[$is_token] = $cryptocurrency_code;
    } else {
        $services = crpay_isset($services, $cryptocurrency_code);
    }
    if ($custom_token) {
        $contract_address = crpay_isset($custom_token, 'contract_address', $contract_address);
    }

    $slugs = false;
    $transactions = [];
    $single_transaction = $action == 'transaction';
    $divider = 1;

    // Custom Blockchain explorer
    $custom_explorer = crpay_settings_get('custom-explorer-active') ? crpay_settings_get('custom-explorer-' . $action . '-url') : false;
    if ($custom_explorer) {
        $path = crpay_settings_get('custom-explorer-' . $action . '-path');
        $data = crpay_curl(str_replace(['{R}', '{N}', '{N2}'], [$single_transaction ? $extra : $address, $cryptocurrency_code, crpay_crypto_name($cryptocurrency_code)], $custom_explorer));
        $data = crpay_get_array_value_by_path($action == 'transactions' ? trim(explode(',', $path)[0]) : $path, json_decode($data, true));
        if ($data) {
            $custom_explorer_divider = 1;
            if (crpay_settings_get('custom-explorer-divider')) {
                $custom_explorer_divider = $cryptocurrency_code == 'eth' ? 1000000000000000000 : 100000000;
            }
            switch ($action) {
                case 'balance':
                    if (is_numeric($data)) {
                        return floatval($data) / $custom_explorer_divider;
                    }
                    break;
                case 'transaction':
                    if (is_array($data) && $data[0]) {
                        return ['time' => $data[0], 'address' => $data[1], 'value' => floatval($data[2]) / $custom_explorer_divider, 'confirmations' => $data[3], 'hash' => $data[4]];
                    }
                    break;
                case 'transactions':
                    if (is_array($data)) {
                        for ($i = 0; $i < count($data); $i++) {
                            $transaction = crpay_get_array_value_by_path($path, $data[$i]);
                            array_push($transactions, ['time' => $transaction[1], 'address' => $transaction[2], 'value' => floatval($transaction[3]) / $custom_explorer_divider, 'confirmations' => $transaction[4], 'hash' => $transaction[5]]);
                        }
                        return $transactions;
                    }
                    break;
            }
        }
    }

    // Multi Network Explorers
    $data_original = false;
    if (empty($services)) {
        return;
    }
    for ($i = 0; $i < count($services); $i++) {
        if (!$return_explorer) {
            if ($services[$i] === 'tatum') {
                $base_url = 'https://api.tatum.io/v3/' . crpay_crypto_get_network($cryptocurrency_code, 'full_name') . '/';
                $header = ['x-api-key: ' . (CRPAY_CLOUD ? TATUM_API_KEY : crpay_crypto_api_key('tatum'))];
                switch ($action) {
                    case 'balance':
                        if ($cryptocurrency_code == 'usdt_tron') {
                            $json = crpay_curl($base_url . 'account/' . $address);
                            $data = json_decode($json, true);
                            if ($is_token) {
                                $trc_20 = crpay_isset($data, 'trc20');
                                if ($trc_20 && count($trc_20) && isset($trc_20[0][$contract_address])) {
                                    return crpay_decimal_number($trc_20[0][$contract_address] / (10 ** crpay_crypto_get_decimals($cryptocurrency_code)));
                                }
                            } else if (isset($data['balance'])) {
                                return crpay_decimal_number($data['balance'] / (10 ** crpay_crypto_get_decimals($cryptocurrency_code)));
                            }
                        } else {
                            $json = crpay_curl($base_url . 'address/balance/' . $address);
                            $data = json_decode($json, true);
                            if (isset($data['incoming'])) {
                                return crpay_decimal_number($data['incoming'] - $data['outgoing']);
                            }
                        }
                        crpay_error($json, 'tatum');
                        continue 2;
                    case 'transactions':
                        if ($cryptocurrency_code == 'usdt_tron') {
                            $json = crpay_curl($base_url . 'transaction/account/' . $address . ($is_token ? '/trc20' : ''), '', $header);
                            $data = json_decode($json, true);
                            if (isset($data['transactions'])) {
                                $slugs = [false, 'from', 'value', false, 'txID', false];
                                $transactions = $data['transactions'];
                                $transactions_data = [];
                                for ($j = 0; $j < count($transactions); $j++) {
                                    $token_info = $transactions[$i]['tokenInfo'];
                                    if (strtolower($token_info['symbol']) == $cryptocurrency_code_base) {
                                        $divider = 10 ** $token_info['decimals'];
                                        array_push($transactions_data, $transactions[$j]);
                                    }
                                }
                                $data = $transactions_data;
                            } else {
                                crpay_error($json, 'tatum');
                                continue 2;
                            }
                        } else {
                            $json = crpay_curl($base_url . 'transaction/address/' . $address . '?pageSize=30', '', $header);
                            $data = json_decode($json, true);
                            if (is_array($data) && count($data) && isset($data[0]['inputs'])) {
                                $slugs = ['ts', 'from', 'value', false, 'hash', 'blockNumber'];
                                for ($j = 0; $j < count($data); $j++) {
                                    $data[$j]['address'] = $data[$j]['inputs'][0]['coin']['address'];
                                    $data[$j]['value'] = 0;
                                    $outputs = $data[$j]['outputs'];
                                    $total = 0;
                                    for ($y = 0; $y < count($outputs); $y++) {
                                        $value = $outputs[$y]['value'];
                                        $total += $value;
                                        if (strtolower($outputs[$y]['address']) == $address_lowercase) {
                                            $data[$j]['value'] = $value;
                                            break;
                                        }
                                    }
                                    if (!$data[$j]['value']) {
                                        $data[$j]['value'] = $total + $data[$j]['fee'];
                                    }
                                }
                            } else if (isset($data['errorCode'])) {
                                crpay_error($json, 'tatum');
                                continue 2;
                            }
                        }
                        break;
                    case 'transaction':
                        $json = crpay_curl($base_url . 'transaction/' . $extra, '', $header);
                        $data = json_decode($json, true);
                        if ($cryptocurrency_code == 'usdt_tron') {
                            if (isset($data['txID'])) {
                                $slugs = ['time', 'from', 'value', 'confirmations', 'txID', 'blockNumber'];
                                $raw = $data['rawData'];
                                $data['time'] = $raw['timestamp'];
                                $data['from'] = $raw['contract'][0]['parameter']['value']['ownerAddressBase58'];
                                $data['confirmations'] = crpay_isset(json_decode(crpay_curl($base_url . 'info', '', $header), true), 'blockNumber', $data['blockNumber']) - $data['blockNumber'];
                                $data['value'] = crpay_decimal_number(hexdec($data['log'][0]['data']) / (10 ** crpay_crypto_get_decimals($cryptocurrency_code)));
                                $data = [$data];
                            } else {
                                crpay_error($json, 'tatum');
                                continue 2;
                            }
                        } else {
                            if (isset($data['hash'])) {
                                $slugs = ['time', 'from', 'value', 'confirmations', 'hash', 'blockNumber'];
                                $inputs = crpay_isset($data, 'inputs', []);
                                $outputs = crpay_isset($data, 'outputs', []);
                                $data['address'] = count($inputs) ? $inputs[0]['coin']['address'] : '';
                                $data['value'] = 0;
                                $total = 0;
                                $data['confirmations'] = 0;
                                for ($y = 0; $y < count($outputs); $y++) {
                                    $value = $outputs[$y]['value'];
                                    $total += $value;
                                    if (strtolower($outputs[$y]['address']) == $address_lowercase) {
                                        $data['value'] = $value;
                                        break;
                                    }
                                }
                                if (!$data['value']) {
                                    $data['value'] = $total + $data['fee'];
                                }
                                if (!empty($data['blockNumber'])) {
                                    $blocks_count = json_decode(crpay_curl($base_url . 'info', '', $header), true);
                                    $data['confirmations'] = isset($blocks_count['blocks']) ? $blocks_count['blocks'] - $data['blockNumber'] + 1 : 0;
                                }
                                $data = [$data];
                            } else {
                                crpay_error($json, 'tatum');
                                continue 2;
                            }
                        }
                        break;
                }
            } else if ($services[$i] === 'blockdaemon') {
                $base_url = 'https://svc.blockdaemon.com/universal/v1/' . crpay_crypto_name($cryptocurrency_code) . '/mainnet/';
                $header = ['Content-Type: application/json', 'Authorization: Bearer ' . (CRPAY_CLOUD ? BLOCKDAEMON_API_KEY : crpay_crypto_api_key('blockdaemon'))];
                switch ($action) {
                    case 'balance':
                        $json = crpay_curl($base_url . 'account/' . $address, '', $header);
                        $data = json_decode($json, true);
                        if (is_array($data) && isset($data[0]['confirmed_balance'])) {
                            return crpay_decimal_number($data[0]['confirmed_balance'] / (10 ** $data[0]['currency']['decimals']));
                        }
                        crpay_error($json, 'blockdaemon');
                        continue 2;
                    case 'transactions':
                    case 'transaction':
                        $json = crpay_curl($base_url . ($single_transaction ? 'tx/' . $extra : 'account/' . $address . '/txs'), '', $header);
                        $data = json_decode($json, true);
                        $transaction_status = crpay_isset($data, 'status');
                        if ($data) {
                            if ($single_transaction) {
                                if (isset($data['events'])) {
                                    $data = [$data];
                                }
                            } else if (isset($data['data'])) {
                                $data = $data['data'];
                            }
                        }
                        if (is_array($data)) {
                            if (count($data) && isset($data[0]['events'])) {
                                $slugs = ['date', 'address', 'value', 'confirmations', 'id', 'block_number'];
                                for ($j = 0; $j < count($data); $j++) {
                                    $events = $data[$j]['events'];
                                    $transaction_value = 0;
                                    $sender_address = '';
                                    for ($y = 0; $y < count($events); $y++) {
                                        switch ($cryptocurrency_code) {
                                            case 'btc':
                                                if (!empty($events[$y]['meta']) && !empty($events[$y]['meta']['addresses'])) {
                                                    $event_address = $events[$y]['meta']['addresses'][0];
                                                    $amount = $events[$y]['amount'];
                                                    if ($events[$y]['type'] == 'utxo_output' && strtolower($event_address) == $address_lowercase) {
                                                        $transaction_value += $amount;
                                                        if (isset($events[$y]['decimals'])) {
                                                            $divider = 10 ** $events[$y]['decimals'];
                                                        }
                                                    } else if ($events[$y]['type'] == 'utxo_input') {
                                                        $sender_address = $event_address;
                                                    }
                                                }
                                                break;
                                            case 'sol':
                                            case 'xrp':
                                            case 'bch':
                                            case 'algo':
                                            case 'ltc':
                                            case 'doge':
                                            case 'eth':
                                                $get_address = false;
                                                if (strtolower(crpay_isset($events[$y], 'destination')) == $address_lowercase) {
                                                    $transaction_value += $events[$y]['amount'];
                                                    $get_address = true;
                                                    if (isset($events[$y]['decimals'])) {
                                                        $divider = 10 ** $events[$y]['decimals'];
                                                    }
                                                } else if (crpay_isset($events[$y], 'type') == 'utxo_input') {
                                                    $get_address = true;
                                                }
                                                if ($get_address && !empty($events[$y]['source'])) {
                                                    $sender_address = $events[$y]['source'];
                                                }
                                                break;
                                        }
                                    }
                                    if ($single_transaction && $cryptocurrency_code == 'sol' && $transaction_status == 'completed') {
                                        $data[$j]['confirmations'] = 9999;
                                    }
                                    $data[$j]['value'] = $transaction_value;
                                    $data[$j]['address'] = $sender_address;
                                }
                            }
                        } else {
                            crpay_error($json, 'blockdaemon');
                            continue 2;
                        }
                        break;
                }
            } else if ($services[$i] === 'blockcypher') {
                $base_url = 'https://api.blockcypher.com/v1/' . $cryptocurrency_code . '/main/';
                switch ($action) {
                    case 'balance':
                        $json = crpay_curl($base_url . 'addrs/' . $address);
                        $data = json_decode($json, true);
                        if ($data && isset($data['balance'])) {
                            return crpay_decimal_number($data['balance'] / (10 ** crpay_crypto_get_decimals($cryptocurrency_code)));
                        }
                        crpay_error($json, 'blockcypher');
                        continue 2;
                    case 'transactions':
                    case 'transaction':
                        $json = crpay_curl($base_url . ($single_transaction ? 'txs/' . $extra : 'addrs/' . $address . '/full'));
                        $data = json_decode($json, true);
                        if ($data) {
                            if ($single_transaction) {
                                if (isset($data['hash']))
                                    $data = [$data];
                            } else if (isset($data['txs']))
                                $data = $data['txs'];
                        }
                        if ($data && is_array($data)) {
                            if (count($data)) {
                                $slugs = ['time', 'address', 'value', 'confirmations', 'hash', 'block_height'];
                                $divider = 10 ** crpay_crypto_get_decimals($cryptocurrency_code_base);
                                for ($j = 0; $j < count($data); $j++) {
                                    $outputs = crpay_isset($data[$j], 'outputs', []);
                                    $data[$j]['time'] = strtotime($data[$j]['received']);
                                    $data[$j]['address'] = $data[$j]['inputs'][0]['addresses'][0];
                                    $data[$j]['value'] = 0;
                                    for ($y = 0; $y < count($outputs); $y++) {
                                        if (strtolower($outputs[$y]['addresses'][0]) == $address_lowercase) {
                                            $data[$j]['value'] = $outputs[$y]['value'];
                                            break;
                                        }
                                    }
                                }
                            }
                        } else {
                            crpay_error($json, 'blockcypher');
                            continue 2;
                        }
                        break;
                }
            }
        }

        // Other explorers
        if (!in_array($services[$i], ['tatum', 'blockdaemon', 'blockcypher'])) {
            $url_part = $services[$i][$action == 'balance' ? 1 : ($action == 'transactions' ? 2 : ($single_transaction ? 3 : 4))];
            if ($url_part === false) {
                continue;
            }
            $url = $services[$i][0] . str_replace('{R}', $single_transaction && !in_array($services[$i][5], ['etherscan', 'bscscan', 'biggestfan']) ? $extra : $address, $url_part);
            if ($is_token) {
                $url = str_replace('{A}', $contract_address, $url);
            }
            $data = $data_original = crpay_curl(crpay_crypto_api_key($services[$i][5], $url));
            switch ($cryptocurrency_code) {
                case 'btc':
                    switch ($action) {
                        case 'balance':
                            $data = json_decode($data, true);
                            switch ($i) {
                                case 0:
                                case 1:
                                    if (isset($data['chain_stats'])) {
                                        return ($data['chain_stats']['funded_txo_sum'] - $data['chain_stats']['spent_txo_sum']) / 100000000;
                                    }
                                    break;
                                case 2:
                                    if (is_numeric($data)) {
                                        return intval($data) / 100000000;
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            $data = json_decode($data, true);
                            $input_slug = false;
                            $output_slug = false;
                            $confirmations = false;
                            $continue = false;

                            // Get transaction and verify the API is working
                            switch ($i) {
                                case 0:
                                case 1:
                                    if (is_array($data) && empty($data['error'])) {
                                        $output_slug = 'vout';
                                        $input_slug = 'vin';
                                        $continue = true;
                                    }
                                    break;
                                case 2:
                                    if (($single_transaction && isset($data['inputs'])) || isset($data['txs'])) {
                                        if (!$single_transaction)
                                            $data = $data['txs'];
                                        $input_slug = 'inputs';
                                        $output_slug = 'out';
                                        $continue = true;
                                    }
                                    break;
                            }
                            if ($continue) {
                                $slugs = ['time', 'address', 'value', 'confirmations', 'hash', 'block_height'];
                                $sender_address = '';
                                $time = 0;
                                $block_height = 0;
                                $hash = '';
                                $divider = $i === 1 ? 1 : 100000000;
                                if ($single_transaction)
                                    $data = [$data];

                                // Get transactions details
                                for ($j = 0; $j < count($data); $j++) {
                                    $transaction_value = 0;
                                    switch ($i) {
                                        case 0:
                                        case 1:
                                            if (crpay_isset($data[$j]['status'], 'confirmed')) {
                                                $time = $data[$j]['status']['block_time'];
                                                $block_height = $data[$j]['status']['block_height'];
                                            }
                                            $hash = $data[$j]['txid'];
                                            break;
                                        case 2:
                                            $time = $data[$j]['time'];
                                            $block_height = $data[$j]['block_height'];
                                            $hash = $data[$j]['hash'];
                                            break;
                                    }

                                    // Get transaction amount
                                    $outputs = $output_slug ? $data[$j][$output_slug] : [];
                                    for ($y = 0; $y < count($outputs); $y++) {
                                        switch ($i) {
                                            case 0:
                                            case 1:
                                                $value = $outputs[$y]['value'];
                                                $output_address = crpay_isset($outputs[$y], 'scriptpubkey_address');
                                                break;
                                            case 2:
                                                $value = $outputs[$y]['value'];
                                                $output_address = $outputs[$y]['addr'];
                                                break;
                                        }
                                        if (strtolower($output_address) == $address_lowercase) {
                                            $transaction_value += $value;
                                        }
                                        $outputs[$y] = ['value' => $value, 'address' => $output_address];
                                    }

                                    // Get sender address
                                    $input = crpay_isset($data[$j], $input_slug);
                                    if ($input && count($input)) {
                                        $input = $input[0];
                                        switch ($i) {
                                            case 0:
                                            case 1:
                                                $sender_address = $input['prevout']['scriptpubkey_address'];
                                                break;
                                            case 2:
                                                $sender_address = $input['prev_out']['addr'];
                                                break;
                                        }
                                    }

                                    // Assign transaction values
                                    $data[$j]['time'] = $time;
                                    $data[$j]['address'] = $sender_address;
                                    $data[$j]['confirmations'] = $confirmations;
                                    $data[$j]['value'] = $transaction_value;
                                    $data[$j]['hash'] = $hash;
                                    $data[$j]['block_height'] = $block_height;
                                }
                            }
                            break;
                        case 'blocks_count':
                            if (is_numeric($data)) {
                                return intval($data);
                            }
                    }
                    break;
                case $custom_token_code['eth']:
                case 'link':
                case 'shib':
                case 'bat':
                case 'usdt':
                case 'usdc':
                case 'eth':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 2:
                                case 0:
                                    $data = crpay_isset($data, 'result');
                                    if (is_numeric($data)) {
                                        require_once(__DIR__ . '/web3.php');
                                        return floatval($data) / ($is_token ? 10 ** ($custom_token ? $custom_token['decimals'] : crpay_eth_get_contract($cryptocurrency_code)[1]) : 1000000000000000000);
                                    }
                                    break;
                                case 1:
                                    if ($is_token) {
                                        $data = crpay_isset($data, 'tokens', []);
                                        for ($j = 0; $j < count($data); $j++) {
                                            if (strtolower(crpay_isset(crpay_isset($data, 'tokenInfo'), 'symbol')) == $cryptocurrency_code) {
                                                return floatval($data['balance']) / (10 ** intval($data['tokenInfo']['decimals']));
                                            }
                                        }
                                    } else {
                                        $data = crpay_isset(crpay_isset($data, 'ETH'), 'balance');
                                        if (is_numeric($data)) {
                                            return floatval($data);
                                        }
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 2:
                                case 0:
                                    $data = crpay_isset($data, 'result');
                                    if (is_array($data)) {
                                        $count = count($data);
                                        $slugs = ['timeStamp', 'from', 'value', 'confirmations', 'hash', 'blockNumber'];
                                        $divider = $is_token ? 1000000 : 1000000000000000000;
                                        if ($single_transaction) {
                                            if ($i === 0) {
                                                $data_single = [];
                                                for ($j = 0; $j < $count; $j++) {
                                                    if ($data[$j]['hash'] == $extra) {
                                                        $data_single = [$data[$j]];
                                                        break;
                                                    }
                                                }
                                                $data = $data_single;
                                            } else {
                                                $data = [$data];
                                            }
                                        } else if ($is_token) {
                                            $data_temp = [];
                                            for ($j = 0; $j < $count; $j++) {
                                                if (strtolower($data[$j]['tokenSymbol']) == $cryptocurrency_code) {
                                                    array_push($data_temp, $data[$j]);
                                                }
                                            }
                                            $data = $data_temp;
                                        }
                                        if ($count && isset($data[0]['tokenDecimal'])) {
                                            $divider = 10 ** intval($data[0]['tokenDecimal']);
                                        }
                                    }
                                    break;
                                case 1:
                                    if ($single_transaction || is_array($data) || $is_token) {
                                        $slugs = ['timestamp', 'from', 'value', 'confirmations', 'hash', 'blockNumber'];
                                        if ($single_transaction) {
                                            $data = [$data];
                                        }
                                    }
                                    if ($is_token) {
                                        if ($single_transaction) {
                                            if (count($data)) {
                                                $transaction_value = 0;
                                                if (isset($data[0]['operations'])) {
                                                    $operations = $data[0]['operations'];
                                                    $address = strtolower($address);
                                                    for ($j = 0; $j < count($operations); $j++) {
                                                        if ($operations[$j]['type'] == 'transfer' && strtolower($operations[$j]['to']) == $address_lowercase) {
                                                            $transaction_value += $operations[$j]['value'];
                                                        }
                                                    }
                                                    $divider = 10 ** intval($operations[0]['tokenInfo']['decimals']);
                                                    $data[0]['value'] = $transaction_value;
                                                }
                                            }
                                        } else {
                                            $data = crpay_isset($data, 'operations', []);
                                            $data_temp = [];
                                            for ($j = 0; $j < count($data); $j++) {
                                                if (strtolower($data[$j]['tokenInfo']['symbol']) == $cryptocurrency_code) {
                                                    array_push($data_temp, $data[$j]);
                                                    $divider = 10 ** intval($data[$j]['tokenInfo']['decimals']);
                                                }
                                            }
                                            $slugs[4] = 'transactionHash';
                                            $data = $data_temp;
                                        }
                                    }
                                    break;
                            }
                            if ($slugs && ((!is_array($data) && !$data) || (count($data) && (!isset($data[0]) || !crpay_isset($data[0], $slugs[0]))))) {
                                $slugs = false;
                            }
                            break;
                        case 'blocks_count':
                            switch ($i) {
                                case 1:
                                    if (is_numeric($data['lastBlock'])) {
                                        return intval($data['lastBlock']);
                                    }
                                    break;
                            }
                    }
                    break;
                case 'doge':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'data');
                                    if ($data && isset($data['confirmed_balance'])) {
                                        return $data['confirmed_balance'];
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'data');
                                    if ($data) {
                                        if (!$single_transaction) {
                                            $data = crpay_isset($data, 'txs');
                                        }
                                        $slugs = ['time', 'address', 'value', 'confirmations', 'txid', false];
                                    } else if (is_array($data)) {
                                        return [];
                                    }
                                    break;
                            }
                            if ($slugs) {
                                if (is_array($data)) {
                                    if ($single_transaction && ($i === 0 || $i === 1)) {
                                        $data['address'] = $data['inputs'][0]['address'];
                                        $outputs = $data['outputs'];
                                        for ($j = 0; $j < count($outputs); $j++) {
                                            if (strtolower($outputs[$j]['address']) == $address_lowercase) {
                                                $data['value'] = $outputs[$j]['value'];
                                                break;
                                            }
                                        }
                                        $data = [$data];
                                    }
                                }
                                if ((!is_array($data) && !$data) || (count($data) && (!isset($data[0]) || (!crpay_isset($data[0], $slugs[0]) && !crpay_isset($data[0], $slugs[1]))))) {
                                    $slugs = false;
                                }
                            }
                            break;
                        case 'blocks_count':
                            switch ($i) {
                                case 0:
                                    if (is_numeric($data['lastBlock'])) {
                                        return intval($data['lastBlock']);
                                    }
                                    break;
                            }
                    }
                    break;
                case 'algo':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset(crpay_isset($data, 'account'), 'amount');
                                    if (is_numeric($data)) {
                                        return floatval($data) / 1000000;
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 0:
                                    $current_round = crpay_isset($data, 'current-round');
                                    $data = crpay_isset($data, $single_transaction ? 'transaction' : 'transactions');
                                    if ($data) {
                                        $slugs = ['round-time', 'sender', 'amount', 'confirmations', 'id', 'confirmed-round'];
                                        $divider = 1000000;
                                        if ($single_transaction) {
                                            $data['amount'] = crpay_isset(crpay_isset($data, 'payment-transaction'), 'amount', -1);
                                            $data['confirmations'] = $current_round - crpay_isset($data, 'confirmed-round');
                                            $data = [$data];
                                        } else {
                                            for ($j = 0; $j < count($data); $j++) {
                                                $data[$j]['amount'] = crpay_isset(crpay_isset($data[$j], 'payment-transaction'), 'amount', -1);
                                                $data[$j]['confirmations'] = $current_round - crpay_isset($data[$j], 'confirmed-round');
                                            }
                                        }
                                    } else if (is_array($data)) {
                                        return [];
                                    }
                                    break;
                            }
                            break;
                        case 'blocks_count':
                            switch ($i) {
                                case 1:
                                    if (is_numeric($data['current-round'])) {
                                        return intval($data['current-round']);
                                    }
                                    break;
                            }
                    }
                    break;
                case $custom_token_code['bsc']:
                case 'usdt_bsc':
                case 'busd':
                case 'bnb':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'result');
                                    if (is_numeric($data)) {
                                        return floatval($data) / 1000000000000000000;
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'result');
                                    if (is_array($data)) {
                                        $slugs = ['timeStamp', 'from', 'value', 'confirmations', 'hash', 'blockNumber'];
                                        $divider = 1000000000000000000;
                                        if ($single_transaction) {
                                            if ($i === 0) {
                                                $data_single = [];
                                                for ($j = 0; $j < count($data); $j++) {
                                                    if ($data[$j]['hash'] == $extra) {
                                                        $data_single = [$data[$j]];
                                                        break;
                                                    }
                                                }
                                                $data = $data_single;
                                            } else {
                                                $data = [$data];
                                            }
                                        }
                                    }
                                    break;
                            }
                            break;
                    }
                    break;
                case 'ltc':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'data');
                                    if ($data && isset($data['confirmed_balance'])) {
                                        return $data['confirmed_balance'];
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'data');
                                    if ($data) {
                                        if (!$single_transaction) {
                                            $data = crpay_isset($data, 'txs');
                                        }
                                        $slugs = ['time', 'address', 'value', 'confirmations', 'txid', false];
                                    } else if (is_array($data))
                                        return [];
                                    break;
                            }
                            if ($slugs) {
                                if (is_array($data)) {
                                    if ($single_transaction && ($i === 0 || $i === 1)) {
                                        $data['address'] = $data['inputs'][0]['address'];
                                        $outputs = $data['outputs'];
                                        for ($j = 0; $j < count($outputs); $j++) {
                                            if (strtolower($outputs[$j]['address']) == $address_lowercase) {
                                                $data['value'] = $outputs[$j]['value'];
                                                break;
                                            }
                                        }
                                        $data = [$data];
                                    }
                                }
                                if ((!is_array($data) && !$data) || (count($data) && (!isset($data[0]) || (!crpay_isset($data[0], $slugs[0]) && !crpay_isset($data[0], $slugs[1]))))) {
                                    $slugs = false;
                                }
                            }
                            break;
                    }
                    break;
                case 'bch':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'balance');
                                    if ($data) {
                                        return $data;
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'txs');
                                    if ($data) {
                                        $slugs = ['time', 'address', 'value', 'confirmations', 'txid', false];
                                    } else if (is_array($data)) {
                                        return [];
                                    }
                                    break;
                            }
                            if ($slugs) {
                                if (is_array($data)) {
                                    for ($j = 0; $j < count($data); $j++) {
                                        $data_transaction = $data[$j][0];
                                        $data_transaction['address'] = str_replace('bitcoincash:', '', $data_transaction['vin'][0]['cashAddress']);
                                        $outputs = $data_transaction['vout'];
                                        $address_prefix = 'bitcoincash:' . $address;
                                        for ($y = 0; $y < count($outputs); $y++) {
                                            if (strtolower($outputs[$y]['scriptPubKey']['addresses'][0]) == $address_prefix) {
                                                $data_transaction['value'] = $outputs[$y]['value'];
                                                break;
                                            }
                                        }
                                        $data[$j] = $data_transaction;
                                    }
                                    if ($single_transaction) {
                                        for ($j = 0; $j < count($data); $j++) {
                                            if ($data[$j]['txid'] == $extra) {
                                                $data = [$data[$j]];
                                                break;
                                            }
                                        }
                                    }
                                }
                                if ((!is_array($data) && !$data) || (count($data) && (!isset($data[0]) || (!crpay_isset($data[0], $slugs[0]) && !crpay_isset($data[0], $slugs[1]))))) {
                                    $slugs = false;
                                }
                            }
                            break;
                    }
                    break;
                case $custom_token_code['trx']:
                case 'trx':
                case 'usdt_tron':
                    $data = json_decode($data, true);
                    if (isset($data)) {
                        switch ($action) {
                            case 'balance':
                                switch ($i) {
                                    case 0:
                                        if ($is_token) {
                                            $data = crpay_isset($data, 'trc20token_balances');
                                            if (is_array($data)) {
                                                $cryptocurrency_code = crpay_crypto_get_base_code($cryptocurrency_code);
                                                for ($j = 0; $j < count($data); $j++) {
                                                    if (strtolower($data[$j]['tokenAbbr']) == $cryptocurrency_code) {
                                                        return floatval($data[$j]['balance']) / 1000000;
                                                    }
                                                }
                                            }
                                        }
                                        break;
                                }
                                break;
                            case 'transaction':
                                switch ($i) {
                                    case 0:
                                        if (isset($data['contractData']) && crpay_isset($data['contractData'], 'contract_address') == $contract_address && isset($data['trigger_info'])) {
                                            $data['value'] = $data['trigger_info']['parameter']['_value'];
                                            $divider = 10 ** $data['tokenTransferInfo']['decimals'];
                                            $data = [$data];
                                            $slugs = ['timestamp', 'ownerAddress', 'value', 'confirmations', 'hash', 'block'];
                                        }
                                        break;
                                }
                                break;
                            case 'transactions':
                                switch ($i) {
                                    case 0:
                                        if (isset($data['data'])) {
                                            $data = $data['data'];
                                            $transactions_data = [];
                                            if ($is_token) {
                                                for ($j = 0; $j < count($data); $j++) {
                                                    $data[$j]['amount'] = crpay_decimal_number($data[$j]['amount'] / (10 ** $data[$j]['decimals']));
                                                    $data[$j]['timestamp'] = $data[$j]['timestamp'] / 1000;
                                                }
                                            }
                                            $slugs = ['timestamp', 'transferFromAddress', 'amount', 'confirmed', 'transactionHash', 'block'];
                                        }
                                        break;
                                }
                                break;
                        }
                    }
                    break;
                case 'xrp':
                    $data = json_decode($data, true);
                    switch ($action) {
                        case 'balance':
                            switch ($i) {
                                case 0:
                                    $data = crpay_isset($data, 'balances', []);
                                    for ($j = 0; $j < count($data); $j++) {
                                        if ($data[$j]['currency'] === 'XRP') {
                                            return $data[$j]['value'];
                                        }
                                    }
                                    break;
                            }
                            break;
                        case 'transaction':
                        case 'transactions':
                            switch ($i) {
                                case 0:
                                    $data = $single_transaction ? [crpay_isset($data, 'transaction')] : crpay_isset($data, 'transactions', []);
                                    $transactions_data = [];
                                    $divider = 1000000;
                                    if ($data && count($data) && $data[0]) {
                                        for ($j = 0; $j < count($data); $j++) {
                                            $tx = crpay_isset($data[$j], 'tx');
                                            if ($tx) {
                                                $data[$j]['address'] = $tx['Account'];
                                                $data[$j]['value'] = $tx['Amount'];
                                                $data[$j]['time'] = strtotime($data[$j]['date']);
                                                $data[$j]['confirmations'] = 9999;
                                                array_push($transactions_data, $data[$i]);
                                            }
                                        }
                                        $data = $transactions_data;
                                        $slugs = ['time', 'address', 'value', 'confirmations', 'hash', false];
                                    } else if (is_array($data)) {
                                        return [];
                                    }
                                    break;
                            }
                    }
                    break;
            }
        }

        // Add the transactions
        if ($slugs) {
            $transactions = [];
            $count = count($data);
            if ($count) {
                for ($j = 0; $j < $count; $j++) {
                    $transaction = $data[$j];
                    array_push($transactions, ['time' => crpay_isset($transaction, $slugs[0]), 'address' => crpay_isset($transaction, $slugs[1], ''), 'value' => crpay_decimal_number($transaction[$slugs[2]] / $divider), 'confirmations' => crpay_isset($transaction, $slugs[3], 0), 'hash' => $transaction[$slugs[4]], 'block_height' => crpay_isset($transaction, $slugs[5], '')]);
                }
                if ($single_transaction) {
                    $transactions[0]['explorer'] = is_string($services[$i]) ? $services[$i] : $services[$i][5];
                }
                return $single_transaction ? $transactions[0] : $transactions;
            }
            return [];
        }
    }
    return $data_original;
}

function crpay_node_rpc($cryptocurrency_code, $method_name, $parameters) {
    $cryptocurrency_code = crpay_crypto_get_network($cryptocurrency_code);
    $node_url = crpay_settings_get($cryptocurrency_code . '-node-url');
    $node_headers = crpay_settings_get($cryptocurrency_code . '-node-headers', []);
    if (!$node_url) {
        crpay_error(crpay_crypto_name($cryptocurrency_code, true) . ' node not found', 'crpay_btc_curl', true);
    }
    if ($node_headers) {
        $node_headers = explode(',', $node_headers);
    }
    $post_fields = ['method' => $method_name, 'params' => $parameters, 'jsonrpc' => '2.0'];
    if ($cryptocurrency_code == 'eth') {
        $post_fields['id'] = 1;
    }
    $response_json = crpay_curl($node_url, json_encode($post_fields), array_merge(['accept: application/json', 'content-type: application/json'], $node_headers), 'POST');
    $response = json_decode($response_json, true);
    $result = crpay_isset($response, 'result');
    if (!$result) {
        crpay_error($response_json, 'crpay_node_rpc');
    }
    return $result ? $result : ($response ? $response : $response_json);
}

/*
 * -----------------------------------------------------------
 * FIAT
 * -----------------------------------------------------------
 *
 */

function crpay_stripe_payment($price_amount, $checkout_url, $client_reference_id, $currency_code = false, $description = false) {
    $response = crpay_stripe_create_session(crpay_stripe_get_price($price_amount, $currency_code)['id'], $checkout_url, $client_reference_id, $description);
    return isset($response['url']) ? $response['url'] : $response;
}

function crpay_stripe_get_price($price_amount, $currency_code = false) {
    $price_amount = intval($price_amount);
    $product_id = crpay_settings_get('stripe-product-id');
    $prices = crpay_stripe_curl('prices?product=' . $product_id . '&limit=100&type=one_time', 'GET');
    $currency_code = strtolower($currency_code);
    if (!isset($prices['data'])) {
        return $prices;
    }
    $prices = $prices['data'];
    for ($i = 0; $i < count($prices); $i++) {
        if ($price_amount == $prices[$i]['unit_amount'] && $prices[$i]['currency'] == $currency_code) {
            return $prices[$i];
        }
    }
    return crpay_stripe_curl('prices?unit_amount=' . $price_amount . '&currency=' . ($currency_code ? $currency_code : crpay_settings_get('currency')) . '&product=' . $product_id);
}

function crpay_stripe_create_session($price_id, $checkout_url, $client_reference_id = false, $description = false) {
    parse_str(crpay_isset(parse_url($checkout_url), 'query'), $attributes);
    $checkout_url = str_replace(['payment_status=cancelled', isset($attributes['cc']) ? 'cc=' . $attributes['cc'] : '', isset($attributes['pay']) ? 'pay=' . $attributes['pay'] : ''], '', $checkout_url);
    while (in_array(substr($checkout_url, -1), ['&', '?'])) {
        $checkout_url = substr($checkout_url, 0, -1);
    }
    $checkout_url = str_replace(['?&', '&&'], ['?', '&'], $checkout_url);
    return crpay_stripe_curl('checkout/sessions?' . (crpay_settings_get('stripe-tax') ? 'automatic_tax[enabled]=true&' : '') . 'metadata[source]=cryptopayment' . (CRPAY_CLOUD ? '&metadata[cloud]=' . crpay_cloud_get_data() : '') . '&cancel_url=' . urlencode($checkout_url . (strpos($checkout_url, '?') ? '&' : '?') . 'payment_status=cancelled') . '&success_url=' . crpay_payment_redirect_url($checkout_url, $client_reference_id) . '&line_items[0][price]=' . $price_id . '&mode=payment&line_items[0][quantity]=1&client_reference_id=' . $client_reference_id . ($description ? '&payment_intent_data[description]=' . urlencode(str_replace(['&', '=', '?'], '', $description)) : ''));
}

function crpay_stripe_curl($url_part, $type = 'POST') {
    $response = crpay_curl('https://api.stripe.com/v1/' . $url_part, '', ['Authorization: Basic ' . base64_encode(crpay_settings_get('stripe-key'))], $type);
    $response = json_decode($response, true);
    if (isset($response['error'])) {
        crpay_error($response['error'], 'crpay_stripe_curl');
    }
    return $response;
}

function crpay_stripe_get_divider($currency_code) {
    return in_array(strtoupper($currency_code), ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF']) ? 1 : 100;
}

function crpay_verifone_create_checkout($price_amount, $checkout_url, $client_reference_id, $title, $currency_code = false) {
    $url = 'https://secure.2checkout.com/checkout/buy?currency=' . ($currency_code ? $currency_code : crpay_settings_get('currency')) . '&dynamic=1&merchant=' . crpay_settings_get('verifone-merchant-id') . '&order-ext-ref=' . crpay_encryption($client_reference_id . '|||' . crpay_settings_get('verifone-key')) . '&price=' . $price_amount . '&prod=' . $title . '&qty=1&return-type=redirect&return-url=' . crpay_payment_redirect_url($checkout_url, $client_reference_id) . '&type=digital';
    return $url . '&signature=' . crpay_verifone_get_signature($url);
}

function crpay_verifone_get_signature($url) {
    parse_str(substr($url, strpos($url, '?') + 1), $values);
    $serialized = '';
    foreach ($values as $key => $value) {
        if (!in_array($key, ['merchant', 'dynamic', 'email'])) {
            $serialized .= mb_strlen($value) . $value;
        }
    }
    return hash_hmac('sha256', $serialized, crpay_settings_get('verifone-word'));
}

function crpay_verifone_curl($url_part, $type = 'POST') {
    $merchant_id = crpay_settings_get('verifone-merchant-id');
    $date = gmdate('Y-m-d H:i:s');
    $string = strlen($merchant_id) . $merchant_id . strlen($date) . $date;
    $hash = hash_hmac('md5', $string, crpay_settings_get('verifone-key'));
    $response = crpay_curl('https://api.2checkout.com/rest/6.0/' . $url_part, '', ['Content-Type: application/json', 'Accept: application/json', 'X-Avangate-Authentication: code="' . $merchant_id . '" date="' . $date . '" hash="' . $hash . '"'], $type);
    return is_string($response) ? json_decode($response, true) : $response;
}

function crpay_vat($amount, $country_code = false, $currency_code = false, $vat_number = false) {
    $ip = $country_code ? ['countryCode' => $country_code] : crpay_ip_info('country,countryCode');
    if (isset($ip['countryCode']) && (!$vat_number || !crpay_settings_get('vat-validation') || !crpay_vat_validation($vat_number))) {
        $rates = json_decode(file_get_contents(__DIR__ . '/../../resources/vat.json'), true)['rates'];
        for ($i = 0; $i < count($rates); $i++) {
            if ($rates[$i]['country_code'] == $ip['countryCode']) {
                $amount = floatval($amount);
                $rate_percentage = $rates[$i]['standard']['rate'];
                $rate = $amount * ($rate_percentage / 100);
                return [round($amount + $rate, 2), round($rate, 2), $rates[$i]['country_code'], $rates[$i]['country_name'], str_replace(['{1}', '{2}'], [strtoupper($currency_code), round($rate, 2)], crpay_('Including {1} {2} for VAT in')) . ' ' . crpay_($rates[$i]['country_name']), $rate_percentage];
            }
        }
    }
    return [$amount ? $amount : 0, 0, '', crpay_isset($ip, 'country', ''), '', 0];
}

function crpay_vat_validation($vat_number) {
    $key = crpay_settings_get('vatsense-key');
    if (!$key) {
        return crpay_error('Missing Vatsense key. Set it in the Cryptopayment settings area.', 'crpay_vat_validation', true);
    }
    $vat_numbers = json_decode(crpay_settings_db('vat-numbers', false, '[]'), true);
    if (isset($vat_numbers[$vat_number])) {
        return $vat_numbers[$vat_number];
    }
    $response = json_decode(crpay_curl('https://api.vatsense.com/1.0/validate?vat_number=' . $vat_number, '', ['CURLOPT_USERPWD' => 'user:' . $key]), true);
    if ($response && crpay_isset($response, 'success')) {
        $vat_numbers[$vat_number] = crpay_isset(crpay_isset($response, 'data', []), 'valid');
        crpay_settings_db('vat-numbers', $vat_numbers);
        return $vat_numbers[$vat_number];
    } else {
        crpay_error($response, 'crpay_vat_validation');
    }
    return false;
}

function crpay_paypal_get_checkout_url($transaction_id, $checkout_url, $amount, $currency_code, $title = '') {
    // Parse and handle the checkout URL parameters
    parse_str($checkout_url, $checkout_url_paramaters);
    if (isset($checkout_url_paramaters['redirect']) && isset($checkout_url_paramaters['pay'])) {
        $checkout_url = $checkout_url_paramaters['redirect'];
    }

    // Retrieve necessary settings
    $paypal_email = crpay_settings_get('paypal-email');
    if (empty($paypal_email)) {
        throw new Exception('PayPal email is not configured.');
    }

    // Construct data for PayPal
    $data = [
        'cmd' => '_xclick',
        'business' => $paypal_email,
        'return' => crpay_payment_redirect_url($checkout_url, $transaction_id, false),
        'cancel_return' => $checkout_url . (strpos($checkout_url, '?') !== false ? '&' : '?') . 'payment_status=cancelled',
        'notify_url' => CRPAY_URL . '/vendor/cryptd/paypal.php',
        'item_name' => empty($title) ? crpay_('Transaction') . ' ' . $transaction_id : $title,
        'amount' => $amount,
        'currency_code' => strtoupper($currency_code),
        'custom' => $transaction_id . (CRPAY_CLOUD ? '|' . crpay_cloud_get_data() : '')
    ];

    // Build the PayPal URL
    $paypal_base_url = 'https://www.' . (crpay_settings_get('paypal-sandbox') ? 'sandbox.' : '') . 'paypal.com/cgi-bin/webscr';
    return $paypal_base_url . '?' . http_build_query($data);
}

/*
 * -----------------------------------------------------------
 * EXCHANGES
 * -----------------------------------------------------------
 *
 */

function crpay_gemini_curl($url_part, $parameters = [], $type = 'POST') {
    $signature = base64_encode(utf8_encode(json_encode(array_merge(['request' => '/v1/' . $url_part, 'nonce' => time()], $parameters))));
    $header = [
        'Content-Type: text/plain',
        'Content-Length: 0',
        'X-GEMINI-APIKEY: ' . crpay_settings_get('gemini-key'),
        'X-GEMINI-PAYLOAD: ' . $signature,
        'X-GEMINI-SIGNATURE: ' . hash_hmac('sha384', $signature, utf8_encode(crpay_settings_get('gemini-key-secret'))),
        'Cache-Control: no-cache'
    ];
    return json_decode(crpay_curl('https://api' . (crpay_settings_get('gemini-sandbox') ? '.sandbox' : '') . '.gemini.com/v1/' . $url_part, '', $header, $type), true);
}

function crpay_gemini_convert_to_fiat($cryptocurrency_code, $amount) {
    $symbol = strtolower($cryptocurrency_code . crpay_settings_get('gemini-conversion-currency'));
    $symbol_uppercase = strtoupper($symbol);
    $price = json_decode(crpay_curl('https://api.gemini.com/v1/pricefeed'), true);
    if (!$price)
        return crpay_gemini_convert_to_fiat($cryptocurrency_code, $amount);
    for ($i = 0; $i < count($price); $i++) {
        if ($price[$i]['pair'] == $symbol_uppercase) {
            $response = ['remaining_amount' => 1];
            $continue = 5;
            while ($continue && crpay_isset($response, 'remaining_amount') != '0' && !crpay_isset($response, 'is_live')) {
                $response = crpay_gemini_curl('order/new', ['symbol' => $symbol, 'amount' => $amount, 'price' => round(floatval($price[$i]['price']) * 0.999, 2), 'side' => 'sell', 'type' => 'exchange limit']);
                $continue--;
            }
            return $response;
        }
    }
    return false;
}

function crpay_coinbase_curl($url_part, $parameters = [], $type = 'POST', $header = []) {
    $body = $parameters ? json_encode($parameters) : '';
    $response_json = crpay_curl('https://api.coinbase.com' . $url_part, $body, array_merge($header, ['Authorization: Bearer ' . crpay_coinbase_get_jwt($url_part, $type), 'Content-Type: application/json']), $type);
    $response = json_decode($response_json, true);
    return $response ? $response : crpay_error($response_json, 'crpay_coinbase_curl', true);
}

function crpay_coinbase_get_jwt($url_part, $type) {
    require_once(__DIR__ . '/../../vendor/jwt/JWTExceptionWithPayloadInterface.php');
    require_once(__DIR__ . '/../../vendor/jwt/BeforeValidException.php');
    require_once(__DIR__ . '/../../vendor/jwt/CachedKeySet.php');
    require_once(__DIR__ . '/../../vendor/jwt/ExpiredException.php');
    require_once(__DIR__ . '/../../vendor/jwt/JWK.php');
    require_once(__DIR__ . '/../../vendor/jwt/JWT.php');
    require_once(__DIR__ . '/../../vendor/jwt/Key.php');
    require_once(__DIR__ . '/../../vendor/jwt/SignatureInvalidException.php');
    $uri = $type . ' api.coinbase.com' . $url_part;
    $key_name = trim(crpay_settings_get('coinbase-key'));
    $private_key_resource = openssl_pkey_get_private(trim(str_replace('\n', PHP_EOL, crpay_settings_get('coinbase-key-secret'))));
    if (!$private_key_resource) {
        return crpay_error('Invalid private key', 'crpay_coinbase_curl', true);
    }
    $time = time();
    $nonce = bin2hex(random_bytes(16));
    $payload = [
        'aud' => ['retail_rest_api_proxy'],
        'sub' => $key_name,
        'iss' => 'coinbase-cloud',
        'nbf' => $time,
        'exp' => $time + 120,
        'uri' => $uri,
    ];
    $headers = [
        'typ' => 'JWT',
        'alg' => 'ES256',
        'kid' => $key_name,
        'nonce' => $nonce
    ];
    return JWT::encode($payload, $private_key_resource, 'ES256', $key_name, $headers);
}

function crpay_coinbase_get_accounts($currency_code = false) {
    $url = '/api/v3/brokerage/accounts';
    $accounts = crpay_settings_db('coinbase_accounts');
    if ($accounts) {
        $accounts = json_decode($accounts, true);
    } else {
        $accounts = [];
        while ($url) {
            $accounts_2 = crpay_coinbase_curl($url, [], 'GET');
            $accounts = array_merge($accounts, crpay_isset($accounts_2, 'data', []));
            $url = isset($accounts_2['pagination']) ? crpay_isset($accounts_2['pagination'], 'next_uri') : false;
        }
        crpay_settings_db('coinbase_accounts', $accounts);
    }
    if ($currency_code) {
        $currency_code = strtoupper($currency_code);
        for ($i = 0; $i < count($accounts); $i++) {
            if (crpay_isset($accounts[$i], 'currency', [])['code'] == $currency_code && $accounts[$i]['type'] != 'vault') {
                return $accounts[$i];
            }
        }
        return false;
    }
    return $accounts;
}

function crpay_coinbase_get_account_fiat_currency_code() {
    $currency_code = crpay_settings_db('coinbase_fiat_currency_code');
    if ($currency_code) {
        return $currency_code;
    }
    $currencies = array_column(json_decode(file_get_contents(__DIR__ . '/../../resources/currencies.json'), true), 0);
    $accounts = crpay_coinbase_get_accounts();
    for ($i = 0; $i < count($accounts); $i++) {
        $currency_code = strtoupper($accounts[$i]['currency']['code']);
        if (in_array($currency_code, $currencies)) {
            crpay_settings_db('coinbase_fiat_currency_code', $currency_code);
            break;
        }
    }
    return $currency_code;
}

function crpay_coinbase_send_transaction($amount, $address, $cryptocurrency_code) {
    $coinbase_2fa = crpay_settings_get('coinbase-2fa');
    if (!$coinbase_2fa) {
        return crpay_error('Coinbase 2FA key not set.', 'crpay_coinbase_send_transaction', true);
    }
    $account = crpay_coinbase_get_accounts($cryptocurrency_code);
    if (!$account) {
        return crpay_error('Coinbase account not found:' . $cryptocurrency_code, 'crpay_coinbase_send_transaction', true);
    }
    $totp = new \OTPHP\TOTP($coinbase_2fa);
    $response = crpay_coinbase_curl('/v2/accounts/' . $account['id'] . '/transactions', ['to' => $address, 'amount' => $amount, 'currency' => $cryptocurrency_code, 'type' => 'send'], 'POST', ['CB-2FA-Token: ' . $totp->now()]);
    if (crpay_isset(crpay_isset($response, 'data', []), 'status') == 'pending') {
        true;
    }
    $error = isset($response['errors']) ? $response['errors'][0]['message'] : json_encode($response);
    crpay_error($error, 'crpay_coinbase_send_transaction');
    return $error;
}

/*
 * -----------------------------------------------------------
 * CLOUD
 * -----------------------------------------------------------
 *
 */

function crpay_cloud_load() {
    if (!defined('CRPAY_DB_NAME') && defined('CLOUD_URL')) {
        $data = crpay_cloud_get_data();
        if ($data) {
            require_once(__DIR__ . '/cloud/functions.php');
            $data = json_decode(crpay_cloud_encryption($data, false), true);
            $path = __DIR__ . '/cloud/config/' . $data['token'] . '.php';
            if (file_exists($path)) {
                require_once($path);
                return true;
            }
            return 'config-file-missing';
        } else
            return 'cloud-data-not-found';
    }
    return true;
}

function crpay_cloud_spend_credit($transaction_amount, $currency) {
    $fee_usd = floatval($transaction_amount) * (defined('CLOUD_FEE') ? CLOUD_FEE : 0.008);
    if (strtoupper($currency) != 'USD') {
        $rate = crpay_usd_rates($currency);
        $fee_usd = $fee_usd / $rate;
    }
    $credit_balance = floatval(crpay_settings_db('credit_balance')) - $fee_usd;
    crpay_cloud_credit_email($credit_balance);
    return crpay_db_query('UPDATE crpay_settings SET value = ' . crpay_db_escape($credit_balance, true) . ' WHERE name = "credit_balance"');
}

function crpay_cloud_credit_email($credit_balance) {
    if ($credit_balance < 5) {
        $account = crpay_account();
        if ($account) {
            if ($credit_balance > 0 && intval(crpay_settings_db('credit_balance_email')) > (time() - 604800)) {
                return;
            }
            $emails = $credit_balance < 0 ? ['Action required: your account has been suspended.', 'Your account has been suspended because your balance is negative. Your checkouts are blocked and your customers cannot make payments.'] : ['Your balance is low: Add credit to avoid service disruption', 'Your balance is less than USD 5. When the balance drops below zero, the checkouts will stop working and your customers will not be able to make payments.'];
            $response = crpay_email_send($account[0], $emails[0], ['title' => $emails[0], 'message' => $emails[1] . ' Click the button below to add credit to your account. <br /> <br /> <a href="' . CRPAY_URL . '#account" style="display:block;text-decoration:none;border:none;background:#2acad6;color:#fff;font-weight:500;margin:30px auto;max-width:200px;padding:15px 30px;border-radius:6px;font-size:17px;white-space:nowrap;text-align:center;cursor:pointer">Add credit to your account</a>']);
            if ($response === true && $credit_balance > 0) {
                crpay_settings_db('credit_balance_email', time());
            }
        }
    }
}

function crpay_cloud_get_data() {
    crpay_cloud_domain_rewrite_load(true);
    return isset($_COOKIE['CRPAY_CLOUD']) ? $_COOKIE['CRPAY_CLOUD'] : (isset($_GET['cloud']) ? crpay_sanatize_string($_GET['cloud']) : crpay_sanatize_string(crpay_isset($_POST, 'cloud')));
}

function crpay_cloud_path_part() {
    return defined('CRPAY_DB_NAME') ? (CRPAY_CLOUD ? '/' . substr(CRPAY_DB_NAME, 4) : '') : '';
}

function crpay_cloud_domain_rewrite_load($set_cookie = true) {
    if (isset($_COOKIE['CRPAY_CLOUD'])) {
        return $_COOKIE['CRPAY_CLOUD'];
    }
    $referral = crpay_isset($_SERVER, 'HTTP_APX_INCOMING_HOST');
    if ($referral) {
        require_once(__DIR__ . '/cloud/functions.php');
        $domains = json_decode(crpay_isset(db_get('SELECT value FROM settings WHERE name = "custom_domains"'), 'value', '{}'), true);
        $referral = crpay_isset($domains, $_SERVER['HTTP_APX_INCOMING_HOST']);
        if ($referral && $set_cookie) {
            $_COOKIE['CRPAY_CLOUD'] = $referral;
        }
        return $referral;
    }
}

?>