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/Field.php                     |   31 
 inc/integrations/Requests.php                  |   11 
 inc/integrations/services/Square.php           |   33 
 inc/integrations/Integrations.php              |   15 
 inc/integrations/services/GoogleMyBusiness.php | 3067 +-------------------
 inc/integrations/services/Cloudflare.php       |  186 
 inc/integrations/services/Facebook.php         |  817 +----
 inc/integrations/services/PostMark.php         |  914 ++---
 inc/integrations/AdminActions.php              |    1 
 inc/integrations/_Base.php                     |   28 
 inc/integrations/services/GoogleMaps.php       |  986 +----
 inc/integrations/UserConnection.php            |    1 
 inc/integrations/services/Umami.php            |  793 -----
 inc/integrations/_setup.php                    |   18 
 inc/integrations/services/Helcim.php           | 1332 --------
 inc/integrations/services/Instagram.php        |  712 ----
 inc/integrations/Admin.php                     |    8 
 inc/integrations/OAuth.php                     |   41 
 18 files changed, 1,209 insertions(+), 7,785 deletions(-)

diff --git a/inc/integrations/Admin.php b/inc/integrations/Admin.php
index 3f321a5..11273b8 100644
--- a/inc/integrations/Admin.php
+++ b/inc/integrations/Admin.php
@@ -26,7 +26,7 @@
 			[
 				'name'		=> 'Clear Credentials',
 				'icon'		=> 'broom',
-				'action'	=> [$this, 'clearCredentials']
+				'action'	=> [$this, 'deleteCredentials']
 			],
 		];
 		foreach ($actions as $a) {
@@ -148,7 +148,7 @@
 			$form .= $this->renderOAuthConnectionForm();
 		}
 
-		if (!empty($this->optionalFields) || $this->hasExtraOptions) {
+		if (!empty($this->optionalFields) || $this->hasExtraOptions()) {
 			$form .= '<div class="integration-content">';
 			if (!empty($this->optionalFields)) {
 				$form .= sprintf(
@@ -157,7 +157,7 @@
 					$this->outputFields($this->optionalFields)
 				);
 			}
-			if ($this->hasExtraOptions) {
+			if ($this->hasExtraOptions()) {
 				$form .= sprintf(
 					'<a href="%s" class="btn">More Settings</a>',
 					$this->determineOptionPage()
@@ -227,4 +227,6 @@
 			return true;
 		}
 
+
+
 }
diff --git a/inc/integrations/AdminActions.php b/inc/integrations/AdminActions.php
index 93e4e7f..edd52e6 100644
--- a/inc/integrations/AdminActions.php
+++ b/inc/integrations/AdminActions.php
@@ -21,6 +21,7 @@
 	{
 		$this->name = $name;
 		$this->setSlug();
+		$this->setCallback($callback);
 	}
 
 	public function setSlug(?string $slug = null):void
diff --git a/inc/integrations/Field.php b/inc/integrations/Field.php
index 788400b..61cfc55 100644
--- a/inc/integrations/Field.php
+++ b/inc/integrations/Field.php
@@ -17,14 +17,17 @@
 	protected array $options;
 	protected mixed $default;
 	protected string $subtype;
+	protected string $placeholder;
 	protected bool $hidden = false;
+	protected int $min;
+	protected int $max;
 
 	public function __construct(string $name, string $label, string $type, callable|string|bool $permission)
 	{
 		$this->name = $name;
 		$this->label = $label;
 		$this->type = $type;
-		$this->permission = $permission;
+		$this->permission = is_callable($permission) ? Closure::fromCallable($permission) : $permission;
 	}
 
 	public function checkPermission():bool
@@ -69,7 +72,21 @@
 
 	public function setSubType(string $type):void
 	{
-		$this->subType = $type;
+		$this->subtype = $type;
+	}
+
+	public function setMin(int $min):void
+	{
+		$this->min = $min;
+	}
+	public function setMax(int $max):void
+	{
+		$this->max = $max;
+	}
+
+	public function setPlaceholder(string $string):void
+	{
+		$this->placeholder = sanitize_text_field($string);
 	}
 
 	public function getConfig():array
@@ -97,6 +114,16 @@
 		if ($this->hidden) {
 			$conf['hidden'] = true;
 		}
+		if (isset($this->min)) {
+			$conf['min'] = $this->min;
+		}
+		if (isset($this->max)) {
+			$conf['max'] = $this->max;
+		}
+		if (isset($this->placeholder)) {
+			$conf['placeholder'] = $this->placeholder;
+		}
 		return $conf;
 	}
+
 }
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index f055894..262f798 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -59,6 +59,7 @@
 		$this->userID = $userID;
 		$this->cache = Cache::for('integrations_' . $this->cacheName);
 
+
 		$this->getPostTypes();
 		$this->getTaxonomies();
 		$this->getUsers();
@@ -262,7 +263,7 @@
 		}
 
 		try {
-			if ($this->isOAuthService && !$this->hasOAuthCredentials()){
+			if (!$this->hasOAuthCredentials()){
 				//If this is an OAuth service, we might only be saving the app credentials first
 				$result = true;
 			} else {
@@ -400,7 +401,10 @@
 
 
 		if (!empty($this->syncPostTypes)) {
-			$this->addSavePost();
+			if (method_exists($this, 'addSavePost')) {
+				$this->addSavePost();
+			}
+
 			add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
 
 			if ($this->canSync['delete']) {
@@ -489,10 +493,7 @@
 
 	public function hasOAuthCredentials(?int $userID = null): bool
 	{
-		if ($userID !== $this->userID) {
-			$this->switchUser($userID);
-		}
-		return $this->hasOAuth && $this->hasValidOAuth();
+		return false;
 	}
 
 
@@ -546,7 +547,7 @@
 
 	public static function hasExtraOptions():bool
 	{
-		return static::getInstance()::$hasExtraOptions;
+		return self::$hasExtraOptions;
 	}
 
 	/**
diff --git a/inc/integrations/OAuth.php b/inc/integrations/OAuth.php
index 3bc410c..dcf89aa 100644
--- a/inc/integrations/OAuth.php
+++ b/inc/integrations/OAuth.php
@@ -11,7 +11,6 @@
 trait OAuth
 {
 	use _Base, Admin;
-	protected bool $hasOAuth = true;
 	protected OAuthURLs $oauth;
 	/**
 	 * Child classes must set this (e.g. 'https://api.example.com/v1/')
@@ -21,7 +20,17 @@
 	protected int $oauthTTL = 300;
 	protected array $oathScopes = [];
 
+	protected ?RateLimits $oauthLimiter;
 
+	protected function initializeOAuthRateLimiter():void
+	{
+		$key = $this->service_name;
+		if (!is_null($this->userID)) {
+			$key .= '_'.$this->userID;
+		}
+		$key .= '_oauth';
+		$this->oauthLimiter = new RateLimits($key, ['s' => 1, 'm' => 10, 'h' => 100]);
+	}
 	/**
 	 * @return array Defaults to the base getRequestHeaders, but can optionally be overridden
 	 */
@@ -67,11 +76,15 @@
 			$this->addAction($a['name'], $a['action'], $a['slug']??null, $a['icon']??null, $a['label']??null);
 		}
 	}
+		public function hasOAuthCredentials(?int $userID = null):bool
+		{
+			if ($userID !== $this->userID) {
+				$this->switchUser($userID);
+			}
+			return $this->hasValidOAuth();
+		}
 		public function handleOAuthConnect():array
 		{
-			if (!$this->hasOAuth) {
-				return $this->response(false, 'Not an OAuth service');
-			}
 			$url = $this->getOAuthUrl();
 			if ($url) {
 				return [
@@ -92,9 +105,6 @@
 
 		public function revokeOAuth():bool
 		{
-			if (!$this->hasOAuth) {
-				return false;
-			}
 			if (empty($this->oauth['revoke'])) {
 				error_log('No revoke url set for '.$this->service_name);
 				return false;
@@ -362,9 +372,7 @@
 
 	public function getOAuthURL(?string $return_url = null):string
 	{
-		if (!$this->hasOAuth) {
-			return '';
-		}
+
 		$credentials = $this->loadCredentials();
 		if (empty($this->oauth['authorize'])) {
 			$this->logError('getOAuthUrl', 'OAuth authorize URL not configured');
@@ -409,9 +417,14 @@
 
 	protected function getRedirectUri():string
 	{
-		if ($this->hasOAuth) {
-			return admin_url('admin-ajax.php?action='.BASE . $this->service_name . '_oauth_callback');
-		}
-		return rest_url('jvb/v1/oauth/callback?service=' . $this->service_name);
+		return admin_url('admin-ajax.php?action='.BASE . $this->service_name . '_oauth_callback');
+	}
+
+	/****************************************************
+	 * UTILITY
+	****************************************************/
+	protected function getAccessToken():string
+	{
+		return $this->loadCredentials()['access_token']??'';
 	}
 }
diff --git a/inc/integrations/Requests.php b/inc/integrations/Requests.php
index ad9ebfc..633c6af 100644
--- a/inc/integrations/Requests.php
+++ b/inc/integrations/Requests.php
@@ -7,12 +7,12 @@
 
 trait Requests
 {
-	protected bool $hasRequests = true;
 	/**
 	 * Child classes must set this (e.g. 'https://api.example.com/v1/')
 	 */
 	protected ?string $baseUrl = null;
 	protected int $ttl = 300; //5 minutes default
+	protected ?RateLimits $requestLimiter = null;
 
 	/**
 	 * Child classes define their auth headers (Bearer token, API key, etc.)
@@ -25,6 +25,15 @@
 	 */
 	abstract protected function handleResponse(array|\WP_Error $response, string $method, string $endpoint): array;
 
+	protected function initializeRequestLimiter():void
+	{
+		$key = $this->service_name;
+		if (!is_null($this->userID)) {
+			$key .= '_'.$this->userID;
+		}
+		$this->requestLimiter = new RateLimits($key);
+	}
+
 	public function getRequest(string $endpoint, array $data = [], bool $force = false, ?int $ttl = null): array
 	{
 		$key = [$this->service_name, $this->userID, $endpoint, $data];
diff --git a/inc/integrations/UserConnection.php b/inc/integrations/UserConnection.php
index 5202e6e..b6ae00e 100644
--- a/inc/integrations/UserConnection.php
+++ b/inc/integrations/UserConnection.php
@@ -37,7 +37,6 @@
 	protected function clearUser():void
 	{
 		$this->userID = false;
-		$this->credentials = [];
 		if (property_exists($this, 'rateLimiter')) {
 			$this->requestLimiter?->saveHistory();
 			$this->requestLimiter = null;
diff --git a/inc/integrations/_Base.php b/inc/integrations/_Base.php
index 1697e69..0f96c90 100644
--- a/inc/integrations/_Base.php
+++ b/inc/integrations/_Base.php
@@ -31,14 +31,7 @@
 	protected string $apiVersion;
 	protected Cache $cache;
 	protected ?string $cacheName = null;
-	//Use Trait "Requests" to set this
-	protected bool $hasRequests = false;
-	protected ?RateLimits $requestLimiter = null;
-	//Use Trait "OAuth" to set this
-	protected ?RateLimits $oauthLimiter = null;
-	protected bool $hasOAuth = false;
-	//Use trait "Webhooks" to set this true
-	protected bool $hasWebhooks = false;
+
 
 	/**
 	 * @var array Each item in the array becomes a li element in the ordered list of instructions
@@ -61,7 +54,7 @@
 	 * @var bool Flag to indicate there are extra options that warrant a dedicated options page for this service
 	 * Usually used for default field values for syncable services
 	 */
-	public bool $hasExtraOptions = false;
+	public static bool $hasExtraOptions = false;
 	public bool $hasBatchCreate = false;
 	public bool $hasBatchUpdate = false;
 	public bool $hasBatchDelete = false;
@@ -93,6 +86,8 @@
 	 */
 	protected array $contentTypes = [];
 
+	protected array $credentials;
+
 
 	/**
 	 * Check if integration is properly configured
@@ -104,7 +99,10 @@
 
 	public function loadCredentials():array
 	{
-		return Auth::getInstance()->getCredentials($this->service_name, $this->userID);
+		if (!isset($this->credentials)) {
+			$this->credentials = Auth::getInstance()->getCredentials($this->service_name, $this->userID);
+		}
+		return $this->credentials;
 	}
 
 	protected function logError(string $method, string $message, array $context = []):void
@@ -143,13 +141,11 @@
 		if (!is_null($this->userID)) {
 			$key .= '_'.$this->userID;
 		}
-
-		if ($this->hasRequests && !isset($this->requestLimiter) && !is_null($this->oauthLimiter)) {
-			$this->requestLimiter = new RateLimits($key);
+		if (method_exists($this, 'initializeRequestLimiter')) {
+			$this->initializeRequestLimiter();
 		}
-		if ($this->hasOAuth && !isset($this->oauthLimiter) && !is_null($this->oauthLimiter)) {
-			$key .= '_oauth';
-			$this->oauthLimiter = new RateLimits($key, ['s' => 1,'m' => 10, 'h' => 100]);
+		if (method_exists($this, 'intializeOAuthRateLimiter')) {
+			$this->initializeOAuthRateLimiter();
 		}
 	}
 
diff --git a/inc/integrations/_setup.php b/inc/integrations/_setup.php
index 7b0179c..733900f 100644
--- a/inc/integrations/_setup.php
+++ b/inc/integrations/_setup.php
@@ -5,18 +5,28 @@
 	require(JVB_DIR . '/inc/integrations/AdminActionResult.php');
 	require(JVB_DIR . '/inc/integrations/AdminActions.php');
 require(JVB_DIR . '/inc/integrations/Ajax.php');
+require(JVB_DIR . '/inc/integrations/APIEndpoint.php');
 require(JVB_DIR . '/inc/integrations/Auth.php');
+require(JVB_DIR . '/inc/integrations/Endpoint.php');
+require(JVB_DIR . '/inc/integrations/EndpointField.php');
 require(JVB_DIR . '/inc/integrations/Field.php');
+require(JVB_DIR . '/inc/integrations/Instance.php');
 require(JVB_DIR . '/inc/integrations/OAuth.php');
 	require(JVB_DIR . '/inc/integrations/OAuthCredentials.php');
 	require(JVB_DIR . '/inc/integrations/OAuthURLs.php');
 require(JVB_DIR . '/inc/integrations/RateLimits.php');
 require(JVB_DIR . '/inc/integrations/Requests.php');
-require(JVB_DIR . '/inc/integrations/SyncDefaults.php');
-	require(JVB_DIR . '/inc/integrations/SyncFrom.php');
-	require(JVB_DIR . '/inc/integrations/SyncHelpers.php');
-	require(JVB_DIR . '/inc/integrations/SyncTo.php');
 require(JVB_DIR . '/inc/integrations/UserConnection.php');
+require(JVB_DIR . '/inc/integrations/SyncDefaults.php');
+	require(JVB_DIR . '/inc/integrations/SyncHelpers.php');
+	require(JVB_DIR . '/inc/integrations/SyncFrom.php');
+		require(JVB_DIR . '/inc/integrations/SyncFromPost.php');
+		require(JVB_DIR . '/inc/integrations/SyncFromTerm.php');
+		require(JVB_DIR . '/inc/integrations/SyncFromUser.php');
+	require(JVB_DIR . '/inc/integrations/SyncTo.php');
+		require(JVB_DIR . '/inc/integrations/SyncToPost.php');
+		require(JVB_DIR . '/inc/integrations/SyncToTerm.php');
+		require(JVB_DIR . '/inc/integrations/SyncToUser.php');
 require(JVB_DIR . '/inc/integrations/Webhooks.php');
 require(JVB_DIR . '/inc/integrations/Integrations.php');
 
diff --git a/inc/integrations/services/Cloudflare.php b/inc/integrations/services/Cloudflare.php
index 1425725..168590b 100644
--- a/inc/integrations/services/Cloudflare.php
+++ b/inc/integrations/services/Cloudflare.php
@@ -1,95 +1,72 @@
 <?php
 
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
 
 class Cloudflare extends Integrations
 {
-	private string $site_key;
-	private string $secret_key;
+	use Admin, Instance;
 	private string $theme = 'light';
 	private string $size = 'normal';
 
-	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()
 	{
 		$this->service_name = 'cloudflare';
-		$this->title = 'Cloudflare Turnstile';
+		$this->title = 'Cloudflare';
 		$this->icon = 'cloud';
-		$this->apiBase = 'https://challenges.cloudflare.com';
-		$this->apiEndpoints = ['turnstile/v0/siteverify'];
 
-		$this->fields = [
-			'site_key'	=> [
-				'label'	=> 'Site Key',
-				'type'	=> 'text',
-				'placeholder'	=> 'Enter Public key for Turnstile widget display.',
-				'hint'	=> 'Public key for Turnstile widget',
-				'required'	=> true
-			],
-			'secret_key'	=> [
-				'label'	=> 'Secret Key',
-				'type'	=> 'text',
-				'subtype'	=> 'password',
-				'placeholder'	=> 'Enter Cloudflare Turnstile Secret Key',
-				'required'	=> true,
-				'hint'	=> 'Secret key for server-side verification'
-			]
-		];
+		$this->defineFields();
+		$this->defineInstructions();
+		parent::__construct();
+	}
 
+	protected function defineFields():void
+	{
+		$site_key = $this->addField('site_key', 'Site Key', 'text', 'manage_options');
+			$site_key->setRequired();
+			$site_key->setHint('Public key for Turnstile Widget');
+		$secret_key = $this->addField('secret_key', 'Secret Key', 'text', 'manage_options');
+			$secret_key->setRequired();
+			$secret_key->setSubType('password');
+			$secret_key->setHint('Secret key for server-side verification of Turnstile Widget');
+	}
+
+	protected function defineInstructions():void
+	{
 		$this->instructions = [
 			'Go to <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank">Cloudflare Dashboard</a>',
 			'Create a new Turnstile widget',
 			'Add your domain to the widget settings',
 			'Copy the Site Key and Secret Key'
 		];
-
-		$this->advanced = [];
-		parent::__construct();
 	}
-
 	protected function initialize(): void
 	{
-		$this->site_key = $this->credentials['site_key'] ?? '';
-		$this->secret_key = $this->credentials['secret_key'] ?? '';
-		$this->theme = $this->credentials['theme'] ?? 'light';
-		$this->size = $this->credentials['size'] ?? 'normal';
-
+		// TODO: Implement initialize() method.
 	}
 
-	/**
-	 * Check if scripts should be enqueued
-	 */
-	public function shouldEnqueueScripts(): bool
+	public function getServiceDescription():string
 	{
-		return $this->isSetUp() && !is_admin();
+		return "Add Turnstile captcha protection to your forms and login pages to prevent spam and bot submissions.";
 	}
 
-	/**
-	 * Test connection by attempting a verification with an invalid token
-	 */
 	public function performConnectionTest(): bool
 	{
-		if (empty($this->site_key) || empty($this->secret_key)) {
+		$credentials = $this->loadCredentials();
+		if (!array_key_exists('site_key', $credentials) || !array_key_exists('secret_key', $credentials)) {
 			return false;
 		}
 
 		// Test with a dummy token to verify API connectivity
 		$response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
 			'body' => [
-				'secret' => $this->secret_key,
+				'secret' => $credentials['secret_key'],
 				'response' => 'test-token-connection-check'
 			],
 			'timeout' => 10
@@ -97,46 +74,14 @@
 
 		// We expect this to fail with success:false, but not with a connection error
 		if (is_wp_error($response)) {
-			$this->logError('Connection test failed: ', [
-				'message'  => $response->get_error_message()
-			]);
 			return false;
 		}
 
 		$result = json_decode(wp_remote_retrieve_body($response), true);
-
 		// If we get a response with success field (even if false), connection works
 		return array_key_exists('success', $result);
 	}
 
-	protected function validateCredentials(array $credentials): bool
-	{
-		if (empty($credentials['site_key'])) {
-			$this->logError('Missing Site Key', [
-				'method'	=> 'validateCredentials'
-			]);
-			return false;
-		}
-
-		if (empty($credentials['secret_key'])) {
-			$this->logError('Missing Secret Key', [
-				'method'	=> 'validateCredentials'
-			]);
-			return false;
-		}
-
-		// Validate format
-		if (!preg_match('/^[0-9a-zA-Z._-]+$/', $credentials['site_key'])) {
-
-			$this->logError('Invalid Site Key', [
-				'method'	=> 'validateCredentials'
-			]);
-			return false;
-		}
-
-		return true;
-	}
-
 	/**
 	 * Enqueue Turnstile script
 	 */
@@ -156,10 +101,10 @@
 	/**
 	 * Render Turnstile widget
 	 * Can be called directly: jvbConnect('cloudflare')->renderTurnstile()
+	 * Or Cloudflare::getInstance()->renderTurnstile()
 	 */
 	public function renderTurnstile(array|string $options = []): void
 	{
-		$this->ensureInitialized();
 		if (!$this->isSetUp()) {
 			return;
 		}
@@ -175,29 +120,36 @@
 			'wrapper_style' => 'margin: 1em 0;'
 		];
 
+		$cred = $this->loadCredentials();
 		$options = wp_parse_args($options, $defaults);
 
-		echo '<div class="' . esc_attr($options['wrapper_class']) . '" style="' . esc_attr($options['wrapper_style']) . '">';
-		echo '<div class="cf-turnstile" ';
-		echo 'data-sitekey="' . esc_attr($this->site_key) . '" ';
-		echo 'data-theme="' . esc_attr($options['theme']) . '" ';
-		echo 'data-size="' . esc_attr($options['size']) . '">';
-		echo '</div>';
-		echo '</div>';
+		echo sprintf(
+			'<div class="%s" style="%s">
+				<div class="cf-turnstile" data-sitekey="%s" data-theme="%s" data-size="%s"></div>
+			</div>',
+			esc_attr($options['wrapper_class']),
+			esc_attr($options['wrapper_style']),
+			$cred['site_key'],
+			$options['theme'],
+			$options['size']
+		);
 	}
 
 	/**
-	 * Verify Turnstile response
-	 * Can be called directly: JVB()->connect('cloudflare')->verifyTurnstile($token)
+	 * Verify the Turnstile response
+	 * Can be called directly via JVB()->connect('cloudflare')->verifyTurnstile($token)
+	 *   or Cloudflare::getInstance()->verifyTurnstile($token)
+	 * @param string|null $token
+	 * @param string $remote_ip
+	 * @return bool
 	 */
-	public function verifyTurnstile(?string $token = null, string $remote_ip = ''): bool
+	public function verifyTurnstile(?string $token = null, string $remote_ip = ''):bool
 	{
-		$this->ensureInitialized();
 		if (!$this->isSetUp()) {
-			return false;
+			return true;
 		}
 
-		// If no token provided, try to get from POST
+		// If no token provided, try to get it from $_POST
 		if (!$token && isset($_POST['cf-turnstile-response'])) {
 			$token = sanitize_text_field($_POST['cf-turnstile-response']);
 		}
@@ -206,61 +158,35 @@
 			return false;
 		}
 
+		$cred = $this->loadCredentials();
 		$data = [
-			'secret' => $this->secret_key,
-			'response' => $token
+			'secret'	=> $cred['secret_key'],
+			'response'	=> $token,
 		];
 
 		if (empty($remote_ip) && isset($_SERVER['REMOTE_ADDR'])) {
-			$remote_ip = $_SERVER['REMOTE_ADDR'];
+			$remote_ip = sanitize_text_field($_SERVER['REMOTE_ADDR']);
 		}
 
 		if (!empty($remote_ip)) {
 			$data['remoteip'] = $remote_ip;
 		}
-
 		$response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
 			'body' => $data,
 			'timeout' => 10
 		]);
 
 		if (is_wp_error($response)) {
-			$this->logError('Turnstile verification failed: ' . $response->get_error_message());
+			$this->logError('verifyTurnstile', 'Turnstile verification failed: ' . $response->get_error_message());
 			return false;
 		}
 
 		$result = json_decode(wp_remote_retrieve_body($response), true);
 
-		if (!($result['success'] ?? false)) {
+		if (!$result['success']??false) {
 			$error_codes = implode(', ', $result['error-codes'] ?? ['Unknown error']);
-			$this->logError('Turnstile verification failed: ' . $error_codes);
+			$this->logError('verifyTurnstile', 'Turnstile verification failed: ' . $error_codes);
 		}
-
 		return $result['success'] ?? false;
 	}
-
-	/**
-	 * Get site key for frontend use
-	 */
-	public function getSiteKey(): string
-	{
-		return $this->site_key;
-	}
-
-	/**
-	 * Get service description
-	 */
-	public function getServiceDescription(): string
-	{
-		return "Add Turnstile captcha protection to your forms and login pages to prevent spam and bot submissions.";
-	}
-
-	/**
-	 * These methods are not used by Cloudflare but must be implemented
-	 */
-	protected function getRequestHeaders(): array
-	{
-		// Cloudflare Turnstile uses POST parameters, not headers
-		return [];
-	}
 }
diff --git a/inc/integrations/services/Facebook.php b/inc/integrations/services/Facebook.php
index 842cc96..04c03f7 100644
--- a/inc/integrations/services/Facebook.php
+++ b/inc/integrations/services/Facebook.php
@@ -5,9 +5,18 @@
  */
 
 namespace JVBase\integrations\services;
+use Exception;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
-use JVBase\meta\Meta;
-use WP_Post;
+use JVBase\integrations\OAuth;
+use JVBase\integrations\OAuthCredentials;
+use JVBase\integrations\OAuthURLs;
+use JVBase\integrations\Requests;
+use JVBase\integrations\SyncDefaults;
+use JVBase\integrations\SyncFrom;
+use JVBase\integrations\SyncTo;
+use JVBase\integrations\UserConnection;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -16,686 +25,254 @@
 
 class Facebook extends Integrations
 {
-	// Facebook-specific properties
-	protected array $allowedContent = [
-		'post',
-		'photo',
-		'video',
-		'event',
-		'offer',
-		'note',
-		'milestone'
-	];
-	private string $page_id = '';
-	private string $page_access_token = '';
-	private array $permissions = [];
-	private array $pages = [];
+	use Admin,
+		Instance,
+		OAuth,
+		UserConnection,
+		Requests,
+		SyncFrom,
+		SyncTo,
+		SyncDefaults;
 
-	// Post type mapping for Facebook
-	private const FB_POST_TYPES = [
-		'post' => 'feed',
-		'photo' => 'photos',
-		'video' => 'videos',
-		'event' => 'events',
-		'offer' => 'offers',
-		'note' => 'notes',
-		'milestone' => 'milestones'
-	];
-
-	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)
+	protected function __construct()
 	{
 		$this->service_name = 'facebook';
 		$this->title = 'Facebook';
 		$this->icon = 'facebook-logo';
 
-		// API Configuration
-		$this->apiBase = [
-			'graph' => 'https://graph.facebook.com/v18.0',
-			'auth' => 'https://www.facebook.com/v18.0'
-		];
+		$this->apiVersion = 'v25.0';
 
-		$this->apiVersion = 'v18.0';
-
-		$this->isOAuthService = true;
-		// OAuth Configuration
-		$this->oauth = [
-			'authorize' => 'https://www.facebook.com/v18.0/dialog/oauth',
-			'token' => 'https://graph.facebook.com/v18.0/oauth/access_token',
-			'revoke' => 'https://graph.facebook.com/v18.0/me/permissions',
-			'scopes' => [
-				'pages_show_list',
-				'pages_read_engagement',
-				'pages_manage_posts',
-				'pages_manage_metadata',
-				'pages_manage_engagement',
-				'pages_read_user_content',
-				'business_management',
-				'instagram_basic',
-				'instagram_content_publish'
-			]
-		];
-
-		// Sync capabilities
 		$this->canSync = [
-			'initial' => true,
-			'update' => true,
-			'delete' => true
+			'create'	=> true,
+			'update'	=> true,
+			'delete'	=> true
 		];
 
-		// Rate limits for Facebook Graph API
-		$this->rate_limits = [
-			'per_second' => 10,
-			'per_minute' => 600,
-			'per_hour' => 200000
-		];
+		$this->defineFields();
+		$this->defineInstructions();
+		$this->defineOAuth();
+		$this->defineActions();
 
-		$this->fields = [
-			'client_id'	=> [
-				'type'	=> 'text',
-				'placeholder' => 'Enter Facebook App ID',
-				'hint'	=> 'Your Facebook App ID from developers.facebook.com',
-				'label'	=> 'App ID',
-				'required'	=> true,
-			],
-			'client_secret' => [
-				'type'	=> 'text',
-				'subtype'=> 'password',
-				'label'	=> 'App Secret',
-				'required'	=> true,
-				'hint'	=> 'Your Facebook App Secret (keep this secure!)'
-			],
-		];
-
-		$this->advanced = [
-
-		];
-
-		$this->instructions = [
-
-		];
-
-		$this->defaults = [
-
-		];
-		$this->hasWebhooks = true;
+		$this->baseUrl = 'https://graph.facebook.com/'.$this->apiVersion;
+		$this->baseOAuthUrl = 'https://www.facebook.com/'.$this->apiVersion;
 
 		parent::__construct();
-
-		$this->actions = array_merge($this->actions, [
-			'sync_pages'	=> 'getUserPages'
-		]);
 	}
 
-	/**
-	 * Initialize Facebook-specific properties
-	 */
-	protected function initialize(): void
-	{
-		$this->page_id = $this->credentials['page_id'] ?? '';
-		$this->page_access_token = $this->credentials['page_access_token'] ?? '';
-		$this->permissions = $this->credentials['permissions'] ?? [];
+		protected function defineFields():void
+		{
+			$client_id = $this->addField(
+				'client_id',
+				'App ID',
+				'text',
+				'manage_options'
+			);
+				$client_id->setRequired();
+				$client_id->setHint('Your Facebook App ID from developers.facebook.com');
 
-		// Build dynamic endpoints based on page
-		if ($this->page_id) {
-			$this->apiEndpoints = [
-				"/{$this->page_id}/feed",
-				"/{$this->page_id}/photos",
-				"/{$this->page_id}/videos",
-				"/{$this->page_id}/events",
-				"/{$this->page_id}/insights",
-				"/{$this->page_id}/conversations",
-				"/{$this->page_id}",
-				"/me/accounts"
+			$client_secret = $this->addField(
+				'client_secret',
+				'App Secret',
+				'text',
+				'manage_options'
+			);
+				$client_secret->setRequired();
+				$client_secret->setSubType('password');
+
+			$pages = $this->addField('pages', 'Pages', 'select', [$this, 'testUserCanConnect']);
+				$pages->setHidden();
+				$pages->setOptions($this->getPageOptions());
+
+			$page = $this->addField('page_id', 'Connect to Page', 'select', [$this, 'testUserCanConnect']);
+				$page->setOptions($this->loadCredentials()['pages']??[]);
+				$page->setHint('Select the page you want to connect this site to.');
+		}
+			public function testUserCanConnect():bool
+			{
+				$user = get_current_user_id();
+				return $this->userCanConnect($user);
+			}
+			protected function getPageOptions():array
+			{
+				return $this->loadCredentials()['pages']??[];
+			}
+		protected function defineInstructions():void
+		{
+
+		}
+		protected function defineActions():void
+		{
+			$this->addAction(
+				'Select Page',
+				[$this, 'handleSelectPage'],
+				'select_page',
+				'plugs-connected'
+			);
+
+			$this->addAction(
+				'Fetch Pages',
+				[$this, 'getPages'],
+				'fetch_pages',
+				'download-simple'
+			);
+		}
+		public function defineOAuth():void
+		{
+			$this->oauth = new OAuthURLs(
+				'https://www.facebook.com/'.$this->apiVersion.'/dialog/oauth',
+				'https://graph.facebook.com/'.$this->apiVersion.'/oauth/access_token',
+				'https://graph.facebook.com/'.$this->apiVersion.'/me/permissions'
+			);
+			$this->oathScopes = [
+				'pages_show_list',
+				'business_management',
+				'pages_manage_metadata',
+				'pages_manage_posts'
 			];
 		}
-	}
-
-	/**
-	 * Get service description
-	 */
-	public function getServiceDescription(): string
+		public function getServiceDescription():string
+		{
+			return 'Connect your Facebook Page to share posts, events, and engage with your audience.';
+		}
+	protected function initialize(): void
 	{
-		return "Connect your Facebook Page to share posts, events, and engage with your audience.";
+		// TODO: Implement initialize() method.
 	}
 
-	/**
-	 * Get request headers for Facebook API
-	 */
+	protected function refreshAccessToken(OAuthCredentials $credentials): array
+	{
+		return $this->response(false, 'Not set up yet');
+	}
+
 	protected function getRequestHeaders(): array
 	{
-		$headers = [
+		return [
 			'Content-Type' => 'application/json',
 			'Accept' => 'application/json'
 		];
-
-		// Use page access token if available, otherwise user token
-		if (!empty($this->page_access_token)) {
-			// Token is sent in URL params for Facebook, not headers
-		}
-
-		return $headers;
 	}
 
-	/**
-	 * Add Facebook-specific OAuth parameters
-	 */
-	protected function addOAuthParams(array $params): array
+
+	protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
 	{
-		$params['display'] = 'popup';
-		$params['auth_type'] = 'rerequest';
-		$params['return_scopes'] = 'true';
-		return $params;
-	}
-
-	/**
-	 * Add Facebook-specific credential data after OAuth
-	 */
-	protected function addCredentialData(array $credentials, array $tokens): array
-	{
-		// Get user's pages after OAuth
-		try {
-			$pages = $this->getUserPages($tokens['access_token']);
-			if (!empty($pages)) {
-				$credentials['pages'] = $pages;
-				// Auto-select first page if not already selected
-				if (empty($credentials['page_id']) && !empty($pages[0])) {
-					$credentials['page_id'] = $pages[0]['id'];
-					$credentials['page_access_token'] = $pages[0]['access_token'];
-					$credentials['page_name'] = $pages[0]['name'];
-				}
-			}
-
-			// Store permissions granted
-			$permissions = $this->getGrantedPermissions($tokens['access_token']);
-			$credentials['permissions'] = $permissions;
-
-		} catch (Exception $e) {
-			$this->logError('Failed to fetch Facebook pages', ['error' => $e->getMessage()]);
+		if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
+			return $this->response(false, 'Rate limit exceeded');
 		}
 
-		return $credentials;
+		$endpoint = str_replace($this->baseUrl, '', $endpoint);
+		return match ($endpoint) {
+			'/me/accounts?access_token='.$this->getAccessToken() 	=> $this->handleGetPagesResponse($response, $method),
+			default => $this->response(false, 'Unhandled Response for ' . $endpoint . ' endpoint, with method: ' . $method),
+		};
+	}
+	/************************************************************
+	 * UTILITY METHODS
+	************************************************************/
+	protected function getPageID():string|false
+	{
+		return $this->loadCredentials()['page_id']??false;
+	}
+	protected function getClientID():string|false
+	{
+		return $this->loadCredentials()['client_id']??false;
+	}
+	protected function getClientSecret():string|false
+	{
+		return $this->loadCredentials()['client_secret']??false;
 	}
 
-	/**
-	 * Test Facebook connection
-	 */
-	protected function performConnectionTest(): bool
+	protected function getFBUserID():string|false
 	{
-		if (empty($this->credentials['access_token'])) {
-			return false;
-		}
-
-		try {
-			// Test by fetching user info
-			$response = wp_remote_get(
-				$this->apiBase['graph'] . '/me?access_token=' . $this->credentials['access_token']
+		$cred = $this->loadCredentials();
+		if ($this->isSetUp() && !array_key_exists('user_id', $cred)) {
+			$resp = $this->getRequest(
+				$this->baseUrl.'/me?access_token='.$cred['access_token'],
 			);
+			if ($resp && !is_wp_error($resp)) {
+				$tmp['user_id'] = $resp['id'];
+				$tmp['user_name'] = $resp['name'];
 
-			if (is_wp_error($response)) {
-				return false;
+				$this->saveCredentials($tmp, false);
 			}
-
-			$code = wp_remote_retrieve_response_code($response);
-			return $code === 200;
-
-		} catch (Exception $e) {
-			$this->logError('Connection test failed', ['error' => $e->getMessage()]);
-			return false;
+			$cred = array_merge($cred, $tmp);
 		}
+		return $cred['user_id']??false;
 	}
-
-	/**
-	 * Render Additional OAuth settings
-	 */
-	protected function renderOAuthConnectedOptions(): void
+	public function getPages():array|false
 	{
-		// Page selection dropdown
-		if (!empty($this->credentials['pages'])) {
-			?>
-			<div class="form-field">
-				<label for="fb_page">Active Facebook Page</label>
-				<select name="credentials[page_id]" id="fb_page">
-					<option value="">Select a page...</option>
-					<?php foreach ($this->credentials['pages'] as $page): ?>
-						<option value="<?php echo esc_attr($page['id']); ?>"
-							<?php selected($this->credentials['page_id'] ?? '', $page['id']); ?>>
-							<?php echo esc_html($page['name']); ?>
-						</option>
-					<?php endforeach; ?>
-				</select>
-			</div>
-			<?php
-		}
-	}
-
-	/**
-	 * Validate webhook from Facebook
-	 */
-	protected function validateWebhook(array $payload): bool
-	{
-		// Handle webhook verification challenge
-		if (isset($payload['hub_mode']) && $payload['hub_mode'] === 'subscribe') {
-			$verify_token = $this->credentials['webhook_verify_token'] ?? '';
-			if ($payload['hub_verify_token'] === $verify_token) {
-				// Echo the challenge for verification
-				echo $payload['hub_challenge'];
-				return true;
-			}
+		if (!$this->getAccessToken()) {
 			return false;
 		}
 
-		// Validate webhook signature for actual events
-		$headers = $payload['_headers'] ?? [];
-		$signature = $headers['x-hub-signature-256'] ?? '';
-
-		if (empty($signature)) {
-			$this->logError('Missing webhook signature');
-			return false;
-		}
-
-		// Calculate expected signature
-		$app_secret = $this->credentials['client_secret'] ?? '';
-		$expected = 'sha256=' . hash_hmac('sha256', json_encode($payload), $app_secret);
-
-		return hash_equals($expected, $signature);
-	}
-
-	/**
-	 * Process webhook from Facebook
-	 */
-	protected function processWebhook(array $payload): bool
-	{
 		try {
-			$object = $payload['object'] ?? '';
-			$entry = $payload['entry'] ?? [];
-
-			foreach ($entry as $item) {
-				$changes = $item['changes'] ?? [];
-
-				foreach ($changes as $change) {
-					$field = $change['field'] ?? '';
-					$value = $change['value'] ?? [];
-
-					switch ($field) {
-						case 'feed':
-							$this->handleFeedUpdate($value);
-							break;
-
-						case 'conversations':
-							$this->handleMessageUpdate($value);
-							break;
-
-						case 'photos':
-							$this->handlePhotoUpdate($value);
-							break;
-
-						default:
-							$this->logDebug('Unhandled webhook field', ['field' => $field]);
-					}
+			return $this->getRequest(
+				$this->apiBase.'/me/accounts?access_token='.$this->getAccessToken(),
+			);
+		} catch (Exception $e) {
+			$this->logError(
+				'getPages',
+				'Failed to load pages',
+				[
+					'error'	=> $e->getMessage(),
+				]
+			);
+		}
+		return [];
+	}
+		protected function handleGetPagesResponse(WP_Error|array $response, string $method):array
+		{
+			$options = [];
+			foreach ($response as $page) {
+				if (array_key_exists('id', $page) && array_key_exists('name', $page)) {
+					$options[$page['id']] = $page['name'];
 				}
 			}
-
-			return true;
-
-		} catch (Exception $e) {
-			$this->logError('Webhook processing failed', [
-				'error' => $e->getMessage(),
-				'payload' => $payload
-			]);
-			return false;
+			if (!empty($options)) {
+				$this->saveCredentials(['pages' => $options]);
+			}
+			return $this->response(true, 'Found pages', $options);
 		}
-	}
 
-	/**
-	 * Handle post save for Facebook sync
-	 */
-	protected function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void
+	/************************************************************
+	 * ACTIONS
+	************************************************************/
+	public function handleSelectPage(array $data):array
 	{
-
-		if (!$this->hasOAuthCredentials()) {
-			error_log('OAuth Not set up for '.$this->service_name);
-			return;
+		if (empty($data['page_id']??'')) {
+			return $this->response(false, 'No page selected.');
 		}
-		$this->queueOperation(self::$syncTo, [
-			'items'		=> [$postID],
-			'user'		=> user_can($post->post_author, 'manage_options') ? null : $post->post_author
-		], [
-			'delay'	=> 60
-		]);
-		Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
+
+		$cred = [
+			'page_id'	=> sanitize_text_field($data['page_id']),
+		];
+		return $this->saveCredentials($cred, false);
 	}
 
-	/**
-	 * Process queued operations
-	 */
-	public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
+	public function handleFetchPages():array
 	{
 		try {
-			$fb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this;
-			switch ($operation->type) {
-				case 'facebook_create_post':
-					return $fb->createFacebookPost($data);
-
-				case 'facebook_update_post':
-					return $fb->updateFacebookPost($data);
-
-				case 'facebook_delete_post':
-					return $fb->deleteFacebookPost($data);
-
-				case 'facebook_create_event':
-					return $fb->createFacebookEvent($data);
-
-				default:
-					return $result;
+			$pages = $this->getPages();
+			if (!$pages) {
+				return $this->response(false, 'No pages found');
 			}
+
+			return $this->response(
+				true,
+				sprintf('Found %d page(s)', count($pages)),
+				$pages
+			);
 		} catch (Exception $e) {
-			$this->logError('Operation failed', [
-				'type' => $operation->type,
-				'error' => $e->getMessage()
-			]);
-			return new WP_Error('operation_failed', $e->getMessage());
+			return $this->response(false, 'Failed to fetch pages: '.$e->getMessage());
 		}
 	}
 
-	/**
-	 * Create a Facebook post
-	 */
-	private function createFacebookPost(array $data): array
+	protected function setContentTypes(): void
 	{
-		$post = get_post($data['post_id']);
-		if (!$post) {
-			return ['success' => false, 'error' => 'Post not found'];
-		}
-
-		// Build Facebook post data
-		$fb_data = [
-			'message' => $this->formatPostContent($post),
-			'access_token' => $this->page_access_token
-		];
-
-		// Add link if present
-		$link = get_post_meta($post->ID, 'link_url', true);
-		if ($link) {
-			$fb_data['link'] = $link;
-		}
-
-		// Handle featured image
-		if (has_post_thumbnail($post->ID)) {
-			$image_url = get_the_post_thumbnail_url($post->ID, 'large');
-			$fb_data['picture'] = $image_url;
-		}
-
-		// Determine endpoint
-		$endpoint = $data['endpoint'] ?? 'feed';
-		$url = $this->apiBase['graph'] . '/' . $this->page_id . '/' . $endpoint;
-
-		// Make the API request
-		$response = wp_remote_post($url, [
-			'body' => $fb_data,
-			'timeout' => 30
-		]);
-
-		if (is_wp_error($response)) {
-			return ['success' => false, 'error' => $response->get_error_message()];
-		}
-
-		$body = json_decode(wp_remote_retrieve_body($response), true);
-
-		if (isset($body['id'])) {
-			// Store Facebook post ID
-			update_post_meta($post->ID, BASE . '_facebook_post_id', $body['id']);
-			update_post_meta($post->ID, BASE . '_facebook_sync_time', time());
-
-			return [
-				'success' => true,
-				'facebook_id' => $body['id'],
-				'message' => 'Posted to Facebook successfully'
-			];
-		}
-
-		return ['success' => false, 'error' => $body['error']['message'] ?? 'Unknown error'];
+		// TODO: Implement setContentTypes() method.
 	}
 
-	/**
-	 * Update a Facebook post
-	 */
-	private function updateFacebookPost(array $data): array
+	protected function formatForService(int $itemID, string $type = 'post'): array
 	{
-		$post = get_post($data['post_id']);
-		$fb_post_id = get_post_meta($post->ID, BASE . '_facebook_post_id', true);
-
-		if (!$fb_post_id) {
-			// No existing post, create new one
-			return $this->createFacebookPost($data);
-		}
-
-		// Facebook doesn't allow editing most post types after creation
-		// We can only update certain fields like message
-		$fb_data = [
-			'message' => $this->formatPostContent($post),
-			'access_token' => $this->page_access_token
-		];
-
-		$url = $this->apiBase['graph'] . '/' . $fb_post_id;
-
-		$response = wp_remote_request($url, [
-			'method' => 'POST',
-			'body' => $fb_data
-		]);
-
-		if (is_wp_error($response)) {
-			return ['success' => false, 'error' => $response->get_error_message()];
-		}
-
-		update_post_meta($post->ID, BASE . '_facebook_sync_time', time());
-
-		return ['success' => true, 'message' => 'Updated on Facebook'];
-	}
-
-	/**
-	 * Delete a Facebook post
-	 */
-	private function deleteFacebookPost(array $data): array
-	{
-		$fb_post_id = get_post_meta($data['post_id'], BASE . '_facebook_post_id', true);
-
-		if (!$fb_post_id) {
-			return ['success' => true, 'message' => 'No Facebook post to delete'];
-		}
-
-		$url = $this->apiBase['graph'] . '/' . $fb_post_id . '?access_token=' . $this->page_access_token;
-
-		$response = wp_remote_request($url, ['method' => 'DELETE']);
-
-		if (!is_wp_error($response)) {
-			delete_post_meta($data['post_id'], BASE . '_facebook_post_id');
-			delete_post_meta($data['post_id'], BASE . '_facebook_sync_time');
-		}
-
-		return ['success' => true, 'message' => 'Deleted from Facebook'];
-	}
-
-	/**
-	 * Create a Facebook event
-	 */
-	private function createFacebookEvent(array $data): array
-	{
-		$post = get_post($data['post_id']);
-		$meta = Meta::forPost($post->ID);
-
-		$event_data = [
-			'name' => $post->post_title,
-			'description' => $this->formatPostContent($post),
-			'start_time' => $meta->get('event_start_date'),
-			'end_time' => $meta->get('event_end_date'),
-			'access_token' => $this->page_access_token
-		];
-
-		// Add location if available
-		$location = $meta->get('event_location');
-		if ($location) {
-			$event_data['location'] = $location;
-		}
-
-		$url = $this->apiBase['graph'] . '/' . $this->page_id . '/events';
-
-		$response = wp_remote_post($url, [
-			'body' => $event_data
-		]);
-
-		if (is_wp_error($response)) {
-			return ['success' => false, 'error' => $response->get_error_message()];
-		}
-
-		$body = json_decode(wp_remote_retrieve_body($response), true);
-
-		if (isset($body['id'])) {
-			update_post_meta($post->ID, BASE . '_facebook_event_id', $body['id']);
-			return ['success' => true, 'event_id' => $body['id']];
-		}
-
-		return ['success' => false, 'error' => $body['error']['message'] ?? 'Unknown error'];
-	}
-
-	/**
-	 * Helper: Get user's Facebook pages
-	 */
-	private function getUserPages(?string $access_token = null): array
-	{
-		$token = $access_token ?: $this->credentials['access_token'];
-
-		$url = $this->apiBase['graph'] . '/me/accounts?access_token=' . $token;
-
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			throw new Exception($response->get_error_message());
-		}
-
-		$body = json_decode(wp_remote_retrieve_body($response), true);
-
-		if (isset($body['error'])) {
-			throw new Exception($body['error']['message']);
-		}
-
-		return $body['data'] ?? [];
-	}
-
-	/**
-	 * Helper: Get granted permissions
-	 */
-	private function getGrantedPermissions(string $access_token): array
-	{
-		$url = $this->apiBase['graph'] . '/me/permissions?access_token=' . $access_token;
-
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			return [];
-		}
-
-		$body = json_decode(wp_remote_retrieve_body($response), true);
-		$permissions = [];
-
-		foreach (($body['data'] ?? []) as $permission) {
-			if ($permission['status'] === 'granted') {
-				$permissions[] = $permission['permission'];
-			}
-		}
-
-		return $permissions;
-	}
-
-	/**
-	 * Helper: Format post content for Facebook
-	 */
-	private function formatPostContent(WP_Post $post): string
-	{
-		$content = $post->post_content;
-
-		// Strip shortcodes
-		$content = strip_shortcodes($content);
-
-		// Convert to plain text
-		$content = wp_strip_all_tags($content);
-
-		// Add link to full post
-		$permalink = get_permalink($post->ID);
-		$content .= "\n\nRead more: " . $permalink;
-
-		// Trim to Facebook's character limit (63,206 characters)
-		if (strlen($content) > 63000) {
-			$content = substr($content, 0, 63000) . '...';
-		}
-
-		return $content;
-	}
-
-	/**
-	 * Helper: Handle feed updates from webhook
-	 */
-	private function handleFeedUpdate(array $data): void
-	{
-		// Process incoming feed updates
-		// Could sync comments, reactions, etc. back to WordPress
-		$this->logDebug('Feed update received', $data);
-	}
-
-	/**
-	 * Helper: Handle message updates from webhook
-	 */
-	private function handleMessageUpdate(array $data): void
-	{
-		// Process incoming messages
-		// Could create notifications or queue responses
-		$this->logDebug('Message received', $data);
-	}
-
-	/**
-	 * Helper: Handle photo updates from webhook
-	 */
-	private function handlePhotoUpdate(array $data): void
-	{
-		// Process photo updates
-		$this->logDebug('Photo update received', $data);
-	}
-
-	/**
-	 * Send test post to Facebook
-	 */
-	private function sendTestPost(): array
-	{
-		$test_data = [
-			'message' => 'Test post from ' . get_bloginfo('name') . ' at ' . current_time('mysql'),
-			'access_token' => $this->page_access_token
-		];
-
-		$url = $this->apiBase['graph'] . '/' . $this->page_id . '/feed';
-
-		$response = wp_remote_post($url, [
-			'body' => $test_data
-		]);
-
-		if (is_wp_error($response)) {
-			return ['success' => false, 'error' => $response->get_error_message()];
-		}
-
-		$body = json_decode(wp_remote_retrieve_body($response), true);
-
-		if (isset($body['id'])) {
-			return ['success' => true, 'post_id' => $body['id']];
-		}
-
-		return ['success' => false, 'error' => $body['error']['message'] ?? 'Unknown error'];
+		return [];
 	}
 }
diff --git a/inc/integrations/services/GoogleMaps.php b/inc/integrations/services/GoogleMaps.php
index 6477a0d..f1e986a 100644
--- a/inc/integrations/services/GoogleMaps.php
+++ b/inc/integrations/services/GoogleMaps.php
@@ -5,8 +5,11 @@
  */
 
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
-use JVBase\registrar\Registrar;
+use JVBase\integrations\Requests;
+use WP_Error;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -14,468 +17,101 @@
 
 class GoogleMaps extends Integrations
 {
-	private string $public_key;
-	private string $private_key;
-	private string $map_id;
-	private array $enabled_apis = [];
-
-	// Edmonton default coordinates
-	private const DEFAULT_LAT = 53.5461;
-	private const DEFAULT_LNG = -113.4938;
-	private const DEFAULT_ZOOM = 11; // City-wide view
-	private const DEFAULT_MAP_ID = '873aed4ca260b4203e45b6e3';
-
-	protected static self $instance;
-	public static function getInstance():self
-	{
-
-		if (!isset(self::$instance)) {
-			self::$instance = new self();
-		}
-		return self::$instance;
-	}
-
+	use Admin, Instance, Requests;
 	protected function __construct()
 	{
 		$this->service_name = 'maps';
 		$this->title = 'Google Maps';
 		$this->icon = 'map-pin';
+
 		$this->apiBase = 'https://maps.googleapis.com/maps/api/';
-		$this->apiEndpoints = [
-			'geocode/json',
-			'place/details/json',
-			'place/nearbysearch/json',
-			'distancematrix/json',
-			'js'
-		];
+		$this->defineFields();
+		$this->defineInstructions();
+		$this->defineActions();
 
-		$this->fields = [
-			'public_key' => [
-				'type'	=> 'text',
-				'subtype' => 'password',
-				'label'	=> 'Maps Platform API Key (Referrer-Restricted)',
-				'hint'	=> 'Restrict this key by <b>HTTP referrers</b> for security. Used for displaying maps in browsers.',
-				'required'	=> true,
-			],
-			'private_key' => [
-				'type'	=> 'text',
-				'subtype'	=> 'password',
-				'label'	=> 'Geocoding & Places API Key (IP-Restricted)',
-				'hint'	=> 'Restrict this key by <br>IP Addresses</b>. Used for geocoding and server-side operations.',
-				'required'	=> true,
-			]
-		];
-		$this->advanced = [
-			'map_id'	=> [
-				'type'	=> 'text',
-				'label'	=> 'Map ID',
-				'hint'	=> 'Required for custom styling. Create a Map ID in <a href="https://console.cloud.google.com/google/maps-apis/studio/maps" target="_blank">Google Cloud Console → Maps → Map Management</a>'
-			],
-			'default_zoom'	=> [
-				'type'	=> 'text',
-				'subtype'	=> 'number',
-				'min'	=> 1,
-				'max'	=> 20,
-				'label'	=> 'Default Zoom',
-				'default'	=> 11
-			],
-		];
-		$this->instructions = [
-			'Create two API keys in <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a>',
-			'Enable <strong>Geocoding API + Places API</strong> for frontend key, restricted by your server IP address',
-			'Enable <strong>Maps Platform API</strong> for server key, restricted by your website domain',
-		];
-		$this->defaults = [
-
-		];
 		parent::__construct();
-
-		$this->actions = array_merge(
-			$this->actions,
-			[
-				'reverse_geocode' => 'processReverseGeocode',
-				'geocode' => 'processGeocode'
-			]
-		);
 		// Auto-enqueue maps script when needed
 		add_action('wp_enqueue_scripts', [$this, 'maybeEnqueueMapsScript']);
-//		add_action('admin_enqueue_scripts', [$this, 'maybeEnqueueMapsScript']);
 	}
+		protected function defineFields():void
+		{
+			$pub = $this->addField('public_key', 'Maps Platform API Key', 'text', 'manage_options');
+				$pub->setHint('Restrict this key by HTTP referrers for security. Used for displaying maps in browsers.');
+				$pub->setSubType('password');
+				$pub->setRequired();
 
+			$priv = $this->addField('private_key', 'Geocoding & Places API Key', 'text', 'manage_options');
+				$priv->setHint('Restrict this key by IP Addresses. Used for geocoding and server-side operations.');
+				$priv->setRequired();
+				$priv->setSubType('password');
+
+			$map_id = $this->addField('map_id', 'Map ID', 'text', 'manage_options');
+				$map_id->setHint('Required for custom styling. Create a Map ID in Google Cloud Console -> Maps -> Map Management.');
+
+			$zoom = $this->addField('default_zoom', 'Default Zoom', 'text', 'manage_options');
+				$zoom->setSubType('number');
+				$zoom->setDefault(11);
+				$zoom->setMin(1);
+				$zoom->setMax(20);
+
+			$lng = $this->addField('default_lng', 'Default Longitude', 'text', 'manage_options');
+				$lng->setSubType('number');
+				$lng->setDefault(-113.4938);
+
+			$lat = $this->addField('default_lat', 'Default Latitude', 'text', 'manage_options');
+				$lng->setSubType('number');
+				$lng->setDefault(53.5461);
+		}
+
+		protected function defineInstructions():void
+		{
+			$this->instructions = [
+				'Create two API keys in <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a>',
+				'Enable <strong>Geocoding API + Places API</strong> for frontend key, restricted by your server IP address',
+				'Enable <strong>Maps Platform API</strong> for server key, restricted by your website domain',
+			];
+		}
+
+		protected function defineActions():void
+		{
+			$this->addAction(
+				'reverse_geocode',
+				[$this, 'processReverseGeocode']
+			);
+
+			$this->addAction(
+				'geocode',
+				[$this, 'processGeocode']
+			);
+		}
+			public function processReverseGeocode(array $data):array
+			{
+				if (!array_key_exists('lat', $data) || !array_key_exists('lng', $data)) {
+					return $this->response(false, 'Invalid coordinates provided', $data);
+				}
+				$lat = floatval($data['lat']);
+				$lng = floatval($data['lng']);
+
+				$result = $this->reverseGeocode($lat, $lng);
+				if ($result) {
+					return $this->response(true, 'Successfully reversed Geocode', ['data'=> $result]);
+				}
+				return $this->response(false, 'Reverse geocoding failed');
+			}
+
+			public function processGeocode(array $data):array
+			{
+				if (!array_key_exists('address', $data)) {
+					return $this->response(false, 'No address provided');
+				}
+				$result = $this->geocodeAddress(sanitize_text_field($data['address']));
+				if ($result) {
+					return $this->response(true, 'Successfully geocoded address', ['data' => $result]);
+				}
+				return $this->response(false, 'Geocoding failed');
+			}
 	protected function initialize(): void
 	{
-		$this->private_key = $this->credentials['private_key'] ?? '';
-		$this->public_key = $this->credentials['public_key'] ?? '';
-		$this->map_id = $this->credentials['map_id'] ?? self::DEFAULT_MAP_ID;
-		$this->rate_limits = ['min_interval' => 0]; // Google Maps has generous limits
-	}
-
-	/**
-	 * Get service description
-	 */
-	public function getServiceDescription(): string
-	{
-		return "Enable location features and embedded maps throughout your site.";
-	}
-
-	/**
-	 * Get request headers (required by base class)
-	 */
-	protected function getRequestHeaders(): array
-	{
-		// Google Maps API uses API key in URL params, not headers
-		return [
-			'Accept' => 'application/json',
-			'Content-Type' => 'application/json'
-		];
-	}
-
-
-	/**
-	 * Process test connection request
-	 */
-	protected function performConnectionTest(): bool
-	{
-		if (!$this->isSetUp()) {
-			return false;
-		}
-
-		// Test with a simple geocoding request
-		$test_address = 'Edmonton, Alberta, Canada';
-		$result = $this->geocodeAddress($test_address);
-
-		return $result !== null;
-	}
-
-	/**
-	 * Process reverse geocode request
-	 */
-	private function processReverseGeocode(array $data): WP_Error|array
-	{
-		$lat = floatval($data['lat'] ?? 0);
-		$lng = floatval($data['lng'] ?? 0);
-
-		if (!$lat || !$lng) {
-			return new WP_Error('invalid_coordinates', 'Invalid coordinates provided');
-		}
-
-		$result = $this->reverseGeocode($lat, $lng);
-
-		if ($result) {
-			return ['success' => true, 'data' => $result];
-		}
-
-		return new WP_Error('geocoding_failed', 'Reverse geocoding failed');
-	}
-
-	/**
-	 * Process geocode request
-	 */
-	private function processGeocode(array $data): WP_Error|array
-	{
-		$address = sanitize_text_field($data['address'] ?? '');
-
-		if (empty($address)) {
-			return new WP_Error('invalid_address', 'No address provided');
-		}
-
-		$result = $this->geocodeAddress($address);
-
-		if ($result) {
-			return ['success' => true, 'data' => $result];
-		}
-
-		return new WP_Error('geocoding_failed', 'Geocoding failed');
-	}
-
-	/**
-	 * Geocode an address to coordinates
-	 */
-	public function geocodeAddress(string $address): ?array
-	{
-		if (!$this->isSetUp()) {
-			return null;
-		}
-
-		$params = [
-			'address' => $address,
-			'key' => $this->private_key
-		];
-
-		$key = $this->cache->generateKey(['geocode', $address]);
-		$cached = $this->cache->get($key);
-		if ($cached) {
-			return $cached;
-		}
-
-		$url = $this->apiBase . 'geocode/json?' . http_build_query($params);
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			$this->logError('Geocoding request failed', [
-				'error' => $response->get_error_message(),
-				'address' => $address
-			]);
-			return null;
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-
-		if ($data['status'] === 'OK' && !empty($data['results'])) {
-			$result = $data['results'][0];
-			$result = [
-				'lat' => $result['geometry']['location']['lat'],
-				'lng' => $result['geometry']['location']['lng'],
-				'formatted_address' => $result['formatted_address'],
-				'place_id' => $result['place_id'] ?? '',
-				'components' => $this->parseAddressComponents($result['address_components'] ?? [])
-			];
-			$this->cache->set($key, $result);
-		} else {
-			$this->logError('Google Maps API error', [
-				'status' => $data['status'] ?? 'unknown',
-				'address' => $address
-			], 'warning');
-			$result = null;
-		}
-
-		return $result;
-	}
-
-	/**
-	 * Reverse geocode coordinates to address
-	 */
-	public function reverseGeocode(float $lat, float $lng): ?array
-	{
-		if (!$this->isSetUp()) {
-			return null;
-		}
-
-		$params = [
-			'latlng' => "{$lat},{$lng}",
-			'key' => $this->private_key
-		];
-
-		$key = $this->cache->generateKey(['reverse', $lat, $lng]);
-		$cached = $this->cache->get($key);
-		if ($cached) {
-			return $cached;
-		}
-
-		$url = $this->apiBase . 'geocode/json?' . http_build_query($params);
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			$this->logError('Reverse geocoding request failed', [
-				'error' => $response->get_error_message(),
-				'lat' => $lat,
-				'lng' => $lng
-			]);
-			return null;
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-
-		if ($data['status'] === 'OK' && !empty($data['results'])) {
-			$result = $data['results'][0];
-			$return = [
-				'formatted_address' => $result['formatted_address'],
-				'place_id' => $result['place_id'] ?? '',
-				'components' => $this->parseAddressComponents($result['address_components'] ?? [])
-			];
-			$this->cache->set($key, $return);
-			return $return;
-		}
-
-		return null;
-	}
-
-	/**
-	 * Get place details by place ID
-	 */
-	public function getPlaceDetails(string $place_id, array $fields = []): ?array
-	{
-		if (!$this->isSetUp()) {
-			return null;
-		}
-
-		$default_fields = ['name', 'formatted_address', 'geometry', 'rating', 'formatted_phone_number'];
-		$fields = empty($fields) ? $default_fields : $fields;
-
-		$key = $this->cache->generateKey(['place', $place_id, $fields]);
-		$cached = $this->cache->get($key);
-		if ($cached) {
-			return $cached;
-		}
-
-		$params = [
-			'place_id' => $place_id,
-			'fields' => implode(',', $fields),
-			'key' => $this->private_key
-		];
-
-		$url = $this->apiBase . 'place/details/json?' . http_build_query($params);
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			$this->logError('Place details request failed', [
-				'error' => $response->get_error_message(),
-				'place_id' => $place_id
-			]);
-			return null;
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-
-		if ($data['status'] === 'OK' && !empty($data['result'])) {
-			$result = $data['result'];
-			$this->cache->set($key, $result);
-			return $result;
-		}
-
-		return null;
-	}
-
-	/**
-	 * Search for places nearby
-	 */
-	public function searchNearby(float $lat, float $lng, int $radius = 1000, string $type = ''): array
-	{
-		if (!$this->isSetUp()) {
-			return [];
-		}
-
-		$params = [
-			'location' => "{$lat},{$lng}",
-			'radius' => $radius,
-			'key' => $this->private_key
-		];
-
-		if (!empty($type)) {
-			$params['type'] = $type;
-		}
-
-		$key = $this->cache->generateKey(['nearby', $lat, $lng, $radius, $type]);
-		$cached = $this->cache->get($key);
-		if ($cached) {
-			return $cached;
-		}
-
-		$url = $this->apiBase . 'place/nearbysearch/json?' . http_build_query($params);
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			$this->logError('Nearby search request failed', [
-				'error' => $response->get_error_message(),
-				'lat' => $lat,
-				'lng' => $lng,
-				'radius' => $radius
-			]);
-			return [];
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-
-		if ($data['status'] === 'OK' && !empty($data['results'])) {
-			$return = $data['results'];
-			$this->cache->set($key, $return);
-			return $return;
-		}
-
-		return [];
-	}
-
-	/**
-	 * Calculate distance between two points
-	 */
-	public function calculateDistance(array $origin, array $destination, string $mode = 'driving'): ?array
-	{
-		if (!$this->isSetUp()) {
-			return null;
-		}
-
-		// Handle different input formats
-		$origin_str = is_array($origin) ? "{$origin['lat']},{$origin['lng']}" : $origin;
-		$dest_str = is_array($destination) ? "{$destination['lat']},{$destination['lng']}" : $destination;
-
-		$params = [
-			'origins' => $origin_str,
-			'destinations' => $dest_str,
-			'mode' => $mode,
-			'units' => 'metric',
-			'key' => $this->private_key
-		];
-
-		$key = $this->cache->generateKey(['distance', $origin_str, $dest_str, $mode]);
-		$cached = $this->cache->get($key);
-		if ($cached) {
-			return $cached;
-		}
-
-		$url = $this->apiBase . 'distancematrix/json?' . http_build_query($params);
-		$response = wp_remote_get($url);
-
-		if (is_wp_error($response)) {
-			$this->logError('Distance calculation request failed', [
-				'error' => $response->get_error_message(),
-				'origin' => $origin_str,
-				'destination' => $dest_str
-			]);
-			return null;
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-
-		if ($data['status'] === 'OK' && !empty($data['rows'][0]['elements'][0])) {
-			$element = $data['rows'][0]['elements'][0];
-			if ($element['status'] === 'OK') {
-				$return = [
-					'distance' => $element['distance'],
-					'duration' => $element['duration'],
-					'mode' => $mode
-				];
-				$this->cache->set($key, $return);
-				return $return;
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Get the JavaScript API URL with key
-	 */
-	public function getJavaScriptApiUrl(array $libraries = ['places', 'marker', 'geometry']): string
-	{
-		if (empty($this->public_key)) {
-			return '';
-		}
-
-		$params = [
-			'key' => $this->public_key,
-			'libraries' => implode(',', $libraries),
-			'callback' => 'initGoogleMaps',
-		];
-
-		return $this->apiBase . 'js?' . http_build_query($params);
-	}
-
-	/**
-	 * Enqueue Google Maps script when needed
-	 */
-	public function maybeEnqueueMapsScript(): void
-	{
-		// Only enqueue if we're on a page that needs maps
-		if (!$this->shouldEnqueueMaps()) {
-			return;
-		}
-
-		if (!$this->isSetUp()) {
-			return;
-		}
-
-		$this->ensureInitialized();
-
 		// Register Google Maps API
 		wp_register_script(
 			'google-maps-api',
@@ -499,36 +135,69 @@
 				'strategy' => 'defer'
 			]
 		);
+	}
+		public function getJavaScriptApiUrl(array $libraries = ['places', 'marker', 'geometry']): string
+		{
+			if (empty($this->getPublicKey())) {
+				return '';
+			}
+
+			$params = [
+				'key' => $this->getPublicKey(),
+				'libraries' => implode(',', $libraries),
+				'callback' => 'initGoogleMaps',
+			];
+
+			return $this->apiBase . 'js?' . http_build_query($params);
+		}
+
+
+	public function maybeEnqueueMapsScript():void
+	{
+		if (!$this->isSetUp()) {
+			return;
+		}
+		if (!$this->shouldEnqueueMaps()) {
+			return;
+		}
 
 		// Add initialization script
-		wp_add_inline_script('google-maps-api', '
-		// Set map defaults
-		window.jvbMapDefaults = {
-			lat: ' . self::DEFAULT_LAT . ',
-			lng: ' . self::DEFAULT_LNG . ',
-			zoom: ' . self::DEFAULT_ZOOM . ',
-			mapId: "' . esc_js($this->map_id) . '"
-		};
+		wp_add_inline_script('google-maps-api',
+			sprintf(
+			'
+				// Set map defaults
+				window.jvbMapDefaults = {
+					lat: %s,
+					lng: %s,
+					zoom: %s,
+					mapId: "%s"
+				};
 
-		// Google Maps initialization callback
-		window.initGoogleMaps = function() {
-			// Dispatch ready event
-			document.dispatchEvent(new Event("googleMapsReady"));
+				// Google Maps initialization callback
+				window.initGoogleMaps = function() {
+					// Dispatch ready event
+					document.dispatchEvent(new Event("googleMapsReady"));
 
-			// Call any registered callbacks
-			if (window.googleMapsCallbacks && Array.isArray(window.googleMapsCallbacks)) {
-				window.googleMapsCallbacks.forEach(function(callback) {
-					if (typeof callback === "function") {
-						callback();
+					// Call any registered callbacks
+					if (window.googleMapsCallbacks && Array.isArray(window.googleMapsCallbacks)) {
+						window.googleMapsCallbacks.forEach(function(callback) {
+							if (typeof callback === "function") {
+								callback();
+							}
+						});
+						window.googleMapsCallbacks = [];
 					}
-				});
-				window.googleMapsCallbacks = [];
-			}
-		};
+				};
 
-		// Initialize callback queue
-		window.googleMapsCallbacks = window.googleMapsCallbacks || [];
-		', 'before');
+				// Initialize callback queue
+				window.googleMapsCallbacks = window.googleMapsCallbacks || [];
+				',
+				$this->getDefaultLat(),
+				$this->getDefaultLng(),
+				$this->getDefaultZoom(),
+				esc_js($this->getMapID())
+			),
+			'before');
 
 		// Enqueue scripts
 		wp_enqueue_script('google-maps-api');
@@ -541,134 +210,53 @@
 			'service' => $this->service_name
 		]);
 	}
-
-	/**
-	 * Check if maps should be enqueued on current page
-	 */
-	private function shouldEnqueueMaps(): bool
-	{
-		// Check for shortcodes
-		if (is_singular() && get_post_meta(get_the_ID(), BASE . 'has_map', true) === true) {
-			return true;
-		}
-
-		if (!empty(Registrar::withFeature('is_content', 'term')) && get_term_meta(get_queried_object_id(), BASE . 'has_map', true) === true) {
-			return true;
-		}
-
-		// Check for specific page templates or post types
-		if (is_singular(['shop', 'artist', 'location'])) {
-			return true;
-		}
-
-		// Check for admin pages that need maps
-		if (is_admin()) {
-			$screen = get_current_screen();
-			if ($screen && in_array($screen->post_type, ['shop', 'artist', 'location'])) {
+		protected function shouldEnqueueMaps():bool
+		{
+			//See if it's a singular post/page that has the map feature flagged
+			if (is_singular() && get_post_meta(get_the_ID(), BASE.'has_map', true) === true) {
 				return true;
 			}
+			if (is_tax() && get_term_meta(get_queried_object_id(), BASE . 'has_map', true) === true) {
+				return true;
+			}
+
+			// Allow filtering
+			return apply_filters('jvb_should_enqueue_maps', false);
 		}
 
-		// Allow filtering
-		return apply_filters('jvb_should_enqueue_maps', false);
-	}
-
-	/**
-	 * Parse address components from Google response
-	 */
-	private function parseAddressComponents(array $components): array
+	public function getServiceDescription():string
 	{
-		$parsed = [
-			'street_number' => '',
-			'street' => '',
-			'city' => '',
-			'province' => '',
-			'country' => '',
-			'postal_code' => ''
-		];
-
-		foreach ($components as $component) {
-			$types = $component['types'];
-
-			if (in_array('street_number', $types)) {
-				$parsed['street_number'] = $component['long_name'];
-			}
-			if (in_array('route', $types)) {
-				$parsed['street'] = $component['long_name'];
-			}
-			if (in_array('locality', $types)) {
-				$parsed['city'] = $component['long_name'];
-			}
-			if (in_array('administrative_area_level_1', $types)) {
-				$parsed['province'] = $component['short_name'];
-			}
-			if (in_array('country', $types)) {
-				$parsed['country'] = $component['short_name'];
-			}
-			if (in_array('postal_code', $types)) {
-				$parsed['postal_code'] = $component['long_name'];
-			}
-		}
-
-		// Combine street number and street name
-		if ($parsed['street_number'] && $parsed['street']) {
-			$parsed['street'] = $parsed['street_number'] . ' ' . $parsed['street'];
-		}
-
-		return $parsed;
+		return 'Allows for a map field, utilizing Google Maps';
 	}
-
-	/**
-	 * Get Map ID
-	 */
-	public function getMapId(): string
+	/**********************************************************************
+	 * UTILITY
+	**********************************************************************/
+	protected function getPrivateKey():string|false
 	{
-		return $this->map_id;
+		return $this->loadCredentials()['private_key']??false;
 	}
-
-	/**
-	 * Get default map settings
-	 */
-	public function getDefaultMapSettings(): array
+	protected function getPublicKey():string|false
 	{
-		return [
-			'center' => [
-				'lat' => self::DEFAULT_LAT,
-				'lng' => self::DEFAULT_LNG
-			],
-			'zoom' => $this->credentials['default_zoom'] ?? self::DEFAULT_ZOOM,
-			'mapId' => $this->map_id,
-			'styles' => $this->getMapStyles()
-		];
+		return $this->loadCredentials()['public_key']??false;
 	}
 
-	/**
-	 * Get map styles based on theme
-	 */
-	private function getMapStyles(): array
+	protected function getMapID():string|false
 	{
-		$theme = $this->credentials['theme'] ?? 'default';
-
-		switch ($theme) {
-			case 'dark':
-				return [
-					['elementType' => 'geometry', 'stylers' => [['color' => '#242f3e']]],
-					['elementType' => 'labels.text.fill', 'stylers' => [['color' => '#746855']]],
-					['elementType' => 'labels.text.stroke', 'stylers' => [['color' => '#242f3e']]],
-					['featureType' => 'road', 'elementType' => 'geometry', 'stylers' => [['color' => '#38414e']]],
-					['featureType' => 'water', 'elementType' => 'geometry', 'stylers' => [['color' => '#17263c']]]
-				];
-
-			case 'minimal':
-				return [
-					['featureType' => 'poi', 'stylers' => [['visibility' => 'off']]],
-					['featureType' => 'transit', 'stylers' => [['visibility' => 'off']]]
-				];
-
-			default:
-				return [];
-		}
+		return $this->loadCredentials()['map_id']??false;
 	}
+	protected function getDefaultZoom():string|false
+	{
+		return $this->loadCredentials()['default_zoom']??false;
+	}
+	protected function getDefaultLng():string|false
+	{
+		return $this->loadCredentials()['default_lng']??false;
+	}
+	protected function getDefaultLat():string|false
+	{
+		return $this->loadCredentials()['default_lat']??false;
+	}
+
 
 	/**
 	 * Generate map application links
@@ -683,152 +271,108 @@
 			'waze' => "https://waze.com/ul?ll={$lat},{$lng}&navigate=yes"
 		];
 	}
-
-	/**
-	 * Render a frontend display map
-	 */
-	public function renderDisplayMap(array $location, array $options = []): string
+	private function parseAddressComponents(array $components):array
 	{
-		if (!$this->isSetUp() || empty($location['lat']) || empty($location['lng'])) {
-			return '';
-		}
-
-		$map_id = 'map-' . uniqid();
-		$lat = (float)$location['lat'];
-		$lng = (float)$location['lng'];
-		$address = $location['address'] ?? '';
-
-		$map_options = array_merge([
-			'height' => '250px',
-			'zoom' => 15,
-			'show_marker' => true,
-			'show_info_window' => true,
-			'info_content' => $address,
-			'interactive' => true
-		], $options);
-
-		ob_start();
-		?>
-		<div id="<?= esc_attr($map_id); ?>"
-			 class="jvb-location-map-display"
-			 style="height: <?= esc_attr($map_options['height']); ?>; border-radius: 8px; overflow: hidden;">
-		</div>
-
-		<script>
-			document.addEventListener('DOMContentLoaded', function() {
-				function initDisplayMap() {
-					if (!window.jvbMaps || !window.jvbMaps.ready) {
-						setTimeout(initDisplayMap, 100);
-						return;
-					}
-
-					window.jvbMaps.createDisplayMap('<?= esc_js($map_id); ?>', {
-						lat: <?= $lat; ?>,
-						lng: <?= $lng; ?>,
-						zoom: <?= intval($map_options['zoom']); ?>,
-						show_marker: <?= $map_options['show_marker'] ? 'true' : 'false'; ?>,
-						show_info_window: <?= $map_options['show_info_window'] ? 'true' : 'false'; ?>,
-						info_content: <?= json_encode($map_options['info_content']); ?>,
-						interactive: <?= $map_options['interactive'] ? 'true' : 'false'; ?>
-					});
+		$parsed = [];
+		$check = [
+			'street_number' => 'long_name',
+			'street' 		=> 'long_name',
+			'city' 			=> 'long_name',
+			'province' 		=> 'short_name',
+			'country'		=> 'short_name',
+			'postal_code'	=> 'long_name'
+		];
+		foreach ($components as $c) {
+			$types = $c['types'];
+			foreach ($check as $ch => $index) {
+				if (in_array($ch, $types)) {
+					$parsed[$ch] = $c[$index];
 				}
-
-				if (typeof google !== 'undefined' && google.maps) {
-					initDisplayMap();
-				} else {
-					document.addEventListener('googleMapsReady', initDisplayMap);
-				}
-			});
-		</script>
-		<?php
-
-		return ob_get_clean();
-	}
-
-	/**
-	 * Render map application links
-	 */
-	public function renderMapLinks(float $lat, float $lng, ?string $address = null, array $options = []): string
-	{
-		$links = $this->generateMapLinks($lat, $lng, $address);
-
-		$link_options = array_merge([
-			'show_icons' => true,
-			'style' => 'buttons', // 'buttons' or 'text'
-			'include' => ['google', 'apple'], // which links to show
-			'target' => '_blank'
-		], $options);
-
-		$output = '<div class="jvb-map-links' . ($link_options['style'] === 'buttons' ? ' button-style' : ' text-style') . '">';
-
-		foreach ($link_options['include'] as $provider) {
-			if (!isset($links[$provider])) continue;
-
-			$icon = '';
-			$label = '';
-
-			switch ($provider) {
-				case 'google':
-					$icon = $link_options['show_icons'] ? jvbIcon('google') : '';
-					$label = 'Google Maps';
-					break;
-				case 'apple':
-					$icon = $link_options['show_icons'] ? jvbIcon('apple') : '';
-					$label = 'Apple Maps';
-					break;
-				case 'waze':
-					$icon = $link_options['show_icons'] ? jvbIcon('waze') : '';
-					$label = 'Waze';
-					break;
 			}
-
-			$output .= sprintf(
-				'<a href="%s" target="%s" class="map-link %s-maps-link">%s<span>%s</span></a>',
-				esc_url($links[$provider]),
-				esc_attr($link_options['target']),
-				esc_attr($provider),
-				$icon,
-				esc_html($label)
-			);
 		}
-
-		$output .= '</div>';
-
-		return $output;
+		if (!empty($parsed['street_number']) && !empty($parsed['street'])) {
+			$parsed['street'] = $parsed['street_number'] . ' ' . $parsed['street'];
+		}
+		return $parsed;
 	}
 
-	/**
-	 * Format address for display
-	 */
-	public function formatAddress(array $location, string $format = 'full'): string
+	public function reverseGeocode(float $lat, float $lng):array|false
 	{
-		if (!empty($location['address'])) {
-			return $location['address'];
+		if (!$this->isSetUp()) {
+			return false;
+		}
+		$params = [
+			'latlng'	=> "{$lat},{$lng}",
+			'key'		=> $this->getPrivateKey()
+		];
+		return $this->getRequest(
+			sprintf(
+				'%s/geocode/json',
+				$this->apiBase,
+			),
+			$params
+		);
+	}
+		protected function handleGeocodeResponse(array $data, string $method):array
+		{
+			if (!array_key_exists('status', $data) || $data['status'] !== 'OK' || !array_key_exists('results', $data) || empty($data['results'])) {
+				return $this->response(false, 'Invalid response provided');
+			}
+			$result = $data['results'][0];
+			return $this->response(true, 'Successfully reversed geocode',[
+				'lat'				=> $result['geometry']['location']['lat'],
+				'lng'				=> $result['geometry']['location']['lng'],
+				'formatted_address'	=> $result['formatted_address'],
+				'place_id'			=> $result['place_id']??'',
+				'components'		=> $this->parseAddressComponents($result['address_compenents']??[])
+			]);
 		}
 
-		// Build address from components if no formatted address
-		$parts = [];
+	public function geocodeAddress(string $address):array|false
+	{
+		if (!$this->isSetUp()) {
+			return false;
+		}
+		$params = [
+			'address'	=> $address,
+			'key'		=> $this->getPrivateKey()
+		];
+		return $this->getRequest(
+			sprintf(
+				'%s/geocode/json',
+				$this->apiBase
+			)
+		);
+	}
 
-		switch ($format) {
-			case 'full':
-				if (!empty($location['street'])) $parts[] = $location['street'];
-				if (!empty($location['city'])) $parts[] = $location['city'];
-				if (!empty($location['province'])) $parts[] = $location['province'];
-				if (!empty($location['postal_code'])) $parts[] = $location['postal_code'];
-				if (!empty($location['country'])) $parts[] = $location['country'];
-				break;
+	protected function getRequestHeaders(): array
+	{
+		return [
+			'Accept' => 'application/json',
+			'Content-Type' => 'application/json'
+		];
+	}
 
-			case 'compact':
-				if (!empty($location['city']) && !empty($location['province'])) {
-					$parts[] = $location['city'] . ', ' . $location['province'];
-				}
-				break;
+	protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
+	{
+		if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
+			return $this->response(false, 'Rate limit exceeded');
+		}
+		$endpoint = str_replace($this->apiBase, '', $endpoint);
+		return  match ($endpoint) {
+			'/geocode/json'	=> $this->handleGeocodeResponse($response, $method),
+			default => $this->response(false, 'Unhandled response for '.$endpoint.' endpoint, with method: '.$method),
+		};
+	}
 
-			case 'city_only':
-				if (!empty($location['city'])) $parts[] = $location['city'];
-				break;
+	protected function performConnectionTest():bool
+	{
+		if (!$this->isSetUp()) {
+			return false;
 		}
 
-		return implode(', ', $parts);
+		$test = 'Edmonton, Alberta, Canada';
+		$result = $this->geocodeAddress($test);
+		return $result['success'];
 	}
 }
diff --git a/inc/integrations/services/GoogleMyBusiness.php b/inc/integrations/services/GoogleMyBusiness.php
index 157181e..89f4f58 100644
--- a/inc/integrations/services/GoogleMyBusiness.php
+++ b/inc/integrations/services/GoogleMyBusiness.php
@@ -1,7 +1,15 @@
 <?php
 namespace JVBase\integrations\services;
+use Exception;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
-use JVBase\meta\Meta;
+use JVBase\integrations\OAuth;
+use JVBase\integrations\OAuthCredentials;
+use JVBase\integrations\OAuthURLs;
+use JVBase\integrations\Requests;
+use JVBase\integrations\UserConnection;
+
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -9,2966 +17,237 @@
 
 class GoogleMyBusiness extends Integrations
 {
-	protected array $allowedContent = [
-		'menu_item',
-		'post',
-		'event',
-	];
-	private ?string $access_token = null;
+	use Admin, Instance, OAuth, Requests, UserConnection;
+
 	protected string $readMask = 'name,title,storefrontAddress,metadata,openInfo,storeCode,categories,phoneNumbers,labels,specialHours';
-	private ?string $location = null;
-	private ?string $refresh_token = null;
-	private ?string $client_id = null;
-	private ?string $client_secret = null;
-	private ?string $account_id = null;
 
-	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)
+	protected function __construct()
 	{
 		$this->service_name = 'gmb';
 		$this->title = 'Google My Business';
 		$this->icon = 'storefront';
-		$this->cacheName = ($userID)? 'gmb_'.$userID : 'gmb';
+
 		$this->canSync = [
 			'initial'	=> true,
 			'update'	=> true,
 			'delete'	=> true,
 		];
-		$this->apiBase = [
-			'base' => 'https://mybusinessbusinessinformation.googleapis.com',
-			'accounts' => 'https://mybusinessaccountmanagement.googleapis.com',
-			'posts' => 'https://mybusiness.googleapis.com',
-			'performance'=> 'https://businessprofileperformance.googleapis.com',
-			'v4' => 'https://mybusiness.googleapis.com/v4',
-		];
+		$this->supportsWebp = false;
 
-		$this->apiEndpoints = [
-			'/accounts/[^/]+/locations/[^/]+/reviews',
-			'/accounts/[^/]+/locations/[^/]+/foodMenus',
-			'/v4/accounts/[^/]+/locations/[^/]+/media',
-			'/v4/accounts/[^/]+/locations/[^/]+/localPosts',
-			'/v4/accounts/[^/]+/locations/[^/]+/localPosts/[^/]+',
-			'/accounts/[^/]+/locations/[^/]+',
-			'/v1/accounts/[^/]+/locations', // Matches /v1/accounts/{accountId}/locations
-			'/v1/accounts',
-			// Add pattern matching for dynamic location endpoints
-			'/v1/locations/[^/]+',  // Matches /v1/locations/{locationId}
-		];
+		$this->defineFields();
+		$this->defineInstructions();
+		$this->defineOAuth();
+		$this->defineActions();
 
-		$this->isOAuthService = true;
-		$this->oauth = [
-			'authorize' => 'https://accounts.google.com/o/oauth2/v2/auth',
-			'token' => 'https://oauth2.googleapis.com/token',
-			'revoke' => 'https://oauth2.googleapis.com/revoke',
-			'scopes' => [
+		parent::__construct();
+	}
+		protected function defineFields():void
+		{
+			$client_id = $this->addField(
+				'client_id',
+				'OAuth Client ID',
+				'text',
+				'manage_options'
+			);
+				$client_id->setRequired();
+				$client_id->setHint('From your Google Cloud Console OAuth credentials.');
+
+			$secret = $this->addField(
+				'client_secret',
+				'OAuth Client Secret',
+				'text',
+				'manage_options',
+			);
+				$secret->setSubType('password');
+				$secret->setRequired();
+
+			if ($this->getAccessToken()) {
+				$accs = $this->addField('accounts', 'Accounts', 'select', [$this, 'testUserCanConnect']);
+					$accs->setHidden();
+					$accs->setOptions($this->getAccountOptions());
+
+				$acc = $this->addField('account_id', 'Set Account', 'select', [$this, 'testUserCanConnect']);
+					$acc->setOptions($this->loadCredentials()['accounts']??[]);
+					$acc->setHint('Select the account to connect to.');
+
+				if ($this->getAccountID()) {
+					$locs = $this->addField('locations', 'Locations', 'select', [$this, 'testUserCanConnect']);
+					$locs->setHidden();
+					$locs->setOptions($this->getLocationOptions());
+
+					$loc = $this->addField('location_id', 'Set Location', 'select', [$this, 'testUserCanConnect']);
+					$loc->setOptions($this->loadCredentials()['locations']??[]);
+					$loc->setHint('Select the location to connect to.');
+				}
+			}
+
+		}
+			public function testUserCanConnect():bool
+			{
+				$user = get_current_user_id();
+				return $this->userCanConnect($user);
+			}
+		protected function defineInstructions():void
+		{
+			$this->instructions = [
+				'Go to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a>',
+				'Create OAuth 2.0 Client ID credentials',
+				'Enable Google My Business API',
+				'Add redirect URI: <code>'.esc_html($this->getRedirectUri()).'</code>',
+				'Use the "Authorize" button below to complete OAuth flow'
+			];
+		}
+		protected function defineOAuth():void
+		{
+			$this->oauth = new OAuthURLs(
+				'https://accounts.google.com/o/oauth2/v2/auth',
+				'https://oauth2.googleapis.com/token',
+				'https://oauth2.googleapis.com/revoke'
+			);
+
+			$this->oathScopes = [
 				'https://www.googleapis.com/auth/business.manage',
 				'https://www.googleapis.com/auth/userinfo.email',
 				'https://www.googleapis.com/auth/userinfo.profile',
 				'https://www.googleapis.com/auth/plus.business.manage'
-			]
-		];
-
-		$this->supportsWebp = false;
-
-		$this->rate_limits = [
-			'per_second' 	=> 15,       // Allow 15 requests in burst window
-			'per_minute' 	=> 60,      // 1-minute burst window
-			'per_hour' 		=> 500      // 500 requests per hour (well below Google's limits)
-		];
-
-		$this->fields = [
-			'client_id'	=> [
-				'type'	=> 'text',
-				'label'	=> 'OAuth Client ID',
-				'placeholder'=> 'Enter Google OAuth Client ID',
-				'hint'	=> 'From your Google Cloud Console OAuth credentials.',
-				'required'	=> true,
-			],
-			'client_secret'	=> [
-				'type'	=> 'text',
-				'subtype' => 'password',
-				'label'	=> 'OAuth Client Secret',
-				'required'	=> true,
-			],
-//			'access_token'	=> [
-//				'type'	=> 'text',
-//				'subtype'	=> 'password',
-//				'label'	=> 'Access Token',
-//				'hint'	=> 'Generated automagically after OAuth authorization.'
-//			],
-//			'refresh_token'	=> [
-//				'type'	=> 'text',
-//				'subtype'	=> 'password',
-//				'label'	=> 'Refresh Token',
-//				'hint'	=> 'Generated automagically after OAuth authorization.'
-//			]
-		];
-
-		$this->advanced = [
-
-		];
-
-		$this->instructions = [
-			'Go to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a>',
-			'Create OAuth 2.0 Client ID credentials',
-			'Enable Google My Business API',
-			'Add redirect URI: <code>'.esc_html($this->getRedirectUri()).'</code>',
-			'Use the "Authorize" button below to complete OAuth flow'
-		];
-
-		parent::__construct();
-
-		$this->actions = array_merge(
-			$this->actions,
-			[
-				'update_location'	=> 'handleUpdateLocation',
-				'select_account'	=> 'handleSelectAccount',
-				'select_location'	=> 'handleSelectLocation',
-				'check_oauth_status'=> 'checkOAuthStatus'
-			]
-		);
-		$this->buttons = array_merge(
-			$this->buttons,
-			[
-				'check_oauth_status' => 'Check OAuth Status'
-			]
-		);
-
-		if (JVB_TESTING) {
-			$this->cache->flush();
+			];
 		}
-	}
+		protected function defineActions():void
+		{
+			$this->addAction(
+				'Update Location',
+				[$this, 'handleUpdateLocation'],
+				'update_location',
+			);
+		}
+			public function handleUpdateLocation(array $data):array
+			{
+				if (empty($data['location_id']??'')) {
+					return $this->response(false, 'No location selected');
+				}
+
+				$credentials = [
+					'location_id'	=> sanitize_text_field($data['location_id'])
+				];
+				return $this->saveCredentials($credentials, false);
+			}
 
 	protected function initialize(): void
 	{
-		if (empty($this->credentials)) {
-			$this->loadCredentials();
-		}
-		$this->access_token = (array_key_exists('access_token', $this->credentials)) ? $this->credentials['access_token'] : null;
-		$this->refresh_token = (array_key_exists('refresh_token', $this->credentials)) ? $this->credentials['refresh_token'] : null;
-		$this->client_id = (array_key_exists('client_id', $this->credentials)) ? $this->credentials['client_id'] : null;
-		$this->client_secret = (array_key_exists('client_secret', $this->credentials)) ? $this->credentials['client_secret'] : null;
-		$this->location = (array_key_exists('location', $this->credentials)) ? $this->credentials['location'] : null;
-		$this->account_id = (array_key_exists('account', $this->credentials)) ? $this->credentials['account'] : null;
-
-		if ($this->account_id) {
-			$this->apiEndpoints[] = "/v1/{$this->account_id}/locations";
-		}
-		if ($this->location) {
-			$locationName = $this->getSelectedLocationResourceName();
-			$this->apiEndpoints[] = "/v4/{$locationName}/localPosts";
-		}
-	}
-
-	protected function performConnectionTest(): bool
-	{
-		try {
-			$accounts = $this->getAccounts(true);
-			return !empty($accounts);
-		} catch (\Exception $e) {
-			return false;
-		}
-	}
-
-	/**
-	 * Check if response contains an error - Google-specific
-	 */
-	protected function isErrorResponse(array $response): bool
-	{
-		// Google APIs return errors in this format:
-		// {"error": {"code": 401, "message": "...", "status": "UNAUTHENTICATED"}}
-		return isset($response['error']) && isset($response['error']['code']);
+		// TODO: Implement initialize() method.
 	}
 
 	protected function getRequestHeaders(): array
 	{
-		return [
-			'Authorization' => 'Bearer ' . $this->access_token,
-			'Content-Type' => 'application/json',
-			'Accept' => 'application/json'
-		];
+		// TODO: Implement getRequestHeaders() method.
 	}
 
-	protected function addOAuthParams(array $params): array
+	protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
 	{
-		$params['access_type'] = 'offline';
-		$params['prompt'] = 'consent';
-		return $params;
+		if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
+			return $this->response(false, 'Rate limit exceeded');
+		}
+
+		return match($endpoint) {
+			'https://mybusinessaccountmanagement.googleapis.com/v1/accounts' => $this->handleGetAccountsResponse($response, $method),
+			sprintf('https://mybusinessbusinessinformation.googleapis.com/v1/%s/locations', $this->getAccountID()??'') => $this->handleGetLocationsResponse($response, $method),
+			default => $this->response(false, 'Unhandled response for '.$endpoint.' endpoint, with method: '.$method)
+		};
 	}
 
-	protected function addCredentialData(array $credentials, array $tokens): array
+	protected function refreshAccessToken(OAuthCredentials $credentials): array
 	{
-		// Preserve GMB-specific settings
-		$existing = $this->credentials;
-		$credentials['location'] = $existing['location'] ?? null;
-		$credentials['account'] = $existing['account'] ?? null;
-		return $credentials;
+		// TODO: Implement refreshAccessToken() method.
 	}
 
-	public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings):void
+	/*********************************************************
+	 * UTILITY
+	*********************************************************/
+	protected function getAccountID():string|false
 	{
-
-		if (!$this->hasOAuthCredentials()) {
-			error_log('OAuth Not set up for '.$this->service_name);
-			return;
-		}
-
-		$this->queueOperation(self::$syncTo, [
-			'items'		=> [$postID],
-			'user'		=> user_can($post->post_author, 'manage_options') ? null : $post->post_author
-		], [
-			'delay'	=> 60
-		]);
-		Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
+		return $this->loadCredentials()['account_id']??false;
 	}
-
-	public function processOperation(\WP_Error|array $result, object $operation, array $data):\WP_Error|array
+	protected function getAccountOptions(bool $force = false):array
 	{
-		try {
-			$gmb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this;
-			$gmb->ensureInitialized();
-			switch ($operation->type) {
-				case 'gmb_create_menu_item':
-				case 'gmb_update_menu_item':
-				case 'gmb_menu_update':
-				case 'gmb_menu_update_' . $this->userID:
-					return $gmb->processMenuUpdate($operation, $data);
-				case 'gmb_create_event':
-				case 'gmb_update_event':
-					return $gmb->syncEventPost($operation, $data);
-				case 'gmb_create_offer':
-				case 'gmb_update_offer':
-					return $gmb->syncOfferPost($operation, $data);
-				case 'gmb_create_post':
-				case 'gmb_update_post':
-					return $gmb->syncStandardPost($operation, $data);
-				case 'gmb_specialHours':
-					return $gmb->setSpecialHours($data['date'], $data['data']);
-				case 'gmb_business_hours':
-					return $gmb->updateBusinessHours($data);
-				case 'gmb_business_info':
-					return $gmb->updateBusinessInfo($data);
-				case 'gmb_update_attributes':
-					return $gmb->updateAttributes($data);
-				default:
-					return $result;
-			}
-		} catch (\Exception $e) {
-			error_log('[GMB] Operation processing error: ' . $e->getMessage());
-			return [
-				'success' => false,
-				'result' => $e->getMessage()
-			];
+		$stored = $this->loadCredentials()['accounts']??[];
+		if ($force || empty($stored)) {
+			$stored = $this->getAccounts();
 		}
+		return $stored;
 	}
 
-	protected function syncEventPost(object $operation, array $data):\WP_Error|array
+	protected function getLocationID():string|false
 	{
-		$location = $this->getSelectedLocationResourceName();
-
-		if (!$location) {
-			return [
-				'success' => false,
-				'result'  => 'No location Name set.'
-			];
-		}
-
-		$postID = $data['post_id'];
-		$meta = Meta::forPost($postID);
-		$fields = [
-			'start_date',
-			'end_date',
-			'start_time',
-			'end_time',
-			'post_excerpt',
-			'post_title',
-			'post_thumbnail',
-			"_{$this->service_name}_item_id"
-		];
-
-		$data = [
-			'summary'		=> $fields['post_excerpt'],
-			'topic_type'	=> 'EVENT',
-			'event'			=> [
-				'title'	=> $fields['post_title'],
-				'start_date'	=> $this->formatDate($fields['start_date']),
-				'end_date'		=> $this->formatDate(($fields['end_date'] === '') ? $fields['start_date'] : $fields['end_date']),
-				'start_time'	=> $this->formatTime($fields['start_time']),
-				'end_time'		=> $this->formatTime($fields['end_time']),
-			]
-		];
-		if ($fields['post_thumbnail'] !== '') {
-			$url = wp_get_attachment_url($fields['post_thumbnail']);
-			if ($url) {
-				$data['media'] = [
-					['mediaFormat' => 'PHOTO', 'sourceUrl' => $url]
-				];
-			}
-		}
-
-		if ($fields["_{$this->service_name}_item_id"] !== '') {
-			$result =  $this->updatePost($fields["_{$this->service_name}_item_id"], $data);
-		} else {
-			$result = $this->createPost($data);
-			$meta->set("_{$this->service_name}_item_id", $result['name']);
-		}
-
-		return [
-			'success'	=> !empty($result),
-			'result'	=>  $result
-		];
+		return $this->loadCredentials()['location_id']??false;
 	}
-
-	protected function syncOfferPost(object $operation, array $data):\WP_Error|array
+	protected function getLocationOptions(bool $force = false):array
 	{
-		$location = $this->getSelectedLocationResourceName();
-
-		if (!$location) {
-			return [
-				'success' => false,
-				'result'  => 'No location Name set.'
-			];
+		$stored = $this->loadCredentials()['locations']??[];
+		if ($force || empty($stored)) {
+			$stored = $this->getLocations();
 		}
-
-		$postID = $data['post_id'];
-		$meta = Meta::forPost($postID);
-		$fields = [
-			'post_excerpt',
-			'post_title',
-			'coupon_code',
-			'termsConditions',
-			'post_thumbnail',
-			"_{$this->service_name}_item_id"
-		];
-
-		$data = [
-			'summary'		=> $fields['post_excerpt'],
-			'topic_type'	=> 'OFFER',
-			'offer'			=> [
-				'couponCode'	=> ($fields['coupon_code'] === '') ? $fields['post_title'] : $fields['coupon_code'],
-				'redeemOnlineUrl'=> get_the_permalink($postID),
-//				'termsConditions'=> get_bloginfo('terms_condition') TODO: setup terms and Conditions
-			]
-		];
-		if ($fields['post_thumbnail'] !== '') {
-			$url = wp_get_attachment_url($fields['post_thumbnail']);
-			if ($url) {
-				$data['media'] = [
-					['mediaFormat' => 'PHOTO', 'sourceUrl' => $url]
-				];
-			}
-		}
-
-		if ($fields["_{$this->service_name}_item_id"] !== '') {
-			$result =  $this->updatePost($fields["_{$this->service_name}_item_id"], $data);
-		} else {
-			$result = $this->createPost($data);
-			$meta->set("_{$this->service_name}_item_id", $result['name']);
-		}
-
-		return [
-			'success'	=> !empty($result),
-			'result'	=>  $result
-		];
+		return $stored;
 	}
-
-
-	protected function syncStandardPost(object $operation, array $data):\WP_Error|array
+	protected function getLocations():array|false
 	{
-		$location = $this->getSelectedLocationResourceName();
-
-		if (!$location) {
-			return [
-				'success' => false,
-				'result'  => 'No location Name set.'
-			];
-		}
-
-		$postID = $data['post_id'];
-		$meta = Meta::forPost($postID);
-		$fields = [
-			'post_excerpt',
-			'post_title',
-			'post_thumbnail',
-			"_{$this->service_name}_item_id"
-		];
-
-		$data = [
-			'summary'		=> $fields['post_excerpt'],
-			'topic_type'	=> 'STANDARD',
-			'call_to_action'			=> [
-				'type'	=> 'learn_more',
-				'url'=> get_the_permalink($postID),
-			]
-		];
-		if ($fields['post_thumbnail'] !== '') {
-			$url = wp_get_attachment_url($fields['post_thumbnail']);
-			if ($url) {
-				$data['media'] = [
-					['mediaFormat' => 'PHOTO', 'sourceUrl' => $url]
-				];
-			}
-		}
-
-		if ($fields["_{$this->service_name}_item_id"] !== '') {
-			$result =  $this->updatePost($fields["_{$this->service_name}_item_id"], $data);
-		} else {
-			$result = $this->createPost($data);
-			$meta->set("_{$this->service_name}_item_id", $result['name']);
-		}
-
-		return [
-			'success'	=> !empty($result),
-			'result'	=>  $result
-		];
-	}
-
-	protected function processMenuUpdate($operation, $data) {
-		try {
-
-			$postID = $data['post_id'];
-			if (!$postID) {
-				return [
-					'success'	=> false,
-					'result'	=> 'No post ID set'
-				];
-			}
-			$post = get_post($postID);
-			$postType = jvbNoBase($post->post_type);
-
-			$args = [
-				'post_type' => jvbCheckBase($postType),
-				'post_status' => 'publish',
-				'posts_per_page' => -1,
-				'meta_query' => [
-					[
-						'key' => BASE.'_keep_synced_' . $this->service_name,
-						'value' => '1',
-						'compare' => '='
-					]
-				]
-			];
-
-			$menu_items = get_posts($args);
-
-			if (empty($menu_items)) {
-				return [
-					'success' => true,
-					'result' => 'No menu items to sync'
-				];
-			}
-
-			$gmb = self::getInstance($data['userID']??null);
-
-			// Build the complete menu structure
-			$menu_data = $gmb->collectMenu($menu_items);
-
-			// Send to Google My Business API
-			$result = $this->updateFoodMenus($menu_data);
-			return [
-				'success' => !is_null($result),
-				'result' => (is_array($result)) ? array_merge($result, ['menu'=>$menu_data]) : ['menu'=>$menu_data],
-			];
-
-		} catch (\Exception $e) {
-			error_log('GMB Menu Update Error: ' . $e->getMessage());
-			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage()
-			];
-		}
-	}
-
-	/***************************************************************
-		API
-	 ***************************************************************/
-	private function getSelectedLocationResourceName(): ?string
-	{
-		if (empty($this->credentials['location'])) {
-			return null;
-		}
-
-		// If already a full resource name, return as-is
-		if (str_starts_with($this->credentials['location'], 'locations/')) {
-			return $this->credentials['location'];
-		}
-
-		// Otherwise, it's a storeCode - need to find the full resource name
-		try {
-			$accounts = $this->getAccounts();
-			foreach ($accounts as $account) {
-				$locations = $this->getLocations($account['name']);
-
-				foreach ($locations as $location) {
-					if ($location['storeCode'] === $this->credentials['location']) {
-						// Auto-migrate: update stored location to use full resource name
-						$this->setSelectedLocation($location['name']);
-						return $location['name']; // This is the full resource name
-					}
-				}
-			}
-		} catch (\Exception $e) {
-			error_log('[GMB] Error finding location resource name: ' . $e->getMessage());
-		}
-
-		return null;
-	}
-	/**
-	 * Update business information
-	 */
-	/**
-	 * @param array $updates
-	 * @return array As expected by OperationQueue.php processOperation
-	 */
-	public function updateBusinessInfo(array $updates): array
-	{
-		/**
-		SEE ALSO: https://developers.google.com/my-business/content/attributes
-
-		MENU EXAMPLE
-		You can add structured menu data, like a food menu, to a location with the use of the PriceList object.
-
-		The following JSON request shows how to publish a breakfast menu to a location. The response contains an instance of the updated Location object.
-
-		HTTP
-
-		PATCH
-		https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}?updateMask=priceLists
-		{
-		"priceLists": [
-		{
-		"priceListId": "Breakfast",
-		"labels": [
-		{
-		"displayName": "Breakfast",
-		"description": "Tasty Google Breakfast",
-		"languageCode": "en"
-		}
-		],
-		"sourceUrl": "http://www.google.com/todays_menu",
-		"sections": [
-		{
-		"sectionId": "entree_menu",
-		"sectionType":"FOOD",
-		"labels": [
-		{
-		"displayName": "Entrées",
-		"description": "Breakfast Entrées",
-		"languageCode": "en"
-		}
-		],
-		"items": [
-		{
-		"itemId": "scramble",
-		"labels": [
-		{
-		"displayName": "Big Scramble",
-		"description": "A delicious scramble filled with Potatoes, Eggs,
-		Bell Peppers, and Sausage",
-		"languageCode": "en"
-		}
-		],
-		"price": {
-		"currencyCode": "USD",
-		"units": "12",
-		"nanos": "200000000"
-		}
-		},
-		{
-		"itemId": "steak_omelette",
-		"labels": [
-		{
-		"displayName": "Steak Omelette",
-		"description": "Three egg omelette with grilled prime rib,
-		fire-roasted bell peppers and onions, saut\u00e9ed mushrooms
-		and melted Swiss cheese",
-		"languageCode": "en"
-		}
-		],
-		"price": {
-		"currencyCode": "USD",
-		"units": "15",
-		"nanos": "750000000"
-		}
-		}
-		]
-		}
-		]
-		}
-		]
-		}
-
-
-		SERVICE DATA
-		If your business offers different service options, you can add structured services data to a location with the use of the PriceList object.
-
-		The following JSON request shows how to publish a service offering to a location. The response contains an instance of the updated Location object.
-
-		HTTP
-
-		PATCH
-		https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}?updateMask=priceLists
-		{
-		"priceLists": [
-		{
-		"priceListId": "Oil Change",
-		"labels": [
-		{
-		"displayName": "Oil Change",
-		"description": "Caseys Qwik Oil Change",
-		"languageCode": "en"
-		}
-		],
-		"sourceUrl": "http://www.google.com/todays_services",
-		"sections": [
-		{
-		"sectionId": "oil_services",
-		"sectionType":”SERVICES”,
-		"labels": [
-		{
-		"displayName": "Services",
-		"description": "Oil Changes",
-		"languageCode": "en"
-		}
-		],
-		"items": [
-		{
-		"itemId": "20-minute-oil-change",
-		"labels": [
-		{
-		"displayName": "20 Minute Oil Change",
-		"description": "Quick oil change and filter service.",
-		"languageCode": "en"
-		}
-		],
-		"price": {
-		"currencyCode": "USD",
-		"units": "30",
-		"nanos": "200000000"
-		}
-		},
-		{
-		"itemId": "full_service_oil_change",
-		"labels": [
-		{
-		"displayName": "Full Service Oil Change",
-		"description": "Quick oil change, filter service, and brake inspection.",
-		"languageCode": "en"
-		}
-		],
-		"price": {
-		"currencyCode": "USD",
-		"units": "45",
-		"nanos": "750000000"
-		}
-		}
-		]
-		}
-		]
-		}
-		]
-		}
-		 **/
-
-		try {
-			$allowed_fields = [
-				'title',           // Business name
-				'phoneNumbers',    // Primary phone
-				'websiteUri',      // Website URL
-				'categories',      // Business categories
-				'storefrontAddress', // Physical address
-				'serviceArea',     // Service area for service businesses
-				'profile',         // Business description
-				'openInfo',        // Opening date info
-				'regularHours',    // Business hours
-				'specialHours',    // Special hours
-			];
-
-			// Filter to only allowed fields
-			$patch_data = array_intersect_key($updates,$allowed_fields);
-
-			$location_name = $this->getSelectedLocationResourceName();
-			if (empty($patch_data)) {
-				throw new \Exception('No valid fields to update');
-			}
-
-			// Create updateMask from the fields being updated
-			$update_mask = implode(',', array_keys($patch_data));
-
-			$response = $this->makeRequest(
-				'PATCH',
-				"/v1/{$location_name}?updateMask={$update_mask}",
-				$patch_data,
-				'base'
-			);
-
-			return [
-				'success'	=> !empty($response),
-				'result'	=> $response
-			];
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method'	=> 'updateBusinessInfo'
-			]);
-			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage(),
-			];
-		}
-	}
-
-
-	/**
-	 * Update business attributes (amenities, services, etc.)
-	 */
-	/**
-	 * @param string $location_name
-	 * @param array $attributes
-	 * @return array As expected by OperationQueue.php processOperation
-	 */
-	public function updateAttributes(array $attributes): array
-	{
-		try {
-			// Attributes like: wheelchair_accessible, wifi, parking, etc.
-			//TODO: sanitize data
-			$attributes_data = [
-				'attributes' => $attributes
-			];
-			$location_name = $this->getSelectedLocationResourceName();
-
-			$response = $this->makeRequest(
-				'PATCH',
-				"/v1/accounts/{$location_name}/attributes",
-				$attributes_data,
-				'base'
-			);
-
-			return [
-				'success'	=> !empty($response),
-				'result'	=> $response
-			];
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method'	=> 'updateAttributes'
-			]);
-			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage()
-			];
-		}
-	}
-	/*****************
-	 * POSTS
-	 ****************/
-	/**
-	 * Create a new GMB post
-	 *
-	 * @param string $location_name The location resource name
-	 * @param array $post_data Post content including summary, callToAction, media, etc.
-	 * @return array|null Created post data or null on failure
-	 */
-
-	/**
-	FROM THE DOCS:
-	EVENT POST
-	Notify your customers about the next event at your business with a Post. Your Post for an event includes start and end dates and times, which display prominently on the Post.
-
-	To make a Post to an account associated with a user, use the accounts.locations.localPosts API.
-
-	To create a Post for an authenticated user, use the following:
-
-	$ POST
-	https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts
-	{
-	"languageCode": "en-US",
-	"summary": "Come in for our spooky Halloween event!",
-	"event": {
-	"title": "Halloween Spook-tacular!",
-	"schedule": {
-	"startDate": {
-	"year": 2017,
-	"month": 10,
-	"day": 31,
-	},
-	"startTime": {
-	"hours": 9,
-	"minutes": 0,
-	"seconds": 0,
-	"nanos": 0,
-	},
-	"endDate": {
-	"year": 2017,
-	"month": 10,
-	"day": 31,
-	},
-	"endTime": {
-	"hours": 17,
-	"minutes": 0,
-	"seconds": 0,
-	"nanos": 0,
-	},
-	}
-	},
-	"media": [
-	{
-	"mediaFormat": "PHOTO",
-	"sourceUrl": "https://www.google.com/real-image.jpg",
-	}
-	],
-	"topicType": "EVENT"
-	}
-
-	CALL TO ACTION POST
-	Posts with a call to action include a button. The text on the call to action button is determined by the actionType field of the Post. A link to a user-provided URL is added to the button.
-
-	To create a Post with a call to action button, use the following:
-
-	$ POST
-	https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts
-	{
-	"languageCode": "en-US",
-	"summary": "Order your Thanksgiving turkeys now!!",
-	"callToAction": {
-	"actionType": "ORDER",
-	"url": "http://google.com/order_turkeys_here",
-	},
-	"media": [
-	{
-	"mediaFormat": "PHOTO",
-	"sourceUrl": "https://www.google.com/real-turkey-photo.jpg",
-	}
-	],
-	"topicType": "OFFER"
-	}
-	Action types
-	The call to action Posts can have different action types that determine the type of call to action Post.
-
-	The following are the supported call to action types:
-
-	Action types
-	BOOK	Creates a Post that prompts a user to book an appointment, table, or something similar.
-	ORDER	Creates a Post that prompts a user to order something.
-	SHOP	Creates a Post that prompts a user to browse a product catalog.
-	LEARN_MORE	Creates a Post that prompts a user to see additional details on a website.
-	SIGN_UP	Creates a Post that prompts a user to register, sign up, or join something.
-	CALL	Creates a Post that prompts a user to call a business.
-
-
-	OFFER POST
-	To create an Offer Post, use the following:
-
-	HTTP
-
-	$ POST
-	https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts
-	{
-	"languageCode": "en-US",
-	"summary": "Buy one Google jetpack, get a second one free!!",
-	"offer": {
-	"couponCode": “BOGO-JET-CODE”,
-	"redeemOnlineUrl": “https://www.google.com/redeem”,
-	"termsConditions": “Offer only valid if you can prove you are a time traveler”
-	},
-	"media": [
-	{
-	"mediaFormat": "PHOTO",
-	"sourceUrl": "https://www.google.com/real-jetpack-photo.jpg",
-	}
-	],
-	"topicType": "OFFER"
-	}
-
-	EDIT POST
-	Once a post is created, you can edit it with a PATCH request.
-
-	To edit a Post, use the following:
-
-	HTTP
-
-	$ PATCH
-	https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts/{localPostId}?updateMask=summary
-	{
-	"summary": "Order your Christmas turkeys now!!"
-	}
-
-
-	DELETE POSTS
-	After a Post is created, you can delete it with a DELETE request.
-
-	To delete a Post, use the following:
-
-	HTTP
-
-	$ DELETE
-	https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts/{localPostId}
-	 **/
-	public function createPost(array $post_data, ?string $location_name = null): ?array
-	{
-		$location_name = (!$location_name) ? $this->getSelectedLocationResourceName() : $location_name;
-		try {
-			// Validate required fields
-			if (empty($post_data['summary'])) {
-				throw new \Exception('Post summary is required');
-			}
-
-			// Build the post object according to API docs
-			$post = [
-				'languageCode' => $post_data['language_code'] ?? 'en-US',
-				'summary' => $post_data['summary'],
-				'topicType' => $post_data['topic_type'] ?? 'STANDARD'
-			];
-
-			// Add optional fields
-			if (!empty($post_data['call_to_action'])) {
-				$post['callToAction'] = [
-					'actionType' => $post_data['call_to_action']['type'] ?? 'LEARN_MORE',
-					'url' => $post_data['call_to_action']['url']
-				];
-			}
-
-			if (!empty($post_data['media'])) {
-				$post['media'] = $post_data['media'];
-			}
-
-			if ($post_data['topic_type'] === 'EVENT' && !empty($post_data['event'])) {
-				$post['event'] = $post_data['event'];
-			}
-
-			if ($post_data['topic_type'] === 'OFFER' && !empty($post_data['offer'])) {
-				$post['offer'] = $post_data['offer'];
-			}
-
-			$response = $this->makeRequest(
-				'POST',
-				"/v4/{$location_name}/localPosts",
-				$post,
-				'posts'
-			);
-
-			$this->cache->invalidate("posts_{$location_name}");
-
-			return $response;
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method'	=> 'createPost'
-			]);
-			return null;
-		}
-	}
-
-	/**
-	 * Update an existing GMB post
-	 */
-	public function updatePost(string $post_name, array $post_data): bool
-	{
-		try {
-			$update_fields = [];
-			$update_mask = [];
-
-			if (isset($post_data['summary'])) {
-				$update_fields['summary'] = $post_data['summary'];
-				$update_mask[] = 'summary';
-			}
-
-			if (isset($post_data['call_to_action'])) {
-				$update_fields['callToAction'] = $post_data['call_to_action'];
-				$update_mask[] = 'callToAction';
-			}
-
-			if (empty($update_fields)) {
-				return true; // Nothing to update
-			}
-
-			$response = $this->makeRequest(
-				'PATCH',
-				"/v4/{$post_name}?updateMask=" . implode(',', $update_mask),
-				$update_fields,
-				'posts'  // Use the posts API base
-			);
-
-			return !empty($response);
-
-		} catch (\Exception $e) {
-
-			$this->logError($e->getMessage(), [
-				'method'	=> 'updatePost'
-			]);
+		if (!$this->getAccessToken() || !$this->getAccountID()) {
 			return false;
 		}
-	}
-
-	/**
-	 * Delete a post
-	 */
-	public function deletePost(string $post_name): bool
-	{
 		try {
-			$this->makeRequest('DELETE', "/v1/accounts/{$post_name}", [], 'posts');
-			return true;
-		} catch (\Exception $e) {
-
-			$this->logError($e->getMessage(), [
-				'method'	=> 'deletePost'
-			]);
-			return false;
-		}
-	}
-	/****************
-	 * HOURS
-	 ***************/
-	/**
-	 * Update regular business hours
-	 *
-	 * @param array $hours Array of periods with open/close times per day
-	 * @return array Success status, according to OperationQueue.php
-	 */
-	public function updateBusinessHours(array $hours): array
-	{
-		if(!$this->isSetUp()) {
-			return [];
-		}
-		$location_name = $this->credentials['location'];
-
-		if (empty($location_name)) {
-			error_log('[GMB] ERROR: No location selected or found');
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		try {
-			// Validate and format hours
-			$periods = $this->validateAndFormatHours($hours);
-
-			if (empty($periods)) {
-				error_log('[GMB] ERROR: No valid periods found after validation');
-				throw new \Exception('No valid business hours provided');
-			}
-
-			$update_data = [
-				'regularHours' => [
-					'periods' => $periods
-				]
-			];
-
-			$endpoint = "/v1/{$location_name}?updateMask=regularHours";
-
-			// Make the API request
-			$response = $this->makeRequest(
-				'PATCH',
-				$endpoint,
-				$update_data,
-				'base'
-			);
-
-			$success = $response !== null;
-
-			// Additional validation - check if the response contains the updated hours
-			if ($success && $response) {
-				if (isset($response['regularHours'])) {
-//					error_log('[GMB] SUCCESS: Updated hours confirmed in response: ' . print_r($response['regularHours'], true));
-				} else {
-//					error_log('[GMB] WARNING: No regularHours in response, but API call succeeded');
-//					error_log('[GMB] Full response keys: ' . implode(', ', array_keys($response)));
-				}
-			}
-
-			return [
-				'success' 	=> $success,
-				'result'	=> $response
-			];
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method'	=> 'updateBusinessHours'
-			]);
-			return [
-				'success' => false,
-				'result'	=> $e->getMessage()
-			];
-		}
-	}
-
-	private function validateAndFormatHours(array $hours): array
-	{
-		$valid_days = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'];
-		$periods = [];
-
-		foreach ($hours as $day => $times) {
-			$formatted_day = strtoupper($day);
-
-			// Convert day names if needed
-			$day_mapping = [
-				'MON' => 'MONDAY',
-				'TUE' => 'TUESDAY',
-				'TUES' => 'TUESDAY',
-				'WED' => 'WEDNESDAY',
-				'THU' => 'THURSDAY',
-				'THUR' => 'THURSDAY',
-				'THURS' => 'THURSDAY',
-				'FRI' => 'FRIDAY',
-				'SAT' => 'SATURDAY',
-				'SUN' => 'SUNDAY'
-			];
-
-			if (isset($day_mapping[$formatted_day])) {
-				$formatted_day = $day_mapping[$formatted_day];
-			}
-
-			if (!in_array($formatted_day, $valid_days)) {
-				error_log('[GMB] Invalid day: ' . $day . ' (formatted: ' . $formatted_day . ')');
-				continue;
-			}
-
-			// Check if day is open with flexible comparison
-			$is_open = false;
-			if (isset($times['open'])) {
-				// Convert to boolean - handles string "1", integer 1, boolean true, etc.
-				$is_open = (bool)(int)$times['open'];
-			}
-
-			if (!$is_open) {
-				error_log('[GMB] Day ' . $formatted_day . ' is closed (open value: ' . ($times['open'] ?? 'not set') . ')');
-				continue;
-			}
-
-			error_log('[GMB] Day ' . $formatted_day . ' is OPEN');
-
-			$open_time = $this->formatTime($times['time_opens'] ?? '');
-			$close_time = $this->formatTime($times['time_closes'] ?? '');
-
-			if (empty($open_time) || empty($close_time)) {
-				error_log('[GMB] Missing or invalid times for ' . $formatted_day);
-				error_log('[GMB] Raw time data - time_opens: "' . ($times['time_opens'] ?? 'not set') . '", time_closes: "' . ($times['time_closes'] ?? 'not set') . '"');
-				continue;
-			}
-
-			$period = [
-				'openDay' => $formatted_day,
-				'openTime' => $open_time,    // Now a proper Google TimeOfDay object
-				'closeDay' => $formatted_day,
-				'closeTime' => $close_time   // Now a proper Google TimeOfDay object
-			];
-
-			$periods[] = $period;
-		}
-		return $periods;
-	}
-
-	/**
-	 * Set special/temporary hours (for holidays, special events, etc.)
-	 *
-	 * @param string $date
-	 * @param array{open?: float, closed?:float, isClosed?: bool, location?: string} $options
-	 * @return array Operation Queue [ 'success' => true/false, 'result' => $results]
-	 */
-	public function setSpecialHours(string $date, array $options = []):array
-	{
-		// Get the full location resource name
-		$location_name = $this->getSelectedLocationResourceName();
-
-		if (empty($location_name)) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		try {
-			// Format the date correctly for Google API
-			$date_obj = [
-				'year' => (int)date('Y', strtotime($date)),
-				'month' => (int)date('n', strtotime($date)),
-				'day' => (int)date('j', strtotime($date))
-			];
-
-			$special_hour_period = [
-				'startDate' => $date_obj,
-				'endDate' => $date_obj, // Same day for single-day special hours
-			];
-
-			if (array_key_exists('isClosed', $options) && $options['isClosed']) {
-				$special_hour_period['closed'] = true;
-			} else {
-				$special_hour_period['closed'] = false;
-
-				// Format times as TimeOfDay objects
-				if (!empty($options['open'])) {
-					$open_time = $this->formatTime($options['open']);
-					if ($open_time) {
-						$special_hour_period['openTime'] = $open_time;
-					}
-				}
-
-				if (!empty($options['close'])) {
-					$close_time = $this->formatTime($options['close']);
-					if ($close_time) {
-						$special_hour_period['closeTime'] = $close_time;
-					}
-				}
-			}
-
-			// Get existing special hours and their periods
-			$existing_special_hours = $location['specialHours'] ?? [];
-			$existing_periods = $existing_special_hours['specialHourPeriods'] ?? [];
-
-			// Add or update the special hour period
-			$found = false;
-			foreach ($existing_periods as &$existing_period) {
-				if (isset($existing_period['startDate']) && $existing_period['startDate'] == $date_obj) {
-					$existing_period = $special_hour_period;
-					$found = true;
-					break;
-				}
-			}
-
-			if (!$found) {
-				$existing_periods[] = $special_hour_period;
-			}
-
-			// Prepare the update data with correct structure
-			$update_data = [
-				'specialHours' => [
-					'specialHourPeriods' => $existing_periods
-				]
-			];
-
-			// Try the PATCH request
-			$response = $this->makeRequest(
-				'PATCH',
-				"/v1/{$location_name}?updateMask=specialHours",
-				$update_data,
-				'base'
-			);
-
-			return [
-				'success'	=> $response !== null,
-				'results'	=> $response
-			];
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method'	=> 'setSpecialHours'
-			]);
-			return [
-				'success'	=> false,
-				'results'	=> $e->getMessage(),
-			];
-		}
-	}
-	/**
-	 * Clear all special hours
-	 */
-	public function clearSpecialHours(string $location_name): bool
-	{
-		try {
-			$update_data = [
-				'specialHours' => []
-			];
-
-			$response = $this->makeRequest(
-				'PATCH',
-				"/v1/{$location_name}?updateMask=specialHours",
-				$update_data,
-				'base'
-			);
-
-			return $response !== null;
-
-		} catch (\Exception $e) {
-
-			$this->logError($e->getMessage(), [
-				'method'	=> 'clearSpecialHours'
-			]);
-			return false;
-		}
-	}
-	/***************************************************************
-		HOURS
-	 ***************************************************************/
-	/**
-	FROM THE DOCS:
-	UPLOAD FROM URL
-	To upload photos from a URL , make the following call to Media.Create. Use the relevant category as needed.
-
-
-	POST https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/media
-	{
-	"mediaFormat": "PHOTO",
-	"locationAssociation": {
-	"category": "COVER"
-	},
-	"sourceUrl": “http://example.com/biz/image.jpg",
-	}
-	To upload videos from a URL with the Google My Business API, make the following call to Media.Create:
-
-
-	POST https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/media
-	{
-	"mediaFormat": "VIDEO",
-	"locationAssociation": {
-	"category": "ADDITIONAL"
-	},
-	"sourceUrl": “http://example.com/biz/video.mp4",
-	}
-	 **/
-	public function uploadImageToGMB(int $imgID, string $category = 'ADDITIONAL'): ?string
-	{
-		if ($imgID === 0) {
-			return null;
-		}
-		if (empty($this->credentials)) {
-			$this->loadCredentials();
-		}
-
-		try {
-			$imgID = $this->getSupportedImage($imgID);
-			$valid_categories = [
-				'COVER', 'PROFILE', 'LOGO', 'EXTERIOR', 'INTERIOR',
-				'PRODUCT', 'AT_WORK', 'FOOD_AND_DRINK', 'MENU',
-				'COMMON_AREA', 'ROOMS', 'TEAMS', 'ADDITIONAL',
-				'FOOD_AND_DRINK'
-			];
-
-			if (!in_array($category, $valid_categories)) {
-				throw new \Exception('Invalid photo category: ' . $category);
-			}
-			$mediaKey = $this->getImageMediaKey($imgID);
-			if ($mediaKey) {
-				return $mediaKey;
-			}
-			$image_url = wp_get_attachment_image_url($imgID, 'full');
-
-			$media_data = [
-				'mediaFormat' => 'PHOTO',
-				'locationAssociation' => [
-					'category' => $category
-				],
-				'sourceUrl' => $image_url
-			];
-			$response = $this->makeRequest(
-				'POST',
-				"/v4/{$this->credentials['account']}/{$this->credentials['location']}/media",
-				$media_data,
-				'posts'
-			);
-
-			if (!is_wp_error($response) && array_key_exists('name', $response)) {
-				$mediaKey = $this->extractMediaKey($response['name']);
-				$this->setImageMediaKey($imgID, $mediaKey);
-				return $mediaKey;
-			}
-			return null;
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method' => 'uploadPhotoFromUrl'
-			]);
-			return null;
-		}
-	}
-
-	public function getImageMediaKey(int $imgID):string|false
-	{
-		$mediaKey = get_post_meta($imgID, BASE.'gmb_mediakey', true);
-		if ($mediaKey !== '') {
-			return $this->testGMBImage($mediaKey);
-		}
-		return false;
-	}
-	public function testGMBImage(string $mediaKey):string|false
-	{
-		$existing = $this->getLocationMedia();
-		$exists = array_filter($existing, function ($item) use ($mediaKey) {
-			return $this->extractMediaKey($item['name']) === $mediaKey;
-		});
-		return (empty($exists)) ? false : $mediaKey;
-	}
-	public function setImageMediaKey(int $imgID, string $key):bool
-	{
-		return update_post_meta($imgID, BASE.'gmb_mediakey', $key);
-	}
-	/***************************************************************
-		CREDENTIALS
-	 ***************************************************************/
-	/**
-	 * Get user's GMB accounts
-	 */
-	public function getAccounts(bool $force = false): array
-	{
-		$ttl = 7 * 24 * 60 * 60; // week in seconds
-		$response = $this->getRequest('/v1/accounts', [], 'accounts', $ttl, $force)??[];
-
-		if (isset($response['accounts']) && is_array($response['accounts'])) {
-			return $response['accounts'];
-		}
-
-		if (is_array($response) && isset($response['name'])) {
-			return [$response];
-		}
-
-		return [];
-	}
-
-	/**
-	 * Get reviews for the current location
-	 * @param int $page_size Number of reviews to fetch (max 50)
-	 * @return array|null
-	 */
-	public function getReviews(int $page_size = 5): ?array
-	{
-		$this->ensureInitialized();
-		if (!$this->location) {
-			throw new \Exception('No location selected');
-		}
-		if (!$this->account_id) {
-			throw new \Exception('No account configured');
-		}
-
-		$location = $this->getSelectedLocationResourceName();
-		$account = $this->account_id;
-
-
-		// Check cache first (weekly refresh = 604800 seconds)
-		$cache_key = ['reviews', $location, $page_size];
-		$cached = $this->cache->get($cache_key);
-		if ($cached !== false) {
-			return $cached;
-		}
-
-		try {
-			// Reviews endpoint from My Business Account Management API
-			$response = $this->getRequest(
-				"/{$account}/{$location}/reviews",
+			return $this->getRequest(
+				sprintf(
+					'/v1/%s/locations',
+					$this->getAccountID()
+				),
 				[
-					'orderBy' => 'updateTime desc'
-				],
-				'v4'
+					'readMask'	=> $this->readMask
+				]
 			);
-
-			$reviews = $response ?? [];
-
-			// Cache for 1 week (604800 seconds)
-			$this->cache->set($cache_key, $reviews, WEEK_IN_SECONDS);
-
-			return $reviews;
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method' => 'getReviews'
-			]);
-			return null;
-		}
-	}
-
-	/**
-	 * Get the URL to view all Google reviews for the current location
-	 * @return string|null The reviews viewing URL or null if not available
-	 */
-	public function getReviewsViewUrl(): ?string
-	{
-		$this->ensureInitialized();
-		try {
-			$location = $this->getLocation();
-
-			if (empty($location)) {
-				return null;
-			}
-
-			// Prefer maps URL as it shows all reviews directly
-			if (!empty($location['metadata']['mapsUrl'])) {
-				return $location['metadata']['mapsUrl'];
-			}
-
-			// Fallback: construct from Place ID
-			if (!empty($location['metadata']['placeId'])) {
-				return 'https://search.google.com/local/reviews?placeid=' .
-					urlencode($location['metadata']['placeId']);
-			}
-
-			return null;
-
-		} catch (\Exception $e) {
-			$this->logError('Failed to get reviews view URL: ' . $e->getMessage(), [
-				'method' => 'getReviewsViewUrl'
-			]);
-			return null;
-		}
-	}
-
-	/**
-	 * Get the URL to leave a review for the current location
-	 * @return string|null The review URL or null if not available
-	 */
-	public function getReviewUrl(): ?string
-	{
-		$this->ensureInitialized();
-		try {
-			$location = $this->getLocation();
-
-			if (empty($location)) {
-				return null;
-			}
-
-			// Try to use Place ID for write review
-			if (!empty($location['metadata']['placeId'])) {
-				return 'https://search.google.com/local/writereview?placeid=' .
-					urlencode($location['metadata']['placeId']);
-			}
-
-			// Fallback to maps URL
-			if (!empty($location['metadata']['mapsUrl'])) {
-				return $location['metadata']['mapsUrl'] . '/reviews';
-			}
-
-			return null;
-
-		} catch (\Exception $e) {
-			$this->logError('Failed to get review URL: ' . $e->getMessage(), [
-				'method' => 'getReviewUrl'
-			]);
-			return null;
-		}
-	}
-
-	/**
-	 * Get locations for an account (with persistent storage)
-	 * Allowed Fields: https://developers.google.com/my-business/content/location-data#list_of_all_supported_filter_fields
-	 */
-	public function getLocations(?string $account_name = null, bool $force = false): array
-	{
-		if (!$this->isSetUp()) {
-			return [];
-		}
-
-		$account_name = $account_name ?: $this->account_id;
-
-		if (empty($account_name) || !str_starts_with($account_name, 'accounts/')) {
-			return [];
-		}
-		$params = ['readMask' => $this->readMask];
-		$ttl = 7 * 24 * 60 * 60; // Week in seconds
-
-		try {
-			$response = $this->getRequest("/v1/{$account_name}/locations", $params,'accounts', $ttl, $force);
-			return isset($response['locations']) ? $response['locations'] : [];
-
-		} catch (\Exception $e) {
-			error_log('[GMB] Error getting locations for ' . $account_name . ': ' . $e->getMessage());
+		} catch (Exception $e)
+		{
+			$this->logError('getLocations', 'Could not get locations: '.$e->getMessage());
 			return [];
 		}
 	}
-
-	public function handleRefreshAccessToken():WP_Error|array
-	{
-		if ($this->refreshOAuthToken()) {
-			return [
-				'success'	=> true,
-				'message'	=> 'Successfully refreshed OAuth Token'
-			];
-		}
-		return new WP_Error('failure', 'Failed to Refresh token');
-	}
-
-	/**
-	 * Manually refresh accounts and locations data from API
-	 */
-	public function refreshStoredData(): array
-	{
-		try {
-			// Fetch fresh accounts data from API
-			$accounts = $this->getAccounts(true);
-			if (!empty($accounts)) {
-				// Fetch and store locations for each account
-				$locationsProcessed = 0;
-				$accountsProcessed = count($accounts);
-				foreach ($accounts as $account) {
-					$locations = $this->getLocations($account['name'], true);
-					if (!empty($locations)) {
-						$locationsProcessed += count($locations);
+		protected function handleGetLocationsResponse(array $response, string $method):array
+		{
+			$locations = [];
+			if (isset($response['locations'])) {
+				foreach ($response['locations'] as $loc) {
+					if (isset($loc['name']) && isset($loc['title'])) {
+						$locations[$loc['name']] = $loc['title'];
 					}
 				}
-
-				return [
-					'success' => true,
-					'accounts' => $accountsProcessed,
-					'locations' => $locationsProcessed,
-					'message' => sprintf(
-						'Successfully refreshed %d accounts with %d total locations',
-						$accountsProcessed,
-						$locationsProcessed
-					)
-				];
 			}
-
-			return [
-				'success' => false,
-				'message' => 'No accounts found'
-			];
-
-		} catch (\Exception $e) {
-			error_log('[GMB] Error refreshing stored data: ' . $e->getMessage());
-			return [
-				'success' => false,
-				'message' => 'Error: ' . $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Get a specific location details
-	 */
-	public function getLocation(?string $location_name = null, bool $force = false):?array
-	{
-		if (!$this->isSetUp()) {
-			return null;
-		}
-		$location_name = $location_name?:$this->credentials['location'];
-		if (empty($location_name)) {
-			error_log('[GMB] No location name provided for getLocation');
-			return null;
+			return $locations;
 		}
 
-		$params = [
-			'readMask' => $this->readMask
-		];
-		$endpoint = "/v1/{$location_name}?".http_build_query($params);
-
-		$location = $this->getRequest($endpoint, [], 'base', 'moderate', $force);
-
-		return $location??null;
-	}
-
-	/**
-	 * Get the currently selected location
-	 */
-	public function getSelectedLocation(): ?string
+	protected function getAccounts(bool $force = false):array|false
 	{
-		return $this->credentials['location'];
-	}
-
-	/**
-	 * Set the selected location
-	 */
-	public function setSelectedLocation(string $location): bool
-	{
-		$this->credentials['location'] = $location;
-
-		if (preg_match('/accounts\/([^\/]+)\//', $location, $matches)) {
-			$this->account_id = 'accounts/' . $matches[1];
-		}
-
-		$credentials = $this->credentials;
-		$credentials['location'] = $location;
-		$credentials['account'] = $this->account_id;
-
-
-		return $this->saveCredentials($credentials)['success'];
-	}
-
-	/*************************************************************
-	RENDER
-	 *************************************************************/
-	/**
-	 * Render business list for connected users
-	 */
-	private function renderBusinessList(): void
-	{
-		try {
-			$accounts = $this->getAccounts();
-			if (empty($accounts)) {
-				echo '<p>No business accounts found.</p>';
-				return;
-			}
-			?>
-			<div class="gmb-business-list">
-				<h4>Your Business Locations</h4>
-				<?php
-				foreach ($accounts as $account) {
-					$locations = $this->getLocations($account['name']);
-					foreach ($locations as $location) {
-						?>
-						<div class="business-location">
-							<strong><?php echo esc_html($location['title']); ?></strong>
-							<?php if ($this->isSelectedLocation($location)): ?>
-								<span class="selected-badge">✓ Selected</span>
-							<?php endif; ?>
-						</div>
-						<?php
-					}
-				}
-				?>
-			</div>
-			<?php
-		} catch (\Exception $e) {
-			echo '<p>Error loading business locations.</p>';
-		}
-	}
-
-	private function isSelectedLocation(array $location): bool
-	{
-		if (empty($this->credentials['location'])) {
+		if (!$this->getAccessToken()) {
 			return false;
 		}
 
-		return ($location['name'] === $this->credentials['location']);
-	}
-
-	/***********************************************************
-		UTILITY
-	 ***********************************************************/
-	/**
-	 * Helper method to strip HTML tags from text
-	 * Preserves plain text content while removing all HTML formatting
-	 */
-	protected function stripHtmlTags(string $text): string
-	{
-		// Remove HTML tags
-		$text = wp_strip_all_tags($text);
-
-		// Decode HTML entities
-		$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
-
-		// Clean up whitespace
-		$text = preg_replace('/\s+/', ' ', $text);
-
-		// Trim
-		return trim($text);
-	}
-	/**
-	 * Format date for GMB API
-	 */
-	protected function formatDate(string $date): array
-	{
-		$timestamp = strtotime($date);
-		return [
-			'year' => (int)date('Y', $timestamp),
-			'month' => (int)date('n', $timestamp),
-			'day' => (int)date('j', $timestamp)
-		];
-	}
-	/**
-	 * Format time for Google My Business API (HH:MM format)
-	 */
-	private function formatTime(string $time): ?array
-	{
-		if (empty($time)) {
-			return null;
-		}
-
-		// Remove any extra whitespace
-		$time = trim($time);
-
-		// Handle different time formats and convert to 24-hour format
-		if (preg_match('/^(\d{1,2}):(\d{2})(?:\s*(AM|PM))?$/i', $time, $matches)) {
-			$hour = (int)$matches[1];
-			$minute = (int)$matches[2];
-			$ampm = isset($matches[3]) ? strtoupper($matches[3]) : '';
-
-			// Convert 12-hour to 24-hour format if needed
-			if ($ampm === 'PM' && $hour !== 12) {
-				$hour += 12;
-			} elseif ($ampm === 'AM' && $hour === 12) {
-				$hour = 0;
-			}
-
-			// Validate hour and minute ranges
-			if ($hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59) {
-				return [
-					'hours' => $hour,
-					'minutes' => $minute
-				];
-			}
-		}
-
-		error_log('[GMB] Invalid time format: ' . $time);
-		return null;
-	}
-
-
-
-	/*****************************************************************************
-	 *
-	 * OAUTH IMPLEMENTATION
-	 *
-	*****************************************************************************/
-	private function getScopes(): string
-	{
-		return implode(' ', [
-			'https://www.googleapis.com/auth/business.manage',
-			'https://www.googleapis.com/auth/userinfo.email',
-			'https://www.googleapis.com/auth/userinfo.profile'
-		]);
-	}
-	/**
-	 * Handle account selection
-	 */
-	public function handleSelectAccount($data = null): array
-	{
-		try {
-			$account_name = null;
-
-			// Extract account name from various sources
-			if (is_array($data)) {
-				$account_name = $data['account'] ?? $data['account'] ?? null;
-			} elseif (isset($_POST['account'])) {
-				$account_name = sanitize_text_field($_POST['account']);
-			}
-
-			if (empty($account_name)) {
-				return [
-					'success'	=> false,
-					'message'	=> 'No Account Selected'
-				];
-			}
-
-			// Get account details
-			$accounts = $this->getAccounts();
-			$selected_account = null;
-
-			foreach ($accounts as $account) {
-				if ($account['name'] === $account_name) {
-					$selected_account = $account;
-					break;
-				}
-			}
-
-			if (!$selected_account) {
-				throw new \Exception('Invalid account selected');
-			}
-
-			// Update credentials with selected account
-			$this->credentials['account'] = $account_name;
-			$this->credentials['selected_account_id'] = $selected_account['accountId'] ?? '';
-
-			$this->saveCredentials($this->credentials);
-
-			return [
-				'success' => true,
-				'message' => 'Account selected successfully',
-				'reload' => true
-			];
-
-		} catch (\Exception $e) {
-			$this->logError('Failed to select account', [
-				'error' => $e->getMessage(),
-				'data' => $data
-			]);
-
-			return [
-				'success' => false,
-				'message' => $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Handle location selection
-	 */
-	public function handleSelectLocation($data = null): array
-	{
-		try {
-			$location_name = null;
-
-			// Extract location name from various sources
-			if (is_array($data)) {
-				$location_name = $data['location'] ?? $data['location'] ?? null;
-			} elseif (isset($_POST['location'])) {
-				$location_name = sanitize_text_field($_POST['location']);
-			}
-
-			if (empty($location_name)) {
-				throw new \Exception('No location selected');
-			}
-
-			// Verify this is a valid location for the selected account
-			if (empty($this->credentials['account'])) {
-				throw new \Exception('No account selected. Please select an account first.');
-			}
-
-			$locations = $this->getLocations($this->credentials['account']);
-			$valid_location = false;
-
-			foreach ($locations as $location) {
-				if ($location['name'] === $location_name) {
-					$valid_location = true;
-					$this->credentials['location_id'] = $location['locationId'] ?? '';
-					break;
-				}
-			}
-
-			if (!$valid_location) {
-				throw new \Exception('Invalid location selected');
-			}
-
-			// Update credentials with selected location
-			$this->credentials['location'] = $location_name;
-
-			// Save updated credentials
-			$this->saveCredentials($this->credentials);
-
-			// Initialize with new location
-			$this->credentials['location'] = $location_name;
-
-			return [
-				'success' => true,
-				'message' => 'Location selected successfully',
-				'reload' => false  // No need to reload for location selection
-			];
-
-		} catch (\Exception $e) {
-			$this->logError('Failed to select location', [
-				'error' => $e->getMessage(),
-				'data' => $data
-			]);
-
-			return [
-				'success' => false,
-				'message' => $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Check OAuth status
-	 */
-	public function checkOAuthStatus(): array
-	{
-		try {
-			$valid = $this->isOAuthValid();
-
-			return [
-				'success' => true,
-				'valid' => $valid,
-				'expires_at' => $this->credentials['expires_at'] ?? null,
-				'has_refresh_token' => !empty($this->credentials['refresh_token'])
-			];
-
-		} catch (\Exception $e) {
-			return [
-				'success' => false,
-				'valid' => false,
-				'message' => $e->getMessage()
-			];
-		}
-	}
-
-
-	/********************************************************************
-	 *
-	 * ADMIN UI
-	 *
-	********************************************************************/
-	/**
-	 * Updated renderOAuthConnectedOptions for better UI
-	 */
-	protected function renderOAuthConnectedOptions(): void
-	{
-		if (!$this->isSetUp()) {
-			return;
-		}
-
-		// Check OAuth status
-		$oauth_valid = $this->isOAuthValid();
-
-		if (!$oauth_valid) {
-			?>
-			<div class="oauth-status">
-				<span class="status-invalid">⚠️ OAuth token expired - please reconnect</span>
-			</div>
-			<?php
-		}
-
-		// Show account selection
-		$accounts = $this->getAccounts();
-		if (!empty($accounts)) {
-			?>
-			<div class="form-field" data-service="gmb">
-				<label for="gmb_account">Google Business Account</label>
-				<select name="account"
-						id="gmb_account"
-						class="jvb-ajax-update"
-						data-action="select_account">
-					<option value="">Select an account...</option>
-					<?php foreach ($accounts as $account): ?>
-						<option value="<?php echo esc_attr($account['name']); ?>"
-							<?php selected($credentials['account'] ?? '', $account['name']); ?>>
-							<?php echo esc_html($account['accountName'] ?? $account['name']); ?>
-						</option>
-					<?php endforeach; ?>
-				</select>
-			</div>
-			<?php
-
-			// Show location selection if account is selected
-			if (!empty($this->credentials['account'])) {
-				$locations = $this->getLocations($this->credentials['account']);
-
-				if (!empty($locations)) {
-					?>
-					<div class="form-field location-field visible" data-service="gmb">
-						<label for="gmb_location">Business Location</label>
-						<select name="location"
-								id="gmb_location"
-								class="jvb-ajax-update"
-								data-action="select_location">
-							<option value="">Select a location...</option>
-							<?php foreach ($locations as $location): ?>
-								<option value="<?php echo esc_attr($location['name']); ?>"
-									<?php selected($this->credentials['location'] ?? '', $location['name']); ?>>
-									<?php echo esc_html($location['title'] ?? $location['name']); ?>
-								</option>
-							<?php endforeach; ?>
-						</select>
-					</div>
-					<?php
-				}
-			}
-
-			// Show current selection status
-			if (!empty($this->credentials['location'])) {
-				?>
-				<div class="connection-status connected">
-					✅ Connected to: <?php echo esc_html($this->credentials['location']); ?>
-				</div>
-				<?php
-			}
-		} else {
-			?>
-			<div class="notice notice-warning">
-				<p>No Google Business accounts found. Please ensure your Google account has access to Google My Business.</p>
-			</div>
-			<?php
-		}
-	}
-
-	public function getServiceDescription(): string
-	{
-		return "Manage your Google My Business listings, posts, hours, and reviews.";
-	}
-
-	/*****************************************************************************
-	 *
-	 * FOOD MENU IMPLEMENTATION
-	 *
-	 *****************************************************************************/
-
-	/**
-	 * Get food menus for the current location
-	 */
-	public function getFoodMenus(): ?array
-	{
-		if (!$this->credentials['location']) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		$response = $this->getRequest(
-			"/v4/{$this->credentials['location']}/foodMenu",
-			[],
-			'posts'
-		);
-		return $response['foodMenus'] ?? [];
-	}
-
-	/**
-	 * Update food menus for the current location
-	 */
-	public function updateFoodMenus(array $menus): bool
-	{
-		if (!$this->credentials['location']) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		try {
-			$food_menus = ['menus' => $menus];
-
-			$endpoint = "/{$this->credentials['account']}/{$this->credentials['location']}/foodMenus";
-
-			// Use dedicated Food Menu API
-			$response = $this->makeRequest(
-				'PATCH',
-				$endpoint,
-				$food_menus,
-				'v4'
-			);
-
-			$this->logDebug('[GMB]Finished updating food menu', [
-				'method'	=> 'updateFoodMenus',
-				'response'	=> $response,
-			]);
-			return !empty($response);
-
-		} catch (\Exception $e) {
-			$this->logError($e->getMessage(), [
-				'method'	=> 'updateFoodMenus'
-			]);
-			return false;
-		}
-	}
-
-	/**
-	 * Upload menu images and return a map of identifiers to mediaKeys
-	 */
-	protected function uploadAndMapMenuImages(array $menus): array
-	{
-		$mediaKeyMap = [];
-
-		// Process all menu items for images
-		foreach ($menus as $menu) {
-			foreach ($menu['sections'] ?? [] as $section) {
-				// Upload section images if needed (though API doesn't support section images yet)
-				if (!empty($section['image']) && is_numeric($section['image'])) {
-					$mediaKey = $this->uploadImageToGMB($section['image'], 'FOOD_AND_DRINK');
-					if ($mediaKey) {
-						$mediaKeyMap['section_' . $section['id']] = $mediaKey;
-					}
-				}
-
-				// Process menu items
-				foreach ($section['items'] ?? [] as $item) {
-					// Upload featured image
-					if (!empty($item['post_id'])) {
-						$featured_image_id = get_post_thumbnail_id($item['post_id']);
-						if ($featured_image_id) {
-							$mediaKey = $this->uploadImageToGMB($featured_image_id, 'FOOD_AND_DRINK');
-							if ($mediaKey) {
-								$mediaKeyMap['item_' . $item['post_id']] = $mediaKey;
-							}
-						}
-
-						// Upload gallery images if available
-						if (!empty($item['gallery_ids'])) {
-							foreach ($item['gallery_ids'] as $gallery_id) {
-								$mediaKey = $this->uploadImageToGMB($gallery_id, 'FOOD_AND_DRINK');
-								if ($mediaKey) {
-									$mediaKeyMap['gallery_'.$gallery_id] = $mediaKey;
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-
-		return $mediaKeyMap;
-	}
-
-	/**
-	 * Extract mediaKey from the full media resource name
-	 */
-	protected function extractMediaKey(string $mediaResourceName): string
-	{
-		// Extract mediaKey from format: accounts/{accountId}/locations/{locationId}/media/{mediaKey}
-		$parts = explode('/media/', $mediaResourceName);
-		return isset($parts[1]) ? $parts[1] : '';
-	}
-
-	/**
-	 * Get all media items for the current location
-	 */
-	public function getLocationMedia(): array
-	{
-		if (!$this->credentials['location']) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		try {
-			$response = $this->getRequest(
-				"/{$this->credentials['account']}/{$this->credentials['location']}/media",
-				[],
-				'v4'
-			);
-
-			return $response['mediaItems'] ?? [];
-		} catch (\Exception $e) {
-			$this->logError('Failed to get location media: ' . $e->getMessage());
-			return [];
-		}
-	}
-
-	public function handleGetAllLocations():WP_Error|array
-	{
-		try {
-			// Check if we should force refresh
-			$force = isset($_POST['force']) && $_POST['force'] === 'true';
-
-			// Get accounts (use cache unless forced)
-			$accounts = $this->getAccounts($force);
-			$all_locations = [];
-
-			foreach ($accounts as $account) {
-				// Get locations for each account (use cache unless forced)
-				$locations = $this->getLocations($account['name'], $force);
-				$all_locations = array_merge($all_locations, $locations);
-			}
-
-			return [
-				'success'	=> true,
-				'locations' => $all_locations,
-				'cached' => !$force
-			];
-
-		} catch (\Exception $e) {
-			return new WP_Error('failure', 'Something went wrong: '.$e->getMessage());
-		}
-	}
-
-	/**
-	 * AJAX handler for updating selected location
-	 */
-	public function handleUpdateLocation($data = null): array
-	{
-		try {
-			// Handle both AJAX and REST API calls
-			$selected_account = null;
-
-			if (is_array($data)) {
-				$selected_account = $data['account'] ?? $data['location'] ?? null;
-			} elseif (isset($_POST['account'])) {
-				$selected_account = sanitize_text_field($_POST['account']);
-			}
-
-			if (empty($selected_account)) {
-				throw new \Exception('No account selected');
-			}
-
-			// Save the selected account
-			$credentials = $this->credentials;
-			$credentials['account'] = $selected_account;
-
-			// Get locations for this account
-			$locations = $this->getLocations($selected_account);
-
-			// Auto-select first location if only one exists
-			if (count($locations) === 1) {
-				$credentials['location'] = $locations[0]['name'];
-			}
-
-			$this->saveCredentials($credentials);
-
-			return [
-				'success' => true,
-				'message' => 'Account selected successfully',
-				'locations' => $locations,
-				'reload' => true  // Trigger page refresh to show location dropdown
-			];
-
-		} catch (\Exception $e) {
-			$this->logError('Failed to update location', [
-				'error' => $e->getMessage(),
-				'data' => $data
-			]);
-
-			return [
-				'success' => false,
-				'message' => $e->getMessage()
-			];
-		}
-	}
-
-	public function clearStoredData(): bool
-	{
-		try {
-			// Use the static method to clear the entire cache group
-			$this->cache->flush();
-			return true;
-
-		} catch (\Exception $e) {
-			error_log('[GMB] Error clearing stored data: ' . $e->getMessage());
-			return false;
-		}
-	}
-
-	public function getPriceLists(): ?array
-	{
-		if (!$this->credentials['location']) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		try {
-			$location_data = $this->getLocation($this->credentials['location']);
-			return $location_data['priceLists'] ?? [];
-		} catch (\Exception $e) {
-
-			$this->logError($e->getMessage(), [
-				'method'	=> 'getPriceLists'
-			]);
-			return null;
-		}
-	}
-
-	/**
-	 * Get insights/analytics
-	 */
-	public function getInsights(string $location_name, string $start_date, string $end_date): ?array
-	{
-//		From the Docs:
-//		Basic insights
-//		Retrieves basic insights for a given list of locations. Use the accounts.locations.reportInsights API to return the insights that are associated with a location.
-//
-//			To return the basic insights associated with a location, use the following:
-//
-//		HTTP
-//
-//		POST
-//		https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations:reportInsights
-//		{
-//			"locationNames": [
-//			"accounts/{accountId}/locations/{locationId}"
-//		],
-//		  "basicRequest": {
-//			"metricRequests": [
-//					 {
-//						 "metric": "QUERIES_DIRECT"
-//					 },
-//					 {
-//						 "metric": "QUERIES_INDIRECT"
-//					 }
-//				  ],
-//				  "timeRange": {
-//				"startTime": "2016-10-12T01:01:23.045123456Z",
-//					   "endTime": "2017-01-10T23:59:59.045123456Z"
-//				  }
-//			}
-//		}
-
-			$params = [
-				'dailyMetrics' => 'BUSINESS_IMPRESSIONS_DESKTOP_MAPS,BUSINESS_IMPRESSIONS_DESKTOP_SEARCH,BUSINESS_IMPRESSIONS_MOBILE_MAPS,BUSINESS_IMPRESSIONS_MOBILE_SEARCH,BUSINESS_DIRECTION_REQUESTS,CALL_CLICKS,WEBSITE_CLICKS',
-				'dailyRange.startDate.year' => date('Y', strtotime($start_date)),
-				'dailyRange.startDate.month' => date('n', strtotime($start_date)),
-				'dailyRange.startDate.day' => date('j', strtotime($start_date)),
-				'dailyRange.endDate.year' => date('Y', strtotime($end_date)),
-				'dailyRange.endDate.month' => date('n', strtotime($end_date)),
-				'dailyRange.endDate.day' => date('j', strtotime($end_date))
-			];
-
 		return $this->getRequest(
-			"/v1/{$location_name}:fetchMultiDailyMetricsTimeSeries?" . http_build_query($params),
-			$params,
-			'performance'
+			'https://mybusinessaccountmanagement.googleapis.com/v1/accounts',
+			[],
+			$force
 		);
 	}
-
-	/**
-	 * Helper: Format hours for GMB API
-	 */
-	private function formatHoursPeriods(array $hours): array
-	{
-		$periods = [];
-		$days = ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'];
-
-		foreach ($hours as $day => $times) {
-			if (empty($times['open']) || empty($times['close'])) {
-				continue;
+		protected function handleGetAccountsResponse(array $response, string $method):array
+		{
+			$accounts = [];
+			if (isset($response['accounts']) && is_array($response['accounts'])) {
+				$accounts = $response['accounts'];
+			} elseif (is_array($response) && isset($response['name'])) {
+				$accounts = [$response];
 			}
-
-			$periods[] = [
-				'openDay' => $days[$day] ?? $day,
-				'openTime' => $times['open'], // Format: "09:00"
-				'closeDay' => $days[$day] ?? $day,
-				'closeTime' => $times['close'] // Format: "17:00"
-			];
-		}
-
-		return $periods;
-	}
-
-
-	/**
-	 * HELPER: Easy interface method for creating a simple post
-	 * JVB()->connect('gmb')->createSimplePost("Check out our new menu!", "https://example.com/menu")
-	 */
-	public function createSimplePost(string $message, ?string $cta_url = null, string $cta_type = 'LEARN_MORE'): ?array
-	{
-		$location_name = $this->getSelectedLocationResourceName();
-
-		if (empty($location_name)) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		$post_data = [
-			'summary' => $message,
-			'topic_type' => 'STANDARD'
-		];
-
-		if ($cta_url) {
-			$post_data['call_to_action'] = [
-				'type' => $cta_type,
-				'url' => $cta_url
-			];
-		}
-
-		return $this->createPost($location_name, $post_data);
-	}
-
-	/**
-	 * HELPER: Easy interface method for updating basic business info
-	 * JVB()->connect('gmb')->updateInfo(['title' => 'New Business Name', 'websiteUri' => 'https://newsite.com'])
-	 */
-	public function updateInfo(array $updates): bool
-	{
-		$location_name = $this->getSelectedLocationResourceName();
-
-		if (empty($location_name)) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		return $this->updateBusinessInfo($location_name, $updates);
-	}
-
-
-	/**
-	 * HELPER: Easy interface for clearing special hours
-	 * JVB()->connect('gmb')->clearAllSpecialHours()
-	 */
-	public function clearAllSpecialHours(): bool
-	{
-		$location_name = $this->getSelectedLocationResourceName();
-
-		if (empty($location_name)) {
-			throw new \Exception('No location selected. Please select a location first.');
-		}
-
-		return $this->clearSpecialHours($location_name);
-	}
-
-
-	protected function mappedMenuFields(string $postType):array
-	{
-		/**
-		 * @param array $fields
-		 * @param string $fields.name Thing
-		 */
-		return apply_filters(
-			'jvbGMBFields',
-		[
-			'name'			=> 'post_title',
-			'price'			=> 'price',
-			'description'	=> 'post_excerpt',
-			'nutritionFacts'=> 'nutritionFacts',
-			'servesNumPeople'=>'servesNumPeople',
-			'portionSize'	=> 'portionSize',
-			'preparationMethods' => 'preparationMethods',
-			'cuisines'		=> 'cuisines',
-			'spiciness'		=> 'spiciness',
-			'allergen'		=> 'allergen',
-			'dietaryRestriction'=>'dietaryRestriction'
-		],
-		jvbNoBase($postType));
-	}
-
-
-
-	/**
-	 * Build menu structure from WordPress posts
-	 */
-	protected function collectMenu(array $menu_items): array
-	{
-
-		$defaultMeta = Meta::forOptions($this->userID.'_integrations');
-		$defaults = ['menu_name', 'menu_description', 'default_section', 'cuisines', 'source_url', 'language', 'default_currency'];
-		$defaults = $defaultMeta->getAll($defaults);
-
-		// Group items by section
-		$sections_map = [];
-		foreach ($menu_items as $item) {
-
-			$gmb_item = $this->buildMenuItem($item, $defaults);
-
-			// Get section for this item
-			$categories = wp_get_post_terms($item->ID, BASE.'section');
-			$section_slug = !empty($categories) ?
-				$categories[0]->slug :
-				'menu-items';
-			$section_name = !empty($categories) ?
-				$categories[0]->name :
-				($defaults['default_section'] ?? 'Menu Items');
-
-			if (!isset($sections_map[$section_slug])) {
-				$sections_map[$section_slug] = [
-					'labels'	=> [
-						'displayName' => $section_name,
-						'languageCode' => $defaults['language'] ?? 'en',
-					],
-					'items' => [],
-				];
-				if (!empty($section['description'])) {
-					$sections_map[$section_slug]['labels']['description'] = $this->stripHtmlTags($section['description']);
+			$options = [];
+			foreach ($accounts as $account) {
+				if (isset($account['name']) && isset($account['accountName'])) {
+					$options[$account['name']] = $account['accountName'];
 				}
 			}
-
-			$sections_map[$section_slug]['items'][] = $gmb_item;
+			return $options;
 		}
-
-		$gmb_sections = $this->getMenuSectionsOrder($sections_map);
-
-		// Build complete menu
-		$gmb_menu = [
-			'cuisines' => $this->getMenuCuisines($menu_items),
-			'labels'	=> [
-				'displayName' => $defaults['menu_name'] ?? 'Our Menu',
-				'languageCode' => $defaults['language'] ?? 'en',
-			],
-			'sections' => $gmb_sections
-		];
-
-		if (array_key_exists('menu_description', $defaults)) {
-			$gmb_menu['labels']['description'] = $this->stripHtmlTags($defaults['menu_description']);
-		}
-
-		// Add source URL if configured
-		if (!empty($defaults['source_url'])) {
-			$gmb_menu['source_url'] = $defaults['source_url'];
-		}
-
-		return [$gmb_menu]; // GMB expects an array of menus
-	}
-
-	protected function buildMenuItem(\WP_Post $item, array $defaults):array
-	{
-		$meta = Meta::forPost($item->ID);
-		$fields = $this->mappedMenuFields($item->post_type);
-		$values = $meta->getAll(array_values($fields));
-
-
-		// Build GMB-formatted item
-		$gmb_item = [
-			'labels'	=> [
-				'displayName' => $item->post_title,
-				'description' => !empty($item->post_excerpt) ?
-					$this->stripHtmlTags($item->post_excerpt) :
-					$this->stripHtmlTags(wp_trim_words($item->post_content, 20)),
-				'languageCode' => $defaults['language'] ?? 'en'
-			],
-			'attributes'	=> []
-		];
-
-		// Process mapped fields
-		foreach ($fields as $googleField => $wpField) {
-			if (in_array($wpField, ['post_title', 'post_excerpt'])) {
-				continue;
-			}
-
-			$value = $values[$wpField] ?? null;
-			if (empty($value)) {
-				continue;
-			}
-
-			// Process field based on type
-			$processed = $this->processMenuField($googleField, $value, $values);
-			if ($processed !== null) {
-				$gmb_item['attributes'] = array_merge($gmb_item['attributes'], $processed);
-			}
-		}
-		$featuredImage = get_post_thumbnail_id($item->ID);
-		$mediaKey = $this->uploadImageToGMB($featuredImage, 'FOOD_AND_DRINK');
-		if ($mediaKey) {
-			$gmb_item['attributes'] = array_merge($gmb_item['attributes'], ['mediaKeys' => $mediaKey]);
-		}
-		if (empty($gmb_item['attributes'])) {
-			unset($gmb_item['attributes']);
-		}
-
-		return $gmb_item;
-	}
-
-	/**
-	 * Process a single menu field value
-	 */
-	protected function processMenuField(string $fieldName, $value, array $allValues): ?array
-	{
-		switch ($fieldName) {
-			case 'price':
-				if (is_numeric($value)) {
-					return [
-						'price' => [
-							'currencyCode'	=> $allValues['price_currency'] ?? 'CAD',
-							'units' => intval($value),
-							'nanos' => (($value - intval($value)) * 1000000000)
-						]
-					];
-				}
-				break;
-
-			case 'nutritionFacts':
-				if (is_array($value)) {
-					return ['nutritionFacts' => $this->processNutritionData($value)];
-				}
-				break;
-
-			case 'allergen':
-				$allergens = $this->processListField($value);
-				return $allergens ? ['allergens' => $allergens] : null;
-
-			case 'dietaryRestriction':
-				$restrictions = $this->processListField($value);
-				return $restrictions ? ['dietaryRestrictions' => $restrictions] : null;
-
-			case 'servesNumPeople':
-				return ['servesNumPeople' => intval($value)];
-
-			case 'portionSize':
-				return ['portionSize' => $value];
-
-			case 'preparationMethods':
-				$methods = $this->processListField($value);
-				return $methods ? ['preparationMethods' => $methods] : null;
-
-			case 'cuisines':
-				$cuisines = $this->processListField($value, 'strtoupper');
-				return $cuisines ? ['cuisines' => $cuisines] : null;
-
-			case 'spiciness':
-				return ['spiciness' => strtoupper($value)];
-
-			default:
-				return [$fieldName => $value];
-		}
-
-		return null;
-	}
-
-	/**
-	 * Process list fields (arrays or comma-separated strings)
-	 */
-	protected function processListField($value, ?callable $transform = null): ?array
-	{
-		if (empty($value)) {
-			return null;
-		}
-
-		// Convert to array if string
-		if (is_string($value)) {
-			$value = array_map('trim', explode(',', $value));
-		}
-
-		// Filter empty values
-		$value = array_values(array_filter($value));
-
-		// Apply transformation if provided
-		if ($transform && !empty($value)) {
-			$value = array_map($transform, $value);
-		}
-
-		return !empty($value) ? $value : null;
-	}
-
-	protected function getMenuSectionsOrder(array $sections_map):array
-	{
-		$optionsMeta = Meta::forOptions('options');
-		$sectionOrder = $optionsMeta->get('menu_section_order');
-
-		// Build final GMB menu structure
-		$ordered = [];
-
-		// First add sections in configured order
-		foreach ($sectionOrder as $section) {
-			$slug = sanitize_title($section['name']);
-			if (isset($sections_map[$slug]) && !empty($sections_map[$slug]['items'])) {
-				$ordered[$slug] = $sections_map[$slug];
-				unset($sections_map[$slug]);
-			}
-		}
-
-		// Add any remaining sections
-		foreach ($sections_map as $slug => $section) {
-			if (!empty($section['items'])) {
-				$ordered[$slug] = $section;
-			}
-		}
-
-		return array_values($ordered);
-	}
-	private function getMenuCuisines(array $menu_items, array $defaults = []): array
-	{
-		$cuisines = [];
-
-		// Check if there's a default cuisine
-		if (!empty($defaults['cuisines'])) {
-			$cuisines = is_array($defaults['cuisines']) ?
-				$defaults['cuisines'] :
-				[$defaults['cuisines']];
-		}
-
-		// Collect cuisines from individual items if specified
-		foreach ($menu_items as $item) {
-			$meta = Meta::forPost($item->ID);
-			$item_cuisines = $meta->get('cuisines');
-
-			if (!empty($item_cuisines)) {
-				$item_cuisines = is_array($item_cuisines) ?
-					$item_cuisines :
-					array_map('trim', explode(',', $item_cuisines));
-
-				foreach ($item_cuisines as $cuisine) {
-					$cuisine = strtoupper(trim($cuisine));
-					if (!in_array($cuisine, $cuisines)) {
-						$cuisines[] = $cuisine;
-					}
-				}
-			}
-		}
-
-		// Ensure we have at least one cuisine (GMB requires it)
-		if (empty($cuisines)) {
-			$cuisines = ['AMERICAN'];
-		}
-
-		return array_map('strtoupper', $cuisines);
-	}
-
-	/**
-	 * Format nutrition facts for GMB API
-	 */
-		protected function processNutritionData(array $nutritionData): array
-	{
-		$processed = [];
-
-		// Handle both repeater format and simple array format
-		if (isset($nutritionData[0]) && is_array($nutritionData[0])) {
-			// Repeater format
-			foreach ($nutritionData as $fact) {
-				if (!empty($fact['name']) && isset($fact['amount'])) {
-					$processed[] = [
-						'name' => $fact['name'],
-						'amount' => floatval($fact['amount']),
-						'unit' => $fact['unit'] ?? 'GRAM'
-					];
-				}
-			}
-		} else {
-			// Simple associative array format
-			foreach ($nutritionData as $name => $value) {
-				if (!empty($value)) {
-					$unit = 'GRAM';
-					if ($name === 'calories') {
-						$unit = 'CALORIE';
-					} elseif (in_array($name, ['cholesterol', 'sodium', 'totalCarbohydrate', 'protein'])) {
-						$unit = 'MILLIGRAM';
-					}
-
-					$processed[] = [
-						'name' => $name,
-						'amount' => floatval($value),
-						'unit' => $unit
-					];
-				}
-			}
-		}
-
-		return $processed;
-	}
-
-	protected function setContentTypes():void
-	{
-		$this->contentTypes = [
-			'menu_item'	=> [
-				'nutritionFacts'	=> [
-					'type'	=> 'repeater',
-					'label'	=> 'Nutrition',
-					'fields'	=> [
-						'name'	=> [
-							'type'	=> 'select',
-							'label'	=> 'Name',
-							'options'	=> [
-								'calories' => 'Calories',
-								'totalFat'	=> 'Total Fat',
-								'cholesterol' => 'Cholesterol',
-								'sodium'	=> 'Sodium',
-								'totalCarbohydrate' => 'Total Carbohydrates',
-								'protein'	=> 'Protein'
-							]
-						],
-						'amount'	=> [
-							'type'	=> 'number',
-							'label'	=> 'Amount',
-						],
-						'unit'	=> [
-							'type' => 'select',
-							'label'	=> 'Unit',
-							'options'	=> [
-								'MILLIGRAM' => 'Milligram',
-								'GRAM'	=> 'Gram',
-								'CALORIE'	=> 'Calorie'
-							]
-						]
-					],
-					'description'	=> 'Recommended. Example: Total Fat: 3g',
-					'section'	=> 'gmb',
-				],
-				'servesNumPeople'	=> [
-					'type'	=> 'number',
-					'label'	=> 'Number of People this Serves',
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				],
-				'portionSize'	=> [
-					'type'	=> 'text',
-					'label'	=> 'Portion size',
-					'description' => 'Example: 8-piece of nuggets',
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				],
-				'preparationMethods' => [
-					'type'	=> 'textarea',
-					'label'	=> 'Preparation Methods',
-					'description'	=> 'Specific methods that the food can be prepared in',
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				],
-				'cuisines'	=> [
-					'type'	=> 'text',
-					'label'	=> 'Cuisines',
-					'description'	=> 'Recommended. The specific cuisine of the food item',
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				],
-				'spiciness'	=> [
-					'type'	=> 'select',
-					'label'	=> 'Spiciness',
-					'options'	=> [
-						'none'	=> 'None',
-						'mild'	=> 'Mild',
-						'medium'	=> 'Medium',
-						'hot'	=> 'Hot',
-					],
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				],
-				'allergen'	=> [
-					'type'	=> 'checkbox',
-					'label'	=> 'Allergens',
-					'options'	=> [
-						'dairy'	=> 'Dairy',
-						'egg'	=> 'Egg',
-						'fish'	=> 'Fish',
-						'peanut'	=> 'Peanut',
-						'shellfish'	=> 'Shellfish',
-						'soy'		=> 'Soy',
-						'tree nut'	=> 'Tree Nut',
-						'wheat'		=> 'Wheat'
-					],
-					'description'	=> 'Recommended',
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				],
-				'dietaryRestriction'	=> [
-					'type'	=> 'checkbox',
-					'label'	=> 'Dietary Restrictions',
-					'description' => 'Recommended.',
-					'options'	=> [
-						'halal' => 'Halal',
-						'kosher'	=> 'Kosher',
-						'organic'	=> 'Organic',
-						'vegan'		=> 'Vegan',
-						'vegetarian'	=> 'Vegetarian',
-						'gluten-free'	=> 'Gluten Free'
-					],
-					'bulkEdit'	=> true,
-					'section'	=> 'gmb',
-				]
-			],
-			'post'	=> [
-				'summary' => [
-					'type' => 'textarea',
-					'label' => 'Post Summary',
-					'required' => true
-				],
-				'language_code' => [
-					'type' => 'text',
-					'label' => 'Language',
-					'default' => 'en-CA'
-				],
-				'media' => [
-					'type' => 'image',
-					'label' => 'Photo',
-				],
-				'call_to_action' => [
-					'type' => 'group',
-					'fields' => [
-						'type' => [
-							'type' => 'select',
-							'options' =>
-								[
-									'BOOK' => 'Book Now',
-									'ORDER' => 'Order Now',
-									'SHOP' => 'Shop Now',
-									'LEARN_MORE' => 'Learn More',
-									'SIGN_UP' => 'Sign up',
-									'CALL' => 'Call'
-								]
-						],
-						'url' => [
-							'type' => 'url',
-							'label' => 'Action URL'
-						]
-					]
-				]
-			],
-			'event' => [
-				'summary' => [
-					'type' => 'textarea',
-					'label' => 'Event Description',
-					'required' => true
-				],
-				'event' => [
-					'title' => [
-						'type' => 'text',
-						'label' => 'Event Title',
-						'required' => true
-					],
-					'start_date' => [
-						'type' => 'date',
-						'label' => 'Start Date',
-						'required' => true
-					],
-					'end_date' => [
-						'type' => 'date',
-						'label' => 'End Date'
-					],
-					'start_time' => [
-						'type' => 'time',
-						'label' => 'Start Time'
-					],
-					'end_time' => [
-						'type' => 'time',
-						'label' => 'End Time'
-					]
-				],
-				'media' => [
-					'type' => 'image',
-					'label' => 'Event Photo'
-				]
-			],
-			'offer' => [
-				'summary' => [
-					'type' => 'textarea',
-					'label' => 'Offer Description',
-					'required' => true
-				],
-				'offer' => [
-					'coupon_code' => [
-						'type' => 'text',
-						'label' => 'Coupon Code'
-					],
-					'redeem_online_url' => [
-						'type' => 'url',
-						'label' => 'Redemption URL'
-					],
-					'terms_conditions' => [
-						'type' => 'textarea',
-						'label' => 'Terms & Conditions']
-				],
-				'media' => ['type' => 'image', 'label' => 'Offer Photo']
-			]
-		];
-	}
-
-
 }
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.
 	}
 }
diff --git a/inc/integrations/services/Instagram.php b/inc/integrations/services/Instagram.php
index 838246f..183679f 100644
--- a/inc/integrations/services/Instagram.php
+++ b/inc/integrations/services/Instagram.php
@@ -2,11 +2,19 @@
 /**
  * Instagram Integration
  * File: /inc/integrations/Instagram.php
+ *
+ * Limits:
+ * 	- 50 uploads (including carousels) per user per 24-hour period
  */
 
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
-use JVBase\meta\Meta;
+use JVBase\integrations\OAuth;
+use JVBase\integrations\OAuthCredentials;
+use JVBase\integrations\OAuthURLs;
+use JVBase\integrations\Requests;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -14,692 +22,56 @@
 
 class Instagram extends Integrations
 {
-	protected array $allowedContent = [
-		'post'
-	];
-	private string $ig_user_id = '';
-	private string $page_id = '';
-	private string $page_access_token = '';
-	private array $business_account = [];
-	private bool $auto_post = false;
-	private array $post_settings = [];
-
-	// Instagram API endpoints
-	private const API_VERSION = 'v18.0';
-	private const GRAPH_API_BASE = 'https://graph.facebook.com/';
-	private const INSTAGRAM_BASE = 'https://graph.instagram.com/';
-
-	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)
+	use Admin, Instance, OAuth, Requests;
+	protected function __construct()
 	{
 		$this->service_name = 'instagram';
 		$this->title = 'Instagram';
 		$this->icon = 'instagram-logo';
-		$this->apiBase = [
-			'graph' => self::GRAPH_API_BASE . self::API_VERSION,
-			'instagram' => self::INSTAGRAM_BASE . self::API_VERSION
-		];
-		$this->apiEndpoints = [
-			'me/accounts',
-			'media',
-			'media_publish',
-			'content_publishing_limit',
-			'insights'
-		];
 
-		$this->isOAuthService = true;
-		// OAuth configuration for Facebook/Instagram
-		$this->oauth = [
-			'authorize' => 'https://www.facebook.com/' . self::API_VERSION . '/dialog/oauth',
-			'token' => self::GRAPH_API_BASE . self::API_VERSION . '/oauth/access_token',
-			'redirect_uri' => rest_url('jvb/v1/oauth/instagram'),
-			'scopes' => [
-				'instagram_basic',
-				'instagram_content_publish',
-				'instagram_manage_insights',
-				'pages_show_list',
-				'pages_read_engagement',
-				'business_management'
-			]
-		];
+		$this->apiVersion = 'v25.0';
+		$this->supportsWebp = false;
 
-		// Define sync capabilities
-		$this->canSync = [
-			'create' => true,
-			'update' => false, // Instagram doesn't allow editing posts
-			'delete' => true
-		];
+//		$this->defineFields();
+		$this->defineOAuth();
+//		$this->defineActions();
 
-		$this->fields = [
-
-		];
-
-		$this->advanced = [
-
-		];
-
-		$this->instructions = [
-			'Setup Facebook in order to use Instagram.'
-		];
-
-		$this->defaults = [
-
-		];
-		$this->hasWebhooks = true;
-		parent::__construct($userID);
-
-		$this->actions = array_merge(
-			$this->actions,
-			[
-				'switch_account'	=> 'switchInstagramAccount'
-			]
-		);
+		$this->baseUrl = 'https://graph.instagram.com/'.$this->apiVersion;
+		parent::__construct();
 	}
 
+	public function defineOAuth():void
+	{
+		$this->oauth = new OAuthURLs(
+			'https://www.facebook.com/'.$this->apiVersion.'/dialog/oauth',
+			'https://graph.facebook.com/'.$this->apiVersion.'/oauth/access_token',
+			'https://graph.facebook.com/'.$this->apiVersion.'/me/permissions'
+		);
+		$this->oathScopes = [
+			'instagram_basic',
+			'instagram_content_publish',
+		];
+	}
 	protected function initialize(): void
 	{
-		$this->page_access_token = $this->credentials['page_access_token'] ?? '';
-		$this->ig_user_id = $this->credentials['ig_user_id'] ?? '';
-		$this->page_id = $this->credentials['page_id'] ?? '';
-		$this->business_account = $this->credentials['business_account'] ?? [];
-		$this->auto_post = $this->credentials['auto_post'] ?? false;
-		$this->post_settings = $this->credentials['post_settings'] ?? [
-			'include_caption' => true,
-			'include_hashtags' => true,
-			'include_location' => false,
-			'default_hashtags' => ''
-		];
-
-		// Set rate limits
-		$this->rate_limits = [
-			'min_interval' => 30,
-			'daily_limit' => 25, // Instagram content publishing limit
-			'hourly_limit' => 5
-		];
+		// TODO: Implement initialize() method.
 	}
 
-	/**
-	 * Get request headers for Instagram API
-	 */
+	protected function refreshAccessToken(OAuthCredentials $credentials): array
+	{
+		// TODO: Implement refreshAccessToken() method.
+		return [];
+	}
+
 	protected function getRequestHeaders(): array
 	{
-		return [
-			'Content-Type' => 'application/json',
-			'Accept' => 'application/json'
-		];
+		// TODO: Implement getRequestHeaders() method.
+		return [];
 	}
 
-	/**
-	 * Build Instagram API URL
-	 */
-	protected function getApiUrl(string $endpoint, ?string $baseKey = null): string|false
+	protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
 	{
-		$base = ($baseKey === 'instagram') ? $this->apiBase['instagram'] : $this->apiBase['graph'];
-
-		// Handle Instagram-specific endpoints
-		if (in_array($endpoint, ['media', 'media_publish'])) {
-			return "{$base}/{$this->ig_user_id}/{$endpoint}";
-		}
-
-		return "{$base}/{$endpoint}";
-	}
-
-	/**
-	 * Add OAuth parameters specific to Facebook/Instagram
-	 */
-	protected function addOAuthParams(array $params): array
-	{
-		$params['display'] = 'popup';
-		$params['auth_type'] = 'rerequest';
-		return $params;
-	}
-
-	public function isSetUp():bool {
-		$fb = JVB()->connect('facebook');
-		return $fb->isSetUp();
-	}
-
-	protected function getOAuthCredentials(): array
-	{
-		// Get Facebook integration
-		$facebook = JVB()->connect('facebook');
-
-		if (!$facebook || !$facebook->isSetUp()) {
-			return [];
-		}
-
-		$fbCredentials = $facebook->getCredentials();
-
-		return [
-			'client_id' => $fbCredentials['app_id'] ?? $fbCredentials['client_id'] ?? '',
-			'client_secret' => $fbCredentials['app_secret'] ?? $fbCredentials['client_secret'] ?? '',
-			'access_token' => $fbCredentials['access_token'] ?? '',
-		];
-	}
-
-	public function getCredentials():array {
-		$credentials = parent::getCredentials();
-		if (empty($credentials)) {
-			$facebook = JVB()->connect('facebook');
-
-			if (!$facebook || !$facebook->isSetUp()) {
-				return [];
-			}
-
-			$fbCredentials = $facebook->getCredentials();
-
-			return [
-				'client_id'	=> $fbCredentials['client_id'],
-				'client_secret' => $fbCredentials['client_secret'],
-			];
-		}
-		return $credentials;
-	}
-
-	private function switchInstagramAccount(array $data): WP_Error|array
-	{
-		$page_id = $data['page_id'] ?? '';
-
-		if (empty($page_id)) {
-			return [
-				'success'	=> false,
-				'message'	=> 'Page ID is required'
-			];
-		}
-
-		// Find the selected page in available pages
-		$selected_page = null;
-		foreach ($this->credentials['available_pages'] ?? [] as $page) {
-			if ($page['id'] === $page_id) {
-				$selected_page = $page;
-				break;
-			}
-		}
-
-		if (!$selected_page || empty($selected_page['instagram_business_account'])) {
-			return [
-				'success'	=> false,
-				'message'	=> 'Invalid page or no Instagram account'
-			];
-		}
-
-		// Update credentials with new page
-		$this->credentials['page_id'] = $selected_page['id'];
-		$this->credentials['page_access_token'] = $selected_page['access_token'];
-		$this->credentials['ig_user_id'] = $selected_page['instagram_business_account']['id'];
-		$this->credentials['business_account'] = $this->getBusinessProfile(
-			$selected_page['instagram_business_account']['id'],
-			$selected_page['access_token']
-		);
-
-		$this->saveCredentials($this->credentials);
-
-		// Reinitialize with new credentials
-		$this->initialize();
-
-		return [
-			'success' => true,
-			'message' => 'Switched to @' . ($this->credentials['business_account']['username'] ?? 'Instagram account'),
-			'account' => $this->credentials['business_account']
-		];
-	}
-	/**
-	 * Handle post save - queue Instagram post creation
-	 */
-	public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void
-	{
-
-		if (!$this->hasOAuthCredentials()) {
-			error_log('OAuth Not set up for '.$this->service_name);
-			return;
-		}
-		if (!has_post_thumbnail($postID)) {
-			return;
-		}
-
-		$this->queueOperation(self::$syncTo, [
-			'items'		=> [$postID],
-			'user'		=> user_can($post->post_author, 'manage_options') ? null : $post->post_author
-		], [
-			'delay'	=> 60
-		]);
-		Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
-	}
-
-	/**
-	 * Process queued operations
-	 */
-	public function processOperation(\WP_Error|array $result, object $operation, array $data): \WP_Error|array
-	{
-		$base = strtolower($this->service_name) . '_';
-		$insta = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this;
-		switch ($operation->type) {
-			case $base . 'create_post':
-				return $insta->processCreatePost($data);
-
-			case $base . 'create_story':
-				return $insta->processCreateStory($data);
-
-			case $base . 'delete_post':
-				return $insta->processDeletePost($data);
-
-			default:
-				return $result;
-		}
-	}
-
-	private function processDeletePost(array $data): array
-	{
-		return [
-			'success' => true,
-			'message' => 'Instagram sync data removed (post remains on Instagram)',
-			'note' => 'Instagram API does not support deleting posts programmatically'
-		];
-	}
-
-	/**
-	 * Process post creation for Instagram
-	 */
-	private function processCreatePost(array $data): array
-	{
-		$post_id = $data['post_id'] ?? 0;
-		$post = get_post($post_id);
-
-		if (!$post) {
-			return ['success' => false, 'message' => 'Post not found'];
-		}
-
-		try {
-			// Get image URL
-			$image_url = get_the_post_thumbnail_url($post_id, 'full');
-			if (!$image_url) {
-				throw new Exception('No featured image found');
-			}
-
-			// Build caption
-			$caption = $this->buildCaption($post);
-
-			// Create media container
-			$container = $this->createMediaContainer($image_url, $caption);
-
-			if (!$container || empty($container['id'])) {
-				throw new Exception('Failed to create media container');
-			}
-
-			// Wait for processing (Instagram needs time to process the image)
-			sleep(10);
-
-			// Publish the media
-			$published = $this->publishMedia($container['id']);
-
-			if ($published && !empty($published['id'])) {
-				// Store Instagram media ID
-				update_post_meta($post_id, BASE . '_instagram_media_id', $published['id']);
-				update_post_meta($post_id, BASE . '_instagram_sync_status', 'synced');
-				update_post_meta($post_id, BASE . '_instagram_sync_date', current_time('mysql'));
-
-				// Get permalink if available
-				if (!empty($published['permalink'])) {
-					update_post_meta($post_id, BASE . '_instagram_permalink', $published['permalink']);
-				}
-
-				return [
-					'success' => true,
-					'message' => 'Posted to Instagram successfully',
-					'instagram_id' => $published['id']
-				];
-			}
-
-			throw new Exception('Failed to publish media');
-
-		} catch (Exception $e) {
-			$this->logError('Instagram post creation failed', [
-				'post_id' => $post_id,
-				'error' => $e->getMessage()
-			]);
-
-			update_post_meta($post_id, BASE . '_instagram_sync_status', 'failed');
-			update_post_meta($post_id, BASE . '_instagram_sync_error', $e->getMessage());
-
-			return [
-				'success' => false,
-				'message' => $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Build caption for Instagram post
-	 */
-	private function buildCaption(\WP_Post $post): string
-	{
-		$caption_parts = [];
-
-		// Add title and description if enabled
-		if ($this->post_settings['include_caption'] ?? true) {
-			$caption_parts[] = $post->post_title;
-
-			if (!empty($post->post_excerpt)) {
-				$caption_parts[] = "\n\n" . $post->post_excerpt;
-			}
-		}
-
-		// Add hashtags
-		if ($this->post_settings['include_hashtags'] ?? true) {
-			$hashtags = [];
-
-			// Get tags from post
-			$tags = wp_get_post_tags($post->ID, ['fields' => 'names']);
-			foreach ($tags as $tag) {
-				$hashtags[] = '#' . str_replace(' ', '', $tag);
-			}
-
-			// Add default hashtags
-			if (!empty($this->post_settings['default_hashtags'])) {
-				$default = preg_split('/[\s,]+/', $this->post_settings['default_hashtags']);
-				foreach ($default as $tag) {
-					$tag = trim($tag);
-					if (strpos($tag, '#') !== 0) {
-						$tag = '#' . $tag;
-					}
-					$hashtags[] = $tag;
-				}
-			}
-
-			if (!empty($hashtags)) {
-				$caption_parts[] = "\n\n" . implode(' ', array_unique($hashtags));
-			}
-		}
-
-		// Add website link
-		$caption_parts[] = "\n\n🔗 " . get_permalink($post);
-
-		return implode('', $caption_parts);
-	}
-
-	/**
-	 * Webhook validation
-	 */
-	protected function validateWebhook(array $payload): bool
-	{
-		// Facebook webhook verification
-		if (isset($payload['hub_mode']) && $payload['hub_mode'] === 'subscribe') {
-			$verify_token = $this->credentials['webhook_verify_token'] ?? 'jvb_instagram_webhook';
-
-			if ($payload['hub_verify_token'] === $verify_token) {
-				// Echo the challenge for verification
-				echo $payload['hub_challenge'];
-				exit;
-			}
-
-			return false;
-		}
-
-		// Validate webhook signature for actual events
-		$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
-		if (!$signature) {
-			return false;
-		}
-
-		$app_secret = $this->credentials['app_secret'] ?? '';
-		if (!$app_secret) {
-			return true; // Allow if no secret configured
-		}
-
-		$expected = 'sha256=' . hash_hmac('sha256', file_get_contents('php://input'), $app_secret);
-		return hash_equals($expected, $signature);
-	}
-
-	/**
-	 * Process webhook
-	 */
-	protected function processWebhook(array $payload): bool
-	{
-		$entry = $payload['entry'][0] ?? [];
-		$changes = $entry['changes'] ?? [];
-
-		foreach ($changes as $change) {
-			$field = $change['field'] ?? '';
-			$value = $change['value'] ?? [];
-
-			switch ($field) {
-				case 'comments':
-					$this->handleCommentWebhook($value);
-					break;
-
-				case 'mentions':
-					$this->handleMentionWebhook($value);
-					break;
-
-				default:
-					$this->logDebug('Unhandled webhook field', ['field' => $field]);
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * API Helper Methods
-	 */
-
-	private function getFacebookPages(string $access_token): array
-	{
-		$response = wp_remote_get(
-			$this->apiBase['graph'] . '/me/accounts?' . http_build_query([
-				'access_token' => $access_token,
-				'fields' => 'id,name,access_token,instagram_business_account'
-			])
-		);
-
-		if (is_wp_error($response)) {
-			$this->logError('Failed to get Facebook pages', ['error' => $response->get_error_message()]);
-			return [];
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-		return $data['data'] ?? [];
-	}
-
-	private function getBusinessProfile(string $ig_user_id, string $access_token): array
-	{
-		$response = wp_remote_get(
-			$this->apiBase['graph'] . '/' . $ig_user_id . '?' . http_build_query([
-				'access_token' => $access_token,
-				'fields' => 'id,username,name,profile_picture_url,followers_count,media_count,biography,website'
-			])
-		);
-
-		if (is_wp_error($response)) {
-			return [];
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true) ?: [];
-	}
-
-	private function createMediaContainer(string $image_url, string $caption): ?array
-	{
-		$response = wp_remote_post(
-			$this->getApiUrl('media', 'instagram'),
-			[
-				'body' => [
-					'image_url' => $image_url,
-					'caption' => $caption,
-					'access_token' => $this->page_access_token
-				]
-			]
-		);
-
-		if (is_wp_error($response)) {
-			$this->logError('Failed to create media container', ['error' => $response->get_error_message()]);
-			return null;
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true);
-	}
-
-	private function publishMedia(string $creation_id): ?array
-	{
-		$response = wp_remote_post(
-			$this->getApiUrl('media_publish', 'instagram'),
-			[
-				'body' => [
-					'creation_id' => $creation_id,
-					'access_token' => $this->page_access_token
-				]
-			]
-		);
-
-		if (is_wp_error($response)) {
-			$this->logError('Failed to publish media', ['error' => $response->get_error_message()]);
-			return null;
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true);
-	}
-
-	private function getPublishingLimit(): array
-	{
-		$response = wp_remote_get(
-			$this->getApiUrl('content_publishing_limit', 'instagram') . '?' . http_build_query([
-				'access_token' => $this->page_access_token
-			])
-		);
-
-		if (is_wp_error($response)) {
-			return new WP_Error('api_error', $response->get_error_message());
-		}
-
-		$data = json_decode(wp_remote_retrieve_body($response), true);
-
-		return [
-			'success' => true,
-			'data' => [
-				'quota_usage' => $data['data'][0]['quota_usage'] ?? 0,
-				'quota_total' => $data['data'][0]['config']['quota_total'] ?? 25,
-				'quota_duration' => $data['data'][0]['config']['quota_duration'] ?? 86400
-			]
-		];
-	}
-
-	protected function performConnectionTest(): bool
-	{
-		if (empty($this->ig_user_id) || empty($this->page_access_token)) {
-			return false;
-		}
-
-		// Test by getting account info
-		$profile = $this->getBusinessProfile($this->ig_user_id, $this->page_access_token);
-
-		if (!empty($profile['id'])) {
-			return true;
-		}
-
-		return false;
-	}
-
-	private function createStory(array $data): WP_Error|array
-	{
-		$image_url = $data['image_url'] ?? '';
-
-		if (empty($image_url)) {
-			return new WP_Error('missing_image', 'Image URL required for story');
-		}
-
-		// Queue story creation
-		$this->queueOperation('create_story', [
-			'image_url' => $image_url,
-			'sticker_url' => $data['sticker_url'] ?? ''
-		], [
-			'priority' => 'high'
-		]);
-
-		return [
-			'success' => true,
-			'message' => 'Story creation queued'
-		];
-	}
-
-	private function processCreateStory(array $data): array
-	{
-		$params = [
-			'image_url' => $data['image_url'],
-			'media_type' => 'STORIES',
-			'access_token' => $this->page_access_token
-		];
-
-		if (!empty($data['sticker_url'])) {
-			$params['sticker_url'] = $data['sticker_url'];
-		}
-
-		// Create story container
-		$response = wp_remote_post(
-			$this->getApiUrl('media', 'instagram'),
-			['body' => $params]
-		);
-
-		if (is_wp_error($response)) {
-			return [
-				'success' => false,
-				'message' => $response->get_error_message()
-			];
-		}
-
-		$container = json_decode(wp_remote_retrieve_body($response), true);
-
-		if (empty($container['id'])) {
-			return [
-				'success' => false,
-				'message' => 'Failed to create story container'
-			];
-		}
-
-		// Wait for processing
-		sleep(5);
-
-		// Publish story
-		$published = $this->publishMedia($container['id']);
-
-		return [
-			'success' => !empty($published['id']),
-			'message' => !empty($published['id']) ? 'Story published' : 'Failed to publish story',
-			'story_id' => $published['id'] ?? null
-		];
-	}
-
-	private function handleCommentWebhook(array $value): void
-	{
-		// Log new comments for moderation
-		$this->logDebug('New Instagram comment', $value);
-
-		// Could trigger notifications or store for moderation
-		do_action(BASE . 'instagram_comment_received', $value);
-	}
-
-	private function handleMentionWebhook(array $value): void
-	{
-		// Log mentions
-		$this->logDebug('New Instagram mention', $value);
-
-		// Could trigger notifications
-		do_action(BASE . 'instagram_mention_received', $value);
-	}
-
-	/**
-	 * Get service description
-	 */
-	public function getServiceDescription(): string
-	{
-		return "Connect your Instagram Business account to automatically share your content and manage your Instagram presence.";
+		// TODO: Implement handleResponse() method.
+		return [];
 	}
 }
diff --git a/inc/integrations/services/PostMark.php b/inc/integrations/services/PostMark.php
index 4f6f2ee..c8a4956 100644
--- a/inc/integrations/services/PostMark.php
+++ b/inc/integrations/services/PostMark.php
@@ -1,7 +1,12 @@
 <?php
 
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
+use JVBase\integrations\Requests;
+use WP_Error;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -14,123 +19,69 @@
  */
 class PostMark extends Integrations
 {
-	protected ?string $server_token = null;
-	protected string $message_stream = 'outbound';
-	protected string $from_email;
-	protected string $from_name;
-	protected bool $track_open;
-	protected bool $track_links;
-	protected ?string $lastMessageId = null;
+	use Admin, Instance, Requests;
 
-	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 static($userID);
-		}
-		return self::$instances[$key];
-	}
-	/**
-	 * Constructor
-	 */
-	protected function __construct(?int $userID = null)
+	protected function __construct()
 	{
 		$this->service_name = 'postmark';
 		$this->title = 'PostMark';
 		$this->icon = 'envelope-simple';
-
-		$this->cacheName = ($userID)? 'gmb_'.$userID : 'postmark';
-
-		// PostMark API configuration
-		$this->apiBase = 'https://api.postmarkapp.com';
 		$this->apiVersion = 'v1';
+		$this->baseUrl = 'https://api.postmarkapp.com';
 
-		// Define available endpoints
-		$this->apiEndpoints = [
-			'email',
-			'email/batch',
-			'email/withTemplate',
-			'bounces',
-			'deliverystats',
-			'messages/outbound',
-			'messages/outbound/[^/]+',
-			'messages/outbound/opens',
-			'stats/outbound',
-			'stats/outbound/opens',
-			'stats/outbound/bounces',
-			'templates',
-			'templates/[^/]+',
-			'server'
-		];
+		$this->defineFields();
+		$this->defineInstructions();
 
-		// Required credentials
-		$this->fields = [
-			'server_token' => [
-				'label' => 'Server API Token',
-				'type' => 'text',
-				'subtype'=> 'password',
-				'placeholder' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
-				'hint' => 'Your PostMark Server API Token'
-			],
-			'message_stream' => [
-				'label' => 'Message Stream ID',
-				'type' => 'text',
-				'placeholder' => 'outbound',
-				'default' => 'outbound',
-				'description' => 'Message stream to use (default: outbound)'
-			],
-			'from_email' => [
-				'label' => 'From Email',
-				'type' => 'email',
-				'placeholder' => 'noreply@yourdomain.com',
-				'description' => 'Default sender email address (must be verified in PostMark)'
-			],
-			'from_name' => [
-				'label' => 'From Name',
-				'type' => 'text',
-				'placeholder' => get_bloginfo('name'),
-				'default' => get_bloginfo('name'),
-				'description' => 'Default sender name'
-			],
-			'track_opens' => [
-				'label' => 'Track Opens',
-				'type' => 'select',
-				'options' => [
-					'false' => 'Disabled',
-					'true' => 'Enabled'
-				],
-				'default' => 'false',
-				'description' => 'Track when emails are opened'
-			],
-			'track_links' => [
-				'label' => 'Track Links',
-				'type' => 'select',
-				'options' => [
+		parent::__construct();
+		$this->defineActions();
+	}
+
+		protected function defineFields():void
+		{
+			$token = $this->addField('service_token', 'Server API Token', 'text', 'manage_options');
+				$token->setSubType('password');
+
+			$stream = $this->addField('message_stream', 'Message Stream ID', 'text', 'manage_options');
+				$stream->setDefault('outbound');
+				$stream->setHint('Message stream to use (default: outbound)');
+
+			$from = $this->addField('from_email', 'From Email', 'email', 'manage_options');
+				$from->setPlaceholder('noreply@yourdomain.com');
+				$from->setDefault(get_option('admin_email'));
+
+			$name = $this->addField('from_name', 'From Name', 'text', 'manage_options');
+				$name->setDefault(get_bloginfo('name'));
+				$name->setHint('Default sender name');
+
+			$opens = $this->addField('track_opens', 'Track Opens', 'select', 'manage_options');
+				$opens->setOptions([
+					'false'	=> 'Disabled',
+					'true'	=> 'Enabled',
+				]);
+				$opens->setDefault('false');
+				$opens->setHint('Track when emails are opened');
+
+			$links = $this->addField('track_links', 'Track Links', 'select', 'manage_options');
+				$links->setOptions([
 					'None' => 'Disabled',
 					'HtmlOnly' => 'HTML Only',
 					'HtmlAndText' => 'HTML and Text',
-				],
-				'default' => 'None',
-				'description' => 'Track link clicks in emails'
-			]
-		];
-
-		parent::__construct();
-
-		// Hook into WordPress mail system if integration is healthy
-		if ($this->is_healthy && $this->isSetUp()) {
-			$this->initializeMailHooks();
+				]);
+				$links->setDefault('None');
+				$links->setHint('Track link clicks in emails');
 		}
-	}
+		protected function defineInstructions():void
+		{
 
-	/**
-	 * Initialize WordPress mail hooks
-	 */
-	protected function initializeMailHooks(): void
+		}
+		protected function defineActions():void
+		{
+
+		}
+	protected function initialize(): void
 	{
 		// Replace wp_mail with PostMark
-		add_filter('wp_mail', [$this, 'sendViaPostMark'], 10, 1);
+		add_filter('wp_mail', [$this, 'wp_mail'], 10, 1);
 
 		// Optional: Hook for logging sent emails
 		add_action('postmark_email_sent', [$this, 'logSentEmail'], 10, 2);
@@ -138,289 +89,224 @@
 	}
 
 	/**
-	 * Send email via PostMark API
-	 * Hooks into wp_mail filter
-	 *
-	 * @param array $args Email arguments from wp_mail
-	 * @return array Modified args (or original if sending via PostMark)
+	 * Fired on the wp_mail filter
+	 * @param array $args Email arguments from wp_mail, including 'to', 'subject', 'message', 'headers', 'attachments'
+	 * @return array
 	 */
-	public function sendViaPostMark(array $args): array
-	{
-		// Extract email components
-		$to = $args['to'];
-		$subject = $args['subject'];
-		$message = $args['message'];
-		$headers = $args['headers'] ?? '';
-		$attachments = $args['attachments'] ?? [];
+		public function wp_mail(array $args):array
+		{
+			// Extract email components
+			$to = $args['to'];
+			$subject = $args['subject'];
+			$message = $args['message'];
+			$headers = $args['headers']??'';
+			$attachments = $args['attachments'] ?? [];
 
-		// Parse headers
-		$parsed_headers = $this->parseMailHeaders($headers);
+			$headers = $this->parseMailHeaders($headers);
 
-		// Determine content type
-		$is_html = $parsed_headers['content-type'] === 'text/html' ||
-			strpos($message, '<html') !== false ||
-			strpos($message, '<body') !== false;
+			$is_html = $headers['content-type'] === 'text/html' || str_contains($message, '<html') || str_contains($message, '<body');
 
-		// Build PostMark payload
-		$payload = $this->buildEmailPayload(
-			$to,
-			$subject,
-			$message,
-			$is_html,
-			$parsed_headers,
-			$attachments
-		);
-
-		// Send via PostMark
-		$result = $this->sendEmail($payload);
-
-		if ($result === true) {
-			error_log('================================ Email sent! ================================');
-			// Prevent default wp_mail from sending
-			add_filter('pre_wp_mail', '__return_true');
-			do_action('postmark_email_sent', $args, $payload);
-		} else {
-			error_log('=-======================[POSTMARK]Something went wrong... ================================');
-			// Log failure but allow fallback to default mail
-			do_action('postmark_email_failed', $args, $result);
-
-			// Optionally fall back to default wp_mail
-			if ($this->shouldFallback()) {
-				return $args;
-			}
-		}
-
-		return $args;
-	}
-
-	/**
-	 * Build email payload for PostMark API
-	 */
-	protected function buildEmailPayload(
-		$to,
-		string $subject,
-		string $message,
-		bool $is_html,
-		array $headers,
-		array $attachments
-	): array {
-		$credentials = $this->getCredentials();
-
-		// Handle multiple recipients
-		$to_addresses = is_array($to) ? implode(',', $to) : $to;
-
-		// Build base payload
-		$payload = [
-			'From' => sprintf(
-				'%s <%s>',
-				$headers['from-name'] ?? $credentials['from_name'],
-				$headers['from'] ?? $credentials['from_email']
-			),
-			'To' => $to_addresses,
-			'Subject' => $subject,
-			'TrackOpens' => $credentials['track_opens'] === 'true',
-			'TrackLinks' => $credentials['track_links'],
-			'MessageStream' => $credentials['message_stream'] ?? 'outbound'
-		];
-
-		// Set content based on type
-		if ($is_html) {
-			$payload['HtmlBody'] = $message;
-			// Generate text version from HTML
-			$payload['TextBody'] = $this->generateTextFromHtml($message);
-		} else {
-			$payload['TextBody'] = $message;
-		}
-
-		// Add CC/BCC if present
-		if (!empty($headers['cc'])) {
-			$payload['Cc'] = is_array($headers['cc']) ?
-				implode(',', $headers['cc']) : $headers['cc'];
-		}
-
-		if (!empty($headers['bcc'])) {
-			$payload['Bcc'] = is_array($headers['bcc']) ?
-				implode(',', $headers['bcc']) : $headers['bcc'];
-		}
-
-		// Add Reply-To if present
-		if (!empty($headers['reply-to'])) {
-			$payload['ReplyTo'] = $headers['reply-to'];
-		}
-
-		// Handle attachments
-		if (!empty($attachments)) {
-			$payload['Attachments'] = $this->processAttachments($attachments);
-		}
-
-		// Add custom headers if needed
-		if (!empty($headers['x-headers'])) {
-			$payload['Headers'] = $headers['x-headers'];
-		}
-
-		// Add tags for tracking (optional)
-		$payload['Tag'] = $this->determineEmailTag($subject, $headers);
-
-		return $payload;
-	}
-
-	/**
-	 * Send email via PostMark API
-	 */
-	protected function sendEmail(array $payload): bool|WP_Error
-	{
-		if (!$this->isSetUp()) {
-			return false;
-		}
-		try {
-			$response = $this->postRequest('email', $payload);
-			error_log('================================ POSTMARK RESPONSE: ================================'.print_r($response, true));
-			if (is_wp_error($response)) {
-				return $response;
-			}
-
-			// Store message ID for tracking
-			if (!empty($response['MessageID'])) {
-				$this->lastMessageId = $response['MessageID'];
-
-				// Cache for potential status checking
-				$this->cache->set(
-					'postmark_message_' . $response['MessageID'],
-					$payload,
-					3600 // 1 hour
-				);
-			}
-
-			return true;
-
-		} catch (\Exception $e) {
-			$this->logError('[POSTMARK]', ['method' => 'sendEmail', 'error' => $e->getMessage()]);
-			return new WP_Error(
-				'postmark_send_failed',
-				$e->getMessage()
+			$response = $this->sendEmail(
+				$to,
+				$subject,
+				$message,
+				$is_html,
+				$headers,
+				$attachments
 			);
-		}
-	}
 
-	/**
-	 * Parse mail headers from various formats
-	 */
-	protected function parseMailHeaders($headers): array
+			if ($response['success']) {
+				error_log('================================ Email sent! ================================');
+				// Prevent default wp_mail from sending
+				add_filter('pre_wp_mail', '__return_true');
+				do_action('postmark_email_sent', $args, $response);
+			} else {
+				error_log('=-======================[POSTMARK]Something went wrong... ================================');
+				// Log failure but allow fallback to default mail
+				do_action('postmark_email_failed', $args, $response);
+			}
+			return $args;
+		}
+
+			protected function parseMailHeaders(string|array $headers):array
+			{
+				$parsed = [
+					'content-type'	=> 'text/plain',
+					'from'			=> null,
+					'from-name'	=> null,
+					'reply-top'		=> null,
+					'cc'			=> [],
+					'bcc'			=> [],
+					'x-headers'		=> [],
+				];
+
+				if (empty($headers)) {
+					return $parsed;
+				}
+
+				if (is_string($headers)) {
+					$headers = explode("\n", $headers);
+				}
+
+				foreach ($headers as $line) {
+					$this->parseHeaderLine($line, $parsed);
+				}
+
+				return $parsed;
+			}
+				protected function parseHeaderLine(string $line, array &$parsed):void
+				{
+					if (empty($line)) {
+						return;
+					}
+					$parts = explode(':', $line, 2);
+					if (count($parts) !== 2) {
+						return;
+					}
+
+					$name = strtolower(trim($parts[0]));
+					$value = trim($parts[1]);
+					switch ($name) {
+						case 'content-type':
+							if (str_contains($value, 'text/html')) {
+								$parsed['content-type'] = $value;
+							}
+							break;
+
+						case 'from':
+							// Parse "Name <email>" format
+							if (preg_match('/^(.+?)\s*<(.+)>$/', $value, $matches)) {
+								$parsed['from-name'] = trim($matches[1], '"\'');
+								$parsed['from'] = $matches[2];
+							} else {
+								$parsed['from'] = $value;
+							}
+							break;
+
+						case 'cc':
+						case 'bcc':
+							$parsed[$name][] = $value;
+							break;
+
+						default:
+							//TODO: Verify we need this. This feels extra clunky
+							//Why not just $parsed['x-headers'][$name] = $value?
+							if (str_contains($name, 'x-')) {
+								$parsed['x-headers'][] = [
+									'Name'	=> $name,
+									'Value'	=> $value
+								];
+							} else {
+								$parsed[$name] = $value;
+							}
+					}
+				}
+
+	protected function getRequestHeaders(): array
 	{
-		$parsed = [
-			'content-type' => 'text/plain',
-			'from' => null,
-			'from-name' => null,
-			'reply-to' => null,
-			'cc' => [],
-			'bcc' => [],
-			'x-headers' => []
+		return [
+			'Accept' => 'application/json',
+			'Content-Type' => 'application/json',
+			'X-Postmark-Server-Token' => $this->getServerToken()
 		];
-
-		if (empty($headers)) {
-			return $parsed;
-		}
-
-		// Handle array format
-		if (is_array($headers)) {
-			foreach ($headers as $header) {
-				$this->parseHeaderLine($header, $parsed);
-			}
-		} else {
-			// Handle string format (multiple lines)
-			$lines = explode("\n", $headers);
-			foreach ($lines as $line) {
-				$this->parseHeaderLine($line, $parsed);
-			}
-		}
-
-		return $parsed;
 	}
 
-	/**
-	 * Parse individual header line
-	 */
-	protected function parseHeaderLine(string $line, array &$parsed): void
+	protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
 	{
-		if (empty($line)) {
-			return;
+		if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
+			return $this->response(false, 'Rate limit exceeded');
 		}
-
-		// Split header name and value
-		$parts = explode(':', $line, 2);
-		if (count($parts) !== 2) {
-			return;
-		}
-
-		$name = strtolower(trim($parts[0]));
-		$value = trim($parts[1]);
-
-		switch ($name) {
-			case 'content-type':
-				if (strpos($value, 'text/html') !== false) {
-					$parsed['content-type'] = 'text/html';
-				}
-				break;
-
-			case 'from':
-				// Parse "Name <email>" format
-				if (preg_match('/^(.+?)\s*<(.+)>$/', $value, $matches)) {
-					$parsed['from-name'] = trim($matches[1], '"\'');
-					$parsed['from'] = $matches[2];
-				} else {
-					$parsed['from'] = $value;
-				}
-				break;
-
-			case 'reply-to':
-				$parsed['reply-to'] = $value;
-				break;
-
-			case 'cc':
-				$parsed['cc'][] = $value;
-				break;
-
-			case 'bcc':
-				$parsed['bcc'][] = $value;
-				break;
-
-			default:
-				// Store custom headers
-				if (strpos($name, 'x-') === 0) {
-					$parsed['x-headers'][] = [
-						'Name' => $parts[0], // Original case
-						'Value' => $value
-					];
-				}
-		}
-	}
-
-	/**
-	 * Process attachments for PostMark
-	 */
-	protected function processAttachments(array $attachments): array
-	{
-		$processed = [];
-
-		foreach ($attachments as $file) {
-			if (!file_exists($file)) {
-				continue;
+		$endpoint = str_replace($this->apiBase, '', $endpoint);
+		$error = $response['ErrorCode'] ?? false;
+		if ($error) {
+			switch ($error) {
+				case '0':
+					//Do nothing, no error found
+					break;
+				//Bad or missing API Token
+				case '10':
+				//Multiple errors
+				case '11':
+				//Resource Not found
+				case '12':
+				//Invalid Pagination Key
+				case '13':
+				//Maintenance; PostMark API down for a sec
+				case '100':
+				//Invalid Email Request
+				case '300':
+				//Sender Signature not found
+				case '400':
+				//Sender signature not confirmed
+				case '401':
+				//Invalid JSON
+				case '402':
+				//Invalid request field(s) - {FIELD_NAME}
+				case '403':
+				//Not allowed to send (no more credits in account)
+				case '405':
+				//Inactive recipient. Recipient hard bounces, or filed spam complaint
+				case '406':
+				//JSON required
+				case '409':
+				//Too many requests (500 max)
+				case '410':
+				//Forbidden Attachment type
+				case '411':
+				//Account is pending
+				case '412':
+				//Account may not exist
+				case '413':
+				//Invalid query string parameter
+				case '500':
+				default:
+					$this->logError(
+						'handleResponse',
+						'Something went wrong: '.$response['Message']??'',
+						$response
+					);
+					return $this->response(false, 'Something went wrong: '.$response['Message']??'');
 			}
-
-			$processed[] = [
-				'Name' => basename($file),
-				'Content' => base64_encode(file_get_contents($file)),
-				'ContentType' => mime_content_type($file) ?: 'application/octet-stream'
-			];
 		}
-
-		return $processed;
+		return match ($endpoint) {
+			'/email' => $this->handleEmailResponse($response, $method),
+			'/email/bulk'	=> $this->handleBulkEmailResponse($response, $method),
+			default => $this->response(false, 'Unhandled Response for '. $endpoint . ' endpoint, with method: '. $method),
+		};
 	}
 
-	/**
-	 * Generate plain text from HTML content
-	 */
-	protected function generateTextFromHtml(string $html): string
+	/**********************************************************
+	 * UTILITY
+	**********************************************************/
+	protected function getServerToken():string|false
+	{
+		return $this->loadCredentials()['server_token']??false;
+	}
+	protected function getMessageStream():string
+	{
+		return $this->loadCredentials()['message_stream']??'outbound';
+	}
+	protected function getFromEmail():string
+	{
+		return $this->loadCredentials()['from_email']??get_option('admin_email');
+	}
+	protected function getFromName():string
+	{
+		return $this->loadCredentials()['from_name']??get_bloginfo('name');
+	}
+	protected function getTrackOpens():string
+	{
+		return $this->loadCredentials()['track_opens']??'false';
+	}
+	protected function getTrackLinks():string
+	{
+		return $this->loadCredentials()['track_links']??'None';
+	}
+
+	/****************************************************************************
+	 * HELPERS
+	****************************************************************************/
+	protected function textFromHtml(string $html):string
 	{
 		// Remove HTML tags
 		$text = strip_tags($html);
@@ -434,228 +320,124 @@
 	}
 
 	/**
-	 * Determine email tag based on content
+	 * @param array $attachments Either an array of file paths, or attachment IDs
+	 * @return array
 	 */
-	protected function determineEmailTag(string $subject, array $headers): string
+	protected function processAttachments(array $attachments):array
 	{
-		// Password reset emails
-		if (stripos($subject, 'password') !== false && stripos($subject, 'reset') !== false) {
+		$processed = [];
+		foreach ($attachments as $file) {
+			if (is_int($file)) {
+				$file = get_attached_file($file);
+			}
+			if (!$file || !file_exists($file)) {
+				continue;
+			}
+			$processed[] = [
+				'Name'	=> basename($file),
+				'Content'	=> base64_encode(file_get_contents($file)),
+				'ContentType'	=> mime_content_type($file) ?? 'application/octet-stream'
+			];
+		}
+
+		return $processed;
+	}
+
+	protected function determineEmailTag(string $subject, array $headers):string
+	{
+		if (array_key_exists('email-tag', $headers)) {
+			return $headers['email-tag'];
+		}
+		$tmp = strtolower($subject);
+		if (str_contains($tmp, 'password')) {
 			return 'password-reset';
 		}
 
-		// New user registration
-		if (stripos($subject, 'welcome') !== false || stripos($subject, 'registration') !== false) {
+		if (str_contains($tmp, 'welcome') || str_contains($tmp, 'registr') || str_contains($tmp, 'register')) {
 			return 'registration';
 		}
 
-		// Account notifications
-		if (stripos($subject, 'account') !== false) {
+		if (str_contains($tmp, 'account')) {
 			return 'account';
 		}
 
-		// Order/transaction emails
-		if (stripos($subject, 'order') !== false || stripos($subject, 'receipt') !== false) {
+		if (str_contains($tmp, 'order') || str_contains($tmp, 'receipt')) {
 			return 'transaction';
 		}
 
-		// Default
 		return 'general';
 	}
 
-	/**
-	 * Check if should fallback to default mail
-	 */
-	protected function shouldFallback(): bool
+	/****************************************************************************
+	 * MAILERS
+	****************************************************************************/
+	public function sendEmail(string $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
 	{
-		// Don't fallback if we've had too many errors
-		if ($this->error_stats['consecutive_errors'] >= 3) {
-			return false;
+		if (!$this->isSetUp()) {
+			return $this->response(false, 'Not set up');
 		}
 
-		// Check if fallback is enabled in settings
-		$credentials = $this->getCredentials();
-		return ($credentials['enable_fallback'] ?? 'true') === 'true';
+		$payload = $this->buildPayload($to, $subject, $message, $is_html, $headers, $attachments);
+
+		$response = $this->postRequest(
+			sprintf('%s/email', $this->apiBase),
+			$payload
+		);
 	}
+		protected function handleEmailResponse(WP_Error|array $response, string $method):array
+		{
 
-	/**
-	 * Override makeRequest to add PostMark headers
-	 */
-	protected function getRequestHeaders(): array {
-		$credentials = $this->getCredentials();
-
-		// Add PostMark specific headers
-		return [
-			'Accept' => 'application/json',
-			'Content-Type' => 'application/json',
-			'X-Postmark-Server-Token' => $credentials['server_token'] ?? ''
-		];
-	}
-
-	/**
-	 * Get email statistics
-	 */
-	public function getEmailStats(int $days = 30): array
-	{
-		$cache_key = 'postmark_stats_' . $days;
-		$cached = $this->cache->get($cache_key);
-
-		if ($cached !== false) {
-			return $cached;
 		}
+		protected function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
+		{
+			$to = is_array($to) ? implode(',',$to) : $to;
 
-		try {
-			$stats = $this->getRequest('stats/outbound', [
-				'fromdate' => date('Y-m-d', strtotime("-{$days} days")),
-				'todate' => date('Y-m-d')
-			]);
+			$payload = [
+				'From'	=> sprintf(
+					'%s <%s>',
+					$headers['from-name'] ?? $this->getFromName(),
+					$headers['from'] ?? $this->getFromEmail()
+				),
+				'To'			=> $to,
+				'Subject'		=> sanitize_text_field($subject),
+				'TrackOpens'	=> $this->getTrackOpens() === 'true',
+				'TrackLinks'	=> $this->getTrackLinks(),
+				'MessageStream'	=> $this->getMessageStream()
+			];
 
-			if (!is_wp_error($stats)) {
-				$this->cache->set($cache_key, $stats, 3600); // Cache for 1 hour
-				return $stats;
+			if ($is_html) {
+				$payload['HtmlBody'] = $message;
+				$payload['TextBody'] = $this->textFromHtml($message);
+			} else {
+				$payload['TextBody'] = $message;
 			}
-		} catch (\Exception $e) {
-			$this->logError('stats/outbound', ['error' => $e->getMessage()]);
+
+			if (!empty($headers['cc'])) {
+				$payload['Cc'] = is_array($headers['cc']) ?
+					implode(',',$headers['cc']) :
+					$headers['cc'];
+			}
+
+			if (!empty($headers['bcc'])) {
+				$payload['Bcc'] = is_array($headers['bcc']) ?
+					implode(',',$headers['bcc']) :
+					$headers['bcc'];
+			}
+
+			if (!empty($headers['reply-to'])) {
+				$payload['ReplyTo'] = $headers['reply-to'];
+			}
+
+			if (!empty($attachments)) {
+				$payload['Attachments'] = $this->processAttachments($attachments);
+			}
+
+			if (!empty($headers['x-headers'])) {
+				$payload['Headers'] = $headers['x-headers'];
+			}
+
+			$payload['Tag'] = $this->determineEmailTag($subject, $headers);
+
+			return $payload;
 		}
-
-		return [];
-	}
-
-	/**
-	 * Get bounce information
-	 */
-	public function getBounces(int $count = 25, int $offset = 0): array
-	{
-		try {
-			return $this->getRequest('bounces', [
-				'count' => $count,
-				'offset' => $offset
-			]);
-		} catch (\Exception $e) {
-			$this->logError('[POSTMARK]', ['method' => 'getBounces', 'error' => $e->getMessage()]);
-			return [];
-		}
-	}
-
-	/**
-	 * Log sent emails for debugging/tracking
-	 */
-	public function logSentEmail(array $args, array $payload): void
-	{
-		if (!defined('WP_DEBUG') || !WP_DEBUG) {
-			return;
-		}
-
-		$this->logDebug('Email sent via PostMark', [
-			'to' => $args['to'],
-			'subject' => $args['subject'],
-			'message_id' => $this->lastMessageId ?? 'unknown',
-			'tag' => $payload['Tag'] ?? 'none'
-		]);
-	}
-
-	/**
-	 * Log failed email attempts
-	 */
-	public function logFailedEmail(array $args, $error): void
-	{
-		$error_message = is_wp_error($error) ? $error->get_error_message() : 'Unknown error';
-
-		$this->logError('Failed to send email via PostMark', [
-			'to' => $args['to'],
-			'subject' => $args['subject'],
-			'error' => $error_message
-		]);
-	}
-
-	/**
-	 * Test email configuration
-	 */
-	public function sendTestEmail(string $to_email): bool|WP_Error
-	{
-		$payload = [
-			'From' => $this->getCredentials()['from_email'] . ' <' . $this->getCredentials()['from_email'] . '>',
-			'To' => $to_email,
-			'Subject' => 'PostMark Test Email - ' . get_bloginfo('name'),
-			'TextBody' => 'This is a test email from your PostMark integration.',
-			'HtmlBody' => '<p>This is a <strong>test email</strong> from your PostMark integration.</p>',
-			'Tag' => 'test',
-			'TrackOpens' => false,
-			'TrackLinks' => 'None'
-		];
-
-		return $this->sendEmail($payload);
-	}
-
-	/**
-	 * Render additional options in dashboard
-	 */
-	public function renderAdditionalOptions(): void
-	{
-		$stats = $this->getEmailStats(7);
-
-		?>
-		<div class="postmark-dashboard">
-			<h3>Email Statistics (Last 7 Days)</h3>
-
-			<?php if (!empty($stats)): ?>
-				<div class="stats-grid">
-					<div class="stat-item">
-						<span class="stat-value"><?= number_format($stats['Sent'] ?? 0) ?></span>
-						<span class="stat-label">Emails Sent</span>
-					</div>
-					<div class="stat-item">
-						<span class="stat-value"><?= number_format($stats['Bounced'] ?? 0) ?></span>
-						<span class="stat-label">Bounced</span>
-					</div>
-					<div class="stat-item">
-						<span class="stat-value"><?= $stats['Opens'] ?? 0 ?>%</span>
-						<span class="stat-label">Open Rate</span>
-					</div>
-				</div>
-			<?php else: ?>
-				<p>No statistics available. Check your API configuration.</p>
-			<?php endif; ?>
-
-			<div class="test-email-section">
-				<h4>Send Test Email</h4>
-				<form id="postmark-test-email" method="post">
-					<input type="email"
-						   name="test_email"
-						   placeholder="test@example.com"
-						   value="<?= esc_attr(wp_get_current_user()->user_email) ?>"
-						   required>
-					<button type="submit" class="button">Send Test</button>
-				</form>
-			</div>
-
-			<?php if ($bounces = $this->getBounces(5)): ?>
-				<div class="recent-bounces">
-					<h4>Recent Bounces</h4>
-					<ul>
-						<?php foreach ($bounces as $bounce): ?>
-							<li>
-								<?= esc_html($bounce['Email']) ?> -
-								<?= esc_html($bounce['Type']) ?>
-								<small>(<?= esc_html($bounce['BouncedAt']) ?>)</small>
-							</li>
-						<?php endforeach; ?>
-					</ul>
-				</div>
-			<?php endif; ?>
-		</div>
-		<?php
-	}
-
-	protected function initialize(): void
-	{
-		if (empty($this->credentials)) {
-			$this->loadCredentials();
-		}
-		$this->server_token = (array_key_exists('server_token', $this->credentials)) ? $this->credentials['server_token'] : null;
-		$this->from_email = (array_key_exists('from_email', $this->credentials)) ? $this->credentials['from_email'] : get_bloginfo('admin_email');
-		$this->from_name = (array_key_exists('from_name', $this->credentials)) ? $this->credentials['from_name'] : get_bloginfo('name');
-		$this->message_stream = (array_key_exists('message_stream', $this->credentials)) ? $this->credentials['message_stream'] : 'outbound';
-		$this->track_open = (array_key_exists('track_open', $this->credentials)) ? $this->credentials['track_open'] : false;
-		$this->track_links = (array_key_exists('track_links', $this->credentials)) ? $this->credentials['track_links'] : false;
-	}
 }
diff --git a/inc/integrations/services/Square.php b/inc/integrations/services/Square.php
index a2b6c9a..aeb001b 100644
--- a/inc/integrations/services/Square.php
+++ b/inc/integrations/services/Square.php
@@ -36,18 +36,16 @@
 		Webhooks;
 
 	protected string $orderPostType = '_square_order';
-	protected string $apiVersion = '2026-07-15';
-	public bool $hasBatchCreate = true;
-	public bool $hasBatchUpdate = true;
-	public bool $hasBatchDelete = true;
 	public array $apiEndpoints = [
 
 	];
 
 	protected function __construct()
 	{
+		$this->service_name = 'square';
 		$this->title = 'Square';
 		$this->icon = 'square-logo';
+		$this->apiVersion = '2026-07-15';
 		$this->supportsWebp = false;
 
 		$this->canSync = [
@@ -55,6 +53,9 @@
 			'update'	=> true,
 			'delete'	=> true,
 		];
+		$this->hasBatchCreate = true;
+		$this->hasBatchUpdate = true;
+		$this->hasBatchDelete = true;
 
 		$this->defineFields();
 		$this->defineInstructions();
@@ -89,8 +90,8 @@
 			$locs->setHidden();
 			$locs->setOptions($this->getLocationOptions());
 
-		$loc = $this->addField('location_id', 'Default Location', 'select', 'manage_options');
-			$loc->setOptions($this->loadCredentials()['locations']);
+		$loc = $this->addField('location_id', 'Set Location', 'select', 'manage_options');
+			$loc->setOptions($this->loadCredentials()['locations']??[]);
 			$loc->setHint('Select default Square location for transactions');
 	}
 
@@ -271,15 +272,12 @@
 			return $this->response(false, 'Rate limit exceeded');
 		}
 		$endpoint = str_replace($this->getBaseURL(), '', $endpoint);
-		switch ($endpoint) {
-			case '/v2/customers/search':
-				return $this->handleEmailSearchResponse($response, $method);
-			case '/v2/customers':
-				return $this->handleCreateUserResponse($response, $method);
-			case '/v2/locations':
-				return $this->handleGetLocationResponse($response, $method);
-		}
-		return $this->response(false, 'Unhandled Response for '.$endpoint.' endpoint, with method: '.$method);
+		return match ($endpoint) {
+			'/v2/customers/search' => $this->handleEmailSearchResponse($response, $method),
+			'/v2/customers' => $this->handleCreateUserResponse($response, $method),
+			'/v2/locations' => $this->handleGetLocationResponse($response, $method),
+			default => $this->response(false, 'Unhandled Response for ' . $endpoint . ' endpoint, with method: ' . $method),
+		};
 	}
 
 	protected function setContentTypes(): void
@@ -914,10 +912,7 @@
 			? 'https://connect.squareup.com'
 			: 'https://connect.squareupsandbox.com';
 	}
-	protected function getAccessToken():string
-	{
-		return $this->loadCredentials()['access_token']??'';
-	}
+
 	/***************************************************************
 	 * ACTIONS
 	***************************************************************/
diff --git a/inc/integrations/services/Umami.php b/inc/integrations/services/Umami.php
index 807ec3b..ebc22df 100644
--- a/inc/integrations/services/Umami.php
+++ b/inc/integrations/services/Umami.php
@@ -1,5 +1,7 @@
 <?php
 namespace JVBase\integrations\services;
+use JVBase\integrations\Admin;
+use JVBase\integrations\Instance;
 use JVBase\integrations\Integrations;
 
 if (!defined('ABSPATH')) {
@@ -8,796 +10,9 @@
 
 class Umami extends Integrations
 {
-	private string $website_id;
-	private string $api_url;
-	private string $api_token;
-
-	// Tracking configuration
-	private array $valid_events = [
-		'view_feed',
-		'view_taxonomy',
-		'view_profile',
-		'view_shop',
-		'view_content',
-		'toggle_favourite',
-		'click_profile',
-		'click_content',
-		'click_taxonomy',
-		'click_shop',
-		'user_session'
-	];
-
-	// Database tables
-	private string $events_table;
-	private string $metrics_table;
-
-	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 static($userID);
-		}
-		return self::$instances[$key];
-	}
-
-	protected function __construct(?int $userID = null)
-	{
-		$this->service_name = 'umami';
-		$this->title = 'Umami.js';
-		$this->icon = 'chart-line';
-		$this->apiBase = '';  // Set dynamically based on credentials
-		$this->apiEndpoints = [
-			'api/websites',
-			'api/websites/{websiteId}/stats',
-			'api/websites/{websiteId}/pageviews',
-			'api/websites/{websiteId}/events'
-		];
-
-		$this->fields = [
-			'website_id' => [
-				'label'		=> 'Website ID',
-				'type'		=> 'text',
-				'placeholder'=> 'Enter Umami Website ID',
-				'hint'		=> 'Found in your Umami dashboard under Settings → Websites.',
-				'required'	=> true
-			],
-		];
-
-		$this->advanced = [
-			'api_url' => [
-				'label'		=> 'API URL',
-				'type'		=> 'text',
-				'placeholder'=> 'https://analytics.yourdomain.com',
-				'hint'		=> 'Your Umami server URL. Leave blank if using umami.is cloud service.'
-			],
-			'api_token' => [
-				'label'		=> 'API Token (Optional)',
-				'type'		=> 'text',
-				'subtype'	=> 'password',
-				'placeholder'=> 'Enter API Token for analytics data',
-				'hint'		=> 'Required only if you want to retrieve analytics data for dashboards.'
-			]
-		];
-
-		$this->instructions = [
-			'Login to your <a href="https://cloud.umami.is/settings/websites" target="_blank">umami.is</a> account, select your website, and copy the website ID'
-		];
-
-		parent::__construct();
-
-		$this->actions = array_merge(
-			$this->actions,
-			[
-				'refresh_data'	=> 	'handleRefreshData'
-			]
-		);
-
-		global $wpdb;
-		$this->events_table = $wpdb->prefix . BASE . 'umami_events';
-		$this->metrics_table = $wpdb->prefix . BASE . 'performance_metrics';
-	}
-
-	/**
-	 * Initialize service-specific properties
-	 */
+	use Admin, Instance;
 	protected function initialize(): void
 	{
-		$this->website_id = $this->credentials['website_id'] ?? '';
-		$this->api_url = $this->credentials['api_url'] ?? '';
-		$this->api_token = $this->credentials['api_token'] ?? '';
-		$this->ttl = intval($this->credentials['cache_duration'] ?? 60) * 60;
-
-		// Set dynamic API base if provided
-		if (!empty($this->api_url)) {
-			$this->apiBase = rtrim($this->api_url, '/');
-		}
-	}
-
-	/**
-	 * Register additional WordPress hooks
-	 */
-	protected function registerAdditionalHooks(): void
-	{
-		// Add tracking script to frontend
-		add_action('wp_head', [$this, 'renderTrackingScript'], 5);
-
-		// Add user session tracking
-		add_action('wp_footer', [$this, 'trackUserSession'], 100);
-
-		// Schedule data collection if API is configured
-		if (!empty($this->api_token)) {
-			add_action('jvb_daily_umami_collection', [$this, 'collectDailyData']);
-
-			if (!wp_next_scheduled('jvb_daily_umami_collection')) {
-				wp_schedule_event(time(), 'daily', 'jvb_daily_umami_collection');
-			}
-		}
-	}
-
-	/***************************************************************************
-	 * TRACKING SCRIPT METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Render tracking script in head
-	 */
-	public function renderTrackingScript(): void
-	{
-		// Skip on local environments
-		if (JVB_TESTING) {
-			return;
-		}
-		if (!$this->isSetUp() || is_admin()) {
-			return;
-		}
-
-
-		$script_url = $this->getTrackingScriptUrl();
-		$website_id = $this->getWebsiteId();
-
-		if (!$website_id || !$script_url) {
-			return;
-		}
-
-		// Preconnect for performance
-		$domain = parse_url($script_url, PHP_URL_HOST);
-		echo '<link rel="preconnect" href="https://' . esc_attr($domain) . '"/>' . "\n";
-
-		// Add tracking script
-		$attributes = [
-			'defer',
-			'src="' . esc_url($script_url) . '"',
-			'data-website-id="' . esc_attr($website_id) . '"'
-		];
-
-		// Add optional configuration
-		if (!empty($this->credentials['respect_dnt']) && $this->credentials['respect_dnt']) {
-			$attributes[] = 'data-do-not-track="true"';
-		}
-
-		if (!empty($this->credentials['domains'])) {
-			$attributes[] = 'data-domains="' . esc_attr($this->credentials['domains']) . '"';
-		}
-
-		echo '<script ' . implode(' ', $attributes) . '></script>' . "\n";
-	}
-
-	/**
-	 * Track user session information
-	 */
-	public function trackUserSession(): void
-	{
-		if (!$this->isSetUp() || is_admin()) {
-			return;
-		}
-
-		$attributes = $this->buildTrackingAttributes('user_session', 'pageview', [
-			'logged_in' => is_user_logged_in() ? 'true' : 'false',
-			'user_type' => $this->getCurrentUserType()
-		]);
-
-		if (empty($attributes)) {
-			return;
-		}
-
-		echo '<div ' . $this->attributesToString($attributes) . ' style="display:none"></div>' . "\n";
-
-		// Add page-specific tracking
-		$this->trackPageView();
-	}
-
-	/**
-	 * Track specific page views
-	 */
-	private function trackPageView(): void
-	{
-		$tracker_args = [];
-		$event = '';
-		$type = '';
-
-		if (is_singular([BASE . 'artist', BASE . 'partner'])) {
-			// Profile view
-			global $post;
-			$event = 'view_profile';
-			$type = get_post_type();
-			$tracker_args = [
-				'id' => get_the_ID(),
-				'from' => $this->getTrafficSource(),
-				'owner_id' => $post->post_author
-			];
-
-			// Check for specific content in query
-			foreach (['tattoo', 'piercing', 'artwork'] as $content_type) {
-				if (isset($_GET[$content_type])) {
-					$tracker_args['item'] = sanitize_text_field($_GET[$content_type]);
-					break;
-				}
-			}
-
-		} elseif (is_tax(BASE . 'shop')) {
-			// Shop view
-			$event = 'view_shop';
-			$type = BASE . 'shop';
-			$tracker_args = [
-				'id' => get_queried_object_id(),
-				'from' => $this->getTrafficSource()
-			];
-
-		} elseif (is_tax()) {
-			// Taxonomy view
-			$obj = get_queried_object();
-			$event = 'view_taxonomy';
-			$type = $obj->taxonomy;
-			$tracker_args = [
-				'id' => $obj->term_id,
-				'from' => $this->getTrafficSource()
-			];
-		}
-
-		if ($event && $type) {
-			$attributes = $this->buildTrackingAttributes($event, $type, $tracker_args);
-			echo '<div ' . $this->attributesToString($attributes) . ' style="display:none"></div>' . "\n";
-		}
-	}
-
-	/***************************************************************************
-	 * TRACKING ATTRIBUTE BUILDER METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Build tracking attributes for events
-	 */
-	public function buildTrackingAttributes(string $event, string $type, array $args = []): array
-	{
-		if (!in_array($event, $this->valid_events)) {
-			return [];
-		}
-
-		// Normalize type
-		$normalized_type = str_replace(BASE, '', $type);
-
-		// Build base attributes
-		$attributes = [
-			'data-umami-event' => esc_attr($event),
-			'data-umami-event-type' => esc_attr($normalized_type)
-		];
-
-		// Add ID if provided
-		if (!empty($args['id'])) {
-			$attributes['data-umami-event-id'] = esc_attr($args['id']);
-		}
-
-		// Add source information
-		if (!empty($args['source_id'])) {
-			$attributes['data-umami-event-source'] = esc_attr($args['source_id']);
-			if (!empty($args['source_type'])) {
-				$attributes['data-umami-event-source-type'] = esc_attr(
-					str_replace(BASE, '', $args['source_type'])
-				);
-			}
-		}
-
-		// Add owner information
-		if (!empty($args['owner_id'])) {
-			$attributes['data-umami-event-owner'] = esc_attr($args['owner_id']);
-			if (!empty($args['owner_type'])) {
-				$attributes['data-umami-event-owner-type'] = esc_attr(
-					str_replace(BASE, '', $args['owner_type'])
-				);
-			}
-		}
-
-		// Add traffic source
-		if (!empty($args['from'])) {
-			$attributes['data-umami-event-from'] = esc_attr($args['from']);
-		}
-
-		// Add item reference
-		if (!empty($args['item'])) {
-			$attributes['data-umami-event-item'] = esc_attr($args['item']);
-		}
-
-		// Add metadata
-		foreach ($args as $key => $value) {
-			if (in_array($key, ['id', 'source_id', 'source_type', 'owner_id', 'owner_type', 'from', 'item'])) {
-				continue;
-			}
-			$clean_key = 'data-umami-event-' . str_replace('_', '-', sanitize_key($key));
-			$attributes[$clean_key] = esc_attr($value);
-		}
-
-		return $attributes;
-	}
-
-	/**
-	 * Convert attributes array to HTML string
-	 */
-	public function attributesToString(array $attributes): string
-	{
-		$attr_strings = [];
-		foreach ($attributes as $key => $value) {
-			$attr_strings[] = $key . '="' . $value . '"';
-		}
-		return implode(' ', $attr_strings);
-	}
-
-	/**
-	 * Public tracking methods for use in templates
-	 */
-	public function trackContentClick(int $id, string $type, array $args = []): string
-	{
-		$args['id'] = $id;
-
-		// Auto-detect owner for content types
-		if (empty($args['owner_id']) && in_array($type, [BASE . 'tattoo', BASE . 'artwork', BASE . 'piercing'])) {
-			$post = get_post($id);
-			if ($post && !empty($post->post_author)) {
-				$args['owner_id'] = $post->post_author;
-				$args['owner_type'] = 'user';
-			}
-		}
-
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_content', $type, $args)
-		);
-	}
-
-	public function trackProfileClick(int $id, string $type, array $args = []): string
-	{
-		$args['id'] = $id;
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_profile', $type, $args)
-		);
-	}
-
-	public function trackTaxonomyClick(int $id, string $taxonomy, array $args = []): string
-	{
-		$args['id'] = $id;
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_taxonomy', $taxonomy, $args)
-		);
-	}
-
-	public function trackContentTaxonomyClick(int $id, string $taxonomy, array $args = []): string
-	{
-		$args['id'] = $id;
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('click_' . $taxonomy, $taxonomy, $args)
-		);
-	}
-
-	public function trackFavouriteToggle(int $id, string $type, bool $is_favourite, array $args = []): string
-	{
-		$args['id'] = $id;
-		$args['action'] = $is_favourite ? 'add' : 'remove';
-		return $this->attributesToString(
-			$this->buildTrackingAttributes('toggle_favourite', $type, $args)
-		);
-	}
-
-	public function trackFeedView(int $id, string $type, array $args = []): array
-	{
-		$args['id'] = $id;
-		return $this->buildTrackingAttributes('view_feed', $type, $args);
-	}
-
-	/***************************************************************************
-	 * API DATA COLLECTION METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Get analytics data from Umami API
-	 */
-	public function getAnalytics(string $start_date, string $end_date, bool $use_cache = true): ?array
-	{
-		if (empty($this->api_url) || empty($this->api_token)) {
-			return null;
-		}
-
-		// Check cache first
-		if ($use_cache && $this->cache) {
-			$cache_key = md5("analytics_{$start_date}_{$end_date}");
-			$cached = $this->cache->get($cache_key);
-			if ($cached !== false) {
-				return $cached;
-			}
-		}
-
-		try {
-			$url = rtrim($this->api_url, '/') . "/api/websites/{$this->website_id}/stats";
-			$params = [
-				'startAt' => strtotime($start_date) * 1000,
-				'endAt' => strtotime($end_date) * 1000
-			];
-
-			$response = wp_remote_get($url . '?' . http_build_query($params), [
-				'headers' => [
-					'Authorization' => 'Bearer ' . $this->api_token,
-					'Accept' => 'application/json'
-				],
-				'timeout' => 30
-			]);
-
-			if (is_wp_error($response)) {
-				$this->logError('API request failed', ['error' => $response->get_error_message()]);
-				return null;
-			}
-
-			$body = wp_remote_retrieve_body($response);
-			$data = json_decode($body, true);
-
-			if (json_last_error() !== JSON_ERROR_NONE) {
-				$this->logError('Invalid JSON response from API');
-				return null;
-			}
-
-			// Cache the result
-			if ($use_cache && $this->cache) {
-				$this->cache->set($cache_key, $data, $this->ttl);
-			}
-
-			return $data;
-
-		} catch (Exception $e) {
-			$this->logError('Exception during API call', ['error' => $e->getMessage()]);
-			return null;
-		}
-	}
-
-	/**
-	 * Get page views for a specific URL
-	 */
-	public function getPageViews(string $url, string $start_date, string $end_date): ?array
-	{
-		if (empty($this->api_url) || empty($this->api_token)) {
-			return null;
-		}
-
-		$api_endpoint = rtrim($this->api_url, '/') . "/api/websites/{$this->website_id}/pageviews";
-		$params = [
-			'startAt' => strtotime($start_date) * 1000,
-			'endAt' => strtotime($end_date) * 1000,
-			'url' => $url,
-			'unit' => 'day'
-		];
-
-		$response = wp_remote_get($api_endpoint . '?' . http_build_query($params), [
-			'headers' => [
-				'Authorization' => 'Bearer ' . $this->api_token,
-				'Accept' => 'application/json'
-			],
-			'timeout' => 30
-		]);
-
-		if (is_wp_error($response)) {
-			return null;
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true);
-	}
-
-	/**
-	 * Get custom events data
-	 */
-	public function getEvents(string $event_name = '', string $start_date = '', string $end_date = ''): ?array
-	{
-		if (empty($this->api_url) || empty($this->api_token)) {
-			return null;
-		}
-
-		$api_endpoint = rtrim($this->api_url, '/') . "/api/websites/{$this->website_id}/events";
-
-		$params = [];
-		if ($start_date && $end_date) {
-			$params['startAt'] = strtotime($start_date) * 1000;
-			$params['endAt'] = strtotime($end_date) * 1000;
-		}
-		if ($event_name) {
-			$params['event'] = $event_name;
-		}
-
-		$url = $api_endpoint;
-		if (!empty($params)) {
-			$url .= '?' . http_build_query($params);
-		}
-
-		$response = wp_remote_get($url, [
-			'headers' => [
-				'Authorization' => 'Bearer ' . $this->api_token,
-				'Accept' => 'application/json'
-			],
-			'timeout' => 30
-		]);
-
-		if (is_wp_error($response)) {
-			return null;
-		}
-
-		return json_decode(wp_remote_retrieve_body($response), true);
-	}
-
-	/**
-	 * Collect daily analytics data and store in database
-	 */
-	public function collectDailyData(): void
-	{
-		if (!$this->isSetUp() || empty($this->api_token)) {
-			return;
-		}
-
-		$yesterday = date('Y-m-d', strtotime('-1 day'));
-		$data = $this->getAnalytics($yesterday, $yesterday, false);
-
-		if (!$data) {
-			$this->logError('Failed to collect daily data', ['date' => $yesterday]);
-			return;
-		}
-
-		global $wpdb;
-
-		// Store basic metrics
-		$wpdb->insert($this->metrics_table, [
-			'date' => $yesterday,
-			'pageviews' => $data['pageviews']['value'] ?? 0,
-			'visitors' => $data['visitors']['value'] ?? 0,
-			'visits' => $data['visits']['value'] ?? 0,
-			'bounces' => $data['bounces']['value'] ?? 0,
-			'total_time' => $data['totaltime']['value'] ?? 0,
-			'created_at' => current_time('mysql')
-		]);
-
-		// Collect and store events
-		$events = $this->getEvents('', $yesterday, $yesterday);
-		if ($events) {
-			foreach ($events as $event) {
-				$wpdb->insert($this->events_table, [
-					'date' => $yesterday,
-					'event_name' => $event['x'] ?? '',
-					'event_count' => $event['y'] ?? 0,
-					'created_at' => current_time('mysql')
-				]);
-			}
-		}
-
-		$this->logDebug('Successfully collected daily data', ['date' => $yesterday]);
-	}
-
-	/***************************************************************************
-	 * HELPER METHODS
-	 ***************************************************************************/
-
-	/**
-	 * Get current user type for tracking
-	 */
-	private function getCurrentUserType(): string
-	{
-		if (!is_user_logged_in()) {
-			return 'guest';
-		}
-
-		$user = wp_get_current_user();
-		$roles = $user->roles;
-
-		if (empty($roles)) {
-			return 'user';
-		}
-
-		$role = array_values($roles)[0];
-		return str_replace(BASE, '', $role);
-	}
-
-	/**
-	 * Determine traffic source
-	 */
-	private function getTrafficSource(): string
-	{
-		if (isset($_GET['utm_source'])) {
-			return sanitize_text_field($_GET['utm_source']);
-		}
-
-		if (isset($_SERVER['HTTP_REFERER'])) {
-			$referer = $_SERVER['HTTP_REFERER'];
-			$site_url = get_site_url();
-
-			if (strpos($referer, $site_url) === false) {
-				$domain = parse_url($referer, PHP_URL_HOST);
-				return $domain ?: 'external';
-			}
-
-			return 'internal';
-		}
-
-		return 'direct';
-	}
-
-	/**
-	 * Get website ID
-	 */
-	public function getWebsiteId(): string
-	{
-		$this->ensureInitialized();
-		return $this->website_id;
-	}
-
-	/**
-	 * Get tracking script URL
-	 */
-	public function getTrackingScriptUrl(): string
-	{
-		if (!empty($this->api_url)) {
-			return rtrim($this->api_url, '/') . '/script.js';
-		}
-
-		// Default to Umami cloud
-		return 'https://cloud.umami.is/script.js';
-	}
-
-	/***************************************************************************
-	 * REQUIRED ABSTRACT METHOD IMPLEMENTATIONS
-	 ***************************************************************************/
-
-	/**
-	 * Get request headers for API calls
-	 */
-	protected function getRequestHeaders(): array
-	{
-		$headers = [
-			'Accept' => 'application/json',
-			'Content-Type' => 'application/json'
-		];
-
-		if (!empty($this->api_token)) {
-			$headers['Authorization'] = 'Bearer ' . $this->api_token;
-		}
-
-		return $headers;
-	}
-
-	protected function handleRefreshData():array
-	{
-		// Collect today's data
-		$today = date('Y-m-d');
-		$data = $this->getAnalytics($today, $today, false);
-
-		if ($data) {
-			// Clear cache for today
-			$cache_key = md5("analytics_{$today}_{$today}");
-			$this->cache->forget($cache_key);
-
-			return [
-				'success' => true,
-				'message' => 'Data refreshed successfully',
-				'data' => $data
-			];
-		}
-
-		return [
-			'success'	=> false,
-			'message'	=> 'Something went wrong...'
-		];
-	}
-
-	/**
-	 * Process queued operations
-	 */
-	public function processOperation(array|\WP_Error $result, object $operation, array $data): array|\WP_Error
-	{
-		$prefix = strtolower($this->service_name) . '_';
-
-		switch ($operation->type) {
-			case $prefix . 'collect_analytics':
-				$this->collectDailyData();
-				return ['success' => true, 'message' => 'Analytics collected'];
-
-			default:
-				return $result;
-		}
-	}
-
-	/**
-	 * Test API connection
-	 */
-	protected function performConnectionTest(): bool
-	{
-		// Basic tracking only needs website ID
-		if (empty($this->website_id)) {
-			return false;
-		}
-
-		// If API credentials provided, test them
-		if (!empty($this->api_url) && !empty($this->api_token)) {
-			try {
-				$response = wp_remote_get(rtrim($this->api_url, '/') . '/api/websites', [
-					'headers' => [
-						'Authorization' => 'Bearer ' . $this->api_token,
-						'Accept' => 'application/json'
-					],
-					'timeout' => 10
-				]);
-
-				if (is_wp_error($response)) {
-					return false;
-				}
-
-				return wp_remote_retrieve_response_code($response) === 200;
-
-			} catch (Exception $e) {
-				$this->logError('Connection test failed', ['error' => $e->getMessage()]);
-				return false;
-			}
-		}
-
-		// Website ID exists, basic tracking will work
-		return true;
-	}
-
-	/**
-	 * Get service description
-	 */
-	public function getServiceDescription(): string
-	{
-		return "Privacy-focused analytics to understand your website traffic.";
-	}
-
-	/**
-	 * Create database tables for metrics storage
-	 */
-	public static function createTables(): void
-	{
-		global $wpdb;
-		$charset_collate = $wpdb->get_charset_collate();
-
-		// Events table
-		$events_table = $wpdb->prefix . BASE . 'umami_events';
-		$events_sql = "CREATE TABLE IF NOT EXISTS $events_table (
-            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-            date date NOT NULL,
-            event_name varchar(255) NOT NULL,
-            event_count int(11) NOT NULL DEFAULT 0,
-            event_data longtext,
-            created_at datetime NOT NULL,
-            PRIMARY KEY (id),
-            KEY date_event (date, event_name)
-        ) $charset_collate;";
-
-		// Metrics table
-		$metrics_table = $wpdb->prefix . BASE . 'performance_metrics';
-		$metrics_sql = "CREATE TABLE IF NOT EXISTS $metrics_table (
-            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
-            date date NOT NULL,
-            pageviews int(11) NOT NULL DEFAULT 0,
-            visitors int(11) NOT NULL DEFAULT 0,
-            visits int(11) NOT NULL DEFAULT 0,
-            bounces int(11) NOT NULL DEFAULT 0,
-            total_time int(11) NOT NULL DEFAULT 0,
-            created_at datetime NOT NULL,
-            PRIMARY KEY (id),
-            UNIQUE KEY date_unique (date)
-        ) $charset_collate;";
-
-		require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
-		dbDelta($events_sql);
-		dbDelta($metrics_sql);
+		// TODO: Implement initialize() method.
 	}
 }

--
Gitblit v1.10.0