From c68aefb847b09daa0697de7684d3451e2e68ce1e Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 09 Jul 2026 23:10:23 +0000
Subject: [PATCH] =Refactor of Integrations.php to be a bit more useful with a suite of methods for create, update, delete back and forths for integrations where sharing is enabled. Also considering a single instance with a connect method to connect as the site (no user) vs connecting as an individual user - rather than recreating instances for every user.

---
 inc/integrations/Square.php |  267 ++++++++++++-----------------------------------------
 1 files changed, 60 insertions(+), 207 deletions(-)

diff --git a/inc/integrations/Square.php b/inc/integrations/Square.php
index 5e1a831..c0191a3 100644
--- a/inc/integrations/Square.php
+++ b/inc/integrations/Square.php
@@ -28,6 +28,7 @@
  */
 class Square extends Integrations
 {
+	protected static string $syncCustomer = 'square_sync_customer';
 	protected array $allowedContent = [
 		'REGULAR',
 		'FOOD_AND_BEV',
@@ -90,14 +91,16 @@
 	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);
+			self::$instances[$key] = new self($userID);
 		}
 		return self::$instances[$key];
 	}
 
 	protected function __construct(?int $userID = null)
 	{
+
 		// Display properties
 		$this->title = 'Square';
 		$this->icon = 'square-logo';
@@ -786,8 +789,8 @@
 						'name' => $variation['name'],
 						'pricing_type' => 'FIXED_PRICING',
 						'price_money' => [
-							'amount' => intval($variation['_square_price'] * 100), // Convert to cents
-							'currency' => 'USD'
+							'amount' => $this->formatPrice($variation['_square_price']??0),
+							'currency' => $this->getCurrency()
 						]
 					]
 				];
@@ -801,8 +804,8 @@
 					'name' => 'Regular',
 					'pricing_type' => 'FIXED_PRICING',
 					'price_money' => [
-						'amount' => intval(($itemData['_square_price'] ?? 0) * 100),
-						'currency' => 'USD'
+						'amount' => $this->formatPrice($itemData['_square_price'] ?? 0),
+						'currency' => $this->getCurrency()
 					]
 				]
 			];
@@ -858,7 +861,6 @@
 			return;
 		}
 
-		add_action('wp_login', [$this, 'trackUserLogin'], 10, 2);
         add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
 
 		add_filter('jvbAdditionalActions', [Checkout::class, 'render']);
@@ -880,8 +882,6 @@
 		add_filter('jvb_checkout_browse_text', function () {
 			return 'browse our menu';
 		});
-		// Register queue executor types
-		add_action('init', [$this, 'registerQueueTypes'], 10);
 	}
 
 	/**
@@ -910,29 +910,9 @@
 			]);
 	}
 
-	public function registerQueueTypes(): void
+	protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
 	{
 		$queue    = JVB()->queue();
-		$executor = new IntegrationExecutor();
-
-		$queue->registry()->register(self::$syncTo, new TypeConfig(
-			executor:   $executor,
-			chunkKey:   'items',
-			chunkSize:  50,
-			maxRetries: 3
-		));
-
-		$queue->registry()->register(self::$deleteFrom, new TypeConfig(
-			executor:   $executor,
-			chunkKey:   'external_ids',
-			chunkSize:  200,
-			maxRetries: 2
-		));
-
-		$queue->registry()->register(self::$syncFrom, new TypeConfig(
-			executor:   $executor,
-			maxRetries: 3
-		));
 
 		$queue->registry()->register(self::$syncCustomer, new TypeConfig(
 			executor:   $executor,
@@ -954,15 +934,15 @@
 	 */
 	protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void
 	{
+		error_log('==== [Square]::handleTheSavePost ====');
 		$this->queueOperation(self::$syncTo, [
 			'items'   => [$postID],
-			'user_id' => $this->userID,
+			'user'		=> user_can($post->post_author, 'manage_options') ? null : $post->post_author
 		], [
 			'priority' => 'high',
 			'delay'    => 30,
 		]);
-
-		update_post_meta($postID, BASE . '_square_sync_status', 'queued');
+		Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
 	}
 
 	/**
@@ -970,16 +950,18 @@
 	 */
 	public function handleDeletePost(int $postID): void
 	{
-		$square_id = get_post_meta($postID, BASE . '_square_catalog_id', true);
+		$item_id = $this->getServiceItemID($postID);
 
-		if ($square_id) {
-			$this->queueOperation(self::$deleteFrom, [
-				'external_ids' => [$square_id],
-				'post_id'      => $postID,
-			], [
-				'priority' => 'high',
-			]);
+		if (empty($item_id)) {
+			return;
 		}
+		$this->queueOperation(self::$deleteFrom, [
+			'external_ids' => [$item_id],
+			'post_id'      => $postID,
+		], [
+			'priority' => 'high',
+		]);
+
 	}
 
 	/**
@@ -1063,6 +1045,9 @@
 
 	private function processBatchSyncResponse(array $response, array $map, array &$success, array &$errors):void
 	{
+		error_log('==== SQUARE::processBatchSyncResponse =====');
+		error_log('Full response: '.print_r($response, true));
+
 		// Handle successful objects
 		if (!empty($response['objects'])) {
 			foreach ($response['objects'] as $object) {
@@ -1162,6 +1147,14 @@
 
 		// Get existing Square catalog ID if it exists
 		$existing_square_id = get_post_meta($postID, BASE . '_square_catalog_id', true);
+		$registrar = Registrar::getInstance($post_type);
+		$product_type = 'FOOD_AND_BEV';
+		if ($registrar) {
+			$conf = $registrar->getIntegration($this->service_name);
+			if ($conf) {
+				$product_type = $conf->getContentType();
+			}
+		}
 
 		// Build the base catalog object
 		$catalog_object = [
@@ -1170,7 +1163,7 @@
 			'item_data' => [
 				'name' => $post->post_title,
 				'description' => wp_strip_all_tags($post->post_content),
-				'product_type' => 'FOOD_AND_BEV',
+				'product_type' => $product_type,
 				'variations' => [],
 				'is_taxable' => true,
 			]
@@ -1182,10 +1175,9 @@
 		}
 
 		// Add variations
-		$variations = $meta->get('product_variations');
+		$variations = $meta->get('_square_product_variations');
 		if (empty($variations)) {
 			// Create default variation if none exist
-			$price = floatval($meta->get('price') ?: 0);
 			$catalog_object['item_data']['variations'][] = [
 				'type' => 'ITEM_VARIATION',
 				'id' => $existing_square_id ? null : '#'.BASE.'menu_item_' . $postID . '_var_default',
@@ -1194,32 +1186,41 @@
 					'ordinal' => 0,
 					'pricing_type' => 'FIXED_PRICING',
 					'price_money' => [
-						'amount' => intval($price * 100), // Convert dollars to cents
-						'currency' => 'CAD'
+						'amount' => $this->formatPrice($meta->get('_square_price')),
+						'currency' => $this->getCurrency()
 					],
 					'sellable' => true,
 					'stockable' => true
 				]
 			];
 		} else {
+			$resetVariations = false;
 			foreach ($variations as $index => $variation) {
-				$existing_var_id = get_post_meta($postID, BASE . '_square_variation_' . $index . '_id', true);
+				$id = '#'.BASE.'menu_item_' . $postID . '_var_' . $index;
+				if (empty($variation['item_id'])) {
+					$resetVariations = true;
+					$variations[$index]['item_id'] = $id;
+					$variation['item_id'] = $id;
+				}
 				$catalog_object['item_data']['variations'][] = [
 					'type' => 'ITEM_VARIATION',
-					'id' => $existing_var_id ?: '#'.BASE.'menu_item_' . $postID . '_var_' . $index,
+					'id' => $variation['item_id'],
 					'item_variation_data' => [
 						'name' => $variation['name'] ?? 'Variation ' . ($index + 1),
 						'ordinal' => $index,
 						'pricing_type' => 'FIXED_PRICING',
 						'price_money' => [
-							'amount' => intval(floatval($variation['price'] ?? 0) * 100),
-							'currency' => 'CAD'
+							'amount' =>$this->formatPrice($variation['price'] ?? 0),
+							'currency' => $this->getCurrency()
 						],
 						'sellable' => true,
 						'stockable' => true
 					]
 				];
 			}
+			if ($resetVariations) {
+				$meta->set('_square_product_variations', $variations);
+			}
 		}
 
 		// Add categories if they exist
@@ -1262,98 +1263,7 @@
 		return $catalog_object;
 	}
 
-	/**
-	 * Build variations from repeater field
-	 */
-	private function buildVariations(int $postID, array $values, string $post_type): array
-	{
-		$variations = [];
-		$product_variations = $values['product_variations'] ?? [];
 
-		// Get variation field mapping
-		$variation_map = $this->getVariationMapping($post_type);
-
-		// If we have repeater variations
-		if (!empty($product_variations) && is_array($product_variations)) {
-			foreach ($product_variations as $index => $variation_data) {
-				// Skip empty variations
-				if (empty($variation_data['name']) && empty($variation_data['_square_sku'])) {
-					continue;
-				}
-
-				$variation = [
-					'type' => 'ITEM_VARIATION',
-					'id' => '#' . $post_type . '_' . $postID . '_var_' . $index,
-					'item_variation_data' => [
-						'name' => $variation_data['name'] ?? 'Variation ' . ($index + 1),
-						'ordinal' => $index,
-						'pricing_type' => 'FIXED_PRICING'
-					]
-				];
-
-				// Check for existing Square variation ID
-				$square_var_id = get_post_meta($postID, BASE . '_square_variation_' . $index . '_id', true);
-				if ($square_var_id) {
-					$variation['id'] = $square_var_id;
-				}
-
-				// Map variation fields
-				foreach ($variation_map as $square_field => $wp_field) {
-					if (isset($variation_data[$wp_field])) {
-						switch ($square_field) {
-							case 'price':
-								$variation['item_variation_data']['price_money'] = [
-									'amount' => intval($variation_data[$wp_field] * 100), // Convert to cents
-									'currency' => 'CAD'
-								];
-								break;
-
-							case 'sku':
-								$variation['item_variation_data']['_square_sku'] = $variation_data[$wp_field];
-								break;
-
-							case 'track_inventory':
-								$variation['item_variation_data']['track_inventory'] = (bool)$variation_data[$wp_field];
-								break;
-
-							case 'service_duration':
-								if (!empty($variation_data[$wp_field])) {
-									$variation['item_variation_data']['service_data'] = [
-										'duration_minutes' => intval($variation_data[$wp_field])
-									];
-								}
-								break;
-
-							default:
-								$variation['item_variation_data'][$square_field] = $variation_data[$wp_field];
-								break;
-						}
-					}
-				}
-
-				$variations[] = $variation;
-			}
-		}
-
-		// If no variations exist, create a default one from base price
-		if (empty($variations) && !empty($values['price'])) {
-			$variations[] = [
-				'type' => 'ITEM_VARIATION',
-				'id' => '#' . $post_type . '_' . $postID . '_var_default',
-				'item_variation_data' => [
-					'name' => 'Regular',
-					'ordinal' => 0,
-					'pricing_type' => 'FIXED_PRICING',
-					'price_money' => [
-						'amount' => intval($values['price'] * 100),
-						'currency' => 'CAD'
-					]
-				]
-			];
-		}
-
-		return $variations;
-	}
 
 	/**
 	 * Get variation mapping for post type
@@ -1616,13 +1526,9 @@
 			];
 		}
 
-		// Generate username from email
-		$username = sanitize_user(current(explode('@', $email)));
-		$username = $this->generateUniqueUsername($username);
-
 		// Create user account without password (they'll set it via email)
 		$user_id = wp_create_user(
-			$username,
+			$email,
 			wp_generate_password(20, true, true), // Temporary random password
 			$email
 		);
@@ -1637,7 +1543,7 @@
 
 		// Set user role (assuming you have a customer role defined)
 		$user = new \WP_User($user_id);
-		$user->set_role(BASE.'foodie'); // Or whatever role
+		$user->set_role(BASE.'foodie');
 
 		// Generate password reset key
 		$reset_key = get_password_reset_key($user);
@@ -1653,7 +1559,7 @@
 		// Link to Square customer if exists
 		$square_customer_id = $this->getOrCreateSquareCustomer([
 			'email' => $email,
-			'name' => $username
+			'name' => $email
 		]);
 
 		if ($square_customer_id) {
@@ -1668,22 +1574,6 @@
 	}
 
 	/**
-	 * Generate unique username
-	 */
-	private function generateUniqueUsername(string $base): string
-	{
-		$username = $base;
-		$counter = 1;
-
-		while (username_exists($username)) {
-			$username = $base . $counter;
-			$counter++;
-		}
-
-		return $username;
-	}
-
-	/**
 	 * Send welcome email with password setup
 	 */
 	private function sendWelcomeEmail(\WP_User $user, string $reset_key): void
@@ -1699,7 +1589,7 @@
 			"%s\n\n" .
 			"Once you've set your password, you can:\n" .
 			"- View your order history\n" .
-			"- Save your favorite items\n" .
+//			"- Save your favorite items\n" .
 			"- Speed up checkout with saved payment methods\n\n" .
 			"If you didn't create this account, please ignore this email.\n\n" .
 			"Thanks,\n",
@@ -1715,49 +1605,6 @@
 		);
 	}
 
-	/**
-	 * Track user login for security
-	 */
-	public function trackUserLogin(string $user_login, \WP_User $user): void
-	{
-		// Check if user has Square integration
-		$role = jvbUserRole($user->ID);
-		$registrar = Registrar::getInstance($role);
-		if ($registrar) {
-			$config = $registrar->getIntegration($this->service_name);
-			if ($config->isCustomer()) {
-				$login_count = (int)get_user_meta($user->ID, BASE . '_square_login_count', true);
-				$login_count++;
-
-				update_user_meta($user->ID, BASE . '_square_login_count', $login_count);
-				update_user_meta($user->ID, BASE . '_square_last_login', current_time('mysql'));
-
-				// Check if password reset is needed
-				if ($login_count % self::PASSWORD_RESET_INTERVAL === 0) {
-					$this->schedulePasswordReset($user->ID);
-				}
-			}
-		}
-	}
-
-	/**
-	 * Schedule password reset for security
-	 */
-	private function schedulePasswordReset(int $user_id): void
-	{
-		update_user_meta($user_id, BASE . '_square_password_reset_required', true);
-
-		// Send notification
-		$user = get_user_by('ID', $user_id);
-		if ($user) {
-			JVB()->email()->sendEmail(
-				$user->user_email,
-				'['.get_bloginfo('name').'] Security Code',
-				'For your security, enter this code to continue accessing your account and saved payment methods.',
-			);
-		}
-	}
-
 	/******************************************************************
 	 * WEBHOOK HANDLING
 	 ******************************************************************/
@@ -3643,4 +3490,10 @@
 		}, $posts->posts);
 
 	}
+
+	public function formatPrice(string|float|int $priceValue):int
+	{
+		//Convert dollars to cents
+		return intval(floatval($priceValue) * 100);
+	}
 }

--
Gitblit v1.10.0