JezK
Edit File: founder-link.php
<?php /** * Plugin Name: Founder Link * Description: Connect WooCommerce with FounderHQ system and automatically send order data * Version: 2.2.4 * Author: Muhammad Syakir Muzafar & Muhamad Iskandar * Text Domain: founder-link * Requires at least: 5.0 * Tested up to: 6.4 * Requires PHP: 7.4 * WC requires at least: 3.0 * WC tested up to: 8.5 * Requires Plugins: woocommerce */ if (!defined('ABSPATH')) { exit; } define('FOUNDER_LINK_VERSION', '2.2.4'); define('FOUNDER_LINK_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('FOUNDER_LINK_PLUGIN_URL', plugin_dir_url(__FILE__)); add_action('before_woocommerce_init', function() { if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true); } }); register_activation_hook(__FILE__, 'founder_link_activate'); function founder_link_activate() { if (!class_exists('WooCommerce')) { deactivate_plugins(plugin_basename(__FILE__)); wp_die( __('Founder Link requires WooCommerce to be installed and active.', 'founder-link'), __('Plugin Activation Error', 'founder-link'), array('back_link' => true) ); } $default_options = array( 'api_url' => 'https://app.founderhq.my/api', 'access_token' => '' ); if (!get_option('founder_link_options')) { add_option('founder_link_options', $default_options); } add_option('founder_link_connection_status', 'not_connected'); } add_action('plugins_loaded', 'founder_link_init'); function founder_link_init() { if (!class_exists('WooCommerce')) { add_action('admin_notices', 'founder_link_woocommerce_missing_notice'); return; } new FounderLink(); } function founder_link_woocommerce_missing_notice() { ?> <div class="notice notice-error"> <p><?php _e('Founder Link requires WooCommerce to be installed and active.', 'founder-link'); ?></p> </div> <?php } class FounderLink { private $version = '2.0.0'; private $plugin_name = 'founder-link'; public function __construct() { if (version_compare(PHP_VERSION, '7.4', '<')) { add_action('admin_notices', array($this, 'php_version_notice')); return; } $this->init_hooks(); } private function init_hooks() { add_action('init', array($this, 'init')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_init', array($this, 'settings_init')); add_action('admin_head', array($this, 'add_admin_styles')); add_action('wp_ajax_founder_link_save_token', array($this, 'save_access_token')); add_action('wp_ajax_founder_link_repush_order', array($this, 'repush_order')); add_action('wp_ajax_founder_link_verify_existing', array($this, 'verify_existing_orders')); add_action('wp_ajax_founder_link_bulk_push_with_check', array($this, 'bulk_push_with_duplicate_check')); add_action('wp_ajax_founder_link_diagnostic', array($this, 'run_diagnostic')); add_action('rest_api_init', array($this, 'register_rest_endpoints')); add_action('woocommerce_payment_complete', array($this, 'send_order_to_system'), 20, 1); add_action('woocommerce_order_status_processing', array($this, 'handle_cod_processing'), 20, 1); add_action('woocommerce_admin_order_data_after_order_details', array($this, 'add_repush_button_to_order')); add_action('manage_shop_order_posts_custom_column', array($this, 'add_repush_column_content'), 10, 2); add_filter('manage_edit-shop_order_columns', array($this, 'add_repush_column_header')); } public function handle_cod_processing($order_id) { $order = wc_get_order($order_id); if (!$order) { return; } $payment_method = $order->get_payment_method(); if ($payment_method === 'cod') { $sync_status = $order->get_meta('_founder_link_sync_status'); if ($sync_status !== 'success') { $this->send_order_to_system($order_id); } } } public function register_rest_endpoints() { register_rest_route('founder-link/v1', '/update-order-status', array( 'methods' => 'POST', 'callback' => array($this, 'handle_order_status_update'), 'permission_callback' => array($this, 'verify_api_permission'), 'args' => array( 'order_id' => array( 'required' => true, 'type' => 'integer', 'description' => 'WooCommerce Order ID' ), 'hq_order_id' => array( 'required' => false, 'type' => 'string', 'description' => 'FounderHQ Order ID' ), 'status' => array( 'required' => true, 'type' => 'string', 'enum' => array('pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed'), 'description' => 'New order status' ), 'note' => array( 'required' => false, 'type' => 'string', 'description' => 'Order note' ), 'tracking_number' => array( 'required' => false, 'type' => 'string', 'description' => 'Tracking number for shipping' ), 'notify_customer' => array( 'required' => false, 'type' => 'boolean', 'default' => true, 'description' => 'Send email notification to customer' ) ) )); register_rest_route('founder-link/v1', '/orders/(?P<hq_order_id>[a-zA-Z0-9-_]+)', array( 'methods' => 'GET', 'callback' => array($this, 'get_order_by_hq_id'), 'permission_callback' => array($this, 'verify_api_permission'), 'args' => array( 'hq_order_id' => array( 'required' => true, 'type' => 'string', 'description' => 'FounderHQ Order ID' ) ) )); register_rest_route('founder-link/v1', '/bulk-update-status', array( 'methods' => 'POST', 'callback' => array($this, 'handle_bulk_status_update'), 'permission_callback' => array($this, 'verify_api_permission'), 'args' => array( 'orders' => array( 'required' => true, 'type' => 'array', 'description' => 'Array of order updates' ) ) )); } public function verify_api_permission($request) { $auth_header = $request->get_header('authorization'); if (!$auth_header) { return new WP_Error('no_auth', 'Authorization header missing', array('status' => 401)); } if (!preg_match('/Bearer\s+(.*)$/i', $auth_header, $matches)) { return new WP_Error('invalid_auth', 'Invalid authorization format', array('status' => 401)); } $token = $matches[1]; $options = get_option('founder_link_options'); if (empty($options['access_token']) || $token !== $options['access_token']) { return new WP_Error('invalid_token', 'Invalid access token', array('status' => 401)); } return true; } public function handle_order_status_update($request) { $order_id = $request->get_param('order_id'); $hq_order_id = $request->get_param('hq_order_id'); $new_status = $request->get_param('status'); $note = $request->get_param('note'); $tracking_number = $request->get_param('tracking_number'); $notify_customer = $request->get_param('notify_customer'); $order = wc_get_order($order_id); if (!$order) { return new WP_Error('order_not_found', 'Order not found', array('status' => 404)); } $stored_hq_id = $order->get_meta('_founder_link_hq_order_id'); if ($hq_order_id && $stored_hq_id && $stored_hq_id !== $hq_order_id) { return new WP_Error('order_mismatch', 'FounderHQ Order ID mismatch', array('status' => 400)); } $old_status = $order->get_status(); try { $order->update_status($new_status, $note ?: 'Status updated by FounderHQ', $notify_customer); if ($hq_order_id) { $order->update_meta_data('_founder_link_hq_order_id', $hq_order_id); } if ($tracking_number) { $order->update_meta_data('_tracking_number', $tracking_number); $order->add_order_note('Tracking number: ' . $tracking_number); } $order->update_meta_data('_founder_link_last_update_from_hq', current_time('mysql')); $order->update_meta_data('_founder_link_updated_by_hq', true); $order->save(); return new WP_REST_Response(array( 'success' => true, 'message' => 'Order status updated successfully', 'order_id' => $order_id, 'old_status' => $old_status, 'new_status' => $new_status, 'order_number' => $order->get_order_number(), 'customer_notified' => $notify_customer ), 200); } catch (Exception $e) { return new WP_Error('update_failed', 'Failed to update order: ' . $e->getMessage(), array('status' => 500)); } } public function get_order_by_hq_id($request) { $hq_order_id = $request->get_param('hq_order_id'); $orders = wc_get_orders(array( 'meta_query' => array( array( 'key' => '_founder_link_hq_order_id', 'value' => $hq_order_id, 'compare' => '=' ) ), 'limit' => 1 )); if (empty($orders)) { return new WP_Error('order_not_found', 'Order not found with FounderHQ ID: ' . $hq_order_id, array('status' => 404)); } $order = $orders[0]; return new WP_REST_Response(array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'status' => $order->get_status(), 'total' => $order->get_total(), 'currency' => $order->get_currency(), 'customer_email' => $order->get_billing_email(), 'date_created' => $order->get_date_created()->format('Y-m-d H:i:s'), 'hq_order_id' => $hq_order_id, 'tracking_number' => $order->get_meta('_tracking_number') ), 200); } public function handle_bulk_status_update($request) { $orders_data = $request->get_param('orders'); $results = array(); $success_count = 0; $error_count = 0; foreach ($orders_data as $order_data) { if (!isset($order_data['order_id']) || !isset($order_data['status'])) { $results[] = array( 'order_id' => isset($order_data['order_id']) ? $order_data['order_id'] : 'unknown', 'success' => false, 'error' => 'Missing required fields: order_id or status' ); $error_count++; continue; } $order = wc_get_order($order_data['order_id']); if (!$order) { $results[] = array( 'order_id' => $order_data['order_id'], 'success' => false, 'error' => 'Order not found' ); $error_count++; continue; } try { $old_status = $order->get_status(); $order->update_status( $order_data['status'], isset($order_data['note']) ? $order_data['note'] : 'Bulk update by FounderHQ', isset($order_data['notify_customer']) ? $order_data['notify_customer'] : true ); if (isset($order_data['tracking_number'])) { $order->update_meta_data('_tracking_number', $order_data['tracking_number']); } $order->update_meta_data('_founder_link_last_update_from_hq', current_time('mysql')); $order->save(); $results[] = array( 'order_id' => $order_data['order_id'], 'success' => true, 'old_status' => $old_status, 'new_status' => $order_data['status'] ); $success_count++; } catch (Exception $e) { $results[] = array( 'order_id' => $order_data['order_id'], 'success' => false, 'error' => $e->getMessage() ); $error_count++; } } return new WP_REST_Response(array( 'success' => true, 'message' => "Processed {$success_count} successful, {$error_count} failed", 'summary' => array( 'total' => count($orders_data), 'successful' => $success_count, 'failed' => $error_count ), 'results' => $results ), 200); } public function php_version_notice() { ?> <div class="notice notice-error"> <p><?php echo sprintf(__('Founder Link requires PHP version 7.4 or higher. You are running version %s.', 'founder-link'), PHP_VERSION); ?></p> </div> <?php } public function init() { load_plugin_textdomain($this->plugin_name, false, dirname(plugin_basename(__FILE__)) . '/languages/'); } public function add_admin_styles() { $current_screen = get_current_screen(); if (!$current_screen || strpos($current_screen->id, 'founder-link') === false) { return; } ?> <style> /* FounderHQ Plugin Styles */ .founder-link-container { max-width: 800px; margin: 20px 0; } .connection-status { margin-bottom: 20px; } .connection-status .notice { padding: 12px; margin: 0 0 20px 0; border-left: 4px solid #ddd; } .connection-status .notice-success { border-left-color: #46b450; background-color: #f0f8f0; } .connection-status .notice-error { border-left-color: #dc3232; background-color: #fdf0f0; } .connection-status .notice-warning { border-left-color: #ffb900; background-color: #fff8e5; } .form-actions { margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; } .form-actions .button { margin-right: 10px; } .founder-link-info { margin-top: 30px; padding: 20px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px; } .founder-link-info h3 { margin-top: 0; color: #333; } .founder-link-info ul { list-style-type: none; padding: 0; } .founder-link-info li { padding: 8px 0; border-bottom: 1px solid #eee; } .founder-link-info li:last-child { border-bottom: none; } .founder-link-info li:before { content: "✓"; color: #46b450; font-weight: bold; margin-right: 10px; } /* FounderHQ All Orders Page Styles */ .founderhq-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .stat-card { background: white; border: 1px solid #ddd; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .stat-card.success { border-left: 4px solid #28a745; } .stat-card.failed { border-left: 4px solid #dc3545; } .stat-card.pending { border-left: 4px solid #ffc107; } .stat-card .stat-number { font-size: 28px; font-weight: bold; color: #333; margin-bottom: 5px; } .stat-card.success .stat-number { color: #28a745; } .stat-card.failed .stat-number { color: #dc3545; } .stat-card.pending .stat-number { color: #e67e22; } .stat-card .stat-label { font-size: 12px; color: #666; text-transform: uppercase; letter-spacing: 1px; } .founderhq-filters { background: white; border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin: 20px 0; } .founderhq-filters select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .founderhq-orders-table { background: white; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; margin: 20px 0; } .founderhq-orders-table table { margin: 0; border: none; background: white; border-collapse: collapse; width: 100%; table-layout: auto; /* CRITICAL: Allows content to define column width */ } .founderhq-orders-table th { background: #f8f9fa !important; padding: 15px 10px; font-weight: 600; text-align: left; border-bottom: 2px solid #dee2e6 !important; border-right: 1px solid #dee2e6 !important; white-space: nowrap; /* Prevent headers from wrapping */ } .founderhq-orders-table td { padding: 12px 10px; border-bottom: 1px solid #eee !important; border-right: 1px solid #eee !important; vertical-align: middle; } .founderhq-orders-table tbody tr:hover { background-color: #f8f9fa !important; } /* --- NEW RESPONSIVE COLUMN STYLES --- */ /* These rules replace the broken percentage-based widths */ /* Columns that should NOT wrap and will auto-size to their content */ .founderhq-orders-table .column-log-id, .founderhq-orders-table .column-wc-order, .founderhq-orders-table .column-hq-order, .founderhq-orders-table .column-wc-status, .founderhq-orders-table .column-status, .founderhq-orders-table .column-date, .founderhq-orders-table .column-actions { width: 1%; /* This encourages the column to shrink-to-fit its content */ white-space: nowrap; } /* The Description column will be flexible and wrap its text */ .founderhq-orders-table .column-description { width: 100%; /* This tells the browser to give this column all remaining space */ white-space: normal; word-break: break-word; } /* Specific alignment and styling for certain columns */ .founderhq-orders-table .column-log-id { text-align: center; font-weight: 600; background: #f9f9f9; } .founderhq-orders-table .column-status, .founderhq-orders-table .column-actions { text-align: center; } /* --- END NEW STYLES --- */ .wc-order-link { display: inline-block; background: #007cba; color: white !important; padding: 4px 8px; border-radius: 4px; text-decoration: none; font-weight: bold; font-size: 12px; } .wc-order-link:hover { background: #005a87; color: white !important; } .hq-order-id { display: inline-block; background: #ff9500; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold; font-size: 12px; } .no-hq-id { color: #999; font-style: italic; } .status-processing { background: #e1f5fe; color: #0277bd; } .status-completed { background: #e8f5e8; color: #2e7d32; } .status-pending { background: #fff3e0; color: #f57c00; } .status-cancelled { background: #ffebee; color: #c62828; } .status-on-hold { background: #fff8e1; color: #f9a825; } .status-failed { background: #ffebee; color: #c62828; } .status-refunded { background: #f3e5f5; color: #7b1fa2; } .sync-status-badge { display: inline-block; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; text-transform: uppercase; } .sync-status-badge.pushed { background: #28a745; color: white; } .sync-status-badge.failed { background: #dc3545; color: white; } .sync-status-badge.pending { background: #ffc107; color: #212529; } .description-text { font-size: 13px; color: #666; } .repush-order-action { background: #007cba; border-color: #005a87; color: white; font-size: 11px; padding: 4px 8px; border-radius: 4px; cursor: pointer; } .repush-order-action:hover { background: #005a87; border-color: #004c75; } /* Progress Bar Styles */ .progress-container { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .progress-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .progress-header h4 { margin: 0; color: #495057; font-size: 16px; font-weight: 600; } .progress-bar-container { background: #e9ecef; border-radius: 10px; height: 8px; overflow: hidden; margin-bottom: 10px; } .progress-bar { background: linear-gradient(90deg, #007cba, #005a87); height: 100%; width: 0%; transition: width 0.3s ease; border-radius: 10px; position: relative; } .progress-bar.success { background: linear-gradient(90deg, #28a745, #20c997); } .progress-bar.error { background: linear-gradient(90deg, #dc3545, #e74c3c); } .progress-info { display: flex; justify-content: space-between; margin-bottom: 5px; } .progress-text { font-weight: 500; color: #495057; } .progress-percentage { font-weight: 600; color: #007cba; } .progress-details { font-size: 13px; color: #6c757d; margin-top: 10px; line-height: 1.4; } .cancel-button { background: #dc3545 !important; border-color: #dc3545 !important; color: white !important; font-size: 12px; padding: 4px 12px; border-radius: 4px; } .cancel-button:hover { background: #c82333 !important; border-color: #bd2130 !important; } /* Mini progress bar for individual buttons */ .mini-progress-container { display: inline-block; margin-left: 8px; } .mini-progress-bar { background: linear-gradient(90deg, #007cba, #005a87); height: 100%; width: 0%; transition: width 0.3s ease; border-radius: 2px; } .mini-progress-bar.success { background: linear-gradient(90deg, #28a745, #20c997); } .mini-progress-bar.error { background: linear-gradient(90deg, #dc3545, #e74c3c); color: white; } .repush-order-action:disabled { background: #ccc; border-color: #ccc; opacity: 0.6; cursor: not-allowed; } .founderhq-pagination { display: flex; justify-content: space-between; align-items: center; margin: 20px 0; padding: 15px; background: white; border: 1px solid #ddd; border-radius: 8px; } .founderhq-pagination .page-numbers { display: inline-block; padding: 8px 12px; margin: 0 2px; text-decoration: none; border: 1px solid #ddd; border-radius: 4px; color: #007cba; } .founderhq-pagination .page-numbers:hover, .founderhq-pagination .page-numbers.current { background: #007cba; color: white; border-color: #005a87; } .pagination-info { font-size: 14px; color: #666; } .no-orders { text-align: center; padding: 40px; color: #999; font-style: italic; } /* Order sync section */ .founder-link-order-section { background: #f9f9f9; padding: 15px; margin: 15px 0; border: 1px solid #ddd; border-radius: 4px; } .founder-link-order-section h3 { margin-top: 0; color: #333; } .sync-status.success { color: #46b450; font-weight: bold; } .sync-status.failed { color: #dc3232; font-weight: bold; } .sync-status.pending { color: #ffb900; font-weight: bold; } /* Mobile responsive */ @media screen and (max-width: 768px) { .founder-link-container { margin: 10px 0; } .founderhq-stats-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; } .founderhq-orders-table { overflow-x: auto; } .founderhq-orders-table table { min-width: 800px; } /* --- FIX FOR MOBILE DESCRIPTION COLUMN --- */ .founderhq-orders-table .column-description { min-width: 200px; /* Ensures the description has enough space to be readable */ } .founderhq-pagination { flex-direction: column; gap: 10px; } .form-actions .button { display: block; margin-bottom: 10px; width: 100%; } } @media (max-width: 600px) { .founder-link-mobile-btns { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; } .founder-link-mobile-btns .button { width: 100% !important; font-size: 17px !important; padding: 14px 0 !important; border-radius: 8px !important; margin: 0 !important; } #founder-link-progress-area { margin-top: 10px !important; } } .founder-link-mobile-btns { display: flex; gap: 10px; margin-bottom: 12px; } .founder-bulk-btn, .founder-verify-btn { padding: 8px 18px !important; font-size: 15px !important; border-radius: 5px !important; font-weight: 350; box-shadow: 0 2px 6px rgba(40,167,69,0.08); transition: background 0.2s, box-shadow 0.2s; } .founder-bulk-btn { background: #28a745 !important; border-color: #28a745 !important; color: #fff !important; margin-left: 0; } .founder-verify-btn { background: #fff !important; color: #007cba !important; border: 2px solid #007cba !important; margin-left: 10px; } .founder-bulk-btn:hover { background: #218838 !important; border-color: #218838 !important; box-shadow: 0 4px 12px rgba(40,167,69,0.12); } .founder-verify-btn:hover { background: #e6f4fa !important; color: #005a87 !important; border-color: #005a87; } </style> <?php } public function add_admin_menu() { add_menu_page( 'Founder Link Settings', 'Founder Link', 'manage_options', 'founder-link', array($this, 'admin_page'), 'dashicons-admin-links', 56 ); add_submenu_page( 'founder-link', 'All Orders', 'All Orders', 'manage_options', 'founder-link-orders', array($this, 'all_orders_page') ); add_submenu_page( 'founder-link', 'Failed Orders', 'Failed Orders', 'manage_options', 'founder-link-failed', array($this, 'failed_orders_page') ); } public function settings_init() { register_setting('founder_link_settings', 'founder_link_options'); add_settings_section( 'founder_link_section', __('FounderHQ Connection Settings', 'founder-link'), array($this, 'settings_section_callback'), 'founder_link_settings' ); add_settings_field( 'api_url', __('API URL', 'founder-link'), array($this, 'api_url_render'), 'founder_link_settings', 'founder_link_section' ); add_settings_field( 'access_token', __('Access Token', 'founder-link'), array($this, 'access_token_render'), 'founder_link_settings', 'founder_link_section' ); } public function settings_section_callback() { echo __('Configure your FounderHQ connection settings below.', 'founder-link'); } public function api_url_render() { $options = get_option('founder_link_options'); $api_url = isset($options['api_url']) ? $options['api_url'] : 'https://app.founderhq.my/api'; ?> <input type="url" name="founder_link_options[api_url]" value="<?php echo esc_attr($api_url); ?>" class="regular-text" placeholder="https://app.founderhq.my/api" required /> <p class="description"><?php _e('Enter your FounderHQ API URL', 'founder-link'); ?></p> <?php } public function access_token_render() { $options = get_option('founder_link_options'); $access_token = isset($options['access_token']) ? $options['access_token'] : ''; ?> <input type="password" name="founder_link_options[access_token]" value="<?php echo esc_attr($access_token); ?>" class="regular-text" placeholder="Enter your access token" required /> <p class="description"><?php _e('Enter your FounderHQ access token', 'founder-link'); ?></p> <?php } public function admin_page() { $options = get_option('founder_link_options'); $connection_status = get_option('founder_link_connection_status', 'not_connected'); $connected_user = get_option('founder_link_connected_user', ''); ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <div class="founder-link-container"> <div class="connection-status"> <?php if ($connection_status === 'connected'): ?> <div class="notice notice-success"> <p><strong><?php _e('Connected Successfully!', 'founder-link'); ?></strong></p> <p><?php echo sprintf(__('Connected as: %s', 'founder-link'), esc_html($connected_user)); ?></p> </div> <?php elseif ($connection_status === 'failed'): ?> <div class="notice notice-error"> <p><strong><?php _e('Connection Failed!', 'founder-link'); ?></strong></p> <p><?php _e('Please check your API URL and access token.', 'founder-link'); ?></p> </div> <?php else: ?> <div class="notice notice-warning"> <p><strong><?php _e('Not Connected', 'founder-link'); ?></strong></p> <p><?php _e('Please configure your settings and test the connection.', 'founder-link'); ?></p> </div> <?php endif; ?> </div> <form method="post" action="options.php" id="founder-link-form"> <?php settings_fields('founder_link_settings'); do_settings_sections('founder_link_settings'); ?> <div class="form-actions"> <button type="button" id="test-connection" class="button button-secondary"> <?php _e('Test Connection', 'founder-link'); ?> </button> <button type="button" id="run-diagnostic" class="button button-secondary" style="margin-left: 10px;"> 🔧 <?php _e('Run Diagnostic', 'founder-link'); ?> </button> <?php submit_button(__('Save Settings', 'founder-link'), 'primary', 'submit', false); ?> </div> </form> <div class="founder-link-info"> <h3><?php _e('How it works:', 'founder-link'); ?></h3> <ul> <li><?php _e('1. Enter your FounderHQ API URL and access token', 'founder-link'); ?></li> <li><?php _e('2. Click "Test Connection" to verify your credentials', 'founder-link'); ?></li> <li><?php _e('3. Save settings once connection is successful', 'founder-link'); ?></li> <li><?php _e('4. Order data will be automatically sent to FounderHQ', 'founder-link'); ?></li> </ul> </div> <div class="founder-link-debug-info" style="margin-top: 30px; padding: 20px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 5px;"> <h3><?php _e('Debug Information', 'founder-link'); ?></h3> <p><strong><?php _e('If you experience issues with verification, please check the following:', 'founder-link'); ?></strong></p> <table class="widefat" style="margin-top: 15px;"> <tr> <td><strong><?php _e('WordPress Debug:', 'founder-link'); ?></strong></td> <td><?php echo defined('WP_DEBUG') && WP_DEBUG ? '<span style="color: green;">✓ Enabled</span>' : '<span style="color: red;">✗ Disabled</span>'; ?></td> </tr> <tr> <td><strong><?php _e('Debug Log:', 'founder-link'); ?></strong></td> <td><?php echo defined('WP_DEBUG_LOG') && WP_DEBUG_LOG ? '<span style="color: green;">✓ Enabled</span>' : '<span style="color: red;">✗ Disabled</span>'; ?></td> </tr> <tr> <td><strong><?php _e('PHP Version:', 'founder-link'); ?></strong></td> <td><?php echo PHP_VERSION; ?></td> </tr> <tr> <td><strong><?php _e('WordPress Version:', 'founder-link'); ?></strong></td> <td><?php echo get_bloginfo('version'); ?></td> </tr> <tr> <td><strong><?php _e('WooCommerce Version:', 'founder-link'); ?></strong></td> <td><?php echo WC()->version; ?></td> </tr> <tr> <td><strong><?php _e('Plugin Version:', 'founder-link'); ?></strong></td> <td><?php echo FOUNDER_LINK_VERSION; ?></td> </tr> <tr> <td><strong><?php _e('Memory Limit:', 'founder-link'); ?></strong></td> <td><?php echo ini_get('memory_limit'); ?></td> </tr> <tr> <td><strong><?php _e('Max Execution Time:', 'founder-link'); ?></strong></td> <td><?php echo ini_get('max_execution_time'); ?> seconds</td> </tr> <tr> <td><strong><?php _e('Error Log Location:', 'founder-link'); ?></strong></td> <td><?php echo WP_CONTENT_DIR . '/debug.log'; ?></td> </tr> </table> <div style="margin-top: 15px; padding: 10px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px;"> <p><strong><?php _e('To enable debugging:', 'founder-link'); ?></strong></p> <p><?php _e('Add these lines to your wp-config.php file:', 'founder-link'); ?></p> <code style="display: block; background: #f8f9fa; padding: 10px; margin: 10px 0; border-radius: 3px;"> define('WP_DEBUG', true);<br> define('WP_DEBUG_LOG', true);<br> define('WP_DEBUG_DISPLAY', false); </code> <p><?php _e('After enabling, check the debug.log file in wp-content/ for detailed error messages.', 'founder-link'); ?></p> </div> </div> </div> </div> <script> jQuery(document).ready(function($) { $('#test-connection').on('click', function() { var button = $(this); var originalText = button.text(); button.html('<span style="display: inline-block; width: 16px; height: 16px; border: 2px solid #ffffff; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; margin-right: 8px;"></span><?php _e('Testing...', 'founder-link'); ?>').prop('disabled', true); var apiUrl = $('input[name="founder_link_options[api_url]"]').val(); var accessToken = $('input[name="founder_link_options[access_token]"]').val(); if (!apiUrl || !accessToken) { alert('<?php _e('Please fill in both API URL and Access Token', 'founder-link'); ?>'); button.text(originalText).prop('disabled', false); return; } $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_save_token', api_url: apiUrl, access_token: accessToken, nonce: '<?php echo wp_create_nonce('founder_link_nonce'); ?>' }, success: function(response) { if (response.success) { alert('<?php _e('Connection successful!', 'founder-link'); ?> ' + response.data.message); location.reload(); } else { alert('<?php _e('Connection failed:', 'founder-link'); ?> ' + response.data.message); } }, error: function() { alert('<?php _e('An error occurred. Please try again.', 'founder-link'); ?>'); }, complete: function() { button.html(originalText).prop('disabled', false); } }); }); $('#run-diagnostic').on('click', function() { var button = $(this); var originalText = button.text(); button.html('<span style="display: inline-block; width: 16px; height: 16px; border: 2px solid #ffffff; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; margin-right: 8px;"></span><?php _e('Running...', 'founder-link'); ?>').prop('disabled', true); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_diagnostic', nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, success: function(response) { if (response.success) { var results = response.data.results; var message = 'Diagnostic Results:\n\n'; message += 'PHP Version: ' + results.php_version + '\n'; message += 'WordPress Version: ' + results.wp_version + '\n'; message += 'WooCommerce Version: ' + results.wc_version + '\n'; message += 'Memory Limit: ' + results.memory_limit + '\n'; message += 'Max Execution Time: ' + results.max_execution_time + 's\n'; message += 'Plugin Configured: ' + (results.plugin_configured ? 'Yes' : 'No') + '\n'; message += 'WP Debug: ' + (results.wp_debug ? 'Enabled' : 'Disabled') + '\n'; message += 'WP Debug Log: ' + (results.wp_debug_log ? 'Enabled' : 'Disabled') + '\n'; message += 'Test Orders Found: ' + results.test_orders_count + '\n'; message += 'Test Order Loaded: ' + (results.test_order_loaded ? 'Yes' : 'No'); alert(message); } else { alert('<?php _e('Diagnostic failed:', 'founder-link'); ?> ' + response.data.message); } }, error: function(xhr, status, error) { alert('<?php _e('Diagnostic error:', 'founder-link'); ?> ' + error + '\n\nStatus: ' + status + '\nResponse: ' + xhr.responseText); }, complete: function() { button.text(originalText).prop('disabled', false); } }); }); }); </script> <?php } public function all_orders_page() { $valid_statuses = array('pending', 'processing', 'on-hold', 'completed'); $all_orders = wc_get_orders(array('status' => $valid_statuses, 'limit' => -1, 'return' => 'objects')); $total_orders = 0; foreach ($all_orders as $order) { if ($order instanceof WC_Order && !($order instanceof WC_Order_Refund)) { $total_orders++; } } $pushed = 0; $failed = 0; $pending = 0; $all_pending_ids = []; foreach ($all_orders as $order) { if (!$order || !is_a($order, 'WC_Order') || ($order instanceof WC_Order_Refund)) continue; $sync_status = $order->get_meta('_founder_link_sync_status'); if ($sync_status === 'success') { $pushed++; } elseif ($sync_status === 'failed') { $failed++; } else { $pending++; $all_pending_ids[] = $order->get_id(); } } $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; $args = array( 'limit' => $per_page, 'offset' => $offset, 'orderby' => 'date', 'order' => 'DESC', 'status' => $valid_statuses ); $orders = wc_get_orders($args); $total_pages = ceil($total_orders / $per_page); ?> <div class="wrap"> <h1 style="margin-bottom: 24px; font-size: 2rem; letter-spacing: -1px; color: #222; font-weight: 700;"> <?php _e('All Orders - FounderHQ Link', 'founder-link'); ?> </h1> <div class="founderhq-stats-grid" style="margin-bottom: 24px;"> <div class="stat-card"> <div class="stat-number"><?php echo $total_orders; ?></div> <div class="stat-label"><?php _e('Total Orders', 'founder-link'); ?></div> </div> <div class="stat-card success"> <div class="stat-number"><?php echo $pushed; ?></div> <div class="stat-label"><?php _e('Pushed', 'founder-link'); ?></div> </div> <div class="stat-card failed"> <div class="stat-number"><?php echo $failed; ?></div> <div class="stat-label"><?php _e('Failed', 'founder-link'); ?></div> </div> <div class="stat-card pending"> <div class="stat-number"><?php echo $pending; ?></div> <div class="stat-label"><?php _e('Pending', 'founder-link'); ?></div> </div> </div> <div class="founder-link-mobile-btns" style="margin-bottom: 12px;"> <button type="button" id="smart-bulk-push" class="button button-primary founder-bulk-btn"> 🚀 <?php _e('Smart Bulk Push', 'founder-link'); ?> </button> <button type="button" id="verify-existing-orders" class="button button-secondary founder-verify-btn"> 🔍 <?php _e('Verify Existing Orders', 'founder-link'); ?> </button> </div> <div id="founder-link-progress-area"></div> <div class="founderhq-orders-table"> <table class="wp-list-table widefat striped"> <thead> <tr> <th class="column-log-id">Log ID</th> <th class="column-wc-order">WC Order ID</th> <th class="column-hq-order">HQ Order ID</th> <th class="column-wc-status">WC Status</th> <th class="column-description">Description</th> <th class="column-status">Status</th> <th class="column-date">Date</th> <th class="column-actions">Actions</th> </tr> </thead> <tbody> <?php $log_id = $offset + 1; $displayed_orders = 0; foreach ($orders as $order): if (!$order || !is_a($order, 'WC_Order')) { continue; } $sync_status = $order->get_meta('_founder_link_sync_status'); $last_error = $order->get_meta('_founder_link_last_error'); $last_sync = $order->get_meta('_founder_link_last_sync'); $hq_order_id = $order->get_meta('_founder_link_hq_order_id'); $displayed_orders++; $order_status = $order->get_status(); $payment_method = $order->get_payment_method(); if ($order_status === 'pending' && $payment_method !== 'cod') { $wc_status_label = __('Pending Payment', 'founder-link'); $wc_status_class = 'pending-payment'; } else { $wc_status_label = strtoupper($order_status); $wc_status_class = $order_status; } if ($sync_status === 'success') { $status_class = 'pushed'; $status_text = 'Pushed'; $description = 'SUCCESS: Submit order success'; } elseif ($sync_status === 'failed') { $status_class = 'failed'; $status_text = 'Failed'; $description = $last_error ? 'ERROR: ' . $last_error : 'ERROR: Unknown error'; } else { $status_class = 'pending'; $status_text = 'Pending'; $description = 'Waiting to be synced'; } ?> <tr> <td class="column-log-id"><?php echo $log_id++; ?></td> <td class="column-wc-order"> <a href="<?php echo admin_url('post.php?post=' . $order->get_id() . '&action=edit'); ?>" target="_blank" class="wc-order-link"> <?php echo $order->get_order_number(); ?> </a> </td> <td class="column-hq-order"> <?php if ($hq_order_id && !empty($hq_order_id)): ?> <span class="hq-order-id"><?php echo esc_html($hq_order_id); ?></span> <?php else: ?> <span class="no-hq-id">-</span> <?php if ($sync_status === 'success'): ?> <br><small style="color: #dc3232;">Missing HQ ID</small> <?php endif; ?> <?php endif; ?> </td> <td class="column-wc-status"> <span class="wc-status status-<?php echo esc_attr($wc_status_class); ?>"> <?php echo esc_html($wc_status_label); ?> </span> </td> <td class="column-description"> <span class="description-text"><?php echo esc_html($description); ?></span> </td> <td class="column-status"> <span class="sync-status-badge <?php echo $status_class; ?>"> <?php echo $status_text; ?> </span> </td> <td class="column-date"> <?php $display_date = $last_sync ? $last_sync : $order->get_date_created()->format('Y-m-d H:i:s'); echo date('D, j M Y H:i', strtotime($display_date)); ?> </td> <td class="column-actions"> <?php if ($status_class !== 'pushed'): ?> <button type="button" class="button button-small repush-order-action" data-order-id="<?php echo $order->get_id(); ?>"> 🔄 <?php echo ($sync_status === 'success') ? __('Repush', 'founder-link') : __('Push', 'founder-link'); ?> </button> <?php endif; ?> </td> </tr> <?php endforeach; ?> <?php if ($displayed_orders === 0): ?> <tr> <td colspan="8" class="no-orders"> <?php _e('No orders found.', 'founder-link'); ?> </td> </tr> <?php endif; ?> </tbody> </table> </div> <?php if ($total_pages > 1): ?> <div class="founderhq-pagination" style="display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px; margin: 20px 0 0 0; padding: 10px 15px; background: white; border: 1px solid #ddd; border-radius: 8px;"> <div class="pagination-info" style="font-size: 14px; color: #666;"> <?php $start = $offset + 1; $end = min($offset + $per_page, $total_orders); echo sprintf(__('Showing %d to %d of %d orders', 'founder-link'), $start, $end, $total_orders); ?> </div> <div class="pagination-links"> <?php $pagination_args = array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'current' => $current_page, 'total' => $total_pages, 'prev_text' => '« ' . __('Previous', 'founder-link'), 'next_text' => __('Next', 'founder-link') . ' »' ); echo paginate_links($pagination_args); ?> </div> </div> <?php endif; ?> </div> <script> jQuery(document).ready(function($) { $('.repush-order-action').on('click', function() { var button = $(this); var orderId = button.data('order-id'); var originalText = button.html(); var progressHtml = '<div style="display: inline-block; margin-left: 8px; max-width: 80px; vertical-align: middle;">' + '<div style="width: 60px; max-width: 80px; height: 4px; background: #e9ecef; border-radius: 2px; overflow: hidden; display: inline-block;">' + '<div class="mini-progress-bar" style="background: linear-gradient(90deg, #007cba, #005a87); height: 100%; width: 0%; transition: width 0.3s ease; max-width: 80px;"></div>' + '</div>' + '</div>'; button.html(originalText + progressHtml).prop('disabled', true); var progressBar = button.find('.mini-progress-bar'); var progressInterval = setInterval(function() { var currentWidth = parseInt(progressBar.css('width')) || 0; var percentage = Math.min(currentWidth + 10, 80); progressBar.css('width', percentage + '%'); }, 200); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_repush_order', order_id: orderId, nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, success: function(response) { clearInterval(progressInterval); if (response.success) { progressBar.css('width', '100%').css('background', 'linear-gradient(90deg, #28a745, #20c997)'); setTimeout(function() { location.reload(); }, 500); } else { progressBar.css('background', 'linear-gradient(90deg, #dc3545, #e74c3c)'); alert('Failed: ' + response.data.message); button.html(originalText).prop('disabled', false); } }, error: function() { clearInterval(progressInterval); progressBar.css('background', 'linear-gradient(90deg, #dc3545, #e74c3c)'); alert('An error occurred. Please try again.'); button.html(originalText).prop('disabled', false); } }); }); $('#verify-existing-orders').on('click', function() { var button = $(this); var originalText = button.html(); if (!confirm('<?php _e('This will verify ALL orders against your FounderHQ system. This may take several minutes for large stores. Continue?', 'founder-link'); ?>')) { return; } button.text('<?php _e('Verifying...', 'founder-link'); ?>').prop('disabled', true); $('#smart-bulk-push').prop('disabled', true); $('#founder-link-progress-area').empty(); var progressDiv = $('<div id="verification-progress" class="progress-container" style="display: none;">' + '<div class="progress-header">' + '<h4><?php _e('Verification Progress', 'founder-link'); ?></h4>' + '<button type="button" id="cancel-verification" class="cancel-button"><?php _e('Cancel', 'founder-link'); ?></button>' + '</div>' + '<div class="progress-info">' + '<span class="progress-text"><?php _e('Starting verification...', 'founder-link'); ?></span>' + '<span class="progress-percentage">0%</span>' + '</div>' + '<div class="progress-bar-container">' + '<div class="progress-bar"></div>' + '</div>' + '<div class="progress-details"></div>' + '</div>'); $('#founder-link-progress-area').append(progressDiv); progressDiv.show(); var progressBar = progressDiv.find('.progress-bar'); var progressText = progressDiv.find('.progress-text'); var progressPercentage = progressDiv.find('.progress-percentage'); var progressDetails = progressDiv.find('.progress-details'); var progressInterval = setInterval(function() { var currentWidth = parseInt(progressBar.css('width')) || 0; var containerWidth = progressBar.parent().width(); var percentage = Math.min((currentWidth / containerWidth) * 100 + 5, 90); progressBar.css('width', percentage + '%'); progressPercentage.text(Math.round(percentage) + '%'); }, 1000); var ajaxRequest = $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_verify_existing', nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, timeout: 300000, success: function(response) { clearInterval(progressInterval); progressBar.css('width', '100%'); progressPercentage.text('100%'); if (response.success) { progressText.html('<strong style="color: #28a745;"><?php _e('Success!', 'founder-link'); ?></strong>'); progressDetails.html(response.data.message); progressBar.addClass('success'); setTimeout(function() { location.reload(); }, 2000); } else { progressText.html('<strong style="color: #dc3545;"><?php _e('Error:', 'founder-link'); ?></strong>'); progressDetails.html(response.data.message); progressBar.addClass('error'); button.html(originalText).prop('disabled', false); } }, error: function(xhr, status, error) { clearInterval(progressInterval); var errorMessage = '<?php _e('An error occurred. Please check logs.', 'founder-link'); ?>'; if (status === 'timeout') { errorMessage = '<?php _e('Verification timed out. Please try again or contact support.', 'founder-link'); ?>'; } progressText.html('<strong style="color: #dc3545;"><?php _e('Error:', 'founder-link'); ?></strong>'); progressDetails.html(errorMessage); progressBar.addClass('error'); button.html(originalText).prop('disabled', false); } }); $('#cancel-verification').on('click', function() { if (confirm('<?php _e('Are you sure you want to cancel?', 'founder-link'); ?>')) { clearInterval(progressInterval); ajaxRequest.abort(); progressDiv.remove(); button.html(originalText).prop('disabled', false); } }); }); $('#smart-bulk-push').on('click', function() { var button = $(this); var originalText = button.html(); if (!confirm('<?php _e('This will check for existing orders and only push new ones. Continue?', 'founder-link'); ?>')) { return; } button.text('<?php _e('Processing...', 'founder-link'); ?>').prop('disabled', true); $('#verify-existing-orders').prop('disabled', true); $('#founder-link-progress-area').empty(); var progressDiv = $('<div id="smart-push-progress" class="progress-container" style="display: none;">' + '<div class="progress-header">' + '<h4><?php _e('Smart Bulk Push Progress', 'founder-link'); ?></h4>' + '<button type="button" id="cancel-smart-push" class="cancel-button"><?php _e('Cancel', 'founder-link'); ?></button>' + '</div>' + '<div class="progress-info">' + '<span class="progress-text"><?php _e('Checking for existing orders...', 'founder-link'); ?></span>' + '<span class="progress-percentage">0%</span>' + '</div>' + '<div class="progress-bar-container">' + '<div class="progress-bar"></div>' + '</div>' + '<div class="progress-details"></div>' + '</div>'); $('#founder-link-progress-area').append(progressDiv); progressDiv.show(); var progressBar = progressDiv.find('.progress-bar'); var progressText = progressDiv.find('.progress-text'); var progressPercentage = progressDiv.find('.progress-percentage'); var progressDetails = progressDiv.find('.progress-details'); var progressInterval = setInterval(function() { var currentWidth = parseInt(progressBar.css('width')) || 0; var containerWidth = progressBar.parent().width(); var percentage = Math.min((currentWidth / containerWidth) * 100 + 4, 85); progressBar.css('width', percentage + '%'); progressPercentage.text(Math.round(percentage) + '%'); }, 800); var ajaxRequest = $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_bulk_push_with_check', nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, timeout: 300000, success: function(response) { clearInterval(progressInterval); progressBar.css('width', '100%'); progressPercentage.text('100%'); if (response.success) { progressText.html('<strong style="color: #28a745;"><?php _e('Success!', 'founder-link'); ?></strong>'); progressDetails.html(response.data.message); progressBar.addClass('success'); setTimeout(function() { location.reload(); }, 3000); } else { progressText.html('<strong style="color: #dc3545;"><?php _e('Error:', 'founder-link'); ?></strong>'); progressDetails.html(response.data.message); progressBar.addClass('error'); button.html(originalText).prop('disabled', false); } }, error: function(xhr, status, error) { clearInterval(progressInterval); var errorMessage = '<?php _e('An error occurred. Please check logs.', 'founder-link'); ?>'; if (status === 'timeout') { errorMessage = '<?php _e('Process timed out. Please try again.', 'founder-link'); ?>'; } progressText.html('<strong style="color: #dc3545;"><?php _e('Error:', 'founder-link'); ?></strong>'); progressDetails.html(errorMessage); progressBar.addClass('error'); button.html(originalText).prop('disabled', false); } }); $('#cancel-smart-push').on('click', function() { if (confirm('<?php _e('Are you sure you want to cancel?', 'founder-link'); ?>')) { clearInterval(progressInterval); ajaxRequest.abort(); progressDiv.remove(); button.html(originalText).prop('disabled', false); } }); }); }); </script> <?php } public function failed_orders_page() { ?> <div class="wrap"> <h1><?php _e('Failed Orders - FounderHQ', 'founder-link'); ?></h1> <?php $args = array('status' => 'any', 'return' => 'ids'); $all_order_ids = wc_get_orders($args); $failed_orders = array(); foreach ($all_order_ids as $order_id) { $order = wc_get_order($order_id); if (!$order) continue; $sync_status = $order->get_meta('_founder_link_sync_status'); if ($sync_status === 'failed') { $failed_orders[] = $order; } } ?> <?php if (empty($failed_orders)): ?> <div class="notice notice-success"> <p><strong><?php _e('Excellent!', 'founder-link'); ?></strong> <?php _e('No failed orders found. All orders are synced successfully.', 'founder-link'); ?></p> </div> <?php else: ?> <div class="notice notice-warning"> <p><strong><?php _e('Attention Required', 'founder-link'); ?></strong> <?php echo sprintf(__('Found %d orders that failed to sync.', 'founder-link'), count($failed_orders)); ?></p> </div> <div style="margin: 20px 0;"> <button type="button" id="repush-all-failed" class="button button-primary"> <?php _e('🔄 Repush All Failed Orders', 'founder-link'); ?> </button> </div> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th><?php _e('Order ID', 'founder-link'); ?></th> <th><?php _e('Date', 'founder-link'); ?></th> <th><?php _e('Customer', 'founder-link'); ?></th> <th><?php _e('Total', 'founder-link'); ?></th> <th><?php _e('Error', 'founder-link'); ?></th> <th><?php _e('Action', 'founder-link'); ?></th> </tr> </thead> <tbody> <?php foreach ($failed_orders as $order): ?> <tr> <td> <a href="<?php echo admin_url('post.php?post=' . $order->get_id() . '&action=edit'); ?>" target="_blank"> <strong>#<?php echo $order->get_order_number(); ?></strong> </a> </td> <td><?php echo $order->get_date_created()->format('M j, Y H:i'); ?></td> <td> <?php echo esc_html($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()); ?><br> <small><?php echo esc_html($order->get_billing_email()); ?></small> </td> <td><strong><?php echo $order->get_formatted_order_total(); ?></strong></td> <td> <small style="color: #dc3232;"> <?php echo esc_html($order->get_meta('_founder_link_last_error') ?: 'Unknown error'); ?> </small> </td> <td> <button type="button" class="button button-small repush-single" data-order-id="<?php echo $order->get_id(); ?>"> <?php _e('Repush', 'founder-link'); ?> </button> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <script> jQuery(document).ready(function($) { $('.repush-single').on('click', function() { var button = $(this); var orderId = button.data('order-id'); var originalText = button.text(); button.text('Pushing...').prop('disabled', true); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_repush_order', order_id: orderId, nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, success: function(response) { if (response.success) { button.closest('tr').fadeOut(); } else { alert('Failed: ' + response.data.message); button.text(originalText).prop('disabled', false); } }, error: function() { alert('An error occurred.'); button.text(originalText).prop('disabled', false); } }); }); $('#repush-all-failed').on('click', function() { if (!confirm('Are you sure you want to repush all failed orders?')) { return; } var button = $(this); var originalText = button.text(); button.text('Repushing...').prop('disabled', true); $('.repush-single').each(function(index) { var orderButton = $(this); var orderId = orderButton.data('order-id'); setTimeout(function() { $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_repush_order', order_id: orderId, nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, success: function(response) { if (response.success) { orderButton.closest('tr').fadeOut(); } } }); }, index * 1000); }); setTimeout(function() { button.text(originalText).prop('disabled', false); location.reload(); }, $('.repush-single').length * 1000 + 2000); }); }); </script> <?php } private function get_orders_statistics() { $args = array('status' => 'any', 'return' => 'ids'); $all_order_ids = wc_get_orders($args); $stats = array( 'total' => count($all_order_ids), 'pushed' => 0, 'failed' => 0, 'pending' => 0 ); foreach ($all_order_ids as $order_id) { $order = wc_get_order($order_id); if (!$order) continue; $sync_status = $order->get_meta('_founder_link_sync_status'); if ($sync_status === 'success') { $stats['pushed']++; } elseif ($sync_status === 'failed') { $stats['failed']++; } else { $stats['pending']++; } } return $stats; } public function save_access_token() { check_ajax_referer('founder_link_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } $api_url = sanitize_url($_POST['api_url']); $access_token = sanitize_text_field($_POST['access_token']); $response = $this->test_connection($api_url, $access_token); if ($response['success']) { $options = array( 'api_url' => $api_url, 'access_token' => $access_token ); update_option('founder_link_options', $options); update_option('founder_link_connection_status', 'connected'); update_option('founder_link_connected_user', $response['user']); wp_send_json_success(array('message' => sprintf(__('Connected as %s', 'founder-link'), $response['user']))); } else { update_option('founder_link_connection_status', 'failed'); wp_send_json_error(array('message' => $response['message'])); } } private function test_connection($api_url, $access_token) { $test_url = rtrim($api_url, '/') . '/validate-token'; $response = wp_remote_post($test_url, array( 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Basic ' . $access_token ), 'body' => json_encode(array( 'action' => 'verify_token' )), 'timeout' => 30 )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (wp_remote_retrieve_response_code($response) === 200 && isset($data['valid']) && $data['valid']) { return array( 'success' => true, 'user' => isset($data['user_name']) ? $data['user_name'] : 'Unknown User' ); } else { return array( 'success' => false, 'message' => isset($data['message']) ? $data['message'] : __('Invalid response from server', 'founder-link') ); } } public function add_repush_button_to_order($order) { $order_id = $order->get_id(); $sync_status = $order->get_meta('_founder_link_sync_status'); $last_error = $order->get_meta('_founder_link_last_error'); $last_sync = $order->get_meta('_founder_link_last_sync'); ?> <div class="founder-link-order-section"> <h3><?php _e('FounderHQ Link Status', 'founder-link'); ?></h3> <div class="founder-link-sync-info"> <?php if ($sync_status === 'success'): ?> <p><span class="sync-status success">✓ <?php _e('Successfully synced', 'founder-link'); ?></span></p> <?php if ($last_sync): ?> <p><small><?php echo sprintf(__('Last synced: %s', 'founder-link'), $last_sync); ?></small></p> <?php endif; ?> <?php elseif ($sync_status === 'failed'): ?> <p><span class="sync-status failed">✗ <?php _e('Sync failed', 'founder-link'); ?></span></p> <?php if ($last_error): ?> <p><small><?php echo sprintf(__('Error: %s', 'founder-link'), esc_html($last_error)); ?></small></p> <?php endif; ?> <button type="button" class="button button-primary repush-order" data-order-id="<?php echo $order_id; ?>"> <?php _e('Repush to FounderHQ', 'founder-link'); ?> </button> <?php else: ?> <p><span class="sync-status pending">⏳ <?php _e('Not synced yet', 'founder-link'); ?></span></p> <button type="button" class="button button-primary repush-order" data-order-id="<?php echo $order_id; ?>"> <?php _e('Push to FounderHQ', 'founder-link'); ?> </button> <?php endif; ?> </div> </div> <script> jQuery(document).ready(function($) { $('.repush-order').on('click', function() { var button = $(this); var orderId = button.data('order-id'); var originalText = button.text(); button.text('<?php _e('Pushing...', 'founder-link'); ?>').prop('disabled', true); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_repush_order', order_id: orderId, nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, success: function(response) { if (response.success) { alert('<?php _e('Order pushed successfully!', 'founder-link'); ?>'); location.reload(); } else { alert('<?php _e('Failed to push order:', 'founder-link'); ?> ' + response.data.message); } }, error: function() { alert('<?php _e('An error occurred. Please try again.', 'founder-link'); ?>'); }, complete: function() { button.text(originalText).prop('disabled', false); } }); }); }); </script> <?php } public function add_repush_column_header($columns) { $columns['founder_link_sync'] = __('FounderHQ', 'founder-link'); return $columns; } public function add_repush_column_content($column, $post_id) { if ($column === 'founder_link_sync') { $order = wc_get_order($post_id); if (!$order) { return; } $sync_status = $order->get_meta('_founder_link_sync_status'); switch ($sync_status) { case 'success': echo '<span style="color: #46b450;">✓ Synced</span>'; break; case 'failed': echo '<span style="color: #dc3232;">✗ Failed</span>'; echo '<br><button type="button" class="button button-small repush-order-list" data-order-id="' . $post_id . '">Repush</button>'; break; default: echo '<span style="color: #ffb900;">⏳ Pending</span>'; echo '<br><button type="button" class="button button-small repush-order-list" data-order-id="' . $post_id . '">Push</button>'; } } } public function send_order_to_system($order_id) { $processed_key = '_founder_link_processed_' . $order_id; if (get_transient($processed_key)) { return; } set_transient($processed_key, true, HOUR_IN_SECONDS); $options = get_option('founder_link_options'); if (empty($options['api_url']) || empty($options['access_token'])) { $order = wc_get_order($order_id); if ($order) { $order->update_meta_data('_founder_link_sync_status', 'failed'); $order->update_meta_data('_founder_link_last_error', 'Plugin not configured'); $order->save(); } return; } $order = wc_get_order($order_id); if (!$order) { return; } $existing_status = $order->get_meta('_founder_link_sync_status'); if ($existing_status === 'success') { return; } $order_data = $this->prepare_order_data($order); $result = $this->send_data_to_system($order_data, 'new_order', true); if ($result['success']) { $order->update_meta_data('_founder_link_sync_status', 'success'); $order->update_meta_data('_founder_link_last_sync', current_time('mysql')); $order->delete_meta_data('_founder_link_last_error'); $hq_order_id = null; $data = $result['data'] ?? null; if ($data) { if (isset($data['hq_order_id']) && !empty($data['hq_order_id'])) { $hq_order_id = $data['hq_order_id']; } elseif (isset($data['data']) && is_array($data['data']) && isset($data['data']['hq_order_id']) && !empty($data['data']['hq_order_id'])) { $hq_order_id = $data['data']['hq_order_id']; } elseif (isset($data['order_id']) && !empty($data['order_id'])) { $hq_order_id = $data['order_id']; } elseif (isset($data['id']) && !empty($data['id'])) { $hq_order_id = $data['id']; } } if ($hq_order_id) { $order->update_meta_data('_founder_link_hq_order_id', $hq_order_id); } $order->save(); } else { $order->update_meta_data('_founder_link_sync_status', 'failed'); $order->update_meta_data('_founder_link_last_error', $result['message']); $order->save(); } } public function send_order_update_to_system($order_id, $old_status, $new_status) { if ($old_status === 'auto-draft' || $old_status === '' || empty($old_status)) { return; } $options = get_option('founder_link_options'); if (empty($options['api_url']) || empty($options['access_token'])) { return; } $order = wc_get_order($order_id); if (!$order) { return; } $sync_status = $order->get_meta('_founder_link_sync_status'); if ($sync_status !== 'success') { return; } $order_data = $this->prepare_order_data($order); $order_data['old_status'] = $old_status; $order_data['new_status'] = $new_status; $result = $this->send_data_to_system($order_data, 'order_status_changed', true); if ($result['success']) { $order->update_meta_data('_founder_link_last_sync', current_time('mysql')); $order->save(); } } public function repush_order() { check_ajax_referer('founder_link_repush_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } $order_id = intval($_POST['order_id']); $order = wc_get_order($order_id); if (!$order) { wp_send_json_error(array('message' => __('Order not found', 'founder-link'))); return; } $options = get_option('founder_link_options'); if (empty($options['api_url']) || empty($options['access_token'])) { wp_send_json_error(array('message' => __('Plugin not configured. Please check settings.', 'founder-link'))); return; } if ($order->get_status() === 'pending' && $order->get_payment_method() !== 'cod') { wp_send_json_error(array('message' => __('Order is pending payment (non-COD) and cannot be pushed until payment is complete.', 'founder-link'))); return; } $verify_result = $this->verify_orders_with_laravel([$order_id], $options); if ($verify_result && $verify_result['success'] && isset($verify_result['data']['results'][0]) && $verify_result['data']['results'][0]['exists']) { $result = $verify_result['data']['results'][0]; $order->update_meta_data('_founder_link_sync_status', 'success'); $order->update_meta_data('_founder_link_hq_order_id', $result['hq_order_id']); $order->update_meta_data('_founder_link_last_sync', $result['created_at']); $order->delete_meta_data('_founder_link_last_error'); $order->save(); wp_send_json_success(array('message' => __('Order already exists in FounderHQ. Marked as pushed.', 'founder-link'))); return; } $order_data = $this->prepare_order_data($order); $result = $this->send_data_to_system($order_data, 'repush_order', true); if ($result['success']) { $order->update_meta_data('_founder_link_sync_status', 'success'); $order->update_meta_data('_founder_link_last_sync', current_time('mysql')); $order->delete_meta_data('_founder_link_last_error'); $hq_order_id = null; $data = $result['data'] ?? null; if ($data) { if (isset($data['hq_order_id']) && !empty($data['hq_order_id'])) { $hq_order_id = $data['hq_order_id']; } elseif (isset($data['data']) && is_array($data['data']) && isset($data['data']['hq_order_id']) && !empty($data['data']['hq_order_id'])) { $hq_order_id = $data['data']['hq_order_id']; } elseif (isset($data['order_id']) && !empty($data['order_id'])) { $hq_order_id = $data['order_id']; } elseif (isset($data['id']) && !empty($data['id'])) { $hq_order_id = $data['id']; } } if ($hq_order_id) { $order->update_meta_data('_founder_link_hq_order_id', $hq_order_id); } $order->save(); wp_send_json_success(array('message' => __('Order pushed successfully', 'founder-link'))); } else { $order->update_meta_data('_founder_link_sync_status', 'failed'); $order->update_meta_data('_founder_link_last_error', $result['message']); $order->save(); wp_send_json_error(array('message' => $result['message'])); } } public function run_diagnostic() { check_ajax_referer('founder_link_repush_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } $diagnostic_results = array(); try { $diagnostic_results['php_version'] = PHP_VERSION; $diagnostic_results['memory_limit'] = ini_get('memory_limit'); $diagnostic_results['max_execution_time'] = ini_get('max_execution_time'); $diagnostic_results['wp_version'] = get_bloginfo('version'); $diagnostic_results['site_url'] = get_site_url(); if (class_exists('WooCommerce')) { $diagnostic_results['wc_version'] = WC()->version; $test_orders = wc_get_orders(array( 'status' => array('pending', 'processing', 'on-hold', 'completed'), 'limit' => 5, 'return' => 'ids' )); $diagnostic_results['test_orders_count'] = count($test_orders); $diagnostic_results['test_orders_ids'] = $test_orders; if (!empty($test_orders)) { $test_order = wc_get_order($test_orders[0]); if ($test_order) { $diagnostic_results['test_order_loaded'] = true; $diagnostic_results['test_order_id'] = $test_order->get_id(); $diagnostic_results['test_order_email'] = $test_order->get_billing_email(); } else { $diagnostic_results['test_order_loaded'] = false; } } } else { $diagnostic_results['wc_version'] = 'WooCommerce not active'; } $options = get_option('founder_link_options'); $diagnostic_results['plugin_configured'] = !empty($options['api_url']) && !empty($options['access_token']); $diagnostic_results['api_url_set'] = !empty($options['api_url']); $diagnostic_results['access_token_set'] = !empty($options['access_token']); $diagnostic_results['wp_debug'] = defined('WP_DEBUG') && WP_DEBUG; $diagnostic_results['wp_debug_log'] = defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; wp_send_json_success(array( 'message' => 'Diagnostic completed successfully', 'results' => $diagnostic_results )); } catch (Exception $e) { wp_send_json_error(array( 'message' => 'Diagnostic failed: ' . $e->getMessage(), 'error_details' => array( 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ) )); } } public function verify_existing_orders() { if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); } try { check_ajax_referer('founder_link_repush_nonce', 'nonce'); } catch (Exception $e) { wp_send_json_error(array('message' => 'Security check failed: ' . $e->getMessage())); return; } if (!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } $old_max_execution_time = ini_get('max_execution_time'); $old_memory_limit = ini_get('memory_limit'); @ini_set('max_execution_time', 300); @ini_set('memory_limit', '512M'); $options = get_option('founder_link_options'); if (empty($options['api_url']) || empty($options['access_token'])) { wp_send_json_error(array('message' => __('Plugin not configured. Please check settings.', 'founder-link'))); return; } $args = array( 'status' => array('pending', 'processing', 'on-hold', 'completed'), 'limit' => -1, 'return' => 'objects', 'orderby' => 'date', 'order' => 'DESC' ); $all_orders = wc_get_orders($args); $all_order_ids = array(); foreach ($all_orders as $order) { if ($order instanceof WC_Order && !($order instanceof WC_Order_Refund)) { $all_order_ids[] = $order->get_id(); } } if (empty($all_order_ids)) { wp_send_json_error(array('message' => __('No orders found to process.', 'founder-link'))); return; } try { $test_order = wc_get_order($all_order_ids[0]); if (!$test_order) { wp_send_json_error(array('message' => 'Cannot load WooCommerce orders. Please check WooCommerce installation.')); return; } } catch (Exception $e) { wp_send_json_error(array('message' => 'Error loading WooCommerce orders: ' . $e->getMessage())); return; } $batch_size = 5; $total_orders = count($all_order_ids); $verified_count = 0; $failed_count = 0; $results = array(); for ($i = 0; $i < $total_orders; $i += $batch_size) { $batch_order_ids = array_slice($all_order_ids, $i, $batch_size); $batch_number = ($i / $batch_size + 1); if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); } try { $verification_result = $this->verify_orders_with_laravel($batch_order_ids, $options); } catch (Exception $e) { $failed_count += count($batch_order_ids); continue; } if ($verification_result['success']) { $batch_results = $verification_result['data']['results']; try { foreach ($batch_results as $result) { try { if ($result['exists']) { $order = wc_get_order($result['wc_order_id']); if ($order) { $order->update_meta_data('_founder_link_sync_status', 'success'); $order->update_meta_data('_founder_link_hq_order_id', $result['hq_order_id']); $order->update_meta_data('_founder_link_last_sync', $result['created_at']); $order->delete_meta_data('_founder_link_last_error'); if (isset($result['match_type'])) { $order->update_meta_data('_founder_link_match_type', $result['match_type']); } $order->save(); $verified_count++; } } else { $failed_count++; $order = wc_get_order($result['wc_order_id']); if ($order) { $order->delete_meta_data('_founder_link_hq_order_id'); $order->update_meta_data('_founder_link_sync_status', 'pending'); $order->delete_meta_data('_founder_link_last_error'); $order->save(); } } } catch (Exception $e) { $failed_count++; } } } catch (Exception $e) { $failed_count += count($batch_order_ids); } } else { $failed_count += count($batch_order_ids); } if ($i + $batch_size < $total_orders) { usleep(100000); } } @ini_set('max_execution_time', $old_max_execution_time); @ini_set('memory_limit', $old_memory_limit); try { wp_send_json_success(array( 'message' => sprintf(__('Verification complete! %d orders verified, %d not found in system.', 'founder-link'), $verified_count, $failed_count), 'verified_count' => $verified_count, 'failed_count' => $failed_count, 'total_processed' => $total_orders )); } catch (Exception $e) { wp_die('Verification completed with errors. Check debug log for details.'); } } public function bulk_push_with_duplicate_check() { check_ajax_referer('founder_link_repush_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } @ini_set('max_execution_time', 300); @ini_set('memory_limit', '512M'); $options = get_option('founder_link_options'); if (empty($options['api_url']) || empty($options['access_token'])) { wp_send_json_error(array('message' => __('Plugin not configured. Please check settings.', 'founder-link'))); return; } $args = array( 'status' => array('pending', 'processing', 'on-hold', 'completed'), 'limit' => -1, 'return' => 'ids' ); $all_order_ids = wc_get_orders($args); if (empty($all_order_ids)) { wp_send_json_error(array('message' => __('No orders found to process.', 'founder-link'))); return; } $verification_result = $this->verify_orders_with_laravel($all_order_ids, $options); if (!$verification_result['success']) { wp_send_json_error(array('message' => 'Failed to check existing orders: ' . $verification_result['message'])); return; } $existing_orders = array(); $new_orders = array(); foreach ($verification_result['data']['results'] as $result) { if ($result['exists']) { $existing_orders[] = $result['wc_order_id']; $order = wc_get_order($result['wc_order_id']); if ($order) { $order->update_meta_data('_founder_link_sync_status', 'success'); $order->update_meta_data('_founder_link_hq_order_id', $result['hq_order_id']); $order->update_meta_data('_founder_link_last_sync', $result['created_at']); $order->delete_meta_data('_founder_link_last_error'); if (isset($result['match_type'])) { $order->update_meta_data('_founder_link_match_type', $result['match_type']); } $order->save(); } } else { $new_orders[] = $result['wc_order_id']; $order = wc_get_order($result['wc_order_id']); if ($order) { $order->delete_meta_data('_founder_link_hq_order_id'); $order->update_meta_data('_founder_link_sync_status', 'pending'); $order->delete_meta_data('_founder_link_last_error'); $order->save(); } } } $batch_size = 10; $total_new_orders = count($new_orders); $pushed_count = 0; $failed_count = 0; $results = array(); for ($i = 0; $i < $total_new_orders; $i += $batch_size) { $batch_order_ids = array_slice($new_orders, $i, $batch_size); foreach ($batch_order_ids as $order_id) { $order = wc_get_order($order_id); if (!$order || !($order instanceof WC_Order) || ($order instanceof WC_Order_Refund)) { $failed_count++; $results[] = array( 'order_id' => $order_id, 'success' => false, 'message' => 'Order not found in WooCommerce or is a refund' ); continue; } if ($order->get_status() === 'pending' && $order->get_payment_method() !== 'cod') { $results[] = array( 'order_id' => $order_id, 'success' => false, 'message' => 'Order is pending payment (non-COD), not pushed.' ); continue; } $sync_status = $order->get_meta('_founder_link_sync_status'); if ($sync_status === 'success') { $results[] = array( 'order_id' => $order_id, 'success' => true, 'message' => 'Already synced' ); continue; } $order_data = $this->prepare_order_data($order); $push_result = $this->send_data_to_system($order_data, 'new_order', true); if ($push_result['success']) { $order->update_meta_data('_founder_link_sync_status', 'success'); $order->update_meta_data('_founder_link_last_sync', current_time('mysql')); $order->delete_meta_data('_founder_link_last_error'); $hq_order_id = null; $data = $push_result['data'] ?? null; if ($data) { if (isset($data['hq_order_id']) && !empty($data['hq_order_id'])) { $hq_order_id = $data['hq_order_id']; } elseif (isset($data['data']) && is_array($data['data']) && isset($data['data']['hq_order_id']) && !empty($data['data']['hq_order_id'])) { $hq_order_id = $data['data']['hq_order_id']; } elseif (isset($data['order_id']) && !empty($data['order_id'])) { $hq_order_id = $data['order_id']; } elseif (isset($data['id']) && !empty($data['id'])) { $hq_order_id = $data['id']; } } if ($hq_order_id) { $order->update_meta_data('_founder_link_hq_order_id', $hq_order_id); } $order->save(); $pushed_count++; $results[] = array( 'order_id' => $order_id, 'success' => true, 'message' => 'Successfully pushed', 'hq_order_id' => $hq_order_id ); } else { $order->update_meta_data('_founder_link_sync_status', 'failed'); $order->update_meta_data('_founder_link_last_error', $push_result['message']); $order->save(); $failed_count++; $results[] = array( 'order_id' => $order_id, 'success' => false, 'message' => $push_result['message'] ); } } if ($i + $batch_size < $total_new_orders) { usleep(200000); } } wp_send_json_success(array( 'message' => sprintf( __('Bulk push complete! %d new orders pushed, %d failed, %d already existed in system.', 'founder-link'), $pushed_count, $failed_count, count($existing_orders) ), 'pushed_count' => $pushed_count, 'failed_count' => $failed_count, 'existing_count' => count($existing_orders), 'total_processed' => $total_new_orders, 'results' => $results )); } private function verify_orders_with_laravel($wc_order_ids, $options) { $api_url = rtrim($options['api_url'], '/') . '/ecwc/verify-existing-orders'; $order_data = array(); $order_processing_errors = array(); foreach ($wc_order_ids as $order_id) { try { $order = wc_get_order($order_id); if ($order && $order instanceof WC_Order && !($order instanceof WC_Order_Refund)) { $order_data[] = array( 'wc_order_id' => $order_id, 'customer_email' => $order->get_billing_email(), 'customer_phone' => $order->get_billing_phone(), 'order_number' => $order->get_order_number(), 'total' => $order->get_total(), 'currency' => $order->get_currency(), 'date_created' => $order->get_date_created()->format('Y-m-d H:i:s') ); } } catch (Exception $e) { $order_processing_errors[] = 'Exception loading order ' . $order_id . ': ' . $e->getMessage(); } } if (!empty($order_processing_errors)) { } $request_body = array( 'wc_order_ids' => $wc_order_ids, 'order_data' => $order_data, 'site_url' => get_site_url(), 'store_name' => get_bloginfo('name'), 'timestamp' => current_time('timestamp') ); $response = wp_remote_post($api_url, array( 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Basic ' . $options['access_token'], 'X-Plugin-Version' => FOUNDER_LINK_VERSION, 'X-Plugin-Action' => 'verify_existing_orders' ), 'body' => json_encode($request_body), 'timeout' => 60 )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $response_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); if ($response_code === 200) { $response_data = json_decode($response_body, true); if ($response_data && isset($response_data['success']) && $response_data['success']) { if (isset($response_data['data']) && isset($response_data['data']['results'])) { $results_count = count($response_data['data']['results']); if ($results_count === count($wc_order_ids)) { } return array( 'success' => true, 'data' => $response_data['data'] ); } else { return array( 'success' => false, 'message' => 'Invalid response structure from server' ); } } else { $error_message = isset($response_data['message']) ? $response_data['message'] : 'Invalid response from server'; return array( 'success' => false, 'message' => $error_message ); } } else { $error_message = 'HTTP ' . $response_code; $response_data = json_decode($response_body, true); if ($response_data && isset($response_data['message'])) { $error_message .= ': ' . $response_data['message']; } return array( 'success' => false, 'message' => $error_message ); } } private function prepare_order_data($order) { $payment_method = $order->get_payment_method(); $payment_method_title = $order->get_payment_method_title(); $payment_gateway_info = $this->get_payment_gateway_details($payment_method); $order_data = array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'status' => $order->get_status(), 'currency' => $order->get_currency(), 'total' => $order->get_total(), 'subtotal' => $order->get_subtotal(), 'tax_total' => $order->get_total_tax(), 'shipping_total' => $order->get_shipping_total(), 'discount_total' => $order->get_total_discount(), 'date_created' => $order->get_date_created()->format('Y-m-d H:i:s'), 'date_modified' => $order->get_date_modified()->format('Y-m-d H:i:s'), 'customer_note' => $order->get_customer_note(), 'store_name' => get_bloginfo('name'), 'url_name' => get_home_url(), 'customer' => array( 'id' => $order->get_customer_id(), 'email' => $order->get_billing_email(), 'first_name' => $order->get_billing_first_name(), 'last_name' => $order->get_billing_last_name(), 'phone' => $order->get_billing_phone() ), 'billing' => array( 'first_name' => $order->get_billing_first_name(), 'last_name' => $order->get_billing_last_name(), 'company' => $order->get_billing_company(), 'address_1' => $order->get_billing_address_1(), 'address_2' => $order->get_billing_address_2(), 'city' => $order->get_billing_city(), 'state' => $order->get_billing_state(), 'postcode' => $order->get_billing_postcode(), 'country' => $order->get_billing_country(), 'email' => $order->get_billing_email(), 'phone' => $order->get_billing_phone() ), 'shipping' => array( 'first_name' => $order->get_shipping_first_name(), 'last_name' => $order->get_shipping_last_name(), 'company' => $order->get_shipping_company(), 'address_1' => $order->get_shipping_address_1(), 'address_2' => $order->get_shipping_address_2(), 'city' => $order->get_shipping_city(), 'state' => $order->get_shipping_state(), 'postcode' => $order->get_shipping_postcode(), 'country' => $order->get_shipping_country() ), 'line_items' => array(), 'shipping_methods' => array(), 'payment_method' => $payment_method, 'payment_method_title' => $payment_method_title, 'payment_gateway' => $payment_gateway_info, 'transaction_id' => $order->get_transaction_id() ); foreach ($order->get_items() as $item_id => $item) { $product = $item->get_product(); $order_data['line_items'][] = array( 'id' => $item_id, 'name' => $item->get_name(), 'product_id' => $item->get_product_id(), 'variation_id' => $item->get_variation_id(), 'quantity' => $item->get_quantity(), 'subtotal' => $item->get_subtotal(), 'total' => $item->get_total(), 'sku' => $product ? $product->get_sku() : '', 'price' => $product ? $product->get_price() : 0 ); } foreach ($order->get_shipping_methods() as $shipping_item_id => $shipping_item) { $order_data['shipping_methods'][] = array( 'id' => $shipping_item_id, 'method_id' => $shipping_item->get_method_id(), 'method_title' => $shipping_item->get_method_title(), 'instance_id' => $shipping_item->get_instance_id(), 'total' => $shipping_item->get_total(), 'taxes' => $shipping_item->get_taxes() ); } $shipping_methods = $order->get_shipping_methods(); if (!empty($shipping_methods)) { $first_shipping = reset($shipping_methods); $order_data['shipping_method'] = $first_shipping->get_method_title(); $order_data['shipping_method_id'] = $first_shipping->get_method_id(); } else { $order_data['shipping_method'] = ''; $order_data['shipping_method_id'] = ''; } return $order_data; } private function get_payment_gateway_details($payment_method) { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); $gateway_info = array( 'id' => $payment_method, 'title' => '', 'description' => '', 'enabled' => false, 'type' => 'unknown', 'supports' => array(), 'icon' => '', 'method_description' => '' ); if (isset($available_gateways[$payment_method])) { $gateway = $available_gateways[$payment_method]; $gateway_info = array( 'id' => $gateway->id, 'title' => $gateway->get_title(), 'description' => $gateway->get_description(), 'enabled' => $gateway->enabled === 'yes', 'type' => $this->classify_payment_type($payment_method), 'supports' => $gateway->supports, 'icon' => $gateway->get_icon(), 'method_description' => $gateway->get_method_description(), 'order_button_text' => $gateway->order_button_text ); } else { $gateway_info['type'] = $this->classify_payment_type($payment_method); $gateway_info['title'] = $this->get_payment_method_display_name($payment_method); } return $gateway_info; } private function classify_payment_type($payment_method) { $payment_types = array( 'fpx' => 'online_banking', 'maybank2u' => 'online_banking', 'cimb_clicks' => 'online_banking', 'public_bank' => 'online_banking', 'rhb_bank' => 'online_banking', 'hong_leong_bank' => 'online_banking', 'ambank' => 'online_banking', 'affin_bank' => 'online_banking', 'alliance_bank' => 'online_banking', 'bank_islam' => 'online_banking', 'bank_muamalat' => 'online_banking', 'bank_rakyat' => 'online_banking', 'bsn' => 'online_banking', 'hsbc_bank' => 'online_banking', 'kfh' => 'online_banking', 'ocbc_bank' => 'online_banking', 'standard_chartered' => 'online_banking', 'uob_bank' => 'online_banking', 'billplz' => 'online_banking', 'toyyibpay' => 'online_banking', 'chip' => 'online_banking', 'tng' => 'e_wallet', 'touchngo' => 'e_wallet', 'boost' => 'e_wallet', 'grabpay' => 'e_wallet', 'shopeepay' => 'e_wallet', 'bigpay' => 'e_wallet', 'mcash' => 'e_wallet', 'vcash' => 'e_wallet', 'alipay' => 'e_wallet', 'wechatpay' => 'e_wallet', 'stripe' => 'credit_card', 'paypal' => 'paypal', 'square' => 'credit_card', 'authorize_net' => 'credit_card', 'braintree' => 'credit_card', 'worldpay' => 'credit_card', 'sage_pay' => 'credit_card', 'billplz' => 'credit_card', 'toyyibpay' => 'credit_card', 'chip' => 'credit_card', 'bacs' => 'bank_transfer', 'bank_transfer' => 'bank_transfer', 'direct_bank_transfer' => 'bank_transfer', 'cod' => 'cash_on_delivery', 'cash_on_delivery' => 'cash_on_delivery', 'cheque' => 'manual_payment', 'manual' => 'manual_payment', 'klarna' => 'bnpl', 'afterpay' => 'bnpl', 'zip' => 'bnpl', 'atome' => 'bnpl', 'riipay' => 'bnpl', 'bitcoin' => 'cryptocurrency', 'ethereum' => 'cryptocurrency', 'crypto' => 'cryptocurrency' ); return isset($payment_types[$payment_method]) ? $payment_types[$payment_method] : 'other'; } private function get_payment_method_display_name($payment_method) { $display_names = array( 'fpx' => 'FPX Online Banking', 'maybank2u' => 'Maybank2u', 'cimb_clicks' => 'CIMB Clicks', 'public_bank' => 'Public Bank', 'rhb_bank' => 'RHB Bank', 'hong_leong_bank' => 'Hong Leong Bank', 'tng' => 'Touch \'n Go eWallet', 'touchngo' => 'Touch \'n Go eWallet', 'boost' => 'Boost', 'grabpay' => 'GrabPay', 'shopeepay' => 'ShopeePay', 'stripe' => 'Credit/Debit Card (Stripe)', 'paypal' => 'PayPal', 'bacs' => 'Direct Bank Transfer', 'cod' => 'Cash on Delivery', 'klarna' => 'Klarna', 'afterpay' => 'Afterpay', 'atome' => 'Atome', 'billplz' => 'Billplz', 'toyyibpay' => 'ToyyibPay', 'chip' => 'Chip', ); return isset($display_names[$payment_method]) ? $display_names[$payment_method] : ucwords(str_replace('_', ' ', $payment_method)); } private function send_data_to_system($data, $action, $return_result = false) { $options = get_option('founder_link_options'); $api_url = rtrim($options['api_url'], '/') . '/ecwc/submit-order'; $debug_info = array( 'hook_called' => current_filter(), 'action_type' => $action, 'order_id' => isset($data['order_id']) ? $data['order_id'] : 'unknown', 'timestamp' => current_time('mysql'), 'user_agent' => 'FounderLink-WordPress-Plugin/' . FOUNDER_LINK_VERSION ); $response = wp_remote_post($api_url, array( 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Basic ' . $options['access_token'], 'X-Plugin-Version' => FOUNDER_LINK_VERSION, 'X-WordPress-Hook' => $debug_info['hook_called'] ), 'body' => json_encode(array( 'action' => $action, 'data' => $data, 'site_url' => get_site_url(), 'timestamp' => current_time('timestamp'), 'debug_info' => $debug_info )), 'timeout' => 30 )); if (is_wp_error($response)) { $error_message = $response->get_error_message(); if ($return_result) { return array( 'success' => false, 'message' => $error_message ); } return; } $response_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); if ($response_code === 200) { $response_data = json_decode($response_body, true); if ($return_result) { return array( 'success' => true, 'message' => isset($response_data['message']) ? $response_data['message'] : 'Success', 'data' => $response_data ); } } else { $error_message = 'HTTP ' . $response_code; $response_data = json_decode($response_body, true); if ($response_data && isset($response_data['message'])) { $error_message .= ': ' . $response_data['message']; } if ($return_result) { return array( 'success' => false, 'message' => $error_message ); } } } } register_deactivation_hook(__FILE__, 'founder_link_deactivate'); function founder_link_deactivate() { delete_option('founder_link_connection_status'); delete_option('founder_link_connected_user'); } add_action('admin_footer', 'founder_link_orders_list_js'); function founder_link_orders_list_js() { $current_screen = get_current_screen(); if (!$current_screen || $current_screen->id !== 'edit-shop_order') { return; } ?> <script> jQuery(document).ready(function($) { $(document).on('click', '.repush-order-list', function() { var button = $(this); var orderId = button.data('order-id'); var originalText = button.text(); button.text('Pushing...').prop('disabled', true); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'founder_link_repush_order', order_id: orderId, nonce: '<?php echo wp_create_nonce('founder_link_repush_nonce'); ?>' }, success: function(response) { if (response.success) { button.closest('td').html('<span style="color: #46b450;">✓ Synced</span>'); } else { alert('Failed: ' + response.data.message); button.text(originalText).prop('disabled', false); } }, error: function() { alert('An error occurred. Please try again.'); button.text(originalText).prop('disabled', false); } }); }); }); </script> <?php } ?>