Plugin Directory

Changeset 3652810


Ignore:
Timestamp:
08/18/2026 12:10:19 PM (2 months ago)
Author:
cashflowsgateway2021
Message:

2.3.8

  • Added a clear log button, to clear our logfile.
  • Moved the return and notify to the abstract controller and included locking.
  • Checked compatibility with Wordpress 7.1 and WooCommerce 11.0.1
Location:
cashflows-payments-by-ideal-checkout
Files:
25 added
1 deleted
5 edited

Legend:

Unmodified
Added
Removed
  • cashflows-payments-by-ideal-checkout/trunk/gateways/abstract.php

    r3608738 r3652810  
    55
    66use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
     7use Automattic\WooCommerce\Enums\PaymentGatewayFeature;
     8use Automattic\WooCommerce\Enums\OrderStatus;
    79
    810class iccf_abstract extends WC_Payment_Gateway
    911{
    1012    private $additionalData = [];
     13    private static $callbackRegistered = false;
     14
     15    public static function initCallbacks()
     16    {
     17        if (self::$callbackRegistered) {
     18            return;
     19        }
     20
     21        add_action('woocommerce_api_iccf_gateway_return', [__CLASS__, 'process_return']);
     22        add_action('woocommerce_api_iccf_gateway_return_failure', [__CLASS__, 'process_return_failure']);
     23        add_action('woocommerce_api_iccf_gateway_notify', [__CLASS__, 'process_notify']);
     24
     25        self::$callbackRegistered = true;
     26    }
     27
     28    private static function acquireOrderLock($orderId)
     29    {
     30        $lockKey = 'iccf_order_lock_' . absint($orderId);
     31
     32        if (false !== get_transient($lockKey)) {
     33            return false;
     34        }
     35
     36        return set_transient($lockKey, wp_generate_uuid4(), 30);
     37    }
     38
     39    private static function releaseOrderLock($orderId)
     40    {
     41        $lockKey = 'iccf_order_lock_' . absint($orderId);
     42        delete_transient($lockKey);
     43    }
     44
     45    private static function redirectAndExit($url)
     46    {
     47        wp_safe_redirect($url);
     48        exit;
     49    }
    1150
    1251    public function __construct()
    … …  
    1857        $this->method_description = sprintf(__('Enable this method to receive transactions with Cashflows', 'ic-cashflows-for-woo').' - '.$this->getPaymentName());
    1958
    20         $this->supports = ['products', 'refunds'];
     59        $this->supports = [
     60            PaymentGatewayFeature::PRODUCTS,
     61            PaymentGatewayFeature::REFUNDS,
     62        ];
    2163
    2264        $this->init_form_fields();
    … …  
    2769
    2870        add_action('woocommerce_update_options_payment_gateways_'.$this->id, [$this, 'process_admin_options']);
     71
     72        self::initCallbacks();
    2973    }
    3074
    … …  
    179223        $paymentCode = $this->getPaymentCode();
    180224
    181         $paymentMethod = strtolower(strtolower(substr($paymentCode, 10)));
     225        $paymentMethod = strtolower(substr($paymentCode, 10));
    182226
    183227        // Change cardsinline to card
    … …  
    282326    }
    283327
     328    /**
     329     * Process payment implementation for Cashflows. This method is called when the user clicks the "Place Order" button on the checkout page.
     330     * @param mixed $orderId
     331     * @throws RouteException
     332     * @return array{redirect: mixed, result: string}
     333     */
    284334    public function process_payment($orderId)
    285335    {
    286         global $woocommerce;
    287336        global $wp_version;
    288337       
    … …  
    544593    }
    545594
     595    /**
     596     * Refund implementation for CashFlows
     597     * @param mixed $refundId
     598     * @param mixed $refundAmount
     599     * @param mixed $refundReason
     600     * @return WP_Error|WP_REST_Response
     601     */
    546602    public function process_refund($refundId, $refundAmount = null, $refundReason = '')
    547603    {
    … …  
    645701        }
    646702    }
     703
     704
     705    /**
     706     * Return URL of the Cashflows implementation
     707     */
     708    public static function process_return()
     709    {
     710        $debugEnabled = iccf_isDebugEnabled();
     711
     712        if (empty($_GET['real_id']) || empty($_GET['paymentjobref']) || empty($_GET['paymentref']) || empty($_GET['ordernumber'])) {
     713            if ($debugEnabled) {
     714                iccf_log('Customer return called with empty data, redirecting to cart');
     715            }
     716
     717            wp_redirect(wc_get_cart_url());
     718        }
     719
     720        $orderId = sanitize_text_field($_GET['real_id']);
     721        $utmOverride = array_key_exists('utm_nooverride', $_GET);
     722        $paymentJobReference = sanitize_text_field($_GET['paymentjobref']);
     723
     724        if ($debugEnabled) {
     725            iccf_log('Customer returned with orderID: '.$orderId.' and Payment Job Reference: '.$paymentJobReference);
     726        }
     727
     728        $order = wc_get_order($orderId);
     729
     730        if (!$order || ($order->get_meta('cf_transaction_id') !== $paymentJobReference)) {
     731            if ($debugEnabled) {
     732                iccf_log('CashFlows return for Order ID: '.$orderId.' but no order found or transaction ID does not match Payment Job Reference. Redirecting to cart');
     733            }
     734
     735            wp_redirect(wc_get_cart_url().($utmOverride ? '?utm_nooverride=1' : ''));
     736        }
     737
     738        $orderStatus = $order->get_status();
     739
     740        if (in_array($orderStatus, wc_get_is_paid_statuses(), true)) {
     741            if ($debugEnabled) {
     742                iccf_log('Customer return for Order ID: '.$orderId.' but order is already completed, redirecting to order received page');
     743            }
     744
     745            $orderReceivedUrl = $order->get_checkout_order_received_url();
     746
     747            self::redirectAndExit($orderReceivedUrl . ($utmOverride ? (str_contains($orderReceivedUrl, '?') ? '&' : '?') . 'utm_nooverride=1' : ''));
     748        }
     749
     750        $lockToken = self::acquireOrderLock($orderId);
     751
     752        if (!$lockToken) {
     753            if ($debugEnabled) {
     754                iccf_log('Customer return for Order ID: '.$orderId.' could not acquire order lock; redirecting to order received page');
     755            }
     756
     757            self::redirectAndExit($order->get_checkout_order_received_url().($utmOverride ? '?utm_nooverride=1' : ''));
     758        }
     759
     760        if ($debugEnabled) {
     761            iccf_log('Customer return acquired lock for Order ID: '.$orderId.' and is processing payment status');
     762        }
     763
     764        try {
     765            $cashflowsPayment = new CashflowsPayment(iccf_getApiKey(), iccf_getConfigurationId());
     766            $cashflowsPayment->setTestmode(iccf_getIntegrationMode());
     767
     768            $transactionStatus = $cashflowsPayment->getStatus($paymentJobReference);
     769            $paymentMethod = $cashflowsPayment->getUsedPaymentMethod();
     770
     771            if ($debugEnabled) {
     772                iccf_log('Transaction status: '.$transactionStatus.' and Payment method: '.$paymentMethod);
     773            }
     774
     775            if (strcasecmp($transactionStatus, 'SUCCESS') === 0) {
     776                $order->add_order_note(__('Status received from CashFlows Pull (status changed)', 'ic-cashflows-for-woo').': '.$transactionStatus.' '.__('with Paymentjob', 'ic-cashflows-for-woo').':<br>'.$paymentJobReference.'<br>'.__('Payment-method', 'ic-cashflows-for-woo').': <b>'.$paymentMethod.'</b>');
     777
     778                $order->payment_complete($paymentJobReference);
     779
     780                if ($debugEnabled) {
     781                    iccf_log('Customer return completed order '.$orderId.' with payment job '.$paymentJobReference.' and redirected to order received page');
     782                }
     783
     784                self::redirectAndExit($order->get_checkout_order_received_url().($utmOverride ? '?utm_nooverride=1' : ''));
     785            }
     786
     787            $order->add_order_note(__('Status received from CashFlows Pull', 'ic-cashflows-for-woo').': '.$transactionStatus.' '.__('with Paymentjob', 'ic-cashflows-for-woo').':<br>'.$paymentJobReference.'<br>'.__('Payment-method', 'ic-cashflows-for-woo').': <b>'.$paymentMethod.'</b>');
     788
     789            $holdStock = get_option('woocommerce_hold_stock_minutes');
     790
     791            if (!empty($holdStock) && ($holdStock > 0)) {
     792                $message = __('<br>If the payment is cancelled or expired, it will be cancelled automatically by WooCommerce .', 'ic-cashflows-for-woo');
     793                $order->add_order_note($message);
     794            } else {
     795                if (strcmp($transactionStatus, 'FAILURE') === 0) {
     796                    $message = __('<br>Update order status: Failed.', 'ic-cashflows-for-woo');
     797                    $order->add_order_note($message);
     798                    $order->update_status('failed');
     799                } elseif (strcmp($transactionStatus, 'CANCELLED') === 0) {
     800                    $message = __('<br>Update order status: Cancelled', 'ic-cashflows-for-woo');
     801                    $order->add_order_note($message);
     802                    $order->update_status('cancelled');
     803                } else {
     804                    $message = __('<br>Status: Unknown', 'ic-cashflows-for-woo');
     805                    $order->add_order_note($message);
     806                }
     807            }
     808
     809            self::redirectAndExit(wc_get_cart_url().($utmOverride ? '?utm_nooverride=1' : ''));
     810        } finally {
     811            self::releaseOrderLock($orderId);
     812        }
     813    }
     814
     815    public static function process_return_failure()
     816    {
     817        $debugEnabled = iccf_isDebugEnabled();
     818
     819        if (empty($_GET['real_id']) || empty($_GET['paymentjobref'])) {
     820            if ($debugEnabled) {
     821                iccf_log('Customer return failure called with empty data, redirecting to cart');
     822            }
     823
     824            self::redirectAndExit(wc_get_cart_url());
     825        } else {
     826            $orderId = sanitize_text_field($_GET['real_id']);
     827            $utmOverride = array_key_exists('utm_nooverride', $_GET);
     828
     829            if ($debugEnabled) {
     830                iccf_log('Customer return failure called with data: '.$orderId);
     831            }
     832
     833            $order = wc_get_order($orderId);
     834
     835            if ($order) {
     836                if (array_key_exists('status', $_GET)) {
     837                    $order->add_order_note(__('Customer Return: <b>Cancelled</b>', 'ic-cashflows-for-woo'));
     838                } else {
     839                    $order->add_order_note(__('Customer Return: <b>Failure</b>', 'ic-cashflows-for-woo'));
     840                }
     841            }
     842
     843            self::redirectAndExit(wc_get_cart_url().($utmOverride ? '?utm_nooverride=1' : ''));
     844        }
     845
     846        return false;
     847    }
     848
     849    public static function process_notify()
     850    {
     851        $debugEnabled = iccf_isDebugEnabled();
     852
     853        $jsonData = @file_get_contents('php://input');
     854        $orderId = sanitize_text_field($_GET['real_id']);
     855
     856        if ($debugEnabled) {
     857            iccf_log('CashFlows Notify called with data: '.$jsonData.' for Order ID: '.$orderId);
     858        }
     859
     860        if (empty($jsonData) || empty($orderId)) {
     861            if ($debugEnabled) {
     862                iccf_log('CashFlows Notify called with empty data or Order ID, returning 406 Not Acceptable');
     863            }
     864
     865            http_response_code(406);
     866            header('HTTP/1.1 406 Not Acceptable');
     867            exit('Not Acceptable');
     868        }
     869
     870        $postData = json_decode($jsonData, true);
     871
     872        if (isset($postData['paymentJobReference'])) {
     873            $paymentJobReference = sanitize_text_field($postData['paymentJobReference']);
     874            $jsonResponse = json_encode([
     875                'paymentJobReference' => sanitize_text_field($postData['paymentJobReference']),
     876                'paymentReference' => sanitize_text_field($postData['paymentReference']),
     877            ]);
     878        } elseif (isset($postData['PaymentJobReference'])) {
     879            $paymentJobReference = sanitize_text_field($postData['PaymentJobReference']);
     880            $jsonResponse = json_encode([
     881                'PaymentJobReference' => sanitize_text_field($postData['PaymentJobReference']),
     882                'PaymentReference' => sanitize_text_field($postData['PaymentReference']),
     883            ]);
     884        } else {
     885            $paymentJobReference = '';
     886            $jsonResponse = json_encode([]);
     887        }
     888
     889        $cashflowsPayment = new CashflowsPayment(iccf_getApiKey(), iccf_getConfigurationId());
     890        $cashflowsPayment->setTestmode(iccf_getIntegrationMode());
     891
     892        $order = wc_get_order($orderId);
     893
     894        if (!$order || ($order->get_meta('cf_transaction_id') !== $paymentJobReference)) {
     895            if ($debugEnabled) {
     896                iccf_log('CashFlows Notify called with Order ID: '.$orderId.' but no order found or transaction ID does not match Payment Job Reference. Returning 406 Not Acceptable');
     897            }
     898
     899            http_response_code(406);
     900            header('HTTP/1.1 406 Not Acceptable');
     901            exit('Not Acceptable');
     902        }
     903
     904        $orderStatus = $order->get_status();
     905
     906        if (in_array($orderStatus, wc_get_is_paid_statuses(), true)) {
     907            if ($debugEnabled) {
     908                iccf_log('CashFlows Notify ignored for Order ID: '.$orderId.' because the order is already paid.');
     909            }
     910
     911            http_response_code(200);
     912            header('HTTP/1.1 200 OK');
     913            exit($jsonResponse);
     914        }
     915
     916        $lockToken = self::acquireOrderLock($orderId);
     917
     918        if (!$lockToken) {
     919            if ($debugEnabled) {
     920                iccf_log('CashFlows Notify skipped for Order ID: '.$orderId.' because the order is already being processed by return flow.');
     921            }
     922
     923            http_response_code(200);
     924            header('HTTP/1.1 200 OK');
     925            exit($jsonResponse);
     926        }
     927
     928        if ($debugEnabled) {
     929            iccf_log('CashFlows Notify acquired lock for Order ID: '.$orderId.' and is processing payment status');
     930        }
     931
     932        try {
     933            $transactionStatus = $cashflowsPayment->getStatus($paymentJobReference);
     934
     935            if (empty($transactionStatus)) {
     936                $order->add_order_note(__('Unable to fetch status from CashFlows Notify with Paymentjob', 'ic-cashflows-for-woo'));
     937
     938                if ($debugEnabled) {
     939                    iccf_log('CashFlows Notify called with Payment Job Reference: '.$paymentJobReference.' but no status found. Returning 406 Not Acceptable');
     940                }
     941
     942                http_response_code(406);
     943                exit('Not Acceptable');
     944            }
     945
     946            if ($debugEnabled) {
     947                iccf_log('CashFlows Notify called with Payment Job Reference: '.$paymentJobReference.' and status: '.$transactionStatus.' for Order ID: '.$orderId);
     948            }
     949
     950            $paymentMethod = $cashflowsPayment->getUsedPaymentMethod();
     951
     952            if (in_array($orderStatus, ['pending', 'failed', 'cancelled', 'on-hold'], true)) {
     953                if (strcasecmp($transactionStatus, 'SUCCESS') === 0) {
     954                    $order->add_order_note(__('Status received from CashFlows Notify (status changed)', 'ic-cashflows-for-woo').': '.$transactionStatus.' '.__('with Paymentjob', 'ic-cashflows-for-woo').':<br>'.$paymentJobReference.'<br>'.__('Payment-method', 'ic-cashflows-for-woo').': <b>'.$paymentMethod.'</b>');
     955                    $order->payment_complete($paymentJobReference);
     956
     957                    if ($debugEnabled) {
     958                        iccf_log('CashFlows Notify completed order '.$orderId.' with payment job '.$paymentJobReference.' via notify callback');
     959                    }
     960                } else {
     961                    $order->add_order_note(__('Status received from CashFlows Notify', 'ic-cashflows-for-woo').': '.$transactionStatus.' '.__('with Paymentjob', 'ic-cashflows-for-woo').':<br>'.$paymentJobReference.'<br>'.__('Payment-method', 'ic-cashflows-for-woo').': <b>'.$paymentMethod.'</b>');
     962
     963                    $holdStock = get_option('woocommerce_hold_stock_minutes');
     964
     965                    if (!empty($holdStock) && ($holdStock > 0)) {
     966                        $message = __('<br>If the payment is cancelled or expired, it will be cancelled automatically by WooCommerce .', 'ic-cashflows-for-woo');
     967                        $order->add_order_note($message);
     968                    } else {
     969                        if (strcmp($transactionStatus, 'FAILURE') === 0) {
     970                            $message = __('<br>Update order status: Failed.', 'ic-cashflows-for-woo');
     971                            $order->add_order_note($message);
     972                            $order->update_status('failed');
     973                        } elseif (strcmp($transactionStatus, 'CANCELLED') === 0) {
     974                            $message = __('<br>Update order status: Cancelled', 'ic-cashflows-for-woo');
     975                            $order->add_order_note($message);
     976                            $order->update_status('cancelled');
     977                        } else {
     978                            $message = __('<br>Status: Unknown', 'ic-cashflows-for-woo');
     979                            $order->add_order_note($message);
     980                        }
     981                    }
     982                }
     983            } else {
     984                $order->add_order_note(__('Status received from CashFlows Notify (no status change)', 'ic-cashflows-for-woo').': '.$transactionStatus.' '.__('with Paymentjob', 'ic-cashflows-for-woo').':<br>'.$paymentJobReference.'<br>'.__('Payment-method', 'ic-cashflows-for-woo').': <b>'.$paymentMethod.'</b>');
     985            }
     986        } finally {
     987            self::releaseOrderLock($orderId);
     988        }
     989
     990        if ($debugEnabled) {
     991            iccf_log('CashFlows Notify processed, preparing JSON response for Order ID: '.$orderId.' statuscode 200 OK');
     992        }
     993
     994        http_response_code(200);
     995        header('HTTP/1.1 200 OK');
     996
     997        exit($jsonResponse);
     998    }
    647999}
  • cashflows-payments-by-ideal-checkout/trunk/gateways/cashflows.cls.php

    r3366335 r3652810  
    3939
    4040    private $aTransaction = [];
    41     private $aTransactionResults = false;
    4241    private $aPaymentJob = [];
    43     private $aErrors = [];
    44 
    45     // Payment intent
    46     private $intentTotal;
    47     private $intentCurrency;
    48     private $intentMethod;
    49     private $intentBillingData;
    50     private $intentBillingIdentity;
    51 
    52     private $intentPaymentJob;
    53     private $intentToken;
    5442    private $requestOrigin;
    5543
  • cashflows-payments-by-ideal-checkout/trunk/ic-cashflows-for-woo.php

    r3608738 r3652810  
    1111 * Plugin URI:        https://payocity.nl/en/payment-service-provider/cashflows
    1212 * Description:       Cashflows Payment Gateway accepts card payments for eCommerce transactions
    13  * Version:           2.3.7
     13 * Version:           2.3.8
    1414 * Requires at least: 5.8
    1515 * Tested up to:      7.0.1
    … …  
    3636
    3737// Define the plugin version
    38 define('ICCF_VERSION', '2.3.7');
     38define('ICCF_VERSION', '2.3.8');
    3939
    4040// Load default plugin functions
    … …  
    5656    }
    5757    add_action('admin_notices', 'iccf_doShowCurlError');
    58 }
    59 
    60 // Is WooCommerce active on this Wordpress installation?
    61 if (is_plugin_active('woocommerce/woocommerce.php') || is_plugin_active_for_network('woocommerce/woocommerce.php')) {
    62     include ICCF_ROOT_PATH . DIRECTORY_SEPARATOR . 'controllers' . DIRECTORY_SEPARATOR . 'woocommerce-cashflows.php';
    63     IdealcheckoutCashflowsForWooCommerce::init();
    64 } else {
    65     // Woocommerce isn't active, show error
    66     function iccf_doShowWoocommerceError()
    67     {
    68         echo '<div class="error"><p>Cashflows for WooCommerce plugin requires WooCommerce to be active</p></div>';
    69     }
    70     add_action('admin_notices', 'iccf_doShowWoocommerceError');
    7158}
    7259
    … …  
    330317    iccf_removeLogFiles(); // Clean up old log files
    331318
     319    $clearLogUrl = wp_nonce_url(
     320        add_query_arg([
     321            'page' => 'wc-settings',
     322            'tab' => 'cashflows_settings_tab',
     323            'iccf_clear_log' => '1',
     324        ], admin_url('admin.php')),
     325        'iccf_clear_log'
     326    );
     327
    332328    // Output the field
    333329    $html .= '
    … …  
    345341                <p><code>' . esc_html($logFile) . '</code></p>
    346342                <p>' . __('The log file is shown below. The log file is rotated when it exceeds 5MB in size. Old log files are automatically removed after the selected number of days.', 'ic-cashflows-for-woo') . '</p>
     343                <p><a href="' . esc_url($clearLogUrl) . '" class="button button-secondary">' . esc_html__('Clear log', 'ic-cashflows-for-woo') . '</a></p>
    347344                <pre style="background: #f9f9f9; border: 1px solid #ddd; padding: 10px; max-height: 400px; overflow: auto;">' . $logFileContents . '</pre>';
    348345    } else {
    349346        $html .= '<p>' . __('No log file found.', 'ic-cashflows-for-woo') . '</p>';
     347        $html .= '<p><a href="' . esc_url($clearLogUrl) . '" class="button button-secondary">' . esc_html__('Clear log', 'ic-cashflows-for-woo') . '</a></p>';
    350348    }
    351349
    … …  
    358356
    359357add_action('woocommerce_admin_field_cashflows_logs_renderer', 'cashflows_render_logs_field');
     358
     359add_action('admin_init', 'iccf_handleClearLogRequest');
     360
     361function iccf_handleClearLogRequest()
     362{
     363    if (!isset($_GET['iccf_clear_log']) || sanitize_text_field(wp_unslash($_GET['iccf_clear_log'])) !== '1') {
     364        return;
     365    }
     366
     367    if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'iccf_clear_log')) {
     368        wp_die(esc_html__('Invalid request.', 'ic-cashflows-for-woo'));
     369    }
     370
     371    if (!current_user_can('manage_woocommerce')) {
     372        wp_die(esc_html__('You do not have permission to clear the log.', 'ic-cashflows-for-woo'));
     373    }
     374
     375    iccf_clearLogFiles();
     376
     377    wp_safe_redirect(
     378        add_query_arg([
     379            'page' => 'wc-settings',
     380            'tab' => 'cashflows_settings_tab',
     381            'iccf_log_cleared' => '1',
     382        ], admin_url('admin.php'))
     383    );
     384    exit;
     385}
     386
     387add_action('admin_notices', 'iccf_showLogClearedNotice');
     388
     389function iccf_showLogClearedNotice()
     390{
     391    if (isset($_GET['iccf_log_cleared']) && sanitize_text_field(wp_unslash($_GET['iccf_log_cleared'])) === '1') {
     392        echo '<div class="updated notice notice-success"><p>' . esc_html__('Cashflows log cleared.', 'ic-cashflows-for-woo') . '</p></div>';
     393    }
     394}
  • cashflows-payments-by-ideal-checkout/trunk/includes/functions.php

    r3608738 r3652810  
    135135                        fclose($stream);
    136136                }
     137        }
     138
     139        /**
     140         * Clear all Cashflows debug log files.
     141         *
     142         * @return bool True when the log files were cleared successfully.
     143         */
     144        function iccf_clearLogFiles()
     145        {
     146                $logDir = ICCF_ROOT_PATH . 'temp' . DIRECTORY_SEPARATOR . 'logs';
     147                $logFile = $logDir . DIRECTORY_SEPARATOR . 'cashflows_debug.log';
     148
     149                if (!is_dir($logDir)) {
     150                        wp_mkdir_p($logDir);
     151                }
     152
     153                $logFiles = glob($logDir . DIRECTORY_SEPARATOR . 'cashflows_debug*.log');
     154
     155                if (!empty($logFiles) && is_array($logFiles)) {
     156                        foreach ($logFiles as $file) {
     157                                @unlink($file);
     158                        }
     159                }
     160
     161                if (!file_exists($logFile)) {
     162                        $handle = @fopen($logFile, 'w');
     163                        if ($handle) {
     164                                @fclose($handle);
     165                                @chmod($logFile, 0600);
     166                        }
     167                }
     168
     169                return true;
    137170        }
    138171
  • cashflows-payments-by-ideal-checkout/trunk/readme.txt

    r3608738 r3652810  
    44Requires Plugins: woocommerce
    55WC requires at least: 9.6
    6 WC tested up to: 10.9.4
     6WC tested up to: 11.0.1
    77Requires at least: 5.8.0
    8 Tested up to: 7.0.1
    9 Stable tag: 2.3.7
     8Tested up to: 7.1
     9Stable tag: 2.3.8
    1010Requires PHP: 7.4
    1111License: GPLv2 or later
    … …  
    8585
    8686== Changelog ==
     87
     88= 2.3.8 =
     89* Added a clear log button, to clear our logfile.
     90* Moved the return and notify to the abstract controller and included locking.
     91* Checked compatibility with Wordpress 7.1 and WooCommerce 11.0.1
    8792
    8893= 2.3.7 =
Note: See TracChangeset for help on using the changeset viewer.