From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh

---
 inc/integrations/services/Helcim.php | 1334 ---------------------------------------------------------
 1 files changed, 4 insertions(+), 1,330 deletions(-)

diff --git a/inc/integrations/services/Helcim.php b/inc/integrations/services/Helcim.php
index 47d97fd..3681d5f 100644
--- a/inc/integrations/services/Helcim.php
+++ b/inc/integrations/services/Helcim.php
@@ -1,6 +1,8 @@
 <?php
 
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\managers\queue\executors\IntegrationExecutor;
 use JVBase\managers\queue\TypeConfig;
@@ -24,1338 +26,10 @@
  */
 class Helcim extends Integrations
 {
-	protected static string $syncCustomer = 'helcim_sync_customer';
-	protected string $service_name = 'helcim';
-	protected string|array $apiBase = 'https://api.helcim.com/v2';
-
-	protected bool $isOAuthService = false;
-
-	/**
-	 * Helcim API rate limits
-	 * @see https://devdocs.helcim.com/docs/api-rate-limits
-	 */
-	protected array $rate_limits = [
-		'per_second' => 5,
-		'per_minute' => 60,
-		'per_hour'   => 1000
-	];
-
-	protected static array $instances = [];
-	public static function getInstance(?int $userID = null):self
-	{
-		$key = is_null($userID) ? 'base' : $userID;
-		if (!array_key_exists($key, self::$instances)) {
-			self::$instances[$key] = new self($userID);
-		}
-		return self::$instances[$key];
-	}
-
-	protected function __construct(?int $userID = null)
-	{
-		$this->title = 'Helcim';
-		$this->icon  = 'credit-card';
-
-		$this->fields = [
-			'api_token' => [
-				'type'     => 'text',
-				'subtype'  => 'password',
-				'label'    => 'API Token',
-				'hint'     => 'Found in Helcim Dashboard → Settings → API Access',
-				'required' => true,
-			],
-			'currency' => [
-				'type'    => 'select',
-				'label'   => 'Currency',
-				'options' => [
-					'CAD' => 'CAD',
-					'USD' => 'USD',
-				],
-				'default' => 'CAD',
-			],
-		];
-
-		$this->advanced = [
-			'fee_saver' => [
-				'type'  => 'true_false',
-				'label' => 'Fee Saver',
-				'hint'  => 'Pass processing fees to customers (not compatible with Google Pay)',
-			],
-			'allow_ach' => [
-				'type'  => 'true_false',
-				'label' => 'Allow ACH/Bank Payments',
-				'hint'  => 'Enable bank account payments alongside credit card',
-			],
-		];
-
-		$this->instructions = [
-			'Go to <a href="https://my.helcim.com" target="_blank">Helcim Dashboard</a>',
-			'Navigate to Settings → API Access',
-			'Create a new API Access Configuration',
-			'Enable permissions: General (Customers, Invoices, Products), Transaction Processing',
-			'Copy the API Token and paste it below',
-		];
-
-		$this->canSync = [
-			'create' => true,
-			'update' => true,
-			'delete' => false,
-		];
-
-		$this->hasWebhooks = false;
-
-		parent::__construct();
-
-		// Helcim-specific actions (processAction dispatches these)
-		$this->actions = array_merge($this->actions, [
-			'initialize_checkout' => 'initializeCheckout',
-			'get_invoices'        => 'handleGetInvoices',
-			'get_invoice'         => 'handleGetInvoice',
-			'get_customer_cards'  => 'handleGetCustomerCards',
-		]);
-
-		$this->buttons = array_merge($this->buttons, [
-			'import_from_helcim' => 'Import Products from Helcim',
-			'sync_to_helcim'    => 'Sync Site to Helcim',
-		]);
-	}
-
-	/*****************************************************************
-	 * ABSTRACT IMPLEMENTATIONS
-	 *****************************************************************/
+	use Admin, Instance;
 
 	protected function initialize(): void
 	{
-		if (empty($this->credentials)) {
-			$this->loadCredentials();
-		}
-
-		$this->apiEndpoints = [
-			'connection-test',
-			'helcim-pay/initialize',
-			'invoices',
-			'customers',
-			'payment/purchase',
-			'payment/preauth',
-			'payment/capture',
-			'payment/refund',
-			'payment/verify',
-			'card-transactions',
-			'card-batches',
-		];
-	}
-
-	protected function getRequestHeaders(): array
-	{
-		return [
-			'api-token'    => $this->credentials['api_token'] ?? '',
-			'Content-Type' => 'application/json',
-			'Accept'       => 'application/json',
-		];
-	}
-
-	protected function performConnectionTest(): bool
-	{
-		try {
-			$response = $this->getRequest('connection-test', [], null, 'none', true);
-			return !is_wp_error($response) && !$this->isErrorResponse($response ?? []);
-		} catch (Exception $e) {
-			$this->logError('Connection test failed', ['error' => $e->getMessage()]);
-			return false;
-		}
-	}
-
-	/*****************************************************************
-	 * CONTENT TYPES — product field definitions
-	 *****************************************************************/
-
-	protected function setContentTypes(): void
-	{
-		$this->has_content = true;
-		$this->defaultContent = 'REGULAR';
-		$types = ['REGULAR', 'SERVICE', 'DIGITAL', 'FOOD_AND_BEV', 'EVENT', 'SUBSCRIPTION', 'DONATION'];
-
-		foreach ($types as $type) {
-			$t = $type === 'REGULAR' ? null : $type;
-			$this->contentTypes[$type] = $this->getHelcimMeta($t);
-		}
-	}
-
-	public function getAdditionalFields(?string $content_type = null):array
-	{
-		if ($content_type === 'customer') {
-			$array = $this->getCustomerFields();
-			return array_combine(
-				array_map(fn($k) => '_square_' . $k, array_keys($array)),
-				$array
-			);
-		}
-		return array_combine(
-			array_map(fn($k) => 'hc_' . $k, array_keys($this->getHelcimMeta($content_type))),
-			$this->getHelcimMeta($content_type)
-		);
-	}
-
-	protected function getCustomerFields():array
-	{
-		return [];
-	}
-
-	/**
-	 * Get Helcim product meta fields by type.
-	 *
-	 * Used by Registrar.php  when helcim is configured
-	 */
-	public function getHelcimMeta(?string $type = null): array
-	{
-		$fields = [
-			// Basic Product Fields
-			'price' => [
-				'type'        => 'number',
-				'bulkEdit'    => true,
-				'label'       => 'Price',
-				'step'        => 0.01,
-				'max'         => 99999,
-				'description' => 'Product price'
-			],
-
-			'product_type' => [
-				'type'  => 'select',
-				'label' => 'Product Type',
-				'options' => [
-					'REGULAR'       => 'Regular Product',
-					'SERVICE'       => 'Service',
-					'DIGITAL'       => 'Digital Product',
-					'FOOD_AND_BEV'  => 'Food & Beverage',
-					'EVENT'         => 'Event/Ticket',
-					'SUBSCRIPTION'  => 'Subscription',
-					'DONATION'      => 'Donation'
-				],
-				'default' => $type ?? 'REGULAR'
-			],
-
-			'cart_quantity' => [
-				'type'   => 'number',
-				'label'  => 'Quantity',
-				'hidden' => true,
-			],
-
-			// Tax & Shipping
-			'tax_exempt' => [
-				'type'    => 'true_false',
-				'label'   => 'Tax Exempt',
-				'section' => 'helcim-tax'
-			],
-
-			'shipping_required' => [
-				'type'    => 'true_false',
-				'label'   => 'Shipping Required',
-				'section' => 'helcim-shipping'
-			],
-
-			'shipping_weight' => [
-				'type'        => 'number',
-				'label'       => 'Shipping Weight (kg)',
-				'step'        => 0.01,
-				'section'     => 'helcim-shipping',
-				'condition'   => [
-					'field'    => 'shipping_required',
-					'operator' => '==',
-					'value'    => true
-				]
-			],
-
-			// Availability
-			'available_online' => [
-				'type'    => 'true_false',
-				'label'   => 'Available Online',
-				'section' => 'helcim-availability',
-				'default' => true
-			],
-
-			'available_for_pickup' => [
-				'type'    => 'true_false',
-				'label'   => 'Available for Pickup',
-				'section' => 'helcim-availability',
-				'default' => true
-			],
-
-			'available_for_delivery' => [
-				'type'    => 'true_false',
-				'label'   => 'Available for Delivery',
-				'section' => 'helcim-availability',
-				'default' => false
-			],
-
-			'_helcim_sku' => [
-				'type'        => 'text',
-				'label'       => 'SKU',
-				'description' => 'Stock keeping unit',
-				'section'     => 'helcim-config'
-			],
-
-			// Product Variations
-			'product_variations' => [
-				'type'        => 'repeater',
-				'label'       => 'Product Variations',
-				'description' => 'Different versions of this product',
-				'add_label'   => 'Add Variation',
-				'section'     => 'variations',
-				'fields'      => $this->getHelcimVariationMeta($type)
-			],
-
-			// Product Options
-			'options' => [
-				'type'   => 'group',
-				'label'  => 'Product Options',
-				'section'=> 'helcim-options',
-				'fields' => [
-					'max_order' => [
-						'type'    => 'number',
-						'label'   => 'Maximum per order',
-						'default' => 50
-					],
-					'min_order' => [
-						'type'    => 'number',
-						'label'   => 'Minimum per order',
-						'default' => 0,
-					],
-					'step' => [
-						'type'    => 'number',
-						'label'   => 'Order increment',
-						'default' => 1,
-					],
-					'preparation_time' => [
-						'type'        => 'number',
-						'label'       => 'Preparation time (minutes)',
-						'description' => 'Time needed to prepare this item',
-						'condition'   => [
-							'field'    => 'product_type',
-							'operator' => 'in',
-							'value'    => ['FOOD_AND_BEV', 'SERVICE']
-						]
-					]
-				]
-			],
-
-			// Subscription Fields
-			'subscription_settings' => [
-				'type'      => 'group',
-				'label'     => 'Subscription Settings',
-				'section'   => 'helcim-subscription',
-				'condition' => [
-					'field'    => 'product_type',
-					'operator' => '==',
-					'value'    => 'SUBSCRIPTION'
-				],
-				'fields' => [
-					'billing_cycle' => [
-						'type'    => 'select',
-						'label'   => 'Billing Cycle',
-						'options' => [
-							'daily'     => 'Daily',
-							'weekly'    => 'Weekly',
-							'monthly'   => 'Monthly',
-							'quarterly' => 'Quarterly',
-							'yearly'    => 'Yearly'
-						],
-						'default' => 'monthly'
-					],
-					'trial_period' => [
-						'type'        => 'number',
-						'label'       => 'Trial Period (days)',
-						'description' => 'Free trial period before billing starts',
-						'default'     => 0
-					],
-					'setup_fee' => [
-						'type'        => 'number',
-						'label'       => 'Setup Fee',
-						'step'        => 0.01,
-						'description' => 'One-time setup fee'
-					]
-				]
-			],
-
-			// Food & Beverage Specific
-			'food_settings' => [
-				'type'      => 'group',
-				'label'     => 'Food & Beverage Settings',
-				'section'   => 'helcim-food',
-				'condition' => [
-					'field'    => 'product_type',
-					'operator' => '==',
-					'value'    => 'FOOD_AND_BEV'
-				],
-				'fields' => [
-					'ingredients' => [
-						'type'        => 'textarea',
-						'label'       => 'Ingredients',
-						'description' => 'List ingredients (comma separated)'
-					],
-					'allergens' => [
-						'type'        => 'checkbox_list',
-						'label'       => 'Allergens',
-						'options'     => [
-							'gluten'   => 'Contains Gluten',
-							'dairy'    => 'Contains Dairy',
-							'nuts'     => 'Contains Nuts',
-							'soy'      => 'Contains Soy',
-							'eggs'     => 'Contains Eggs',
-							'seafood'  => 'Contains Seafood'
-						]
-					],
-					'dietary_options' => [
-						'type'    => 'checkbox_list',
-						'label'   => 'Dietary Options',
-						'options' => [
-							'vegetarian'   => 'Vegetarian',
-							'vegan'        => 'Vegan',
-							'gluten_free'  => 'Gluten Free',
-							'dairy_free'   => 'Dairy Free',
-							'keto'         => 'Keto Friendly',
-							'halal'        => 'Halal',
-							'kosher'       => 'Kosher'
-						]
-					],
-					'spice_level' => [
-						'type'    => 'range',
-						'label'   => 'Spice Level',
-						'min'     => 0,
-						'max'     => 5,
-						'default' => 0
-					],
-					'serving_size' => [
-						'type'        => 'text',
-						'label'       => 'Serving Size',
-						'description' => 'e.g., "Serves 2-3"'
-					]
-				]
-			],
-
-			// Service Specific
-			'service_settings' => [
-				'type'      => 'group',
-				'label'     => 'Service Settings',
-				'section'   => 'helcim-service',
-				'condition' => [
-					'field'    => 'product_type',
-					'operator' => '==',
-					'value'    => 'SERVICE'
-				],
-				'fields' => [
-					'service_duration' => [
-						'type'        => 'number',
-						'label'       => 'Duration (minutes)',
-						'description' => 'Service duration in minutes'
-					],
-					'booking_required' => [
-						'type'  => 'true_false',
-						'label' => 'Booking Required'
-					],
-					'capacity' => [
-						'type'        => 'number',
-						'label'       => 'Service Capacity',
-						'description' => 'Maximum number of customers per service'
-					],
-					'staff_required' => [
-						'type'        => 'number',
-						'label'       => 'Staff Required',
-						'description' => 'Number of staff needed',
-						'default'     => 1
-					]
-				]
-			],
-
-			// Event Specific
-			'event_settings' => [
-				'type'      => 'group',
-				'label'     => 'Event Settings',
-				'section'   => 'helcim-event',
-				'condition' => [
-					'field'    => 'product_type',
-					'operator' => '==',
-					'value'    => 'EVENT'
-				],
-				'fields' => [
-					'event_date' => [
-						'type'  => 'datetime',
-						'label' => 'Event Date & Time'
-					],
-					'event_location' => [
-						'type'  => 'text',
-						'label' => 'Event Location'
-					],
-					'max_attendees' => [
-						'type'  => 'number',
-						'label' => 'Maximum Attendees'
-					],
-					'early_bird_price' => [
-						'type'        => 'number',
-						'label'       => 'Early Bird Price',
-						'step'        => 0.01,
-						'description' => 'Discounted price for early registrations'
-					],
-					'early_bird_deadline' => [
-						'type'  => 'date',
-						'label' => 'Early Bird Deadline'
-					]
-				]
-			]
-		];
-
-		// Add inventory fields if configured
-		if ($config['hasInventory'] ?? false) {
-			$fields['_helcim_inventory'] = [
-				'type'     => 'number',
-				'label'    => 'Inventory',
-				'bulkEdit' => true,
-				'section'  => 'inventory'
-			];
-
-			$fields['track_inventory'] = [
-				'type'    => 'true_false',
-				'label'   => 'Track Inventory',
-				'section' => 'inventory',
-				'default' => true
-			];
-
-			$fields['low_stock_threshold'] = [
-				'type'        => 'number',
-				'label'       => 'Low Stock Alert',
-				'description' => 'Alert when stock falls below this level',
-				'section'     => 'inventory',
-				'default'     => 5
-			];
-
-			$fields['product_variations']['fields']['inventory'] = [
-				'type'        => 'number',
-				'label'       => 'Stock Quantity',
-				'description' => 'Current stock for this variation'
-			];
-		}
-
-		return $fields;
-	}
-
-	public function getHelcimVariationMeta(?string $type = null):array
-	{
-
-		$base = [
-			'name' => [
-				'type'        => 'text',
-				'label'       => 'Variation Name',
-				'description' => 'e.g., "Small", "Large", "Red"'
-			],
-			'price' => [
-				'type'        => 'number',
-				'label'       => 'Price',
-				'step'        => 0.01,
-				'max'         => 99999,
-				'description' => 'Price for this variation'
-			],
-			'sku' => [
-				'type'        => 'text',
-				'label'       => 'SKU',
-				'description' => 'Stock keeping unit for this variation'
-			],
-			'track_inventory' => [
-				'type'  => 'true_false',
-				'label' => 'Track Inventory',
-			],
-			'_helcim_variation_id' => [
-				'type'        => 'text',
-				'label'       => 'Helcim Variation ID',
-				'description' => 'Helcim ID for this variation',
-				'hidden'      => true
-			],
-			'_helcim_last_sync' => [
-				'type'   => 'datetime',
-				'label'  => 'Last Sync',
-				'hidden' => true
-			],
-			'options' => [
-				'type'        => 'group',
-				'label'       => 'Variation Options',
-				'collapsible' => true,
-				'fields'      => [
-					'color' => [
-						'type'        => 'color',
-						'label'       => 'Color',
-						'description' => 'Visual color for this variation'
-					],
-					'size' => [
-						'type'    => 'select',
-						'label'   => 'Size',
-						'options' => [
-							''      => 'N/A',
-							'xs'    => 'Extra Small',
-							's'     => 'Small',
-							'm'     => 'Medium',
-							'l'     => 'Large',
-							'xl'    => 'Extra Large',
-							'xxl'   => '2X Large',
-							'custom'=> 'Custom'
-						]
-					],
-					'custom_size' => [
-						'type'        => 'text',
-						'label'       => 'Custom Size',
-						'condition'   => [
-							'field'    => 'size',
-							'operator' => '==',
-							'value'    => 'custom'
-						]
-					],
-					'weight' => [
-						'type'        => 'number',
-						'label'       => 'Weight (kg)',
-						'step'        => 0.01,
-						'description' => 'Weight of this variation'
-					],
-					'dimensions' => [
-						'type'   => 'group',
-						'label'  => 'Dimensions',
-						'fields' => [
-							'length' => [
-								'type'  => 'number',
-								'label' => 'Length (cm)',
-								'step'  => 0.1
-							],
-							'width' => [
-								'type'  => 'number',
-								'label' => 'Width (cm)',
-								'step'  => 0.1
-							],
-							'height' => [
-								'type'  => 'number',
-								'label' => 'Height (cm)',
-								'step'  => 0.1
-							]
-						]
-					]
-				]
-			]
-		];
-
-		$extras = [
-			'SERVICE' => [
-				'service_duration'	=> [
-					'type'        => 'number',
-					'label'       => 'Duration (minutes)',
-					'description' => 'Duration for this service variation'
-				],
-				'available_for_booking'	=> [
-					'type'  => 'true_false',
-					'label' => 'Available for Booking'
-				]
-			],
-
-			'FOOD_AND_BEV'	=> [
-				'portion_size'	=> [
-					'type'    => 'select',
-					'label'   => 'Portion Size',
-					'options' => [
-						'small'    => 'Small',
-						'regular'  => 'Regular',
-						'large'    => 'Large',
-						'family'   => 'Family Size'
-					]
-				],
-				'calories'	=> [
-					'type'        => 'number',
-					'label'       => 'Calories',
-					'description' => 'Calorie count for this variation'
-				]
-			],
-
-			'DIGITAL'	=> [
-				'download_limit'	=> [
-					'type'        => 'number',
-					'label'       => 'Download Limit',
-					'description' => 'Maximum number of downloads',
-					'default'     => -1
-				],
-				'expiry_days'	=> [
-					'type'        => 'number',
-					'label'       => 'Access Duration (days)',
-					'description' => 'Days until download expires',
-					'default'     => 0
-				]
-			]
-		];
-		if ($type && array_key_exists($type, $extras)){
-			$base = array_merge($base, $extras[$type]);
-		}
-		return $base;
-	}
-
-	/*****************************************************************
-	 * HELCIMPAY.JS — Frontend Scripts & Checkout Initialization
-	 *****************************************************************/
-
-	protected function registerAdditionalHooks(): void
-	{
-		if (!$this->isSetUp()) {
-			return;
-		}
-
-		add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
-
-		// Shared checkout UI (replaces provider-specific outputCheckout)
-		add_filter('jvbAdditionalActions', [Checkout::class, 'render']);
-
-		// Checkout description filter
-		add_filter('jvb_checkout_description', function (string $desc, string $provider) {
-			if ($provider === 'helcim') {
-				return 'Securely checkout with your name, email, and payments processed by Helcim.';
-			}
-			return $desc;
-		}, 10, 2);
-
-		// Register webhook endpoint (handled by parent)
-		$this->registerWebhookEndpoint();
-	}
-
-	public function enqueueScripts(): void
-	{
-		// HelcimPay.js SDK
-		wp_enqueue_script(
-			'helcim-pay-sdk',
-			'https://secure.helcim.app/helcim-pay/services/start.js',
-			[],
-			null,
-			true
-		);
-
-		// Base cart checkout (shared with Square)
-		wp_register_script(
-			'jvb-checkout',
-			JVB_URL . 'assets/js/min/checkout.min.js',
-			['jvb-utility', 'jvb-queue', 'jvb-a11y', 'jvb-cache', 'jvb-tabs', 'jvb-popup'],
-			'1.1.31',
-			true
-		);
-
-		// Helcim checkout (extends CartCheckout)
-		wp_register_script(
-			'jvb-helcim-checkout',
-			JVB_URL . 'assets/js/min/helcim.min.js',
-			['jvb-checkout', 'helcim-pay-sdk'],
-			'1.1.31',
-			true
-		);
-
-		wp_localize_script('jvb-helcim-checkout', 'helcimConfig', [
-			'api_url'      => rest_url('jvb/v1/helcim/'),
-			'nonce'        => wp_create_nonce('wp_rest'),
-			'currency'     => $this->credentials['currency'] ?? 'CAD',
-			'is_logged_in' => is_user_logged_in(),
-			'user_email'   => is_user_logged_in() ? wp_get_current_user()->user_email : '',
-			'isOpen'       => apply_filters('jvb_store_is_open', '1'),
-		]);
-
-		wp_enqueue_script('jvb-helcim-checkout');
-	}
-
-
-	protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
-	{
-		$queue    = JVB()->queue();
-
-		$queue->registry()->register(self::$import, new TypeConfig(
-			executor:   $executor,
-			maxRetries: 3
-		));
-
-		$queue->registry()->register(self::$syncCustomer, new TypeConfig(
-			executor:   $executor,
-			maxRetries: 2
-		));
-	}
-
-	/**
-	 * Initialize a HelcimPay.js checkout session.
-	 *
-	 * Server-side: POST /helcim-pay/initialize → returns checkoutToken + secretToken.
-	 * Client-side: appendHelcimPayIframe(checkoutToken) renders the payment modal.
-	 * Tokens are valid for 60 minutes.
-	 *
-	 * @param array $data [
-	 *     'amount'      => float,   // Required
-	 *     'invoiceId'   => string,  // Optional — pay a specific invoice
-	 *     'customerId'  => int,     // Optional — associate with Helcim customer
-	 *     'paymentType' => string,  // purchase|preauth|verify (default: purchase)
-	 * ]
-	 */
-	public function initializeCheckout(array $data): array
-	{
-		if (empty($data['amount']) || (float) $data['amount'] <= 0) {
-			return ['success' => false, 'message' => 'Invalid amount'];
-		}
-
-		$paymentMethod = !empty($this->credentials['allow_ach']) ? 'cc-ach' : 'cc';
-
-		$body = [
-			'paymentType'   => $data['paymentType'] ?? 'purchase',
-			'amount'        => (float) $data['amount'],
-			'currency'      => $this->credentials['currency'] ?? 'CAD',
-			'paymentMethod' => $paymentMethod,
-		];
-
-		if (!empty($data['invoiceId'])) {
-			$body['invoiceNumber'] = $data['invoiceId'];
-		}
-
-		if (!empty($data['customerId'])) {
-			$body['customerId'] = (int) $data['customerId'];
-		}
-
-		if (!empty($this->credentials['fee_saver'])) {
-			$body['hasConvenienceFee'] = true;
-		}
-
-		$response = $this->postRequest('helcim-pay/initialize', $body);
-
-		if (is_wp_error($response)) {
-			return ['success' => false, 'message' => $response->get_error_message()];
-		}
-
-		if (empty($response['checkoutToken'])) {
-			return ['success' => false, 'message' => 'Failed to initialize checkout'];
-		}
-
-		return [
-			'success'       => true,
-			'checkoutToken' => $response['checkoutToken'],
-			'secretToken'   => $response['secretToken'] ?? '',
-		];
-	}
-
-	/*****************************************************************
-	 * INVOICES — Helcim is source of truth
-	 *****************************************************************/
-
-	/**
-	 * Get invoices for a customer.
-	 *
-	 * @param array $data ['email' => string] or ['customerId' => int]
-	 */
-	public function handleGetInvoices(array $data): array
-	{
-		$customerId = $data['customerId'] ?? null;
-
-		if (empty($customerId) && !empty($data['email'])) {
-			$customerId = $this->getCustomerIdByEmail($data['email']);
-		}
-
-		if (!$customerId) {
-			return ['success' => true, 'invoices' => []];
-		}
-
-		$response = $this->getRequest('invoices', ['customerId' => $customerId], null, 'minimal');
-
-		if (is_wp_error($response) || !is_array($response)) {
-			return ['success' => false, 'message' => 'Failed to fetch invoices'];
-		}
-
-		return ['success' => true, 'invoices' => $response];
-	}
-
-	/**
-	 * Get a single invoice by ID.
-	 */
-	public function handleGetInvoice(array $data): array
-	{
-		$invoiceId = $data['invoiceId'] ?? null;
-
-		if (!$invoiceId) {
-			return ['success' => false, 'message' => 'Invoice ID required'];
-		}
-
-		$response = $this->getRequest("invoices/{$invoiceId}", [], null, 'minimal');
-
-		if (is_wp_error($response) || !is_array($response)) {
-			return ['success' => false, 'message' => 'Failed to fetch invoice'];
-		}
-
-		return ['success' => true, 'invoice' => $response];
-	}
-
-	/*****************************************************************
-	 * CUSTOMERS & CARDS
-	 *****************************************************************/
-
-	/**
-	 * Find Helcim customer ID by email.
-	 */
-	public function getCustomerIdByEmail(string $email): ?int
-	{
-		$cacheKey = 'customer_email_' . md5($email);
-		$cached   = $this->cache->get($cacheKey);
-
-		if ($cached !== false) {
-			return (int) $cached;
-		}
-
-		$response = $this->getRequest('customers', ['search' => $email], null, 'none', true);
-
-		if (is_wp_error($response) || empty($response)) {
-			return null;
-		}
-
-		$customers = is_array($response) ? $response : [];
-		$emailLower = strtolower($email);
-
-		foreach ($customers as $customer) {
-			$contactEmail = strtolower($customer['contactEmail'] ?? $customer['email'] ?? '');
-			if ($contactEmail === $emailLower) {
-				$this->cache->set($cacheKey, $customer['id'], $this->cacheStrategy['aggressive']);
-				return (int) $customer['id'];
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Get or create a Helcim customer.
-	 */
-	public function getOrCreateCustomer(array $info): ?int
-	{
-		if (empty($info['email'])) {
-			return null;
-		}
-
-		$existing = $this->getCustomerIdByEmail($info['email']);
-		if ($existing) {
-			return $existing;
-		}
-
-		$response = $this->postRequest('customers', [
-			'contactName'  => $info['name'] ?? '',
-			'contactEmail' => $info['email'],
-			'cellphone'    => $info['phone'] ?? '',
-		]);
-
-		if (is_wp_error($response) || empty($response['id'])) {
-			return null;
-		}
-
-		return (int) $response['id'];
-	}
-
-	/**
-	 * Get saved cards for a customer.
-	 */
-	public function handleGetCustomerCards(array $data): array
-	{
-		$customerId = $data['customerId'] ?? null;
-
-		if (empty($customerId) && !empty($data['email'])) {
-			$customerId = $this->getCustomerIdByEmail($data['email']);
-		}
-
-		if (!$customerId) {
-			return ['success' => true, 'cards' => []];
-		}
-
-		$response = $this->getRequest("customers/{$customerId}/cards", [], null, 'moderate');
-
-		if (is_wp_error($response) || !is_array($response)) {
-			return ['success' => false, 'message' => 'Failed to fetch cards'];
-		}
-
-		return ['success' => true, 'cards' => $response];
-	}
-
-	/**
-	 * Get bank accounts for a customer.
-	 */
-	public function getCustomerBankAccounts(int $customerId): array
-	{
-		$response = $this->getRequest("customers/{$customerId}/bank-accounts", [], null, 'moderate');
-		return (!is_wp_error($response) && is_array($response)) ? $response : [];
-	}
-
-	/*****************************************************************
-	 * TRANSACTIONS
-	 *****************************************************************/
-
-	public function getTransactions(array $params = []): array
-	{
-		$response = $this->getRequest('card-transactions', $params, null, 'minimal');
-		return (!is_wp_error($response) && is_array($response)) ? $response : [];
-	}
-
-	public function refundPayment(array $data): array
-	{
-		$response = $this->postRequest('payment/refund', $data);
-
-		if (is_wp_error($response)) {
-			return ['success' => false, 'message' => $response->get_error_message()];
-		}
-
-		return ['success' => true, 'transaction' => $response];
-	}
-
-	/*****************************************************************
-	 * PRODUCT SYNC
-	 *****************************************************************/
-
-	protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void
-	{
-
-		error_log('==== [Helcim]::handleTheSavePost ====');
-		$this->queueOperation(self::$syncTo, [
-			'items'   => [$postID],
-			'user'		=> user_can($post->post_author, 'manage_options') ? null : $post->post_author
-		], [
-			'priority' => 'high',
-			'delay'    => 30,
-		]);
-		Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
-	}
-	/**
-	 * Handle post deletion
-	 */
-	public function handleDeletePost(int $postID): void
-	{
-		$item_id = Meta::forPost($postID)->get("_{$this->service_name}_item_id");
-
-		if (empty($item_id)) {
-			return;
-		}
-		$this->queueOperation(self::$deleteFrom, [
-			'external_ids' => [$item_id],
-			'post_id'      => $postID,
-		], [
-			'priority' => 'high',
-		]);
-
-	}
-
-	protected function handleImportFromHelcim(): array
-	{
-		$this->queueOperation('import_products', [
-			'user_id' => $this->userID,
-		], ['priority' => 'normal']);
-
-		return ['success' => true, 'message' => 'Import from Helcim queued'];
-	}
-
-	/*****************************************************************
-	 * USER ↔ CUSTOMER LINKING
-	 *****************************************************************/
-
-	public function linkUserToCustomer(int $userId, int $helcimCustomerId): void
-	{
-		update_user_meta($userId, BASE . '_helcim_customer_id', $helcimCustomerId);
-	}
-
-	public function getUserCustomerId(int $userId): ?int
-	{
-		$id = get_user_meta($userId, BASE . '_helcim_customer_id', true);
-		return $id ? (int) $id : null;
-	}
-
-	/**
-	 * Resolve customer ID from user meta, falling back to email lookup + auto-link.
-	 */
-	public function resolveCustomerId(int $userId): ?int
-	{
-		$id = $this->getUserCustomerId($userId);
-		if ($id) {
-			return $id;
-		}
-
-		$user = get_userdata($userId);
-		if (!$user || empty($user->user_email)) {
-			return null;
-		}
-
-		$id = $this->getCustomerIdByEmail($user->user_email);
-		if ($id) {
-			$this->linkUserToCustomer($userId, $id);
-		}
-
-		return $id;
-	}
-
-	/*****************************************************************
-	 * VALIDATION
-	 *****************************************************************/
-
-	/**
-	 * Validate a HelcimPay.js transaction using the secret token.
-	 *
-	 * After the frontend receives a SUCCESS message event, call this
-	 * server-side to verify the transaction hash.
-	 */
-	public function validateTransaction(string $secretToken, array $transactionData): bool
-	{
-		$hash = hash('sha256', $secretToken . json_encode($transactionData));
-		return hash_equals($hash, $transactionData['hash'] ?? '');
-	}
-	/*******************************************************************
-	 * WEBHOOKS
-	*******************************************************************/
-	/**
-	 * Validate Helcim webhook signature.
-	 *
-	 * Helcim signs webhooks with HMAC-SHA256 using:
-	 *   signedContent = "{webhook-id}.{webhook-timestamp}.{body}"
-	 *   key = base64_decode(verifierToken)
-	 *
-	 * Headers: webhook-id, webhook-timestamp, webhook-signature (v1,{base64hash})
-	 *
-	 * @see https://devdocs.helcim.com/docs/enabling-webhooks-for-transactions
-	 */
-	protected function validateWebhook(array $payload): bool
-	{
-		$headers = $payload['_headers'] ?? [];
-
-		$webhookId        = $headers['webhook_id'][0]        ?? $headers['webhook-id']        ?? '';
-		$webhookTimestamp  = $headers['webhook_timestamp'][0] ?? $headers['webhook-timestamp'] ?? '';
-		$webhookSignature = $headers['webhook_signature'][0]  ?? $headers['webhook-signature'] ?? '';
-
-		if (empty($webhookId) || empty($webhookTimestamp) || empty($webhookSignature)) {
-			$this->logError('Webhook missing required headers', [], 'warning');
-			return false;
-		}
-
-		// Verify timestamp is within 5 minutes (prevent replay attacks)
-		$now = time();
-		if (abs($now - (int)$webhookTimestamp) > 300) {
-			$this->logError('Webhook timestamp too old', [
-				'webhook_timestamp' => $webhookTimestamp,
-				'server_time'       => $now,
-			], 'warning');
-			return false;
-		}
-
-		$secret = $this->getAdvancedSetting('webhook_signature_key');
-		if (empty($secret)) {
-			// If no signature key configured, allow webhook but log warning
-			$this->logDebug('No webhook signature key configured — skipping verification');
-			return true;
-		}
-
-		// Reconstruct the raw body from the payload (minus our injected _headers)
-		$body = $payload['_raw_body'] ?? json_encode(
-			array_diff_key($payload, ['_headers' => 1, '_raw_body' => 1])
-		);
-
-		$signedContent = "{$webhookId}.{$webhookTimestamp}.{$body}";
-		$secretBytes   = base64_decode($secret);
-		$expectedHash  = base64_encode(
-			hash_hmac('sha256', $signedContent, $secretBytes, true)
-		);
-
-		// webhook-signature may contain multiple signatures: "v1,hash1 v2,hash2"
-		$signatures = explode(' ', $webhookSignature);
-		foreach ($signatures as $sig) {
-			$parts = explode(',', $sig, 2);
-			if (count($parts) === 2 && hash_equals($expectedHash, $parts[1])) {
-				return true;
-			}
-		}
-
-		$this->logError('Webhook signature mismatch', [
-			'webhook_id' => $webhookId,
-		], 'warning');
-
-		return false;
-	}
-
-	/**
-	 * Process a validated Helcim webhook event.
-	 *
-	 * Helcim sends minimal payloads: {"id": "12345", "type": "cardTransaction"}
-	 * We fetch the full transaction details from the API.
-	 */
-	protected function processWebhook(array $payload): bool
-	{
-		$type = $payload['type'] ?? '';
-		$id   = $payload['id']  ?? '';
-
-		if (empty($type) || empty($id)) {
-			$this->logError('Webhook missing type or id', $payload, 'warning');
-			return false;
-		}
-
-		return match ($type) {
-			'cardTransaction' => $this->handleTransactionWebhook($id),
-			'terminalCancel'  => $this->handleTerminalCancelWebhook($payload),
-			default           => $this->handleUnknownWebhook($type, $id),
-		};
-	}
-
-	/**
-	 * Handle a cardTransaction webhook — fetch full transaction, update records.
-	 */
-	protected function handleTransactionWebhook(string $transactionId): bool
-	{
-		// Fetch full transaction from Helcim API
-		$transaction = $this->getRequest("card-transactions/{$transactionId}");
-
-		if (is_wp_error($transaction) || empty($transaction)) {
-			$this->logError('Failed to fetch transaction for webhook', [
-				'transaction_id' => $transactionId,
-			]);
-			return false;
-		}
-
-		$status = $transaction['status'] ?? '';
-
-		// Fire action for other parts of the system to react
-		do_action('jvb_helcim_transaction', $transaction, $status);
-
-		// If linked to an invoice, update invoice cache
-		$invoiceNumber = $transaction['invoiceNumber'] ?? '';
-		if (!empty($invoiceNumber)) {
-			$this->cache->delete("invoice_{$invoiceNumber}");
-			do_action('jvb_helcim_invoice_updated', $invoiceNumber, $transaction);
-		}
-
-		// Log for debugging
-		$this->logDebug('Transaction webhook processed', [
-			'transaction_id' => $transactionId,
-			'status'         => $status,
-			'amount'         => $transaction['amount'] ?? 0,
-			'type'           => $transaction['type'] ?? '',
-		]);
-
-		return true;
-	}
-
-	/**
-	 * Handle Smart Terminal cancel webhook
-	 */
-	protected function handleTerminalCancelWebhook(array $payload): bool
-	{
-		do_action('jvb_helcim_terminal_cancel', $payload);
-
-		$this->logDebug('Terminal cancel webhook processed', [
-			'payload' => $payload,
-		]);
-
-		return true;
-	}
-
-	/**
-	 * Handle unknown webhook types (future-proofing)
-	 */
-	protected function handleUnknownWebhook(string $type, string $id): bool
-	{
-		$this->logDebug('Unknown webhook type received', [
-			'type' => $type,
-			'id'   => $id,
-		]);
-
-		do_action("jvb_helcim_webhook_{$type}", $id);
-		return true;
-	}
-
-	/**
-	 * Extract unique webhook ID for deduplication
-	 */
-	protected function extractWebhookId(array $payload): ?string
-	{
-		$headers = $payload['_headers'] ?? [];
-		return $headers['webhook_id'][0] ?? $headers['webhook-id'] ?? $payload['id'] ?? null;
-	}
-
-	/**
-	 * Override the webhook request handler to capture raw body for signature verification
-	 */
-	public function handleWebhookRequest(\WP_REST_Request $request): \WP_REST_Response
-	{
-		$payload = $request->get_params();
-		$payload['_headers']  = $request->get_headers();
-		$payload['_raw_body'] = $request->get_body();
-
-		$success = $this->handleWebhook($payload);
-
-		return new \WP_REST_Response([
-			'success' => $success,
-		], $success ? 200 : 400);
-	}
-	/***********************************************************************
-	 * POST HOOKS
-	***********************************************************************/
-	/**
-	 * Sync a WordPress post to Helcim as a product.
-	 * Called by IntegrationExecutor::processSyncTo()
-	 */
-	public function syncPostToService(int $postID): array|\WP_Error
-	{
-		$post = get_post($postID);
-		if (!$post) {
-			return new \WP_Error('not_found', "Post {$postID} not found");
-		}
-
-		$helcimProductId = get_post_meta($postID, BASE . '_helcim_item_id', true);
-		$productData     = $this->buildProductPayload($postID);
-
-		if (is_wp_error($productData)) {
-			return $productData;
-		}
-
-		if ($helcimProductId) {
-			// Update existing
-			$response = $this->patchRequest("products/{$helcimProductId}", $productData);
-		} else {
-			// Create new
-			$response = $this->postRequest('products', $productData);
-		}
-
-		if (is_wp_error($response)) {
-			update_post_meta($postID, BASE . '_helcim_sync_status', 'error');
-			return $response;
-		}
-
-		// Store Helcim product ID
-		$newId = $response['id'] ?? $response['productId'] ?? $helcimProductId;
-		update_post_meta($postID, BASE . '_helcim_item_id', $newId);
-		update_post_meta($postID, BASE . '_helcim_sync_status', 'synced');
-		update_post_meta($postID, BASE . '_helcim_last_sync', current_time('mysql'));
-
-		return ['success' => true, 'helcim_id' => $newId];
-	}
-
-	/**
-	 * Delete a product from Helcim.
-	 * Called by IntegrationExecutor::processDeleteFrom()
-	 */
-	public function deleteFromService(string $externalId): array|\WP_Error
-	{
-		$response = $this->deleteRequest("products/{$externalId}");
-
-		if (is_wp_error($response)) {
-			return $response;
-		}
-
-		return ['success' => true, 'deleted' => $externalId];
-	}
-
-	/**
-	 * Build Helcim product payload from a WordPress post.
-	 */
-	protected function buildProductPayload(int $postID): array|\WP_Error
-	{
-		$meta = \JVBase\meta\Meta::forPost($postID);
-		$post = get_post($postID);
-
-		$price = $meta->get('price');
-		if (empty($price) || !is_numeric($price)) {
-			return new \WP_Error('invalid_price', "Post {$postID} has no valid price");
-		}
-
-		return [
-			'name'        => $post->post_title,
-			'description' => wp_strip_all_tags($post->post_content),
-			'sku'         => $meta->get('sku') ?: "wp-{$postID}",
-			'price'       => (float) $price,
-			'taxExempt'   => (bool) $meta->get('tax_exempt'),
-		];
+		// TODO: Implement initialize() method.
 	}
 }

--
Gitblit v1.10.0