Skip to content

Commit

Permalink
rest of implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
budzanowski committed Oct 18, 2024
1 parent 88be837 commit 2bcf69b
Show file tree
Hide file tree
Showing 6 changed files with 575 additions and 54 deletions.
111 changes: 58 additions & 53 deletions class-pinterest-for-woocommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ public function init_plugin() {
add_action( 'init', array( Pinterest\Billing::class, 'schedule_event' ) );
add_action( 'init', array( Pinterest\AdCredits::class, 'schedule_event' ) );
add_action( 'init', array( Pinterest\RefreshToken::class, 'schedule_event' ) );
add_action( 'init', array( Pinterest\CommerceIntegrationSync::class, 'schedule_event' ) );

// Register the marketing channel if the feature is included.
if ( defined( 'WC_MCM_EXISTS' ) ) {
Expand All @@ -311,6 +312,8 @@ public function init_plugin() {

add_action( 'action_scheduler_failed_execution', array( self::class, 'action_scheduler_reset_connection' ), 10, 2 );

add_action( 'pinterest_for_woocommerce_retry_commerce_integration', array( self::class, 'create_commerce_integration' ), 10, 1 );

// Handle the Pinterest verification URL.
add_action( 'parse_request', array( $this, 'verification_request' ) );

Expand Down Expand Up @@ -880,6 +883,9 @@ private static function flush_options() {
// Cancel scheduled jobs.
Pinterest\ProductSync::cancel_jobs();
Heartbeat::cancel_jobs();

// Cancel Create Commerce Integration scheduled actions if any.
as_unschedule_all_actions( 'pinterest_for_woocommerce_retry_commerce_integration' );
}

/**
Expand Down Expand Up @@ -960,66 +966,61 @@ public function maybe_inject_verification_code() {
}

/**
* Connects WC to Pinterest.
* Creates Commerce Integration with Pinterest.
*
* @return array the result of APIV5::create_commerce_integration.
* @throws Exception In case of 404, 409 and 500 errors from Pinterest.
* @param int $attempt Create Commerce Integration attempt number.
*
* @return void
* @throws Exception In case of Advertiser ID is missing while preparing commerce integration data.
* @see Pinterest\CommerceIntegrationSync::prepare_commerce_integration_data.
* @see Pinterest\API\APIV5::create_commerce_integration
* @since 1.4.0
*/
public static function create_commerce_integration(): array {
global $wp_version;

$external_business_id = self::generate_external_business_id();
$connection_data = self::get_data( 'connection_info_data', true );

// It does not make any sense to create integration without Advertiser ID.
if ( empty( $connection_data['advertiser_id'] ) ) {
throw new Exception(
sprintf(
esc_html__(
'Commerce Integration cannot be created: Advertiser ID is missing.',
'pinterest-for-woocommerce'
)
)
);
public static function create_commerce_integration( $attempt = 1 ): void {
if ( empty( $attempt ) || ! is_numeric( $attempt ) ) {
$attempt = 1;
}

$integration_data = array(
'external_business_id' => $external_business_id,
'connected_merchant_id' => $connection_data['merchant_id'] ?? '',
'connected_advertiser_id' => $connection_data['advertiser_id'] ?? '',
'partner_metadata' => json_encode(
array(
'plugin_version' => PINTEREST_FOR_WOOCOMMERCE_VERSION,
'wc_version' => defined( 'WC_VERSION' ) ? WC_VERSION : 'unknown',
'wp_version' => $wp_version,
'locale' => get_locale(),
'currency' => get_woocommerce_currency(),
)
),
);
$integration_data = Pinterest\CommerceIntegrationSync::prepare_commerce_integration_data();

if ( ! empty( $connection_data['tag_id'] ) ) {
$integration_data['connected_tag_id'] = $connection_data['tag_id'];
}

$response = Pinterest\API\APIV5::create_commerce_integration( $integration_data );
try {
$response = Pinterest\API\APIV5::create_commerce_integration( $integration_data );

/*
* In case of successful response we save our integration data into a database.
* Data we save includes but not limited to:
* external business id,
* id,
* connected_user_id,
* etc.
*/
self::save_integration_data( $response );
} catch ( PinterestApiException $e ) {
/**
* Number of retries has exceeded.
* Other retries are going to be handled weekly by Commerce Integration Sync action.
*
* @see Pinterest\CommerceIntegrationSync
*/
if ( 3 === $attempt ) {
return;
}

/*
* In case of successful response we save our integration data into a database.
* Data we save includes but not limited to:
* external business id,
* id,
* connected_user_id,
* etc.
*/
self::save_integration_data( $response );
as_unschedule_all_actions( 'pinterest_for_woocommerce_retry_commerce_integration' );

self::save_setting( 'tracking_advertiser', $response['connected_advertiser_id'] );
self::save_setting( 'tracking_tag', $response['connected_tag_id'] );
// Schedule a retry.
as_schedule_single_action(
time() + 10 * $attempt * MINUTE_IN_SECONDS,
'pinterest_for_woocommerce_retry_commerce_integration',
array(
'attempt' => $attempt + 1,
),
PINTEREST_FOR_WOOCOMMERCE_PREFIX
);
}

return $response;
self::save_setting( 'tracking_advertiser', $integration_data['connected_advertiser_id'] );
self::save_setting( 'tracking_tag', $integration_data['connected_tag_id'] ?? '' );
}

/**
Expand Down Expand Up @@ -1048,7 +1049,10 @@ public static function update_commerce_integration( string $external_business_id
*/
public static function delete_commerce_integration(): bool {
try {
$external_business_id = self::get_data( 'integration_data' )['external_business_id'];
$external_business_id = self::get_data( 'integration_data' )['external_business_id'] ?? '';
if ( empty( $external_business_id ) ) {
return false;
}
Pinterest\API\APIV5::delete_commerce_integration( $external_business_id );
return true;
} catch ( PinterestApiException $e ) {
Expand All @@ -1060,6 +1064,7 @@ public static function delete_commerce_integration(): bool {
/**
* Used to generate external business id to pass it Pinterest when creating a connection between WC and Pinterest.
*
* @deprecated since 1.4.10
* @since 1.4.0
*
* @return string
Expand Down Expand Up @@ -1384,8 +1389,8 @@ public static function is_setup_complete() {
* @return boolean
*/
public static function is_connected() {
$integration = self::get_data( 'integration_data' );
return ! empty( $integration['id'] ?? '' );
$token_data = self::get_data( 'token_data', true );
return ! empty( $token_data['access_token'] ?? '' );
}


Expand Down
33 changes: 33 additions & 0 deletions src/API/APIV5.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,39 @@ public static function domain_metatag_verification_request( string $domain ): ar
);
}

/**
* Retrieves Commerce Integration.
*
* @param string $external_business_id WooCommerce's External Business ID (an autogenerated identifier of a customer).
* @return array {
* Integration data returned by Pinterest.
*
* @type string $id ID of the integration (string all digits).
* @type string $external_business_id External business ID for the integration.
* @type string $connected_merchant_id Connected merchant ID for the integration.
* @type string $connected_user_id Connected user ID for the integration.
* @type string $connected_advertiser_id Connected advertiser ID for the integration.
* @type string $connected_lba_id Connected LBA ID for the integration.
* @type string $connected_tag_id Connected tag ID for the integration.
* @type int $partner_access_token_expiry Partner access token expiry for the integration.
* @type int $partner_refresh_token_expiry Partner refresh token expiry for the integration.
* @type string $scopes Scopes for the integration.
* @type int $created_timestamp Created timestamp for the integration.
* @type int $updated_timestamp Updated timestamp for the integration.
* @type string $additional_id_1 Additional ID 1 for the integration.
* @type string $partner_metadata Partner metadata for the integration.
* }
* @throws PinterestApiException If the request fails with other than 200 status.
* @link https://developers.pinterest.com/docs/api/v5/integrations_commerce-get
* @since x.x.x
*/
public static function get_commerce_integration( string $external_business_id ): array {
return self::make_request(
"integrations/commerce/{$external_business_id}",
'GET',
);
}

/**
* Create commerce integration.
*
Expand Down
2 changes: 1 addition & 1 deletion src/API/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function get_settings() {
$settings = Pinterest_For_Woocommerce()::get_settings( true );
if ( empty( $settings['account_data']['id'] ) ) {
$integration_data = Pinterest_For_Woocommerce::get_data( 'integration_data' );
$settings['account_data']['id'] = $integration_data['connected_user_id'] ?? '';
$settings['account_data']['id'] = $integration_data['connected_user_id'] ?? 'unknown';
}
return array(
PINTEREST_FOR_WOOCOMMERCE_OPTION_NAME => $settings,
Expand Down
178 changes: 178 additions & 0 deletions src/CommerceIntegrationSync.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php //phpcs:disable WordPress.WP.AlternativeFunctions --- Uses FS read/write in order to reliable append to an existing file.
/**
* Pinterest for WooCommerce Commerce Integration Sync
*
* @package Pinterest_For_WooCommerce/Classes/
* @version 1.0.0
*/

namespace Automattic\WooCommerce\Pinterest;

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

use Automattic\WooCommerce\Pinterest\API\APIV5;
use Exception;
use Pinterest_For_Woocommerce;
use WC_Log_Levels;

/**
* Handling Pinterest Commerce Integration synchronisation.
* Pinterest is mostly interested in the `partner_metadata` part of it.
*/
class CommerceIntegrationSync {

/**
* Check and schedule a weekly event.
*
* @return void
*/
public static function schedule_event() {
if ( ! Pinterest_For_Woocommerce::is_connected() ) {
return;
}

if ( ! has_action( Heartbeat::WEEKLY, array( self::class, 'handle_sync' ) ) ) {
add_action( Heartbeat::WEEKLY, array( self::class, 'handle_sync' ) );
}
}

/**
* Handle Pinterest Commerce Integration weekly sync.
*
* @since x.x.x
* @return bool
*/
public static function handle_sync(): bool {
try {
$external_business_id = Pinterest_For_Woocommerce::get_data( 'integration_data' )['external_business_id'] ?? '';
if ( empty( $external_business_id ) ) {
Pinterest_For_Woocommerce::create_commerce_integration();
return true;
}

$integration = APIV5::get_commerce_integration( $external_business_id );
$data = self::prepare_commerce_integration_data( $integration['external_business_id'] );
if ( $integration['partner_metadata'] === $data['partner_metadata'] ) {
return true;
}

$response = APIV5::update_commerce_integration( $integration['external_business_id'], $data );
Pinterest_For_Woocommerce::save_integration_data( $response );
return true;
} catch ( PinterestApiException $e ) {
Logger::log(
$e->getMessage(),
WC_Log_Levels::ERROR,
'pinterest-for-woocommerce-commerce-integration-sync'
);
return false;
} catch ( Exception $e ) {
/*
* As thrown from create_commerce_integration call in case Advertiser ID is missing.
* Extremely unlikely at this stage.
*/
Logger::log(
$e->getMessage(),
WC_Log_Levels::ERROR,
'pinterest-for-woocommerce-commerce-integration-sync'
);
return false;
}
}

/**
* Prepares Commerce Integration Data.
*
* @param string $external_business_id Auto-generated if empty External Business ID to pass to Pinterest.
*
* @since x.x.x
* @return array
* @throws Exception In case of Advertiser ID is missing.
*/
public static function prepare_commerce_integration_data( string $external_business_id = '' ): array {
global $wp_version;

if ( empty( $external_business_id ) ) {
$external_business_id = self::generate_external_business_id();
}
$connection_data = Pinterest_For_Woocommerce::get_data( 'connection_info_data', true );

// It does not make any sense to create integration without Advertiser ID.
if ( empty( $connection_data['advertiser_id'] ) ) {
throw new Exception(
sprintf(
esc_html__(
'Commerce Integration cannot be created: Advertiser ID is missing.',
'pinterest-for-woocommerce'
)
)
);
}

$integration_data = array(
'external_business_id' => $external_business_id,
'connected_merchant_id' => $connection_data['merchant_id'] ?? '',
'connected_advertiser_id' => $connection_data['advertiser_id'],
'partner_metadata' => json_encode(
array(
'plugin_version' => PINTEREST_FOR_WOOCOMMERCE_VERSION,
'wc_version' => defined( 'WC_VERSION' ) ? WC_VERSION : 'unknown',
'wp_version' => $wp_version,
'locale' => get_locale(),
'currency' => get_woocommerce_currency(),
)
),
);

if ( ! empty( $connection_data['tag_id'] ) ) {
$integration_data['connected_tag_id'] = $connection_data['tag_id'];
}
/**
* Allows modifications to commerce integration data when creating or updating Pinterest Commerce Integration.
*
* @since x.x.x
*
* @param array $integration_data {
* An array of integration data as rquired by the Pinterest API endpoint documentation.
* @link https://developers.pinterest.com/docs/api/v5/integrations_commerce-post
*
* @type string $external_business_id - Woo's external business ID.
* @type string $connected_merchant_id - Connected merchant ID for the integration.
* @type string $connected_advertiser_id - Connected advertiser ID for the integration.
* @type string $connected_tag_id - Connected Pinterest Tag ID for the integration.
* @type string $partner_metadata - Partner metadata for the integration.
* }
*/
return apply_filters( 'pinterest_for_woocommerce_commerce_integration_data', $integration_data );
}

/**
* Used to generate external business id to pass it Pinterest when creating a connection between WC and Pinterest.
*
* @since x.x.x
*
* @return string
*/
public static function generate_external_business_id(): string {
$name = (string) parse_url( esc_url( get_site_url() ), PHP_URL_HOST );
if ( empty( $name ) ) {
$name = sanitize_title( get_bloginfo( 'name' ) );
}
$id = uniqid( sprintf( 'woo-%s-', $name ), false );

/**
* Filters the shop's external business id.
*
* This is passed to Pinterest when connecting.
* Should be non-empty and without special characters,
* otherwise the ID will be obtained from the site's name as fallback.
*
* @since 1.4.0
*
* @param string $id the shop's external business id.
*/
return (string) apply_filters( 'wc_pinterest_external_business_id', $id );
}
}
Loading

0 comments on commit 2bcf69b

Please sign in to comment.