From a239ef33ec489ce6d0f25ac2f256df0ac54a7e50 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 16 Sep 2026 00:07:34 +0000
Subject: [PATCH] =Tackling the integrations again. This time Helcim, with some minnor edits to GMB and Square

---
 inc/integrations/services/Square.php           |   27 ++++--
 inc/integrations/services/GoogleMyBusiness.php |   69 +++++++++++++++++
 inc/integrations/services/Helcim.php           |  130 ++++++++++++++++++++++++++++++++
 3 files changed, 215 insertions(+), 11 deletions(-)

diff --git a/inc/integrations/services/GoogleMyBusiness.php b/inc/integrations/services/GoogleMyBusiness.php
index 17d7df1..694285e 100644
--- a/inc/integrations/services/GoogleMyBusiness.php
+++ b/inc/integrations/services/GoogleMyBusiness.php
@@ -169,12 +169,81 @@
 
 	protected function refreshAccessToken(OAuthCredentials $credentials): array
 	{
+		if (empty($this->getClientID()) || empty($this->getClientSecret()) || empty($credentials->refresh_token)) {
+			$this->logError('refreshAccessToken', 'Missing refresh token');
+			return $this->response(false, 'Missing client ID, client Secret, or refresh token');
+		}
+		$response = wp_remote_post(
+			$this->oauth->getToken(),
+			[
+				'timeout'	=> 15,
+				'body'	=> [
+					'grant_type' 	=> 'refresh_token',
+					'refresh_token'	=> $credentials->refresh_token,
+					'client_id'		=>$this->getClientID(),
+					'client_secret'	=>$this->getClientSecret()
+				],
+				'headers'	=> $this->getOAuthRequestHeaders()
+			]
+		);
+		if (is_wp_error($response)) {
+			$this->logError(
+				'refreshAccessToken',
+				'Failed to refresh GMB Token',
+				[
+					'error'	=> $response->get_error_message()
+				]
+			);
+			return $this->response(false, 'Failed to refresh GMB Token');
+		}
 
+		$data = json_decode(wp_remote_retrieve_body($response), true);
+		if (empty($data['access_token'])) {
+			$this->logError('refreshAccessToken', 'No access token in response');
+			return $this->response(false, 'No access token in response');
+		}
+		$credentials = [
+			'access_token'	=> $data['access_token'],
+			'expires_at'	=> time() + (int)($data['expires_in']??3600),
+			// Google usually omits refresh_token on refresh — keep the old one
+			'refresh_token' => $body['refresh_token'] ?? $credentials->refresh_token,
+		];
+		$this->saveCredentials($credentials);
+
+		return $this->response(true, 'Successfully refreshed token');
 	}
 
 	/*********************************************************
 	 * UTILITY
 	*********************************************************/
+	protected function getClientID():string|false
+	{
+		$switch = false;
+		if ($this->userID !== null) {
+			$switch = $this->userID;
+			$this->switchUser(null);
+		}
+
+		$ID =  $this->loadCredentials()['client_id']??false;
+		if ($switch) {
+			$this->switchUser($switch);
+		}
+		return $ID;
+	}
+	protected function getClientSecret():string|false
+	{
+		$switch = false;
+		if ($this->userID !== null) {
+			$switch = $this->userID;
+			$this->switchUser(null);
+		}
+
+		$ID = $this->loadCredentials()['client_secret']??false;
+		if ($switch) {
+			$this->switchUser($switch);
+		}
+		return $ID;
+	}
 	protected function getAccountID():string|false
 	{
 		return $this->loadCredentials()['account_id']??false;
diff --git a/inc/integrations/services/Helcim.php b/inc/integrations/services/Helcim.php
index d057b17..f388c96 100644
--- a/inc/integrations/services/Helcim.php
+++ b/inc/integrations/services/Helcim.php
@@ -4,6 +4,12 @@
 use JVBase\integrations\Admin;
 use JVBase\Instance;
 use JVBase\integrations\Integrations;
+use JVBase\integrations\OAuth;
+use JVBase\integrations\OAuthCredentials;
+use JVBase\integrations\Requests;
+use JVBase\integrations\SyncToPost;
+use JVBase\integrations\UserConnection;
+use WP_Error;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -22,10 +28,132 @@
  */
 class Helcim extends Integrations
 {
-	use Admin, Instance;
+	use Admin, Instance, OAuth, Requests, UserConnection, SyncToPost;
+
+	protected function __construct()
+	{
+		$this->service_name = 'helcim';
+		$this->title = 'Helcim';
+		$this->icon = 'currency-dollar';
+
+		$this->canSync = [
+			'initial'	=> true,
+			'update'	=> true,
+			'delete'	=> true,
+		];
+
+		$this->baseUrl = 'https://api.helcim.com/v2';
+
+		$this->defineFields();
+		$this->defineInstructions();
+		$this->defineActions();
+
+		parent::__construct();
+	}
+		protected function defineFields():void
+		{
+			$api_token = $this->addField(
+				'api_token',
+				'API Token',
+				'text',
+				[$this, 'testUserCanConnect']
+			);
+				$api_token->setSubType('password');
+				$api_token->setRequired();
+				$api_token->setHint('Found in Helcim Dashboard → Settings → API Access');
+
+			$currency = $this->addField(
+				'currency',
+				'Currency',
+				'select',
+				[$this, 'testUserSetup']
+			);
+				$currency->setOptions([
+					'CAD'	=> 'CAD',
+					'USD'	=> 'USD'
+				]);
+				$currency->setDefault('CAD');
+
+			$feeSaver = $this->addField(
+				'fee_saver',
+				'Fee Saver',
+				'true_false',
+				[$this, 'testUserSetup']
+			);
+				$feeSaver->setHint('Pass processing fees to customers? (Not compatible with Google Pay)');
+
+			$allowACH = $this->addField(
+				'allow_ach',
+				'Allow ACH/Bank Payments',
+				'true_false',
+				[$this, 'testUserSetup']
+			);
+				$allowACH->setHint('Enable bank account payments alongside credit card');
+		}
+			public function testUserCanConnect():bool
+			{
+				$user = get_current_user_id();
+				return $this->userCanConnect($user);
+			}
+			public function testUserSetup():bool
+			{
+				return $this->isSetUp() && $this->testUserCanConnect() && !empty($this->getApiToken());
+			}
+
+		protected function defineInstructions():void
+		{
+			$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',
+			];
+		}
+
+		protected function defineActions():void
+		{
+			$this->addAction(
+				'Initialize Checkout',
+				[$this, 'initializeCheckout'],
+				'initialize_checkout'
+			);
+		}
 
 	protected function initialize(): void
 	{
 		// TODO: Implement initialize() method.
 	}
+
+	protected function refreshAccessToken(OAuthCredentials $credentials): array
+	{
+		// TODO: Implement refreshAccessToken() method.
+	}
+
+	protected function getRequestHeaders(): array
+	{
+		// TODO: Implement getRequestHeaders() method.
+	}
+
+	protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array
+	{
+		// TODO: Implement handleResponse() method.
+	}
+
+	protected function setContentTypes(): void
+	{
+		// TODO: Implement setContentTypes() method.
+	}
+
+	protected function formatForService(int $itemID, string $type = 'post'): array
+	{
+		// TODO: Implement formatForService() method.
+	}
+	/*************************************************************************
+	 * UTILITY
+	*************************************************************************/
+	protected function getApiToken():string|false
+	{
+		return $this->loadCredentials()['api_token']??false;
+	}
 }
diff --git a/inc/integrations/services/Square.php b/inc/integrations/services/Square.php
index 89d49c1..c642247 100644
--- a/inc/integrations/services/Square.php
+++ b/inc/integrations/services/Square.php
@@ -254,10 +254,15 @@
 
 	protected function refreshAccessToken(OAuthCredentials $credentials): array
 	{
-		if (!$this->getClientID() || !$this->getClientSecret())
+		if (!$this->getClientID() || !$this->getClientSecret() || empty($credentials->refresh_token)) {
+			$this->logError('refreshAccessToken', 'Missing client credentials or refresh token');
+			return $this->response(false, 'No client ID or client secret provided');
+		}
+
 		$response = wp_remote_post(
 			$this->oauth->getToken(),
 			[
+				'timeout'	=> 15,
 				'body'	=> [
 					'client_id'		=> $this->getClientID(),
 					'client_secret' => $this->getClientSecret(),
@@ -279,16 +284,18 @@
 		}
 
 		$data = json_decode(wp_remote_retrieve_body($response));
-		if (isset($data['access_token'])) {
-			$credentials = [
-				'access_token'	=>  $data['access_token'],
-				'expires_at'	=> time() + ($data['expires_in'] ?? 2592000) // 30 days default
-			];
-
-			$this->saveCredentials($credentials);
-			return $this->response(true, 'Successfully refreshed access token');
+		if (empty($data['access_token'])) {
+			$this->logError('refreshAccessToken', 'No access_token in refresh response', ['body' => $data]);
+			return $this->response(false, 'No Access Token in Response');
 		}
-		return $this->response(false, 'Something went wrong that wasn\'t caught - could not refresh access token');
+		$credentials = [
+			'access_token'  => $data['access_token'],
+			'expires_at'    => strtotime($data['expires_at'] ?? '') ?: (time() + 2592000),
+			'refresh_token' => $data['refresh_token'] ?? $credentials->refresh_token,
+		];
+		$this->saveCredentials($credentials);
+
+		return $this->response(true, 'Successfully refreshed access token');
 	}
 
 	protected function getRequestHeaders(): array

--
Gitblit v1.10.0