From a8ff85042ffbfe94b678fd818c02deb613bfd116 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 07 Aug 2026 20:42:54 +0000
Subject: [PATCH] =Continuing on with Integrations setup. Working on PostMark now

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

diff --git a/inc/integrations/services/Helcim.php b/inc/integrations/services/Helcim.php
index 355e9c2..6c41266 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\integrations\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\managers\queue\executors\IntegrationExecutor;
 use JVBase\managers\queue\TypeConfig;
@@ -24,1336 +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
-	{
-		$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