From df6c00db050e188a6bd5707e72c4f1f331ced923 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 08 Feb 2026 20:46:43 +0000
Subject: [PATCH] =Port over to jakevan 2

---
 inc/integrations/Helcim.php | 2634 ++++++++++++++++++++++++++---------------------------------
 1 files changed, 1,173 insertions(+), 1,461 deletions(-)

diff --git a/inc/integrations/Helcim.php b/inc/integrations/Helcim.php
index a70ac16..d9e4e97 100644
--- a/inc/integrations/Helcim.php
+++ b/inc/integrations/Helcim.php
@@ -1,1633 +1,1345 @@
 <?php
 namespace JVBase\integrations;
 
-use JVBase\meta\Meta;
 use Exception;
 use WP_Error;
-use WP_REST_Request;
-use WP_REST_Response;
+use WP_Post;
+use JVBase\ui\Checkout;
+use JVBase\managers\queue\TypeConfig;
+use JVBase\managers\queue\executors\IntegrationExecutor;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
 
 /**
- * Helcim Integration for JVBase
- * Handles bidirectional sync, customer management, and order processing
+ * Helcim Integration Class
+ *
+ * Handles HelcimPay.js checkout, invoice retrieval, customer/card management,
+ * and bidirectional product sync via API token authentication.
+ *
+ * Helcim is the source of truth for invoices and orders.
+ * Products sync bidirectionally through the standard integration field flow.
+ *
+ * @since 1.0.0
  */
 class Helcim extends Integrations
 {
-	// Helcim-specific configuration
-	private string $api_token;
-	private string $account_id;
-	private string $terminal_id;
-	private string $webhook_secret;
+	protected string $service_name = 'helcim';
+	protected string|array $apiBase = 'https://api.helcim.com/v2';
 
-	// Field mapping cache
-	private array $field_mappings = [];
-	private array $category_cache = [];
+	protected bool $isOAuthService = false;
 
-	// Order processing
-	private bool $is_test_mode = false;
-	private array $payment_form_settings = [];
-
-	// User security
-	private const PASSWORD_RESET_INTERVAL = 3; // Reset password every 3 logins
+	/**
+	 * 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
+	];
 
 	public function __construct(?int $userID = null)
 	{
-		$this->service_name = 'helcim';
 		$this->title = 'Helcim';
-		$this->icon = 'currency-circle-dollar';
+		$this->icon  = 'credit-card';
 
-		// Helcim API endpoints
-		$this->apiBase = [
-			'production' => 'https://api.helcim.com/v2',
-			'sandbox' => 'https://api-sandbox.helcim.com/v2'
+		$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->apiEndpoints = [
-			'commerce/invoice',
-			'commerce/transaction',
-			'commerce/customer',
-			'commerce/card-batch',
-			'commerce/terminal',
-			'commerce/product',
-			'commerce/order',
-			'payment/purchase',
-			'payment/verify',
-			'payment/capture',
-			'payment/refund',
-			'customer/create',
-			'customer/update',
-			'customer/get',
-			'inventory/product',
-			'inventory/batch'
+		$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' => true
+			'delete' => false,
 		];
 
-		$this->fields = [
-			'test_mode' => [
-				'type'	=> 'select',
-				'label'	=> 'Environment',
-				'options'	=> [
-					'1'	=> 'Test Mode',
-					'0'	=> 'Production'
-				],
-				'default'	=> '1'
-			],
-			'api_token'	=> [
-				'type'	=> 'text',
-				'subtype'	=> 'password',
-				'required'	=> true,
-				'hint'	=> 'Your Helcim API Token'
-			],
-			'account_id'	=> [
-				'type'	=> 'text',
-				'required'	=> true,
-				'label'	=> 'Account ID',
-				'hint'	=> 'Your Helcim Account ID',
-			],
-			'webhook_secret'	=> [
-				'type'	=> 'text',
-				'subtype'	=> 'password',
-				'label'	=> 'Webhook Secret',
-				'hint'	=> 'For webhook verification',
-				'required'	=> true,
-			]
-		];
-
-		$this->advanced = [
-
-		];
-
-		$this->instructions = [
-			'Once you are set up, add this URL to your Helcim webhook settings: <code>'.esc_html(rest_url('jvb/v1/webhooks/helcim')).'</code>'
-		];
-
-		$this->defaults = [
-
-		];
-
-		$this->handleWebhooks = true;
+		$this->handleWebhooks = false;
 
 		parent::__construct($userID);
 
-		$this->actions = array_merge(
-			$this->actions,
-			[
-				'import_from_helcim'	=> 'handleImportFromHelcim',
-				'sync_to_helcim'		=> 'handleSyncToHelcim'
-			]
-		);
-		// Initialize field mappings
-		$this->initializeFieldMappings();
+		// 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',
+		]);
 	}
 
-	/**
-	 * Initialize service-specific settings
-	 */
+	/*****************************************************************
+	 * ABSTRACT IMPLEMENTATIONS
+	 *****************************************************************/
+
 	protected function initialize(): void
 	{
-		$this->api_token = $this->credentials['api_token'] ?? '';
-		$this->account_id = $this->credentials['account_id'] ?? '';
-		$this->terminal_id = $this->credentials['terminal_id'] ?? '';
-		$this->webhook_secret = $this->credentials['webhook_secret'] ?? '';
-		$this->is_test_mode = (bool)($this->credentials['test_mode'] ?? false);
+		if (empty($this->credentials)) {
+			$this->loadCredentials();
+		}
 
-		// Set the appropriate API base
-		$this->apiBase = $this->is_test_mode ? $this->apiBase['sandbox'] : $this->apiBase['production'];
-
-		// Load payment form settings
-		$this->payment_form_settings = [
-			'card' => $this->credentials['enable_card_payments'] ?? true,
-			'ach' => $this->credentials['enable_ach_payments'] ?? false,
-			'apple_pay' => $this->credentials['enable_apple_pay'] ?? false,
-			'google_pay' => $this->credentials['enable_google_pay'] ?? false,
+		$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);
+		}
+	}
+
 	/**
-	 * Register additional WordPress hooks
+	 * Get Helcim product meta fields by type.
+	 *
+	 * Used by FieldRegistry when 'use_helcim' => true is set
+	 * in a JVB_CONTENT definition.
 	 */
+	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
 	{
-		$this->ensureInitialized();
 		if (!$this->isSetUp()) {
 			return;
 		}
-		// User login tracking for security
-		add_action('wp_login', [$this, 'trackUserLogin'], 10, 2);
 
-		add_action('wp_footer', [$this, 'outputCheckout']);
-
-		// Enqueue checkout scripts
 		add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
 
-		// REST API endpoints for checkout
-		add_action('rest_api_init', [$this, 'registerRestRoutes']);
-	}
+		// Shared checkout UI (replaces provider-specific outputCheckout)
+		add_filter('jvbAdditionalActions', [Checkout::class, 'render']);
 
-	/**
-	 * Register REST API routes
-	 */
-	public function registerRestRoutes(): void
-	{
-		register_rest_route('jvb/v1', '/helcim/checkout', [
-			'methods' => 'POST',
-			'callback' => [$this, 'handleCheckout'],
-			'permission_callback' => '__return_true'
-		]);
-
-		register_rest_route('jvb/v1', '/helcim/customer', [
-			'methods' => 'POST',
-			'callback' => [$this, 'handleCustomerLookup'],
-			'permission_callback' => '__return_true'
-		]);
-
-		register_rest_route('jvb/v1', '/helcim/order-status/(?P<order_id>[a-zA-Z0-9-]+)', [
-			'methods' => 'GET',
-			'callback' => [$this, 'handleOrderStatus'],
-			'permission_callback' => '__return_true'
-		]);
-
-		register_rest_route('jvb/v1', '/helcim/create-account', [
-			'methods' => 'POST',
-			'callback' => [$this, 'handleAccountCreation'],
-			'permission_callback' => '__return_true'
-		]);
-	}
-
-	/**
-	 * Initialize field mappings for all content types
-	 */
-	private function initializeFieldMappings(): void
-	{
-		foreach (JVB_CONTENT as $key => $config) {
-			if (isset($config['integrations']['helcim'])) {
-				$post_type = jvbCheckBase($key);
-				$this->field_mappings[$post_type] = $this->getFieldMapping($post_type);
+		// 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 queue operation types with IntegrationExecutor
+		$this->registerQueueTypes();
+
+		// Register webhook endpoint (handled by parent)
+		$this->registerWebhookEndpoint();
 	}
 
-	/**
-	 * Get field mapping for a post type
-	 */
-	public function getFieldMapping(string $post_type): array
-	{
-		return apply_filters(BASE . '_helcim_field_mapping', [
-			'name' => 'title',
-			'description' => 'content',
-			'price' => 'price',
-			'sku' => '_helcim_sku',
-			'product_code' => '_helcim_product_code',
-			'inventory' => '_helcim_inventory',
-			'product_type' => 'product_type',
-			'tax_exempt' => 'tax_exempt',
-			'shipping_required' => 'shipping_required'
-		], $post_type);
-	}
-
-	/**
-	 * Output checkout form
-	 */
-	public function outputCheckout(): void
-	{
-		if (is_singular(BASE.'dash') || is_post_type_archive(BASE.'dash')) {
-			return;
-		}
-		?>
-		<button type="button" class="toggle-cart row" title="Your Cart" data-action="toggle-cart" aria-label="Open Cart" aria-controls="checkout" aria-expanded="false" hidden>
-			<?= jvbIcon('shopping-cart')?><span class="abs"></span><span class="abs count"></span>
-		</button>
-		<aside id="cart" class="main">
-			<form id="checkout" data-form-id="checkout" data-save="checkout">
-				<?php
-				$tabs = [
-					'cartItems' => [
-						'title'	=> 'Your Order',
-						'icon'	=> 'cart',
-						'description' => 'Review and modify your order items',
-						'content'	=> $this->cartContent()
-					],
-					'account' => [
-						'title' => 'Account',
-						'icon' => 'user',
-						'description' => $this->getAccountTabDescription(),
-						'content' => $this->renderAccountSection()
-					],
-					'checkout'	=> [
-						'title'	=> 'Checkout',
-						'icon'	=> 'checkout',
-						'description' => 'Complete your order with Helcim secure payments',
-						'content'	=> $this->renderCheckoutSection()
-					],
-					'order'	=> [
-						'title'	=> 'Order Status',
-						'icon' => 'truck',
-						'hidden'	=> true,
-						'description' => 'Track your order status',
-						'content'	=> $this->renderOrderStatus()
-					]
-				];
-				jvbRenderTabs($tabs);
-				?>
-				<div class="cart-total row end">
-					<p class="tax">Tax: <span></span></p>
-					<p class="total">GRAND TOTAL: <span></span></p>
-				</div>
-			</form>
-		</aside>
-		<?php
-		$this->outputCheckoutTemplates();
-	}
-
-	/**
-	 * Get account tab description based on login status
-	 */
-	private function getAccountTabDescription(): string
-	{
-		if (is_user_logged_in()) {
-			return 'Manage your account and view order history';
-		}
-		return 'Login or create an account for faster checkout';
-	}
-
-	/**
-	 * Render account section
-	 */
-	private function renderAccountSection(): string
-	{
-		ob_start();
-		?>
-		<div class="account-section">
-			<?php if (is_user_logged_in()): ?>
-				<?php $this->renderLoggedInAccount(); ?>
-			<?php else: ?>
-				<?php $this->renderGuestAccount(); ?>
-			<?php endif; ?>
-		</div>
-		<?php
-		return ob_get_clean();
-	}
-
-	/**
-	 * Render logged in account view
-	 */
-	private function renderLoggedInAccount(): void
-	{
-		$user = wp_get_current_user();
-		$customer_id = get_user_meta($user->ID, BASE . '_helcim_customer_id', true);
-		?>
-		<div class="logged-in-account">
-			<p>Welcome back, <?= esc_html($user->display_name) ?>!</p>
-
-			<div class="account-actions">
-				<button type="button" class="button" onclick="helcimCheckout.loadSavedCards()">
-					<?= jvbIcon('credit-card') ?> Saved Cards
-				</button>
-				<button type="button" class="button" onclick="helcimCheckout.loadOrderHistory()">
-					<?= jvbIcon('receipt') ?> Order History
-				</button>
-				<button type="button" class="button" onclick="helcimCheckout.loadFavorites()">
-					<?= jvbIcon('heart') ?> Favorites
-				</button>
-			</div>
-
-			<div id="account-content"></div>
-		</div>
-		<?php
-	}
-
-	/**
-	 * Render guest account view
-	 */
-	private function renderGuestAccount(): void
-	{
-		?>
-		<div class="guest-account">
-			<div class="login-section">
-				<h3>Returning Customer?</h3>
-				<p>Login with your email to access saved cards and order history</p>
-
-				<div class="email-login">
-					<input type="email"
-						   id="login-email"
-						   placeholder="Enter your email"
-						   autocomplete="email">
-					<button type="button"
-							class="button primary"
-							onclick="helcimCheckout.loginWithEmail()">
-						Continue
-					</button>
-				</div>
-
-				<div id="login-status"></div>
-			</div>
-
-			<div class="guest-checkout">
-				<h3>New Customer?</h3>
-				<p>You can checkout as a guest or create an account after your order</p>
-
-				<label>
-					<input type="checkbox" id="create-account-offer">
-					Offer to create account after checkout
-				</label>
-			</div>
-		</div>
-		<?php
-	}
-
-	/**
-	 * Render checkout section
-	 */
-	private function renderCheckoutSection(): string
-	{
-		ob_start();
-		?>
-		<div class="checkout-section">
-			<h3>Customer Information</h3>
-
-			<input type="text" name="name" placeholder="Full Name" required autocomplete="name">
-			<input type="email" name="email" placeholder="Email" required autocomplete="email">
-			<input type="tel" name="phone" placeholder="Phone" required autocomplete="tel">
-
-			<h3>Pickup/Delivery Details</h3>
-			<select name="fulfillment_type" required>
-				<option value="pickup">Pickup</option>
-				<option value="delivery">Delivery</option>
-			</select>
-
-			<div class="pickup-details" data-show-if="fulfillment_type:pickup">
-				<input type="datetime-local" name="pickup_time" required>
-			</div>
-
-			<div class="delivery-details" data-show-if="fulfillment_type:delivery" style="display:none;">
-				<input type="text" name="delivery_address" placeholder="Delivery Address" autocomplete="street-address">
-				<input type="text" name="delivery_instructions" placeholder="Delivery Instructions">
-			</div>
-
-			<textarea name="special_instructions" placeholder="Special instructions or dietary notes"></textarea>
-
-			<h3>Payment Information</h3>
-			<div id="saved-cards"></div>
-			<div id="helcim-card-container"></div>
-
-			<button type="submit" class="button primary checkout-button">
-				Place Order
-			</button>
-		</div>
-		<?php
-		return ob_get_clean();
-	}
-
-	/**
-	 * Render order status section
-	 */
-	protected function renderOrderStatus(): string
-	{
-		ob_start();
-		?>
-		<div class="order-confirmation">
-			<h2>Order Confirmed!</h2>
-			<div id="order-status" data-order="">
-				<p>Order #<span class="order-num"></span></p>
-				<div class="status-timeline">
-					<div class="status-item active" data-status="received">Order Received</div>
-					<div class="status-item" data-status="preparing">Preparing</div>
-					<div class="status-item" data-status="ready">Ready</div>
-					<div class="status-item" data-status="complete">Complete</div>
-				</div>
-				<div class="order-eta">
-					Estimated time: <span id="eta">Calculating...</span>
-				</div>
-			</div>
-		</div>
-		<?php
-		return ob_get_clean();
-	}
-
-	/**
-	 * Output checkout templates
-	 */
-	private function outputCheckoutTemplates(): void
-	{
-		?>
-		<template class="cartItem">
-			<tr class="item">
-				<td class="item">
-					<label for="quantity"></label>
-					<div class="quantity field" data-min="0" data-max="50" data-step="1" data-price="" data-id="">
-						<button type="button" class="decrease" aria-label="Decrease quantity">
-							<?= jvbIcon('minus-square') ?>
-						</button>
-						<input type="number" name="quantity" value="1" min="0" max="50">
-						<button type="button" class="increase" aria-label="Increase quantity">
-							<?= jvbIcon('plus') ?>
-						</button>
-					</div>
-				</td>
-				<td class="price"></td>
-				<td class="total"></td>
-				<td>
-					<button type="button" class="remove" aria-label="Remove item">
-						<?= jvbIcon('trash') ?>
-					</button>
-				</td>
-			</tr>
-		</template>
-
-		<template class="savedCard">
-			<label class="saved-card-option">
-				<input type="radio" name="payment_method" value="">
-				<span class="card-details">
-					<span class="card-brand"></span>
-					•••• <span class="last-4"></span>
-					<span class="exp-date"></span>
-				</span>
-			</label>
-		</template>
-		<?php
-	}
-
-	/**
-	 * Cart content section
-	 */
-	private function cartContent(): string
-	{
-		ob_start();
-		?>
-		<div class="cart-items">
-			<table>
-				<thead>
-				<tr>
-					<th>Item</th>
-					<th>Price</th>
-					<th>Total</th>
-					<th></th>
-				</tr>
-				</thead>
-				<tbody></tbody>
-			</table>
-
-			<div class="cart-actions">
-				<button type="button" class="button" onclick="helcimCheckout.clearCart()">
-					<?= jvbIcon('trash') ?> Clear Cart
-				</button>
-			</div>
-		</div>
-		<?php
-		return ob_get_clean();
-	}
-
-	/**
-	 * Enqueue checkout scripts
-	 */
 	public function enqueueScripts(): void
 	{
-		$this->ensureInitialized();
-		if (!$this->isSetUp()) {
-			return;
-		}
-		// Helcim JS SDK
-		$sdk_url = $this->is_test_mode
-			? 'https://helcim-js-sandbox.helcim.com/v1/helcim.js'
-			: 'https://js.helcim.com/v1/helcim.js';
-
+		// HelcimPay.js SDK
 		wp_enqueue_script(
-			'helcim-js-sdk',
-			$sdk_url,
+			'helcim-pay-sdk',
+			'https://secure.helcim.app/helcim-pay/services/start.js',
 			[],
 			null,
-			[
-				'strategy' => 'defer',
-				'in_footer' => true
-			]
+			true
 		);
 
-		// Register custom checkout script
+		// 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-utility',
-				'jvb-queue',
-				'jvb-a11y',
-				'jvb-cache',
-				'jvb-tabs',
-				'jvb-modal',
-			],
-			'1.0.0',
-			[
-				'strategy' => 'defer',
-				'in_footer' => true
-			]
+			['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');
-
-		// Localize the checkout script with Helcim config
-		wp_localize_script(
-			'jvb-helcim-checkout',
-			'helcimConfig',
-			[
-				'isOpen' => jvbIsOpen(),
-				'apiUrl' => rest_url('jvb/v1/helcim/'),
-				'nonce' => wp_create_nonce('wp_rest'),
-				'accountId' => $this->account_id,
-				'testMode' => $this->is_test_mode
-			]
-		);
 	}
 
-	/******************************************************************
-	 * POST SYNC METHODS
-	 ******************************************************************/
+
+	protected function registerQueueTypes(): void
+	{
+		$queue    = JVB()->queue();
+		$executor = new IntegrationExecutor();
+
+		$queue->registry()->register('helcim_sync_to', new TypeConfig(
+			executor:  $executor,
+			chunkKey:  'items',
+			chunkSize: 10,
+			maxRetries: 3
+		));
+
+		$queue->registry()->register('helcim_sync_from', new TypeConfig(
+			executor:  $executor,
+			chunkKey:  'items',
+			chunkSize: 10,
+			maxRetries: 3
+		));
+
+		$queue->registry()->register('helcim_delete_from', new TypeConfig(
+			executor:  $executor,
+			chunkKey:  'external_ids',
+			chunkSize: 20,
+			maxRetries: 2
+		));
+
+		$queue->registry()->register('helcim_import', new TypeConfig(
+			executor:   $executor,
+			maxRetries: 3
+		));
+
+		$queue->registry()->register('helcim_sync_customer', new TypeConfig(
+			executor:   $executor,
+			maxRetries: 2
+		));
+	}
 
 	/**
-	 * Handle post save for Helcim sync
+	 * 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
 	{
-		// Queue the sync operation
-		$this->queueOperation('sync_to_helcim', [
-			'items' => [$postID],
+		$fields = $this->getSyncFields($postID, 'post', ['share_to_helcim', 'schedule_helcim']);
+
+		if (empty($fields['share_to_helcim'])) {
+			return;
+		}
+
+		// Uses IntegrationExecutor via TypeRegistry instead of FilteredExecutor
+		$this->queueOperation('sync_to', [
+			'items'   => [$postID],
 			'user_id' => $this->userID,
-			'content_type' => $settings['content_type'] ?? 'REGULAR'
 		], [
 			'priority' => 'high',
-			'delay' => 30, // Small delay to batch multiple saves
+			'delay'    => 30,
 		]);
 
 		update_post_meta($postID, BASE . '_helcim_sync_status', 'queued');
 	}
 
-	/**
-	 * Handle post deletion
-	 */
-	public function handleDeletePost(int $postID): void
+	protected function handleImportFromHelcim(): array
 	{
-		$helcim_id = get_post_meta($postID, BASE . '_helcim_product_id', true);
+		$this->queueOperation('import_products', [
+			'user_id' => $this->userID,
+		], ['priority' => 'normal']);
 
-		if ($helcim_id) {
-			$this->queueOperation('delete_from_helcim', [
-				'helcim_ids' => [$helcim_id],
-				'post_id' => $postID
-			], [
-				'priority' => 'high'
-			]);
-		}
+		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;
 	}
 
 	/**
-	 * Process queued operations
+	 * Resolve customer ID from user meta, falling back to email lookup + auto-link.
 	 */
-	public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
+	public function resolveCustomerId(int $userId): ?int
 	{
-		$base = strtolower($this->service_name).'_';
-		$helcim = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this;
-
-		switch ($operation->type) {
-			case $base.'sync_to_helcim':
-				return $helcim->processSyncToHelcim($data);
-
-			case $base.'delete_from_helcim':
-				return $helcim->processDeleteFromHelcim($data);
-
-			case $base.'import_catalog':
-				return $helcim->processImportCatalog($data);
-
-			case $base.'sync_customer':
-				return $helcim->processSyncCustomer($data);
-
-			default:
-				return $result;
-		}
-	}
-
-	/**
-	 * Process sync to Helcim
-	 */
-	private function processSyncToHelcim(array $data): array
-	{
-		$items = $data['items'] ?? [];
-		$content_type = $data['content_type'] ?? 'REGULAR';
-		$success_count = 0;
-		$errors = [];
-
-		foreach ($items as $post_id) {
-			try {
-				$post = get_post($post_id);
-				if (!$post) continue;
-
-				$meta = Meta::forPost($post_id);
-				$field_map = $this->field_mappings[$post->post_type] ?? [];
-
-				// Prepare product data for Helcim
-				$product_data = [
-					'name' => $post->post_title,
-					'description' => $post->post_content,
-					'productCode' => get_post_meta($post_id, BASE . '_helcim_product_code', true) ?: 'WP-' . $post_id,
-					'type' => $content_type,
-					'price' => floatval($meta->get('price')) * 100, // Convert to cents
-					'taxable' => (bool)$meta->get('is_taxable'),
-				];
-
-				// Handle variations
-				$variations = $meta->get('product_variations');
-				if (!empty($variations)) {
-					$product_data['variations'] = $this->prepareVariations($variations);
-				}
-
-				// Check if product exists
-				$helcim_id = get_post_meta($post_id, BASE . '_helcim_product_id', true);
-
-				if ($helcim_id) {
-					// Update existing product
-					$response = $this->putRequest('inventory/product/' . $helcim_id, $product_data);
-				} else {
-					// Create new product
-					$response = $this->postRequest('inventory/product', $product_data);
-
-					if (!is_wp_error($response) && isset($response['productId'])) {
-						update_post_meta($post_id, BASE . '_helcim_product_id', $response['productId']);
-						$helcim_id = $response['productId'];
-					}
-				}
-
-				if (!is_wp_error($response)) {
-					update_post_meta($post_id, BASE . '_helcim_sync_status', 'success');
-					update_post_meta($post_id, BASE . '_helcim_last_sync', current_time('mysql'));
-					$success_count++;
-				} else {
-					throw new Exception($response->get_error_message());
-				}
-
-			} catch (Exception $e) {
-				$errors[] = "Post $post_id: " . $e->getMessage();
-				update_post_meta($post_id, BASE . '_helcim_sync_status', 'failed');
-				update_post_meta($post_id, BASE . '_helcim_sync_error', $e->getMessage());
-			}
+		$id = $this->getUserCustomerId($userId);
+		if ($id) {
+			return $id;
 		}
 
-		return [
-			'success' => count($errors) === 0,
-			'result' => [
-				'synced' => $success_count,
-				'errors' => $errors
-			]
-		];
-	}
-
-	/**
-	 * Prepare variations for Helcim
-	 */
-	private function prepareVariations(array $variations): array
-	{
-		$helcim_variations = [];
-
-		foreach ($variations as $index => $variation) {
-			$helcim_variations[] = [
-				'name' => $variation['name'] ?? '',
-				'price' => floatval($variation['price'] ?? 0) * 100,
-				'sku' => $variation['sku'] ?? '',
-				'inventory' => intval($variation['inventory'] ?? 0),
-			];
-		}
-
-		return $helcim_variations;
-	}
-
-	/**
-	 * Process delete from Helcim
-	 */
-	private function processDeleteFromHelcim(array $data): array
-	{
-		$helcim_ids = $data['helcim_ids'] ?? [];
-		$success_count = 0;
-
-		foreach ($helcim_ids as $helcim_id) {
-			$response = $this->deleteRequest('inventory/product/' . $helcim_id);
-
-			if (!is_wp_error($response)) {
-				$success_count++;
-			}
-		}
-
-		return [
-			'success' => $success_count > 0,
-			'result' => ['deleted' => $success_count]
-		];
-	}
-
-	/**
-	 * Process import from Helcim catalog
-	 */
-	private function processImportCatalog(array $data): array
-	{
-		$page = 1;
-		$imported = 0;
-
-		do {
-			$response = $this->getRequest('inventory/product', [
-				'page' => $page,
-				'limit' => 100
-			]);
-
-			if (is_wp_error($response)) {
-				break;
-			}
-
-			$products = $response['products'] ?? [];
-
-			foreach ($products as $product) {
-				$this->importHelcimProduct($product);
-				$imported++;
-			}
-
-			$page++;
-			$has_more = count($products) === 100;
-
-		} while ($has_more);
-
-		return [
-			'success' => true,
-			'result' => ['imported' => $imported]
-		];
-	}
-
-	/**
-	 * Import a single Helcim product
-	 */
-	private function importHelcimProduct(array $product): void
-	{
-		// Find existing post by Helcim ID
-		$args = [
-			'post_type' => $this->syncPostTypes,
-			'meta_key' => BASE . '_helcim_product_id',
-			'meta_value' => $product['productId'],
-			'posts_per_page' => 1
-		];
-
-		$existing = get_posts($args);
-
-		if ($existing) {
-			$post_id = $existing[0]->ID;
-
-			// Update existing post
-			wp_update_post([
-				'ID' => $post_id,
-				'post_title' => $product['name'],
-				'post_content' => $product['description'] ?? ''
-			]);
-		} else {
-			// Create new post
-			$post_id = wp_insert_post([
-				'post_title' => $product['name'],
-				'post_content' => $product['description'] ?? '',
-				'post_type' => $this->syncPostTypes[0] ?? 'post',
-				'post_status' => 'publish'
-			]);
-		}
-
-		if ($post_id) {
-			// Update meta data
-			$meta = Meta::forPost($post_id);
-			$meta->setAll([
-				'price' => $product['price'] / 100, // Convert from cents
-				'_helcim_product_id' => $product['productId'],
-				'_helcim_product_code' => $product['productCode'],
-				'_helcim_last_sync' => current_time('mysql')
-			]);
-		}
-	}
-
-	/******************************************************************
-	 * CUSTOMER MANAGEMENT
-	 ******************************************************************/
-
-	/**
-	 * Track user login for security
-	 */
-	public function trackUserLogin(string $user_login, \WP_User $user): void
-	{
-		// Check if user has Helcim integration
-		$user_roles = $user->roles;
-
-		foreach ($user_roles as $role) {
-			$role_key = jvbNoBase($role);
-			if (isset(JVB_USER[$role_key]['integrations']['helcim']['is_customer'])) {
-				$login_count = (int)get_user_meta($user->ID, BASE . '_helcim_login_count', true);
-				$login_count++;
-
-				update_user_meta($user->ID, BASE . '_helcim_login_count', $login_count);
-				update_user_meta($user->ID, BASE . '_helcim_last_login', current_time('mysql'));
-
-				// Check if password reset is needed
-				if ($login_count % self::PASSWORD_RESET_INTERVAL === 0) {
-					$this->schedulePasswordReset($user->ID);
-				}
-
-				break;
-			}
-		}
-	}
-
-	/**
-	 * Schedule password reset for security
-	 */
-	private function schedulePasswordReset(int $user_id): void
-	{
-		update_user_meta($user_id, BASE . '_helcim_password_reset_required', true);
-
-		// Send notification
-		$user = get_user_by('ID', $user_id);
-		if ($user) {
-			JVB()->email()->sendEmail(
-				$user->user_email,
-				'Security: Password Reset Required',
-				'For your security, please reset your password to continue accessing your account and saved payment methods.',
-			);
-		}
-	}
-
-	/**
-	 * Handle customer lookup
-	 */
-	public function handleCustomerLookup(WP_REST_Request $request): WP_REST_Response
-	{
-		$email = sanitize_email($request->get_param('email'));
-
-		if (!$email) {
-			return new WP_REST_Response(['error' => 'Email required'], 400);
-		}
-
-		// Check WordPress user first
-		$user = get_user_by('email', $email);
-
-		if ($user) {
-			// Check if user has customer role
-			$has_customer_role = false;
-			foreach ($user->roles as $role) {
-				$role_key = jvbNoBase($role);
-				if (isset(JVB_USER[$role_key]['integrations']['helcim']['is_customer'])) {
-					$has_customer_role = true;
-					break;
-				}
-			}
-
-			if ($has_customer_role) {
-				// Get saved cards and order history
-				$customer_id = get_user_meta($user->ID, BASE . '_helcim_customer_id', true);
-
-				if ($customer_id) {
-					$customer_data = $this->getHelcimCustomer($customer_id);
-
-					return new WP_REST_Response([
-						'exists' => true,
-						'has_account' => true,
-						'customer' => [
-							'name' => $user->display_name,
-							'email' => $user->user_email
-						],
-						'cards' => $customer_data['cards'] ?? [],
-						'orders' => $this->getUserOrders($user->ID)
-					]);
-				}
-			}
-
-			return new WP_REST_Response([
-				'exists' => true,
-				'has_account' => true,
-				'no_customer_role' => true,
-				'message' => 'Account exists but not set up for orders. Would you like to enable ordering?'
-			]);
-		}
-
-		// Check Helcim for customer
-		$helcim_customer = $this->searchHelcimCustomer($email);
-
-		if ($helcim_customer) {
-			return new WP_REST_Response([
-				'exists' => true,
-				'has_account' => false,
-				'helcim_only' => true,
-				'message' => 'Found your previous orders. Create an account to access them?'
-			]);
-		}
-
-		return new WP_REST_Response([
-			'exists' => false,
-			'message' => 'New customer'
-		]);
-	}
-
-	/**
-	 * Get Helcim customer data
-	 */
-	private function getHelcimCustomer(string $customer_id): array
-	{
-		$cached = $this->cache->get('helcim_customer_' . $customer_id);
-
-		if ($cached !== false) {
-			return $cached;
-		}
-
-		$response = $this->getRequest('customer/' . $customer_id);
-
-		if (is_wp_error($response)) {
-			return [];
-		}
-
-		// Get saved cards
-		$cards_response = $this->getRequest('customer/' . $customer_id . '/cards');
-		$cards = [];
-
-		if (!is_wp_error($cards_response) && isset($cards_response['cards'])) {
-			foreach ($cards_response['cards'] as $card) {
-				$cards[] = [
-					'id' => $card['cardToken'],
-					'last_4' => $card['cardLast4'],
-					'card_brand' => $card['cardBrand'],
-					'exp_month' => $card['expiryMonth'],
-					'exp_year' => $card['expiryYear']
-				];
-			}
-		}
-
-		$customer_data = [
-			'customer' => $response,
-			'cards' => $cards
-		];
-
-		$this->cache->set('helcim_customer_' . $customer_id, $customer_data, HOUR_IN_SECONDS);
-
-		return $customer_data;
-	}
-
-	/**
-	 * Search for Helcim customer by email
-	 */
-	private function searchHelcimCustomer(string $email): ?array
-	{
-		$response = $this->getRequest('customer/search', [
-			'email' => $email
-		]);
-
-		if (!is_wp_error($response) && isset($response['customers'][0])) {
-			return $response['customers'][0];
-		}
-
-		return null;
-	}
-
-	/**
-	 * Get user's order history
-	 */
-	private function getUserOrders(int $user_id): array
-	{
-		$orders = get_user_meta($user_id, BASE . '_helcim_orders', true) ?: [];
-
-		// Get last 10 orders
-		return array_slice($orders, -10);
-	}
-
-	/**
-	 * Handle account creation
-	 */
-	public function handleAccountCreation(WP_REST_Request $request): WP_REST_Response
-	{
-		$email = sanitize_email($request->get_param('email'));
-		$name = sanitize_text_field($request->get_param('name'));
-
-		if (!$email || !is_email($email)) {
-			return new WP_REST_Response(['error' => 'Valid email required'], 400);
-		}
-
-		// Check if user already exists
-		if (email_exists($email)) {
-			return new WP_REST_Response([
-				'success' => false,
-				'exists' => true,
-				'message' => 'An account with this email already exists. Please log in instead.'
-			], 409);
-		}
-
-		// Generate username from email
-		$username = sanitize_user(current(explode('@', $email)));
-		$username = $this->generateUniqueUsername($username);
-
-		// Create user account
-		$user_id = wp_create_user(
-			$username,
-			wp_generate_password(20, true, true), // Temporary password
-			$email
-		);
-
-		if (is_wp_error($user_id)) {
-			$this->logError('Failed to create customer account', [
-				'email' => $email,
-				'error' => $user_id->get_error_message()
-			]);
-			return new WP_REST_Response(['error' => 'Failed to create account'], 500);
-		}
-
-		// Set user role
-		$user = new \WP_User($user_id);
-		$user->set_role(BASE.'foodie'); // Or appropriate role from JVB_USER
-
-		// Update display name
-		if ($name) {
-			wp_update_user([
-				'ID' => $user_id,
-				'display_name' => $name
-			]);
-		}
-
-		// Generate password reset key
-		$reset_key = get_password_reset_key($user);
-
-		if (!is_wp_error($reset_key)) {
-			$this->sendWelcomeEmail($user, $reset_key);
-		}
-
-		// Link to Helcim customer if exists
-		$helcim_customer = $this->searchHelcimCustomer($email);
-
-		if ($helcim_customer) {
-			update_user_meta($user_id, BASE . '_helcim_customer_id', $helcim_customer['customerId']);
-		} else {
-			// Create new Helcim customer
-			$customer_response = $this->postRequest('customer', [
-				'customerCode' => 'WP-' . $user_id,
-				'contactName' => $name ?: $username,
-				'email' => $email
-			]);
-
-			if (!is_wp_error($customer_response) && isset($customer_response['customerId'])) {
-				update_user_meta($user_id, BASE . '_helcim_customer_id', $customer_response['customerId']);
-			}
-		}
-
-		return new WP_REST_Response([
-			'success' => true,
-			'message' => 'Account created! Check your email to set your password.',
-			'user_id' => $user_id
-		]);
-	}
-
-	/**
-	 * Generate unique username
-	 */
-	private function generateUniqueUsername(string $base): string
-	{
-		$username = $base;
-		$counter = 1;
-
-		while (username_exists($username)) {
-			$username = $base . $counter;
-			$counter++;
-		}
-
-		return $username;
-	}
-
-	/**
-	 * Send welcome email
-	 */
-	private function sendWelcomeEmail(\WP_User $user, string $reset_key): void
-	{
-		$site_name = get_bloginfo('name');
-		$reset_url = get_home_url(null, "login?action=rp&key=$reset_key&login=" . rawurlencode($user->user_login), 'login');
-
-		$message = sprintf(
-			"Welcome to %s!\n\n" .
-			"Your account has been created. Please click the button below to set your password:\n\n" .
-			"%s\n\n" .
-			"Or, copy and paste the link below:\n\n".
-			"%s\n\n" .
-			"Once you've set your password, you can:\n" .
-			"- View your order history\n" .
-			"- Save your favorite items\n" .
-			"- Speed up checkout with saved payment methods\n\n" .
-			"If you didn't create this account, please ignore this email.\n\n" .
-			"Thanks,\n",
-			$site_name,
-			JVB()->email()->button('Reset Password', $reset_url),
-			JVB()->email()->link($reset_url),
-		);
-
-		JVB()->email()->sendEmail(
-			$user->user_email,
-			sprintf('[%s] Welcome! Set Your Password', $site_name),
-			$message
-		);
-	}
-
-	/******************************************************************
-	 * ORDER PROCESSING
-	 ******************************************************************/
-
-	/**
-	 * Handle checkout
-	 */
-	public function handleCheckout(WP_REST_Request $request): WP_REST_Response
-	{
-		$cart_items = $request->get_param('items');
-		$customer_info = $request->get_param('customer');
-		$payment_token = $request->get_param('payment_token');
-
-		if (empty($cart_items) || empty($payment_token)) {
-			return new WP_REST_Response(['error' => 'Invalid order data'], 400);
-		}
-
-		// Calculate order total
-		$order_total = $this->calculateOrderTotal($cart_items);
-
-		// Create Helcim invoice
-		$invoice_response = $this->createHelcimInvoice($cart_items, $customer_info, $order_total);
-
-		if (is_wp_error($invoice_response)) {
-			return new WP_REST_Response(['error' => $invoice_response->get_error_message()], 500);
-		}
-
-		// Process payment
-		$payment_response = $this->processHelcimPayment($payment_token, $invoice_response['invoiceId'], $order_total);
-
-		if (is_wp_error($payment_response)) {
-			return new WP_REST_Response(['error' => $payment_response->get_error_message()], 500);
-		}
-
-		// Save order to user if logged in
-		if (is_user_logged_in()) {
-			$this->saveOrderToUser(get_current_user_id(), $invoice_response['invoiceId']);
-		}
-
-		return new WP_REST_Response([
-			'success' => true,
-			'order_id' => $invoice_response['invoiceId'],
-			'receipt_url' => $payment_response['receiptUrl'] ?? '',
-			'message' => 'Order placed successfully!'
-		]);
-	}
-
-	/**
-	 * Calculate order total
-	 */
-	private function calculateOrderTotal(array $cart_items): int
-	{
-		$total = 0;
-
-		foreach ($cart_items as $item) {
-			$post_id = intval($item['id'] ?? 0);
-			if (!$post_id) continue;
-
-			$meta = Meta::forPost($post_id);
-			$price = floatval($meta->get('price'));
-			$quantity = intval($item['quantity'] ?? 1);
-
-			$total += ($price * $quantity * 100); // Convert to cents
-		}
-
-		// Add tax
-		$tax_rate = floatval(get_option(BASE . 'helcim_tax_rate', 0.05));
-		$tax = intval($total * $tax_rate);
-
-		return $total + $tax;
-	}
-
-	/**
-	 * Create Helcim invoice
-	 */
-	private function createHelcimInvoice(array $cart_items, array $customer_info, int $total): array|WP_Error
-	{
-		$line_items = [];
-
-		foreach ($cart_items as $item) {
-			$post_id = intval($item['id'] ?? 0);
-			if (!$post_id) continue;
-
-			$post = get_post($post_id);
-			$meta = Meta::forPost($post_id);
-
-			$line_items[] = [
-				'description' => $post->post_title,
-				'quantity' => intval($item['quantity'] ?? 1),
-				'price' => floatval($meta->get('price')) * 100,
-				'productCode' => get_post_meta($post_id, BASE . '_helcim_product_code', true) ?: 'WP-' . $post_id
-			];
-		}
-
-		// Get or create customer
-		$customer_id = $this->getOrCreateHelcimCustomer($customer_info);
-
-		return $this->postRequest('commerce/invoice', [
-			'customerId' => $customer_id,
-			'invoiceNumber' => 'INV-' . time(),
-			'tipAmount' => 0,
-			'depositAmount' => 0,
-			'notes' => $customer_info['notes'] ?? '',
-			'lineItems' => $line_items
-		]);
-	}
-
-	/**
-	 * Process Helcim payment
-	 */
-	private function processHelcimPayment(string $payment_token, string $invoice_id, int $amount): array|WP_Error
-	{
-		return $this->postRequest('payment/purchase', [
-			'paymentToken' => $payment_token,
-			'amount' => $amount,
-			'currency' => 'CAD',
-			'invoiceId' => $invoice_id
-		]);
-	}
-
-	/**
-	 * Get or create Helcim customer
-	 */
-	private function getOrCreateHelcimCustomer(array $customer_info): ?string
-	{
-		if (empty($customer_info['email'])) {
+		$user = get_userdata($userId);
+		if (!$user || empty($user->user_email)) {
 			return null;
 		}
 
-		// Search for existing customer
-		$existing = $this->searchHelcimCustomer($customer_info['email']);
-
-		if ($existing) {
-			return $existing['customerId'];
+		$id = $this->getCustomerIdByEmail($user->user_email);
+		if ($id) {
+			$this->linkUserToCustomer($userId, $id);
 		}
 
-		// Create new customer
-		$response = $this->postRequest('customer', [
-			'customerCode' => 'GUEST-' . time(),
-			'contactName' => $customer_info['name'] ?? '',
-			'email' => $customer_info['email'],
-			'phone' => $customer_info['phone'] ?? ''
-		]);
-
-		if (!is_wp_error($response) && isset($response['customerId'])) {
-			return $response['customerId'];
-		}
-
-		return null;
+		return $id;
 	}
 
+	/*****************************************************************
+	 * VALIDATION
+	 *****************************************************************/
+
 	/**
-	 * Save order to user meta
+	 * 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.
 	 */
-	private function saveOrderToUser(int $user_id, string $order_id): void
+	public function validateTransaction(string $secretToken, array $transactionData): bool
 	{
-		$orders = get_user_meta($user_id, BASE . '_helcim_orders', true) ?: [];
-		$orders[] = [
-			'order_id' => $order_id,
-			'date' => current_time('mysql')
-		];
-
-		// Keep only last 50 orders
-		if (count($orders) > 50) {
-			$orders = array_slice($orders, -50);
-		}
-
-		update_user_meta($user_id, BASE . '_helcim_orders', $orders);
+		$hash = hash('sha256', $secretToken . json_encode($transactionData));
+		return hash_equals($hash, $transactionData['hash'] ?? '');
 	}
-
+	/*******************************************************************
+	 * WEBHOOKS
+	*******************************************************************/
 	/**
-	 * Handle order status
-	 */
-	public function handleOrderStatus(WP_REST_Request $request): WP_REST_Response
-	{
-		$order_id = $request->get_param('order_id');
-
-		if (!$order_id) {
-			return new WP_REST_Response(['error' => 'Order ID required'], 400);
-		}
-
-		// Check cache first
-		$cached_status = get_transient(BASE . 'helcim_order_' . $order_id);
-
-		if ($cached_status !== false) {
-			return new WP_REST_Response($cached_status);
-		}
-
-		// Fetch from Helcim
-		$response = $this->getRequest('commerce/invoice/' . $order_id);
-
-		if (is_wp_error($response)) {
-			return new WP_REST_Response(['error' => 'Could not fetch order status'], 500);
-		}
-
-		$status = [
-			'status' => $response['status'] ?? 'unknown',
-			'eta' => $response['estimatedTime'] ?? null,
-			'items' => $response['lineItems'] ?? []
-		];
-
-		// Cache for 1 minute
-		set_transient(BASE . 'helcim_order_' . $order_id, $status, MINUTE_IN_SECONDS);
-
-		return new WP_REST_Response($status);
-	}
-
-	/******************************************************************
-	 * WEBHOOK HANDLING
-	 ******************************************************************/
-
-	/**
-	 * Validate webhook signature
+	 * 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
 	{
-		$signature = $_SERVER['HTTP_HELCIM_SIGNATURE'] ?? '';
+		$headers = $payload['_headers'] ?? [];
 
-		if (!$signature || !$this->webhook_secret) {
+		$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;
 		}
 
-		$body = file_get_contents('php://input');
-		$expected = hash_hmac('sha256', $body, $this->webhook_secret);
+		// 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;
+		}
 
-		return hash_equals($expected, $signature);
+		$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 webhook event
+	 * 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
 	{
-		$event_type = $payload['eventType'] ?? '';
-		$data = $payload['data'] ?? [];
+		$type = $payload['type'] ?? '';
+		$id   = $payload['id']  ?? '';
 
-		switch ($event_type) {
-			case 'transaction.success':
-			case 'transaction.declined':
-				return $this->handleTransactionWebhook($data);
-
-			case 'invoice.paid':
-			case 'invoice.updated':
-				return $this->handleInvoiceWebhook($data);
-
-			case 'customer.created':
-			case 'customer.updated':
-				return $this->handleCustomerWebhook($data);
-
-			default:
-				$this->logDebug('Unhandled webhook type', ['type' => $event_type]);
-				return true;
-		}
-	}
-
-	/**
-	 * Handle transaction webhook
-	 */
-	private function handleTransactionWebhook(array $data): bool
-	{
-		$transaction_id = $data['transactionId'] ?? '';
-		$status = $data['status'] ?? '';
-
-		if (!$transaction_id) {
+		if (empty($type) || empty($id)) {
+			$this->logError('Webhook missing type or id', $payload, 'warning');
 			return false;
 		}
 
-		// Update cached transaction status
-		set_transient(BASE . 'helcim_transaction_' . $transaction_id, $status, HOUR_IN_SECONDS);
-
-		// Trigger action for other integrations
-		do_action(BASE . 'helcim_transaction_updated', $transaction_id, $status, $data);
-
-		return true;
+		return match ($type) {
+			'cardTransaction' => $this->handleTransactionWebhook($id),
+			'terminalCancel'  => $this->handleTerminalCancelWebhook($payload),
+			default           => $this->handleUnknownWebhook($type, $id),
+		};
 	}
 
 	/**
-	 * Handle invoice webhook
+	 * Handle a cardTransaction webhook — fetch full transaction, update records.
 	 */
-	private function handleInvoiceWebhook(array $data): bool
+	protected function handleTransactionWebhook(string $transactionId): bool
 	{
-		$invoice_id = $data['invoiceId'] ?? '';
-		$status = $data['status'] ?? '';
+		// Fetch full transaction from Helcim API
+		$transaction = $this->getRequest("card-transactions/{$transactionId}");
 
-		if (!$invoice_id) {
+		if (is_wp_error($transaction) || empty($transaction)) {
+			$this->logError('Failed to fetch transaction for webhook', [
+				'transaction_id' => $transactionId,
+			]);
 			return false;
 		}
 
-		// Update cached order status
-		set_transient(BASE . 'helcim_order_' . $invoice_id, $status, HOUR_IN_SECONDS);
+		$status = $transaction['status'] ?? '';
 
-		// Trigger action for other integrations
-		do_action(BASE . 'helcim_order_updated', $invoice_id, $status, $data);
+		// Fire action for other parts of the system to react
+		do_action('jvb_helcim_transaction', $transaction, $status);
 
-		return true;
-	}
-
-	/**
-	 * Handle customer webhook
-	 */
-	private function handleCustomerWebhook(array $data): bool
-	{
-		$customer_id = $data['customerId'] ?? '';
-		$email = $data['email'] ?? '';
-
-		if (!$customer_id || !$email) {
-			return false;
+		// 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);
 		}
 
-		// Find WordPress user with this Helcim customer ID
-		$users = get_users([
-			'meta_key' => BASE . '_helcim_customer_id',
-			'meta_value' => $customer_id,
-			'number' => 1
+		// Log for debugging
+		$this->logDebug('Transaction webhook processed', [
+			'transaction_id' => $transactionId,
+			'status'         => $status,
+			'amount'         => $transaction['amount'] ?? 0,
+			'type'           => $transaction['type'] ?? '',
 		]);
 
-		if (!empty($users)) {
-			$user = $users[0];
-			update_user_meta($user->ID, BASE . '_helcim_customer_updated', current_time('mysql'));
+		return true;
+	}
 
-			// Clear cached customer data
-			$this->cache->forget('helcim_customer_' . $user->ID);
-		}
+	/**
+	 * 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;
 	}
 
-	/******************************************************************
-	 * CONNECTION TESTING
-	 ******************************************************************/
+	/**
+	 * 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;
+	}
 
 	/**
-	 * Perform connection test
+	 * Extract unique webhook ID for deduplication
 	 */
-	protected function performConnectionTest(): bool
+	protected function extractWebhookId(array $payload): ?string
 	{
-		if (empty($this->api_token) || empty($this->account_id)) {
-			throw new Exception('Missing required credentials');
+		$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");
 		}
 
-		$response = $this->getRequest('account');
+		$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)) {
-			throw new Exception($response->get_error_message());
+			update_post_meta($postID, BASE . '_helcim_sync_status', 'error');
+			return $response;
 		}
 
-		return isset($response['accountId']);
+		// 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];
 	}
 
 	/**
-	 * Get request headers
+	 * Delete a product from Helcim.
+	 * Called by IntegrationExecutor::processDeleteFrom()
 	 */
-	protected function getRequestHeaders(): array
+	public function deleteFromService(string $externalId): array|\WP_Error
 	{
-		$headers = [
-			'Content-Type' => 'application/json',
-			'Accept' => 'application/json'
-		];
+		$response = $this->deleteRequest("products/{$externalId}");
 
-		// Add authorization header
-		if (!empty($this->api_token)) {
-			$headers['api-token'] = $this->api_token;
+		if (is_wp_error($response)) {
+			return $response;
 		}
 
-		return $headers;
+		return ['success' => true, 'deleted' => $externalId];
 	}
 
-	/******************************************************************
-	 * INTEGRATION ACTIONS
-	 ******************************************************************/
-
-	protected function handleImportFromHelcim()
-	{
-		$this->queueOperation('import_catalog', [
-			'user_id' => $this->userID
-		], [
-			'priority' => 'normal'
-		]);
-
-		return [
-			'success'	=> true,
-			'message'	=> 'Import synced'
-		];
-	}
-
-	protected function handleSyncToHelcim()
-	{
-		$post_types = array_map(function ($type) {
-			return jvbCheckBase($type);
-		}, $this->syncPostTypes);
-
-		// Get all posts to sync
-		$posts = get_posts([
-			'post_type' => $post_types,
-			'posts_per_page' => -1,
-			'post_status' => 'publish'
-		]);
-
-		$post_ids = wp_list_pluck($posts, 'ID');
-
-		// Queue sync operation
-		$this->queueOperation('sync_to_helcim', [
-			'items' => $post_ids,
-			'user_id' => $this->userID
-		], [
-			'priority' => 'normal'
-		]);
-
-		return [
-			'success' => true,
-			'message' => sprintf('Queued %d items for sync to Helcim', count($post_ids))
-		];
-	}
-
-	/******************************************************************
-	 * ADMIN UI
-	 ******************************************************************/
-
 	/**
-	 * Process sync customer operation
+	 * Build Helcim product payload from a WordPress post.
 	 */
-	private function processSyncCustomer(array $data): array
+	protected function buildProductPayload(int $postID): array|\WP_Error
 	{
-		$user_id = $data['user_id'] ?? 0;
+		$meta = \JVBase\meta\Meta::forPost($postID);
+		$post = get_post($postID);
 
-		if (!$user_id) {
-			return [
-				'success' => false,
-				'result' => ['error' => 'No user ID provided']
-			];
-		}
-
-		$user = get_user_by('ID', $user_id);
-		if (!$user) {
-			return [
-				'success' => false,
-				'result' => ['error' => 'User not found']
-			];
-		}
-
-		// Get or create Helcim customer
-		$helcim_customer_id = $this->getOrCreateHelcimCustomer([
-			'email' => $user->user_email,
-			'name' => $user->display_name
-		]);
-
-		if ($helcim_customer_id) {
-			update_user_meta($user_id, BASE . '_helcim_customer_id', $helcim_customer_id);
-
-			return [
-				'success' => true,
-				'result' => ['customer_id' => $helcim_customer_id]
-			];
+		$price = $meta->get('price');
+		if (empty($price) || !is_numeric($price)) {
+			return new \WP_Error('invalid_price', "Post {$postID} has no valid price");
 		}
 
 		return [
-			'success' => false,
-			'result' => ['error' => 'Could not sync customer']
+			'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'),
 		];
 	}
 }

--
Gitblit v1.10.0