Plugin Directory

Changeset 3462628


Ignore:
Timestamp:
02/16/2026 03:15:20 PM (6 weeks ago)
Author:
cryptocloud
Message:

New Update Release 3.0.0

Location:
cryptocloud-crypto-payment-gateway/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • cryptocloud-crypto-payment-gateway/trunk/readme.txt

    r2982657 r3462628  
    11=== CryptoCloud - Crypto Payment Gateway ===
    22Contributors: cryptocloud
    3 Tags:cryptocurrency, crypto, bitcoin, donation, payment gateway, crypto payment gateway
     3Tags:cryptocurrency, bitcoin payment gateway, cryptocurrency payments, payment gateway, crypto payment gateway
    44Requires at least:4.5
    5 Tested up to:6.3.2
     5Tested up to:6.9.1
    66Requires PHP: 5.6
    77License: GPL2
    8 Stable tag:2.1.2
     8Stable tag:3.0.0
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 CryptoCloud - cryptocurrency payment system for business. We offer to you a possibility to accept payments worldwide in the most popular cryptocurrencies USDT/ETH/BTC/LTC and many others.
     11CryptoCloud - cryptocurrency payment system for business. We offer to you a possibility to accept payments worldwide in 40 cryptocurrencies.
    1212
    1313== Description ==
     
    4646**Start accepting cryptocurrency right now!**
    4747
    48 - [Demo payment page](https://demopay.cryptocloud.plus/?currency=USDT&network=TRC20&lang=en?utm_source=wordpress.org&utm_medium=module-shop&utm_campaign=woocommerce)
     48- [Demo payment page](https://demopay.cryptocloud.plus/?lang=en?utm_source=wordpress.org&utm_medium=module-shop&utm_campaign=woocommerce)
    4949- To learn about other features, please visit our [website](https://cryptocloud.plus/en?utm_source=wordpress.org&utm_medium=module-shop&utm_campaign=woocommerce) and [sign up](https://app.cryptocloud.plus/registration?lang=en?utm_source=wordpress.org&utm_medium=module-shop&utm_campaign=woocommerce)
    5050- Any questions left? Contact our [support team](https://t.me/cryptocloud_supbot)
  • cryptocloud-crypto-payment-gateway/trunk/woocryptocloud.php

    r2982654 r3462628  
    11<?php
    2 
    32/*
    43 * Plugin Name: WooCommerce - Платежный модуль CryptoCloud
     
    76 * Author URI:  https://cryptocloud.plus/
    87 * Author: CryptoCloud
    9  * Version: 2.1.2
     8 * Version: 2.3.2
    109 * Text Domain: cryptocloud
    1110 * Domain Path: /languages
     
    1413define('WCCC_Payment_Gateway', dirname(__FILE__));
    1514
     15// Declare HPOS compatibility
     16add_action('before_woocommerce_init', function () {
     17    if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
     18        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
     19    }
     20});
     21
    1622add_action('plugins_loaded', 'init_woo_cryptocloud', 0);
    1723
    18 function init_woo_cryptocloud() {
     24function init_woo_cryptocloud()
     25{
    1926
    2027    if (!class_exists('WC_Payment_Gateway')) return;
    2128
    22     class WC_Gateway_CryptoCloud extends WC_Payment_Gateway {
    23        
    24         function __construct() {
     29    class WC_Gateway_CryptoCloud extends WC_Payment_Gateway
     30    {
     31
     32        function __construct()
     33        {
    2534            $this->id = 'cryptocloud';
    2635            $this->method_title = "CryptoCloud Payment System";
    27             $this->method_description = "Adds the ability to accept payments via the CryptoCloud payment system to WooCommerce";
     36            $this->method_description = __("Adds the ability to accept payments via the CryptoCloud payment system to WooCommerce", 'cryptocloud');
    2837
    2938            $this->init_form_fields();
     
    3342            $this->description = $this->get_option('description');
    3443
    35             if (version_compare(WOOCOMMERCE_VERSION, '2.0.0', '>=') ) {
     44            if (version_compare(WOOCOMMERCE_VERSION, '2.0.0', '>=')) {
    3645                add_action('woocommerce_update_options_payment_gateways_' . $this->id, array(&$this, 'process_admin_options'));
    3746            } else {
     
    4251        }
    4352
    44         function init_form_fields() {
     53        function init_form_fields()
     54        {
    4555            $this->form_fields = array(
    4656                'enabled' => array(
     
    5161                'title' => array(
    5262                    'title' => "Name",
    53                     'type' => 'text',
    54                     'description' => "The name that the user sees during the payment",
     63                    'type' => 'textarea',
     64                    'description' => __("The name that the user sees during the payment", 'cryptocloud'),
    5565                    'default' => "CryptoCloud Payment System",
    5666                    'desc_tip' => true,
     67                    'css' => 'height: 60px; width: 400px;',
    5768                ),
    5869                'description' => array(
    5970                    'title' => "Description",
     71                    'type' => 'textarea',
     72                    'description' => __("The description that the user sees during the payment", 'cryptocloud'),
     73                    'default' => __("Accepting payment via cryptocurrency. It is possible to pay the bill using a bank card.", 'cryptocloud'),
     74                    'desc_tip' => true,
     75                    'css' => 'height: 80px; width: 400px;',
     76                ),
     77                'apikey' => array(
     78                    'title' => __('API KEY', 'cryptocloud'),
     79                    'type' => 'textarea',
     80                    'description' => __('API key from your CryptoCloud personal account.', 'cryptocloud'),
     81                    'css' => 'height: 60px; width: 400px;',
     82                ),
     83                'merchant_id' => array(
     84                    'title' => __('Shop ID', 'cryptocloud'),
    6085                    'type' => 'text',
    61                     'description' => "The description that the user sees during the payment",
    62                     'default' => "Accepting payment via cryptocurrency. It is possible to pay the bill using a bank card.",
    63                     'desc_tip' => true,
    64                 ),
    65                 'apikey' => array(
    66                     'title' => 'API KEY',
    67                     'type' => 'text',
    68                 ),
    69                 'merchant_id' => array(
    70                     'title' => __('SHOP ID','cryptocloud'),
    71                     'type' => 'text',
     86                    'description' => __('Store ID from your CryptoCloud personal account.', 'cryptocloud'),
     87                ),
     88                'webhook_secret' => array(
     89                    'title' => __('Webhook Secret Key', 'cryptocloud'),
     90                    'type' => 'textarea',
     91                    'description' => __('A secret key for verifying the JWT signature of webhook notifications. You can find it in your project settings in CryptoCloud.', 'cryptocloud'),
     92                    'css' => 'height: 60px; width: 400px;',
    7293                ),
    7394            );
    7495        }
    75    
    76         function process_payment($order_id) {
     96
     97        function process_payment($order_id)
     98        {
    7799            global $woocommerce;
    78100
     
    83105            $amount = number_format($amount, 2, '.', '');
    84106
    85             $email=$order->get_billing_email();
    86 
    87             $merchant_id = $this->get_option("merchant_id");
     107            $email = $order->get_billing_email();
     108
     109            $shop_id = $this->get_option("merchant_id");
    88110            $apikey = $this->get_option("apikey");
    89111
    90112            $currency = get_woocommerce_currency();
     113
    91114            $data_request = array(
    92                 'shop_id'   => $merchant_id,
    93                 'amount'    => $amount,
    94                 'currency'  => $currency,
    95                 'order_id'  => $order_id,
    96                 'email'     => $email
     115                'shop_id' => $shop_id,
     116                'amount' => $amount,
     117                'currency' => $currency,
     118                'order_id' => (string)$order_id,
     119                'email' => $email
    97120            );
    98        
     121
    99122            $headers = array(
    100                 'Authorization'=> "Token " . $apikey
     123                'Authorization' => "Token " . $apikey,
     124                'Content-Type' => 'application/json'
    101125            );
    102126
    103127            $args = array(
    104                 'body'        => $data_request,
    105                 'timeout'     => '60',
    106                 'httpversion' => '1.0',
    107                 'headers'     => $headers,
     128                'body' => json_encode($data_request),
     129                'timeout' => 30,
     130                'httpversion' => '1.1',
     131                'headers' => $headers,
    108132            );
    109             $response = wp_remote_post('https://api.cryptocloud.plus/v1/invoice/create', $args);
    110             $json_data = wp_remote_retrieve_body($response);
    111             $json_data = json_decode($json_data, true);
    112             var_dump($json_data);
    113             $url = $json_data['pay_url'];
    114             $status= $json_data['status'];
    115             if($status=='success'){
    116                 $woocommerce->cart->empty_cart();
    117             }
    118 
    119             return array('result' => $status, 'redirect' => $url);
    120         }
    121 
    122         public function callback() {
    123             if (!isset($_POST['order_id'])) exit;
    124 
    125             $order_id = intval($_POST['order_id']);
     133
     134            $response = wp_remote_post('https://api.cryptocloud.plus/v2/invoice/create', $args);
     135
     136            // Обработка ошибок HTTP
     137            if (is_wp_error($response)) {
     138                wc_add_notice('Connection to the payment system failed. Please try again later.', 'error');
     139                $this->log_error('HTTP Error: ' . $response->get_error_message());
     140                return;
     141            }
     142
     143            $response_code = wp_remote_retrieve_response_code($response);
     144            if ($response_code !== 200) {
     145                wc_add_notice('The payment system is temporarily unavailable. Error code:' . $response_code, 'error');
     146                $this->log_error('HTTP Response Code: ' . $response_code . ', Body: ' . wp_remote_retrieve_body($response));
     147                return;
     148            }
     149
     150            $json_data = json_decode(wp_remote_retrieve_body($response), true);
     151
     152            // Проверка структуры ответа
     153            if (!$json_data || !isset($json_data['status']) || $json_data['status'] !== 'success') {
     154                $error_msg = isset($json_data['message']) ? $json_data['message'] : 'Неизвестная ошибка';
     155                wc_add_notice('Error creating payment. ' . $error_msg, 'error');
     156                $this->log_error('Invalid API response: ' . wp_remote_retrieve_body($response));
     157                return;
     158            }
     159
     160            if (!isset($json_data['result']['link'])) {
     161                wc_add_notice('Error creating payment link.', 'error');
     162                $this->log_error('Missing link in response: ' . wp_remote_retrieve_body($response));
     163                return;
     164            }
     165
     166            $url = $json_data['result']['link'];
     167            $invoice_id = $json_data['result']['uuid'];
     168
     169            $order = wc_get_order($order_id);
     170            $order->update_meta_data('_cryptocloud_invoice_id', $invoice_id);
     171            $order->save();
     172
     173            // Очищаем корзину только после успешного создания инвойса
     174            $woocommerce->cart->empty_cart();
     175
     176            return array(
     177                'result' => 'success',
     178                'redirect' => $url
     179            );
     180        }
     181
     182        public function callback()
     183        {
     184            // Получаем сырое тело запроса
     185            $raw_body = file_get_contents('php://input');
     186            $received_data = array();
     187
     188            // Определяем тип контента и парсим данные
     189            $content_type = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '';
     190
     191            if (strpos($content_type, 'application/json') !== false) {
     192                // JSON данные
     193                $received_data = json_decode($raw_body, true);
     194                if (json_last_error() !== JSON_ERROR_NONE) {
     195                    $this->log_error('Invalid JSON in webhook');
     196                    status_header(400);
     197                    echo 'Invalid JSON';
     198                    exit;
     199                }
     200            } else {
     201                // Form-urlencoded данные
     202                parse_str($raw_body, $received_data);
     203            }
     204
     205            if (empty($received_data)) {
     206                $this->log_error('Empty received data');
     207                status_header(400);
     208                echo 'Empty data';
     209                exit;
     210            }
     211
     212            // Проверяем обязательные поля
     213            if (!isset($received_data['invoice_id']) || !isset($received_data['status'])) {
     214                $this->log_error('Missing required fields in webhook');
     215                status_header(400);
     216                echo 'Missing required fields';
     217                exit;
     218            }
     219
     220            $invoice_id = sanitize_text_field($received_data['invoice_id']);
     221            $status = sanitize_text_field($received_data['status']);
     222            $order_id = isset($received_data['order_id']) ? intval($received_data['order_id']) : 0;
     223
     224            // Логируем основные данные callback
     225            $this->log_info("Webhook received - Invoice: $invoice_id, Status: $status, Order: $order_id");
     226
     227            // Проверяем JWT подпись если секретный ключ установлен
     228            $webhook_secret = $this->get_option('webhook_secret');
     229            if (!empty($webhook_secret)) {
     230                if (isset($received_data['token'])) {
     231                    $token = sanitize_text_field($received_data['token']);
     232                    if (!$this->verify_jwt_token($token, $invoice_id, $webhook_secret)) {
     233                        $this->log_error('JWT token verification failed for invoice: ' . $invoice_id);
     234                        status_header(401);
     235                        echo 'Invalid token';
     236                        exit;
     237                    }
     238                } else {
     239                    $this->log_error('No token parameter found in webhook data');
     240                    status_header(400);
     241                    echo 'Missing token';
     242                    exit;
     243                }
     244            }
     245
     246            // Находим заказ по ID из webhook или ищем по ID инвойса
     247            if ($order_id === 0) {
     248                $order_id = $this->find_order_by_invoice_id($invoice_id);
     249                if ($order_id === 0) {
     250                    $this->log_error('Order not found for invoice: ' . $invoice_id);
     251                    status_header(404);
     252                    echo 'Order not found';
     253                    exit;
     254                }
     255            }
    126256
    127257            $order = new WC_Order($order_id);
    128258
    129             $amount = $order->get_total();
    130             $amount = str_replace(',', '.', $amount);
    131             $amount = number_format($amount, 2, '.', '');
    132    
    133             $order->payment_complete();
    134            
     259            $order = wc_get_order($order_id);
     260            $stored_invoice_id = $order->get_meta('_cryptocloud_invoice_id', true);
     261
     262            // Нормализуем идентификаторы для сравнения (убираем префикс "INV-" если есть)
     263            $normalized_stored = str_replace('INV-', '', $stored_invoice_id);
     264            $normalized_received = str_replace('INV-', '', $invoice_id);
     265
     266            if ($normalized_stored !== $normalized_received) {
     267                $this->log_error('Invoice ID mismatch for order: ' . $order_id);
     268                status_header(400);
     269                echo 'Invoice ID mismatch';
     270                exit;
     271            }
     272
     273            // Обрабатываем статусы согласно документации
     274            switch ($status) {
     275                case 'success':
     276                case 'paid':
     277                    if (!$order->is_paid()) {
     278                        $order->payment_complete();
     279                        $order->add_order_note('Payment successfully completed via CryptoCloud. Invoice ID: ' . $invoice_id);
     280                        $this->log_info('Payment completed for order: ' . $order_id);
     281                    }
     282                    break;
     283
     284                case 'failed':
     285                    if (!$order->has_status('failed')) {
     286                        $order->update_status('failed', 'Payment failed via CryptoCloud. Invoice ID: ' . $invoice_id);
     287                        $this->log_info('Payment failed for order: ' . $order_id);
     288                    }
     289                    break;
     290
     291                case 'created':
     292                    // Инвойс создан, но еще не оплачен - ничего не делаем
     293                    break;
     294
     295                default:
     296                    $this->log_error('Unknown invoice status: ' . $status);
     297                    status_header(400);
     298                    echo 'Unknown status';
     299                    exit;
     300            }
     301
     302            status_header(200);
     303            echo 'OK';
    135304            exit;
     305        }
     306
     307        /**
     308         * Проверяет JWT токен из параметра token
     309         */
     310        private function verify_jwt_token($jwt_token, $expected_invoice_id, $secret_key)
     311        {
     312            // Разбираем JWT токен
     313            $jwt_parts = explode('.', $jwt_token);
     314            if (count($jwt_parts) !== 3) {
     315                $this->log_error('Invalid JWT format');
     316                return false;
     317            }
     318
     319            list($header, $payload, $signature) = $jwt_parts;
     320
     321            // Декодируем header и payload
     322            $header_decoded = base64_decode(str_replace(['-', '_'], ['+', '/'], $header));
     323            $payload_decoded = base64_decode(str_replace(['-', '_'], ['+', '/'], $payload));
     324
     325            $header_data = json_decode($header_decoded, true);
     326            $payload_data = json_decode($payload_decoded, true);
     327
     328            // Проверяем алгоритм
     329            if (!isset($header_data['alg']) || $header_data['alg'] !== 'HS256') {
     330                $this->log_error('Invalid JWT algorithm');
     331                return false;
     332            }
     333
     334            // Проверяем срок действия токена
     335            if (isset($payload_data['exp']) && $payload_data['exp'] < time()) {
     336                $this->log_error('JWT token expired');
     337                return false;
     338            }
     339
     340            // Проверяем, что invoice_id в токене совпадает с ожидаемым
     341            if (!isset($payload_data['id']) || $payload_data['id'] !== $expected_invoice_id) {
     342                $this->log_error('JWT token invoice_id mismatch');
     343                return false;
     344            }
     345
     346            // Проверяем подпись с помощью HMAC SHA256
     347            $expected_signature = hash_hmac('sha256', $header . '.' . $payload, $secret_key, true);
     348            $expected_signature_base64 = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($expected_signature));
     349
     350            if (!hash_equals($expected_signature_base64, $signature)) {
     351                $this->log_error('JWT signature mismatch');
     352                return false;
     353            }
     354
     355            return true;
     356        }
     357
     358        /**
     359         * Ищет заказ по ID инвойса (HPOS compatible)
     360         */
     361        private function find_order_by_invoice_id($invoice_id)
     362        {
     363            // Нормализуем идентификатор
     364            $normalized_invoice_id = str_replace('INV-', '', $invoice_id);
     365
     366            // Используем WC_Order_Query для поиска по метаданным
     367            $orders = wc_get_orders(array(
     368                'limit' => 1,
     369                'meta_query' => array(
     370                    array(
     371                        'key' => '_cryptocloud_invoice_id',
     372                        'value' => array($invoice_id, 'INV-' . $normalized_invoice_id),
     373                        'compare' => 'IN'
     374                    )
     375                ),
     376                'return' => 'ids'
     377            ));
     378
     379            return !empty($orders) ? $orders[0] : 0;
     380        }
     381
     382        /**
     383         * Логирование ошибок
     384         */
     385        private function log_error($message)
     386        {
     387            if (function_exists('wc_get_logger')) {
     388                $logger = wc_get_logger();
     389                $logger->error('CryptoCloud ERROR: ' . $message, array('source' => 'cryptocloud'));
     390            }
     391        }
     392
     393        /**
     394         * Логирование информации
     395         */
     396        private function log_info($message)
     397        {
     398            if (function_exists('wc_get_logger')) {
     399                $logger = wc_get_logger();
     400                $logger->info('CryptoCloud INFO: ' . $message, array('source' => 'cryptocloud'));
     401            }
    136402        }
    137403    }
    138404}
    139405
    140 function add_woo_cryptocloud($methods) {
    141     $methods[] = 'WC_Gateway_CryptoCloud';
     406function add_woo_cryptocloud($methods)
     407{
     408    $methods[] = 'WC_Gateway_CryptoCloud';
    142409    return $methods;
    143410}
Note: See TracChangeset for help on using the changeset viewer.