Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions src/API/Google/Ads.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@
use Google\Ads\GoogleAds\V23\Enums\AccessRoleEnum\AccessRole;
use Google\Ads\GoogleAds\V23\Enums\ProductLinkInvitationStatusEnum\ProductLinkInvitationStatus;
use Google\Ads\GoogleAds\V23\Resources\ProductLinkInvitation;
use Google\Ads\GoogleAds\V23\Services\FetchIncentiveRequest;
use Google\Ads\GoogleAds\V23\Services\FetchIncentiveRequest\IncentiveType;
use Google\Ads\GoogleAds\V23\Services\Incentive;
use Google\Ads\GoogleAds\V23\Services\IncentiveOffer\OfferType;
use Google\Ads\GoogleAds\V23\Services\ListAccessibleCustomersRequest;
use Google\Ads\GoogleAds\V23\Services\UpdateProductLinkInvitationRequest;
use Google\ApiCore\ApiException;
use Google\ApiCore\ValidationException;
use Google\Type\Money;

defined( 'ABSPATH' ) || exit;

Expand Down Expand Up @@ -220,6 +225,123 @@ public function use_store_currency(): bool {
return $this->options->update( OptionsInterface::ADS_ACCOUNT_CURRENCY, get_woocommerce_currency() );
}

/**
* Fetch available incentive offers from the Google Ads API.
*
* @since 3.3.0
*
* @param string $country_code ISO 3166-1 alpha-2 country code.
* @param string $language_code ISO 639-1 language code.
*
* @return array Structured incentive offer data. Always returns a valid structure,
* falling back to an empty CYO_INCENTIVE response on API errors.
*/
public function fetch_incentives( string $country_code, string $language_code ): array {
$empty_response = [
'type' => OfferType::name( OfferType::CYO_INCENTIVE ),
'termsAndConditionsUrl' => '',
'incentives' => [],
];

try {
$request = new FetchIncentiveRequest();
$request->setCountryCode( $country_code );
$request->setLanguageCode( $language_code );

$response = $this->client->getIncentiveServiceClient()->fetchIncentive( $request );
$offer = $response->getIncentiveOffer();

if ( ! $offer || ! $offer->hasType() ) {
return $empty_response;
}

$result = [
'type' => OfferType::name( $offer->getType() ),
'termsAndConditionsUrl' => $offer->getConsolidatedTermsAndConditionsUrl(),
'incentives' => [],
];

if ( OfferType::CYO_INCENTIVE === $offer->getType() && $offer->hasCyoIncentives() ) {
$cyo = $offer->getCyoIncentives();

$offer_map = [
'low' => $cyo->getLowOffer(),
'medium' => $cyo->getMediumOffer(),
'high' => $cyo->getHighOffer(),
];

foreach ( $offer_map as $level => $incentive ) {
if ( $incentive ) {
$result['incentives'][] = $this->format_incentive( $incentive, $level );
}
}
}

return $result;
} catch ( ApiException $e ) {
do_action( 'woocommerce_gla_ads_client_exception', $e, __METHOD__ );

return $empty_response;
}
}

/**
* Format an Incentive protobuf message into an array for the REST response.
*
* @since 3.3.0
*
* @param Incentive $incentive The incentive object.
* @param string $level The offer level (low, medium, high).
*
* @return array
*/
protected function format_incentive( Incentive $incentive, string $level ): array {
$data = [
'id' => (string) $incentive->getIncentiveId(),
'type' => IncentiveType::name( $incentive->getType() ),
'offer' => $level,
'termsAndConditionsUrl' => $incentive->getIncentiveTermsAndConditionsUrl(),
'requirement' => [],
];

if ( $incentive->hasRequirement() ) {
$requirement = $incentive->getRequirement();

if ( $requirement->hasSpend() ) {
$spend = $requirement->getSpend();
$data['requirement']['spend'] = [
'awardAmount' => $this->format_money( $spend->getAwardAmount() ),
'requiredAmount' => $this->format_money( $spend->getRequiredAmount() ),
];
}
}

return $data;
}

/**
* Format a Money protobuf message into an array.
*
* @since 3.3.0
*
* @param Money|null $money The Money object.
*
* @return array
*/
protected function format_money( ?Money $money ): array {
if ( ! $money ) {
return [
'currencyCode' => '',
'units' => '0',
];
}

return [
'currencyCode' => $money->getCurrencyCode(),
'units' => (string) $money->getUnits(),
];
}

/**
* Convert ads ID from a resource name to an int.
*
Expand Down
184 changes: 184 additions & 0 deletions src/API/Site/Controllers/Ads/IncentivesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads;

use Automattic\WooCommerce\GoogleListingsAndAds\API\Google\Ads;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
use WP_REST_Request as Request;

defined( 'ABSPATH' ) || exit;

/**
* Class IncentivesController
*
* Handles fetching Choose-Your-Own (CYO) incentive offers from the Google Ads API.
*
* @since 3.3.0
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads
*/
class IncentivesController extends BaseController {

/**
* @var Ads
*/
protected $ads;

/**
* @var WC
*/
protected $wc;

/**
* IncentivesController constructor.
*
* @param RESTServer $rest_server
* @param Ads $ads
* @param WC $wc
*/
public function __construct( RESTServer $rest_server, Ads $ads, WC $wc ) {
parent::__construct( $rest_server );
$this->ads = $ads;
$this->wc = $wc;
}

/**
* Register rest routes with WordPress.
*/
public function register_routes(): void {
$this->register_route(
'ads/incentives',
[
[
'methods' => TransportMethods::READABLE,
'callback' => $this->get_incentives_callback(),
'permission_callback' => $this->get_permission_callback(),
],
'schema' => $this->get_api_response_schema_callback(),
]
);
}

/**
* @return callable
*/
protected function get_incentives_callback(): callable {
return function ( Request $request ) {
$country_code = $this->wc->get_base_country();
$language_code = $this->get_language_code();

$incentives = $this->ads->fetch_incentives( $country_code, $language_code );

return $this->prepare_item_for_response( $incentives, $request );
};
}

/**
* Get the ISO 639-1 language code from the WordPress locale.
*
* @return string
*/
protected function get_language_code(): string {
$locale = get_locale();

if ( empty( $locale ) ) {
return 'en';
}

return strtolower( substr( $locale, 0, 2 ) );
}

/**
* Get the item schema properties for the controller.
*
* @return array
*/
protected function get_schema_properties(): array {
return [
'type' => [
'type' => 'string',
'description' => __( 'The offer type.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'termsAndConditionsUrl' => [
'type' => 'string',
'description' => __( 'The consolidated terms and conditions URL.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
],
'incentives' => [
'type' => 'array',
'description' => __( 'The available incentive offers.', 'google-listings-and-ads' ),
'context' => [ 'view' ],
'items' => [
'type' => 'object',
'properties' => [
'id' => [
'type' => 'string',
'description' => __( 'The incentive ID.', 'google-listings-and-ads' ),
],
'type' => [
'type' => 'string',
'description' => __( 'The incentive type.', 'google-listings-and-ads' ),
],
'offer' => [
'type' => 'string',
'enum' => [ 'low', 'medium', 'high' ],
'description' => __( 'The offer level.', 'google-listings-and-ads' ),
],
'termsAndConditionsUrl' => [
'type' => 'string',
'description' => __( 'The terms and conditions URL for this incentive.', 'google-listings-and-ads' ),
],
'requirement' => [
'type' => 'object',
'properties' => [
'spend' => [
'type' => 'object',
'properties' => [
'awardAmount' => [
'type' => 'object',
'properties' => [
'currencyCode' => [
'type' => 'string',
],
'units' => [
'type' => 'string',
],
],
],
'requiredAmount' => [
'type' => 'object',
'properties' => [
'currencyCode' => [
'type' => 'string',
],
'units' => [
'type' => 'string',
],
],
],
],
],
],
],
],
],
],
];
}

/**
* Get the item schema name for the controller.
*
* Used for building the API response schema.
*
* @return string
*/
protected function get_schema_title(): string {
return 'incentives';
}
}
8 changes: 8 additions & 0 deletions src/Google/Ads/ServiceClientFactoryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Google\Ads\GoogleAds\V23\Services\Client\AssetGroupListingGroupFilterServiceClient;
use Google\Ads\GoogleAds\V23\Services\Client\AssetGroupServiceClient;
use Google\Ads\GoogleAds\V23\Services\Client\BillingSetupServiceClient;
use Google\Ads\GoogleAds\V23\Services\Client\IncentiveServiceClient;
use Google\Ads\GoogleAds\V23\Services\Client\CampaignBudgetServiceClient;
use Google\Ads\GoogleAds\V23\Services\Client\CampaignCriterionServiceClient;
use Google\Ads\GoogleAds\V23\Services\Client\CampaignServiceClient;
Expand Down Expand Up @@ -197,6 +198,13 @@ public function getGoogleAdsServiceClient(): GoogleAdsServiceClient {
return new GoogleAdsServiceClient( $this->getGoogleAdsClientOptions() );
}

/**
* @return IncentiveServiceClient
*/
public function getIncentiveServiceClient(): IncentiveServiceClient {
return new IncentiveServiceClient( $this->getGoogleAdsClientOptions() );
}

/**
* @return ProductLinkInvitationServiceClient
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Internal/DependencyManagement/RESTServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\BudgetRecommendationController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\CampaignController as AdsCampaignController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\IncentiveCreditsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\IncentivesController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\ReportsController as AdsReportsController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\SetupCompleteController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\AssetGroupController as AdsAssetGroupController;
Expand Down Expand Up @@ -128,6 +129,7 @@ public function register(): void {
$this->share( BudgetRecommendationController::class, Ads::class );
$this->share( GoogleAccountController::class, Connection::class );
$this->share( IncentiveCreditsController::class );
$this->share( IncentivesController::class, Ads::class, WC::class );
$this->share( JetpackAccountController::class, Manager::class, Middleware::class );
$this->share( MerchantCenterProductStatsController::class, MerchantStatuses::class, ProductSyncStats::class );
$this->share( MerchantCenterIssuesController::class, MerchantStatuses::class, ProductHelper::class );
Expand Down
Loading
Loading