sirjeff 1 week ago
commit
275a4602c7
37 changed files with 5251 additions and 0 deletions
  1. 20 0
      .gitignore
  2. 1 0
      README.md
  3. 25 0
      assets/css/laybuy-iframe.css
  4. 0 0
      assets/css/laybuy.css
  5. 9 0
      assets/css/laybuy_admin.css
  6. BIN
      assets/images/laybuy-logo.png
  7. 31 0
      assets/images/laybuy_logo_small.svg
  8. BIN
      assets/images/laybuy_logo_transparent.png
  9. 153 0
      assets/js/laybuy-admin.js
  10. 178 0
      includes/Laybuy/ApiGateway.php
  11. 276 0
      includes/Laybuy/Plugin/UpdateManager.php
  12. 137 0
      includes/Laybuy/ProcessManager.php
  13. 104 0
      includes/Laybuy/Processes/AbstractProcess.php
  14. 53 0
      includes/Laybuy/Processes/CancelQuote.php
  15. 122 0
      includes/Laybuy/Processes/CreateOrder/CompatibilityMode/Process.php
  16. 7 0
      includes/Laybuy/Processes/CreateOrder/CreateOrderAbstract.php
  17. 133 0
      includes/Laybuy/Processes/CreateOrder/WC_GT_3_6/Process.php
  18. 444 0
      includes/Laybuy/Processes/CreateOrder/WC_LT_3_6/Process.php
  19. 34 0
      includes/Laybuy/Processes/CreateQuote/CreateQuoteAbstract.php
  20. 210 0
      includes/Laybuy/Processes/CreateQuote/WC_GT_3_6/Process.php
  21. 345 0
      includes/Laybuy/Processes/CreateQuote/WC_LT_3_6/Process.php
  22. 173 0
      includes/Laybuy/Processes/RedirectPayment/CompatibilityMode/Process.php
  23. 173 0
      includes/Laybuy/Processes/RedirectPayment/Process.php
  24. 102 0
      includes/Laybuy/Processes/Refund.php
  25. 95 0
      includes/Laybuy/SupportRequest.php
  26. 28 0
      includes/admin/wysiwyg.php
  27. 120 0
      includes/assets.php
  28. 1073 0
      includes/class-wc-gateway-laybuy.php
  29. 40 0
      includes/class-wc-laybuy-helper.php
  30. 77 0
      includes/class-wc-laybuy-logger.php
  31. 5 0
      includes/constants.php
  32. 475 0
      includes/laybuy-settings.php
  33. 41 0
      includes/wc-functions.php
  34. 247 0
      laybuy-gateway-for-woocommerce.php
  35. 275 0
      readme.txt
  36. 19 0
      templates/laybuy-page-template.php
  37. 26 0
      uninstall.php

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+*.log
+wp-config.php
+wp-content/advanced-cache.php
+wp-content/backup-db/
+wp-content/backups/
+wp-content/blogs.dir/
+wp-content/cache/
+wp-content/upgrade/
+wp-content/uploads/
+wp-content/mu-plugins/
+wp-content/wp-cache-config.php
+wp-content/plugins/hello.php
+
+/.htaccess
+/license.txt
+/readme.html
+/sitemap.xml
+/sitemap.xml.gz
+.idea
+

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# WooCommerce

+ 25 - 0
assets/css/laybuy-iframe.css

@@ -0,0 +1,25 @@
+.laybuy-content-container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+}
+.laybuy-content-container:before {
+    content: "";
+    display: block;
+    padding-top: 66.6%;
+}
+
+.laybuy-content-container iframe {
+    border: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+    max-width: calc(740px + 58px);
+    margin-left: auto;
+    margin-right: auto;
+}

File diff suppressed because it is too large
+ 0 - 0
assets/css/laybuy.css


+ 9 - 0
assets/css/laybuy_admin.css

@@ -0,0 +1,9 @@
+#wp-category_pages_info_text-wrap, #wp-category_pages_info_text-wrap + .description, #wp-product_pages_info_text-wrap, #wp-product_pages_info_text-wrap + .description , #woocommerce_laybuy_cart_page_info_text, #woocommerce_laybuy_cart_page_info_text + .description, #wp-checkout_page_info_text-wrap, #wp-checkout_page_info_text-wrap + .description{
+    display:none ;
+}
+.currency-select {
+    height: 100%!important;
+}
+.credentials {
+    /*display: none;*/
+}

BIN
assets/images/laybuy-logo.png


+ 31 - 0
assets/images/laybuy_logo_small.svg

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg width="113px" height="25px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 113.5 25.5" style="enable-background:new 0 0 113.5 25.5;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#786DFF;}
+	.st1{fill:#010101;}
+</style>
+<path class="st0" d="M19.2,2.3l-2.8,2.8c-0.2,0.2-0.5,0-0.5,0l-3.6-3.7c-1.2-1.2-3-1.2-4.2,0l0,0c-1.2,1.2-1.2,3,0,4.2l6,6
+	c1.2,1.2,3,1.2,4.2,0l5-5l0.9-0.9c0.6-0.6,1.6-0.6,2.2,0c0,0,0,0,0,0c0.6,0.6,0.6,1.5,0,2.1c0,0,0,0,0,0l-0.9,0.9l-9,9.1
+	c-0.1,0.2-0.4,0.2-0.5,0c0,0,0,0,0,0L5.9,7.9c-1.2-1.2-3-1.2-4.2,0l0,0c-1.2,1.2-1.2,3,0,4.2L14,24.4l-0.1-0.1c1.2,1.2,3,1.2,4.2,0
+	l11.4-11.4l0.1-0.1c2.9-2.9,3-7.6,0.2-10.5c-2.9-2.9-7.6-3-10.5-0.2C19.3,2.1,19.2,2.2,19.2,2.3L19.2,2.3z"/>
+<path class="st1" d="M47.9,20.2h-6.6V9.7c0-0.2-0.2-0.4-0.5-0.4c-0.2,0-0.4,0.2-0.4,0.4v10.9c0,0.2,0.2,0.4,0.4,0.4h7
+	c0.2,0,0.4-0.2,0.4-0.4S48.1,20.2,47.9,20.2L47.9,20.2L47.9,20.2z"/>
+<path class="st1" d="M57.2,9.6c-0.1-0.3-0.3-0.4-0.6-0.4h0c-0.3,0-0.5,0.2-0.6,0.4l-5,10.9c0,0.1-0.1,0.2-0.1,0.3
+	c0,0.2,0.2,0.4,0.4,0.4c0.2,0,0.4-0.1,0.4-0.3l1.4-3H60l1.4,3c0.1,0.2,0.2,0.3,0.4,0.3c0.2,0,0.4-0.2,0.4-0.4c0,0,0,0,0,0
+	c0-0.1,0-0.2-0.1-0.3L57.2,9.6z M53.5,17l3.1-6.8l3.1,6.8H53.5z"/>
+<path class="st1" d="M72.9,9.2c-0.2,0-0.3,0.1-0.4,0.3l-4.3,6.1L64,9.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.2,0-0.4,0.2-0.5,0.4
+	c0,0.1,0,0.2,0.1,0.3l4.6,6.5v4.3c0,0.2,0.2,0.4,0.5,0.4c0.2,0,0.4-0.2,0.4-0.4v-4.3l4.5-6.4c0.1-0.1,0.1-0.2,0.1-0.3
+	C73.3,9.4,73.1,9.3,72.9,9.2z"/>
+<path class="st1" d="M84.2,14.9c0.8-0.5,1.5-1.2,1.5-2.5v0c0-0.7-0.3-1.5-0.8-2c-0.7-0.7-1.7-1-3.1-1h-4.2c-0.7,0-1.3,0.6-1.3,1.3
+	c0,0,0,0,0,0v9.1c0,0.7,0.6,1.3,1.3,1.3c0,0,0,0,0,0H82c2.6,0,4.4-1.1,4.4-3.2v0C86.4,16.2,85.6,15.4,84.2,14.9z M78.9,11.6h2.5
+	c1.1,0,1.7,0.4,1.7,1.2v0c0,0.9-0.7,1.2-1.9,1.2h-2.4V11.6z M83.8,17.5c0,0.9-0.7,1.3-1.8,1.3h-3.1v-2.6h3
+	C83.3,16.2,83.8,16.7,83.8,17.5L83.8,17.5z"/>
+<path class="st1" d="M98.1,9.2c-0.7,0-1.3,0.6-1.3,1.3c0,0,0,0,0,0V16c0,1.9-1,2.8-2.5,2.8s-2.5-1-2.5-2.9v-5.4
+	c0-0.7-0.6-1.3-1.3-1.2c-0.7,0-1.2,0.6-1.2,1.2V16c0,3.5,1.9,5.2,5.1,5.2s5.1-1.7,5.1-5.3v-5.4C99.4,9.8,98.9,9.2,98.1,9.2
+	C98.1,9.2,98.1,9.2,98.1,9.2z"/>
+<path class="st1" d="M111.4,9.2c-0.5,0-0.9,0.3-1.2,0.8l-2.9,4.1l-2.8-4.1c-0.3-0.5-0.7-0.8-1.3-0.8c-0.7,0-1.3,0.5-1.3,1.2
+	c0,0,0,0,0,0c0,0.3,0.1,0.7,0.3,0.9l3.8,5.1v3.3c0,0.7,0.6,1.3,1.3,1.3s1.3-0.6,1.3-1.3v-3.4l3.8-5.1c0.2-0.3,0.3-0.6,0.3-0.9
+	c0-0.7-0.5-1.2-1.1-1.3C111.5,9.2,111.4,9.2,111.4,9.2z"/>
+</svg>

BIN
assets/images/laybuy_logo_transparent.png


+ 153 - 0
assets/js/laybuy-admin.js

@@ -0,0 +1,153 @@
+jQuery(function($) {
+
+    $('#woocommerce_laybuy_environment').on('change', function () {
+        $('#woocommerce_laybuy_currency').trigger('change');
+    }).trigger('change');
+
+    function showCredentials(currency) {
+
+        var env = $('#woocommerce_laybuy_environment').val();
+        var envHide = env == 'production' ? 'sandbox' : 'production';
+
+        $('#woocommerce_laybuy_' + env + '_' + currency + '_merchant_id').closest('tr').show();
+        $('#woocommerce_laybuy_' + env + '_' + currency + '_api_key').closest('tr').show();
+
+        $('#woocommerce_laybuy_' + envHide + '_' + currency + '_merchant_id').closest('tr').hide();
+        $('#woocommerce_laybuy_' + envHide + '_' + currency + '_api_key').closest('tr').hide();
+    }
+
+    function hideAllCredentials() {
+
+        var currencies = [];
+
+        currenciesList = $('#woocommerce_laybuy_currency option');
+
+        currenciesList.each(function(){
+            currencies.push($(this).val());
+        });
+
+        currencies.push('global');
+
+        for (var i in currencies) {
+
+            var currency = currencies[i];
+
+            $('#woocommerce_laybuy_sandbox_' + currency + '_merchant_id').closest('tr').hide();
+            $('#woocommerce_laybuy_sandbox_' + currency + '_api_key').closest('tr').hide();
+
+            $('#woocommerce_laybuy_production_' + currency + '_merchant_id').closest('tr').hide();
+            $('#woocommerce_laybuy_production_' + currency + '_api_key').closest('tr').hide();
+
+        };
+    }
+
+    if ($('#woocommerce_laybuy_laybuy_advance_setting').is(':checked')) {
+        $("#wp-category_pages_info_text-wrap").show();
+        $("#wp-category_pages_info_text-wrap .description").show();
+        $("#wp-product_pages_info_text-wrap").show();
+        $("#wp-product_pages_info_text-wrap + .description").show();
+        $("#woocommerce_laybuy_cart_page_info_text").show();
+        $("#woocommerce_laybuy_cart_page_info_text .description").show();
+        $("#wp-checkout_page_info_text-wrap").show();
+        $("#wp-checkout_page_info_text-wrap .description").show();
+        $("#wp-checkout_page_info_text-wrap").closest('tr').show();
+        $("#woocommerce_laybuy_laybuy_page_enabled").closest('tr').show();
+        $("#woocommerce_laybuy_laybuy_compatibility_mode").closest('tr').show();
+        $("#woocommerce_laybuy_laybuy_geolocate_ip").closest('tr').show();
+        $("#woocommerce_laybuy_laybuy_price_breakdown_out_of_stock").closest('tr').show();
+        $("#woocommerce_laybuy_laybuy_billing_phone_field").closest('tr').show();
+    }
+
+    $('#woocommerce_laybuy_laybuy_advance_setting').on('change', function () {
+        var $elEnabled = $('#woocommerce_laybuy_laybuy_advance_setting');
+        if ($elEnabled.is(':checked')) {
+            $("#wp-category_pages_info_text-wrap").show();
+            $("#wp-category_pages_info_text-wrap .description").show();
+            $("#wp-product_pages_info_text-wrap").show();
+            $("#wp-product_pages_info_text-wrap + .description").show();
+            $("#woocommerce_laybuy_cart_page_info_text").show();
+            $("#woocommerce_laybuy_cart_page_info_text .description").show();
+            $("#wp-checkout_page_info_text-wrap").show();
+            $("#wp-checkout_page_info_text-wrap .description").show();
+            $("#wp-checkout_page_info_text-wrap").closest('tr').show();
+            $("#woocommerce_laybuy_laybuy_page_enabled").closest('tr').show();
+            $("#woocommerce_laybuy_laybuy_compatibility_mode").closest('tr').show();
+            $('[for="woocommerce_laybuy_currency"]').text('Currencies');
+            $("#woocommerce_laybuy_laybuy_geolocate_ip").closest('tr').show();
+            $("#woocommerce_laybuy_laybuy_price_breakdown_out_of_stock").closest('tr').show();
+            $("#woocommerce_laybuy_laybuy_billing_phone_field").closest('tr').show();
+        } else {
+
+            $("#wp-category_pages_info_text-wrap").hide();
+            $("#wp-category_pages_info_text-wrap .description").hide();
+            $("#wp-product_pages_info_text-wrap").hide();
+            $("#wp-product_pages_info_text-wrap + .description").hide();
+            $("#woocommerce_laybuy_cart_page_info_text").hide();
+            $("#woocommerce_laybuy_cart_page_info_text .description").hide();
+            $("#wp-checkout_page_info_text-wrap").hide();
+            $("#wp-checkout_page_info_text-wrap .description").hide();
+            $("#wp-checkout_page_info_text-wrap").closest('tr').hide();
+            $('[for="woocommerce_laybuy_currency"]').text('Default Currency');
+            $("#woocommerce_laybuy_laybuy_compatibility_mode").closest('tr').hide();
+            $("#woocommerce_laybuy_laybuy_page_enabled").closest('tr').hide();
+            $("#woocommerce_laybuy_laybuy_geolocate_ip").closest('tr').hide();
+            $("#woocommerce_laybuy_laybuy_price_breakdown_out_of_stock").closest('tr').hide();
+            $("#woocommerce_laybuy_laybuy_billing_phone_field").closest('tr').hide();
+        }
+    }).trigger('change');
+
+    hideAllCredentials();
+
+    $('#woocommerce_laybuy_currency').on('change', function () {
+
+        hideAllCredentials();
+        var $self = $(this);
+
+        if ($('#woocommerce_laybuy_global').is(':checked')) {
+            showCredentials('global');
+        } else {
+            var currencies = $('#woocommerce_laybuy_currency').val();
+            for (var i in currencies) {
+                showCredentials(currencies[i]);
+            }
+        }
+    }).trigger('change');
+
+    $('#woocommerce_laybuy_global').on('change', function () {
+        var currencies = $('#woocommerce_laybuy_currency').val();
+
+        hideAllCredentials();
+
+        if ($(this).is(':checked')) {
+            showCredentials('global');
+        } else {
+            for (var i in currencies) {
+                showCredentials(currencies[i]);
+            }
+        }
+    });
+
+    $('#laybuy_send_support_request').on('click', function (e) {
+
+        e.preventDefault();
+        var $self = $(this);
+
+        $self.text('Sending...');
+
+        var data = {
+            action: 'send_laybuy_support_request'
+        };
+
+        jQuery.post( ajaxurl, data, function(response) {
+            if (response.success === true){
+                alert('Your support request has been sent!');
+            } else {
+                alert('Wait, something went wrong or too many requests, try later');
+            }
+        }).done(function(){
+
+        }).always(function () {
+            $self.text('Send Support Request');
+        });
+    });
+});

+ 178 - 0
includes/Laybuy/ApiGateway.php

@@ -0,0 +1,178 @@
+<?php
+
+class Laybuy_ApiGateway
+{
+    const PRODUCTION_API_ENDPOINT = 'https://api.laybuy.com/';
+    const SANDBOX_API_ENDPOINT = 'https://sandbox-api.laybuy.com/';
+
+    const PAYMENT_STATUS_SUCCESS   = 'SUCCESS';
+    const PAYMENT_STATUS_ERROR     = 'ERROR';
+    const PAYMENT_STATUS_DECLINED  = 'DECLINED';
+    const PAYMENT_STATUS_CANCELLED = 'CANCELLED';
+
+
+    protected $api_endpoint;
+    protected $settings;
+
+    public function __construct($settings)
+    {
+        $this->settings = $settings;
+
+        if (!isset($settings['environment'])) {
+            return;
+        }
+
+        if ($settings['environment'] == 'sandbox') {
+            $this->api_endpoint = self::SANDBOX_API_ENDPOINT;
+        } else {
+            $this->api_endpoint = self::PRODUCTION_API_ENDPOINT;
+        }
+    }
+
+    public function createOrder($data)
+    {
+        return $this->post_to_api($this->api_endpoint . 'order/create', $data);
+    }
+
+    public function confirmOrder($data)
+    {
+        return $this->post_to_api($this->api_endpoint . 'order/confirm', $data);
+    }
+
+    public function refund($data)
+    {
+        return $this->post_to_api($this->api_endpoint . 'order/refund', $data);
+    }
+
+    /**
+     * Get the Merchant ID from our user settings.
+     *
+     * @since	2.0.0
+     * @return	string
+     */
+    public function get_merchant_id() {
+
+        if ($this->isGlobal()) {
+            return $this->settings["{$this->settings['environment']}_global_merchant_id"];
+        }
+
+        $currency = get_woocommerce_currency();
+
+        if (in_array($currency, $this->settings['currency'])) {
+            return $this->settings["{$this->settings['environment']}_{$currency}_merchant_id"];
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the Secret Key from our user settings.
+     *
+     * @since	2.0.0
+     * @return	string
+     */
+    public function get_api_key() {
+
+        if ($this->isGlobal()) {
+            return $this->settings["{$this->settings['environment']}_global_api_key"];
+        }
+
+        $currency = get_woocommerce_currency();
+
+        if (in_array($currency, $this->settings['currency'])) {
+            return $this->settings["{$this->settings['environment']}_{$currency}_api_key"];
+        }
+
+        return false;
+    }
+
+    /**
+     * POST to an API endpoint and load the response.
+     */
+    public function post_to_api($url, $data) {
+
+        WC_Laybuy_Logger::log("POST {$url}");
+
+        $response = wp_remote_post( $url, array(
+            'timeout' => 30,
+            'headers' => array(
+                'Authorization' => $this->build_authorization_header(),
+                'User-Agent' => $this->build_user_agent_header(),
+                'Content-Type' => 'application/json',
+                'Accepts' => 'application/json'
+            ),
+            'body' => json_encode($data)
+        ) );
+
+        if (!is_wp_error( $response )) {
+            $body = json_decode(wp_remote_retrieve_body( $response ));
+
+            if (!is_null($body)) {
+                return $body;
+            }
+
+            WC_Laybuy_Logger::log("Failed to parse Laybuy response: " . var_export( $response, true));
+        } else {
+            # Unable to establish a secure connection with the Laybuy API endpoint.
+            # Likely a TLS or network error.
+            # Log the error details.
+            foreach ($response->errors as $code => $messages_arr) {
+                $messages_str = implode("\n", $messages_arr);
+                WC_Laybuy_Logger::error("API NETWORK ERROR! Code: \"{$code}\"; Message(s):\n" . $messages_str);
+            }
+
+            # Get CloudFlare Header for the error
+            $cf_ray = wp_remote_retrieve_header($response, "cf-ray");
+
+            if (!empty($cf_ray)) {
+                WC_Laybuy_Logger::error("Error CF-Ray: " .  $cf_ray);
+            }
+            else {
+                WC_Laybuy_Logger::error("No CF-Ray Detected");
+            }
+
+            # Return the WP_Error object.
+            return $response;
+        }
+
+        return false;
+    }
+
+
+    public function build_authorization_header() {
+        return 'Basic ' . base64_encode($this->get_merchant_id() . ':' . $this->get_api_key());
+    }
+
+    /**
+     * Build the Laybuy User-Agent header for use with the APIs.
+     */
+    private function build_user_agent_header() {
+        global $wp_version;
+
+        $plugin_version = WC_Laybuy::$version;
+        $php_version = PHP_VERSION;
+        $woocommerce_version = WC()->version;
+        $merchant_id = $this->get_merchant_id();
+
+        $extra_detail_1 = '';
+        $extra_detail_2 = '';
+
+        $matches = array();
+        if (array_key_exists('SERVER_SOFTWARE', $_SERVER) && preg_match('/^[a-zA-Z0-9]+\/\d+(\.\d+)*/', $_SERVER['SERVER_SOFTWARE'], $matches)) {
+            $s = $matches[0];
+            $extra_detail_1 .= "; {$s}";
+        }
+
+        if (array_key_exists('REQUEST_SCHEME', $_SERVER) && array_key_exists('HTTP_HOST', $_SERVER)) {
+            $s = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
+            $extra_detail_2 .= " {$s}";
+        }
+
+        return "Laybuy Gateway for WooCommerce/{$plugin_version} (PHP/{$php_version}; WordPress/{$wp_version}; WooCommerce/{$woocommerce_version}; Merchant/{$merchant_id}{$extra_detail_1}){$extra_detail_2}";
+    }
+
+    protected function isGlobal()
+    {
+        return $this->settings['global'] == 'yes';
+    }
+}

+ 276 - 0
includes/Laybuy/Plugin/UpdateManager.php

@@ -0,0 +1,276 @@
+<?php
+
+class Laybuy_Plugin_UpdateManager
+{
+    public $pluginVersion;
+    public $pluginDbVersion;
+
+    public function setPluginVersion($pluginVersion)
+    {
+        $this->pluginVersion = $pluginVersion;
+        return $this;
+    }
+
+    public function setPluginDbVersion($pluginDbVersion)
+    {
+        $this->pluginDbVersion = $pluginDbVersion;
+        return $this;
+    }
+
+    public function update()
+    {
+        // update to 5.0.6
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.0.6', '<')) {
+            $this->update_5_0_6();
+        }
+
+        // update to 5.0.11
+        if (!$this->pluginDbVersion || version_compare($this->pluginDbVersion, '5.0.11', '<')) {
+            $this->update_5_0_11();
+        }
+
+        //  update to 5.0.13
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.0.13', '<')) {
+            $this->update_5_0_13();
+        }
+
+        // update to 5.1.0
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.1.0', '<')) {
+            $this->update_5_1_0();
+        }
+
+        // update to 5.1.5
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.1.5', '<')) {
+            $this->update_5_1_5();
+        }
+
+        // update to 5.1.11
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.1.11', '<')) {
+            $this->update_5_1_11();
+        }
+
+        // update to 5.2.4
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.2.4', '<')) {
+            $this->update_5_2_4();
+        }
+
+        // update to 5.2.5
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.2.5', '<')) {
+            $this->update_5_2_5();
+        }
+
+        // update to 5.2.6
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.2.6', '<')) {
+            $this->update_5_2_6();
+        }
+
+        // update to 5.2.8
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.2.8', '<')) {
+            $this->update_5_2_8();
+        }
+
+        // update to 5.2.9
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.2.9', '<')) {
+            $this->update_5_2_9();
+        }
+
+        // update to 5.3.8
+        if ($this->pluginDbVersion && version_compare($this->pluginDbVersion, '5.3.8', '<')) {
+            $this->update_5_3_8();
+        }
+
+        $this->fixCurrentPluginVersion();
+    }
+
+    public function update_5_0_11()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        if (empty($settings['product_pages_info_text'])) {
+            $settings['product_pages_info_text'] = 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with <a href="#" id="laybuy-what-is-modal">[laybuy_logo] <span style="font-size: 12px"><u>what\'s this?</u></span></a>';
+        }
+
+        if (empty($settings['category_pages_info_text'])) {
+            $settings['category_pages_info_text'] = 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with <span id="laybuy-what-is-modal" class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>';
+        }
+
+        if (empty($settings['cart_page_info_text'])) {
+            $settings['cart_page_info_text'] = '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]<span id="laybuy-what-is-modal"><u style="font-size: 11px">what\'s this?</u></span></td></tr>';
+        }
+
+        if (empty($settings['checkout_page_info_text'])) {
+            $settings['checkout_page_info_text'] = '<div class="laybuy-checkout-content "><p class="title"> Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />';
+        }
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_0_6()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['sandbox_global_merchant_id']    = '';
+        $settings['sandbox_global_api_key']        = '';
+        $settings['production_global_merchant_id'] = '';
+        $settings['production_global_api_key']     = '';
+        $settings['global'] = 'yes';
+        $settings['currency'] = [];
+
+        $currencies = ['NZD', 'AUD', 'GBP', 'USD'];
+        $activeCurrencies = [];
+        $apiProductionKeys = [];
+        $apiSandboxKeys = [];
+
+        foreach ($currencies as $currency) {
+
+            if (
+                !empty($settings["production_{$currency}_api_key"]) ||
+                !empty($settings["sandbox_{$currency}_api_key"])
+            ) {
+                $activeCurrencies[] = $currency;
+            }
+
+            if (!empty($settings["production_{$currency}_api_key"])) {
+                $apiProductionKeys[$settings["production_{$currency}_merchant_id"]] = $settings["production_{$currency}_api_key"];
+            }
+
+            if (!empty($settings["sandbox_{$currency}_api_key"])) {
+                $apiSandboxKeys[$settings["sandbox_{$currency}_merchant_id"]] = $settings["sandbox_{$currency}_api_key"];
+            }
+        }
+
+        if (count(array_unique($apiProductionKeys)) == 1) {
+            $settings['production_global_merchant_id'] = key($apiProductionKeys);
+            $settings['production_global_api_key']     = $apiProductionKeys[key($apiProductionKeys)];
+        }
+
+        if (count(array_unique($apiSandboxKeys)) == 1) {
+            $settings['sandbox_global_merchant_id'] = key($apiSandboxKeys);
+            $settings['sandbox_global_api_key'] = $apiSandboxKeys[key($apiSandboxKeys)];
+        }
+
+        if (
+            count(array_unique($apiProductionKeys)) > 1 ||
+            count(array_unique($apiSandboxKeys)) > 1
+        ) {
+            $settings['global'] = 'no';
+        }
+
+        $settings['currency'] = $activeCurrencies;
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_0_13()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+        $settings['cart_page_info_text'] = '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]<span id="laybuy-what-is-modal"><u style="font-size: 11px">what\'s this?</u></span></td></tr>';
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_1_0()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+        $settings['laybuy_compatibility_mode'] = 'no';
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_1_5()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['NZD_pay_limit_min'] = 0.06;
+        $settings['NZD_pay_limit_max'] = 1500;
+
+        $settings['AUD_pay_limit_min'] = 0.06;
+        $settings['AUD_pay_limit_max'] = 1200;
+
+        $settings['GBP_pay_limit_min'] = 0.06;
+        $settings['GBP_pay_limit_max'] = 720;
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_1_11()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['product_price_breakdown_hook_priority'] = 11;
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_2_4()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['laybuy_geolocate_ip'] = 'no';
+        $settings['laybuy_logo_theme'] = 'white';
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_2_5()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['sandbox_USD_merchant_id']    = '';
+        $settings['sandbox_USD_api_key']        = '';
+        $settings['production_USD_merchant_id'] = '';
+        $settings['production_USD_api_key']     = '';
+
+        $settings['USD_pay_limit_min'] = 0.06;
+        $settings['USD_pay_limit_max'] = 1200;
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_2_6()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+        $product_types = [];
+        foreach (wc_get_product_types() as $value => $label) {
+            $product_types[] = $value;
+        }
+
+        $settings['product_types'] = $product_types;
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_2_8()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['laybuy_price_breakdown_out_of_stock'] = 'no';
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_2_9()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['laybuy_billing_phone_field'] = 'billing_phone';
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function update_5_3_8()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $settings['product_pages_info_text'] = 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]';
+        $settings['category_pages_info_text'] = 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]';
+        $settings['cart_page_info_text'] = '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>';
+        $settings['checkout_page_info_text'] = '<div class="laybuy-checkout-content "><p class="title"> Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />';
+
+        update_option('woocommerce_laybuy_settings', $settings);
+    }
+
+    public function fixCurrentPluginVersion()
+    {
+        if (version_compare($this->pluginDbVersion, $this->pluginVersion, '<')) {
+            update_option('wc_laybuy_version', $this->pluginVersion);
+        }
+    }
+}

+ 137 - 0
includes/Laybuy/ProcessManager.php

@@ -0,0 +1,137 @@
+<?php
+
+require_once dirname(__FILE__) . '/Processes/AbstractProcess.php';
+require_once dirname(__FILE__) . '/Processes/CancelQuote.php';
+require_once dirname(__FILE__) . '/Processes/Refund.php';
+require_once dirname(__FILE__) . '/Processes/CreateOrder/CompatibilityMode/Process.php';
+require_once dirname(__FILE__) . '/Processes/CreateOrder/CreateOrderAbstract.php';
+require_once dirname(__FILE__) . '/Processes/CreateOrder/WC_LT_3_6/Process.php';
+require_once dirname(__FILE__) . '/Processes/CreateOrder/WC_GT_3_6/Process.php';
+require_once dirname(__FILE__) . '/Processes/CreateQuote/CreateQuoteAbstract.php';
+require_once dirname(__FILE__) . '/Processes/CreateQuote/WC_LT_3_6/Process.php';
+require_once dirname(__FILE__) . '/Processes/CreateQuote/WC_GT_3_6/Process.php';
+require_once dirname(__FILE__) . '/Processes/RedirectPayment/Process.php';
+require_once dirname(__FILE__) . '/Processes/RedirectPayment/CompatibilityMode/Process.php';
+
+class Laybuy_ProcessManager
+{
+    private static $instance;
+
+    private $apiGateway;
+    private $wcGateway;
+    private $compatibilityMode;
+
+    public function setApiGateway($gateway)
+    {
+        $this->apiGateway = $gateway;
+        return $this;
+    }
+
+    public function getApiGateway()
+    {
+        return $this->apiGateway;
+    }
+
+    public function setWcGateway($gateway)
+    {
+        $this->wcGateway = $gateway;
+        return $this;
+    }
+
+    public function getWcGateway()
+    {
+        return $this->wcGateway;
+    }
+
+    public function setCompatibilityMode($mode)
+    {
+        $this->compatibilityMode = $mode;
+        return $this;
+    }
+
+    public function getCompatibilityMode()
+    {
+        return $this->compatibilityMode;
+    }
+
+    public static function getInstance()
+    {
+        if (!self::$instance) {
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    public function createOrderFromQuote($quote_id)
+    {
+        if (version_compare( WC_VERSION, '3.6', '<' )) {
+            $process = new Laybuy_Processes_CreateOrder_WC_LT_3_6_Process();
+        } else {
+            $process = new Laybuy_Processes_CreateOrder_WC_GT_3_6_Process();
+        }
+
+        $process->setProcessManager($this);
+        $process->setQuoteId($quote_id);
+        return $process->execute();
+    }
+
+    public function createQuote($checkout)
+    {
+        if (version_compare( WC_VERSION, '3.6', '<' )) {
+            $process = new Laybuy_Processes_CreateQuote_WC_LT_3_6_Process();
+        } else {
+            $process = new Laybuy_Processes_CreateQuote_WC_GT_3_6_Process();
+        }
+
+        $process->setProcessManager($this);
+        $process->setCheckout($checkout);
+        $process->execute();
+    }
+
+    public function createOrder($orderId)
+    {
+        $process = new Laybuy_Processes_CreateOrder_CompatibilityMode_Process();
+
+        $process->setOrderId($orderId);
+        $process->setProcessManager($this);
+        $process->execute();
+    }
+
+    public function refund($orderId, $amount, $reason)
+    {
+        $process = new Laybuy_Processes_Refund();
+
+        $process->setProcessManager($this)
+            ->setOrderId($orderId)
+            ->setAmount($amount)
+            ->setReason($reason);
+
+        return $process->execute();
+    }
+
+    public function cancelQuote($quoteId, $status = 'cancelled')
+    {
+        $process = new Laybuy_Processes_CancelQuote();
+        return $process->setQuoteId($quoteId)
+            ->setStatus($status)
+            ->execute();
+    }
+
+    public function processRedirectPayment($id, $status, $token)
+    {
+        if ($this->compatibilityMode) {
+            $process = new Laybuy_Processes_RedirectPayment_CompatibilityMode_Process();
+            $process->setOrderId($id);
+        } else {
+            $process = new Laybuy_Processes_RedirectPayment_Process();
+            $process->setQuoteId($id);
+        }
+
+        return $process
+            ->setStatus($status)
+            ->setToken($token)
+            ->setProcessManager($this)
+            ->execute();
+    }
+}

+ 104 - 0
includes/Laybuy/Processes/AbstractProcess.php

@@ -0,0 +1,104 @@
+<?php
+
+abstract class Laybuy_Processes_AbstractProcess
+{
+    const PAYMENT_METHOD_LAYBUY = 'laybuy';
+
+    protected $processManager;
+
+    abstract public function execute();
+
+    public function setProcessManager(Laybuy_ProcessManager $processManager)
+    {
+        $this->processManager = $processManager;
+        return $this;
+    }
+
+    public function getProcessManager()
+    {
+        return $this->processManager;
+    }
+
+    /**
+     * Function for encoding data for storage as WP Post Meta.
+     *
+     * @return	string
+     */
+    protected function _encode($data)
+    {
+        return base64_encode(serialize($data));
+    }
+
+    /**
+     * Function for decoding data from storage as WP Post Meta.
+     * @return	mixed
+     */
+    protected function _decode($string)
+    {
+        return unserialize(base64_decode($string));
+    }
+
+    protected function _makeUniqueReference($id) {
+        return '#' . uniqid() . $id . time();
+    }
+
+    protected function _buildReturnUrl($quote_id, $nonce, $extra_args = []) {
+
+        $site_url = get_site_url();
+        $site_url_components = parse_url($site_url);
+        $return_url = '';
+
+        // Scheme
+
+        if (isset($site_url_components['scheme'])) {
+            $return_url .= $site_url_components['scheme'] . '://';
+        }
+
+        // Host
+
+        if (isset($site_url_components['host'])) {
+            $return_url .= $site_url_components['host'];
+        }
+
+        // Port
+
+        if (isset($site_url_components['port'])) {
+            $return_url .= ':' . $site_url_components['port'];
+        }
+
+        // Path
+
+        if (isset($site_url_components['path'])) {
+            $return_url .= rtrim($site_url_components['path'], '/') . '/';
+        } else {
+            $return_url .= '/';
+        }
+
+        // Query
+
+        $existing_args = array();
+
+        if (isset($site_url_components['query'])) {
+            parse_str($site_url_components['query'], $existing_args);
+        }
+
+        $args = array(
+            'gateway_id' => constant('WC_GATEWAY_LAYBUY_ID'),
+            'quote_id' => $quote_id,
+            'post_type' => 'laybuy_quote',
+            '_wpnonce' => $nonce
+        );
+
+        $args = array_merge($existing_args, $args, $extra_args);
+
+        $return_url .= '?' . http_build_query($args);
+
+        // Fragment
+
+        if (isset($site_url_components['fragment'])) {
+            $return_url .= '#' . $site_url_components['fragment'];
+        }
+
+        return $return_url;
+    }
+}

+ 53 - 0
includes/Laybuy/Processes/CancelQuote.php

@@ -0,0 +1,53 @@
+<?php
+
+class Laybuy_Processes_CancelQuote extends Laybuy_Processes_AbstractProcess
+{
+    public $quoteId;
+    public $status;
+
+    public function setQuoteId($quoteId)
+    {
+        $this->quoteId = $quoteId;
+        return $this;
+    }
+
+    public function getQuoteId()
+    {
+        return $this->quoteId;
+    }
+
+    public function setStatus($status)
+    {
+        $this->status = $status;
+        return $this;
+    }
+
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    public function execute()
+    {
+        global $wpdb;
+
+        $quote_id = $this->getQuoteId();
+        $status = $this->getStatus();
+
+        WC_Laybuy_Logger::log("Cancelling WP Quote with ID:{$quote_id}");
+
+        # Mark the quote as cancelled.
+        update_post_meta( $quote_id, 'status', $status );
+
+        # Don't use `wp_trash_post` or `wp_delete_post`
+        # because we don't want any hooks to fire.
+
+        WC_Laybuy_Logger::log("Running DB query: 'DELETE FROM `{$wpdb->postmeta}` WHERE `post_id` = $quote_id'");
+        WC_Laybuy_Logger::log($wpdb->query( $wpdb->prepare( "DELETE FROM `{$wpdb->postmeta}` WHERE `post_id` = %d", $quote_id ) ) . " row(s) deleted from `{$wpdb->postmeta}` table.");
+
+        WC_Laybuy_Logger::log("Running DB query: 'DELETE FROM `{$wpdb->posts}` WHERE `ID` = $quote_id LIMIT 1'");
+        WC_Laybuy_Logger::log($wpdb->query( $wpdb->prepare( "DELETE FROM `{$wpdb->posts}` WHERE `ID` = %d LIMIT 1", $quote_id ) ) . " row(s) deleted from `{$wpdb->posts}` table.");
+
+        return true;
+    }
+}

+ 122 - 0
includes/Laybuy/Processes/CreateOrder/CompatibilityMode/Process.php

@@ -0,0 +1,122 @@
+<?php
+
+class Laybuy_Processes_CreateOrder_CompatibilityMode_Process extends Laybuy_Processes_AbstractProcess
+{
+    public $orderId;
+
+    public function setOrderId($orderId)
+    {
+        $this->orderId = $orderId;
+    }
+
+    public function getOrderId()
+    {
+        return $this->orderId;
+    }
+
+    public function execute()
+    {
+        # Get posted data.
+        $order_id = $this->getOrderId();
+
+        if (!$order_id) {
+            throw new InvalidArgumentException(
+                'Parameter order_id is not set. Please call setCheckout before executing the process'
+            );
+        }
+
+        $order = new WC_Order( $order_id );
+        $cart = WC()->cart;
+
+        $nonce = wp_create_nonce( "laybuy_{$order_id}_create" );
+        update_post_meta( $order_id, '_laybuy_order_nonce', $nonce );
+
+        $apiData = array(
+            'amount'    => $cart->get_total( 'edit' ),
+            'returnUrl' => $this->_buildReturnUrl($order_id, $nonce, ['compatibility' => 1]),
+            'merchantReference' => $this->_makeUniqueReference($order_id),
+            'customer' => array(
+                'firstName' => $order->get_billing_first_name(),
+                'lastName' => $order->get_billing_last_name(),
+                'email' => $order->get_billing_email(),
+                'phone' => $order->get_billing_phone()
+            ),
+            'billingAddress' => array(
+                "address1" => $order->get_billing_address_1(),
+                "city" => $order->get_billing_city(),
+                "postcode" => $order->get_billing_postcode(),
+                "country" => $order->get_billing_country(),
+            ),
+            'items' => array()
+        );
+
+        $discount_tax = $cart->get_discount_tax();
+        $cart_tax = $cart->get_cart_contents_tax() + $cart->get_fee_tax();
+        $shipping_tax = $cart->get_shipping_tax();
+        $total = $cart->get_total( 'edit' );
+        $shipping_total = $cart->get_shipping_total();
+
+        $apiData['tax'] = floatval($cart_tax + $shipping_tax + $discount_tax);
+
+        // items total
+        $items_total = $total;
+
+        // shipping
+        if ($shipping_total > 0) {
+            $apiData['items'][] = array(
+                'id' => 'shipping_fee_for_order#' . $order_id,
+                'description' => 'Shipping fee for this order',
+                'quantity' => '1',
+                'price' =>  $shipping_total
+            );
+            $items_total -= $shipping_total;
+        }
+
+        // tax
+        if ($cart_tax) {
+            $apiData['items'][] = array(
+                'id' => 'total_tax_amount_for_order#' . $order_id,
+                'description' => 'Tax amount for this order',
+                'quantity' => '1',
+                'price' => $cart_tax + $shipping_tax + $discount_tax
+            );
+            $items_total -= ($cart_tax + $shipping_tax + $discount_tax);
+        }
+
+        $apiData['items'][] = [
+            'id'          => 'item_for_order___#' . $order_id,
+            'description' => 'Purchase from ' . get_bloginfo('name'),
+            'quantity'    => 1,
+            'price'       => $items_total
+        ];
+
+        WC_Laybuy_Logger::log("Sending a request to create a Laybuy Order");
+        WC_Laybuy_Logger::log(print_r($apiData, true));
+
+        $gateway = $this->getProcessManager()->getApiGateway();
+        // Send the order data to Laybuy to get a token.
+        $response = $gateway->createOrder($apiData);
+
+        if ($response === false || $response->result === Laybuy_ApiGateway::PAYMENT_STATUS_ERROR) {
+
+            # Log the error and return a truthy integer (otherwise WooCommerce will not bypass the standard order creation process).
+            WC_Laybuy_Logger::error("Laybuy_ApiGateway::createOrder() returned -2 (Laybuy did not provide a token for this order.)");
+            WC_Laybuy_Logger::error("API Payload: " . print_r($apiData, true));
+            WC_Laybuy_Logger::error("API Response: " . var_export($response, true));
+
+            return -2;
+        }
+
+        $result = array(
+            'result'	=> 'success',
+            'redirect'	=> $response->paymentUrl
+        );
+
+        if ( is_ajax() ) {
+            wp_send_json( $result );
+        } else {
+            wp_redirect( $result['redirect'] );
+            exit;
+        }
+    }
+}

+ 7 - 0
includes/Laybuy/Processes/CreateOrder/CreateOrderAbstract.php

@@ -0,0 +1,7 @@
+<?php
+
+abstract class Laybuy_Processes_CreateOrder_CreateOrderAbstract extends Laybuy_Processes_AbstractProcess
+{
+    abstract public function setQuoteId($quoteId);
+    abstract public function getQuoteId();
+}

+ 133 - 0
includes/Laybuy/Processes/CreateOrder/WC_GT_3_6/Process.php

@@ -0,0 +1,133 @@
+<?php
+
+class Laybuy_Processes_CreateOrder_WC_GT_3_6_Process extends Laybuy_Processes_CreateOrder_CreateOrderAbstract
+{
+    public $quoteId;
+
+    public function setQuoteId($quoteId)
+    {
+        $this->quoteId = $quoteId;
+    }
+
+    public function getQuoteId()
+    {
+        return $this->quoteId;
+    }
+
+    public function execute()
+    {
+        $quote_id = $this->getQuoteId();
+
+        WC_Laybuy_Logger::log("Creating an WC order from a quote {$quote_id}.");
+        
+        # Get persisted data
+
+        $checkout = WC()->checkout;
+        $data = $this->_decode(get_post_meta( $quote_id, 'posted', true ));
+        $_POST = $this->_decode(get_post_meta( $quote_id, '$_POST', true ));
+        $cart = $this->_decode(get_post_meta( $quote_id, 'cart', true ));
+
+        $cart_hash = $this->_decode(get_post_meta( $quote_id, 'cart_hash', true ));
+
+        $chosen_shipping_methods = $this->_decode(get_post_meta( $quote_id, 'chosen_shipping_methods', true ));
+        $shipping_packages = $this->_decode(get_post_meta( $quote_id, 'shipping_packages', true ));
+
+        $customer_id = $this->_decode(get_post_meta( $quote_id, 'customer_id', true ));
+        $order_vat_exempt = $this->_decode(get_post_meta( $quote_id, 'order_vat_exempt', true ));
+        $currency = $this->_decode(get_post_meta( $quote_id, 'currency', true ));
+        $prices_include_tax = $this->_decode(get_post_meta( $quote_id, 'prices_include_tax', true ));
+        $customer_ip_address = $this->_decode(get_post_meta( $quote_id, 'customer_ip_address', true ));
+        $customer_user_agent = $this->_decode(get_post_meta( $quote_id, 'customer_user_agent', true ));
+        $customer_note = $this->_decode(get_post_meta( $quote_id, 'customer_note', true ));
+        $payment_method = $this->_decode(get_post_meta( $quote_id, 'payment_method', true ));
+        $shipping_total = $this->_decode(get_post_meta( $quote_id, 'shipping_total', true ));
+        $discount_total = $this->_decode(get_post_meta( $quote_id, 'discount_total', true ));
+        $discount_tax = $this->_decode(get_post_meta( $quote_id, 'discount_tax', true ));
+        $cart_tax = $this->_decode(get_post_meta( $quote_id, 'cart_tax', true ));
+        $shipping_tax = $this->_decode(get_post_meta( $quote_id, 'shipping_tax', true ));
+        $total = $this->_decode(get_post_meta( $quote_id, 'total', true ));
+
+        try {
+
+            wp_delete_post( $quote_id, true );
+
+            /**
+             * @see WC_Checkout::create_order
+             */
+
+            $order = new WC_Order();
+
+            $fields_prefix = array(
+                'shipping' => true,
+                'billing'  => true,
+            );
+
+            $shipping_fields = array(
+                'shipping_method' => true,
+                'shipping_total'  => true,
+                'shipping_tax'    => true,
+            );
+
+            foreach ( $data as $key => $value ) {
+                if ( is_callable( array( $order, "set_{$key}" ) ) ) {
+                    $order->{"set_{$key}"}( $value );
+                } elseif ( isset( $fields_prefix[ current( explode( '_', $key ) ) ] ) ) {
+                    if ( ! isset( $shipping_fields[ $key ] ) ) {
+                        $order->update_meta_data( '_' . $key, $value );
+                    }
+                }
+            }
+
+            $order->set_created_via( 'checkout' );
+            $order->set_cart_hash( $cart_hash );
+            $order->set_customer_id( $customer_id );
+            $order->add_meta_data( 'is_vat_exempt', $order_vat_exempt );
+            $order->set_currency( $currency );
+            $order->set_prices_include_tax( $prices_include_tax );
+            $order->set_customer_ip_address( $customer_ip_address );
+            $order->set_customer_user_agent( $customer_user_agent );
+            $order->set_customer_note( $customer_note );
+            $order->set_payment_method( $payment_method );
+            $order->set_shipping_total( $shipping_total );
+            $order->set_discount_total( $discount_total );
+            $order->set_discount_tax( $discount_tax );
+            $order->set_cart_tax( $cart_tax );
+            $order->set_shipping_tax( $shipping_tax );
+            $order->set_total( $total );
+
+            $checkout->create_order_line_items( $order, $cart );
+            $checkout->create_order_fee_lines( $order, $cart );
+            $checkout->create_order_shipping_lines( $order, $chosen_shipping_methods, $shipping_packages );
+            $checkout->create_order_tax_lines( $order, $cart );
+            $checkout->create_order_coupon_lines( $order, $cart );
+
+            # Store the Post ID in the superglobal array so we can use it in
+            # self::filter_woocommerce_new_order_data, which is attached to the
+            # "woocommerce_new_order_data" hook and allows us
+            # to inject data into the `wp_insert_post` call.
+
+            $GLOBALS['laybuy_quote_id'] = $quote_id;
+
+            do_action( 'woocommerce_checkout_create_order', $order, $data );
+
+            $order_id = $order->save();
+
+            do_action( 'woocommerce_checkout_update_order_meta', $order_id, $data );
+
+            # Clear globals after use, if not already cleared.
+
+            if (isset($GLOBALS['laybuy_quote_id'])) {
+                unset($GLOBALS['laybuy_quote_id']);
+            }
+
+            WC_Laybuy_Logger::log("Order created successfully from a quote {$quote_id}");
+
+            return $order;
+        } catch ( Exception $e ) {
+
+            WC_Laybuy_Logger::log("Error while WC creating an order from a quote {$quote_id} " . $e->getMessage());
+
+            return new WP_Error( 'checkout-error', $e->getMessage() );
+        }
+    }
+}

+ 444 - 0
includes/Laybuy/Processes/CreateOrder/WC_LT_3_6/Process.php

@@ -0,0 +1,444 @@
+<?php
+
+class Laybuy_Processes_CreateOrder_WC_LT_3_6_Process extends Laybuy_Processes_CreateOrder_CreateOrderAbstract
+{
+    public $quoteId;
+
+    public function setQuoteId($quoteId)
+    {
+        $this->quoteId = $quoteId;
+    }
+
+    public function getQuoteId()
+    {
+        return $this->quoteId;
+    }
+
+    public function execute()
+    {
+        try {
+
+            $quote_id = $this->getQuoteId();
+
+            WC_Laybuy_Logger::log("Creating an WC order from a quote {$quote_id}. Transaction start");
+
+            // Start transaction if available
+            wc_transaction_query( 'start' );
+
+            # Retrieve the order data from the Laybuy_Quote item.
+
+            $_POST = $this->_decode(get_post_meta( $quote_id, '$_POST', true ));
+
+            $customer_id = get_post_meta( $quote_id, 'customer_id', true );
+            $cart_hash = get_post_meta( $quote_id, 'cart_hash', true );
+            $cart_shipping_total = (float)get_post_meta( $quote_id, 'cart_shipping_total', true );
+            $cart_shipping_tax_total = (float)get_post_meta( $quote_id, 'cart_shipping_tax_total', true );
+            $cart_discount_total = (float)get_post_meta( $quote_id, 'cart_discount_total', true );
+            $cart_discount_tax_total = (float)get_post_meta( $quote_id, 'cart_discount_tax_total', true );
+            $cart_tax_total = (float)get_post_meta( $quote_id, 'cart_tax_total', true );
+            $cart_total = (float)get_post_meta( $quote_id, 'cart_total', true );
+            $cart_items = json_decode(get_post_meta( $quote_id, 'cart_items', true ), true);
+            $cart_fees = json_decode(get_post_meta( $quote_id, 'cart_fees', true ), false);
+            $cart_coupons = json_decode(get_post_meta( $quote_id, 'cart_coupons', true ), true);
+            $cart_taxes = json_decode(get_post_meta( $quote_id, 'cart_taxes', true ), true);
+
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+                $chosen_shipping_methods = json_decode(get_post_meta( $quote_id, 'chosen_shipping_methods', true ), true);
+            }
+            $shipping_packages = json_decode(get_post_meta( $quote_id, 'shipping_packages', true ), true);
+            $billing_address = json_decode(get_post_meta( $quote_id, 'billing_address', true ), true);
+            $shipping_address = json_decode(get_post_meta( $quote_id, 'shipping_address', true ), true);
+            $posted = json_decode(get_post_meta( $quote_id, 'posted', true ), true);
+
+            // Force-delete the Laybuy_Quote item. This will make its ID available to be used as the WC_Order ID.
+
+            wp_delete_post( $quote_id, true );
+
+            //  Create the WC_Order item.
+            $order_data = array(
+                'status'        => apply_filters( 'woocommerce_default_order_status', 'pending' ),
+                'customer_id'   => $customer_id,
+                'customer_note' => isset( $posted['order_comments'] ) ? $posted['order_comments'] : '',
+                'cart_hash'     => $cart_hash,
+                'created_via'   => 'checkout',
+            );
+
+            $GLOBALS['laybuy_quote_id'] = $quote_id;
+            $order = wc_create_order( $order_data );
+
+            if (isset($GLOBALS['laybuy_quote_id'])) {
+                unset($GLOBALS['laybuy_quote_id']);
+            }
+
+            if ( is_wp_error( $order ) ) {
+                throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 520 ) );
+            } elseif ( false === $order ) {
+                throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 521 ) );
+            } else {
+                # avoid older WooCommerce error
+                if (method_exists($order, "get_id")) {
+                    $order_id = $order->get_id();
+                }
+                else {
+                    $order_id = $order->ID;
+                }
+                do_action( 'woocommerce_new_order', $order_id );
+            }
+
+            $this->create_order_line_items($order, $cart_items);
+            $this->create_order_fee_lines($order, $cart_fees);
+            $this->create_order_shipping_lines($order, $shipping_packages, $chosen_shipping_methods);
+            $this->create_order_tax_lines($order, $cart_taxes);
+            $this->create_order_coupon_lines($order, $cart_coupons);
+
+            /**
+             * @since 2.0.3
+             * Decode the shipping & billing address fields.
+             */
+            foreach($billing_address as $key => $billing_data) {
+                $billing_address[$key] = $this->_decode($billing_data);
+            }
+            foreach($shipping_address as $key => $shipping_data) {
+                $shipping_address[$key] = $this->_decode($shipping_data);
+            }
+
+            $order->set_address( $billing_address, 'billing' );
+            $order->set_address( $shipping_address, 'shipping' );
+            $order->set_payment_method( $this->getProcessManager()->getWcGateway() );
+
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+                $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+                $order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
+                $order->set_customer_user_agent( wc_get_user_agent() );
+                $order->set_shipping_total( $cart_shipping_total );
+                $order->set_discount_total( $cart_discount_total );
+                $order->set_discount_tax( $cart_discount_tax_total );
+                $order->set_cart_tax( $cart_tax_total );
+                $order->set_shipping_tax( $cart_shipping_tax_total );
+            } else {
+                $order->set_total( $cart_shipping_total, 'shipping' );
+                $order->set_total( $cart_discount_total, 'cart_discount' );
+                $order->set_total( $cart_discount_tax_total, 'cart_discount_tax' );
+                $order->set_total( $cart_tax_total, 'tax' );
+                $order->set_total( $cart_shipping_tax_total, 'shipping_tax' );
+            }
+
+            $order->set_total( $cart_total );
+
+            do_action( 'woocommerce_checkout_update_order_meta', $order_id, $posted );
+
+            WC_Laybuy_Logger::log("Committing transaction");
+
+            // If we got here, the order was created without problems!
+            wc_transaction_query( 'commit' );
+
+            WC_Laybuy_Logger::log("Order created successfully from a quote {$quote_id}");
+        } catch ( Exception $e ) {
+
+            // There was an error adding order data!
+            wc_transaction_query( 'rollback' );
+
+            WC_Laybuy_Logger::log("Error while WC creating an order from a quote {$quote_id} " . $e->getMessage());
+
+            return new WP_Error( 'checkout-error', $e->getMessage() );
+        }
+
+        return $order;
+    }
+
+    public function create_order_line_items($order, $cart_items) {
+
+        WC_Laybuy_Logger::log("create_order_line_items - Start");
+        WC_Laybuy_Logger::log("cart_items: " . print_r($cart_items, true));
+
+        // Store the line items to the new/resumed order
+        foreach ( $cart_items as $cart_item_key => $cart_item ) {
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+                $values = array(
+                    'data' => wc_get_product($cart_item['id']),
+                    'quantity' => $cart_item['props']['quantity'],
+                    'variation' => $this->_decode($cart_item['props']['variation']),
+                    'line_subtotal' => $cart_item['props']['subtotal'],
+                    'line_total' => $cart_item['props']['total'],
+                    'line_subtotal_tax' => $cart_item['props']['subtotal_tax'],
+                    'line_tax' => $cart_item['props']['total_tax'],
+                    'line_tax_data' => $cart_item['props']['taxes']
+                );
+
+                # Also reinsert any custom line item fields
+                # that may have been attached by third-party plugins.
+
+                foreach( $cart_item as $cart_item_key => $cart_item_value ) {
+                    if( !in_array($cart_item_key, array('id', 'props')) ) {
+                        $values[$cart_item_key] = $this->_decode($cart_item_value);
+                    }
+                }
+
+                $item                       = apply_filters( 'woocommerce_checkout_create_order_line_item_object', new WC_Order_Item_Product(), $cart_item_key, $values, $order );
+                $item->legacy_values        = $values; // @deprecated For legacy actions.
+                $item->legacy_cart_item_key = $cart_item_key; // @deprecated For legacy actions.
+                $item->set_props( array(
+                    'quantity'     => $cart_item['props']['quantity'],
+                    'variation'    => $this->_decode($cart_item['props']['variation']),
+                    'subtotal'     => $cart_item['props']['subtotal'],
+                    'total'        => $cart_item['props']['total'],
+                    'subtotal_tax' => $cart_item['props']['subtotal_tax'],
+                    'total_tax'    => $cart_item['props']['total_tax'],
+                    'taxes'        => $cart_item['props']['taxes'],
+                    'name'         => $this->_decode($cart_item['props']['name']),
+                    'tax_class'    => $this->_decode($cart_item['props']['tax_class']),
+                    'product_id'   => $cart_item['props']['product_id'],
+                    'variation_id' => $cart_item['props']['variation_id']
+                ) );
+                $item->set_backorder_meta();
+
+                do_action( 'woocommerce_checkout_create_order_line_item', $item, $cart_item_key, $values, $order );
+
+                // Add item to order and save.
+                $order->add_item( $item );
+            } else {
+                $product = new $cart_item['class']( $cart_item['id'] );
+                unset( $cart_item['class'] );
+                unset( $cart_item['id'] );
+                $cart_item['data'] = $product;
+
+                $item_id = $order->add_product(
+                    $product,
+                    $cart_item['quantity'],
+                    array(
+                        'variation' => $this->_decode($cart_item['variation']),
+                        'totals'    => $cart_item['totals']
+                    )
+                );
+
+                if ( ! $item_id ) {
+                    throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 525 ) );
+                }
+
+                // Allow plugins to add order item meta
+                do_action( 'woocommerce_add_order_item_meta', $item_id, $cart_item, $cart_item_key );
+            }
+        }
+
+        WC_Laybuy_Logger::log("create_order_line_items - End");
+    }
+
+    /*
+     * Add coupon lines to the order.
+     */
+    public function create_order_coupon_lines($order, $cart_coupons) {
+
+        WC_Laybuy_Logger::log("create_order_coupon_lines - Start");
+        WC_Laybuy_Logger::log("cart_coupons: " . print_r($cart_coupons, true));
+
+        // Store coupons
+        foreach ( $cart_coupons as $code => $coupon_data ) {
+
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+
+                $item = new WC_Order_Item_Coupon();
+                $item->set_props(
+                    array(
+                        'code'         => $code,
+                        'discount'     => $coupon_data['discount_amount'],
+                        'discount_tax' => $coupon_data['discount_tax_amount'],
+                    )
+                );
+
+                // Avoid storing used_by - it's not needed and can get large.
+                if (isset($coupon_data['used_by'])) {
+                    unset( $coupon_data['used_by'] );
+                }
+
+                $item->add_meta_data( 'coupon_data', $coupon_data );
+
+                $coupon = new WC_Coupon( $code );
+                /**
+                 * Action hook to adjust item before save.
+                 *
+                 * @since 3.0.0
+                 */
+                do_action( 'woocommerce_checkout_create_order_coupon_item', $item, $code, $coupon, $order );
+
+                // Add item to order and save.
+                $order->add_item( $item );
+
+            } else {
+                if ( ! $order->add_coupon( $code, $coupon_data['discount_amount'], $coupon_data['discount_tax_amount'] ) ) {
+                    throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 529 ) );
+                }
+            }
+        }
+    }
+
+    public function create_order_tax_lines($order, $cart_taxes) {
+
+        WC_Laybuy_Logger::log("create_order_tax_lines - Start");
+        WC_Laybuy_Logger::log("cart_taxes: " . print_r($cart_taxes, true));
+
+        // Store tax rows
+        foreach ( $cart_taxes as $tax_rate_id => $cart_tax ) {
+
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+                if ( $tax_rate_id && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
+                    $item = new WC_Order_Item_Tax();
+                    $item->set_props(
+                        array(
+                            'rate_id'            => $tax_rate_id,
+                            'tax_total'          => $cart_tax['tax_amount'],
+                            'shipping_tax_total' => $cart_tax['shipping_tax_amount'],
+                            'rate_code'          => WC_Tax::get_rate_code( $tax_rate_id ),
+                            'label'              => WC_Tax::get_rate_label( $tax_rate_id ),
+                            'compound'           => WC_Tax::is_compound( $tax_rate_id ),
+                        )
+                    );
+
+                    /**
+                     * Action hook to adjust item before save.
+                     *
+                     * @since 3.0.0
+                     */
+                    do_action( 'woocommerce_checkout_create_order_tax_item', $item, $tax_rate_id, $order );
+
+                    // Add item to order and save.
+                    $order->add_item( $item );
+                } else {
+                    if ( ! $order->add_tax( $tax_rate_id, $cart_tax['tax_amount'], $cart_tax['shipping_tax_amount'] ) ) {
+                        throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 528 ) );
+                    }
+                }
+            }
+        }
+
+        WC_Laybuy_Logger::log("create_order_tax_lines - End");
+    }
+
+    public function create_order_shipping_lines($order, $shipping_packages, $chosen_shipping_methods) {
+
+        WC_Laybuy_Logger::log("create_order_shipping_lines - Start");
+        WC_Laybuy_Logger::log("shipping_packages: " . print_r($shipping_packages, true));
+
+        /**
+         * Store shipping for all packages
+         * see WC_Checkout::create_order_shipping_lines
+         */
+
+        foreach ( $shipping_packages as $package_key => $package_data ) {
+
+            $package_metadata = $this->_decode( $package_data['package_metadata'] );
+
+            if (version_compare( WC_VERSION, '3.0.0', '>=' )) {
+                $package = $this->_decode( $package_data['package'] );
+
+                if ( isset( $chosen_shipping_methods[ $package_key ], $package['rates'][ $chosen_shipping_methods[ $package_key ] ] ) ) {
+                    $shipping_rate = $package['rates'][ $chosen_shipping_methods[ $package_key ] ];
+                    $item = new WC_Order_Item_Shipping;
+                    $item->legacy_package_key = $package_key; // @deprecated For legacy actions.
+                    $item->set_props( array(
+                        'method_title' => $shipping_rate->label,
+                        'method_id'    => $shipping_rate->method_id,
+                        'instance_id'  => $shipping_rate->instance_id,
+                        'total'        => wc_format_decimal( $shipping_rate->cost ),
+                        'taxes'        => array(
+                            'total' => $shipping_rate->taxes,
+                        ),
+                    ) );
+
+                    foreach ( $package_metadata as $key => $value ) {
+                        $item->add_meta_data( $key, $value, true );
+                    }
+
+                    /**
+                     * Action hook to adjust item before save.
+                     * @since WooCommerce 3.0.0
+                     */
+                    do_action( 'woocommerce_checkout_create_order_shipping_item', $item, $package_key, $package, $order );
+
+                    // Add item to order and save.
+                    $order->add_item( $item );
+                }
+            } else {
+                $package = new WC_Shipping_Rate( $package_data['id'], $this->_decode($package_data['label']), $package_data['cost'], $package_data['taxes'], $package_data['method_id'] );
+
+                foreach ($package_metadata as $key => $value) {
+                    $package->add_meta_data($key, $value);
+                }
+
+                $item_id = $order->add_shipping( $package );
+
+                if ( ! $item_id ) {
+                    throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 527 ) );
+                }
+
+                // Allows plugins to add order item meta to shipping
+                do_action( 'woocommerce_add_shipping_order_item', $order->get_id(), $item_id, $package_key );
+            }
+        }
+
+        WC_Laybuy_Logger::log("create_order_shipping_lines - End");
+    }
+
+    public function create_order_fee_lines($order, $cart_fees) {
+
+        WC_Laybuy_Logger::log("create_order_fee_lines - Start");
+        WC_Laybuy_Logger::log("cart_fees: " . print_r($cart_fees, true));
+
+        // Store fees
+        foreach ( $cart_fees as $fee_key => $fee ) {
+
+            $tax_data = array();
+
+            if (is_string($fee)) { // decode custom fee object
+                $fee = $this->_decode($fee);
+            }
+
+            foreach ($fee->tax_data as $key_str => $amount) {
+                $tax_data[(int)$key_str] = $this->_decode($amount);
+            }
+
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+
+                $item                 = new WC_Order_Item_Fee();
+                $item->legacy_fee     = $fee; // @deprecated For legacy actions.
+                $item->legacy_fee_key = $fee_key; // @deprecated For legacy actions.
+                $item->set_props(
+                    array(
+                        'name'      => $fee->name,
+                        'tax_class' => $fee->taxable ? $fee->tax_class : 0,
+                        'amount'    => $fee->amount,
+                        'total'     => $fee->total,
+                        'total_tax' => $fee->tax,
+                        'taxes'     => array(
+                            'total' => $tax_data,
+                        ),
+                    )
+                );
+
+                /**
+                 * Action hook to adjust item before save.
+                 *
+                 * @since 3.0.0
+                 */
+                do_action( 'woocommerce_checkout_create_order_fee_item', $item, $fee_key, $fee, $order );
+
+                // Add item to order and save.
+                $order->add_item( $item );
+
+
+            } else {
+
+                $fee->tax_data = $tax_data;
+                $item_id = $order->add_fee( $fee );
+
+                if ( ! $item_id ) {
+                    throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 526 ) );
+                }
+            }
+
+            // Allow plugins to add order item meta to fees
+            do_action( 'woocommerce_add_order_fee_meta', $order->get_id(), $item_id, $fee, $fee_key );
+        }
+
+        WC_Laybuy_Logger::log("create_order_fee_lines - End");
+    }
+}

+ 34 - 0
includes/Laybuy/Processes/CreateQuote/CreateQuoteAbstract.php

@@ -0,0 +1,34 @@
+<?php
+
+abstract class Laybuy_Processes_CreateQuote_CreateQuoteAbstract extends Laybuy_Processes_AbstractProcess
+{
+    abstract public function setCheckout($checkout);
+    abstract public function getCheckout();
+
+    /**
+     * Checking if the Cart Item Details is a Custom Field or normal / default WooCommerce Product Line Item structure.
+     *
+     * @since	2.0.4
+     * @param	string $key 	The Key to be checked for Custom Field processing.
+     * @return	bool			Whether or not the given key is a Custom Field (not WooCommerce Default structure).
+     */
+    protected function _isProductDetailCustom($key) {
+        # Array of default values to exclude from the Custom Fields Handling
+        $default_keys = 	array(
+
+            'key',
+            'variation_id',
+            'variation',
+            'quantity',
+            'data_hash',
+            'line_tax_data',
+            'line_subtotal',
+            'line_subtotal_tax',
+            'line_total',
+            'line_tax',
+            'data'
+        );
+
+        return !in_array($key, $default_keys);
+    }
+}

+ 210 - 0
includes/Laybuy/Processes/CreateQuote/WC_GT_3_6/Process.php

@@ -0,0 +1,210 @@
+<?php
+
+class Laybuy_Processes_CreateQuote_WC_GT_3_6_Process extends Laybuy_Processes_CreateQuote_CreateQuoteAbstract
+{
+    public $checkout;
+
+    public function setCheckout($checkout)
+    {
+        $this->checkout = $checkout;
+    }
+
+    public function getCheckout()
+    {
+        return $this->checkout;
+    }
+
+    public function execute()
+    {
+        # Get posted data.
+        $checkout = $this->getCheckout();
+
+        if (!$checkout) {
+            throw new InvalidArgumentException(
+                'Parameter checkout is not set. Please call setCheckout before executing the process'
+            );
+        }
+
+        $data = $checkout->get_posted_data();
+
+        if ($data['payment_method'] != self::PAYMENT_METHOD_LAYBUY) {
+            return;
+        }
+
+        $quote_id = wp_insert_post( array(
+            'post_content' => 'Thank you for your order. Now redirecting to Laybuy to complete your payment...',
+            'post_title' => 'Laybuy Order',
+            'post_status' => 'publish',
+            'post_type' => 'laybuy_quote'
+        ), true );
+
+        if (is_wp_error($quote_id)) {
+            $errors_str = implode($quote_id->get_error_messages(), ' ');
+            WC_Laybuy_Logger::error("WC_Payment_Gateway_Laybuy::create_order_quote() returned -1 (Could not create laybuy_quote post. WordPress threw error(s): {$errors_str})");
+            return -1;
+        }
+
+        WC_Laybuy_Logger::log("New WP Quote created with ID:{$quote_id} and permalink:\"" . get_permalink( $quote_id ) . "\"");
+
+        $cart = WC()->cart;
+
+        $cart_hash = $cart->get_cart_hash();
+        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
+
+        $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
+        $shipping_packages = WC()->shipping()->get_packages();
+
+        $customer_id = apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
+        $order_vat_exempt = ( $cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no' );
+        $currency = get_woocommerce_currency();
+
+        $prices_include_tax = ( get_option( 'woocommerce_prices_include_tax' ) === 'yes' || get_woocommerce_currency() === WC_Laybuy_Helper::CURRENCY_CODE_GB);
+
+        $customer_ip_address = WC_Geolocation::get_ip_address();
+        $customer_user_agent = wc_get_user_agent();
+        $customer_note = ( isset( $data['order_comments'] ) ? $data['order_comments'] : '' );
+        $payment_method = ( isset( $available_gateways[ $data['payment_method'] ] ) ? $available_gateways[ $data['payment_method'] ] : $data['payment_method'] );
+        $shipping_total = $cart->get_shipping_total();
+
+        $discount_total = $cart->get_discount_total();
+        $discount_tax = $cart->get_discount_tax();
+        $cart_tax = $cart->get_cart_contents_tax() + $cart->get_fee_tax();
+        $shipping_tax = $cart->get_shipping_tax();
+        $total = $cart->get_total( 'edit' );
+
+        $nonce = wp_create_nonce( "laybuy_quote_{$quote_id}_create" );
+        update_post_meta( $quote_id, '_laybuy_quote_nonce', $nonce );
+
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+        $defaultPhoneKey = 'billing_phone';
+        $billingPhone = null;
+
+        if (isset($data[$defaultPhoneKey])) {
+            $billingPhone = $data[$defaultPhoneKey];
+        }
+
+        if (!$billingPhone && isset($data[$settings['laybuy_billing_phone_field']])) {
+            $billingPhone = $data[$settings['laybuy_billing_phone_field']];
+        }
+
+        $apiData = array(
+            'amount'    => $total,
+            'returnUrl' => $this->_buildReturnUrl($quote_id, $nonce),
+            'merchantReference' => $this->_makeUniqueReference($quote_id),
+            'customer' => array(
+                'firstName' => $data['billing_first_name'],
+                'lastName' => $data['billing_last_name'],
+                'email' => $data['billing_email'],
+                'phone' => $billingPhone
+            ),
+            'billingAddress' => array(
+                "address1" => $data['billing_address_1'],
+                "city" => $data['billing_city'],
+                "postcode" => $data['billing_postcode'],
+                "country" => $data['billing_country'],
+            ),
+            'items' => array()
+        );
+
+        $apiData['tax'] = floatval($cart_tax + $shipping_tax + $discount_tax);
+
+        // items total
+        $items_total = $total;
+
+        // shipping
+        if ($shipping_total > 0) {
+            $apiData['items'][] = array(
+                'id' => 'shipping_fee_for_order#' . $quote_id,
+                'description' => 'Shipping fee for this order',
+                'quantity' => '1',
+                'price' =>  $shipping_total
+            );
+            $items_total -= $shipping_total;
+        }
+
+        // tax
+        if ($cart_tax) {
+            $apiData['items'][] = array(
+                'id' => 'total_tax_amount_for_order#' . $quote_id,
+                'description' => 'Tax amount for this order',
+                'quantity' => '1',
+                'price' => $cart_tax + $shipping_tax + $discount_tax
+            );
+            $items_total -= ($cart_tax + $shipping_tax + $discount_tax);
+        }
+
+        $apiData['items'][] = [
+            'id'          => 'item_for_order___#' . $quote_id,
+            'description' => 'Purchase from ' . get_bloginfo('name'),
+            'quantity'    => 1,
+            'price'       => $items_total
+        ];
+
+        WC_Laybuy_Logger::log("Sending a request to create a Laybuy Order");
+        WC_Laybuy_Logger::log(print_r($apiData, true));
+
+        $gateway = $this->getProcessManager()->getApiGateway();
+        // Send the order data to Laybuy to get a token.
+        $response = $gateway->createOrder($apiData);
+
+        if ($response === false || $response->result === Laybuy_ApiGateway::PAYMENT_STATUS_ERROR) {
+
+            $this->getProcessManager()->cancelQuote($quote_id, 'failed');
+
+            # Log the error and return a truthy integer (otherwise WooCommerce will not bypass the standard order creation process).
+            WC_Laybuy_Logger::error("Laybuy_ApiGateway::createOrder() returned -2 (Laybuy did not provide a token for this order.)");
+            WC_Laybuy_Logger::error("API Payload: " . print_r($data, true));
+            WC_Laybuy_Logger::error("API Response: " . var_export($response, true));
+
+            return -2;
+        }
+
+        # Process Get Laybuy Token End
+
+        WC_Laybuy_Logger::log("Laybuy Order Created");
+        WC_Laybuy_Logger::log("Response Data: " . print_r($response, true));
+
+        # Add the meta data to the Afterpay_Quote post record.
+
+        add_post_meta( $quote_id, 'status', 'pending' );
+
+        add_post_meta( $quote_id, 'token', $response->token );
+
+        add_post_meta( $quote_id, 'posted', $this->_encode($data) );
+        add_post_meta( $quote_id, '$_POST', $this->_encode($_POST) );
+        add_post_meta( $quote_id, 'cart', $this->_encode($cart) );
+
+        add_post_meta( $quote_id, 'cart_hash', $this->_encode($cart_hash) );
+
+        add_post_meta( $quote_id, 'chosen_shipping_methods', $this->_encode($chosen_shipping_methods) );
+        add_post_meta( $quote_id, 'shipping_packages', $this->_encode($shipping_packages) );
+
+        add_post_meta( $quote_id, 'customer_id', $this->_encode($customer_id) );
+        add_post_meta( $quote_id, 'order_vat_exempt', $this->_encode($order_vat_exempt) );
+        add_post_meta( $quote_id, 'currency', $this->_encode($currency) );
+        add_post_meta( $quote_id, 'prices_include_tax', $this->_encode($prices_include_tax) );
+        add_post_meta( $quote_id, 'customer_ip_address', $this->_encode($customer_ip_address) );
+        add_post_meta( $quote_id, 'customer_user_agent', $this->_encode($customer_user_agent) );
+        add_post_meta( $quote_id, 'customer_note', $this->_encode($customer_note) );
+        add_post_meta( $quote_id, 'payment_method', $this->_encode($payment_method) );
+        add_post_meta( $quote_id, 'shipping_total', $this->_encode($shipping_total) );
+        add_post_meta( $quote_id, 'discount_total', $this->_encode($discount_total) );
+        add_post_meta( $quote_id, 'discount_tax', $this->_encode($discount_tax) );
+        add_post_meta( $quote_id, 'cart_tax', $this->_encode($cart_tax) );
+        add_post_meta( $quote_id, 'shipping_tax', $this->_encode($shipping_tax) );
+        add_post_meta( $quote_id, 'total', $this->_encode($total) );
+
+        $result = array(
+            'result'	=> 'success',
+            'redirect'	=> $response->paymentUrl
+        );
+
+        if ( is_ajax() ) {
+            wp_send_json( $result );
+        } else {
+            wp_redirect( $result['redirect'] );
+            exit;
+        }
+    }
+}

+ 345 - 0
includes/Laybuy/Processes/CreateQuote/WC_LT_3_6/Process.php

@@ -0,0 +1,345 @@
+<?php
+
+class Laybuy_Processes_CreateQuote_WC_LT_3_6_Process extends Laybuy_Processes_CreateQuote_CreateQuoteAbstract
+{
+    public $checkout;
+
+    public function setCheckout($checkout)
+    {
+        $this->checkout = $checkout;
+    }
+
+    public function getCheckout()
+    {
+        return $this->checkout;
+    }
+
+    public function execute()
+    {
+        $checkout = $this->getCheckout();
+
+        if (!$checkout) {
+            throw new InvalidArgumentException(
+                'Parameter checkout is not set. Please call setCheckout before executing the process'
+            );
+        }
+
+        $posted = method_exists($checkout, 'get_posted_data') ? $checkout->get_posted_data() : $checkout->posted;
+
+        if ($posted['payment_method'] !== self::PAYMENT_METHOD_LAYBUY) {
+            return;
+        }
+
+        $quote_id = wp_insert_post( array(
+            'post_content' => 'Thank you for your order. Now redirecting to Laybuy to complete your payment...',
+            'post_title' => 'Laybuy Order',
+            'post_status' => 'publish',
+            'post_type' => 'laybuy_quote'
+        ), true );
+
+
+        if (is_wp_error($quote_id)) {
+            $errors_str = implode($quote_id->get_error_messages(), ' ');
+            WC_Laybuy_Logger::error("WC_Payment_Gateway_Laybuy::create_order_quote() returned -1 (Could not create laybuy_quote post. WordPress threw error(s): {$errors_str})");
+            return -1;
+        }
+
+        WC_Laybuy_Logger::log("New WP Quote created with ID:{$quote_id} and permalink:\"" . get_permalink( $quote_id ) . "\"");
+
+        $cart     = WC()->cart;
+        $session  = WC()->session;
+
+        // Billing
+        $billing = array();
+        $billing_encoded = array();
+
+        if ( $checkout->checkout_fields['billing'] ) {
+            foreach ( array_keys( $checkout->checkout_fields['billing'] ) as $field ) {
+                $field_name = str_replace( 'billing_', '', $field );
+                $billing[$field_name] = $checkout->get_posted_address_data( $field_name );
+                $billing_encoded[$field_name] = $this->_encode($billing[ $field_name ]);
+            }
+        }
+
+        if (empty($billing['phone']) || strlen(preg_replace('/[^0-9+]/i', '', $billing['phone'])) <= 6) {
+            $billing['phone'] = "00 000 000";
+        }
+
+        $nonce = wp_create_nonce( "laybuy_quote_{$quote_id}_create" );
+        update_post_meta( $quote_id, '_laybuy_quote_nonce', $nonce );
+
+        $data = array(
+            'amount'    => $cart->total,
+            'returnUrl' => $this->_buildReturnUrl($quote_id, $nonce),
+            'merchantReference' => $this->_makeUniqueReference($quote_id),
+            'customer' => array(
+                'firstName' => $billing['first_name'],
+                'lastName' => $billing['last_name'],
+                'email' => $billing['email'],
+                'phone' => $billing['phone']
+            ),
+            'billingAddress' => array(
+                "address1" => $billing['address_1'],
+                "city" => $billing['city'],
+                "postcode" => $billing['postcode'],
+                "country" => $billing['country'],
+            ),
+            'items' => array()
+        );
+
+        if ('yes' === get_option('woocommerce_prices_include_tax') || get_woocommerce_currency() === WC_Laybuy_Helper::CURRENCY_CODE_GB) {
+            $data['tax'] = number_format($cart->tax_total + $cart->shipping_tax_total, 2, '.', '');
+        }
+
+
+        // Shipping total
+        $shipping_total = $cart->shipping_total;
+
+        if ($cart->shipping_tax_total > 0) {
+            $shipping_total += $cart->shipping_tax_total;
+        }
+
+        // items total
+        $items_total = $cart->total;
+
+        // shipping
+        if ($shipping_total > 0) {
+            $data['items'][] = array(
+                'id' => 'shipping_fee_for_order#' . $quote_id,
+                'description' => 'Shipping fee for this order',
+                'quantity' => '1',
+                'price' =>  $shipping_total
+            );
+            $items_total -= $shipping_total;
+        }
+
+        // tax
+        if ($cart->tax_total && 'no' === get_option( 'woocommerce_prices_include_tax' ) ) {
+            $data['items'][] = array(
+                'id' => 'total_tax_amount_for_order#' . $quote_id,
+                'description' => 'Tax amount for this order',
+                'quantity' => '1',
+                'price' => $cart->tax_total
+            );
+            $items_total -= $cart->tax_total;
+        }
+
+        $data['items'][] = [
+            'id'          => 'item_for_order___#' . $quote_id,
+            'description' => 'Purchase from ' . get_bloginfo('name'),
+            'quantity'    => 1,
+            'price'       => $items_total
+        ];
+
+        // Store data to build a WC_Order object later.
+        $cart_items = array();
+
+        // see WC_Checkout::create_order_line_items
+        foreach ($cart->get_cart() as $cart_item_key => $values) {
+            $product = $values['data'];
+
+            if ( WC_Laybuy_Helper::is_wc_gt( '3.0' )) {
+                $cart_items[$cart_item_key] = array(
+                    'props' => array(
+                        'quantity'     => $values['quantity'],
+                        'variation'    => $this->_encode($values['variation']),
+                        'subtotal'     => $values['line_subtotal'],
+                        'total'        => $values['line_total'],
+                        'subtotal_tax' => $values['line_subtotal_tax'],
+                        'total_tax'    => $values['line_tax'],
+                        'taxes'        => $values['line_tax_data']
+                    )
+                );
+                if ($product) {
+                    $cart_items[$cart_item_key]['id'] = $product->get_id();
+
+                    $cart_items[$cart_item_key]['props'] = array_merge($cart_items[$cart_item_key]['props'], array(
+                        'name'         => $this->_encode($product->get_name()),
+                        'tax_class'    => $this->_encode($product->get_tax_class()),
+                        'product_id'   => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
+                        'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0
+                    ));
+                }
+                
+                // custom fields
+                foreach( $values as $field => $field_value ) {
+                    if ( $this->_isProductDetailCustom($field) ) {
+                        $cart_items[$cart_item_key][$field] = $this->_encode($field_value);
+                    }
+                }
+
+            } else {
+                $cart_items[$cart_item_key] = array(
+                    'class' => $this->_encode(get_class($product)),
+                    'id' => $product->id,
+                    'quantity' => $values['quantity'],
+                    'variation' => $this->_encode($values['variation']),
+                    'totals' => array(
+                        'subtotal' => $values['line_subtotal'],
+                        'subtotal_tax' => $values['line_subtotal_tax'],
+                        'total' => $values['line_total'],
+                        'tax' => $values['line_tax'],
+                        'tax_data' => $values['line_tax_data'] # Since WooCommerce 2.2
+                    )
+                );
+            }
+        }
+
+        // Fees
+        $cart_fees = array();
+
+        foreach ( $cart->get_fees() as $fee_key => $fee ) {
+            $cart_fees[$fee_key] = $this->_encode($fee);
+        }
+
+        // Discounts
+        if ($cart->has_discount()) {
+            // The total is stored in $cart->get_total_discount(), but we should also be able to get a list.
+            $data['discounts'] = array();
+            foreach ($cart->coupon_discount_amounts as $code => $amount) {
+                $data['discounts'][] = array(
+                    'displayName' => $code,
+                    'amount' => array(
+                        'amount' => number_format($amount, 2, '.', ''),
+                        'currency' => get_woocommerce_currency()
+                    )
+                );
+            }
+        }
+
+        // Coupons
+        $cart_coupons = array();
+        foreach ($cart->get_coupons() as $code => $coupon) {
+            $cart_coupons[$code] = array(
+                'discount_amount' => $cart->get_coupon_discount_amount($code),
+                'discount_tax_amount' => $cart->get_coupon_discount_tax_amount($code)
+            );
+        }
+
+        // Taxes
+        $cart_taxes = array();
+        foreach (array_keys($cart->taxes + $cart->shipping_taxes) as $tax_rate_id) {
+            if ($tax_rate_id && $tax_rate_id !== apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' )) {
+                $cart_taxes[$tax_rate_id] = array(
+                    'tax_amount' => $cart->get_tax_amount($tax_rate_id),
+                    'shipping_tax_amount' => $cart->get_shipping_tax_amount($tax_rate_id)
+                );
+            }
+        }
+
+        // Shipping costs.
+        if ($shipping_total > 0) {
+            $data['shippingAmount'] = array(
+                'amount' => number_format($shipping_total, 2, '.', ''),
+                'currency' => get_woocommerce_currency()
+            );
+        }
+
+        // Shipping address.
+        $shipping = array();
+        $shipping_encoded = array();
+
+        if ( $checkout->checkout_fields['shipping'] ) {
+            foreach ( array_keys( $checkout->checkout_fields['shipping'] ) as $field ) {
+                $field_name = str_replace( 'shipping_', '', $field );
+                $shipping[ $field_name ] = $checkout->get_posted_address_data( $field_name, 'shipping' );
+                $shipping_encoded[ $field_name ] = $this->_encode($shipping[ $field_name ]);
+            }
+        }
+
+        // shipping methods
+        $chosen_shipping_methods = $session->get( 'chosen_shipping_methods' );
+
+        // Shipping packages.
+        $shipping_packages = array();
+
+        foreach (WC()->shipping->get_packages() as $package_key => $package) {
+            if (isset($package['rates'][$checkout->shipping_methods[$package_key]])) {
+                $shipping_rate = $package['rates'][$checkout->shipping_methods[$package_key]];
+                $package_metadata = $shipping_rate->get_meta_data();
+
+                $shipping_packages[$package_key] = array();
+                if (version_compare( WC_VERSION, '3.0.0', '>=' )) {
+                    $shipping_packages[$package_key]['package'] = $this->_encode($package);
+                } else {
+                    $shipping_packages[$package_key]['id'] = $shipping_rate->id;
+                    $shipping_packages[$package_key]['label'] = $this->_encode($shipping_rate->label);
+                    $shipping_packages[$package_key]['cost'] = $shipping_rate->cost;
+                    $shipping_packages[$package_key]['taxes'] = $shipping_rate->taxes;
+                    $shipping_packages[$package_key]['method_id'] = $shipping_rate->method_id;
+                }
+                $shipping_packages[$package_key]['package_metadata'] = $this->_encode($package_metadata);
+            }
+        }
+
+        WC_Laybuy_Logger::log("Sending a request to create a Laybuy Order");
+        WC_Laybuy_Logger::log(print_r($data, true));
+
+
+        # Process Get Laybuy Token Start
+
+        // Send the order data to Laybuy to get a token.
+        $response = $this->getProcessManager()->getApiGateway()->createOrder($data);
+
+        if ($response === false || $response->result === Laybuy_ApiGateway::PAYMENT_STATUS_ERROR) {
+
+            $this->getProcessManager()->cancelQuote($quote_id, 'failed');
+
+            # Log the error and return a truthy integer (otherwise WooCommerce will not bypass the standard order creation process).
+            WC_Laybuy_Logger::error("Laybuy_ApiGateway::createOrder() returned -2 (Laybuy did not provide a token for this order.)");
+            WC_Laybuy_Logger::error("API Payload: " . print_r($data, true));
+            WC_Laybuy_Logger::error("API Response: " . var_export($response, true));
+
+            return -2;
+        }
+
+        WC_Laybuy_Logger::log("Laybuy Order Created\nResponse Data: ". print_r($response, true));
+
+        # Post Create
+
+        // Add the meta data to the Laybuy_Quote post record.
+        add_post_meta( $quote_id, 'status', 'pending' );
+        add_post_meta( $quote_id, 'token', $response->token );
+        add_post_meta( $quote_id, 'customer_id', apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() ) ); # WC_Checkout::$customer_id is private. See WC_Checkout::process_checkout() for how it populates this property.
+        add_post_meta( $quote_id, 'cart_hash', md5( json_encode( wc_clean( $cart->get_cart_for_session() ) ) . $cart->total ) );
+        add_post_meta( $quote_id, 'cart_shipping_total', $cart->shipping_total );
+        add_post_meta( $quote_id, 'cart_shipping_tax_total', $cart->shipping_tax_total );
+        add_post_meta( $quote_id, 'cart_discount_total', $cart->get_cart_discount_total() );
+        add_post_meta( $quote_id, 'cart_discount_tax_total', $cart->get_cart_discount_tax_total() );
+        add_post_meta( $quote_id, 'cart_tax_total', $cart->tax_total );
+        add_post_meta( $quote_id, 'cart_total', $cart->total );
+        add_post_meta( $quote_id, 'cart_items', json_encode($cart_items) );
+        add_post_meta( $quote_id, 'cart_fees', json_encode($cart_fees) );
+        add_post_meta( $quote_id, 'cart_coupons', json_encode($cart_coupons) );
+        add_post_meta( $quote_id, 'cart_taxes', json_encode($cart_taxes) );
+        add_post_meta( $quote_id, 'cart_needs_shipping', (bool)$cart->needs_shipping() );
+
+        add_post_meta( $quote_id, 'shipping_packages', json_encode($shipping_packages) );
+        add_post_meta( $quote_id, 'billing_address', json_encode($billing_encoded) );
+        add_post_meta( $quote_id, 'shipping_address', json_encode($shipping_encoded) );
+        add_post_meta( $quote_id, 'api_data', json_encode($data) );
+
+        add_post_meta( $quote_id, 'currency', $this->_encode(get_woocommerce_currency()));
+        add_post_meta( $quote_id, 'total', $this->_encode($cart->total) );
+
+        if ( WC_Laybuy_Helper::is_wc_gt( '3.0' ) ) {
+            add_post_meta( $quote_id, 'chosen_shipping_methods', json_encode($chosen_shipping_methods) );
+        }
+
+        //  Store the Checkout Posted Data within a Post Meta to run the woocommerce_checkout_order_processed hooks
+        add_post_meta( $quote_id, 'posted', json_encode($posted) );
+        add_post_meta( $quote_id, '$_POST', $this->_encode($_POST) );
+
+        $result = array(
+            'result'	=> 'success',
+            'redirect'	=> $response->paymentUrl
+        );
+
+        if ( is_ajax() ) {
+            wp_send_json( $result );
+        } else {
+            wp_redirect( $result['redirect'] );
+            exit;
+        }
+    }
+}

+ 173 - 0
includes/Laybuy/Processes/RedirectPayment/CompatibilityMode/Process.php

@@ -0,0 +1,173 @@
+<?php
+
+class Laybuy_Processes_RedirectPayment_CompatibilityMode_Process extends Laybuy_Processes_AbstractProcess
+{
+    public $orderId;
+    public $status;
+    public $token;
+
+    public function setOrderId($orderId)
+    {
+        $this->orderId = $orderId;
+        return $this;
+    }
+
+    public function getOrderId()
+    {
+        return $this->orderId;
+    }
+
+    public function setStatus($status)
+    {
+        $this->status = $status;
+        return $this;
+    }
+
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    public function setToken($token)
+    {
+        $this->token = $token;
+        return $this;
+    }
+
+    public function getToken()
+    {
+        return $this->token;
+    }
+
+    public function execute()
+    {
+        $status   = $this->getStatus();
+        $order_id = $this->getOrderId();
+
+        if (function_exists('wc_get_order') ) {
+            $order = wc_get_order( $order_id );
+        } else {
+            $order = new WC_Order( $order_id );
+        }
+
+        switch ($status) {
+
+            case Laybuy_ApiGateway::PAYMENT_STATUS_SUCCESS:
+
+                if (get_post_meta( $order_id, '_laybuy_order_nonce', true ) !==  $_GET['_wpnonce']) {
+                    wp_redirect( wc_get_checkout_url() );
+                    exit;
+                }
+
+                WC_Laybuy_Logger::log("Confirming the payment for an order {$order_id}");
+
+                // confirm payment
+                $response = $this->getProcessManager()
+                    ->getApiGateway()
+                    ->confirmOrder([
+                        'token' => $this->getToken(),
+                        'currency' => $order->get_currency(),
+                        'amount' => $order->get_total()
+                    ]);
+
+                // returns a WP error object
+                if (is_wp_error($response) || !$response) {
+
+                    WC_Laybuy_Logger::log("Failed while confirming the payment for a order {$order_id}");
+                    WC_Laybuy_Logger::log("Response data: " . print_r($response, true));
+
+                    $this->cancelOrder($order, 'failed');
+
+                    # Store an error notice and redirect the customer back to the checkout.
+                    wc_add_notice( __( 'Your payment token has expired. Please try again.', 'woo_laybuy' ), 'error' );
+                    if (wp_redirect( wc_get_checkout_url() )) {
+                        exit;
+                    }
+                }
+
+                if ($response->result !== Laybuy_ApiGateway::PAYMENT_STATUS_SUCCESS) {
+
+                    WC_Laybuy_Logger::log("Failed while confirming the payment for a order {$order_id}");
+                    WC_Laybuy_Logger::log("Response data: " . print_r($response, true));
+
+                    $this->cancelOrder($order, 'failed');
+
+                    wc_add_notice( __( $response->error, 'woo_laybuy' ), 'error' );
+                    wp_redirect( wc_get_checkout_url() );
+                    exit;
+                }
+
+                WC_Laybuy_Logger::log("Payment confirmed. Order ID {$order_id}");
+                WC_Laybuy_Logger::log("Response: " . print_r($response, true));
+
+                update_post_meta($order->get_id(), '_laybuy_order_id', $response->orderId );
+                update_post_meta($order->get_id(), '_laybuy_token', $this->getToken() );
+
+                // save to the DB
+                $order->save();
+
+                $order->add_order_note( __( "Payment approved. Laybuy Order ID: {$response->orderId}", 'woo_laybuy') );
+
+                // complete the order and reduce stock if required
+                $order->payment_complete($response->orderId);
+
+                wc_empty_cart();
+                $redirect = $this->getProcessManager()->getWcGateway()->get_return_url($order);
+
+                wp_redirect( html_entity_decode( $redirect ) );
+                exit;
+
+                break;
+
+            case Laybuy_ApiGateway::PAYMENT_STATUS_DECLINED:
+
+                WC_Laybuy_Logger::log("Payment declined. Order ID {$order_id}");
+
+                wc_add_notice( __( 'Your payment was declined.', 'woo_laybuy' ), 'error' );
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+
+                break;
+            case Laybuy_ApiGateway::PAYMENT_STATUS_CANCELLED:
+
+                WC_Laybuy_Logger::log("Payment canceled. Order ID {$order_id}");
+
+                $this->cancelOrder($order);
+
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+
+                break;
+            case Laybuy_ApiGateway::PAYMENT_STATUS_ERROR:
+
+                WC_Laybuy_Logger::log("Payment error.");
+
+                wc_add_notice(__('Your payment could not be processed. Please try again.', 'woo_laybuy'), 'error');
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+                break;
+            default:
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+                break;
+        }
+    }
+
+    public function cancelOrder($order, $status = 'cancelled')
+    {
+        if (method_exists($order, 'get_cancel_order_url_raw')) {
+            if (wp_redirect( $order->get_cancel_order_url_raw() )) {
+                exit;
+            }
+        } else {
+            $order->update_status( $status );
+            if (wp_redirect( WC()->cart->get_cart_url() )) {
+                exit;
+            }
+        }
+    }
+}

+ 173 - 0
includes/Laybuy/Processes/RedirectPayment/Process.php

@@ -0,0 +1,173 @@
+<?php
+
+class Laybuy_Processes_RedirectPayment_Process extends Laybuy_Processes_AbstractProcess
+{
+    public $quoteId;
+    public $status;
+    public $token;
+
+    public function setQuoteId($quoteId)
+    {
+        $this->quoteId = $quoteId;
+        return $this;
+    }
+
+    public function getQuoteId()
+    {
+        return $this->quoteId;
+    }
+
+    public function setStatus($status)
+    {
+        $this->status = $status;
+        return $this;
+    }
+
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    public function setToken($token)
+    {
+        $this->token = $token;
+        return $this;
+    }
+
+    public function getToken()
+    {
+        return $this->token;
+    }
+
+    public function execute()
+    {
+        $status = $this->getStatus();
+        $quote_id = $this->getQuoteId();
+
+        switch ($status) {
+
+            case Laybuy_ApiGateway::PAYMENT_STATUS_SUCCESS:
+
+                if (get_post_meta( $quote_id, '_laybuy_quote_nonce', true ) !==  $_GET['_wpnonce']) {
+                    wp_redirect( wc_get_checkout_url() );
+                    exit;
+                }
+
+                WC_Laybuy_Logger::log("Confirming the payment for a quote {$quote_id}");
+
+                $currency = $this->_decode(get_post_meta( $quote_id, 'currency', true ));
+                $amount   = floatval($this->_decode(get_post_meta( $quote_id, 'total', true )));
+
+                // confirm payment
+                $response = $this->getProcessManager()
+                    ->getApiGateway()
+                    ->confirmOrder([
+                        'token' => $this->getToken(),
+                        'currency' => $currency,
+                        'amount' => $amount
+                    ]);
+
+                // returns a WP error object
+                if (is_wp_error($response) || !$response) {
+
+                    WC_Laybuy_Logger::log("Failed while confirming the payment for a quote {$quote_id}");
+                    WC_Laybuy_Logger::log("Request data: " . print_r(['token' => $this->getToken(), 'currency' => $currency, 'amount' => $amount], true));
+                    WC_Laybuy_Logger::log("Response data: " . print_r($response, true));
+
+                    $this->getProcessManager()->cancelQuote($quote_id, 'failed');
+
+                    # Store an error notice and redirect the customer back to the checkout.
+                    wc_add_notice( __( 'Your payment token has expired. Please try again.', 'woo_laybuy' ), 'error' );
+                    if (wp_redirect( wc_get_checkout_url() )) {
+                        exit;
+                    }
+                }
+
+                if ($response->result !== Laybuy_ApiGateway::PAYMENT_STATUS_SUCCESS) {
+
+                    WC_Laybuy_Logger::log("Failed while confirming the payment for a quote {$quote_id}");
+                    WC_Laybuy_Logger::log("Request data: " . print_r(['token' => $this->getToken(), 'currency' => $currency, 'amount' => $amount], true));
+                    WC_Laybuy_Logger::log("Response data: " . print_r($response, true));
+
+                    $this->getProcessManager()->cancelQuote($quote_id, 'failed');
+
+                    wc_add_notice( __( $response->error, 'woo_laybuy' ), 'error' );
+                    wp_redirect( wc_get_checkout_url() );
+                    exit;
+                }
+
+                WC_Laybuy_Logger::log("Payment confirmed. Quote {$quote_id}");
+                WC_Laybuy_Logger::log("Response: " . print_r($response, true));
+
+                $posted = $this->_decode(get_post_meta( $quote_id, 'posted', true ));
+                $order = $this->getProcessManager()->createOrderFromQuote($quote_id);
+
+                if( is_wp_error($order) ) {
+
+                    wc_add_notice( 'Invalid order.', 'error' );
+
+                    WC_Laybuy_Logger::error("Could not create order for quote {$quote_id}");
+
+                    wp_redirect( wc_get_checkout_url() );
+                    exit;
+                }
+
+                do_action( 'woocommerce_checkout_order_processed', $order->get_id(), $posted, $order );
+
+                update_post_meta($order->get_id(), '_laybuy_order_id', $response->orderId );
+                update_post_meta($order->get_id(), '_laybuy_token', $_GET['token'] );
+
+                // save to the DB
+                $order->save();
+
+                $order->add_order_note( __( "Payment approved. Laybuy Order ID: {$response->orderId}", 'woo_laybuy') );
+
+                // complete the order and reduce stock if required
+                $order->payment_complete($response->orderId);
+
+                wc_empty_cart();
+                $redirect = $this->getProcessManager()->getWcGateway()->get_return_url($order);
+
+                wp_redirect( html_entity_decode( $redirect ) );
+                exit;
+
+                break;
+
+            case Laybuy_ApiGateway::PAYMENT_STATUS_DECLINED:
+
+                WC_Laybuy_Logger::log("Payment declined. Quote {$quote_id}");
+
+                wc_add_notice( __( 'Your payment was declined.', 'woo_laybuy' ), 'error' );
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+
+                break;
+            case Laybuy_ApiGateway::PAYMENT_STATUS_CANCELLED:
+
+                $this->getProcessManager()->cancelQuote($_GET['quote_id']);
+
+                WC_Laybuy_Logger::log("Payment canceled. Quote {$quote_id}");
+
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+
+                break;
+            case Laybuy_ApiGateway::PAYMENT_STATUS_ERROR:
+
+                WC_Laybuy_Logger::log("Payment error.");
+
+                wc_add_notice(__('Your payment could not be processed. Please try again.', 'woo_laybuy'), 'error');
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+                break;
+            default:
+                if (wp_redirect( wc_get_checkout_url() )) {
+                    exit;
+                }
+                break;
+        }
+    }
+}

+ 102 - 0
includes/Laybuy/Processes/Refund.php

@@ -0,0 +1,102 @@
+<?php
+
+class Laybuy_Processes_Refund extends Laybuy_Processes_AbstractProcess
+{
+    public $orderId;
+    public $amount;
+    public $reason;
+
+    public function setOrderId($orderId)
+    {
+        $this->orderId = (int) $orderId;
+        return $this;
+    }
+
+    public function getOrderId()
+    {
+        return $this->orderId;
+    }
+
+    public function setAmount($amount)
+    {
+        $this->amount = $amount;
+        return $this;
+    }
+
+    public function getAmount()
+    {
+        return $this->amount;
+    }
+
+    public function setReason($reason)
+    {
+        $this->reason = $reason;
+        return $this;
+    }
+
+    public function getReason()
+    {
+        return $this->reason;
+    }
+
+    public function execute()
+    {
+        $order_id = $this->getOrderId();
+        $amount   = $this->getAmount();
+        $reason   = $this->getReason();
+
+        WC_Laybuy_Logger::log("Refunding WooCommerce Order #{$order_id} for \${$amount}...");
+
+        if (function_exists('wc_get_order')) {
+            $order = wc_get_order( $order_id );
+        } else {
+            $order = new WC_Order( $order_id );
+        }
+
+        $laybuy_token = get_post_meta($order_id, '_laybuy_token', TRUE);
+        $laybuy_order_id = get_post_meta( $order_id, '_laybuy_order_id', true );
+
+        if (!$laybuy_token) {
+
+            WC_Laybuy_Logger::error("Refund order {$order_id} failed:\n | Token not found");
+
+            $ret['error'] = 'Token not found error';
+            return $ret;
+        }
+
+        $response = $this->getProcessManager()->getApiGateway()->refund([
+                'orderId' => $laybuy_order_id,
+                'token' => $laybuy_token,
+                'amount' => $amount,
+                'refundReference' => $this->_makeUniqueReference($order_id),
+            ]
+        );
+
+        if( is_wp_error( $response ) ) {
+
+            WC_Laybuy_Logger::error("Failed to refund the order {$order_id}. " . print_r($response, true));
+
+            $order->update_status( 'failed', __( $response->get_error_message(), 'woocommerce_laybuy' ) );
+            wc_add_notice( __( 'Failed to refund the order, please try again, Error message: ', 'woocommerce_laybuy' ) . $response->get_error_message(), 'error' );
+
+            return $response;
+        }
+
+        if ('ERROR' === $response->result) {
+
+            $order->update_status( 'failed', __( $response->error, 'woocommerce_laybuy' ) );
+            $order->add_order_note( __( "Failed to send refund of \${$amount} to Laybuy.", 'woo_laybuy' ) );
+
+            wc_add_notice( __( "Failed to refund order {$order_id}", 'woocommerce_laybuy' ) . $response->error, 'error' );
+
+            return false;
+        }
+
+        WC_Laybuy_Logger::log("Successfully refunded {$amount} for order {$order_id}. " . print_r($response, true));
+
+        $order->add_order_note( __( "Refunded \${$amount} via Laybuy (Refund ID: {$response->refundId}). Reason: {$reason}", 'woo_laybuy' ) );
+        update_post_meta( $order_id, '_laybuy_refund_id', $response->refundId );
+
+        return true;
+    }
+}

+ 95 - 0
includes/Laybuy/SupportRequest.php

@@ -0,0 +1,95 @@
+<?php
+
+class Laybuy_SupportRequest
+{
+    const API_ENDPOINT = 'https://laybuy-support.staging.overdose.digital/api/support-request';
+    const API_KEY = 'ThnaJEEWp7zqJkEhYrGbdBJQLjLkUxgu';
+
+    public function send()
+    {
+        $body = [
+            'platform' => 'woocommerce',
+            'meta' => [
+                'website' => get_site_url(),
+                'date' => date('Y-m-d H:i:s', time())
+            ],
+            'env' => [
+                'wp_version' => get_bloginfo( 'version' ),
+                'wc_version' => WC_VERSION,
+                'laybuy_version' => WC_LAYBUY_VERSION
+            ],
+            'settings' => get_option( 'woocommerce_laybuy_settings', true),
+            'log' => null
+        ];
+
+        $logfile = WC_LOG_DIR . WC_Log_Handler_File::get_log_file_name('laybuy');
+
+        if (file_exists($logfile)) {
+            $body['log'] = base64_encode(file_get_contents($logfile));
+        } else {
+
+            $logFiles = WC_Log_Handler_File::get_log_files();
+
+            // try to find relevant logs for the last 14 days
+            $relevantLogFiles = [];
+            for ($i = 1; $i <= 14; $i++) {
+                $relevantLogFiles[] = 'laybuy-' . date('Y-m-d', strtotime("-{$i} days"));
+            }
+
+            $logFiles = array_filter($logFiles, function ($logFile) use ($relevantLogFiles) {
+                $result = (bool) strstr($logFile, 'laybuy');
+
+                if (!$result) {
+                    return false;
+                }
+
+                foreach ($relevantLogFiles as $relevantLogFile) {
+                    if (false !== strpos($logFile, $relevantLogFile)) {
+                        return true;
+                    }
+                }
+            });
+
+            if (count($logFiles) > 0) {
+
+                $logFiles = array_reverse($logFiles);
+                $logFiles = array_values(array_slice($logFiles, 0, 2));
+
+                $body['log'] = base64_encode(
+                    file_get_contents(WC_LOG_DIR . DIRECTORY_SEPARATOR . $logFiles[0]) . "\n\n\n" .
+                    file_get_contents(WC_LOG_DIR . DIRECTORY_SEPARATOR . $logFiles[1])
+                );
+            }
+        }
+
+        $laybuySupportResponse = wp_remote_post(self::API_ENDPOINT, [
+            'headers' => [
+                'x-access-token' => self::API_KEY
+            ],
+            'body' => $body
+        ]);
+
+        if ( is_wp_error( $laybuySupportResponse ) ) {
+            wp_send_json_error();
+        }
+
+        $laybuySupportResponse = json_decode($laybuySupportResponse['body'], true);
+
+        if (!isset($laybuySupportResponse['success']) || !$laybuySupportResponse['success']) {
+            wp_send_json_error();
+        }
+
+        $to = 'overdose@laybuy.com';
+        $subject = 'Woo Support Request from ' . get_site_url();
+        $body = $laybuySupportResponse['link'];
+        $headers = ['Content-Type: text/html; charset=UTF-8'];
+
+        $result = wp_mail( $to, $subject, $body, $headers );
+
+        if (!$result) {
+            wp_send_json_error();
+        }
+
+        wp_send_json_success();
+    }
+}

+ 28 - 0
includes/admin/wysiwyg.php

@@ -0,0 +1,28 @@
+<tr valign="top">
+    <th scope="row" class="titledesc">
+        <?php if (!empty($title)): ?>
+            <label for="<?php echo $id; ?>">
+                <?php _e( $title, 'woo_laybuy' ); ?>
+            </label>
+        <?php endif; ?>
+    </th>
+    <td class="forminp">
+        <fieldset>
+            <?php if (!empty($title)): ?>
+                <legend class="screen-reader-text">
+                    <span><?php _e( $title, 'woo_laybuy' ); ?></span>
+                </legend>
+            <?php endif; ?>
+            <?php
+            wp_editor(html_entity_decode($value), $id, array(
+                'textarea_name' => $name,
+                'editor_class' => $class,
+                'editor_css' => $css,
+                'autop' => true,
+                'textarea_rows' => 8
+            ));
+            ?>
+            <p class="description"><?php _e( $description, 'woo_laybuy' ); ?></p>
+        </fieldset>
+    </td>
+</tr>

+ 120 - 0
includes/assets.php

@@ -0,0 +1,120 @@
+<?php
+
+return array(
+    'gbp' => array(
+        'name' => "GB",
+        'product_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]</a>',
+        'category_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_page' => '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_cta' => 'Six interest-free payments totalling',
+        'checkout_page_first_step' => 'First Installment',
+        'checkout_page_footer' => 'You will be redirected to the Laybuy website when you click',
+        'fallback_asset' => 'Installments by [laybuy_logo] available between <strong>[MIN_LIMIT]</strong> - <strong>[MAX_LIMIT]</strong>',
+        'checkout_page' => '<div class="laybuy-checkout-content "><p class="title"> Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+        'category_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with <span id="laybuy-what-is-modal" class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_pay_over_limit_asset' => '<tr><td colspan="2" style="font-size: 14px">or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_pay_over_limit' => '<div class="laybuy-checkout-content "><p class="title"> Pay <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+
+        'available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+        'available_on_orders_under' => '[laybuy_logo]<br>Available on orders under [MAX_PRICE]',
+
+        'product_thumbnails_available_on_orders_between' => 'Available on orders [MIN_PRICE] to [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_over' => 'Available on orders over [MIN_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_under' => 'Available on orders under [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+
+        'cart_available_on_orders_between' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders [MIN_PRICE] to [MAX_PRICE]</td></tr>',
+        'cart_available_on_orders_over' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders over [MIN_PRICE]</td></tr>',
+
+        'checkout_available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'checkout_available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+    ),
+    'aud' => array(
+        'name' => "AU",
+        'product_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]</a>',
+        'category_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_page' => '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_cta' => 'Six interest-free payments totalling',
+        'checkout_page_first_step' => 'First Installment',
+        'checkout_page_footer' => 'You will be redirected to the Laybuy website when you click',
+        'fallback_asset' => 'Installments by [laybuy_logo] available between <strong>[MIN_LIMIT]</strong> - <strong>[MAX_LIMIT]</strong>',
+        'checkout_page' => '<div class="laybuy-checkout-content "><p class="title"> Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+        'category_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with <span id="laybuy-what-is-modal" class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_pay_over_limit_asset' => '<tr><td colspan="2" style="font-size: 14px">or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_pay_over_limit' => '<div class="laybuy-checkout-content "><p class="title"> Pay <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+
+        'available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+        'available_on_orders_under' => '[laybuy_logo]<br>Available on orders under [MAX_PRICE]',
+
+        'product_thumbnails_available_on_orders_between' => 'Available on orders [MIN_PRICE] to [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_over' => 'Available on orders over [MIN_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_under' => 'Available on orders under [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+
+        'cart_available_on_orders_between' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders [MIN_PRICE] to [MAX_PRICE]</td></tr>',
+        'cart_available_on_orders_over' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders over [MIN_PRICE]</td></tr>',
+
+        'checkout_available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'checkout_available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+    ),
+    'nzd' => array(
+        'name' => "NZ",
+        'product_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]</a>',
+        'category_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_page' => '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_cta' => 'Six interest-free payments totalling',
+        'checkout_page_first_step' => 'First Installment',
+        'checkout_page_footer' => 'You will be redirected to the Laybuy website when you click',
+        'fallback_asset' => 'Installments by [laybuy_logo] available between <strong>[MIN_LIMIT]</strong> - <strong>[MAX_LIMIT]</strong>',
+        'checkout_page' => '<div class="laybuy-checkout-content "><p class="title"> Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+        'category_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with <span id="laybuy-what-is-modal" class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_pay_over_limit_asset' => '<tr><td colspan="2" style="font-size: 14px">or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_pay_over_limit' => '<div class="laybuy-checkout-content "><p class="title"> Pay <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+
+        'available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+        'available_on_orders_under' => '[laybuy_logo]<br>Available on orders under [MAX_PRICE]',
+
+        'product_thumbnails_available_on_orders_between' => 'Available on orders [MIN_PRICE] to [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_over' => 'Available on orders over [MIN_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_under' => 'Available on orders under [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+
+        'cart_available_on_orders_between' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders [MIN_PRICE] to [MAX_PRICE]</td></tr>',
+        'cart_available_on_orders_over' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders over [MIN_PRICE]</td></tr>',
+
+        'checkout_available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'checkout_available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+    ),
+    'usd' => array(
+        'name' => "US",
+        'product_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]</a>',
+        'category_page' => 'or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_page' => '<tr><td colspan="2" style="font-size: 14px">or 6 weekly interest-free payments from <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_cta' => 'Six interest-free payments totalling',
+        'checkout_page_first_step' => 'First Installment',
+        'checkout_page_footer' => 'You will be redirected to the Laybuy website when you click',
+        'fallback_asset' => 'Installments by [laybuy_logo] available between <strong>[MIN_LIMIT]</strong> - <strong>[MAX_LIMIT]</strong>',
+        'checkout_page' => '<div class="laybuy-checkout-content "><p class="title"> Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+        'category_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with <span id="laybuy-what-is-modal" class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_page_pay_over_limit' => 'or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo]',
+        'cart_pay_over_limit_asset' => '<tr><td colspan="2" style="font-size: 14px">or from <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong> with [laybuy_logo link=https://www.laybuy.com]</td></tr>',
+        'checkout_page_pay_over_limit' => '<div class="laybuy-checkout-content "><p class="title"> Pay <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg"><img src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg"><img class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg"><img class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg"></div></div><div style="clear:both" />',
+
+        'available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+        'available_on_orders_under' => '[laybuy_logo]<br>Available on orders under [MAX_PRICE]',
+
+        'product_thumbnails_available_on_orders_between' => 'Available on orders [MIN_PRICE] to [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_over' => 'Available on orders over [MIN_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+        'product_thumbnails_available_on_orders_under' => 'Available on orders under [MAX_PRICE]<br><span class="laybuy-cat-page" style="width:50px"> [laybuy_logo]</span>',
+
+        'cart_available_on_orders_between' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders [MIN_PRICE] to [MAX_PRICE]</td></tr>',
+        'cart_available_on_orders_over' => '<tr><td colspan="2" style="font-size: 14px">[laybuy_logo link=https://www.laybuy.com]available on orders over [MIN_PRICE]</td></tr>',
+
+        'checkout_available_on_orders_between' => '[laybuy_logo]<br>Available on orders [MIN_PRICE] to [MAX_PRICE]',
+        'checkout_available_on_orders_over' => '[laybuy_logo]<br>Available on orders over [MIN_PRICE]',
+    ),
+);

+ 1073 - 0
includes/class-wc-gateway-laybuy.php

@@ -0,0 +1,1073 @@
+<?php
+
+if ( ! defined( 'ABSPATH' ) ) {
+    exit;
+}
+
+class WC_Payment_Gateway_Laybuy extends WC_Payment_Gateway
+{
+    const GATEWAY_NAME = 'laybuy';
+    const PAYMENTS_COUNT = 6;
+
+    const COOKIE_GEOLOCATION_COUNTRY = 'laybuy_geo_country';
+
+    const LOGO_THEME_WHITE = 'white';
+    const LOGO_THEME_DARK  = 'dark';
+
+    protected $supported_currencies;
+
+    protected $pay_over_time_limit_max = 1200;
+    protected $pay_over_time_limit_min = 0.06;
+
+    protected $pay_limits_max = [
+        'NZD' => 1500,
+        'AUD' => 1200,
+        'GBP' => 720,
+        'USD' => 1200,
+    ];
+
+    protected $compatibility_mode = false;
+
+    protected $assets = [];
+
+    protected $apiGateway;
+
+    protected static $instance;
+
+    const MODE_BASIC = 'basic';
+    const MODE_PLUS  = 'plus';
+
+    protected $mode = self::MODE_BASIC;
+
+    public function __construct() {
+
+        $this->id = self::GATEWAY_NAME;
+        $this->icon = apply_filters( 'woocommerce_laybuy_gateway_icon', 'https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_logo_small.svg' );
+        $this->has_fields   = false;
+        $this->method_title = 'Laybuy';
+        $this->method_description = __( 'Use Laybuy as a credit card processor for WooCommerce.', 'woo_laybuy' );
+
+        $this->supported_currencies = array(
+            'AUD', 'NZD', 'GBP', 'USD'
+        );
+
+        if (defined('WC_LAYBUY_PLUS')) {
+            $this->mode = self::MODE_PLUS;
+        }
+
+        // Get setting values.
+        $this->title         = $this->get_option( 'title' );
+        $this->description   = 'Pay by Laybuy';
+        $this->enabled       = $this->get_option( 'enabled' ) === 'yes';
+
+        $this->assets = include 'assets.php';
+
+        $currency =	get_woocommerce_currency();
+
+        if (!empty($this->assets[strtolower($currency)])) {
+            $this->assets =	$this->assets[strtolower($currency)];
+        } else {
+            $this->assets = $this->assets['aud'];
+        }
+
+        $this->supports	= array('products', 'refunds');
+
+        $this->init_form_fields();
+        $this->init_settings();
+
+        if ($this->enabled === 'no') {
+            return;
+        }
+
+        $currency = get_woocommerce_currency();
+        if (in_array($currency, $this->supported_currencies)) {
+            if ($this->is_plus()) {
+
+                $this->pay_over_time_limit_min = floatval($this->settings["${currency}_pay_limit_min"]);
+                $this->pay_over_time_limit_max = floatval($this->settings["${currency}_pay_limit_max"]);
+
+                $this->check_pay_over_time_limits();
+
+            } else {
+                $this->pay_over_time_limit_max = $this->pay_limits_max[$currency];
+            }
+        }
+
+        if (!is_admin()) {
+            $this->compatibility_mode = $this->settings['laybuy_compatibility_mode'] == 'yes';
+        }
+
+        $this->apiGateway = new Laybuy_ApiGateway($this->settings);
+
+        if (array_key_exists('debug', $this->settings)) {
+            WC_Laybuy_Logger::$enabled = $this->settings['debug'] === 'yes';
+        }
+    }
+
+    /**
+     * Instantiate the class if no instance exists. Return the instance.
+     *
+     * @since	2.0.0
+     * @return	WC_Payment_Gateway_Laybuy
+     */
+    public static function getInstance()
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new self;
+        }
+        return self::$instance;
+    }
+
+    /**
+     * Initialise Gateway Settings Form Fields
+     */
+    public function init_form_fields() {
+        $this->form_fields = require( dirname( __FILE__ ) . '/laybuy-settings.php' );
+    }
+
+    /**
+     * Is currency enabled for laybuy
+     * @return	bool
+     */
+    public function is_currency_enabled($currency) {
+        return in_array($currency, (array) @$this->settings['currency']);
+    }
+
+    public function payment_fields() {
+        if ($description = $this->get_description()) {
+            $description = apply_filters('checkout_modify_description', $description, $this->get_order_total());
+            echo wpautop(wptexturize($description));
+        }
+    }
+
+    /**
+     * Process a refund if supported.
+     *
+     * Note:	This overrides the method defined in the parent class.
+     *
+     * @since	1.0.0
+     * @see		WC_Payment_Gateway::process_refund()		For the method that this overrides.
+     * @param	int			$order_id
+     * @param	float		$amount							Optional. The amount to refund. This cannot exceed the total.
+     * @param	string		$reason							Optional. The reason for the refund. Defaults to an empty string.
+     * @return	bool
+     */
+    public function process_refund($order_id, $amount = null, $reason = '') {
+
+        $order_id = (int) $order_id;
+
+        if (function_exists('wc_get_order')) {
+            $order = wc_get_order( $order_id );
+        } else {
+            $order = new WC_Order( $order_id );
+        }
+
+        if (method_exists($this, 'can_refund_order') && !$this->can_refund_order($order)) {
+            WC_Laybuy_Logger::error('Refund Failed - No Transaction ID.');
+            return false;
+        }
+
+        $processManager = Laybuy_ProcessManager::getInstance();
+        $processManager->setApiGateway($this->apiGateway)
+            ->setWcGateway($this);
+
+        return $processManager->refund($order_id, $amount, $reason);
+    }
+
+    public function generate_wysiwyg_html($key, $data) {
+        $html = '';
+
+        $id = str_replace('-', '', $key);
+        $class = array_key_exists('class', $data) ? $data['class'] : '';
+        $css = array_key_exists('css', $data) ? ('<style>' . $data['css'] . '</style>') : '';
+        $name = "{$this->plugin_id}{$this->id}_{$key}";
+        $title = array_key_exists('title', $data) ? $data['title'] : '';
+        $value = array_key_exists($key, $this->settings) ? esc_attr( $this->settings[$key] ) : '';
+        $description = array_key_exists('description', $data) ? $data['description'] : '';
+
+        ob_start();
+
+        include dirname( __FILE__ ) . '/admin/wysiwyg.php';
+
+        $html = ob_get_clean();
+
+        return $html;
+    }
+
+    /**
+     * Process the HTML from one of the rich text editors and output the converted string.
+     */
+    private function process_and_print_laybuy_paragraph($output_filter, $product = null) {
+
+        if (is_admin()) {
+            return;
+        }
+
+        if (is_null($product)) {
+            $product = $this->get_product_from_the_post();
+        }
+
+        if (!$this->is_product_supported($product, true)) {
+            # Don't display anything on the product page if the product is not supported when purchased on its own.
+            return;
+        }
+
+        if (!$this->is_currency_supported()) {
+            # Don't display anything on the product page if the website currency is not within supported currencies.
+            return;
+        }
+
+        if (!$product->is_in_stock() && $this->settings['laybuy_price_breakdown_out_of_stock'] == 'no') {
+            return;
+        }
+
+        $of_or_from = 'of';
+        $price = $this->getProductPrice($product);
+        $price = floatval($price);
+
+        if (!$price) {
+            return '';
+        }
+
+        if (!$this->is_product_within_limits($product, true) && $output_filter == 'laybuy_html_on_product_thumbnails') {
+
+            if ($this->is_plus() && $price < $this->pay_over_time_limit_min) {
+                $html = $this->get_plus_limits_html('product_thumbnails_');
+            } else {
+                $html = $this->assets['category_page_pay_over_limit'];
+            }
+
+            $html = str_replace(array(
+                '[PAY_TODAY]',
+                '[AMOUNT]',
+                '[MIN_PRICE]',
+                '[MAX_PRICE]',
+            ), array(
+                $this->display_price_html( $price - $this->pay_over_time_limit_max ),
+                $this->display_price_html( $this->pay_over_time_limit_max / 5 ),
+                wc_price( $this->pay_over_time_limit_min, ['decimals' => 0] ),
+                wc_price( $this->pay_over_time_limit_max, ['decimals' => 0] ),
+            ), $html);
+
+        } else {
+
+            $amount = $this->display_price_html( round($price / self::PAYMENTS_COUNT, 2) );
+
+            $html = str_replace(array(
+                '[OF_OR_FROM]',
+                '[AMOUNT]'
+            ), array(
+                $of_or_from,
+                $amount
+            ), $this->settings['category_pages_info_text']);
+        }
+
+        # Execute shortcodes on the string after running internal replacements,
+        # but before applying filters and rendering.
+        $html = do_shortcode( "<p class=\"laybuy-payment-info\">{$html}</p>" );
+
+        # Add the Modal Window to the page
+        # Website Admin have no access to the Modal Window codes for data integrity reasons
+        //$html = $this->apply_modal_window($html);
+
+        # Allow other plugins to maniplulate or replace the HTML echoed by this funtion.
+        echo apply_filters( $output_filter, $html, $product, $price );
+    }
+
+    /**
+     * Print a paragraph of Laybuy info onto each product item in the shop loop if enabled and the product is valid.
+     *
+     * Note:	Hooked onto the "woocommerce_after_shop_loop_item_title" Action.
+     */
+    public function print_info_for_listed_products($product = null) {
+
+        if (!$this->isAllowCurrencyForLaybuy()) {
+            return;
+        }
+
+        if( is_single()) {
+
+            if (!isset($this->settings['show_info_on_product_pages'])
+                || $this->settings['show_info_on_product_pages'] != 'yes'
+                || empty($this->settings['category_pages_info_text'])
+                || !empty($product) && (!$product->is_in_stock() && $this->settings['laybuy_price_breakdown_out_of_stock'] == 'no') ) {
+                # Don't display anything on Single product page unless the Display on Individual Page is enabled
+                # The Variant selected is in stock
+                return;
+            }
+        }
+        else { #Category Pages
+
+            if (!isset($this->settings['show_info_on_category_pages'])
+                || $this->settings['show_info_on_category_pages'] != 'yes'
+                || empty($this->settings['category_pages_info_text'])) {
+                # Don't display anything on product items within the shop loop unless
+                # the "Payment info on product listing pages" box is ticked
+                # and there is a message to display.
+                return;
+            }
+
+            $this->process_and_print_laybuy_paragraph(
+                'laybuy_html_on_product_thumbnails',
+                $product
+            );
+        }
+    }
+
+    /**
+     * Render Laybuy elements (logo and payment schedule) on Cart page.
+     *
+     * This is dependant on all of the following criteria being met:
+     *		- The Laybuy Payment Gateway is enabled.
+     *		- The cart total is valid and within the merchant payment limits.
+     *		- The "Payment Info on Cart Page" box is ticked and there is a message to display.
+     *		- All of the items in the cart are considered eligible to be purchased with Laybuy.
+     *
+     * Note:	Hooked onto the "woocommerce_cart_totals_after_order_total" Action.
+     */
+    public function render_schedule_on_cart_page() {
+
+        $total = WC()->cart->total;
+
+        if (!array_key_exists('enabled', $this->settings) || $this->settings['enabled'] != 'yes') {
+            return;
+        } else {
+
+            if ($total <= 0 ) {
+                return;
+            }
+        }
+        if(!$this->isAllowCurrencyForLaybuy()) {
+            return;
+        }
+
+        if (
+            !isset($this->settings['show_info_on_cart_page']) ||
+            $this->settings['show_info_on_cart_page'] != 'yes' ||
+            empty($this->settings['cart_page_info_text'])
+        ) {
+            return;
+        }
+
+        foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
+            $product = $cart_item['data'];
+            if (!$this->is_product_supported($product)) {
+                return;
+            }
+        }
+
+
+        if ($this->is_plus() && $total < $this->pay_over_time_limit_min) {
+
+            $html = '<tr><td colspan="2">' . $this->get_plus_limits_html('cart_') . '<td/><tr/>';
+            $html = str_replace(array(
+                '[MIN_PRICE]',
+                '[MAX_PRICE]',
+            ), array(
+                wc_price( $this->pay_over_time_limit_min, ['decimals' => 0] ),
+                wc_price( $this->pay_over_time_limit_max, ['decimals' => 0] ),
+            ), $html);
+
+        } else if ( $this->pay_over_time_limit_max && $total > $this->pay_over_time_limit_max) {
+
+            $fallback_asset = $this->assets['cart_pay_over_limit_asset'];
+            $html = '<tr><td colspan="2">' . $fallback_asset . '<td/><tr/>';
+
+            $html = str_replace(array(
+                '[PAY_TODAY]',
+                '[AMOUNT]'
+            ), array(
+                $this->display_price_html( $total - max($this->pay_over_time_limit_min, $this->pay_over_time_limit_max) ),
+                $this->display_price_html( max($this->pay_over_time_limit_min, $this->pay_over_time_limit_max) / 5 )
+            ), $html);
+        } else {
+            $schedule = $this->calculation(WC()->cart->total);
+            $amount = $this->display_price_html($schedule['minimum_today']);
+
+            $html = str_replace(array(
+                '[AMOUNT]',
+            ), array(
+                $amount,
+            ), $this->settings['cart_page_info_text']);
+        }
+
+        # Execute shortcodes on the string before applying filters and rendering it.
+        $html = do_shortcode( $html );
+
+        # Add the Modal Window to the page
+        # Website Admin have no access to the Modal Window codes for data integrity reasons
+        // $html = $this->apply_modal_window($html);
+
+        # Allow other plugins to maniplulate or replace the HTML echoed by this funtion.
+        echo apply_filters( 'laybuy_html_on_cart_page', $html );
+    }
+
+
+    /**
+     * Convert the global $post object to a WC_Product instance.
+     */
+    private function get_product_from_the_post() {
+        global $post;
+
+        if (function_exists('wc_get_product')) {
+            $product = wc_get_product( $post->ID );
+        } else {
+            $product = new WC_Product( $post->ID );
+        }
+
+        return $product;
+    }
+
+    /**
+     * Is the given product supported by the Laybuy gateway?
+     */
+    private function is_product_supported($product, $alone = false) {
+
+        if (!isset($this->settings['enabled']) || $this->settings['enabled'] != 'yes') {
+            return false;
+        }
+
+        $productTypes = (array) $this->settings['product_types'];
+
+        if (false !== array_search('variable', $productTypes)) {
+            $productTypes[] = 'variation';
+        }
+
+        if (!empty($productTypes) && !in_array($product->get_type(), $productTypes)) {
+            return false;
+        }
+
+        # Allow other plugins to exclude Laybuy from products that would otherwise be supported.
+        return (bool)apply_filters( 'laybuy_is_product_supported', true, $product, $alone );
+    }
+
+    private function is_product_within_limits($product, $alone = false) {
+
+        if ($this->is_plus()) {
+            return $this->is_product_within_limits_plus($product);
+        }
+
+        $price = $this->getProductPrice($product);
+
+        if ( $price < $this->pay_over_time_limit_min || $price > $this->pay_over_time_limit_max ) {
+            return false;
+        }
+
+        if ( $alone && $price < $this->pay_over_time_limit_min) {
+            # If the product is viewed as being on its own and priced lower that the merchant's minimum, it will be considered as not supported.
+            return false;
+        }
+
+        return true;
+    }
+
+    private function is_product_within_limits_plus($product)
+    {
+        $price = $this->getProductPrice($product);
+
+        if ($this->pay_over_time_limit_min && $this->pay_over_time_limit_max) {
+            return $price >= $this->pay_over_time_limit_min && $price <= $this->pay_over_time_limit_max;
+        }
+
+        if (!$this->pay_over_time_limit_min && $this->pay_over_time_limit_max) {
+            return $price <= $this->pay_over_time_limit_max;
+        }
+
+        if ($this->pay_over_time_limit_min && !$this->pay_over_time_limit_max) {
+            return $price >= $this->pay_over_time_limit_min;
+        }
+
+        return true;
+    }
+
+    /**
+     * Is the the website currency supported by the Laybuy gateway?
+     */
+    private function is_currency_supported() {
+        $store_currency = strtoupper(get_woocommerce_currency());
+        return in_array($store_currency, $this->supported_currencies);
+    }
+
+    private function display_price_html($price) {
+        if (function_exists('wc_price')) {
+            return wc_price($price);
+        } elseif (function_exists('woocommerce_price')) {
+            return woocommerce_price($price);
+        }
+    }
+
+    /**
+     * Provide a shortcode for rendering the standard Laybuy logo on individual product pages.
+     *
+     * E.g.:
+     * 	- [laybuy_product_logo]
+     *
+     * @return	string
+     */
+    public function shortcode_laybuy_logo($atts) {
+
+        $atts = shortcode_atts( array(
+            'width' => '80px',
+            'link' => '',
+            'style' => 'float:none; display:inline-block; vertical-align:middle; height:1.2em; top: -2px; position: relative;'
+        ), $atts );
+
+        $logoUrl = 'https://integration-assets.laybuy.com/woocommerce_laybuy_icons/';
+
+        if ($this->settings['laybuy_logo_theme'] === self::LOGO_THEME_WHITE) {
+            $logoUrl .= 'laybuy_logo_small.svg';
+        } else {
+            $logoUrl .= 'laybuy_logo_small_white.svg';
+        }
+
+        if (!empty($atts['link'])) {
+            return <<<LOGO
+<a href = "{$atts['link']}" target = "_blank" class="laybuy-logo-link" ><img style = "{$atts['style']}" src="{$logoUrl}" alt = "Laybuy" /></a >
+LOGO;
+        } else {
+
+            return <<<LOGO
+<img src="{$logoUrl}" alt = "Laybuy" />
+LOGO;
+        }
+    }
+
+    public function shortcode_laybuy_iframe_src() {
+
+        $currency = get_woocommerce_currency();
+
+        switch ($currency) {
+            case 'AUD' :
+                $code = 'au';
+                break;
+            case 'GBP' :
+                $code = 'gb';
+                break;
+            case 'USD' :
+                $code = 'us';
+                break;
+            default :
+                $code = 'nz';
+                break;
+        }
+        return 'https://integration-assets.laybuy.com/laybuy-cms-page/dist/index_' . $code . '.html';
+    }
+
+    public function product_price_breakdown() {
+
+        global $product;
+
+        if (
+            !is_product() ||
+            (!$product->is_in_stock() && $this->settings['laybuy_price_breakdown_out_of_stock'] == 'no') ||
+            !$this->is_product_supported($product, true)
+        ) {
+            return;
+        }
+
+        $settings = get_option( 'woocommerce_laybuy_settings', true );
+
+        $font_size  = (strlen($settings['laybuy_fontsize_in_breakdowns']) == 4) ? $settings['laybuy_fontsize_in_breakdowns'] : '12px';
+        $logo_width = '80px';
+
+        if ($font_size === '14px') {
+            $logo_width = '90px';
+        }
+        elseif ($font_size === '16px') {
+            $logo_width = '100px';
+        }
+        elseif ($font_size === '18px') {
+            $logo_width = '110px';
+        }
+        elseif ($font_size === '20px') {
+            $logo_width = '120px';
+        }
+
+        $price = floatval($this->getProductPrice($product));
+
+        if (!$this->isAllowCurrencyForLaybuy() || empty($price)) {
+            return;
+        }
+
+        $payment_breakdown = $this->calculation($price);
+        $weekly_payment = wc_price($payment_breakdown['weekly_payment']);
+
+        if (!$price) {
+            return '';
+        }
+
+        if ($this->is_plus() && $price < $this->pay_over_time_limit_min) {
+
+            $html = str_replace(array(
+                '[MIN_PRICE]',
+                '[MAX_PRICE]',
+            ), array(
+                wc_price( $this->pay_over_time_limit_min, ['decimals' => 0]),
+                wc_price( $this->pay_over_time_limit_max, ['decimals' => 0])
+            ), $this->get_plus_limits_html());
+
+            echo '<p class="laybuy-inline-widget">' . do_shortcode($html) . '</p>';
+
+            return;
+        }
+
+        if (!$this->is_product_within_limits($product)) {
+
+            $html = $this->assets['product_page_pay_over_limit'];
+
+            $html = str_replace(array(
+                '[PAY_TODAY]',
+                '[AMOUNT]'
+            ), array(
+                $this->display_price_html( $price - $this->pay_over_time_limit_max ),
+                $this->display_price_html( $this->pay_over_time_limit_max / 5 )
+            ), $html);
+
+            $html_breakdown = '<p class="laybuy-inline-widget">' . do_shortcode($html) . '</p>';
+            echo $html_breakdown;
+
+            return;
+        }
+
+
+        $html_breakdown = '<p class="laybuy-inline-widget">';
+        $html_breakdown .= str_replace(array(
+            '[AMOUNT]',
+            '[LOGO_WIDTH]'
+        ), array(
+            $weekly_payment,
+            $logo_width
+        ), do_shortcode( $this->settings['product_pages_info_text'] ));
+
+        $html_breakdown .= '</p>';
+
+        echo $html_breakdown;
+    }
+
+    public function isAllowCurrencyForLaybuy() {
+        return in_array(get_woocommerce_currency(), $this->settings['currency']);
+    }
+
+    public function checkout_modify_description( $description, $total ) {
+
+        $settings = get_option( 'woocommerce_laybuy_settings', true );
+
+        if ($total < $this->pay_over_time_limit_min) {
+            return '';
+        }
+
+        $payment_breakdown = $this->calculation( $total );
+        $currencyCode = $settings['laybuy_currency_prefix_in_breakdowns'] === 'yes';
+
+        $price_prefix = '';
+
+        if ($currencyCode) {
+            $price_prefix = get_woocommerce_currency() . ' ';
+        }
+        if(!$this->isAllowCurrencyForLaybuy()) {
+            return;
+        }
+
+        $weekly_payment = $price_prefix . wc_price( $payment_breakdown['weekly_payment'] );
+
+        if ($this->pay_over_time_limit_max && $total > $this->pay_over_time_limit_max) {
+
+            if (isset($settings['laybuy_wide_layout_setting']) && $settings['laybuy_wide_layout_setting'] == "yes") {
+                $html = '<div class="laybuy-checkout-content "><p class="title">Pay <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img style="width:25% !important" class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg" /><img style="width:25% !important" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg" /><img style="width:25% !important" class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg" /><img style="width:25% !important" class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg" /></div>
+</div><div style="clear: both;"></div>';
+            } else {
+                $html = $this->assets['checkout_page_pay_over_limit'];
+            }
+
+            $html = str_replace(array(
+                '[PAY_TODAY]',
+                '[AMOUNT]'
+            ), array(
+                $this->display_price_html( $total - $this->pay_over_time_limit_max ),
+                $this->display_price_html( $this->pay_over_time_limit_max / 5 )
+            ), $html);
+
+        } else {
+            if (isset($settings['laybuy_wide_layout_setting']) && $settings['laybuy_wide_layout_setting'] == "yes") {
+                $html = '<div class="laybuy-checkout-content "><p class="title">Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img style="width:25% !important" class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg" /><img style="width:25% !important" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg" /><img style="width:25% !important" class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg" /><img style="width:25% !important" class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg" /></div>
+</div><div style="clear: both;"></div>';
+            } else {
+                $html = do_shortcode($this->settings['checkout_page_info_text']);
+            }
+        }
+
+        $html = nl2br($html);
+
+        if (!$html) {
+            return '';
+        }
+
+        $html_breakdown = '<p class="laybuy-checkout-widget">';
+        $html_breakdown .= str_replace(array(
+            '[AMOUNT]'
+        ), array(
+            $weekly_payment
+        ), $html);
+
+        $html_breakdown .= '</p>';
+
+        return $html_breakdown;
+    }
+
+    public function calculation($dividend, $divisor = self::PAYMENTS_COUNT) {
+
+        // multiplying it to 100 makes is having a better precision.
+        // stripe use this
+        $dividend = $dividend * 100;
+
+        if( 0 < ($dividend % $divisor) ) {
+            // get weeklys
+            $weekly_payment = intval( $dividend / $divisor );
+
+            // get minimum payment
+            $minimum_today = intval( $dividend - ($weekly_payment * 5) );
+
+            $calculation = array(
+                'minimum_today' => $minimum_today * 0.01,
+                'weekly_payment' => $weekly_payment * 0.01
+            );
+        } else {
+            $even_weekly_payment = $dividend / $divisor;
+
+            $calculation = array(
+                'minimum_today' => $even_weekly_payment * 0.01,
+                'weekly_payment' => $even_weekly_payment * 0.01
+            );
+
+        }
+
+        return $calculation;
+    }
+
+    /**
+     * Processses the orders that are redirected.
+     *
+     * @since 4.0.0
+     * @version 4.0.0
+     */
+    public function process_redirect_order() {
+
+        if (
+            !isset($_GET['gateway_id']) ||
+            constant('WC_GATEWAY_LAYBUY_ID') !== $_GET['gateway_id'] ||
+            !isset($_GET['post_type']) ||
+            !isset($_GET['quote_id'])
+        ) {
+            return;
+        }
+
+        $quote_id = wc_clean( $_GET['quote_id'] );
+        $status   = strtoupper($_GET['status']);
+        $token    = $_GET['token'];
+
+        $processManager = Laybuy_ProcessManager::getInstance();
+        $processManager->setApiGateway($this->apiGateway)
+            ->setWcGateway($this)
+            ->setCompatibilityMode($this->compatibility_mode);
+
+        $processManager->processRedirectPayment($quote_id, $status, $token);
+    }
+
+    /**
+     * Create order - Part 1 of 2.
+     *
+     * Override WooCommerce's create_order function and make our own order-quote object. We will manually
+     * convert this into a proper WC_Order object later, if the checkout completes successfully. Part of the data
+     * collected here is submitted to the Laybuy API to generate a token, the rest is persisted to the
+     * database to build the WC_Order object. Based on WooCommerce 2.6.8.
+     *
+     * Note:	This needs to follow the WC_Checkout::create_order() method very closely. In order to properly
+     * 			create the WC_Order object later, we need to make sure we're storing all of the data that will be
+     * 			needed later. If it fails, it needs to return an integer that evaluates to true in order to bypass the
+     * 			standard WC_Order creation process.
+     *
+     * Note:	Hooked onto the "woocommerce_create_order" Filter.
+     *
+     */
+    public function create_order_quote($null, $checkout) {
+
+        if ($this->compatibility_mode) {
+            return;
+        }
+
+        $processManager = Laybuy_ProcessManager::getInstance();
+        $processManager->setApiGateway($this->apiGateway)
+            ->setWcGateway($this)
+            ->createQuote($checkout);
+    }
+
+    public function process_payment($order_id)
+    {
+        return Laybuy_ProcessManager::getInstance()
+            ->setApiGateway($this->apiGateway)
+            ->setWcGateway($this)
+            ->createOrder($order_id);
+    }
+
+    /**
+     * If calling wc_create_order() for an Laybuy Quote, tell wp_insert_post() to reuse the ID of the quote.
+     */
+    public function filter_woocommerce_new_order_data( $order_data ) {
+        if (array_key_exists('laybuy_quote_id', $GLOBALS) && is_numeric($GLOBALS['laybuy_quote_id']) && $GLOBALS['laybuy_quote_id'] > 0) {
+            $order_data['import_id'] = (int) $GLOBALS['laybuy_quote_id'];
+            unset($GLOBALS['laybuy_quote_id']);
+        }
+        return $order_data;
+    }
+
+    /**
+     * Checks to see if all criteria is met before showing payment method.
+     *
+     * @return bool
+     */
+    public function is_available() {
+
+        return true; // debug
+
+        $is_available = parent::is_available();
+
+        if (!$is_available) {
+            return false;
+        }
+
+        $currency = get_woocommerce_currency();
+
+        if ($this->settings['laybuy_geolocate_ip'] === 'no') {
+            return $this->is_currency_enabled($currency);
+        }
+
+        $country = $this->get_country();
+
+        if ($country) {
+
+            $map = ['GBP' => 'GB', 'AUD' => 'AU', 'NZD' => 'NZ', 'USD' => 'US'];
+
+            $countryList = [];
+
+            foreach (array_values($this->settings['currency']) as $cur) {
+                if (isset($map[$cur])) {
+                    $countryList[] = $map[$cur];
+                }
+            }
+
+            if (count($countryList) > 0 && !in_array($country, $countryList)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        return $this->is_currency_enabled($currency);
+    }
+
+    public function filter_woocommerce_get_price_html($price, $product) {
+
+        if (is_object($product) && $product instanceof WC_Product) {
+            ob_start();
+            $this->print_info_for_listed_products($product);
+            $html = ob_get_clean();
+
+            return $price . $html;
+        }
+        return $price;
+    }
+
+    public function getProductPrice($product)
+    {
+        if ($product->is_type('composite')) {
+            return $product->get_composite_price_including_tax( 'min', true );
+        }
+
+        if ($product->is_type('bundle')) {
+            return $product->get_bundle_price_including_tax( 'min', true );
+        }
+
+        if ($product->is_type('grouped')) {
+
+            $children = $product->get_children();
+            $price = 0;
+            foreach ($children as $key => $value) {
+                $childProduct = wc_get_product( $value );
+                if ($price !== 0) {
+                    $price = min($price, $this->getProductPrice($childProduct));
+                } else {
+                    $price = $this->getProductPrice($childProduct);
+                }
+            }
+            return $price;
+        }
+
+        if ($product->is_type( 'variable' )) {
+            return $product->get_variation_price( 'min', false );
+        }
+
+        if (function_exists('wc_get_price_including_tax')) {
+            return wc_get_price_including_tax( $product );
+        } elseif (method_exists($product, 'get_price_including_tax')) {
+            return $product->get_price_including_tax();
+        } else {
+            return $product->get_price();
+        }
+    }
+
+    public function get_plus_limits_html($namespace = '')
+    {
+        if ($this->pay_over_time_limit_min && !$this->pay_over_time_limit_max) {
+            return $this->assets["${namespace}available_on_orders_over"];
+        }
+
+        if (!$this->pay_over_time_limit_min && $this->pay_over_time_limit_max) {
+            return $this->assets["${namespace}available_on_orders_under"];
+        }
+
+        return $this->assets["${namespace}available_on_orders_between"];
+    }
+
+    public function is_plus()
+    {
+        return self::MODE_PLUS === $this->mode;
+    }
+
+    public function check_pay_over_time_limits()
+    {
+        $currency = get_woocommerce_currency();
+
+        if (
+            in_array($currency, $this->supported_currencies) &&
+            !$this->pay_over_time_limit_min &&
+            !$this->pay_over_time_limit_max
+        ) {
+
+            $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+
+            $settings["{$currency}_pay_limit_min"] = 0.06;
+            $settings["{$currency}_pay_limit_max"] = $this->pay_limits_max[$currency];
+
+            update_option('woocommerce_laybuy_settings', $settings);
+        }
+    }
+
+    public function check_cart_within_limits($gateways)
+    {
+        if (is_admin()) {
+            return $gateways;
+        }
+
+        if (WC()->cart->total < $this->pay_over_time_limit_min) {
+            if (isset($_GET['debug'])) {
+                var_dump('UNSET');exit;
+            }
+            unset($gateways[$this->id]);
+
+        }
+
+        return $gateways;
+    }
+
+    public function checkout_add_laybuy_paragraph()
+    {
+        if (!$this->is_plus()) {
+            return;
+        }
+
+        if (WC()->cart->total < $this->pay_over_time_limit_min) {
+
+            $html = str_replace(array(
+                '[MIN_PRICE]',
+                '[MAX_PRICE]',
+            ), array(
+                wc_price( $this->pay_over_time_limit_min, ['decimals' => 0]),
+                wc_price( $this->pay_over_time_limit_max, ['decimals' => 0]),
+            ), $this->get_plus_limits_html());
+
+            echo '<div class="laybuy-checkout-widget">' . do_shortcode($html) . '</div>';
+        }
+    }
+
+    public function getProductPriceBreakdownHookPriority()
+    {
+        return $this->settings['product_price_breakdown_hook_priority'];
+    }
+
+    public function change_woocommerce_currency( $currency )
+    {
+        if (!isset($this->settings['laybuy_geolocate_ip']) || $this->settings['laybuy_geolocate_ip'] === 'no') {
+            return $currency;
+        }
+
+        $country = $this->get_country();
+
+        if (empty($country)) {
+            return $currency;
+        }
+
+        $map = ['GB' => 'GBP', 'AU' => 'AUD', 'NZ' => 'NZD', 'US' => 'USD'];
+
+        if (!isset($map[$country]) || empty($this->settings['currency'])) {
+            return $currency;
+        }
+
+        $countryList = array_values($this->settings['currency']);
+
+        if (in_array($map[$country], $countryList)) {
+            $currency = $map[$country];
+        }
+
+        return $currency;
+    }
+
+    public function is_enabled() {
+        return $this->enabled == 'yes';
+    }
+
+    private function get_country()
+    {
+        $country = null;
+
+        if (isset($_COOKIE[self::COOKIE_GEOLOCATION_COUNTRY])) {
+            $country = $_COOKIE[self::COOKIE_GEOLOCATION_COUNTRY];
+        } else {
+
+            $geolocation = apply_filters('laybuy_geolocation', []);
+
+            if (empty($geolocation)) {
+                $geolocation = WC_Geolocation::geolocate_ip();
+            }
+
+            if (!empty($geolocation['country'])) {
+                $country = $geolocation['country'];
+                setcookie( self::COOKIE_GEOLOCATION_COUNTRY, $country, time()+3600*24*30);
+            }
+        }
+
+        return $country;
+    }
+
+    public function generate_support_request_btn_html( $key, $data )
+    {
+        $field_key = $this->get_field_key( $key );
+
+        ob_start();
+        ?>
+        <tr valign="top">
+            <th scope="row" class="titledesc">
+                <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
+            </th>
+            <td class="forminp">
+                <fieldset>
+                    <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
+                    <button id="laybuy_send_support_request" class="button-cancel woocommerce-save-button" type="button" value="<?php esc_attr_e( 'Send Support Report', 'woocommerce' ); ?>"><?php esc_html_e( 'Send Support Request', 'woocommerce' ); ?></button>
+                    <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
+                </fieldset>
+            </td>
+        </tr>
+        <?php
+
+        return ob_get_clean();
+    }
+}

+ 40 - 0
includes/class-wc-laybuy-helper.php

@@ -0,0 +1,40 @@
+<?php
+
+class WC_Laybuy_Helper {
+
+    const CURRENCY_CODE_NZ = 'NZD';
+    const CURRENCY_CODE_AU = 'AUD';
+    const CURRENCY_CODE_GB = 'GBP';
+    const CURRENCY_CODE_US = 'USD';
+
+    static public function get_currency_list() {
+        return [
+            self::CURRENCY_CODE_NZ => 'New Zealand Dollars',
+            self::CURRENCY_CODE_AU => 'Australian Dollars',
+            self::CURRENCY_CODE_GB => 'Great British Pounds',
+            self::CURRENCY_CODE_US => 'US Dollars'
+        ];
+    }
+
+    /**
+     * Checks if WC version is less than passed in version.
+     *
+     * @since 4.1.11
+     * @param string $version Version to check against.
+     * @return bool
+     */
+    public static function is_wc_lt( $version ) {
+        return version_compare( WC_VERSION, $version, '<' );
+    }
+
+    /**
+     * Checks if WC version is greater than passed in version.
+     *
+     * @since 4.1.11
+     * @param string $version Version to check against.
+     * @return bool
+     */
+    public static function is_wc_gt( $version ) {
+        return version_compare( WC_VERSION, $version, '>=' );
+    }
+}

+ 77 - 0
includes/class-wc-laybuy-logger.php

@@ -0,0 +1,77 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+
+/**
+ * Log all things!
+ *
+ * @since 4.0.0
+ * @version 4.0.0
+ */
+class WC_Laybuy_Logger {
+
+    public static $logger;
+
+    public static $enabled;
+
+    const WC_LOG_FILENAME = 'laybuy';
+
+    /**
+     * Utilize WC logger class
+     *
+     * @since 4.0.0
+     * @version 4.0.0
+     */
+    public static function log( $message, $start_time = null, $end_time = null ) {
+
+        if ( ! class_exists( 'WC_Logger' ) ) {
+            return;
+        }
+
+        if ( !self::$enabled ) {
+            return;
+        }
+
+        if ( apply_filters( 'wc_laybuy_logging', true, $message ) ) {
+            if ( empty( self::$logger ) ) {
+                if ( WC_Laybuy_Helper::is_wc_lt( '3.0' ) ) {
+                    self::$logger = new WC_Logger();
+                } else {
+                    self::$logger = wc_get_logger();
+                }
+            }
+
+            if ( ! is_null( $start_time ) ) {
+
+                $formatted_start_time = date_i18n( get_option( 'date_format' ) . ' g:ia', $start_time );
+                $end_time             = is_null( $end_time ) ? current_time( 'timestamp' ) : $end_time;
+                $formatted_end_time   = date_i18n( get_option( 'date_format' ) . ' g:ia', $end_time );
+                $elapsed_time         = round( abs( $end_time - $start_time ) / 60, 2 );
+
+                $log_entry  = "\n" . '====Laybuy Version: ' . WC_LAYBUY_VERSION . '====' . "\n";
+                $log_entry .= '====Start Log ' . $formatted_start_time . '====' . "\n" . $message . "\n";
+                $log_entry .= '====End Log ' . $formatted_end_time . ' (' . $elapsed_time . ')====' . "\n\n";
+
+            } else {
+
+                $log_entry  = "\n" . '====Laybuy Version: ' . WC_LAYBUY_VERSION . '====' . "\n";
+                $log_entry .= '====Start Log====' . "\n" . $message . "\n" . '====End Log====' . "\n\n";
+            }
+
+            if ( WC_Laybuy_Helper::is_wc_lt( '3.0' ) ) {
+                self::$logger->add( self::WC_LOG_FILENAME, $log_entry );
+            } else {
+                self::$logger->debug( $log_entry, array( 'source' => self::WC_LOG_FILENAME ) );
+            }
+        }
+    }
+
+    public static function info($message, $start_time = null, $end_time = null) {
+        return self::log("Info: {$message}", $start_time, $end_time);
+    }
+
+    public static function error($message, $start_time = null, $end_time = null) {
+        return self::log("Error: {$message}", $start_time, $end_time);
+    }
+}

+ 5 - 0
includes/constants.php

@@ -0,0 +1,5 @@
+<?php
+if( !defined( 'ABSPATH' ) ) exit;
+
+define( 'WC_GATEWAY_LAYBUY_ID', 'wc-laybuy');
+define( 'WC_LAYBUY_WOOCOMMERCE_LAYBUY_PLUGIN_PATH', plugin_dir_url(__FILE__));

+ 475 - 0
includes/laybuy-settings.php

@@ -0,0 +1,475 @@
+<?php
+
+if ( ! defined( 'ABSPATH' ) ) {
+    exit;
+}
+
+# Process Region-based Assets
+$assets					= 	include 'assets.php';
+$currency				=	strtolower(get_woocommerce_currency());
+
+if (!empty($assets[$currency])) {
+    $region_assets = $assets[$currency];
+} else {
+    $region_assets = $assets['nzd'];
+}
+
+$product_page_asset  = $region_assets['product_page'];
+$ctg_page_asset 	 = $region_assets['category_page'];
+$cart_page_asset 	 = $region_assets['cart_page'];
+$checkout_page_asset = $region_assets['checkout_page'];
+
+$settings = array(
+        'enabled' => array(
+            'title'   => __( 'Enable/Disable', 'woo_laybuy' ),
+            'type'    => 'checkbox',
+            'label'   => __( 'Enable Laybuy', 'woo_laybuy' ),
+            'default' => 'no'
+        ),
+        'title' => array(
+            'title'       => __( 'Title', 'woo_laybuy' ),
+            'type'        => 'text',
+            'description' => __( 'This is the title for this payment method. The customer will see this during checkout.', 'woo_laybuy' ),
+            'default'     => __( 'Laybuy', 'woo_laybuy' ),
+            'desc_tip'    => true,
+        ),
+        'environment' => array(
+            'title'       => __( 'Environment', 'woo_laybuy' ),
+            'type'        => 'select',
+            'description' => __( 'Select the sandbox environment for testing purposes only.', 'woo_laybuy' ),
+            'default'     => 'production',
+            'options'     => array(
+                'sandbox' => __( 'Sandbox', 'woo_laybuy' ),
+                'production' => __( 'Production', 'woo_laybuy' )
+            ),
+            'desc_tip'    => true,
+        ),
+        'currency' => array(
+            'title'   => __( 'Default Currency' , 'woo_laybuy' ),
+            'type'    => 'multiselect',
+            'options'     => array(
+                'NZD' => __( 'New Zealand Dollars', 'woo_laybuy' ),
+                'AUD' => __( 'Australian Dollars', 'woo_laybuy' ),
+                'GBP' => __( 'Great British Pounds', 'woo_laybuy' ),
+                'USD' => __( 'United States Dollar', 'woo_laybuy' ),
+            ),
+            'label'   => __( 'Supported currencies', 'woo_laybuy' ),
+            'class' => 'currency-select',
+            'default'     => 'NZD',
+        ),
+        'global' => array(
+            'title'   => __( 'Laybuy Global' , 'woo_laybuy' ),
+            'type'    => 'checkbox',
+            'label'   => __( 'Enable', 'woo_laybuy' ),
+            'default' => 'yes'
+        ),
+        // NZD
+        "sandbox_NZD_merchant_id" => array(
+            'title'       => __("NZD Merchant ID (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+            'label_class' => array('credentials-label'),
+        ),
+        "sandbox_NZD_api_key" => array(
+            'title'       => __("NZD API Key (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_NZD_merchant_id" => array(
+            'title'       => __("NZD Merchant ID (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_NZD_api_key" => array(
+            'title'       => __("NZD API Key (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        // AUD
+        "sandbox_AUD_merchant_id" => array(
+            'title'       => __("AUD Merchant ID (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "sandbox_AUD_api_key" => array(
+            'title'       => __("AUD API Key (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_AUD_merchant_id" => array(
+            'title'       => __("AUD Merchant ID (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_AUD_api_key" => array(
+            'title'       => __("AUD API Key (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        // GBP
+        "sandbox_GBP_merchant_id" => array(
+            'title'       => __("GBP Merchant ID (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "sandbox_GBP_api_key" => array(
+            'title'       => __("GBP API Key (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_GBP_merchant_id" => array(
+            'title'       => __("GBP Merchant ID (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_GBP_api_key" => array(
+            'title'       => __("GBP API Key (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        // USD
+        "sandbox_USD_merchant_id" => array(
+            'title'       => __("USD Merchant ID (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "sandbox_USD_api_key" => array(
+            'title'       => __("USD API Key (sandbox)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_USD_merchant_id" => array(
+            'title'       => __("USD Merchant ID (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_USD_api_key" => array(
+            'title'       => __("USD API Key (production)", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        // Global
+        "sandbox_global_merchant_id" => array(
+            'title'       => __("Global Merchant ID", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "sandbox_global_api_key" => array(
+            'title'       => __("Global API Key", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_global_merchant_id" => array(
+            'title'       => __("Global Merchant ID", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+        "production_global_api_key" => array(
+            'title'       => __("Global API Key", 'woo_laybuy'),
+            'type'        => 'text',
+            'description' => __('This will be supplied by Laybuy.com', 'woo_laybuy'),
+            'default'     => '',
+            'desc_tip'    => TRUE,
+        ),
+);
+
+if ($this->is_plus()) {
+
+    $settings['NZD_pay_limit_min'] = array(
+        'title'       => __("Limit Min [NZD]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Min limit for the payments in NZD', 'woo_laybuy'),
+        'default'     => '0.06',
+        'desc_tip'    => TRUE,
+    );
+    $settings['NZD_pay_limit_max'] = array(
+        'title'       => __("Limit Max [NZD]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Max limit for the payments in NZD', 'woo_laybuy'),
+        'default'     => '1500',
+        'desc_tip'    => TRUE,
+    );
+
+    $settings['AUD_pay_limit_min'] = array(
+        'title'       => __("Limit Min [AUD]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Min limit for the payments in AUD', 'woo_laybuy'),
+        'default'     => '0.06',
+        'desc_tip'    => TRUE,
+    );
+    $settings['AUD_pay_limit_max'] = array(
+        'title'       => __("Limit Max [AUD]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Max limit for the payments in AUD', 'woo_laybuy'),
+        'default'     => '1200',
+        'desc_tip'    => TRUE,
+    );
+
+    $settings['GBP_pay_limit_min'] = array(
+        'title'       => __("Limit Min [GBP]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Min limit for the payments in GBP', 'woo_laybuy'),
+        'default'     => '0.06',
+        'desc_tip'    => TRUE,
+    );
+    $settings['GBP_pay_limit_max'] = array(
+        'title'       => __("Limit Max [GBP]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Max limit for the payments in GBP', 'woo_laybuy'),
+        'default'     => '720',
+        'desc_tip'    => TRUE,
+    );
+
+    $settings['USD_pay_limit_min'] = array(
+        'title'       => __("Limit Min [USD]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Min limit for the payments in USD', 'woo_laybuy'),
+        'default'     => '0.06',
+        'desc_tip'    => TRUE,
+    );
+    $settings['USD_pay_limit_max'] = array(
+        'title'       => __("Limit Max [USD]", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Max limit for the payments in USD', 'woo_laybuy'),
+        'default'     => '1200',
+        'desc_tip'    => TRUE,
+    );
+}
+
+$settings['debug'] =  array(
+    'title'       => __('Debug', 'woo_laybuy'),
+    'label'       => __('Enable verbose debug logging', 'woo_laybuy'),
+    'type'        => 'checkbox',
+    'description'		=>
+        __( 'The Laybuy log is in the ', 'woo_laybuy' ) .
+        '<code>wc-logs</code>' .
+        __( ' folder, which is accessible from the ', 'woo_laybuy' ) .
+        '<a href="' . admin_url( 'admin.php?page=wc-status&tab=logs' ) . '">' .
+        __( 'WooCommerce System Status', 'woo_laybuy' ) .
+        '</a>' .
+        __( ' page.', 'woo_laybuy' ),
+    'default'     => 'no',
+);
+
+$settings['presentational_customisation_title'] = array(
+    'title'				=> __( 'Customisation', 'woo_laybuy' ),
+    'type'				=> 'title',
+    'description'		=> __( 'Please feel free to customise the presentation of the Laybuy elements below to suit the individual needs of your web store.</p><p><em>Note: Advanced customisations may require the assistance of your web development team.</em>', 'woo_laybuy' )
+);
+
+$product_types = [];
+foreach (wc_get_product_types() as $value => $label) {
+    $product_types[$value] = $label;
+}
+
+$settings['product_types'] = array(
+    'title'	 => __( 'Displayed for Product Types', 'woo_laybuy' ),
+    'type'    => 'multiselect',
+    'description'	=> __( 'Select product types for which Laybuy widget is displayed', 'woo_laybuy' ),
+    'options' => $product_types,
+    'custom_attributes' => array(
+        'size' => '4'
+    ),
+    'default'  => array_keys($product_types),
+);
+
+$settings += array(
+    'show_info_on_category_pages' => array(
+        'title'				=> __( 'Payment Info on Category Pages', 'woo_laybuy' ),
+        'label'				=> __( 'Enable', 'woo_laybuy' ),
+        'type'				=> 'checkbox',
+        'description'		=> __( 'Enable to display Laybuy elements on category pages', 'woo_laybuy' ),
+        'default'			=> 'yes'
+    ),
+    'category_pages_info_text' => array(
+        'type'				=> 'wysiwyg',
+        'default'			=> $ctg_page_asset,
+        'description'		=> __( 'Use [AMOUNT] to insert the calculated instalment amount. Use [OF_OR_FROM] to insert "from" if the product\'s price is variable, or "of" if it is static.', 'woo_laybuy' )
+    ),
+    'show_info_on_product_pages' => array(
+        'title'				=> __( 'Payment Info on Individual Product Pages', 'woo_laybuy' ),
+        'label'				=> __( 'Enable', 'woo_laybuy' ),
+        'type'				=> 'checkbox',
+        'description'		=> __( 'Enable to display Laybuy elements on individual product pages', 'woo_laybuy' ),
+        'default'			=> 'yes'
+    ),
+    'product_pages_info_text' => array(
+        'type'				=> 'wysiwyg',
+        'default'			=> $product_page_asset,
+        'description'		=> __( 'Use [AMOUNT] to insert the calculated instalment amount. Use [OF_OR_FROM] to insert "from" if the product\'s price is variable, or "of" if it is static.', 'woo_laybuy' )
+    ),
+
+    'price_breakdown_option_product_page_position' => array(
+        'title'       => __('Product Price breakdown Position', 'woo_laybuy'),
+        'type'        => 'select',
+        'description' => 'Select where on the Product page you would like the breakdown to display, see <a href="https://businessbloomer.com/woocommerce-visual-hook-guide-single-product-page/" target="_blank">here</a> for a visual guide',
+        'default'     => 'disable',
+        'options'     => array(
+            'woocommerce_single_product_summary'        => __('woocommerce_single_product_summary'    ),
+            'woocommerce_before_add_to_cart_form'       => __('woocommerce_before_add_to_cart_form'   ),
+            'woocommerce_before_variations_form'        => __('woocommerce_before_variations_form'    ),
+            'woocommerce_before_add_to_cart_button'     => __('woocommerce_before_add_to_cart_button' ),
+            'woocommerce_before_single_variation'       => __('woocommerce_before_single_variation'   ),
+            'woocommerce_single_variation'              => __('woocommerce_single_variation'          ),
+            'woocommerce_after_single_variation'        => __('woocommerce_after_single_variation'    ),
+            'woocommerce_after_add_to_cart_button'      => __('woocommerce_after_add_to_cart_button'  ),
+            'woocommerce_after_add_to_cart_form'        => __('woocommerce_after_add_to_cart_form'    ),
+            'woocommerce_product_meta_start'            => __('woocommerce_product_meta_start'        ),
+            'woocommerce_product_meta_end'              => __('woocommerce_product_meta_end'          ),
+        ),
+    ),
+
+    'product_price_breakdown_hook_priority' => [
+        'title'       => __("Product Price Breakdown Hook Priority", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Choose hook priority for the product price breakdown hook. Default is 11.', 'woo_laybuy'),
+        'default'     => 11,
+        'desc_tip'    => false,
+    ],
+
+    'checkout_page_info_text' => array(
+        'title'				=> __( 'Payment Info on Checkout Page', 'woo_laybuy' ),
+        'type'				=> 'wysiwyg',
+        'default'			=> $checkout_page_asset,
+        'description'		=> __( 'Use [AMOUNT] to insert the calculated instalment amount. In this case, the instalment amount will be calculated based on the grand total of the cart, including tax and shipping.', 'woo_laybuy' )
+    ),
+    'show_info_on_cart_page' => array(
+        'title'				=> __( 'Payment Info on Cart Page', 'woo_laybuy' ),
+        'label'				=> __( 'Enable', 'woo_laybuy' ),
+        'type'				=> 'checkbox',
+        'description'		=> __( 'Enable to display Laybuy elements on the cart page', 'woo_laybuy' ),
+        'default'			=> 'yes'
+    ),
+    'cart_page_info_text' => array(
+        'type'				=> 'textarea',
+        'default'			=> $cart_page_asset,
+        'description'		=> __( 'Use [AMOUNT] to insert the calculated instalment amount. In this case, the instalment amount will be calculated based on the grand total of the cart, including tax and shipping.', 'woo_laybuy' )
+    ),
+    'laybuy_fontsize_in_breakdowns' => array(
+        'title'       => __('Font Size Breakdowns', 'woo_laybuy'),
+        'type'        => 'select',
+        'default'     => '12px',
+        'description' => __('Select the Font Size in the breakdowns', 'woo_laybuy'),
+        'options'     => array(
+            '10px'  => __('10px', 'woo_laybuy'),
+            '12px'  => __('12px', 'woo_laybuy'),
+            '14px'  => __('14px', 'woo_laybuy'),
+            '16px'  => __('16px', 'woo_laybuy'),
+            '18px'  => __('18px', 'woo_laybuy'),
+            '20px'  => __('20px', 'woo_laybuy'),
+        )
+    ),
+
+    'laybuy_logo_theme' => array(
+        'title'       => __('Laybuy Logo Theme', 'woo_laybuy'),
+        'type'        => 'select',
+        'default'     => 'White',
+        'description' => __('Select the Laybuy logo theme in the breakdowns', 'woo_laybuy'),
+        'options'     => array(
+            'white'  => __('White', 'woo_laybuy'),
+            'dark'  => __('Dark', 'woo_laybuy'),
+        )
+    ),
+
+    'laybuy_currency_prefix_in_breakdowns' => array(
+        'title'       => __('Show Currency code in Breakdowns', 'woo_laybuy'),
+        'type'        => 'checkbox',
+        'default'     => 'yes',
+        'description' => __('Show the currency code (ie NZD) in the breakdowns', 'woo_laybuy'),
+    ),
+    'laybuy_page_enabled' => array(
+        'title'       => __('Laybuy Page', 'woo_laybuy'),
+        'label'		  => __( 'Enable', 'woo_laybuy' ),
+        'type'        => 'checkbox',
+        'default'     => 'yes',
+        'description' => __('Enable /laybuy info page', 'woo_laybuy'),
+    ),
+    'laybuy_wide_layout_setting' => array(
+        'title'       => __('Set the laybuy checkout to wide'),
+        'type'        => 'checkbox',
+        'default'     => 'no',
+        'description' => __('Use this to display the Laybuy process in a single row, instead of two rows. Best suited for wide/single column checkout themes.', 'woo_laybuy'),
+    ),
+    'laybuy_compatibility_mode' => array(
+        'title'       => __('Compatibility Mode'),
+        'type'        => 'checkbox',
+        'default'     => 'no',
+        'description' => __('Use this mode only if experiencing challenges with the display of order data within the WooCommerce admin views for Laybuy orders.', 'woo_laybuy'),
+    ),
+
+    'laybuy_geolocate_ip' => array(
+        'title'       => __('Geolocation IP'),
+        'label'		  => __( 'Enable', 'woo_laybuy' ),
+        'type'        => 'checkbox',
+        'default'     => 'no',
+        'description' => __('Enable geolocation to display Laybuy payment method only for supported countries (NZ, AU, GB).', 'woo_laybuy'),
+    ),
+
+    'laybuy_price_breakdown_out_of_stock' => array(
+        'title'       => __('Price breakdown for "out of stock" products'),
+        'label'		  => __( 'Enable', 'woo_laybuy' ),
+        'type'        => 'checkbox',
+        'default'     => 'no',
+        'description' => __('Price breakdown snippet will appear on products that are "out of stock"', 'woo_laybuy'),
+    ),
+
+    'laybuy_billing_phone_field' => array(
+        'title'       => __("Billing Phone Field Name", 'woo_laybuy'),
+        'type'        => 'text',
+        'description' => __('Override for custom checkout phone field name', 'woo_laybuy'),
+        'default'     => 'billing_phone',
+    ),
+
+    'laybuy_advance_setting' => array(
+        'title'       => __('Developer Mode (Advanced)', 'woo_laybuy'),
+        'type'        => 'checkbox',
+        'default'     => 'no',
+        'description' => __('This is only for developers, be careful with this setting. You should not need to use this.', 'woo_laybuy'),
+    ),
+    'laybuy_send_support_request' => array(
+        'title'       => __('', 'woo_laybuy'),
+        'type'        => 'support_request_btn',
+        'desc_tip'    => false,
+        'description' => __('Submit email support request to Laybuy.', 'woo_laybuy'),
+    ),
+);
+
+return apply_filters('woocommerce_laybuy_settings', $settings);

+ 41 - 0
includes/wc-functions.php

@@ -0,0 +1,41 @@
+<?php
+if( !function_exists('wp_get_timezone_string') ) {
+    /**
+     * Returns the timezone string for a site, even if it's set to a UTC offset
+     *
+     * Adapted from http://www.php.net/manual/en/function.timezone-name-from-abbr.php#89155
+     *
+     * @return string valid PHP timezone string
+     */
+    function wp_get_timezone_string() {
+
+        // if site timezone string exists, return it
+        if ( $timezone = get_option( 'timezone_string' ) )
+            return $timezone;
+
+        // get UTC offset, if it isn't set then return UTC
+        if ( 0 === ( $utc_offset = get_option( 'gmt_offset', 0 ) ) )
+            return 'UTC';
+
+        // adjust UTC offset from hours to seconds
+        $utc_offset *= 3600;
+
+        // attempt to guess the timezone string from the UTC offset
+        if ( $timezone = timezone_name_from_abbr( '', $utc_offset, 0 ) ) {
+            return $timezone;
+        }
+
+        // last try, guess timezone string manually
+        $is_dst = date( 'I' );
+
+        foreach ( timezone_abbreviations_list() as $abbr ) {
+            foreach ( $abbr as $city ) {
+                if ( $city['dst'] == $is_dst && $city['offset'] == $utc_offset )
+                    return $city['timezone_id'];
+            }
+        }
+
+        // fallback to UTC
+        return 'UTC';
+    }
+}

+ 247 - 0
laybuy-gateway-for-woocommerce.php

@@ -0,0 +1,247 @@
+<?php
+/*
+Plugin Name: Laybuy Gateway for WooCommerce
+Description: Provide Laybuy as a payment option for WooCommerce orders.
+Author: Laybuy
+Author URI: https://www.laybuy.com/
+Version: 5.3.9
+Text Domain: laybuy-gateway-for-woocommerce
+License: GPL2
+Tested up to: 6.0.2
+WC requires at least: 3.0.0
+WC tested up to: 6.8.2
+Woocommerce Laybuy is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+any later version.
+
+Woocommerce Laybuy is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Woocommerce Laybuy. If not, see https://www.laybuy.com/.
+*/
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+function woocommerce_laybuy_missing_wc_notice() {
+    echo '<div class="error"><p><strong>' . sprintf( esc_html__( 'LayBuy requires WooCommerce to be installed and active. You can download %s here.', 'woocommerce-gateway-stripe' ), '<a href="https://woocommerce.com/" target="_blank">WooCommerce</a>' ) . '</strong></p></div>';
+}
+
+add_action( 'plugins_loaded', 'woocommerce_gateway_laybuy_init' );
+
+function woocommerce_gateway_laybuy_init() {
+    if ( ! class_exists( 'WooCommerce' ) ) {
+        add_action( 'admin_notices', 'woocommerce_laybuy_missing_wc_notice' );
+        return;
+    }
+}
+
+define( 'WC_LAYBUY_VERSION', '5.3.9' );
+define('WC_LAYBUY_PLUGIN_URL', plugin_dir_url(__FILE__));
+
+
+class WC_Laybuy
+{
+    protected static $instance;
+    public static $version = WC_LAYBUY_VERSION;
+
+    private function __construct() {
+
+        add_action('parse_request', array($this, 'laybuy_plugin_version'));
+        add_filter( 'page_template', array($this, 'laybuy_page_template') );
+
+        if (!defined('LAYBUY_MAIN_PATH')) {
+            define('LAYBUY_MAIN_PATH', plugin_dir_path(__FILE__));
+        }
+
+        require_once dirname( __FILE__ ) . '/includes/constants.php';
+        require_once dirname( __FILE__ ) . '/includes/wc-functions.php';
+        require_once dirname( __FILE__ ) . '/includes/class-wc-laybuy-helper.php';
+        require_once dirname( __FILE__ ) . '/includes/class-wc-laybuy-logger.php';
+        require_once dirname( __FILE__ ) . '/includes/class-wc-gateway-laybuy.php';
+
+        require_once dirname( __FILE__ ) . '/includes/Laybuy/ApiGateway.php';
+        require_once dirname( __FILE__ ) . '/includes/Laybuy/ProcessManager.php';
+        require_once dirname( __FILE__ ) . '/includes/Laybuy/SupportRequest.php';
+
+        $gateway = WC_Payment_Gateway_Laybuy::getInstance();
+        $settings = get_option( 'woocommerce_laybuy_settings', true );
+
+        add_filter( 'woocommerce_payment_gateways', array( $this, 'add_gateways' ), 10, 1 );
+        add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array($this, 'filter_action_links'), 10, 1 );
+        add_filter( 'woocommerce_available_payment_gateways', array($gateway, 'check_cart_within_limits'), 99, 1 );
+
+        if ($gateway->is_enabled()) {
+            add_filter( 'woocommerce_currency', [$gateway, 'change_woocommerce_currency']);
+        }
+
+        add_action( 'wp_enqueue_scripts', array($this, 'init_website_assets'), 10, 0 );
+        add_action( 'admin_enqueue_scripts', array($this, 'init_admin_assets'), 10, 0 );
+        add_action( 'woocommerce_update_options_payment_gateways_laybuy', array( $gateway, 'process_admin_options' ) );
+        add_action( 'woocommerce_settings_saved', array($this, 'wc_settings_saved'));
+        add_action( 'woocommerce_review_order_before_payment', array($gateway, 'checkout_add_laybuy_paragraph'));
+
+        add_shortcode( 'laybuy_iframe_src', array($gateway, 'shortcode_laybuy_iframe_src') );
+        // add_shortcode( 'laybuy_paragraph', array($gateway, 'shortcode_laybuy_paragraph') );
+
+        if (!$gateway->is_available()) {
+            return;
+        }
+
+        // filters
+        add_filter( 'woocommerce_create_order', array($gateway, 'create_order_quote'), 10, 2 );
+        add_filter( 'woocommerce_new_order_data', array($gateway, 'filter_woocommerce_new_order_data'), 10, 1 );
+        add_filter( 'checkout_modify_description', array($gateway, 'checkout_modify_description'), 10, 2 );
+
+        if (WC_Laybuy_Helper::is_wc_gt('3.7')) {
+            add_filter('woocommerce_get_price_html', array($gateway, 'filter_woocommerce_get_price_html'), 199, 2);
+        } else {
+            add_action( 'woocommerce_after_shop_loop_item_title', array($gateway, 'print_info_for_listed_products'), 199, 0 );
+        }
+
+        // actions
+        add_action( 'woocommerce_cart_totals_after_order_total', array($gateway, 'render_schedule_on_cart_page'), 10, 0 );
+        add_action( 'template_redirect', array( $gateway, 'process_redirect_order' ) );
+
+        if (isset($gateway->settings['price_breakdown_option_product_page_position'])) {
+            add_action(
+                $gateway->settings['price_breakdown_option_product_page_position'],
+                [$gateway, 'product_price_breakdown'],
+                $gateway->getProductPriceBreakdownHookPriority()
+            );
+        }
+
+        // shortcodes
+        add_shortcode( 'laybuy_logo', array($gateway, 'shortcode_laybuy_logo') );
+        add_action( 'wp_ajax_send_laybuy_support_request', array(new Laybuy_SupportRequest(), 'send'));
+    }
+
+    /**
+     * Initialise the class and return an instance.
+     *
+     * Note:	Hooked onto the "plugins_loaded" Action.
+     *
+     * @since	2.0.0
+     * @uses	self::load_classes()
+     * @return	WC_Laybuy
+     */
+    public static function init()
+    {
+        if (!defined('WC_VERSION')) {
+            return;
+        }
+
+        if (version_compare( WC_VERSION, '3.9', '>=' )) {
+            if (!file_exists(WC()->plugin_path() .'/vendor/autoload.php')) {
+                return;
+            }
+        }
+
+        require_once dirname( __FILE__ ) . '/includes/Laybuy/Plugin/UpdateManager.php';
+
+        $laybuyCurrentPluginVersion = get_option('wc_laybuy_version');
+
+        $pluginUpdateManager = new Laybuy_Plugin_UpdateManager();
+        $pluginUpdateManager->setPluginVersion(WC_LAYBUY_VERSION)
+            ->setPluginDbVersion($laybuyCurrentPluginVersion)
+            ->update();
+
+        if (is_null(self::$instance)) {
+            self::$instance = new self;
+        }
+
+        return self::$instance;
+    }
+
+    public function laybuy_page_template($page_template) {
+
+        if ( is_page( 'laybuy' ) ) {
+            $page_template = dirname( __FILE__ ) . '/templates/laybuy-page-template.php';
+        }
+        return $page_template;
+    }
+
+    /**
+     * Add the gateways to WooCommerce.
+     *
+     * @since 1.0.0
+     * @version 4.0.0
+     */
+    public function add_gateways($methods) {
+
+        if (isset($_GET['debug'])) {
+            var_dump('add laybuy gateway');
+        }
+        $methods[] = 'WC_Payment_Gateway_LayBuy';
+
+        return $methods;
+    }
+
+    /**
+     * Note: Hooked onto the "plugin_action_links_laybuy-gateway-for-woocommerce/laybuy-gateway-for-woocommerce.php" Action.
+     *
+     * @since	2.0.0
+     * @see		self::__construct()		For hook attachment.
+     * @param	array	$links
+     * @return	array
+     */
+    public function filter_action_links($links)
+    {
+        $additional_links = array(
+            '<a href="' . admin_url( 'admin.php?page=wc-settings&tab=checkout&section=laybuy' ) . '">' . __( 'Settings', 'woo_laybuy' ) . '</a>',
+        );
+        return array_merge($additional_links, $links);
+    }
+
+    /**
+     * Note: Hooked onto the "admin_enqueue_scripts" Action.
+     *
+     * @since	2.0.0
+     * @see		self::__construct()		For hook attachment.
+     */
+    public function init_admin_assets()
+    {
+        wp_enqueue_script( 'laybuy_admin_js', plugins_url( 'assets/js/laybuy-admin.js', __FILE__ ), array(), WC_LAYBUY_VERSION );
+        wp_enqueue_style('laybuy_admin_css',  plugins_url( 'assets/css/laybuy_admin.css', __FILE__ ), array(), WC_LAYBUY_VERSION );
+    }
+
+    public function init_website_assets() {
+        wp_enqueue_style( 'laybuy_css', plugins_url( 'assets/css/laybuy.css', __FILE__ ), array(), WC_LAYBUY_VERSION );
+    }
+
+    public function laybuy_plugin_version() {
+        if($_SERVER["REQUEST_URI"] == '/laybuy_version') {
+            echo "Laybuy version: " . self::$version;
+            exit();
+        }
+    }
+
+    public function wc_settings_saved()
+    {
+        $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
+        $laybuyPage = get_page_by_path( 'laybuy' );
+
+        if ($settings['laybuy_page_enabled'] == 'yes') {
+            if (!$laybuyPage)
+                wp_insert_post( array(
+                    'post_title'    => 'How Laybuy works',
+                    'post_content'  => '',
+                    'post_status'   => 'publish',
+                    'post_author'   => 1,
+                    'post_type'     => 'page',
+                    'post_name'     => 'laybuy'
+                ));
+
+        } else if ($settings['laybuy_page_enabled'] == 'no') {
+            if ($laybuyPage)
+                wp_delete_post(intval($laybuyPage->ID), true);
+        }
+    }
+}
+
+add_action( 'plugins_loaded', array('WC_Laybuy', 'init'), 10, 0 );

+ 275 - 0
readme.txt

@@ -0,0 +1,275 @@
+=== Laybuy Payment Extension for WooCommerce  ===
+Contributors: laybuy, overdose
+Tags: woocommerce, payment-gateway
+Requires at least: 4.6
+Tested up to: 6.0.2
+Stable tag: 5.3.9
+Requires PHP: 5.6.32
+License: Apache License
+License URI: https://www.apache.org/licenses/LICENSE-2.0.html
+
+Laybuy WooCommerce Gateway Plugin
+
+== Description ==
+
+This extension allows you to integrate your WooCommerce store platform with the https://laybuy.com payment system
+
+= REQUIREMENTS =
+
+* PHP version 5.6 or greater (PHP 7.1+ is recommended)
+* MySQL version 5.5 or greater (MySQL 5.6+ is recommended)
+* WooCommerce 3.3+ / Requires WordPress 4.5+
+
+
+== Installation ==
+
+1. Use the github's download feature to download a zip of the plugin (Clone or Download -> Download ZIP) to the `/wp-content/plugins/laybuy-woocommerce` directory, or install the plugin through the WordPress plugins screen directly.
+2. Activate the plugin through the 'Plugins' screen in WordPress
+3. Browse to Admin -> Wocommerce -> Settings -> Checkout -> Laybuy, here you can set your Laybuy Merchant details and choose to display the product price breakdown. The breakdown is displayed with Woocommerce's Product actions, there is a link in the Description to show you where these will display.
+
+== Changelog ==
+
+= 5.3.9 =
+*Release Date: Thu, 8 Nov 2022*
+
+* Some minor bug fixes
+
+= 5.3.8 =
+*Release Date: Mon, 7 Nov 2022*
+
+* Remove "what's laybuy" popup to comply with legal legislation
+
+= 5.3.7 =
+*Release Date: Tue, 20 Sep 2022*
+
+* Compatible styles with some theme
+
+= 5.3.6 =
+*Release Date: Tue, 6 Sep 2022*
+
+* Compatibility with PHP 8.1 verified
+* Compatibility with WC 6.8.2 verified
+
+= 5.3.5 =
+*Release Date: Wed, 15 Dec 2021*
+
+* Redirect from Laybuy wp action
+
+= 5.3.4 =
+*Release Date: Fri, 27 Aug 2021*
+
+* Support request button added
+
+= 5.3.3 =
+*Release Date: Thu, 12 Aug 2021*
+
+* Compatibility with WordPress 5.8
+
+= 5.3.2 =
+*Release Date: Thu, 9 July 2021*
+
+* Support of legacy WooCommerce
+
+= 5.3.1 =
+*Release Date: Thu, 4 March 2021*
+
+* Missing phone number bug fix on checkout
+
+= 5.3.0 =
+*Release Date: Tue, 2 March 2021*
+
+* Compatibility with WooCommerce 5.0.0
+* Small bug fixes
+
+= 5.2.9 =
+*Release Date: Mon, 1 March 2021*
+
+* Add billing phone field in advanced settings
+* Fix bug on cart on Variable Product
+
+= 5.2.8 =
+*Release Date: Thu, 12 Jan 2021*
+
+* Change product price breakdown hook priority
+* Add price breakdown snippet on products that are "out of stock" (optional)
+
+= 5.2.7 =
+*Release Date: Thu, 3 Dec 2020*
+
+* Small bug fix for product type when the selected list is empty
+
+= 5.2.6 =
+*Release Date: Tue, 1 Dec 2020*
+
+* Add advanced product type setting in admin to control which product type to display the laybuy widget for
+
+= 5.2.5 =
+*Release Date: Wed, 25 Nov 2020*
+
+* Add support of the US dollar
+
+= 5.2.4 =
+*Release Date: Sat, 31 Oct 2020*
+
+* Disable geolocation tracking by default. Add laybuy_geolocation filter for more flexibility
+* Added white/dark theme logo selection from the admin
+* Fixed some small bugs upon activation
+
+= 5.2.3 =
+*Release Date: Tue, 24 Sep 2020*
+
+* Tested with PHP up to 7.3.22 version
+* Add currency and order amount in confirmation order request to trigger extra checks
+
+= 5.2.2 =
+*Release Date: Mon, 7 Sep 2020*
+
+= 5.2.1 =
+*Release Date: Mon, 2 Sep 2020*
+
+* Hide widget from not supported countries
+
+= 5.2.0 =
+*Release Date: Mon, 2 Sep 2020*
+
+* Fix currency issue
+
+= 5.1.12 =
+*Release Date: Mon, 31 Aug 2020*
+
+* Don't show Laybuy for non supported countries while using Country Based currency plugins.
+
+= 5.1.11 =
+*Release Date: Tue, 30 June 2020*
+
+* Add Product Price Breakdown Hook Priority
+
+= 5.1.10 =
+*Release Date: Tue, 26 May 2020*
+
+* Compatibility with Woocommerce Composite Products
+* Compatibility with Woocommerce Product Bundles
+
+= 5.1.9 =
+*Release Date: Mon, 18 May 2020*
+
+* Compatibility with WooCommerce 4.1.0
+
+= 5.1.8 =
+*Release Date: Mon, 13 March 2020*
+
+* Fix wording for some cases on cart and checkout pages
+
+= 5.1.7 =
+*Release Date: Mon, 9 March 2020*
+
+* Some bug fixes for Laybuy Plus extension
+
+= 5.1.6 =
+*Release Date: Mon, 9 March 2020*
+
+* Added support of Laybuy Plus extension
+
+= 5.1.5 =
+*Release Date: Thu, 27 Feb 2020*
+
+* Fix compatibility issue for woo plugins using woocommerce_checkout_order_processed hook
+
+= 5.1.4 =
+*Release Date: Thu, 20 Feb 2020*
+
+* Small bug fix
+
+= 5.0.6 =
+*Release Date: Mon, 14 Oct 2019*
+
+= 5.1.3 =
+*Release Date: Thu, 20 Feb 2020*
+
+* Add price calculation fallback on LayBuy print paragraph
+
+= 5.1.2 =
+*Release Date: Thu, 20 Feb 2020*
+
+* Extend logging coverage
+
+= 5.1.1 =
+*Release Date: Thu, 20 Feb 2020*
+
+* Hide LayBuy price breakdown in admin panel
+
+= 5.1.0 =
+*Release Date: Thu, 13 Feb 2020*
+
+* Added Compatibility Mode
+* Fix price breakdown for variable products
+
+= 5.0.18 =
+*Release Date: Mon, 4 Feb 2020*
+
+* Added over limit payments
+
+= 5.0.17 =
+*Release Date: Mon, 3 Feb 2020*
+
+* Small fix of a laybuy paragraph showing for expensive or out of stock products
+
+= 5.0.16 =
+*Release Date: Mon, 27 Jan 2020*
+
+* Added support of Woocommerce 3.9.0
+
+= 5.0.15 =
+*Release Date: Mon, 29 Nov 2019*
+
+* Turn on/off Laybuy info page (/laybuy) in advanced settings
+
+= 5.0.14 =
+*Release Date: Mon, 25 Nov 2019*
+
+* Minor style fixes
+
+= 5.0.13 =
+*Release Date: Tue, 19 Nov 2019*
+
+* Minor UI fix on cart page
+
+= 5.0.12 =
+*Release Date: Thu, 14 Nov 2019*
+
+* Show shipping without GST in an invoice
+
+= 5.0.11 =
+*Release Date: Mon, 12 Nov 2019*
+
+* Add usage of php version_compare function
+
+= 5.0.10 =
+*Release Date: Mon, 12 Nov 2019*
+
+* Price breakdown predefined text settings for the clients updating from the old version of the plugin
+
+= 5.0.9 =
+*Release Date: Mon, 11 Nov 2019*
+
+* Remove price breakdown table view
+
+= 5.0.8 =
+*Release Date: Thursday, 1 Nov 2019*
+
+* Fix product price breakdown displaying issue
+
+= 5.0.7 =
+*Release Date: Thursday, 21 Oct 2019*
+
+* Add extra layer of logging
+* Add support of Laybuy Global
+
+= 5.0.4 =
+*Release Date: Thursday, 24 Sep 2019*
+
+* Compatibility with WooCommerce 3.7.
+* Compatibility with WooCommerce Checkout Add-Ons
+* Compatibility with Order Delivery Date Pro for WooCommerce
+* Removed unnecessary module description in module settings
+* Fixed some minor warnings from error logs

+ 19 - 0
templates/laybuy-page-template.php

@@ -0,0 +1,19 @@
+<?php if ( ! defined( 'ABSPATH' ) ) {
+exit; // Exit if accessed directly
+}
+
+?><!DOCTYPE html>
+<html <?php language_attributes(); ?>>
+    <head>
+        <meta name="viewport" content="width=device-width" />
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+        <meta name="robots" content="noindex, nofollow" />
+        <title><?php _e( 'What is Laybuy?', 'woo_laybuy' ); ?></title>
+        <link rel="stylesheet" href="<?php echo esc_url( str_replace( array( 'http:', 'https:' ), '', WC_LAYBUY_PLUGIN_URL ) . '/assets/css/laybuy-iframe.css' ); ?>" type="text/css" />
+    </head>
+    <body>
+        <div class="laybuy-content-container">
+            <iframe src="<?php echo do_shortcode('[laybuy_iframe_src]') ?>"></iframe>
+        </div>
+    </body>
+</html>

+ 26 - 0
uninstall.php

@@ -0,0 +1,26 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+    exit; // Exit if accessed directly
+}
+
+// if uninstall not called from WordPress exit
+if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
+    exit;
+}
+
+/*
+ * Only remove ALL product and page data if WC_REMOVE_ALL_DATA constant is set to true in user's
+ * wp-config.php. This is to prevent data loss when deleting the plugin from the backend
+ * and to ensure only the site owner can perform this action.
+ */
+if ( defined( 'WC_REMOVE_ALL_DATA' ) && true === WC_REMOVE_ALL_DATA ) {
+    // Delete options.
+    delete_option( 'woocommerce_laybuy_settings' );
+
+    // temp
+    $laybuyPage = get_page_by_path( 'laybuy' );
+
+    if ($laybuyPage) {
+        wp_delete_post($laybuyPage->ID, true);
+    }
+}

Some files were not shown because too many files changed in this diff