Add Affiliate Location Extensions

Java

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.googleads.examples.extensions;

import static com.google.ads.googleads.examples.utils.CodeSampleHelper.getPrintableDateTime;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v15.common.MatchingFunction;
import com.google.ads.googleads.v15.enums.AffiliateLocationFeedRelationshipTypeEnum.AffiliateLocationFeedRelationshipType;
import com.google.ads.googleads.v15.enums.AffiliateLocationPlaceholderFieldEnum.AffiliateLocationPlaceholderField;
import com.google.ads.googleads.v15.enums.FeedOriginEnum.FeedOrigin;
import com.google.ads.googleads.v15.enums.PlaceholderTypeEnum.PlaceholderType;
import com.google.ads.googleads.v15.errors.GoogleAdsError;
import com.google.ads.googleads.v15.errors.GoogleAdsException;
import com.google.ads.googleads.v15.resources.AttributeFieldMapping;
import com.google.ads.googleads.v15.resources.CampaignFeed;
import com.google.ads.googleads.v15.resources.CustomerFeed;
import com.google.ads.googleads.v15.resources.Feed;
import com.google.ads.googleads.v15.resources.Feed.AffiliateLocationFeedData;
import com.google.ads.googleads.v15.resources.FeedMapping;
import com.google.ads.googleads.v15.services.CampaignFeedOperation;
import com.google.ads.googleads.v15.services.CampaignFeedServiceClient;
import com.google.ads.googleads.v15.resources.FeedName;
import com.google.ads.googleads.v15.services.FeedOperation;
import com.google.ads.googleads.v15.services.FeedServiceClient;
import com.google.ads.googleads.v15.services.GoogleAdsRow;
import com.google.ads.googleads.v15.services.GoogleAdsServiceClient;
import com.google.ads.googleads.v15.services.GoogleAdsServiceClient.SearchPagedResponse;
import com.google.ads.googleads.v15.services.MutateCampaignFeedsResponse;
import com.google.ads.googleads.v15.services.MutateFeedsResponse;
import com.google.ads.googleads.v15.utils.ResourceNames;
import com.google.api.gax.grpc.GrpcStatusCode;
import com.google.api.gax.rpc.ApiException;
import com.google.common.collect.ImmutableList;
import io.grpc.Status.Code;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Adds a feed that syncs retail addresses for a given retail chain ID and associates the feed with
 * a campaign for serving affiliate location extensions.
 */
public class AddAffiliateLocationExtensions {
  private static final int MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10;

  private static class AddAffiliateLocationExtensionsParams extends CodeSampleParams {

    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private long customerId;

    @Parameter(names = ArgumentNames.CAMPAIGN_ID, required = true)
    private long campaignId;

    @Parameter(
        names = ArgumentNames.CHAIN_ID,
        description =
            "The retail chain ID. See"
                + " https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids"
                + " a complete list of valid retail chain IDs",
        required = true)
    private long chainId;
  }

  public static void main(String[] args) throws IOException {
    AddAffiliateLocationExtensionsParams params = new AddAffiliateLocationExtensionsParams();
    if (!params.parseArguments(args)) {

      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
      params.campaignId = Long.parseLong("INSERT_CAMPAIGN_ID_HERE");
      params.chainId = Long.parseLong("INSERT_CHAIN_ID_HERE");
    }

    GoogleAdsClient googleAdsClient = null;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      System.exit(1);
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      System.exit(1);
    }

    try {
      new AddAffiliateLocationExtensions()
          .runExample(googleAdsClient, params.customerId, params.campaignId, params.chainId);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      gae.printStackTrace();
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
    }
  }

  /** Runs the example. */
  private void runExample(
      GoogleAdsClient googleAdsClient, long customerId, long campaignId, long chainId) {
    String feedResourceName =
        createAffiliateLocationExtensionFeed(googleAdsClient, customerId, chainId);
    // The feed created above will not be available for use in a campaign feed until the associated
    // feed mappings have been created by Google.
    FeedMapping feedMapping = waitForFeedToBeReady(googleAdsClient, customerId, feedResourceName);
    // Creates the campaign feed now that the feed mappings are ready.
    createCampaignFeed(
        googleAdsClient, customerId, campaignId, feedMapping, feedResourceName, chainId);
  }

  /** Creates an affiliate location extension feed. */
  private String createAffiliateLocationExtensionFeed(
      GoogleAdsClient googleAdsClient, long customerId, long chainId) {
    // Removes all existing location extension feeds. This is an optional step, but is required for
    // this code example to run correctly more than once. This is because:
    //   1. Google Ads only allows one location extension feed per email address.
    //   2. A Google Ads account cannot have a location extension feed and an affiliate
    // location extension feed at the same time.
    removeLocationExtensionFeeds(googleAdsClient, customerId);

    // Creates a feed that will sync to retail addresses for a given retail chain ID.
    // Do not add FeedAttributes to this object as Google Ads will add
    // them automatically because this will be a system generated feed.
    Feed feed =
        Feed.newBuilder()
            .setName("Affiliate Location Extension feed #" + getPrintableDateTime())
            .setAffiliateLocationFeedData(
                AffiliateLocationFeedData.newBuilder()
                    .addChainIds(chainId)
                    .setRelationshipType(AffiliateLocationFeedRelationshipType.GENERAL_RETAILER))
            // Since this feed's contents will be managed by Google,
            // you must set its origin to GOOGLE.
            .setOrigin(FeedOrigin.GOOGLE)
            .build();

    // Constructs an operation to create the feed.
    FeedOperation op = FeedOperation.newBuilder().setCreate(feed).build();

    // Creates the FeedServiceClient.
    try (FeedServiceClient feedService =
        googleAdsClient.getLatestVersion().createFeedServiceClient()) {
      // Issues a mutate request to add the feed.
      MutateFeedsResponse response =
          feedService.mutateFeeds(String.valueOf(customerId), ImmutableList.of(op));

      // Displays the results.
      String resourceName = response.getResults(0).getResourceName();
      System.out.printf(
          "Affliate location extension feed created with resource name: %s.%n", resourceName);

      return resourceName;
    }
  }

  /** Removes all location extension feeds. */
  private void removeLocationExtensionFeeds(GoogleAdsClient googleAdsClient, long customerId) {
    // Removes a location extension feed. Does this with the following steps:
    //   1. Retrieves the existing CustomerFeed links.
    //   2. Removes the CustomerFeed so that the location extensions from the feed stop serving.
    //   3. Removes the feed so that Google Ads will no longer sync from the Business Profile
    // account.
    List<CustomerFeed> oldCustomerFeeds =
        getLocationExtensionCustomerFeeds(googleAdsClient, customerId);
    if (oldCustomerFeeds.isEmpty()) {
      // Optional: You may also want to remove the CampaignFeeds and AdGroupFeeds.
      removeCustomerFeeds(googleAdsClient, customerId, oldCustomerFeeds);
    }
    List<Feed> feeds = getLocationExtensionFeeds(googleAdsClient, customerId);

    if (!feeds.isEmpty()) {
      removeFeeds(googleAdsClient, customerId, feeds);
    }
  }

  /** Creates a CampaignFeed. This links a preexisting {@link Feed} to a campaign. */
  private void createCampaignFeed(
      GoogleAdsClient googleAdsClient,
      long customerId,
      long campaignId,
      FeedMapping feedMapping,
      String feedResourceName,
      long chainId) {
    // Gets the attribute ID that is used for the chain ID. This will be used to filter the feed to
    // extract affiliate locations.
    long attributeIdForChainId = getAttributeIdForChainId(feedMapping);

    // Extracts the feed ID.
    long feedId = Long.valueOf(FeedName.parse(feedResourceName).getFeedId());

    // Constructs a matching function string which filters the Feed to extract affiliate locations.
    String matchingFunction =
        String.format("IN(FeedAttribute[%d, %d], %d)", feedId, attributeIdForChainId, chainId);

    // Creates a CampaignFeed object.
    CampaignFeed campaignFeed =
        CampaignFeed.newBuilder()
            .setFeed(feedResourceName)
            .addPlaceholderTypes(PlaceholderType.AFFILIATE_LOCATION)
            .setMatchingFunction(
                MatchingFunction.newBuilder().setFunctionString(matchingFunction).build())
            .setCampaign(ResourceNames.campaign(customerId, campaignId))
            .build();

    // Creates an operation to create the CampaignFeed.
    CampaignFeedOperation operation =
        CampaignFeedOperation.newBuilder().setCreate(campaignFeed).build();

    // Adds a CampaignFeed that associates the feed with this campaign for
    // the AFFILIATE_LOCATION placeholder type.
    try (CampaignFeedServiceClient feedServiceClient =
        googleAdsClient.getLatestVersion().createCampaignFeedServiceClient()) {
      MutateCampaignFeedsResponse response =
          feedServiceClient.mutateCampaignFeeds(
              String.valueOf(customerId), ImmutableList.of(operation));
      System.out.printf(
          "Campaign feed created with resource name: %s.%n",
          response.getResultsList().get(0).getResourceName());
    }
  }

  /** Gets the feed item attribute ID that specifies the chain ID. */
  private long getAttributeIdForChainId(FeedMapping feedMapping) {
    Optional<AttributeFieldMapping> fieldMapping =
        feedMapping.getAttributeFieldMappingsList().stream()
            .filter(
                m -> m.getAffiliateLocationField() == AffiliateLocationPlaceholderField.CHAIN_ID)
            .findFirst();
    if (!fieldMapping.isPresent()) {
      throw new RuntimeException("Affiliate location field mapping isn't setup correctly");
    }
    return fieldMapping.get().getFeedAttributeId();
  }

  /** Removes a list of feeds from the customerID. */
  private void removeFeeds(GoogleAdsClient googleAdsClient, long customerId, List<Feed> feeds) {
    List<FeedOperation> removeOperations =
        feeds.stream()
            .map(f -> FeedOperation.newBuilder().setRemove(f.getResourceName()).build())
            .collect(Collectors.toList());
    try (FeedServiceClient feedServiceClient =
        googleAdsClient.getLatestVersion().createFeedServiceClient()) {
      feedServiceClient.mutateFeeds(String.valueOf(customerId), removeOperations);
    }
  }

  /** Retrieves all location extension feeds from a specified customer ID. */
  private List<Feed> getLocationExtensionFeeds(GoogleAdsClient googleAdsClient, long customerId) {
    String query =
        "SELECT"
            + "  customer_feed.resource_name, "
            + "  customer_feed.feed, "
            + "  customer_feed.status, "
            + "  customer_feed.matching_function.function_string "
            + "FROM"
            + "  customer_feed "
            + "WHERE"
            + "  customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION)"
            + "  AND customer_feed.status = ENABLED";

    try (GoogleAdsServiceClient client =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      SearchPagedResponse response = client.search(String.valueOf(customerId), query);

      List<Feed> results = new ArrayList();
      response.iterateAll().forEach(row -> results.add(row.getFeed()));
      return results;
    }
  }

  /** Removes all feeds linked at the customer (global) level. */
  private void removeCustomerFeeds(
      GoogleAdsClient googleAdsClient, long customerId, List<CustomerFeed> oldCustomerFeeds) {
    List<FeedOperation> operations =
        oldCustomerFeeds.stream()
            .map(f -> FeedOperation.newBuilder().setRemove(f.getResourceName()).build())
            .collect(Collectors.toList());
    if (!operations.isEmpty()) {
      try (FeedServiceClient feedServiceClient =
          googleAdsClient.getLatestVersion().createFeedServiceClient()) {
        feedServiceClient.mutateFeeds(String.valueOf(customerId), operations);
      }
    }
  }

  /** Retrieves the location extension feeds linked to a customer ID. */
  private List<CustomerFeed> getLocationExtensionCustomerFeeds(
      GoogleAdsClient googleAdsClient, long customerId) {
    String query =
        "SELECT"
            + "  customer_feed.resource_name, "
            + "  customer_feed.feed, "
            + "  customer_feed.status, "
            + "  customer_feed.matching_function.function_string "
            + "FROM"
            + "  customer_feed "
            + "WHERE"
            + "  customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION) "
            + "  AND customer_feed.status = ENABLED";
    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      SearchPagedResponse response =
          googleAdsServiceClient.search(String.valueOf(customerId), query);
      List<CustomerFeed> results = new ArrayList();
      response.iterateAll().forEach(row -> results.add(row.getCustomerFeed()));
      return results;
    }
  }

  /** Waits for a feed (identified by it's feedResourceName) to become ready. */
  private FeedMapping waitForFeedToBeReady(
      GoogleAdsClient googleAdsServiceClient, long customerId, String feedResourceName) {
    int numAttempts = 0;
    int sleepSeconds = 0;

    // Waits for Google's servers to setup the feed with feed attributes and attribute mapping.
    // This process is asynchronous, so we wait until the feed mapping is created, with exponential
    // backoff.
    while (numAttempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS) {
      Optional<FeedMapping> feedMapping =
          getFeedMapping(googleAdsServiceClient, customerId, feedResourceName);

      if (feedMapping.isPresent()) {
        System.out.printf("Feed with resource name '%s' is now ready.%n", feedResourceName);
        return feedMapping.get();
      } else {
        numAttempts++;
        sleepSeconds = (int) (5 * Math.pow(2, numAttempts));
        System.out.printf(
            "Checked: %d time(s). Feed is not ready "
                + "yet. Waiting %d seconds before trying again.%n",
            numAttempts, sleepSeconds);
        try {
          Thread.sleep(sleepSeconds * 1000);
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          throw new RuntimeException(e);
        }
      }
    }
    throw new ApiException(
        String.format("Feed is not ready after %d retries.", numAttempts),
        null,
        GrpcStatusCode.of(Code.DEADLINE_EXCEEDED),
        true);
  }

  /** Retrieves the {@link FeedMapping} for a given feed. */
  private Optional<FeedMapping> getFeedMapping(
      GoogleAdsClient googleAdsServiceClient, long customerId, String feedResourceName) {
    String query =
        String.format(
            "SELECT"
                + "  feed_mapping.resource_name, "
                + "  feed_mapping.attribute_field_mappings, "
                + "  feed_mapping.status "
                + "FROM"
                + "  feed_mapping "
                + "WHERE feed_mapping.feed = '%s'"
                + "  AND feed_mapping.status = ENABLED "
                + "  AND feed_mapping.placeholder_type = AFFILIATE_LOCATION "
                + "LIMIT 1",
            feedResourceName);
    try (GoogleAdsServiceClient client =
        googleAdsServiceClient.getLatestVersion().createGoogleAdsServiceClient()) {
      SearchPagedResponse response = client.search(String.valueOf(customerId), query);
      Iterator<GoogleAdsRow> iterator = response.iterateAll().iterator();
      if (iterator.hasNext()) {
        return Optional.of(iterator.next().getFeedMapping());
      }
      return Optional.empty();
    }
  }
}

      

C#

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using CommandLine;
using Google.Ads.Gax.Examples;
using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V15.Common;
using Google.Ads.GoogleAds.V15.Errors;
using Google.Ads.GoogleAds.V15.Resources;
using Google.Ads.GoogleAds.V15.Services;
using Google.Api.Gax;
using Grpc.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using static Google.Ads.GoogleAds.V15.Enums.AffiliateLocationFeedRelationshipTypeEnum.Types;
using static Google.Ads.GoogleAds.V15.Enums.AffiliateLocationPlaceholderFieldEnum.Types;
using static Google.Ads.GoogleAds.V15.Enums.FeedOriginEnum.Types;
using static Google.Ads.GoogleAds.V15.Enums.PlaceholderTypeEnum.Types;
using static Google.Ads.GoogleAds.V15.Resources.Feed.Types;

namespace Google.Ads.GoogleAds.Examples.V15
{
    /// <summary>
    /// This code example adds a feed that syncs retail addresses for a given retail chain ID and
    /// associates the feed with a campaign for serving affiliate location extensions.
    /// </summary>
    public class AddAffiliateLocationExtensions : ExampleBase
    {
        /// <summary>
        /// Command line options for running the <see cref="AddAffiliateLocationExtensions"/>
        /// example.
        /// </summary>
        public class Options : OptionsBase
        {
            /// <summary>
            /// The customer ID for which the call is made.
            /// </summary>
            [Option("customerId", Required = true, HelpText =
                "The customer ID for which the call is made.")]
            public long CustomerId { get; set; }

            /// <summary>
            /// The retail chain ID.
            /// </summary>
            [Option("chainId", Required = true, HelpText =
                "The retail chain ID.")]
            public long ChainId { get; set; }

            /// <summary>
            /// The campaign ID for which the affiliate location extensions are added.
            /// </summary>
            [Option("campaignId", Required = true, HelpText =
                "The campaign ID for which the affiliate location extensions are added.")]
            public long CampaignId { get; set; }
        }

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            Options options = ExampleUtilities.ParseCommandLine<Options>(args);

            AddAffiliateLocationExtensions codeExample = new AddAffiliateLocationExtensions();
            Console.WriteLine(codeExample.Description);
            codeExample.Run(new GoogleAdsClient(), options.CustomerId, options.ChainId, options.CampaignId);
        }

        // The maximum number of attempts to make to retrieve the FeedMappings before throwing an
        // exception.
        private const int MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10;

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This code example adds a feed that syncs retail addresses for a given retail chain " +
            "ID and associates the feed with a campaign for serving affiliate location extensions.";

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="chainId">The retail chain ID.</param>
        /// <param name="campaignId">
        /// The campaign ID for which the affiliate location extensions are added.
        /// </param>
        public void Run(GoogleAdsClient client, long customerId, long chainId, long campaignId)
        {
            try
            {
                string feedResourceName = CreateAffiliateLocationExtensionFeed(
                    client, customerId, chainId);
                // After the completion of the feed creation operation above the added feed will not
                // be available for usage in a campaign feed until the feed mappings are created. We
                // will wait with an exponential back-off policy until the feed mappings have been
                // created.
                FeedMapping feedMapping = WaitForFeedToBeReady(client, customerId,
                    feedResourceName);
                CreateCampaignFeed(client, customerId, campaignId, feedMapping,
                    feedResourceName, chainId);
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }
        }

        /// <summary>
        /// Creates the Affiliate Location Extension feed.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="chainId">The retail chain ID.</param>
        /// <returns>Resource name of the newly created Affiliate Location Extension feed.</returns>
        private static string CreateAffiliateLocationExtensionFeed(GoogleAdsClient client,
            long customerId, long chainId)
        {
            // Optional: Delete all existing location extension feeds. This is an optional step, and
            // is required for this code example to run correctly more than once.
            // 1. Google Ads only allows one location extension feed per email address.
            // 2. A Google Ads account cannot have a location extension feed and an affiliate
            // location extension feed at the same time.
            DeleteLocationExtensionFeeds(client, customerId);

            // Get the FeedServiceClient.
            FeedServiceClient feedService = client.GetService(Services.V15.FeedService);

            // Creates a feed that will sync to retail addresses for a given retail chain ID. Do not
            // add FeedAttributes to this object as Google Ads will add them automatically because
            // this will be a system generated feed.
            Feed feed = new Feed()
            {
                Name = "Affiliate Location Extension feed #" + ExampleUtilities.GetRandomString(),

                AffiliateLocationFeedData = new AffiliateLocationFeedData()
                {
                    ChainIds = { chainId },
                    RelationshipType = AffiliateLocationFeedRelationshipType.GeneralRetailer
                },
                // Since this feed's contents will be managed by Google, you must set its origin to
                // GOOGLE.
                Origin = FeedOrigin.Google
            };

            FeedOperation operation = new FeedOperation()
            {
                Create = feed
            };

            // Adds the feed.
            MutateFeedsResponse response =
                feedService.MutateFeeds(customerId.ToString(), new[] { operation });

            // Displays the results.
            string feedResourceName = response.Results[0].ResourceName;
            Console.WriteLine($"Affliate location extension feed created with resource name: " +
                $"{feedResourceName}.");
            return feedResourceName;
        }

        /// <summary>
        /// Deletes the old location extension feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        private static void DeleteLocationExtensionFeeds(GoogleAdsClient client, long customerId)
        {
            // To delete a location extension feed, you need to
            // 1. Delete the CustomerFeed so that the location extensions from the feed stop
            // serving.
            // 2. Delete the feed so that Google Ads will no longer sync from the Business Profile
            // account.
            CustomerFeed[] oldCustomerFeeds =
                GetLocationExtensionCustomerFeeds(client, customerId);
            if (oldCustomerFeeds.Length != 0)
            {
                // Optional: You may also want to delete the CampaignFeeds and AdGroupFeeds.
                DeleteCustomerFeeds(client, customerId, oldCustomerFeeds);
            }
            Feed[] feeds = GetLocationExtensionFeeds(client, customerId);

            if (feeds.Length != 0)
            {
                RemoveFeeds(client, customerId, feeds);
            }
        }

        /// <summary>
        /// Gets the location extension feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <returns>The list of location extension feeds.</returns>
        private static Feed[] GetLocationExtensionFeeds(GoogleAdsClient client, long customerId)
        {
            List<Feed> feeds = new List<Feed>();
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V15.GoogleAdsService);

            // Create the query.
            string query = $"SELECT feed.resource_name, feed.status, " +
                $"feed.places_location_feed_data.email_address, " +
                $"feed.affiliate_location_feed_data.chain_ids " +
                $" from feed where feed.status = ENABLED";

            PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
                googleAdsService.Search(customerId.ToString(), query);

            foreach (GoogleAdsRow row in result)
            {
                // A location extension feed can be identified by checking whether the
                // PlacesLocationFeedData field is set (Location extensions feeds) or
                // AffiliateLocationFeedData field is set (Affiliate location extension feeds)
                Feed feed = row.Feed;
                if (feed.PlacesLocationFeedData != null || feed.AffiliateLocationFeedData != null)
                {
                    feeds.Add(feed);
                }
            }
            return feeds.ToArray();
        }

        /// <summary>
        /// Removes the feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="feeds">The list of feeds to remove.</param>
        private static void RemoveFeeds(GoogleAdsClient client, long customerId, Feed[] feeds)
        {
            List<FeedOperation> operations = new List<FeedOperation>();
            foreach (Feed feed in feeds)
            {
                FeedOperation operation = new FeedOperation()
                {
                    Remove = feed.ResourceName,
                };
                operations.Add(operation);
            }
            FeedServiceClient feedService = client.GetService(
                Services.V15.FeedService);

            feedService.MutateFeeds(customerId.ToString(), operations.ToArray());
        }

        /// <summary>
        /// Gets the location extension customer feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <returns>The list of location extension customer feeds.</returns>
        private static CustomerFeed[] GetLocationExtensionCustomerFeeds(GoogleAdsClient client,
            long customerId)
        {
            List<CustomerFeed> customerFeeds = new List<CustomerFeed>();
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V15.GoogleAdsService);

            // Create the query. A location extension customer feed can be identified by filtering
            // for placeholder_types=LOCATION (location extension feeds) or placeholder_types
            // =AFFILIATE_LOCATION (affiliate location extension feeds)
            string query = $"SELECT customer_feed.resource_name, customer_feed.feed, " +
                $"customer_feed.status, customer_feed.matching_function.function_string from " +
                $"customer_feed " +
                $"WHERE customer_feed.placeholder_types CONTAINS " +
                $"ANY(LOCATION, AFFILIATE_LOCATION) and customer_feed.status=ENABLED";

            PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
                googleAdsService.Search(customerId.ToString(), query);

            foreach (GoogleAdsRow row in result)
            {
                customerFeeds.Add(row.CustomerFeed);
            }
            return customerFeeds.ToArray();
        }

        /// <summary>
        /// Deletes the customer feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="customerFeeds">The customer feeds to delete.</param>
        private static void DeleteCustomerFeeds(GoogleAdsClient client, long customerId,
            CustomerFeed[] customerFeeds)
        {
            List<CustomerFeedOperation> operations = new List<CustomerFeedOperation>();
            foreach (CustomerFeed customerFeed in customerFeeds)
            {
                CustomerFeedOperation operation = new CustomerFeedOperation()
                {
                    Remove = customerFeed.ResourceName,
                };
                operations.Add(operation);
            }

            CustomerFeedServiceClient feedService = client.GetService(
                Services.V15.CustomerFeedService);

            feedService.MutateCustomerFeeds(customerId.ToString(), operations.ToArray());
        }

        /// <summary>
        /// Gets the Affiliate Location Extension feed mapping.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="feedResourceName">The feed resource name.</param>
        /// <returns>The newly created feed mapping.</returns>
        private static FeedMapping GetAffiliateLocationExtensionFeedMapping(GoogleAdsClient client,
            long customerId, string feedResourceName)
        {
            // Get the GoogleAdsService.
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V15.GoogleAdsService);

            // Create the query.
            string query = $"SELECT feed_mapping.resource_name, " +
                $"feed_mapping.attribute_field_mappings, feed_mapping.status FROM " +
                $"feed_mapping WHERE feed_mapping.feed = '{feedResourceName}' and " +
                $"feed_mapping.status = ENABLED and feed_mapping.placeholder_type = " +
                "AFFILIATE_LOCATION LIMIT 1";

            // Issue a search request.
            PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
                googleAdsService.Search(customerId.ToString(), query);

            // Display the results.
            GoogleAdsRow googleAdsRow = result.FirstOrDefault();
            return (googleAdsRow == null) ? null : googleAdsRow.FeedMapping;
        }

        /// <summary>
        /// Waits for the Affliate location extension feed to be ready.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="feedResourceName">Resource name of the feed.</param>
        private static FeedMapping WaitForFeedToBeReady(GoogleAdsClient client, long customerId,
            string feedResourceName)
        {
            int numAttempts = 0;
            int sleepSeconds = 0;

            while (numAttempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS)
            {
                // Once you create a feed, Google's servers will setup the feed by creating feed
                // attributes and feed mapping. Once the feed mapping is created, it is ready to be
                // used for creating customer feed. This process is asynchronous, so we wait until
                // the feed mapping is created, peforming exponential backoff.
                FeedMapping feedMapping = GetAffiliateLocationExtensionFeedMapping(
                    client, customerId, feedResourceName);

                if (feedMapping == null)
                {
                    numAttempts++;
                    sleepSeconds = (int) (5 * Math.Pow(2, numAttempts));
                    Console.WriteLine($"Checked: #{numAttempts} time(s). Feed is not ready " +
                        $"yet. Waiting {sleepSeconds} seconds before trying again.");
                    Thread.Sleep(sleepSeconds * 1000);
                }
                else
                {
                    Console.WriteLine($"Feed {feedResourceName} is now ready.");
                    return feedMapping;
                }
            }
            throw new RpcException(new Status(StatusCode.DeadlineExceeded,
                $"Feed is not ready after {MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS}" +
                $" retries."));
        }

        /// <summary>
        /// Creates the campaign feed.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="campaignId">
        /// The campaign ID for which the affiliate location extensions are added.
        /// </param>
        /// <param name="feedMapping">
        /// The affliate location extension feedmapping for <paramref name="feedResourceName"/>
        /// </param>
        /// <param name="feedResourceName">The feed resource name.</param>
        /// <param name="chainId">The retail chain ID.</param>
        private static void CreateCampaignFeed(GoogleAdsClient client, long customerId,
            long campaignId, FeedMapping feedMapping, string feedResourceName, long chainId)
        {
            // Get the CampaignFeedService.
            CampaignFeedServiceClient campaignFeedService = client.GetService(
                Services.V15.CampaignFeedService);

            long attributeIdForChainId = GetAttributeIdForChainId(feedMapping);
            string feedId = FeedName.Parse(feedResourceName).FeedId;

            string matchingFunction =
                $"IN(FeedAttribute[{feedId}, {attributeIdForChainId}], {chainId})";
            // Adds a CampaignFeed that associates the feed with this campaign for the
            // AFFILIATE_LOCATION placeholder type.
            CampaignFeed campaignFeed = new CampaignFeed()
            {
                Feed = feedResourceName,
                PlaceholderTypes = { PlaceholderType.AffiliateLocation },
                MatchingFunction = new MatchingFunction()
                {
                    FunctionString = matchingFunction
                },
                Campaign = ResourceNames.Campaign(customerId, campaignId),
            };

            CampaignFeedOperation operation = new CampaignFeedOperation()
            {
                Create = campaignFeed
            };

            MutateCampaignFeedsResponse campaignFeedsResponse =
                campaignFeedService.MutateCampaignFeeds(
                    customerId.ToString(), new[] { operation });

            // Displays the result.
            string addedCampaignFeed = campaignFeedsResponse.Results[0].ResourceName;
            Console.WriteLine($"Campaign feed created with resource name: {addedCampaignFeed}.");
            return;
        }

        /// <summary>
        /// Gets the feed attribute ID for the retail chain Id.
        /// </summary>
        /// <param name="feedMapping">The feed mapping.</param>
        /// <returns>The feeed attribute ID.</returns>
        /// <exception cref="ArgumentException">
        /// Affiliate location feed mapping isn't setup correctly.
        /// </exception>
        public static long GetAttributeIdForChainId(FeedMapping feedMapping)
        {
            foreach (AttributeFieldMapping fieldMapping in feedMapping.AttributeFieldMappings)
            {
                if (fieldMapping.AffiliateLocationField ==
                    AffiliateLocationPlaceholderField.ChainId)
                {
                    return fieldMapping.FeedAttributeId;
                }
            }
            throw new ArgumentException("Affiliate location feed mapping isn't setup correctly.");
        }
    }
}

      

PHP

<?php

/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Google\Ads\GoogleAds\Examples\Extensions;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Examples\Utils\Helper;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V15\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V15\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V15\GoogleAdsException;
use Google\Ads\GoogleAds\Lib\V15\GoogleAdsServerStreamDecorator;
use Google\Ads\GoogleAds\Util\V15\ResourceNames;
use Google\Ads\GoogleAds\V15\Common\MatchingFunction;
use Google\Ads\GoogleAds\V15\Enums\AffiliateLocationFeedRelationshipTypeEnum\AffiliateLocationFeedRelationshipType;
use Google\Ads\GoogleAds\V15\Enums\AffiliateLocationPlaceholderFieldEnum\AffiliateLocationPlaceholderField;
use Google\Ads\GoogleAds\V15\Enums\FeedOriginEnum\FeedOrigin;
use Google\Ads\GoogleAds\V15\Enums\PlaceholderTypeEnum\PlaceholderType;
use Google\Ads\GoogleAds\V15\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V15\Resources\AttributeFieldMapping;
use Google\Ads\GoogleAds\V15\Resources\CampaignFeed;
use Google\Ads\GoogleAds\V15\Resources\CustomerFeed;
use Google\Ads\GoogleAds\V15\Resources\Feed;
use Google\Ads\GoogleAds\V15\Resources\Feed\AffiliateLocationFeedData;
use Google\Ads\GoogleAds\V15\Resources\FeedMapping;
use Google\Ads\GoogleAds\V15\Services\CampaignFeedOperation;
use Google\Ads\GoogleAds\V15\Services\CustomerFeedOperation;
use Google\Ads\GoogleAds\V15\Services\FeedOperation;
use Google\Ads\GoogleAds\V15\Services\FeedServiceClient;
use Google\Ads\GoogleAds\V15\Services\GoogleAdsRow;
use Google\Ads\GoogleAds\V15\Services\MutateCampaignFeedsRequest;
use Google\Ads\GoogleAds\V15\Services\MutateCustomerFeedsRequest;
use Google\Ads\GoogleAds\V15\Services\MutateFeedsRequest;
use Google\Ads\GoogleAds\V15\Services\SearchGoogleAdsRequest;
use Google\Ads\GoogleAds\V15\Services\SearchGoogleAdsStreamRequest;
use Google\ApiCore\ApiException;
use RuntimeException;

/**
 * This code example adds a feed that syncs retail addresses for a given retail chain ID and
 * associates the feed with a campaign for serving affiliate location extensions.
 */
class AddAffiliateLocationExtensions
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    // The retail chain ID. For a complete list of valid retail chain IDs, see
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#chain-ids.
    private const CHAIN_ID = 'INSERT_CHAIN_ID_HERE';
    // The campaign ID for which the affiliate location extensions are added.
    private const CAMPAIGN_ID = 'INSERT_CAMPAIGN_ID_HERE';
    // Optional: Delete all existing location extension feeds. This is required for this code
    // example to run correctly more than once.
    // 1. Google Ads only allows one location extension feed per email address.
    // 2. A Google Ads account cannot have a location extension feed and an affiliate location
    // extension feed at the same time.
    private const DELETE_EXISTING_FEEDS = false;

    // The maximum number of attempts to make to retrieve the feed mapping before throwing an
    // exception.
    private const MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS = 10;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CHAIN_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CAMPAIGN_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::DELETE_EXISTING_FEEDS => GetOpt::OPTIONAL_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())
            ->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            // We set this value to true to show how to use GAPIC v2 source code. You can remove the
            // below line if you wish to use the old-style source code. Note that in that case, you
            // probably need to modify some parts of the code below to make it work.
            // For more information, see
            // https://developers.devsite.corp.google.com/google-ads/api/docs/client-libs/php/gapic.
            ->usingGapicV2Source(true)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::CHAIN_ID] ?: self::CHAIN_ID,
                $options[ArgumentNames::CAMPAIGN_ID] ?: self::CAMPAIGN_ID,
                filter_var(
                    $options[ArgumentNames::DELETE_EXISTING_FEEDS]
                        ?: self::DELETE_EXISTING_FEEDS,
                    FILTER_VALIDATE_BOOLEAN
                )
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param int $chainId the retail chain ID
     * @param int $campaignId the campaign ID for which the affiliate location extensions are added
     * @param bool $shouldDeleteExistingFeeds true if it should delete the existing feeds. The
     *     example will throw an error if feeds already exist and this option is not set to true
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $chainId,
        int $campaignId,
        bool $shouldDeleteExistingFeeds
    ) {
        if ($shouldDeleteExistingFeeds) {
            self::deleteLocationExtensionFeeds($googleAdsClient, $customerId);
        }
        $feedResourceName = self::createAffiliateLocationExtensionFeed(
            $googleAdsClient,
            $customerId,
            $chainId
        );
        // After the completion of the feed creation operation above the added feed will not
        // be available for usage in a campaign feed until the feed mapping is created.
        // We then need to wait for the feed mapping to be created.
        $feedMapping = self::waitForFeedToBeReady(
            $googleAdsClient,
            $customerId,
            $feedResourceName
        );
        self::createCampaignFeed(
            $googleAdsClient,
            $customerId,
            $campaignId,
            $feedMapping,
            $feedResourceName,
            $chainId
        );
    }

    /**
     * Deletes the existing location extension feeds.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     */
    private static function deleteLocationExtensionFeeds(
        GoogleAdsClient $googleAdsClient,
        int $customerId
    ) {
        // To delete a location extension feed, you need to
        // 1. Delete the customer feed so that the location extensions from the feed stop serving.
        // 2. Delete the feed so that Google Ads will no longer sync from the Business Profile
        // account.
        $customerFeeds = self::getLocationExtensionCustomerFeeds($googleAdsClient, $customerId);
        if (!empty($customerFeeds)) {
            // Optional: You may also want to delete the campaign and ad group feeds.
            self::removeCustomerFeeds($googleAdsClient, $customerId, $customerFeeds);
        }
        $feeds = self::getLocationExtensionFeeds($googleAdsClient, $customerId);
        if (!empty($feeds)) {
            self::removeFeeds($googleAdsClient, $customerId, $feeds);
        }
    }

    /**
     * Gets the existing location extension customer feeds.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @return CustomerFeed[] the list of location extension customer feeds
     */
    private static function getLocationExtensionCustomerFeeds(
        GoogleAdsClient $googleAdsClient,
        int $customerId
    ): array {
        $customerFeeds = [];

        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
        // Creates the query. A location extension customer feed can be identified by filtering
        // for placeholder_types as LOCATION (location extension feeds) or
        // placeholder_types as AFFILIATE_LOCATION (affiliate location extension feeds).
        $query = 'SELECT customer_feed.resource_name ' .
            'FROM customer_feed ' .
            'WHERE customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION) ' .
            'AND customer_feed.status = ENABLED';
        // Issues a search stream request.
        /** @var GoogleAdsServerStreamDecorator $stream */
        $stream = $googleAdsServiceClient->searchStream(
            SearchGoogleAdsStreamRequest::build($customerId, $query)
        );
        // Iterates over all rows in all messages to collect the results.
        foreach ($stream->iterateAllElements() as $googleAdsRow) {
            /** @var GoogleAdsRow $googleAdsRow */
            $customerFeeds[] = $googleAdsRow->getCustomerFeed();
        }

        return $customerFeeds;
    }

    /**
     * Removes the customer feeds.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param CustomerFeed[] $customerFeeds the list of customer feeds to remove
     */
    private static function removeCustomerFeeds(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        array $customerFeeds
    ) {
        $operations = [];
        foreach ($customerFeeds as $customerFeed) {
            /** @var CustomerFeed $customerFeed */
            $operations[] = new CustomerFeedOperation([
                'remove' => $customerFeed->getResourceName()
            ]);
        }

        // Issues a mutate request to remove the customer feeds.
        $googleAdsClient->getCustomerFeedServiceClient()->mutateCustomerFeeds(
            MutateCustomerFeedsRequest::build($customerId, $operations)
        );
    }

    /**
     * Gets the existing location extension feeds.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @return Feed[] the list of location extension feeds
     */
    private static function getLocationExtensionFeeds(
        GoogleAdsClient $googleAdsClient,
        int $customerId
    ): array {
        $feeds = [];

        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
        // Creates the query.
        $query = 'SELECT feed.resource_name ' .
            'FROM feed ' .
            'WHERE feed.status = ENABLED ' .
            'AND feed.origin = USER';
        // Issues a search stream request.
        /** @var GoogleAdsServerStreamDecorator $stream */
        $stream = $googleAdsServiceClient->searchStream(
            SearchGoogleAdsStreamRequest::build($customerId, $query)
        );
        // Iterates over all rows in all messages to collect the results.
        foreach ($stream->iterateAllElements() as $googleAdsRow) {
            /** @var GoogleAdsRow $googleAdsRow */
            $feeds[] = $googleAdsRow->getFeed();
        }

        return $feeds;
    }

    /**
     * Removes the feeds.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param Feed[] $feeds the list of feeds to remove
     */
    private static function removeFeeds(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        array $feeds
    ) {
        $operations = [];
        foreach ($feeds as $feed) {
            /** @var Feed $feed */
            $operations[] = new FeedOperation([
                'remove' => $feed->getResourceName()
            ]);
        }

        // Issues a mutate request to remove the feeds.
        $googleAdsClient->getFeedServiceClient()->mutateFeeds(
            MutateFeedsRequest::build($customerId, $operations)
        );
    }

    /**
     * Creates the affiliate location extension feed.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param int $chainId the retail chain ID
     * @return string the resource name of the newly created affiliate location extension feed
     */
    private static function createAffiliateLocationExtensionFeed(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $chainId
    ): string {
        // Creates a feed that will sync to retail addresses for a given retail chain ID.
        // Do not add feed attributes, Google Ads will add them automatically because this will
        // be a system generated feed.
        $feed = new Feed([
            'name' => 'Affiliate Location Extension feed #' . Helper::getPrintableDatetime(),
            'affiliate_location_feed_data' => new AffiliateLocationFeedData([
                'chain_ids' => [$chainId],
                'relationship_type' => AffiliateLocationFeedRelationshipType::GENERAL_RETAILER
            ]),
            // Since this feed's contents will be managed by Google, you must set its origin to
            // GOOGLE.
            'origin' => FeedOrigin::GOOGLE
        ]);

        // Creates the feed operation.
        $operation = new FeedOperation();
        $operation->setCreate($feed);

        // Issues a mutate request to add the feed and prints some information.
        $feedServiceClient = $googleAdsClient->getFeedServiceClient();
        $response =
            $feedServiceClient->mutateFeeds(MutateFeedsRequest::build($customerId, [$operation]));
        $feedResourceName = $response->getResults()[0]->getResourceName();
        printf(
            "Affiliate location extension feed created with resource name: '%s'.%s",
            $feedResourceName,
            PHP_EOL
        );

        return $feedResourceName;
    }

    /**
     * Waits for the affiliate location extension feed to be ready. An exponential back-off
     * policy with a maximum number of attempts is used to poll the server.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $feedResourceName the feed resource name
     * @throws RuntimeException if the feed mapping is still not ready and the maximum number of
     *     attempts has been reached
     * @return FeedMapping the newly created feed mapping
     */
    private static function waitForFeedToBeReady(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $feedResourceName
    ): FeedMapping {
        $numAttempts = 0;

        while ($numAttempts < self::MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS) {
            // Once you create a feed, Google's servers will setup the feed by creating feed
            // attributes and feed mapping. Once the feed mapping is created, it is ready to be
            // used for creating customer feed.
            // This process is asynchronous, so we wait until the feed mapping is created,
            // performing exponential backoff.
            $feedMapping = self::getAffiliateLocationExtensionFeedMapping(
                $googleAdsClient,
                $customerId,
                $feedResourceName
            );

            if (is_null($feedMapping)) {
                $numAttempts++;
                $sleepSeconds = intval(5 * pow(2, $numAttempts));
                printf(
                    'Checked: %d time(s). Feed is not ready yet. ' .
                    'Waiting %d seconds before trying again.%s',
                    $numAttempts,
                    $sleepSeconds,
                    PHP_EOL
                );
                sleep($sleepSeconds);
            } else {
                printf("Feed '%s' is now ready.%s", $feedResourceName, PHP_EOL);
                return $feedMapping;
            }
        }

        throw new RuntimeException(sprintf(
            "The affiliate location feed mapping is still not ready after %d attempt(s).%s",
            self::MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS,
            PHP_EOL
        ));
    }

    /**
     * Gets the affiliate location extension feed mapping.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $feedResourceName the feed resource name
     * @return FeedMapping|null the feed mapping if it exists otherwise null
     */
    private static function getAffiliateLocationExtensionFeedMapping(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $feedResourceName
    ): ?FeedMapping {
        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
        // Creates a query that retrieves the feed mapping.
        $query = sprintf(
            "SELECT feed_mapping.resource_name, " .
            "feed_mapping.attribute_field_mappings, " .
            "feed_mapping.status " .
            "FROM feed_mapping " .
            "WHERE feed_mapping.feed = '%s' " .
            "AND feed_mapping.status = ENABLED " .
            "AND feed_mapping.placeholder_type = AFFILIATE_LOCATION " .
            "LIMIT 1",
            $feedResourceName
        );

        // Issues a search request.
        $response = $googleAdsServiceClient->search(
            SearchGoogleAdsRequest::build($customerId, $query)->setReturnTotalResultsCount(true)
        );

        return $response->getPage()->getPageElementCount() === 1
            ? $response->getIterator()->current()->getFeedMapping()
            : null;
    }

    /**
     * Creates the campaign feed.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param int $campaignId the campaign ID for which the affiliate location extensions are added
     * @param FeedMapping $feedMapping the affiliate location extension feed mapping
     * @param string $feedResourceName the feed resource name
     * @param int $chainId the retail chain ID
     */
    private static function createCampaignFeed(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $campaignId,
        FeedMapping $feedMapping,
        string $feedResourceName,
        int $chainId
    ) {
        $matchingFunction = sprintf(
            'IN(FeedAttribute[%d, %d], %d)',
            FeedServiceClient::parseName($feedResourceName)['feed'],
            self::getAttributeIdForChainId($feedMapping),
            $chainId
        );

        // Adds a campaign feed that associates the feed with this campaign for the
        // AFFILIATE_LOCATION placeholder type.
        $campaignFeed = new CampaignFeed([
            'feed' => $feedResourceName,
            'placeholder_types' => [PlaceholderType::AFFILIATE_LOCATION],
            'matching_function' => new MatchingFunction(['function_string' => $matchingFunction]),
            'campaign' => ResourceNames::forCampaign($customerId, $campaignId)
        ]);

        // Creates the campaign feed operation.
        $operation = new CampaignFeedOperation();
        $operation->setCreate($campaignFeed);

        // Issues a mutate request to add the campaign feed and prints some information.
        $campaignFeedServiceClient = $googleAdsClient->getCampaignFeedServiceClient();
        $response = $campaignFeedServiceClient->mutateCampaignFeeds(
            MutateCampaignFeedsRequest::build($customerId, [$operation])
        );
        printf(
            "Campaign feed created with resource name: '%s'.%s",
            $response->getResults()[0]->getResourceName(),
            PHP_EOL
        );
    }

    /**
     * Gets the feed attribute ID for the retail chain ID.
     *
     * @param FeedMapping $feedMapping the feed mapping
     * @@return int the feed attribute ID
     */
    private static function getAttributeIdForChainId(FeedMapping $feedMapping): int
    {
        foreach ($feedMapping->getAttributeFieldMappings() as $fieldMapping) {
            /** @var AttributeFieldMapping $fieldMapping */
            if (
                $fieldMapping->getAffiliateLocationField()
                === AffiliateLocationPlaceholderField::CHAIN_ID
            ) {
                return $fieldMapping->getFeedAttributeId();
            }
        }

        throw new RuntimeException(
            "Affiliate location feed mapping isn't setup correctly." . PHP_EOL
        );
    }
}

AddAffiliateLocationExtensions::main();

      

Python

#!/usr/bin/env python
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Demonstrates how to add Affiliate Location extensions.

This example adds a feed that syncs retail addresses for a given retail chain ID
and associates the feed with a campaign for serving affiliate location
extensions.
"""


import argparse
import sys
from time import sleep
from uuid import uuid4

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

# The maximum number of attempts to retrieve the FeedMappings before throwing an
# exception.
MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10


def main(client, customer_id, chain_id, campaign_id):
    """Demonstrates how to add Affiliate Location extensions.

    Args:
        client: An initialized instance.
        customer_id: The client customer ID.
        chain_id: The retail chain ID. See:
            https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids
            for a complete list of valid retail chain IDs.
        campaign_id: The campaign ID for which the affiliate location extensions
            will be added.
    """
    feed_resource_name = create_affiliate_location_extension_feed(
        client, customer_id, chain_id
    )
    # After the completion of the feed creation operation above, the added
    # feed will not be available for usage in a campaign feed until the feed
    # mappings are created. We will wait with an exponential back-off policy
    # until the feed mappings have been created.
    feed_mapping = wait_for_feed_to_be_ready(
        client, customer_id, feed_resource_name
    )
    create_campaign_feed(
        client,
        customer_id,
        campaign_id,
        feed_mapping,
        feed_resource_name,
        chain_id,
    )


def create_affiliate_location_extension_feed(client, customer_id, chain_id):
    """Creates the Affiliate Location Extension feed.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
        chain_id: The retail chain ID.

    Returns:
        The string resource name of the newly created Affiliate Location
        Extension feed.
    """
    # Optional: Remove all existing location extension feeds. This is an
    # optional step, and is required for this code example to run correctly more
    # than once.
    # This is because Google Ads only allows one location extension feed per
    # email address, and a Google Ads account cannot have a location extension
    # feed and an affiliate location extension feed at the same time.
    remove_location_extension_feeds(client, customer_id)

    # Get the FeedServiceClient.
    feed_service = client.get_service("FeedService")

    # Create a feed that will sync to retail addresses for a given retail chain
    # ID. Do not add FeedAttributes to this object as Google Ads will add
    # them automatically as this will be a system generated feed.
    feed_operation = client.get_type("FeedOperation")
    feed = feed_operation.create
    feed.name = f"Affiliate Location Extension feed #{uuid4()}"
    feed.affiliate_location_feed_data.chain_ids.append(chain_id)
    feed.affiliate_location_feed_data.relationship_type = (
        client.enums.AffiliateLocationFeedRelationshipTypeEnum.GENERAL_RETAILER
    )
    # Since this feed's contents will be managed by Google, you must set its
    # origin to GOOGLE.
    feed.origin = client.enums.FeedOriginEnum.GOOGLE

    # Add the feed.
    mutate_feeds_response = feed_service.mutate_feeds(
        customer_id=customer_id, operations=[feed_operation]
    )

    # Display the results.
    feed_resource_name = mutate_feeds_response.results[0].resource_name
    print(
        "Affiliate location extension feed created with resource name: "
        f"{feed_resource_name}."
    )
    return feed_resource_name


def remove_location_extension_feeds(client, customer_id):
    """Removes the old location extension feeds.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
    """
    # To remove a location extension feed, you need to:
    # 1. Remove the CustomerFeed so that the location extensions from the feed
    # stop serving.
    # 2. Remove the feed so that Google Ads will no longer sync from the
    # Business Profile account.
    # Optional: You may also want to remove the CampaignFeeds and AdGroupFeeds.
    old_customer_feeds = get_location_extension_customer_feeds(
        client, customer_id
    )

    if old_customer_feeds:
        remove_customer_feeds(client, customer_id, old_customer_feeds)

    feeds = get_location_extension_feeds(client, customer_id)

    if feeds:
        remove_feeds(client, customer_id, feeds)


def get_location_extension_feeds(client, customer_id):
    """Gets the location extension feeds.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
    Returns:
        The list of location extension feeds.
    """
    googleads_service = client.get_service("GoogleAdsService")

    # Create the query.
    query = """
        SELECT
          feed.resource_name,
          feed.status,
          feed.places_location_feed_data.email_address,
          feed.affiliate_location_feed_data.chain_ids
        FROM feed
        WHERE feed.status = ENABLED"""

    search_results = googleads_service.search(
        customer_id=customer_id, query=query
    )

    # A location extension feed can be identified by checking whether the
    # PlacesLocationFeedData field is set (Location extensions feeds) or
    # AffiliateLocationFeedData field is set (Affiliate location extension
    # feeds)
    return [
        row.feed
        for row in search_results
        if row.feed.places_location_feed_data
        or row.feed.affiliate_location_feed_data
    ]


def remove_feeds(client, customer_id, feeds):
    """Removes the feeds.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
        feeds: The list of feeds to remove.
    """
    operations = []

    for feed in feeds:
        operation = client.get_type("FeedOperation")
        operation.remove = feed.resource_name
        operations.append(operation)

    feed_service = client.get_service("FeedService")
    feed_service.mutate_feeds(customer_id=customer_id, operations=operations)


def get_location_extension_customer_feeds(client, customer_id):
    """Gets the location extension customer feeds.

    Args:
        client: The Google Ads API client.
        customer_id: The customer ID from which to fetch feeds.
    Returns:
        A list of location extension customer feeds.

    """
    googleads_service = client.get_service("GoogleAdsService")

    # Create the query. A location extension customer feed can be identified by
    # filtering for placeholder_types=LOCATION (location extension feeds) or
    # placeholder_types =AFFILIATE_LOCATION (affiliate location extension feeds)
    query = """
        SELECT
          customer_feed.resource_name,
          customer_feed.feed,
          customer_feed.status,
          customer_feed.matching_function.function_string
        FROM customer_feed
        WHERE
          customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION)
          AND customer_feed.status=ENABLED"""

    search_results = googleads_service.search(
        customer_id=customer_id, query=query
    )

    return [row.customer_feed for row in search_results]


def remove_customer_feeds(client, customer_id, customer_feeds):
    """Removes the customer feeds.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
        customer_feeds: The customer feeds to remove.
    """
    operations = []

    for customer_feed in customer_feeds:
        operation = client.get_type("CustomerFeedOperation")
        operation.remove = customer_feed.resource_name
        operations.append(operation)

    customer_feed_service = client.get_service("CustomerFeedService")
    customer_feed_service.mutate_customer_feeds(
        customer_id=customer_id, operations=operations
    )


def get_affiliate_location_extension_feed_mapping(
    client, customer_id, feed_resource_name
):
    """Gets the Affiliate Location Extension feed mapping.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
        feed_resource_name: The feed resource name.
    Returns:
        The newly created feed mapping.
    """
    # Get the GoogleAdsService.
    googleads_service = client.get_service("GoogleAdsService")

    # Create the query.
    query = f"""
        SELECT
          feed_mapping.resource_name,
          feed_mapping.attribute_field_mappings,
          feed_mapping.status
        FROM feed_mapping
        WHERE
          feed_mapping.feed = '{feed_resource_name}'
          AND feed_mapping.status = ENABLED
          AND feed_mapping.placeholder_type = AFFILIATE_LOCATION
        LIMIT 1"""

    # Issue a search request.
    search_results = googleads_service.search(
        customer_id=customer_id, query=query
    )

    # Return the feed mapping that results from the search.
    # It is possible that the feed is not yet ready, so we catch the exception
    # if the feed mapping is not yet available.
    try:
        row = next(iter(search_results))
    except StopIteration:
        return None
    else:
        return row.feed_mapping


def wait_for_feed_to_be_ready(client, customer_id, feed_resource_name):
    """Waits for the Affiliate location extension feed to be ready.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
        feed_resource_name: The resource name of the feed.

    Returns:
        The newly created FeedMapping.

    Raises:
        Exception: if the feed is not ready after the specified number of
            retries.
    """
    num_attempts = 0
    sleep_seconds = 0

    while num_attempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS:
        # Once you create a feed, Google's servers will setup the feed by
        # creating feed attributes and feed mapping. Once the feed mapping is
        # created, it is ready to be used for creating customer feed.
        # This process is asynchronous, so we wait until the feed mapping is
        # created, performing exponential backoff.
        feed_mapping = get_affiliate_location_extension_feed_mapping(
            client, customer_id, feed_resource_name
        )

        if feed_mapping is None:
            num_attempts += 1
            sleep_seconds = 5 * 2**num_attempts
            print(
                f"Checked {num_attempts} time(s). Feed is not ready "
                f"yet. Waiting {sleep_seconds} seconds before trying again."
            )
            sleep(sleep_seconds)
        else:
            print(f"Feed {feed_resource_name} is now ready.")
            return feed_mapping

    raise Exception(
        f"Feed is not ready after "
        f"{MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS} retries."
    )


def create_campaign_feed(
    client, customer_id, campaign_id, feed_mapping, feed_resource_name, chain_id
):
    """Creates the campaign feed.

    Args:
        client: The Google Ads API client.
        customer_id: The Google Ads customer ID.
        campaign_id: The campaign ID to which the affiliate location extensions
            will be added.
        feed_mapping: The affiliate location extension feedmapping for the feed
            resource name.
        feed_resource_name: The feed resource name.
        chain_id: The retail chain ID.
    """
    # Get the CampaignFeedService.
    campaign_feed_service = client.get_service("CampaignFeedService")
    feed_service = client.get_service("FeedService")

    attribute_id_for_chain_id = get_attribute_id_for_chain_id(
        client, feed_mapping
    )
    feed_id = feed_service.parse_feed_path(feed_resource_name)["feed_id"]

    matching_function = (
        f"IN(FeedAttribute[{feed_id}, {attribute_id_for_chain_id}], {chain_id})"
    )

    # Add a CampaignFeed that associates the feed with this campaign for
    # the AFFILIATE_LOCATION placeholder type.
    campaign_feed_operation = client.get_type("CampaignFeedOperation")
    campaign_feed = campaign_feed_operation.create
    campaign_feed.feed = feed_resource_name
    campaign_feed.placeholder_types.append(
        client.enums.PlaceholderTypeEnum.AFFILIATE_LOCATION
    )
    campaign_feed.matching_function.function_string = matching_function
    campaign_feed.campaign = client.get_service(
        "CampaignService"
    ).campaign_path(customer_id, campaign_id)

    mutate_campaign_feeds_response = (
        campaign_feed_service.mutate_campaign_feeds(
            customer_id=customer_id, operations=[campaign_feed_operation]
        )
    )

    # Display the result.
    print(
        "Campaign feed created with resource name: "
        f"{mutate_campaign_feeds_response.results[0].resource_name}."
    )


def get_attribute_id_for_chain_id(client, feed_mapping):
    """Gets the feed attribute ID for the retail chain ID.

    Args:
        client: The Google Ads API client.
        feed_mapping: The FeedMapping in which to search.
    Returns:
        The feed attribute ID.
    Raises:
        Exception: If no AffiliateLocationField with a retail chain ID is found
            in the FeedMapping.
    """
    for field_mapping in feed_mapping.attribute_field_mappings:
        if (
            field_mapping.affiliate_location_field
            == client.enums.AffiliateLocationPlaceholderFieldEnum.CHAIN_ID
        ):
            return field_mapping.feed_attribute_id

    raise Exception(
        "No AffiliateLocationField with a retail chain ID was "
        "found in the FeedMapping."
    )


if __name__ == "__main__":
    # will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    googleads_client = GoogleAdsClient.load_from_storage(version="v15")

    parser = argparse.ArgumentParser(
        description="Demonstrates how to add Affiliate Location extensions."
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-a",
        "--chain_id",
        type=int,
        required=True,
        help="The retail chain ID. See: "
        "https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids "
        "for a complete list of valid retail chain IDs.",
    )
    parser.add_argument(
        "-i",
        "--campaign_id",
        type=int,
        required=True,
        help="The campaign ID for which the affiliate location extensions will "
        "be added.",
    )
    args = parser.parse_args()

    try:
        main(
            googleads_client,
            args.customer_id,
            args.chain_id,
            args.campaign_id,
        )
    except GoogleAdsException as ex:
        print(
            f"Request with ID '{ex.request_id}'' failed with status "
            f"'{ex.error.code().name}' and includes the following errors:"
        )
        for error in ex.failure.errors:
            print(f"\tError with message '{error.message}'.")
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(f"\t\tOn field: {field_path_element.field_name}")
        sys.exit(1)

      

Ruby

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code example adds a feed that syncs retail addresses for a given retail
# chain ID and associates the feed with a campaign for serving affiliate
# location extensions.

require 'optparse'
require 'google/ads/google_ads'
require 'date'

def add_affiliate_location_extensions(
  customer_id,
  chain_id,
  campaign_id,
  should_delete_existing_feeds)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  if should_delete_existing_feeds
    delete_location_extension_feeds(client, customer_id)
  end

  feed_resource_name = create_affiliate_location_extension_feed(
    client,
    customer_id,
    chain_id,
  )

  # After the completion of the feed creation operation above the added feed
  # will not be available for usage in a campaign feed until the feed mapping
  # is created. We then need to wait for the feed mapping to be created.
  feed_mapping = wait_for_feed_to_be_ready(
    client,
    customer_id,
    feed_resource_name,
  )

  create_campaign_feed(
    client,
    customer_id,
    campaign_id,
    feed_mapping,
    feed_resource_name,
    chain_id,
  )
end

# Deletes the existing location extension feeds.
def delete_location_extension_feeds(client, customer_id)
  # To delete a location extension feed, you need to
  # 1. Delete the customer feed so that the location extensions from the feed
  #    stop serving.
  # 2. Delete the feed so that Google Ads will no longer sync from the Business
  #    Profile account.
  customer_feeds = get_location_extension_customer_feeds(client, customer_id)
  unless customer_feeds.empty?
    # Optional: You may also want to delete the campaign and ad group feeds.
    remove_customer_feeds(client, customer_id, customer_feeds)
  end
  feeds = get_location_extension_feeds(client, customer_id)
  unless feeds.empty?
    remove_feeds(client, customer_id, customer_feeds)
  end
end

# Gets the existing location extension customer feeds.
def get_location_extension_customer_feeds(client, customer_id)
  customer_feeds = []

  # Creates the query. A location extension customer feed can be identified
  # by filtering for placeholder_types as LOCATION (location extension feeds) or
  # placeholder_types as AFFILIATE_LOCATION (affiliate location extension feeds).
  query = <<~QUERY
    SELECT customer_feed.resource_name
    FROM customer_feed
    WHERE customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION)
    AND customer_feed.status = ENABLED
  QUERY

  # Issues a search stream request.
  responses = client.service.google_ads.search_stream(
    customer_id: customer_id,
    query: query,
  )

  # Iterates over all rows in all messages to collect the results.
  responses.each do |response|
    response.results.each do |row|
      customer_feeds << row.customer_feed
    end
  end

  customer_feeds
end

# Removes the customer feeds.
def remove_customer_feeds(client, customer_id, customer_feeds)
  operations = []

  customer_feeds.each do |customer_feed|
    operations << client.operation.remove_resource.customer_feed(
      customer_feed.resource_name)
  end

  # Issues a mutate request to remove the customer feeds.
  client.service.customer_feed.mutate_customer_feeds(customer_id, operations)
end

# Gets the existing location extension feeds.
def get_location_extension_feeds(client, customer_id)
  feeds = []

  # Creates the query.
  query = <<~QUERY
    SELECT feed.resource_name
    FROM feed
    WHERE feed.status = ENABLED
    AND feed.origin = USER
  QUERY

  # Issues a search stream request.
  responses = client.service.google_ads.search_stream(
    customer_id: customer_id,
    query: query,
  )

  # Iterates over all rows in all messages to collect the results.
  responses.each do |response|
    response.results.each do |row|
      feeds << row.feed
    end
  end

  feeds
end

# Removes the feeds.
def remove_feeds(client, customer_id, feeds)
  operations = []

  feeds.each do |feed|
    operations << client.operation.remove_resource.feed(feed.resource_name)
  end

  # Issues a mutate request to remove the feeds.
  client.service.feed.mutate_feeds(customer_id, operations)
end

# Creates the affiliate location extension feed.
def create_affiliate_location_extension_feed(client, customer_id, chain_id)
  # Creates a feed that will sync to retail addresses for a given retail
  # chain ID.
  # Do not add feed attributes, Google Ads will add them automatically because
  # this will be a system generated feed.
  operation = client.operation.create_resource.feed do |feed|
    feed.name = "Affiliate Location Extension feed ##{(Time.new.to_f * 1000).to_i}"
    feed.affiliate_location_feed_data = client.resource.affiliate_location_feed_data do |data|
      data.chain_ids << chain_id
      data.relationship_type = :GENERAL_RETAILER
    end
    # Since this feed's contents will be managed by Google, you must set its
    # origin to GOOGLE.
    feed.origin = :GOOGLE
  end

  # Issues a mutate request to add the feed and prints some information.
  response = client.service.feed.mutate_feeds(
    customer_id: customer_id,
    operations: [operation],
  )

  feed_resource_name = response.results.first.resource_name
  puts "Affiliate location extension feed created with resource name: #{feed_resource_name}"

  feed_resource_name
end

# Waits for the affiliate location extension feed to be ready. An exponential
# back-off policy with a maximum number of attempts is used to poll the server.
def wait_for_feed_to_be_ready(client, customer_id, feed_resource_name)
  number_of_attempts = 0

  while number_of_attempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS
    # Once you create a feed, Google's servers will setup the feed by creating
    # feed attributes and feed mapping. Once the feed mapping is created, it is
    # ready to be used for creating customer feed.
    # This process is asynchronous, so we wait until the feed mapping is
    # created, performing exponential backoff.
    feed_mapping = get_affiliated_location_extension_feed_mapping(
      client, customer_id, feed_resource_name)

    if feed_mapping.nil?
      number_of_attempts += 1
      sleep_seconds = POLL_FREQUENCY_SECONDS * (2 ** number_of_attempts)
      puts "Checked #{number_of_attempts} time(s). Feed is not ready yet. " \
        "Waiting #{sleep_seconds} seconds before trying again."
      sleep sleep_seconds
    else
      puts "Feed #{feed_resource_name} is now ready."
      return feed_mapping
    end
  end

  raise "The affiliate location feed mapping is still not ready after " \
    "#{MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS} attempt(s)."
end

# Gets the affiliate location extension feed mapping.
def get_affiliated_location_extension_feed_mapping(
  client,
  customer_id,
  feed_resource_name)
  # Creates a query that retrieves the feed mapping.
  query = <<~QUERY
    SELECT feed_mapping.resource_name,
           feed_mapping.attribute_field_mappings,
           feed_mapping.status
    FROM feed_mapping
    WHERE feed_mapping.feed = '#{feed_resource_name}'
    AND feed_mapping.status = ENABLED
    AND feed_mapping.placeholder_type = AFFILIATE_LOCATION
    LIMIT 1
  QUERY

  # Issues a search request
  responses = client.service.google_ads.search(
    customer_id: customer_id,
    query: query,
    return_total_results_count: true,
  )

  response = responses.page.response
  response.total_results_count == 1 ? response.results.first.feed_mapping : nil
end

# Creates the campaign feed.
def create_campaign_feed(
  client,
  customer_id,
  campaign_id,
  feed_mapping,
  feed_resource_name,
  chain_id)
  matching_function = "IN(FeedAttribute[#{feed_resource_name.split('/')[3]}, " \
    "#{get_attribute_id_for_chain_id(feed_mapping)}], #{chain_id})"

  # Adds a campaign feed that associates the feed with this campaign for the
  # AFFILIATE_LOCATION placeholder type.
  operation = client.operation.create_resource.campaign_feed do |cf|
    cf.feed = feed_resource_name
    cf.placeholder_types << :AFFILIATE_LOCATION
    cf.matching_function = client.resource.matching_function do |m|
      m.function_string = matching_function
    end
    cf.campaign = client.path.campaign(customer_id, campaign_id)
  end

  # Issues a mutate request to add the campaign feed and prints some information.
  response = client.service.campaign_feed.mutate_campaign_feeds(
    customer_id: customer_id,
    operations: [operation],
  )
  puts "Campaign feed created with resource name: " \
    "#{response.results.first.resource_name}"
end

# Gets the feed attribute ID for the retail chain ID.
def get_attribute_id_for_chain_id(feed_mapping)
  feed_mapping.attribute_field_mappings.each do |fm|
    if fm.affiliate_location_field == :CHAIN_ID
      return fm.feed_attribute_id
    end
  end

  raise "Affiliate location feed mapping isn't setup correctly."
end

if __FILE__ == $0
  POLL_FREQUENCY_SECONDS = 5
  MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10

  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'
  options[:chain_id] = 'INSERT_CHAIN_ID_HERE'
  options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE'
  options[:should_delete_existing_feeds] = false

  OptionParser.new do |opts|
    opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.on('-N', '--chain-id CHAIN-ID', String, 'Chain ID') do |v|
      options[:chain_id] = v
    end

    opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v|
      options[:campaign_id] = v
    end

    opts.on('-D', '--should-delete-existing-feeds SHOULD-DELETE-EXISTING-FEEDS',
      TrueClass, 'Should delete existing feeds') do |v|
      options[:should_delete_existing_feeds] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    add_affiliate_location_extensions(
      options.fetch(:customer_id).tr("-", ""),
      options.fetch(:chain_id).to_i,
      options.fetch(:campaign_id),
      options[:should_delete_existing_feeds])
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      if error.location
        error.location.field_path_elements.each do |field_path_element|
          STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
        end
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED
        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
    raise
  end
end

      

Perl

#!/usr/bin/perl -w
#
# Copyright 2020, Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code example adds a feed that syncs retail addresses for a given retail
# chain ID and associates the feed with a campaign for serving affiliate location
# extensions.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::Utils::SearchStreamHandler;
use Google::Ads::GoogleAds::V15::Resources::Feed;
use Google::Ads::GoogleAds::V15::Resources::AffiliateLocationFeedData;
use Google::Ads::GoogleAds::V15::Resources::CampaignFeed;
use Google::Ads::GoogleAds::V15::Common::MatchingFunction;
use
  Google::Ads::GoogleAds::V15::Enums::AffiliateLocationFeedRelationshipTypeEnum
  qw(GENERAL_RETAILER);
use Google::Ads::GoogleAds::V15::Enums::FeedOriginEnum qw(GOOGLE);
use Google::Ads::GoogleAds::V15::Enums::PlaceholderTypeEnum
  qw(AFFILIATE_LOCATION);
use Google::Ads::GoogleAds::V15::Enums::AffiliateLocationPlaceholderFieldEnum
  qw(CHAIN_ID);
use
  Google::Ads::GoogleAds::V15::Services::CustomerFeedService::CustomerFeedOperation;
use Google::Ads::GoogleAds::V15::Services::FeedService::FeedOperation;
use
  Google::Ads::GoogleAds::V15::Services::CampaignFeedService::CampaignFeedOperation;
use Google::Ads::GoogleAds::V15::Utils::ResourceNames;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd          qw(abs_path);
use Data::Uniqid qw(uniqid);
use Time::HiRes  qw(sleep);

# The maximum number of attempts to make to retrieve the feed mapping before
# throwing an exception.
use constant MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS => 10;

# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
my $customer_id = "INSERT_CUSTOMER_ID_HERE";
# The retail chain ID. For a complete list of valid retail chain IDs, see
# https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids.
my $chain_id = "INSERT_CHAIN_ID_HERE";
# The campaign ID for which the affiliate location extensions are added.
my $campaign_id = "INSERT_CAMPAIGN_ID_HERE";
# Optional: Delete all existing location extension feeds. This is required for
# this code example to run correctly more than once.
# 1. Google Ads only allows one location extension feed per email address.
# 2. A Google Ads account cannot have a location extension feed and an affiliate
# location extension feed at the same time.
my $delete_existing_feeds = 0;

sub add_affiliate_location_extensions {
  my ($api_client, $customer_id, $chain_id, $campaign_id,
    $delete_existing_feeds)
    = @_;

  if ($delete_existing_feeds) {
    delete_location_extension_feeds($api_client, $customer_id);
  }

  my $feed_resource_name =
    create_affiliate_location_extension_feed($api_client, $customer_id,
    $chain_id);

  # After the completion of the feed creation operation above the added feed
  # will not be available for usage in a campaign feed until the feed mapping
  # is created.
  # We will wait with an exponential back-off policy until the feed mapping has
  # been created.
  my $feed_mapping =
    wait_for_feed_to_be_ready($api_client, $customer_id, $feed_resource_name);

  create_campaign_feed($api_client, $customer_id, $campaign_id, $feed_mapping,
    $feed_resource_name, $chain_id);

  return 1;
}

# Deletes the existing location extension feeds.
sub delete_location_extension_feeds {
  my ($api_client, $customer_id) = @_;

  # To delete a location extension feed, you need to
  # 1. Delete the customer feed so that the location extensions from the feed stop serving.
  # 2. Delete the feed so that Google Ads will no longer sync from the Business Profile account.
  my $customer_feeds =
    get_location_extension_customer_feeds($api_client, $customer_id);
  if (scalar @$customer_feeds > 0) {
    # Optional: You may also want to delete the campaign and ad group feeds.
    remove_customer_feeds($api_client, $customer_id, $customer_feeds);
  }

  my $feeds = get_location_extension_feeds($api_client, $customer_id);
  if (scalar @$feeds > 0) {
    remove_feeds($api_client, $customer_id, $feeds);
  }
}

# Gets the existing location extension customer feeds.
sub get_location_extension_customer_feeds {
  my ($api_client, $customer_id) = @_;

  my $customer_feeds = [];

  my $google_ads_service = $api_client->GoogleAdsService();
  # Create the query. A location extension customer feed can be identified by
  # filtering for placeholder_types as LOCATION (location extension feeds) or
  # placeholder_types as AFFILIATE_LOCATION (affiliate location extension feeds).
  my $search_query =
    "SELECT customer_feed.resource_name FROM customer_feed " .
    "WHERE customer_feed.placeholder_types CONTAINS " .
    "ANY(LOCATION, AFFILIATE_LOCATION) AND customer_feed.status = ENABLED";

  my $search_stream_handler =
    Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({
      service => $google_ads_service,
      request => {
        customerId => $customer_id,
        query      => $search_query
      }});

  # Issue a search stream request.
  $search_stream_handler->process_contents(
    sub {
      my $google_ads_row = shift;
      push @$customer_feeds, $google_ads_row->{customerFeed};
    });

  return $customer_feeds;
}

# Removes the customer feeds.
sub remove_customer_feeds {
  my ($api_client, $customer_id, $customer_feeds) = @_;

  my $operations = [];
  foreach my $customer_feed (@$customer_feeds) {
    push @$operations,
      Google::Ads::GoogleAds::V15::Services::CustomerFeedService::CustomerFeedOperation
      ->new({remove => $customer_feed->{resourceName}});
  }

  # Issue a mutate request to remove the customer feeds.
  $api_client->CustomerFeedService()->mutate({
    customerId => $customer_id,
    operations => $operations
  });
}

# Gets the existing location extension feeds.
sub get_location_extension_feeds {
  my ($api_client, $customer_id) = @_;

  my $feeds = [];

  my $google_ads_service = $api_client->GoogleAdsService();
  # Create the query.
  my $search_query = "SELECT feed.resource_name FROM feed " .
    "WHERE feed.status = ENABLED AND feed.origin = USER";

  my $search_stream_handler =
    Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({
      service => $google_ads_service,
      request => {
        customerId => $customer_id,
        query      => $search_query
      }});

  # Issue a search stream request.
  $search_stream_handler->process_contents(
    sub {
      my $google_ads_row = shift;
      push @$feeds, $google_ads_row->{feed};
    });

  return $feeds;
}

# Removes the feeds.
sub remove_feeds {
  my ($api_client, $customer_id, $feeds) = @_;

  my $operations = [];
  foreach my $feed (@$feeds) {
    push @$operations,
      Google::Ads::GoogleAds::V15::Services::FeedService::FeedOperation->new(
      {remove => $feed->{resourceName}});
  }

  # Issue a mutate request to remove the feeds.
  $api_client->FeedService()->mutate({
    customerId => $customer_id,
    operations => $operations
  });
}

# Creates the affiliate location extension feed.
sub create_affiliate_location_extension_feed {
  my ($api_client, $customer_id, $chain_id) = @_;

  # Create a feed that will sync to retail addresses for a given retail chain ID.
  # Do not add feed attributes, Google Ads will add them automatically because
  # this will be a system generated feed.
  my $feed = Google::Ads::GoogleAds::V15::Resources::Feed->new({
      name => "Affiliate Location Extension feed #" . uniqid(),
      affiliateLocationFeedData =>
        Google::Ads::GoogleAds::V15::Resources::AffiliateLocationFeedData->new({
          chainIds         => [$chain_id],
          relationshipType => GENERAL_RETAILER
        }
        ),
      # Since this feed's contents will be managed by Google, you must set its
      # origin to GOOGLE.
      origin => GOOGLE
    });

  # Create the feed operation.
  my $operation =
    Google::Ads::GoogleAds::V15::Services::FeedService::FeedOperation->new({
      create => $feed
    });

  # Issue a mutate request to add the feed and print some information.
  my $response = $api_client->FeedService()->mutate({
      customerId => $customer_id,
      operations => [$operation]});
  my $feed_resource_name = $response->{results}[0]{resourceName};
  printf
    "Affiliate location extension feed created with resource name: '%s'.\n",
    $feed_resource_name;

  return $feed_resource_name;
}

# Waits for the affiliate location extension feed to be ready.
sub wait_for_feed_to_be_ready {
  my ($api_client, $customer_id, $feed_resource_name) = @_;

  my $num_attempts = 0;
  while ($num_attempts < MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS) {
    # Once you create a feed, Google's servers will setup the feed by creating
    # feed attributes and feed mapping. Once the feed mapping is created, it is
    # ready to be used for creating customer feed.
    # This process is asynchronous, so we wait until the feed mapping is created,
    # performing exponential backoff.
    my $feed_mapping =
      get_affiliate_location_extension_feed_mapping($api_client, $customer_id,
      $feed_resource_name);

    if (!$feed_mapping) {
      $num_attempts++;
      my $sleep_seconds = 5 * (2**$num_attempts);
      printf "Checked: %d time(s). Feed is not ready yet. " .
        "Waiting %d seconds before trying again.\n",
        $num_attempts,
        $sleep_seconds;
      sleep($sleep_seconds);
    } else {
      printf "Feed '%s' is now ready.\n", $feed_resource_name;
      return $feed_mapping;
    }
  }

  die(
    sprintf "The affiliate location feed mapping is still not ready " .
      "after %d attempt(s).\n",
    MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS
  );
}

# Gets the affiliate location extension feed mapping.
sub get_affiliate_location_extension_feed_mapping {
  my ($api_client, $customer_id, $feed_resource_name) = @_;

  # Create a query that retrieves the feed mapping.
  my $search_query =
    "SELECT feed_mapping.resource_name, " .
    "feed_mapping.attribute_field_mappings, " .
    "feed_mapping.status FROM feed_mapping " .
    "WHERE feed_mapping.feed = '$feed_resource_name' " .
    "AND feed_mapping.status = ENABLED " .
    "AND feed_mapping.placeholder_type = AFFILIATE_LOCATION LIMIT 1";

  # Issue a search request.
  my $response = $api_client->GoogleAdsService()->search({
    customerId              => $customer_id,
    query                   => $search_query,
    returnTotalResultsCount => "true"
  });

  return $response->{totalResultsCount} && $response->{totalResultsCount} == 1
    ? $response->{results}[0]{feedMapping}
    : undef;
}

# Create the campaign feed.
sub create_campaign_feed {
  my ($api_client, $customer_id, $campaign_id, $feed_mapping,
    $feed_resource_name, $chain_id)
    = @_;

  my $feed_id                   = $1 if $feed_resource_name =~ /(\d+)$/;
  my $attribute_id_for_chain_id = get_attribute_id_for_chain_id($feed_mapping);
  my $matching_function =
    "IN(FeedAttribute[$feed_id, $attribute_id_for_chain_id], $chain_id)";

  # Add a campaign feed that associates the feed with this campaign for the
  # AFFILIATE_LOCATION placeholder type.
  my $campaign_feed = Google::Ads::GoogleAds::V15::Resources::CampaignFeed->new(
    {
      feed             => $feed_resource_name,
      placeholderTypes => AFFILIATE_LOCATION,
      matchingFunction =>
        Google::Ads::GoogleAds::V15::Common::MatchingFunction->new({
          functionString => $matching_function
        }
        ),
      campaign => Google::Ads::GoogleAds::V15::Utils::ResourceNames::campaign(
        $customer_id, $campaign_id
      )});

  # Create the campaign feed operation.
  my $operation =
    Google::Ads::GoogleAds::V15::Services::CampaignFeedService::CampaignFeedOperation
    ->new({
      create => $campaign_feed
    });

  # Issue a mutate request to add the campaign feed and print some information.
  my $response = $api_client->CampaignFeedService()->mutate({
      customerId => $customer_id,
      operations => [$operation]});

  printf
    "Campaign feed created with resource name: '%s'.\n",
    $response->{results}[0]{resourceName};
}

# Gets the feed attribute ID for the retail chain ID.
sub get_attribute_id_for_chain_id {
  my ($feed_mapping) = @_;

  foreach my $field_mapping (@{$feed_mapping->{attributeFieldMappings}}) {
    if ($field_mapping->{affiliateLocationField} eq CHAIN_ID) {
      return $field_mapping->{feedAttributeId};
    }
  }

  die "Affiliate location feed mapping isn't setup correctly.";
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s"           => \$customer_id,
  "chain_id=i"              => \$chain_id,
  "campaign_id=i"           => \$campaign_id,
  "delete_existing_feeds=i" => \$delete_existing_feeds,
);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2)
  if not check_params($customer_id, $chain_id, $campaign_id);

# Call the example.
add_affiliate_location_extensions($api_client, $customer_id =~ s/-//gr,
  $chain_id, $campaign_id, $delete_existing_feeds);

=pod

=head1 NAME

add_affiliate_location_extensions

=head1 DESCRIPTION

This code example adds a feed that syncs retail addresses for a given retail
chain ID and associates the feed with a campaign for serving affiliate location
extensions.

=head1 SYNOPSIS

add_affiliate_location_extensions.pl [options]

    -help                               Show the help message.
    -customer_id                        The Google Ads customer ID.
    -chain_id                           The retail chain ID.
    -campaign_id                        The campaign ID for which the affiliate
                                        location extensions are added.
    -delete_existing_feeds              [optional] Non-zero if it should delete
                                        the existing feeds.

=cut