feat: Switch to OAuth 2.0 authentication for Google Merchant API
- Replace service account authentication with OAuth 2.0 user flow - Add "Authorize with Google" button in admin settings - Handle OAuth callback and token exchange - Store refresh token for automatic access token renewal - Add revoke authorization functionality - Update admin UI to show authorization status - Update price updater to use new OAuth credentials - Add CSRF protection with state parameter This change supports organizations that have disabled service account key creation via iam.disableServiceAccountKeyCreation policy. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,9 +44,13 @@ class Informatiq_SP_Admin {
|
|||||||
// Register settings.
|
// Register settings.
|
||||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||||
|
|
||||||
|
// Handle OAuth callback.
|
||||||
|
add_action( 'admin_init', array( $this, 'handle_oauth_callback' ) );
|
||||||
|
|
||||||
// Handle AJAX requests.
|
// Handle AJAX requests.
|
||||||
add_action( 'wp_ajax_informatiq_sp_manual_sync', array( $this, 'handle_manual_sync' ) );
|
add_action( 'wp_ajax_informatiq_sp_manual_sync', array( $this, 'handle_manual_sync' ) );
|
||||||
add_action( 'wp_ajax_informatiq_sp_test_connection', array( $this, 'handle_test_connection' ) );
|
add_action( 'wp_ajax_informatiq_sp_test_connection', array( $this, 'handle_test_connection' ) );
|
||||||
|
add_action( 'wp_ajax_informatiq_sp_revoke_auth', array( $this, 'handle_revoke_auth' ) );
|
||||||
|
|
||||||
// Enqueue admin assets.
|
// Enqueue admin assets.
|
||||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
||||||
@@ -72,7 +76,8 @@ class Informatiq_SP_Admin {
|
|||||||
public function register_settings() {
|
public function register_settings() {
|
||||||
// Register settings.
|
// Register settings.
|
||||||
register_setting( 'informatiq_sp_settings', 'informatiq_sp_merchant_id' );
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_merchant_id' );
|
||||||
register_setting( 'informatiq_sp_settings', 'informatiq_sp_service_account' );
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_client_id' );
|
||||||
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_client_secret' );
|
||||||
register_setting( 'informatiq_sp_settings', 'informatiq_sp_minimum_margin' );
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_minimum_margin' );
|
||||||
register_setting( 'informatiq_sp_settings', 'informatiq_sp_auto_update_enabled' );
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_auto_update_enabled' );
|
||||||
register_setting( 'informatiq_sp_settings', 'informatiq_sp_update_frequency' );
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_update_frequency' );
|
||||||
@@ -109,9 +114,17 @@ class Informatiq_SP_Admin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
add_settings_field(
|
add_settings_field(
|
||||||
'informatiq_sp_service_account',
|
'informatiq_sp_oauth_credentials',
|
||||||
__( 'Service Account JSON', 'informatiq-smart-pricing' ),
|
__( 'OAuth 2.0 Credentials', 'informatiq-smart-pricing' ),
|
||||||
array( $this, 'render_service_account_field' ),
|
array( $this, 'render_oauth_credentials_field' ),
|
||||||
|
'informatiq_sp_settings',
|
||||||
|
'informatiq_sp_google_settings'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'informatiq_sp_authorization',
|
||||||
|
__( 'Google Authorization', 'informatiq-smart-pricing' ),
|
||||||
|
array( $this, 'render_authorization_field' ),
|
||||||
'informatiq_sp_settings',
|
'informatiq_sp_settings',
|
||||||
'informatiq_sp_google_settings'
|
'informatiq_sp_google_settings'
|
||||||
);
|
);
|
||||||
@@ -141,6 +154,108 @@ class Informatiq_SP_Admin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle OAuth callback from Google.
|
||||||
|
*/
|
||||||
|
public function handle_oauth_callback() {
|
||||||
|
// Check if this is an OAuth callback.
|
||||||
|
if ( ! isset( $_GET['informatiq_sp_oauth'] ) || $_GET['informatiq_sp_oauth'] !== 'callback' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify state parameter.
|
||||||
|
$state = isset( $_GET['state'] ) ? sanitize_text_field( $_GET['state'] ) : '';
|
||||||
|
$saved_state = get_transient( 'informatiq_sp_oauth_state' );
|
||||||
|
|
||||||
|
if ( empty( $state ) || $state !== $saved_state ) {
|
||||||
|
add_settings_error(
|
||||||
|
'informatiq_sp_messages',
|
||||||
|
'informatiq_sp_oauth_error',
|
||||||
|
__( 'OAuth state mismatch. Please try again.', 'informatiq-smart-pricing' ),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_transient( 'informatiq_sp_oauth_state' );
|
||||||
|
|
||||||
|
// Check for errors.
|
||||||
|
if ( isset( $_GET['error'] ) ) {
|
||||||
|
$error = sanitize_text_field( $_GET['error'] );
|
||||||
|
$error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( $_GET['error_description'] ) : $error;
|
||||||
|
add_settings_error(
|
||||||
|
'informatiq_sp_messages',
|
||||||
|
'informatiq_sp_oauth_error',
|
||||||
|
sprintf( __( 'Authorization failed: %s', 'informatiq-smart-pricing' ), $error_desc ),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get authorization code.
|
||||||
|
$code = isset( $_GET['code'] ) ? sanitize_text_field( $_GET['code'] ) : '';
|
||||||
|
|
||||||
|
if ( empty( $code ) ) {
|
||||||
|
add_settings_error(
|
||||||
|
'informatiq_sp_messages',
|
||||||
|
'informatiq_sp_oauth_error',
|
||||||
|
__( 'No authorization code received.', 'informatiq-smart-pricing' ),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange code for tokens.
|
||||||
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
||||||
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
||||||
|
$redirect_uri = $this->get_oauth_redirect_uri();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$tokens = Informatiq_SP_Google_API::exchange_code_for_tokens(
|
||||||
|
$code,
|
||||||
|
$client_id,
|
||||||
|
$client_secret,
|
||||||
|
$redirect_uri
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save refresh token.
|
||||||
|
update_option( 'informatiq_sp_refresh_token', $tokens['refresh_token'] );
|
||||||
|
|
||||||
|
$this->logger->info( 'Google OAuth authorization successful' );
|
||||||
|
|
||||||
|
// Redirect to settings page with success message.
|
||||||
|
wp_redirect( admin_url( 'admin.php?page=informatiq-smart-pricing&oauth=success' ) );
|
||||||
|
exit;
|
||||||
|
|
||||||
|
} catch ( Exception $e ) {
|
||||||
|
$this->logger->error( 'OAuth token exchange failed: ' . $e->getMessage() );
|
||||||
|
add_settings_error(
|
||||||
|
'informatiq_sp_messages',
|
||||||
|
'informatiq_sp_oauth_error',
|
||||||
|
sprintf( __( 'Token exchange failed: %s', 'informatiq-smart-pricing' ), $e->getMessage() ),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get OAuth redirect URI.
|
||||||
|
*
|
||||||
|
* @return string Redirect URI.
|
||||||
|
*/
|
||||||
|
private function get_oauth_redirect_uri() {
|
||||||
|
return admin_url( 'admin.php?page=informatiq-smart-pricing&informatiq_sp_oauth=callback' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if plugin is authorized.
|
||||||
|
*
|
||||||
|
* @return bool True if authorized.
|
||||||
|
*/
|
||||||
|
private function is_authorized() {
|
||||||
|
return ! empty( get_option( 'informatiq_sp_refresh_token' ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render admin page.
|
* Render admin page.
|
||||||
*/
|
*/
|
||||||
@@ -149,6 +264,16 @@ class Informatiq_SP_Admin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for OAuth success message.
|
||||||
|
if ( isset( $_GET['oauth'] ) && $_GET['oauth'] === 'success' ) {
|
||||||
|
add_settings_error(
|
||||||
|
'informatiq_sp_messages',
|
||||||
|
'informatiq_sp_message',
|
||||||
|
__( 'Google authorization successful!', 'informatiq-smart-pricing' ),
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if form was submitted.
|
// Check if form was submitted.
|
||||||
if ( isset( $_GET['settings-updated'] ) ) {
|
if ( isset( $_GET['settings-updated'] ) ) {
|
||||||
// Update cron schedule if frequency changed.
|
// Update cron schedule if frequency changed.
|
||||||
@@ -185,14 +310,20 @@ class Informatiq_SP_Admin {
|
|||||||
<h2><?php esc_html_e( 'Manual Actions', 'informatiq-smart-pricing' ); ?></h2>
|
<h2><?php esc_html_e( 'Manual Actions', 'informatiq-smart-pricing' ); ?></h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button type="button" class="button button-secondary" id="informatiq-sp-test-connection">
|
<button type="button" class="button button-secondary" id="informatiq-sp-test-connection" <?php disabled( ! $this->is_authorized() ); ?>>
|
||||||
<?php esc_html_e( 'Test Google API Connection', 'informatiq-smart-pricing' ); ?>
|
<?php esc_html_e( 'Test Google API Connection', 'informatiq-smart-pricing' ); ?>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="button button-primary" id="informatiq-sp-manual-sync">
|
<button type="button" class="button button-primary" id="informatiq-sp-manual-sync" <?php disabled( ! $this->is_authorized() ); ?>>
|
||||||
<?php esc_html_e( 'Run Manual Sync Now', 'informatiq-smart-pricing' ); ?>
|
<?php esc_html_e( 'Run Manual Sync Now', 'informatiq-smart-pricing' ); ?>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<?php if ( ! $this->is_authorized() ) : ?>
|
||||||
|
<p class="description" style="color: #d63638;">
|
||||||
|
<?php esc_html_e( 'Please authorize with Google first to use these features.', 'informatiq-smart-pricing' ); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div id="informatiq-sp-sync-status" class="notice" style="display: none;"></div>
|
<div id="informatiq-sp-sync-status" class="notice" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -217,7 +348,7 @@ class Informatiq_SP_Admin {
|
|||||||
* Render Google settings section description.
|
* Render Google settings section description.
|
||||||
*/
|
*/
|
||||||
public function render_google_settings_section() {
|
public function render_google_settings_section() {
|
||||||
echo '<p>' . esc_html__( 'Configure your Google Merchant Center API credentials.', 'informatiq-smart-pricing' ) . '</p>';
|
echo '<p>' . esc_html__( 'Configure your Google Merchant Center API credentials using OAuth 2.0.', 'informatiq-smart-pricing' ) . '</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,25 +374,100 @@ class Informatiq_SP_Admin {
|
|||||||
<input type="text" name="informatiq_sp_merchant_id" id="informatiq_sp_merchant_id"
|
<input type="text" name="informatiq_sp_merchant_id" id="informatiq_sp_merchant_id"
|
||||||
value="<?php echo esc_attr( $value ); ?>" class="regular-text">
|
value="<?php echo esc_attr( $value ); ?>" class="regular-text">
|
||||||
<p class="description">
|
<p class="description">
|
||||||
<?php esc_html_e( 'Your Google Merchant Center ID.', 'informatiq-smart-pricing' ); ?>
|
<?php esc_html_e( 'Your Google Merchant Center ID (found in Merchant Center settings).', 'informatiq-smart-pricing' ); ?>
|
||||||
</p>
|
</p>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render service account field.
|
* Render OAuth credentials field.
|
||||||
*/
|
*/
|
||||||
public function render_service_account_field() {
|
public function render_oauth_credentials_field() {
|
||||||
$value = get_option( 'informatiq_sp_service_account' );
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
||||||
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
||||||
?>
|
?>
|
||||||
<textarea name="informatiq_sp_service_account" id="informatiq_sp_service_account"
|
<div class="informatiq-sp-oauth-credentials">
|
||||||
rows="10" class="large-text code"><?php echo esc_textarea( $value ); ?></textarea>
|
<p>
|
||||||
<p class="description">
|
<label for="informatiq_sp_client_id"><strong><?php esc_html_e( 'Client ID:', 'informatiq-smart-pricing' ); ?></strong></label><br>
|
||||||
<?php esc_html_e( 'Paste your Google Service Account JSON key here.', 'informatiq-smart-pricing' ); ?>
|
<input type="text" name="informatiq_sp_client_id" id="informatiq_sp_client_id"
|
||||||
</p>
|
value="<?php echo esc_attr( $client_id ); ?>" class="large-text">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="informatiq_sp_client_secret"><strong><?php esc_html_e( 'Client Secret:', 'informatiq-smart-pricing' ); ?></strong></label><br>
|
||||||
|
<input type="password" name="informatiq_sp_client_secret" id="informatiq_sp_client_secret"
|
||||||
|
value="<?php echo esc_attr( $client_secret ); ?>" class="regular-text">
|
||||||
|
</p>
|
||||||
|
<p class="description">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: redirect URI */
|
||||||
|
esc_html__( 'Create OAuth 2.0 credentials in Google Cloud Console. Use this as your Authorized redirect URI: %s', 'informatiq-smart-pricing' ),
|
||||||
|
'<code>' . esc_html( $this->get_oauth_redirect_uri() ) . '</code>'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render authorization field.
|
||||||
|
*/
|
||||||
|
public function render_authorization_field() {
|
||||||
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
||||||
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
||||||
|
$is_authorized = $this->is_authorized();
|
||||||
|
$has_credentials = ! empty( $client_id ) && ! empty( $client_secret );
|
||||||
|
|
||||||
|
if ( $is_authorized ) {
|
||||||
|
?>
|
||||||
|
<div class="informatiq-sp-auth-status authorized">
|
||||||
|
<span class="dashicons dashicons-yes-alt" style="color: #00a32a;"></span>
|
||||||
|
<strong style="color: #00a32a;"><?php esc_html_e( 'Authorized', 'informatiq-smart-pricing' ); ?></strong>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Plugin is authorized to access your Google Merchant Center.', 'informatiq-smart-pricing' ); ?>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button type="button" class="button button-secondary" id="informatiq-sp-revoke-auth">
|
||||||
|
<?php esc_html_e( 'Revoke Authorization', 'informatiq-smart-pricing' ); ?>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
} elseif ( $has_credentials ) {
|
||||||
|
// Generate authorization URL.
|
||||||
|
$state = wp_generate_password( 32, false );
|
||||||
|
set_transient( 'informatiq_sp_oauth_state', $state, 600 ); // 10 minutes.
|
||||||
|
|
||||||
|
$auth_url = Informatiq_SP_Google_API::get_authorization_url(
|
||||||
|
$client_id,
|
||||||
|
$this->get_oauth_redirect_uri(),
|
||||||
|
$state
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<div class="informatiq-sp-auth-status not-authorized">
|
||||||
|
<span class="dashicons dashicons-warning" style="color: #dba617;"></span>
|
||||||
|
<strong style="color: #dba617;"><?php esc_html_e( 'Not Authorized', 'informatiq-smart-pricing' ); ?></strong>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Click the button below to authorize this plugin with your Google account.', 'informatiq-smart-pricing' ); ?>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="<?php echo esc_url( $auth_url ); ?>" class="button button-primary">
|
||||||
|
<?php esc_html_e( 'Authorize with Google', 'informatiq-smart-pricing' ); ?>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
} else {
|
||||||
|
?>
|
||||||
|
<div class="informatiq-sp-auth-status no-credentials">
|
||||||
|
<span class="dashicons dashicons-info" style="color: #72aee6;"></span>
|
||||||
|
<em><?php esc_html_e( 'Enter your OAuth credentials above and save settings first.', 'informatiq-smart-pricing' ); ?></em>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render minimum margin field.
|
* Render minimum margin field.
|
||||||
*/
|
*/
|
||||||
@@ -418,12 +624,13 @@ class Informatiq_SP_Admin {
|
|||||||
private function render_sidebar() {
|
private function render_sidebar() {
|
||||||
?>
|
?>
|
||||||
<div class="informatiq-sp-sidebar-box">
|
<div class="informatiq-sp-sidebar-box">
|
||||||
<h3><?php esc_html_e( 'How It Works', 'informatiq-smart-pricing' ); ?></h3>
|
<h3><?php esc_html_e( 'Setup Steps', 'informatiq-smart-pricing' ); ?></h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li><?php esc_html_e( 'Configure Google Merchant Center credentials', 'informatiq-smart-pricing' ); ?></li>
|
<li><?php esc_html_e( 'Create OAuth 2.0 credentials in Google Cloud Console', 'informatiq-smart-pricing' ); ?></li>
|
||||||
<li><?php esc_html_e( 'Set your minimum profit margin', 'informatiq-smart-pricing' ); ?></li>
|
<li><?php esc_html_e( 'Enter Client ID and Client Secret above', 'informatiq-smart-pricing' ); ?></li>
|
||||||
<li><?php esc_html_e( 'Enable automatic updates', 'informatiq-smart-pricing' ); ?></li>
|
<li><?php esc_html_e( 'Click "Authorize with Google"', 'informatiq-smart-pricing' ); ?></li>
|
||||||
<li><?php esc_html_e( 'Plugin automatically updates prices daily', 'informatiq-smart-pricing' ); ?></li>
|
<li><?php esc_html_e( 'Enter your Merchant ID', 'informatiq-smart-pricing' ); ?></li>
|
||||||
|
<li><?php esc_html_e( 'Set your minimum margin and enable automation', 'informatiq-smart-pricing' ); ?></li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -461,6 +668,10 @@ class Informatiq_SP_Admin {
|
|||||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! $this->is_authorized() ) {
|
||||||
|
wp_send_json_error( array( 'message' => __( 'Please authorize with Google first.', 'informatiq-smart-pricing' ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$scheduler = new Informatiq_SP_Scheduler( $this->price_updater );
|
$scheduler = new Informatiq_SP_Scheduler( $this->price_updater );
|
||||||
$results = $scheduler->trigger_manual_update();
|
$results = $scheduler->trigger_manual_update();
|
||||||
@@ -491,16 +702,28 @@ class Informatiq_SP_Admin {
|
|||||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
||||||
$service_account = get_option( 'informatiq_sp_service_account' );
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
||||||
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
||||||
|
$refresh_token = get_option( 'informatiq_sp_refresh_token' );
|
||||||
|
|
||||||
if ( empty( $merchant_id ) || empty( $service_account ) ) {
|
if ( empty( $merchant_id ) ) {
|
||||||
wp_send_json_error( array( 'message' => __( 'Please configure Google Merchant settings first.', 'informatiq-smart-pricing' ) ) );
|
wp_send_json_error( array( 'message' => __( 'Please enter your Merchant ID.', 'informatiq-smart-pricing' ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $refresh_token ) ) {
|
||||||
|
wp_send_json_error( array( 'message' => __( 'Please authorize with Google first.', 'informatiq-smart-pricing' ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$google_api = new Informatiq_SP_Google_API( $merchant_id, $service_account, $this->logger );
|
$google_api = new Informatiq_SP_Google_API(
|
||||||
$success = $google_api->test_connection();
|
$merchant_id,
|
||||||
|
$client_id,
|
||||||
|
$client_secret,
|
||||||
|
$refresh_token,
|
||||||
|
$this->logger
|
||||||
|
);
|
||||||
|
$success = $google_api->test_connection();
|
||||||
|
|
||||||
if ( $success ) {
|
if ( $success ) {
|
||||||
wp_send_json_success( array( 'message' => __( 'Connection successful!', 'informatiq-smart-pricing' ) ) );
|
wp_send_json_success( array( 'message' => __( 'Connection successful!', 'informatiq-smart-pricing' ) ) );
|
||||||
@@ -512,6 +735,23 @@ class Informatiq_SP_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle revoke authorization AJAX request.
|
||||||
|
*/
|
||||||
|
public function handle_revoke_auth() {
|
||||||
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||||
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_option( 'informatiq_sp_refresh_token' );
|
||||||
|
|
||||||
|
$this->logger->info( 'Google OAuth authorization revoked' );
|
||||||
|
|
||||||
|
wp_send_json_success( array( 'message' => __( 'Authorization revoked.', 'informatiq-smart-pricing' ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueue admin assets.
|
* Enqueue admin assets.
|
||||||
*
|
*
|
||||||
@@ -544,8 +784,10 @@ class Informatiq_SP_Admin {
|
|||||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||||
'nonce' => wp_create_nonce( 'informatiq_sp_admin' ),
|
'nonce' => wp_create_nonce( 'informatiq_sp_admin' ),
|
||||||
'strings' => array(
|
'strings' => array(
|
||||||
'syncInProgress' => __( 'Sync in progress...', 'informatiq-smart-pricing' ),
|
'syncInProgress' => __( 'Sync in progress...', 'informatiq-smart-pricing' ),
|
||||||
'testInProgress' => __( 'Testing connection...', 'informatiq-smart-pricing' ),
|
'testInProgress' => __( 'Testing connection...', 'informatiq-smart-pricing' ),
|
||||||
|
'revokeConfirm' => __( 'Are you sure you want to revoke Google authorization?', 'informatiq-smart-pricing' ),
|
||||||
|
'revokeInProgress' => __( 'Revoking...', 'informatiq-smart-pricing' ),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
bindEvents: function() {
|
bindEvents: function() {
|
||||||
$('#informatiq-sp-manual-sync').on('click', this.handleManualSync);
|
$('#informatiq-sp-manual-sync').on('click', this.handleManualSync);
|
||||||
$('#informatiq-sp-test-connection').on('click', this.handleTestConnection);
|
$('#informatiq-sp-test-connection').on('click', this.handleTestConnection);
|
||||||
|
$('#informatiq-sp-revoke-auth').on('click', this.handleRevokeAuth);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,6 +139,46 @@
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle revoke authorization button click
|
||||||
|
*/
|
||||||
|
handleRevokeAuth: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $button = $(this);
|
||||||
|
|
||||||
|
// Confirm action
|
||||||
|
if (!confirm(informatiqSP.strings.revokeConfirm || 'Are you sure you want to revoke Google authorization?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable button and show loading state
|
||||||
|
$button.prop('disabled', true).text(informatiqSP.strings.revokeInProgress || 'Revoking...');
|
||||||
|
|
||||||
|
// Make AJAX request
|
||||||
|
$.ajax({
|
||||||
|
url: informatiqSP.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'informatiq_sp_revoke_auth',
|
||||||
|
nonce: informatiqSP.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
// Reload page to show updated status
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (response.data.message || 'Unknown error'));
|
||||||
|
$button.prop('disabled', false).text('Revoke Authorization');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
|
alert('Error: ' + errorThrown);
|
||||||
|
$button.prop('disabled', false).text('Revoke Authorization');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Google Merchant API integration using direct REST calls.
|
* Google Merchant API integration using OAuth 2.0 authentication.
|
||||||
*
|
*
|
||||||
* This class implements authentication and API calls to the new Google Merchant API
|
* This class implements authentication and API calls to the Google Merchant API
|
||||||
* without requiring Composer or external dependencies.
|
* using OAuth 2.0 user authentication flow.
|
||||||
*
|
*
|
||||||
* @package InformatiqSmartPricing
|
* @package InformatiqSmartPricing
|
||||||
*/
|
*/
|
||||||
@@ -24,6 +24,13 @@ class Informatiq_SP_Google_API {
|
|||||||
*/
|
*/
|
||||||
const API_BASE_URL = 'https://merchantapi.googleapis.com';
|
const API_BASE_URL = 'https://merchantapi.googleapis.com';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth authorization endpoint.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth token endpoint.
|
* OAuth token endpoint.
|
||||||
*
|
*
|
||||||
@@ -45,13 +52,6 @@ class Informatiq_SP_Google_API {
|
|||||||
*/
|
*/
|
||||||
private $access_token;
|
private $access_token;
|
||||||
|
|
||||||
/**
|
|
||||||
* Token expiration time.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $token_expires;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merchant ID (account ID).
|
* Merchant ID (account ID).
|
||||||
*
|
*
|
||||||
@@ -60,11 +60,25 @@ class Informatiq_SP_Google_API {
|
|||||||
private $merchant_id;
|
private $merchant_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service account credentials.
|
* OAuth Client ID.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $credentials;
|
private $client_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth Client Secret.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $client_secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh token.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $refresh_token;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger instance.
|
* Logger instance.
|
||||||
@@ -76,27 +90,97 @@ class Informatiq_SP_Google_API {
|
|||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param string $merchant_id Google Merchant ID.
|
* @param string $merchant_id Google Merchant ID.
|
||||||
* @param string $service_account Service account JSON.
|
* @param string $client_id OAuth Client ID.
|
||||||
* @param Informatiq_SP_Logger $logger Logger instance.
|
* @param string $client_secret OAuth Client Secret.
|
||||||
* @throws Exception If authentication fails.
|
* @param string $refresh_token OAuth Refresh Token.
|
||||||
|
* @param Informatiq_SP_Logger $logger Logger instance.
|
||||||
|
* @throws Exception If initialization fails.
|
||||||
*/
|
*/
|
||||||
public function __construct( $merchant_id, $service_account, $logger ) {
|
public function __construct( $merchant_id, $client_id, $client_secret, $refresh_token, $logger ) {
|
||||||
$this->merchant_id = $merchant_id;
|
$this->merchant_id = $merchant_id;
|
||||||
$this->logger = $logger;
|
$this->client_id = $client_id;
|
||||||
|
$this->client_secret = $client_secret;
|
||||||
|
$this->refresh_token = $refresh_token;
|
||||||
|
$this->logger = $logger;
|
||||||
|
|
||||||
// Parse service account JSON.
|
if ( empty( $client_id ) || empty( $client_secret ) ) {
|
||||||
$this->credentials = json_decode( $service_account, true );
|
throw new Exception( 'OAuth Client ID and Client Secret are required' );
|
||||||
|
|
||||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
|
||||||
throw new Exception( 'Invalid service account JSON: ' . json_last_error_msg() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( empty( $this->credentials['client_email'] ) || empty( $this->credentials['private_key'] ) ) {
|
if ( empty( $refresh_token ) ) {
|
||||||
throw new Exception( 'Service account JSON must contain client_email and private_key' );
|
throw new Exception( 'Not authorized. Please authorize with Google first.' );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the authorization URL for OAuth flow.
|
||||||
|
*
|
||||||
|
* @param string $client_id OAuth Client ID.
|
||||||
|
* @param string $redirect_uri Redirect URI after authorization.
|
||||||
|
* @param string $state State parameter for CSRF protection.
|
||||||
|
* @return string Authorization URL.
|
||||||
|
*/
|
||||||
|
public static function get_authorization_url( $client_id, $redirect_uri, $state ) {
|
||||||
|
$params = array(
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'redirect_uri' => $redirect_uri,
|
||||||
|
'response_type' => 'code',
|
||||||
|
'scope' => self::API_SCOPE,
|
||||||
|
'access_type' => 'offline',
|
||||||
|
'prompt' => 'consent',
|
||||||
|
'state' => $state,
|
||||||
|
);
|
||||||
|
|
||||||
|
return self::AUTH_URL . '?' . http_build_query( $params );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange authorization code for tokens.
|
||||||
|
*
|
||||||
|
* @param string $code Authorization code from Google.
|
||||||
|
* @param string $client_id OAuth Client ID.
|
||||||
|
* @param string $client_secret OAuth Client Secret.
|
||||||
|
* @param string $redirect_uri Redirect URI used in authorization.
|
||||||
|
* @return array Tokens array with access_token and refresh_token.
|
||||||
|
* @throws Exception If token exchange fails.
|
||||||
|
*/
|
||||||
|
public static function exchange_code_for_tokens( $code, $client_id, $client_secret, $redirect_uri ) {
|
||||||
|
$response = wp_remote_post(
|
||||||
|
self::TOKEN_URL,
|
||||||
|
array(
|
||||||
|
'body' => array(
|
||||||
|
'code' => $code,
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_secret' => $client_secret,
|
||||||
|
'redirect_uri' => $redirect_uri,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
),
|
||||||
|
'timeout' => 30,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
throw new Exception( 'Token exchange failed: ' . $response->get_error_message() );
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||||
|
|
||||||
|
if ( isset( $body['error'] ) ) {
|
||||||
|
throw new Exception( 'Token error: ' . ( $body['error_description'] ?? $body['error'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $body['access_token'] ) || empty( $body['refresh_token'] ) ) {
|
||||||
|
throw new Exception( 'Invalid token response from Google' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'access_token' => $body['access_token'],
|
||||||
|
'refresh_token' => $body['refresh_token'],
|
||||||
|
'expires_in' => $body['expires_in'] ?? 3600,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get access token, refreshing if necessary.
|
* Get access token, refreshing if necessary.
|
||||||
*
|
*
|
||||||
@@ -104,33 +188,38 @@ class Informatiq_SP_Google_API {
|
|||||||
* @throws Exception If token retrieval fails.
|
* @throws Exception If token retrieval fails.
|
||||||
*/
|
*/
|
||||||
private function get_access_token() {
|
private function get_access_token() {
|
||||||
// Return cached token if still valid (with 60 second buffer).
|
// Return cached token if available.
|
||||||
if ( $this->access_token && $this->token_expires > ( time() + 60 ) ) {
|
if ( $this->access_token ) {
|
||||||
return $this->access_token;
|
return $this->access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create JWT for token exchange.
|
// Refresh the access token.
|
||||||
$jwt = $this->create_jwt();
|
|
||||||
|
|
||||||
// Exchange JWT for access token.
|
|
||||||
$response = wp_remote_post(
|
$response = wp_remote_post(
|
||||||
self::TOKEN_URL,
|
self::TOKEN_URL,
|
||||||
array(
|
array(
|
||||||
'body' => array(
|
'body' => array(
|
||||||
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
'client_id' => $this->client_id,
|
||||||
'assertion' => $jwt,
|
'client_secret' => $this->client_secret,
|
||||||
|
'refresh_token' => $this->refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
),
|
),
|
||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( is_wp_error( $response ) ) {
|
if ( is_wp_error( $response ) ) {
|
||||||
throw new Exception( 'Token request failed: ' . $response->get_error_message() );
|
throw new Exception( 'Token refresh failed: ' . $response->get_error_message() );
|
||||||
}
|
}
|
||||||
|
|
||||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||||
|
|
||||||
if ( isset( $body['error'] ) ) {
|
if ( isset( $body['error'] ) ) {
|
||||||
|
// If refresh token is invalid, user needs to re-authorize.
|
||||||
|
if ( $body['error'] === 'invalid_grant' ) {
|
||||||
|
// Clear the stored refresh token.
|
||||||
|
delete_option( 'informatiq_sp_refresh_token' );
|
||||||
|
throw new Exception( 'Authorization expired. Please re-authorize with Google.' );
|
||||||
|
}
|
||||||
throw new Exception( 'Token error: ' . ( $body['error_description'] ?? $body['error'] ) );
|
throw new Exception( 'Token error: ' . ( $body['error_description'] ?? $body['error'] ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,71 +227,11 @@ class Informatiq_SP_Google_API {
|
|||||||
throw new Exception( 'No access token in response' );
|
throw new Exception( 'No access token in response' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->access_token = $body['access_token'];
|
$this->access_token = $body['access_token'];
|
||||||
$this->token_expires = time() + ( $body['expires_in'] ?? 3600 );
|
|
||||||
|
|
||||||
return $this->access_token;
|
return $this->access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a signed JWT for authentication.
|
|
||||||
*
|
|
||||||
* @return string Signed JWT.
|
|
||||||
* @throws Exception If JWT creation fails.
|
|
||||||
*/
|
|
||||||
private function create_jwt() {
|
|
||||||
$now = time();
|
|
||||||
|
|
||||||
// JWT Header.
|
|
||||||
$header = array(
|
|
||||||
'alg' => 'RS256',
|
|
||||||
'typ' => 'JWT',
|
|
||||||
);
|
|
||||||
|
|
||||||
// JWT Claims.
|
|
||||||
$claims = array(
|
|
||||||
'iss' => $this->credentials['client_email'],
|
|
||||||
'scope' => self::API_SCOPE,
|
|
||||||
'aud' => self::TOKEN_URL,
|
|
||||||
'iat' => $now,
|
|
||||||
'exp' => $now + 3600,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Encode header and claims.
|
|
||||||
$header_encoded = $this->base64url_encode( wp_json_encode( $header ) );
|
|
||||||
$claims_encoded = $this->base64url_encode( wp_json_encode( $claims ) );
|
|
||||||
|
|
||||||
// Create signature input.
|
|
||||||
$signature_input = $header_encoded . '.' . $claims_encoded;
|
|
||||||
|
|
||||||
// Sign with private key.
|
|
||||||
$private_key = openssl_pkey_get_private( $this->credentials['private_key'] );
|
|
||||||
|
|
||||||
if ( ! $private_key ) {
|
|
||||||
throw new Exception( 'Invalid private key in service account' );
|
|
||||||
}
|
|
||||||
|
|
||||||
$signature = '';
|
|
||||||
$success = openssl_sign( $signature_input, $signature, $private_key, OPENSSL_ALGO_SHA256 );
|
|
||||||
|
|
||||||
if ( ! $success ) {
|
|
||||||
throw new Exception( 'Failed to sign JWT' );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return complete JWT.
|
|
||||||
return $signature_input . '.' . $this->base64url_encode( $signature );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base64url encode (URL-safe base64).
|
|
||||||
*
|
|
||||||
* @param string $data Data to encode.
|
|
||||||
* @return string Encoded data.
|
|
||||||
*/
|
|
||||||
private function base64url_encode( $data ) {
|
|
||||||
return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an API request to the Merchant API.
|
* Make an API request to the Merchant API.
|
||||||
*
|
*
|
||||||
@@ -411,8 +440,8 @@ class Informatiq_SP_Google_API {
|
|||||||
$endpoint .= '&pageToken=' . urlencode( $page_token );
|
$endpoint .= '&pageToken=' . urlencode( $page_token );
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->api_request( 'GET', $endpoint );
|
$response = $this->api_request( 'GET', $endpoint );
|
||||||
$products = $response['products'] ?? array();
|
$products = $response['products'] ?? array();
|
||||||
$all_products = array_merge( $all_products, $products );
|
$all_products = array_merge( $all_products, $products );
|
||||||
|
|
||||||
$page_token = $response['nextPageToken'] ?? null;
|
$page_token = $response['nextPageToken'] ?? null;
|
||||||
|
|||||||
@@ -58,19 +58,21 @@ class Informatiq_SP_Price_Updater {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Get plugin settings.
|
// Get plugin settings.
|
||||||
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
||||||
$service_account = get_option( 'informatiq_sp_service_account' );
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
||||||
$minimum_margin = (float) get_option( 'informatiq_sp_minimum_margin', 10 );
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
||||||
|
$refresh_token = get_option( 'informatiq_sp_refresh_token' );
|
||||||
|
$minimum_margin = (float) get_option( 'informatiq_sp_minimum_margin', 10 );
|
||||||
|
|
||||||
// Validate settings.
|
// Validate settings.
|
||||||
if ( empty( $merchant_id ) || empty( $service_account ) ) {
|
if ( empty( $merchant_id ) || empty( $refresh_token ) ) {
|
||||||
$this->logger->error( 'Google Merchant settings not configured. Please configure settings first.' );
|
$this->logger->error( 'Google Merchant settings not configured. Please configure and authorize first.' );
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Google API.
|
// Initialize Google API.
|
||||||
try {
|
try {
|
||||||
$google_api = new Informatiq_SP_Google_API( $merchant_id, $service_account, $this->logger );
|
$google_api = new Informatiq_SP_Google_API( $merchant_id, $client_id, $client_secret, $refresh_token, $this->logger );
|
||||||
} catch ( Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
$this->logger->error( 'Failed to initialize Google API: ' . $e->getMessage() );
|
$this->logger->error( 'Failed to initialize Google API: ' . $e->getMessage() );
|
||||||
return $results;
|
return $results;
|
||||||
@@ -362,19 +364,21 @@ class Informatiq_SP_Price_Updater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get settings.
|
// Get settings.
|
||||||
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
||||||
$service_account = get_option( 'informatiq_sp_service_account' );
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
||||||
$minimum_margin = (float) get_option( 'informatiq_sp_minimum_margin', 10 );
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
||||||
|
$refresh_token = get_option( 'informatiq_sp_refresh_token' );
|
||||||
|
$minimum_margin = (float) get_option( 'informatiq_sp_minimum_margin', 10 );
|
||||||
|
|
||||||
if ( empty( $merchant_id ) || empty( $service_account ) ) {
|
if ( empty( $merchant_id ) || empty( $refresh_token ) ) {
|
||||||
return array(
|
return array(
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Google Merchant settings not configured',
|
'message' => 'Google Merchant settings not configured or not authorized',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$google_api = new Informatiq_SP_Google_API( $merchant_id, $service_account, $this->logger );
|
$google_api = new Informatiq_SP_Google_API( $merchant_id, $client_id, $client_secret, $refresh_token, $this->logger );
|
||||||
$updated = $this->process_single_product( $product, $google_api, $minimum_margin );
|
$updated = $this->process_single_product( $product, $google_api, $minimum_margin );
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
|
|||||||
Reference in New Issue
Block a user