Product Ads Code Example

This example demonstrates how to apply product conditions for Product Ads in Microsoft Shopping Campaigns.

Tip

Use the language selector in the documentation header to choose C#, Java, Php, or Python.

To get access and refresh tokens for your Microsoft Advertising user and make your first service call using the Bing Ads API, see the Quick Start guide. You'll want to review the Get Started guide and walkthroughs for your preferred language e.g., C#, Java, Php, and Python.

Supporting files for C#, Java, Php, and Python examples are available at GitHub. You can clone each repository or repurpose snippets as needed.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;
using Microsoft.BingAds.V13.CampaignManagement;
using Microsoft.BingAds;

namespace BingAdsExamplesLibrary.V13
{
    /// <summary>
    /// How to apply product conditions for Microsoft Shopping Campaigns.
    /// </summary>
    public class ProductAds : ExampleBase
    {
        public override string Description
        {
            get { return "Microsoft Shopping Campaigns | Campaign Management V13"; }
        }

        public async override Task RunAsync(AuthorizationData authorizationData)
        {
            try
            {
                ApiEnvironment environment = ((OAuthDesktopMobileAuthCodeGrant)authorizationData.Authentication).Environment;

                CampaignManagementExampleHelper CampaignManagementExampleHelper = new CampaignManagementExampleHelper(
                    OutputStatusMessageDefault: this.OutputStatusMessage);
                CampaignManagementExampleHelper.CampaignManagementService = new ServiceClient<ICampaignManagementService>(
                    authorizationData: authorizationData,
                    environment: environment);

                // Get a list of all Bing Merchant Center stores associated with your CustomerId.

                OutputStatusMessage("-----\nGetBMCStoresByCustomerId:");
                IList<BMCStore> stores = (await CampaignManagementExampleHelper.GetBMCStoresByCustomerIdAsync(null))?.BMCStores;
                if (stores == null || stores.Count <= 0)
                {
                    OutputStatusMessage(
                        string.Format("You do not have any BMC stores registered for CustomerId {0}.", authorizationData.CustomerId)
                    );
                    return;
                }

                OutputStatusMessage("BMCStores:");
                CampaignManagementExampleHelper.OutputArrayOfBMCStore(stores);

                // Create a Shopping campaign with product conditions.

                var campaigns = new[] {
                    new Campaign
                    {
                        CampaignType = CampaignType.Shopping,
                        Languages = new string[] { "All" },
                        Name = "Everyone's Shoes " + DateTime.UtcNow,
                        DailyBudget = 50,
                        BudgetType = BudgetLimitType.DailyBudgetStandard,
                        Settings = new[] {
                            new ShoppingSetting() {
                                Priority = 0,
                                SalesCountryCode = "US",
                                StoreId = (int)stores[0].Id
                            }
                        },
                        TimeZone = "PacificTimeUSCanadaTijuana",
                    }
                };

                OutputStatusMessage("-----\nAddCampaigns:");
                AddCampaignsResponse addCampaignsResponse = await CampaignManagementExampleHelper.AddCampaignsAsync(
                    accountId: authorizationData.AccountId,
                    campaigns: campaigns);
                long?[] campaignIds = addCampaignsResponse.CampaignIds.ToArray();
                BatchError[] campaignErrors = addCampaignsResponse.PartialErrors.ToArray();
                OutputStatusMessage("CampaignIds:");
                CampaignManagementExampleHelper.OutputArrayOfLong(campaignIds);
                OutputStatusMessage("PartialErrors:");
                CampaignManagementExampleHelper.OutputArrayOfBatchError(campaignErrors);
                long campaignId = (long)campaignIds[0];

                // Optionally, you can create a ProductScope criterion that will be associated with your Microsoft Shopping campaign. 
                // You'll also be able to add more specific product conditions for each ad group.

                var campaignCriterions = new BiddableCampaignCriterion[] {
                    new BiddableCampaignCriterion() {
                        CampaignId = campaignId,
                        CriterionBid = null,  // Not applicable for product scope
                        Criterion = new ProductScope() {
                            Conditions = new ProductCondition[] {
                                new ProductCondition {
                                    Operand = "Condition",
                                    Attribute = "New"
                                },
                                new ProductCondition {
                                    Operand = "CustomLabel0",
                                    Attribute = "MerchantDefinedCustomLabel"
                                },
                            }
                        },
                        Status = CampaignCriterionStatus.Active
                    }
                };

                OutputStatusMessage("-----\nAddCampaignCriterions:");
                var addCampaignCriterionsResponse = await CampaignManagementExampleHelper.AddCampaignCriterionsAsync(
                    campaignCriterions: campaignCriterions,
                    criterionType: CampaignCriterionType.ProductScope);
                long?[] campaignCriterionIds = addCampaignCriterionsResponse.CampaignCriterionIds.ToArray();
                OutputStatusMessage("CampaignCriterionIds:");
                CampaignManagementExampleHelper.OutputArrayOfLong(campaignCriterionIds);
                BatchErrorCollection[] campaignCriterionErrors =
                    addCampaignCriterionsResponse.NestedPartialErrors.ToArray();
                OutputStatusMessage("NestedPartialErrors:");
                CampaignManagementExampleHelper.OutputArrayOfBatchErrorCollection(campaignCriterionErrors);

                // Create the ad group that will have the product partitions.

                var adGroups = new[] {
                    new AdGroup
                    {
                        Name = "Everyone's Red Shoe Sale",
                        StartDate = null,
                        EndDate = new Date {
                            Month = 12,
                            Day = 31,
                            Year = DateTime.UtcNow.Year + 1
                        },
                        CpcBid = new Bid { Amount = 0.09 },
                    }
                };

                OutputStatusMessage("-----\nAddAdGroups:");
                AddAdGroupsResponse addAdGroupsResponse = await CampaignManagementExampleHelper.AddAdGroupsAsync(
                    campaignId: (long)campaignIds[0],
                    adGroups: adGroups,
                    returnInheritedBidStrategyTypes: false);
                long?[] adGroupIds = addAdGroupsResponse.AdGroupIds.ToArray();
                BatchError[] adGroupErrors = addAdGroupsResponse.PartialErrors.ToArray();
                OutputStatusMessage("AdGroupIds:");
                CampaignManagementExampleHelper.OutputArrayOfLong(adGroupIds);
                OutputStatusMessage("PartialErrors:");
                CampaignManagementExampleHelper.OutputArrayOfBatchError(adGroupErrors);
                long adGroupId = (long)adGroupIds[0];

                // Bid all products

                var helper = new PartitionActionHelper(adGroupId);

                var root = helper.AddUnit(
                    parent: null,
                    condition: new ProductCondition { Operand = "All", Attribute = null },
                    bidAmount: 0.35,
                    isNegative: false);

                OutputStatusMessage("-----\nApplyProductPartitionActions:");
                OutputStatusMessage("Applying only the root as a Unit with a bid...");
                var applyProductPartitionActionsResponse = await CampaignManagementExampleHelper.ApplyProductPartitionActionsAsync(
                    criterionActions: helper.PartitionActions);

                OutputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
                var adGroupCriterions = await CampaignManagementExampleHelper.GetAdGroupCriterionsByIdsAsync(
                    adGroupCriterionIds: null,
                    adGroupId: adGroupId,
                    criterionType: AdGroupCriterionType.ProductPartition,
                    null);

                OutputStatusMessage("The ad group's product partition only has a tree root node: \n");
                OutputProductPartitions(adGroupCriterions?.AdGroupCriterions);

                // Let's update the bid of the root Unit we just added.

                BiddableAdGroupCriterion updatedRoot = new BiddableAdGroupCriterion
                {
                    Id = applyProductPartitionActionsResponse.AdGroupCriterionIds[0],
                    CriterionBid = new FixedBid
                    {
                        Amount = 0.45
                    }
                };

                helper = new PartitionActionHelper(adGroupId);
                helper.UpdatePartition(updatedRoot);

                OutputStatusMessage("-----\nApplyProductPartitionActions:");
                OutputStatusMessage("Updating the bid for the tree root node...");
                await CampaignManagementExampleHelper.ApplyProductPartitionActionsAsync(
                    criterionActions: helper.PartitionActions);

                OutputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
                adGroupCriterions = await CampaignManagementExampleHelper.GetAdGroupCriterionsByIdsAsync(
                    adGroupCriterionIds: null,
                    adGroupId: adGroupId,
                    criterionType: AdGroupCriterionType.ProductPartition,
                    null);

                OutputStatusMessage("Updated the bid for the tree root node: \n");
                OutputProductPartitions(adGroupCriterions?.AdGroupCriterions);
                
                // Initialize and overwrite any existing tree root, and build a product partition group tree structure in multiple steps. 
                // You could build the entire tree in a single call since there are less than 5,000 nodes; however, 
                // we will build it in steps to demonstrate how to use the results from ApplyProductPartitionActions to update the tree. 

                helper = new PartitionActionHelper(adGroupId);

                // Check whether a root node exists already.

                OutputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
                adGroupCriterions = await CampaignManagementExampleHelper.GetAdGroupCriterionsByIdsAsync(
                    adGroupCriterionIds: null,
                    adGroupId: adGroupId,
                    criterionType: AdGroupCriterionType.ProductPartition,
                    null);

                var existingRoot = GetRootNode(adGroupCriterions?.AdGroupCriterions);
                if (existingRoot != null)
                {
                    helper.DeletePartition(existingRoot);
                }

                root = helper.AddSubdivision(
                    null,
                    new ProductCondition { Operand = "All", Attribute = null }
                );
                
                // The direct children of any node must have the same Operand. 
                // For this example we will use CategoryL1 nodes as children of the root. 
                // For a list of valid CategoryL1 through CategoryL5 values, see the Bing Category Taxonomy:
                // https://go.microsoft.com/fwlink?LinkId=507666

                var animalsSubdivision = helper.AddSubdivision(
                    parent: root,
                    condition: new ProductCondition { Operand = "CategoryL1", Attribute = "Animals & Pet Supplies" });

                // If you use a CategoryL2 node, it must be a descendant (child or later) of a CategoryL1 node. 
                // In other words you cannot have a CategoryL2 node as parent of a CategoryL1 node. 
                // For this example we will a CategoryL2 node as child of the CategoryL1 Animals & Pet Supplies node. 

                var petSuppliesSubdivision = helper.AddSubdivision(
                    parent: animalsSubdivision,
                    condition: new ProductCondition { Operand = "CategoryL2", Attribute = "Pet Supplies" });

                var brandA = helper.AddUnit(
                    parent: petSuppliesSubdivision,
                    condition: new ProductCondition { Operand = "Brand", Attribute = "Brand A" },
                    bidAmount: 0.35,
                    isNegative: false);
                                
                // If you won't bid on Brand B, set the helper method's bidAmount to '0' and isNegative to true. 
                // The helper method will create a NegativeAdGroupCriterion and apply the condition.
                
                var brandB = helper.AddUnit(
                    parent: petSuppliesSubdivision,
                    condition: new ProductCondition { Operand = "Brand", Attribute = "Brand B" },
                    bidAmount: 0,
                    isNegative: true);

                var otherBrands = helper.AddUnit(
                    parent: petSuppliesSubdivision,
                    condition: new ProductCondition { Operand = "Brand", Attribute = null },
                    bidAmount: 0.35,
                    isNegative: false);

                var otherPetSupplies = helper.AddUnit(
                    parent: animalsSubdivision,
                    condition: new ProductCondition { Operand = "CategoryL2", Attribute = null },
                    bidAmount: 0.35,
                    isNegative: false);

                var electronics = helper.AddUnit(
                    parent: root,
                    condition: new ProductCondition { Operand = "CategoryL1", Attribute = "Electronics" },
                    bidAmount: 0.35,
                    isNegative: false);

                var otherCategoryL1 = helper.AddUnit(
                    root,
                    new ProductCondition { Operand = "CategoryL1", Attribute = null },
                    0.35,
                    false
                );

                OutputStatusMessage("-----\nApplyProductPartitionActions:");
                OutputStatusMessage("Applying product partitions to the ad group...");
                applyProductPartitionActionsResponse = await CampaignManagementExampleHelper.ApplyProductPartitionActionsAsync(
                    criterionActions: helper.PartitionActions);

                // To retrieve product partitions after they have been applied, call GetAdGroupCriterionsByIds. 
                // The product partition with ParentCriterionId set to null is the root node.

                OutputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
                adGroupCriterions = await CampaignManagementExampleHelper.GetAdGroupCriterionsByIdsAsync(
                    adGroupCriterionIds: null,
                    adGroupId: adGroupId,
                    criterionType: AdGroupCriterionType.ProductPartition,
                    null);
                
                // The product partition group tree now has 9 nodes. 
                 
                //All other (Root Node)
                // |
                // +-- Animals & Pet Supplies (CategoryL1)
                // |    |
                // |    +-- Pet Supplies (CategoryL2)
                // |    |    |
                // |    |    +-- Brand A
                // |    |    |    
                // |    |    +-- Brand B
                // |    |    |    
                // |    |    +-- All other (Brand)
                // |    |         
                // |    +-- All other (CategoryL2)
                // |        
                // +-- Electronics (CategoryL1)
                // |   
                // +-- All other (CategoryL1)
                
                OutputStatusMessage("The product partition group tree now has 9 nodes: \n");
                OutputProductPartitions(adGroupCriterions?.AdGroupCriterions);
                
                // Let's replace the Electronics (CategoryL1) node created above with an Electronics (CategoryL1) node that 
                // has children i.e. Brand C (Brand), Brand D (Brand), and All other (Brand) as follows: 
                 
                //Electronics (CategoryL1)
                //|
                //+-- Brand C (Brand)
                //|
                //+-- Brand D (Brand)
                //|
                //+-- All other (Brand)
           
                helper = new PartitionActionHelper(adGroupId);

                // To replace a node we must know its Id and its ParentCriterionId. In this case the parent of the node 
                // we are replacing is All other (Root Node), and was created at Index 1 of the previous ApplyProductPartitionActions call. 
                // The node that we are replacing is Electronics (CategoryL1), and was created at Index 8. 

                var rootId = applyProductPartitionActionsResponse.AdGroupCriterionIds[1];
                electronics.Id = applyProductPartitionActionsResponse.AdGroupCriterionIds[8];
                helper.DeletePartition(electronics);

                var parent = new BiddableAdGroupCriterion() { Id = rootId };

                var electronicsSubdivision = helper.AddSubdivision(
                    parent: parent,
                    condition: new ProductCondition { Operand = "CategoryL1", Attribute = "Electronics" }
                );

                var brandC = helper.AddUnit(
                    parent: electronicsSubdivision,
                    condition: new ProductCondition { Operand = "Brand", Attribute = "Brand C" },
                    bidAmount: 0.35,
                    isNegative: false);

                var brandD = helper.AddUnit(
                    parent: electronicsSubdivision,
                    condition: new ProductCondition { Operand = "Brand", Attribute = "Brand D" },
                    bidAmount: 0.35,
                    isNegative: false);

                var otherElectronicsBrands = helper.AddUnit(
                    parent: electronicsSubdivision,
                    condition: new ProductCondition { Operand = "Brand", Attribute = null },
                    bidAmount: 0.35,
                    isNegative: false);

                OutputStatusMessage("-----\nApplyProductPartitionActions:");
                OutputStatusMessage(
                    "Updating the product partition group to refine Electronics (CategoryL1) with 3 child nodes..."
                );
                applyProductPartitionActionsResponse = await CampaignManagementExampleHelper.ApplyProductPartitionActionsAsync(
                    criterionActions: helper.PartitionActions);

                OutputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
                adGroupCriterions = await CampaignManagementExampleHelper.GetAdGroupCriterionsByIdsAsync(
                    adGroupCriterionIds: null,
                    adGroupId: adGroupId,
                    criterionType: AdGroupCriterionType.ProductPartition,
                    null);
                
                // The product partition group tree now has 12 nodes, including the children of Electronics (CategoryL1):
                 
                //All other (Root Node)
                // |
                // +-- Animals & Pet Supplies (CategoryL1)
                // |    |
                // |    +-- Pet Supplies (CategoryL2)
                // |    |    |
                // |    |    +-- Brand A
                // |    |    |    
                // |    |    +-- Brand B
                // |    |    |    
                // |    |    +-- All other (Brand)
                // |    |         
                // |    +-- All other (CategoryL2)
                // |        
                // +-- Electronics (CategoryL1)
                // |    |
                // |    +-- Brand C (Brand)
                // |    |
                // |    +-- Brand D (Brand)
                // |    |
                // |    +-- All other (Brand)
                // |   
                // +-- All other (CategoryL1)

                OutputStatusMessage(
                    "The product partition group tree now has 12 nodes, including the children of Electronics (CategoryL1): \n"
                );
                OutputProductPartitions(adGroupCriterions?.AdGroupCriterions);
                
                // Create a product ad. You must add at least one product ad to the ad group. 
                // The product ad identifier can be used for reporting analytics.
                // Use Merchant Promotions if you want tags to appear at the bottom of your product ad 
                // as "special offer" links, helping to increase customer engagement. For details
                // on Merchant Promotions see https://help.bingads.microsoft.com/#apex/3/en/56805/0.

                var ads = new Ad[] {
                    new ProductAd {}
                };

                OutputStatusMessage("-----\nAddAds:");
                AddAdsResponse addAdsResponse = await CampaignManagementExampleHelper.AddAdsAsync(
                    adGroupId: (long)adGroupIds[0],
                    ads: ads);
                long?[] adIds = addAdsResponse.AdIds.ToArray();
                BatchError[] adErrors = addAdsResponse.PartialErrors.ToArray();
                OutputStatusMessage("AdIds:");
                CampaignManagementExampleHelper.OutputArrayOfLong(adIds);
                OutputStatusMessage("PartialErrors:");
                CampaignManagementExampleHelper.OutputArrayOfBatchError(adErrors);

                // Delete the campaign and everything it contains e.g., ad groups and ads.

                OutputStatusMessage("-----\nDeleteCampaigns:");
                await CampaignManagementExampleHelper.DeleteCampaignsAsync(
                    accountId: authorizationData.AccountId,
                    campaignIds: new[] { (long)campaignIds[0] });
                OutputStatusMessage(string.Format("Deleted Campaign Id {0}", campaignIds[0]));
            }
            // Catch authentication exceptions
            catch (OAuthTokenRequestException ex)
            {
                OutputStatusMessage(string.Format("Couldn't get OAuth tokens. Error: {0}. Description: {1}", ex.Details.Error, ex.Details.Description));
            }
            // Catch Campaign Management service exceptions
            catch (FaultException<Microsoft.BingAds.V13.CampaignManagement.AdApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.Errors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException<Microsoft.BingAds.V13.CampaignManagement.ApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (FaultException<Microsoft.BingAds.V13.CampaignManagement.EditorialApiFaultDetail> ex)
            {
                OutputStatusMessage(string.Join("; ", ex.Detail.OperationErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
                OutputStatusMessage(string.Join("; ", ex.Detail.BatchErrors.Select(error => string.Format("{0}: {1}", error.Code, error.Message))));
            }
            catch (Exception ex)
            {
                OutputStatusMessage(ex.Message);
            }
        }

        /// <summary>
        /// Returns the root node of a tree. This operation assumes that a complete 
        /// product partition tree is provided for one ad group. The node that has
        /// null ParentCriterionId is the root node.
        /// </summary>
        /// <param name="adGroupCriterions">The ad group criterions that contain 
        /// the product partition tree.</param>
        /// <returns>The ad group criterion that represents the tree root node.</returns>
        private AdGroupCriterion GetRootNode(IList<AdGroupCriterion> adGroupCriterions)
        {
            AdGroupCriterion rootNode = null;
            foreach (AdGroupCriterion adGroupCriterion in adGroupCriterions)
            {
                if (((ProductPartition)(adGroupCriterion.Criterion)).ParentCriterionId == null)
                {
                    rootNode = adGroupCriterion;
                    break;
                }
            }
            return rootNode;
        }

        /// <summary>
        /// Helper class used to maintain a list of product partition actions for an ad group.
        /// The list of partition actions can be passed to the ApplyProductPartitionActions service operation.
        /// </summary>
        private class PartitionActionHelper
        {
            /// <summary>
            /// Each criterion is associated with the same ad group.
            /// </summary>
            private long adGroupId;

            /// <summary>
            /// Each new subdivision will be assigned a temporary negative identifier, since it does not exist 
            /// and does not yet have a Microsoft Advertising system identifier. This identifier will be used as the ParentCriterionId 
            /// for any child node of the subdivision. 
            /// </summary>
            private long referenceId = -1;

            /// <summary>
            /// The list of partition actions that can be passed to the ApplyProductPartitionActions service operation.
            /// </summary>
            private List<AdGroupCriterionAction> partitionActions = new List<AdGroupCriterionAction>();

            /// <summary>
            /// Initializes an instance of the PartitionActionHelper class.
            /// </summary>
            /// <param name="adGroupId">The ad group identifier associated with each criterion.</param>
            public PartitionActionHelper(long adGroupId)
            {
                this.adGroupId = adGroupId;
            }

            /// <summary>
            /// Returns the list of partition actions that can be passed to the ApplyProductPartitionActions service operation.
            /// </summary>
            public IList<AdGroupCriterionAction> PartitionActions
            {
                get
                {
                    return partitionActions;
                }
            }

            /// <summary>
            /// Sets the Add action for a new BiddableAdGroupCriterion corresponding to the specified ProductCondition, 
            /// and adds it to the helper's list of AdGroupCriterionAction. 
            /// </summary>
            /// <param name="parent">The parent of the product partition subdivision that you want to add.</param>
            /// <param name="condition">The condition or product filter for the new product partition.</param>
            /// <returns>The ad group criterion that was added to the list of PartitionActions.</returns>
            public AdGroupCriterion AddSubdivision(
                AdGroupCriterion parent,
                ProductCondition condition
                )
            {
                var biddableAdGroupCriterion = new BiddableAdGroupCriterion()
                {
                    Id = this.referenceId--,
                    Criterion = new ProductPartition()
                    {
                        // If the root node is a unit, it would not have a parent
                        ParentCriterionId = parent != null ? parent.Id : null,
                        Condition = condition,
                        PartitionType = ProductPartitionType.Subdivision
                    },
                    CriterionBid = null,
                    AdGroupId = this.adGroupId
                };

                var partitionAction = new AdGroupCriterionAction()
                {
                    Action = ItemAction.Add,
                    AdGroupCriterion = biddableAdGroupCriterion
                };

                this.partitionActions.Add(partitionAction);

                return biddableAdGroupCriterion;
            }

            /// <summary>
            /// Sets the Add action for a new AdGroupCriterion corresponding to the specified ProductCondition, 
            /// and adds it to the helper's list of AdGroupCriterionAction. 
            /// </summary>
            /// <param name="parent">The parent of the product partition unit that you want to add.</param>
            /// <param name="condition">The condition or product filter for the new product partition.</param>
            /// <param name="bidAmount">The bid amount for the new product partition.</param>
            /// <param name="isNegative">Indicates whether or not to add a NegativeAdGroupCriterion. 
            /// The default value is false, in which case a BiddableAdGroupCriterion will be added.</param>
            /// <returns>The ad group criterion that was added to the list of PartitionActions.</returns>
            public AdGroupCriterion AddUnit(
                AdGroupCriterion parent,
                ProductCondition condition,
                double bidAmount,
                bool isNegative
                )
            {
                AdGroupCriterion adGroupCriterion;

                if (isNegative)
                {
                    adGroupCriterion = new NegativeAdGroupCriterion();
                }
                else
                {
                    adGroupCriterion = new BiddableAdGroupCriterion()
                    {
                        CriterionBid = new FixedBid()
                        {
                            Amount = bidAmount
                        },

                        // This destination URL is used if specified; otherwise, the destination URL is determined 
                        // by the corresponding value of the 'Link' that you specified for the product offer 
                        // in your Bing Merchant Center catalog.
                        DestinationUrl = null,

                        // You could use a tracking template which would override the campaign level
                        // tracking template. Tracking templates defined for lower level entities 
                        // override those set for higher level entities.
                        // In this example we are using the campaign level tracking template.
                        TrackingUrlTemplate = null,

                        // Set custom parameters that are specific to this criterion, 
                        // and can be used by the criterion, ad group, campaign, or account level tracking template. 
                        // In this example we are using the campaign level tracking template.
                        UrlCustomParameters = new CustomParameters
                        {
                            Parameters = new[] {
                                new CustomParameter(){
                                    Key = "promoCode",
                                    Value = "PROMO1"
                                },
                                new CustomParameter(){
                                    Key = "season",
                                    Value = "summer"
                                },
                            }
                        }
                    };
                }

                adGroupCriterion.Criterion = new ProductPartition()
                {
                    // If the root node is a unit, it would not have a parent
                    ParentCriterionId = parent != null ? parent.Id : null,
                    Condition = condition,
                    PartitionType = ProductPartitionType.Unit
                };

                adGroupCriterion.AdGroupId = this.adGroupId;

                var partitionAction = new AdGroupCriterionAction()
                {
                    Action = ItemAction.Add,
                    AdGroupCriterion = adGroupCriterion
                };

                this.partitionActions.Add(partitionAction);

                return adGroupCriterion;
            }

            /// <summary>
            /// Sets the Delete action for the specified AdGroupCriterion, 
            /// and adds it to the helper's list of AdGroupCriterionAction. 
            /// </summary>
            /// <param name="adGroupCriterion">The ad group criterion whose product partition you want to delete.</param>
            public void DeletePartition(AdGroupCriterion adGroupCriterion)
            {
                adGroupCriterion.AdGroupId = this.adGroupId;

                var partitionAction = new AdGroupCriterionAction()
                {
                    Action = ItemAction.Delete,
                    AdGroupCriterion = adGroupCriterion
                };

                this.partitionActions.Add(partitionAction);

                return;
            }

            /// <summary>
            /// Sets the Update action for the specified BiddableAdGroupCriterion, 
            /// and adds it to the helper's list of AdGroupCriterionAction. 
            /// You can only update the CriterionBid and DestinationUrl elements 
            /// of the BiddableAdGroupCriterion. 
            /// When working with product partitions, youu cannot update the Criterion (ProductPartition). 
            /// To update a ProductPartition, you must delete the existing node (DeletePartition) and 
            /// add a new one (AddUnit or AddSubdivision) during the same call to ApplyProductPartitionActions. 
            /// </summary>
            /// <param name="biddableAdGroupCriterion">The biddable ad group criterion to update.</param>
            public void UpdatePartition(BiddableAdGroupCriterion biddableAdGroupCriterion)
            {
                biddableAdGroupCriterion.AdGroupId = this.adGroupId;

                var partitionAction = new AdGroupCriterionAction()
                {
                    Action = ItemAction.Update,
                    AdGroupCriterion = biddableAdGroupCriterion
                };

                this.partitionActions.Add(partitionAction);

                return;
            }
        }

        /// <summary>
        /// Outputs the list of AdGroupCriterion, formatted as a tree. 
        /// Each AdGroupCriterion must be either a BiddableAdGroupCriterion or NegativeAdGroupCriterion. 
        /// To ensure the complete tree is represented, you should first call GetAdGroupCriterionsByIds 
        /// where CriterionTypeFilter is ProductPartition, and pass the returned list of AdGroupCriterion to this method. 
        /// </summary>
        /// <param name="adGroupCriterions">The list of ad group criterions to output formatted as a tree.</param>
        private void OutputProductPartitions(IList<AdGroupCriterion> adGroupCriterions)
        {
            // Set up the tree for output

            Dictionary<long, List<AdGroupCriterion>> childBranches = new Dictionary<long, List<AdGroupCriterion>>();
            AdGroupCriterion treeRoot = null;

            foreach (var adGroupCriterion in adGroupCriterions)
            {
                ProductPartition partition = (ProductPartition)adGroupCriterion.Criterion;
                childBranches[(long)(adGroupCriterion.Id)] = new List<AdGroupCriterion>();

                // The product partition with ParentCriterionId set to null is the root node.
                if (partition.ParentCriterionId != null)
                {
                    childBranches[(long)(partition.ParentCriterionId)].Add(adGroupCriterion);
                }
                else
                {
                    treeRoot = adGroupCriterion;
                }
            }

            // Outputs the tree root node and any children recursively
            OutputProductPartitionTree(treeRoot, childBranches, 0);
        }

        /// <summary>
        /// Outputs the details of the specified product partition node, 
        /// and passes any children to itself recursively.
        /// </summary>
        /// <param name="node">The node to output, whether a Subdivision or Unit.</param>
        /// <param name="childBranches">The child branches or nodes if any exist.</param>
        /// <param name="treeLevel">
        /// The number of descendents from the tree root node. 
        /// Used by this operation to format the tree structure output.
        /// </param>
        private void OutputProductPartitionTree(
            AdGroupCriterion node,
            Dictionary<long, List<AdGroupCriterion>> childBranches,
            int treeLevel)
        {
            OutputStatusMessage(string.Format("{0}{1}",
                "".PadLeft(treeLevel, '\t'),
                ((ProductPartition)(node.Criterion)).PartitionType)
            );

            OutputStatusMessage(string.Format("{0}ParentCriterionId: {1}",
                "".PadLeft(treeLevel, '\t'),
                ((ProductPartition)(node.Criterion)).ParentCriterionId)
            );

            OutputStatusMessage(string.Format("{0}Id: {1}",
                "".PadLeft(treeLevel, '\t'),
                node.Id)
            );

            if (((ProductPartition)(node.Criterion)).PartitionType == ProductPartitionType.Unit)
            {
                var biddableAdGroupCriterion = node as BiddableAdGroupCriterion;
                if (biddableAdGroupCriterion != null)
                {
                    OutputStatusMessage(string.Format("{0}Bid Amount: {1}",
                        "".PadLeft(treeLevel, '\t'),
                        ((FixedBid)(biddableAdGroupCriterion.CriterionBid)).Amount)
                    );
                    OutputStatusMessage(string.Format("{0}DestinationUrl: {1}",
                        "".PadLeft(treeLevel, '\t'),
                        biddableAdGroupCriterion.DestinationUrl)
                    );
                    OutputStatusMessage(string.Format("{0}TrackingUrlTemplate: {1}",
                        "".PadLeft(treeLevel, '\t'),
                        biddableAdGroupCriterion.TrackingUrlTemplate)
                    );
                }
                else
                {
                    var negativeAdGroupCriterion = node as NegativeAdGroupCriterion;
                    if (negativeAdGroupCriterion != null)
                    {
                        OutputStatusMessage(string.Format("{0}Not Bidding on this Condition",
                            "".PadLeft(treeLevel, '\t'))
                        );
                    }
                }
            }

            var nullAttribute = ((ProductPartition)(node.Criterion)).ParentCriterionId != null ? "(All other)" : "(Tree Root)";
            OutputStatusMessage(string.Format("{0}Attribute: {1}",
                "".PadLeft(treeLevel, '\t'),
                ((ProductPartition)(node.Criterion)).Condition.Attribute ?? nullAttribute)
            );

            OutputStatusMessage(string.Format("{0}Operand: {1}\n",
                "".PadLeft(treeLevel, '\t'),
                ((ProductPartition)(node.Criterion)).Condition.Operand)
            );

            foreach (AdGroupCriterion childNode in childBranches[(long)(node.Id)])
            {
                OutputProductPartitionTree(childNode, childBranches, treeLevel + 1);
            }
        }

    }
}
package com.microsoft.bingads.examples.v13;

import java.rmi.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import com.microsoft.bingads.*;
import com.microsoft.bingads.v13.campaignmanagement.*;

// How to apply product conditions for Microsoft Shopping Campaigns.

public class ProductAds extends ExampleBase {
        
    private static ArrayOfAdGroupCriterionAction _partitionActions = new ArrayOfAdGroupCriterionAction();
    private static long _referenceId = -1;
        
    final static long campaignIdKey = -123; 
    final static long adGroupIdKey = -1234; 
    
    public static void main(java.lang.String[] args) {
     
        try
        {
            authorizationData = getAuthorizationData();

            CampaignManagementExampleHelper.CampaignManagementService = new ServiceClient<ICampaignManagementService>(
                        authorizationData, 
                        API_ENVIRONMENT,
                        ICampaignManagementService.class);

            // Get a list of all Bing Merchant Center stores associated with your CustomerId.

            outputStatusMessage("-----\nGetBMCStoresByCustomerId:");
            final ArrayOfBMCStore stores = CampaignManagementExampleHelper.getBMCStoresByCustomerId(null).getBMCStores();

            if (stores == null)
            {
                outputStatusMessage(String.format("You do not have any BMC stores registered for CustomerId %d.", authorizationData.getCustomerId()));
                return;
            }
            
            outputStatusMessage("BMCStores:");
            CampaignManagementExampleHelper.outputArrayOfBMCStore(stores);

            // Create a Shopping campaign with product conditions.

            Campaign campaign = new Campaign();
            campaign.setBudgetType(BudgetLimitType.DAILY_BUDGET_STANDARD);
            ArrayList<CampaignType> campaignTypes = new ArrayList<CampaignType>();
            campaignTypes.add(CampaignType.SHOPPING);
            campaign.setCampaignType(campaignTypes);
            campaign.setDailyBudget(50.00);
            ArrayOfstring languages = new ArrayOfstring();
            languages.getStrings().add("All");
            campaign.setLanguages(languages);
            campaign.setName("Everyone's Shoes " + System.currentTimeMillis());            
            ArrayOfSetting settings = new ArrayOfSetting();
            ShoppingSetting shoppingSetting = new ShoppingSetting();
            shoppingSetting.setPriority(0);
            shoppingSetting.setSalesCountryCode("US");
            shoppingSetting.setStoreId(stores.getBMCStores().get(0).getId());
            settings.getSettings().add(shoppingSetting);
            campaign.setSettings(settings);            
            campaign.setTimeZone("PacificTimeUSCanadaTijuana");
            ArrayOfCampaign campaigns = new ArrayOfCampaign();
            campaigns.getCampaigns().add(campaign);

            outputStatusMessage("-----\nAddCampaigns:");
            AddCampaignsResponse addCampaignsResponse = CampaignManagementExampleHelper.addCampaigns(
                    authorizationData.getAccountId(), 
                    campaigns);            
            ArrayOfNullableOflong campaignIds = addCampaignsResponse.getCampaignIds();
            ArrayOfBatchError campaignErrors = addCampaignsResponse.getPartialErrors();
            outputStatusMessage("CampaignIds:");
            CampaignManagementExampleHelper.outputArrayOfNullableOflong(campaignIds);
            outputStatusMessage("PartialErrors:");
            CampaignManagementExampleHelper.outputArrayOfBatchError(campaignErrors);
            
            // Optionally, you can create a ProductScope criterion that will be associated with your Microsoft Shopping campaign. 
            // You'll also be able to add more specific product conditions for each ad group.

            ArrayList<CampaignCriterionType> criterionType = new ArrayList<CampaignCriterionType>();
            criterionType.add(CampaignCriterionType.PRODUCT_SCOPE);

            BiddableCampaignCriterion campaignCriterion = new BiddableCampaignCriterion();
            campaignCriterion.setCampaignId(campaignIds.getLongs().get(0));
            ProductScope criterion = new ProductScope();
            ArrayOfProductCondition conditions = new ArrayOfProductCondition();
            ProductCondition condition1 = new ProductCondition();
            condition1.setAttribute("New");
            condition1.setOperand("Condition");
            conditions.getProductConditions().add(condition1);
            ProductCondition condition2 = new ProductCondition();
            condition2.setAttribute("MerchantDefinedCustomLabel");
            condition2.setOperand("CustomLabel0");
            conditions.getProductConditions().add(condition2);
            criterion.setConditions(conditions);
            campaignCriterion.setCriterion(criterion);
            ArrayOfCampaignCriterion campaignCriterions = new ArrayOfCampaignCriterion();
            campaignCriterions.getCampaignCriterions().add(campaignCriterion);

            outputStatusMessage("-----\nAddCampaignCriterions:");
            AddCampaignCriterionsResponse addCriterionResponse = CampaignManagementExampleHelper.addCampaignCriterions(
                    campaignCriterions, 
                    criterionType);
            outputStatusMessage("CampaignCriterionIds:");
            CampaignManagementExampleHelper.outputArrayOfNullableOflong(addCriterionResponse.getCampaignCriterionIds());
            outputStatusMessage("NestedPartialErrors:");
            CampaignManagementExampleHelper.outputArrayOfBatchErrorCollection(addCriterionResponse.getNestedPartialErrors());

            // Create the ad group that will have the product partitions.
            
            ArrayOfAdGroup adGroups = new ArrayOfAdGroup();
            AdGroup adGroup = new AdGroup();
            adGroup.setName("Everyone's Red Shoe Sale");
            adGroup.setStartDate(null);
            Calendar calendar = Calendar.getInstance();
            adGroup.setEndDate(new com.microsoft.bingads.v13.campaignmanagement.Date());
            adGroup.getEndDate().setDay(31);
            adGroup.getEndDate().setMonth(12);
            adGroup.getEndDate().setYear(calendar.get(Calendar.YEAR));
            Bid CpcBid = new Bid();
            CpcBid.setAmount(0.09);
            adGroup.setCpcBid(CpcBid);
            adGroups.getAdGroups().add(adGroup);

            outputStatusMessage("-----\nAddAdGroups:");
            AddAdGroupsResponse addAdGroupsResponse = CampaignManagementExampleHelper.addAdGroups(
                    campaignIds.getLongs().get(0), 
                    adGroups, 
                    false);
            ArrayOfNullableOflong adGroupIds = addAdGroupsResponse.getAdGroupIds();
            ArrayOfBatchError adGroupErrors = addAdGroupsResponse.getPartialErrors();
            outputStatusMessage("AdGroupIds:");
            CampaignManagementExampleHelper.outputArrayOfNullableOflong(adGroupIds);
            outputStatusMessage("PartialErrors:");
            CampaignManagementExampleHelper.outputArrayOfBatchError(adGroupErrors); 
            
            // Create an update the ad group product partitions.

            addAndUpdateAdGroupCriterion(adGroupIds.getLongs().get(0));
            ApplyProductPartitionActionsResponse applyPartitionActionsResponse = addBranchAndLeafCriterion(adGroupIds.getLongs().get(0));

            long rootId = applyPartitionActionsResponse.getAdGroupCriterionIds().getLongs().get(1);
            long electronicsCriterionId = applyPartitionActionsResponse.getAdGroupCriterionIds().getLongs().get(8);
            updateBranchAndLeafCriterion(adGroupIds.getLongs().get(0), rootId, electronicsCriterionId);

            // Create a product ad. You must add at least one product ad to the ad group. 
            // The product ad identifier can be used for reporting analytics.
            // Use Merchant Promotions if you want tags to appear at the bottom of your product ad 
            // as "special offer" links, helping to increase customer engagement. For details
            // on Merchant Promotions see https://help.bingads.microsoft.com/#apex/3/en/56805/0.

            ArrayOfAd ads = new ArrayOfAd();
            ProductAd productAd = new ProductAd();
            ads.getAds().add(productAd);
            
            outputStatusMessage("-----\nAddAds:");
            AddAdsResponse addAdsResponse = CampaignManagementExampleHelper.addAds(
                    adGroupIds.getLongs().get(0), 
                    ads);
            ArrayOfNullableOflong adIds = addAdsResponse.getAdIds();
            ArrayOfBatchError adErrors = addAdsResponse.getPartialErrors();
            outputStatusMessage("AdIds:");
            CampaignManagementExampleHelper.outputArrayOfNullableOflong(adIds);
            outputStatusMessage("PartialErrors:");
            CampaignManagementExampleHelper.outputArrayOfBatchError(adErrors);

            // Delete the campaign and everything it contains e.g., ad groups and ads.

            outputStatusMessage("-----\nDeleteCampaigns:");
            ArrayOflong deleteCampaignIds = new ArrayOflong();
            deleteCampaignIds.getLongs().add(campaignIds.getLongs().get(0));
            CampaignManagementExampleHelper.deleteCampaigns(
                    authorizationData.getAccountId(), 
                    deleteCampaignIds);
            outputStatusMessage(String.format("Deleted CampaignId %d", deleteCampaignIds.getLongs().get(0))); 
        } 
        catch (Exception ex) {
            String faultXml = ExampleExceptionHelper.getBingAdsExceptionFaultXml(ex, System.out);
            outputStatusMessage(faultXml);
            String message = ExampleExceptionHelper.handleBingAdsSDKException(ex, System.out);
            outputStatusMessage(message);
        }
    }
    
    // Add a criterion to the ad group and then update it. 

    static void addAndUpdateAdGroupCriterion(long adGroupId) throws RemoteException, Exception
    {
        // Add a biddable criterion as the root.

        ProductCondition rootCondition = new ProductCondition();
        rootCondition.setAttribute(null);
        rootCondition.setOperand("All");

        AdGroupCriterion root = addPartition(
                        adGroupId,
                        null, 
                        rootCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        outputStatusMessage("-----\nApplyProductPartitionActions:");
        outputStatusMessage("Applying a biddable criterion as the root...");
        ApplyProductPartitionActionsResponse applyPartitionActionsResponse = CampaignManagementExampleHelper.applyProductPartitionActions(
                _partitionActions);
        CampaignManagementExampleHelper.outputArrayOfNullableOflong(applyPartitionActionsResponse.getAdGroupCriterionIds());
        CampaignManagementExampleHelper.outputArrayOfBatchError(applyPartitionActionsResponse.getPartialErrors());

        ArrayList<AdGroupCriterionType> criterionType = new ArrayList<AdGroupCriterionType>();
        criterionType.add(AdGroupCriterionType.PRODUCT_PARTITION);
        
        outputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
        ArrayOfAdGroupCriterion adGroupCriterions = CampaignManagementExampleHelper.getAdGroupCriterionsByIds(
            null,
            adGroupId, 
            criterionType,
            null).getAdGroupCriterions();

        outputStatusMessage("Printing the ad group's product partition; contains only the tree root node");
        printProductPartitions(adGroupCriterions);

        // Update the bid of the root node that we just added.

        BiddableAdGroupCriterion updatedRoot = new BiddableAdGroupCriterion();
        updatedRoot.setId(applyPartitionActionsResponse.getAdGroupCriterionIds().getLongs().get(0));
        updatedRoot.setAdGroupId(adGroupId);
        updatedRoot.setCriterionBid(getFixedBid(0.40));

        _partitionActions.getAdGroupCriterionActions().clear();

        addPartitionAction(updatedRoot, ItemAction.UPDATE);

        outputStatusMessage("-----\nApplyProductPartitionActions:");
        outputStatusMessage("Updating the bid for the tree root node...");
        applyPartitionActionsResponse = CampaignManagementExampleHelper.applyProductPartitionActions(
                _partitionActions);

        criterionType.add(AdGroupCriterionType.PRODUCT_PARTITION);
        
        outputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
        adGroupCriterions = CampaignManagementExampleHelper.getAdGroupCriterionsByIds(
            null,
            adGroupId, 
            criterionType,
            null).getAdGroupCriterions();

        outputStatusMessage("Updated the bid for the tree root node");
        printProductPartitions(adGroupCriterions);
    }

    // Add a criterion to the ad group and then update it. 

    static ApplyProductPartitionActionsResponse addBranchAndLeafCriterion(long adGroupId) throws RemoteException, Exception
    {
        _partitionActions.getAdGroupCriterionActions().clear();

        ArrayList<AdGroupCriterionType> criterionType = new ArrayList<AdGroupCriterionType>();
        criterionType.add(AdGroupCriterionType.PRODUCT_PARTITION);
        outputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
        ArrayOfAdGroupCriterion adGroupCriterions = CampaignManagementExampleHelper.getAdGroupCriterionsByIds(
            null,
            adGroupId, 
            criterionType,
            null).getAdGroupCriterions();

        AdGroupCriterion existingRoot = getRootNode(adGroupCriterions);

        if (existingRoot != null)
        {
                addPartitionAction(existingRoot, ItemAction.DELETE);

        }

        ProductCondition rootCondition = new ProductCondition();
        rootCondition.setAttribute(null);
        rootCondition.setOperand("All");

        AdGroupCriterion root = addPartition(
                        adGroupId,
                        null, 
                        rootCondition,
                        ProductPartitionType.SUBDIVISION, 
                        null, 
                        false);


        ProductCondition animalsSubdivisionCondition = new ProductCondition();
        animalsSubdivisionCondition.setAttribute("Animals & Pet Supplies");
        animalsSubdivisionCondition.setOperand("CategoryL1");

        AdGroupCriterion animalsSubdivision = addPartition(
                        adGroupId,
                        root, 
                        animalsSubdivisionCondition, 
                        ProductPartitionType.SUBDIVISION, 
                        null, 
                        false);

        ProductCondition petSuppliesSubdivisionCondition = new ProductCondition();
        petSuppliesSubdivisionCondition.setAttribute("Pet Supplies");
        petSuppliesSubdivisionCondition.setOperand("CategoryL2");

        AdGroupCriterion petSuppliesSubdivision = addPartition(
                        adGroupId,
                        animalsSubdivision, 
                        petSuppliesSubdivisionCondition, 
                        ProductPartitionType.SUBDIVISION, 
                        null, 
                        false);

        ProductCondition brandACondition = new ProductCondition();
        brandACondition.setAttribute("Brand A");
        brandACondition.setOperand("Brand");

        AdGroupCriterion brandA = addPartition(
                        adGroupId,
                        petSuppliesSubdivision, 
                        brandACondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        ProductCondition brandBCondition = new ProductCondition();
        brandBCondition.setAttribute("Brand B");
        brandBCondition.setOperand("Brand");

        AdGroupCriterion brandB = addPartition(
                        adGroupId,
                        petSuppliesSubdivision, 
                        brandBCondition, 
                        ProductPartitionType.UNIT, 
                        null, 
                        true);

        ProductCondition otherBrandsCondition = new ProductCondition();
        otherBrandsCondition.setAttribute(null);
        otherBrandsCondition.setOperand("Brand");

        AdGroupCriterion otherBrands = addPartition(
                        adGroupId,
                        petSuppliesSubdivision, 
                        otherBrandsCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        ProductCondition otherPetSuppliesCondition = new ProductCondition();
        otherPetSuppliesCondition.setAttribute(null);
        otherPetSuppliesCondition.setOperand("CategoryL2");

        AdGroupCriterion otherPetSupplies = addPartition(
                        adGroupId,
                        animalsSubdivision, 
                        otherPetSuppliesCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        ProductCondition electronicsCondition = new ProductCondition();
        electronicsCondition.setAttribute("Electronics");
        electronicsCondition.setOperand("CategoryL1");

        AdGroupCriterion electronics = addPartition(
                        adGroupId,
                        root, 
                        electronicsCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        ProductCondition otherCategoryL1Condition = new ProductCondition();
        otherCategoryL1Condition.setAttribute(null);
        otherCategoryL1Condition.setOperand("CategoryL1");

        AdGroupCriterion otherCategoryL1 = addPartition(
                        adGroupId,
                        root, 
                        otherCategoryL1Condition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        outputStatusMessage("-----\nApplyProductPartitionActions:");
        outputStatusMessage("Applying product partitions to the ad group...");
        ApplyProductPartitionActionsResponse applyPartitionActionsResponse = CampaignManagementExampleHelper.applyProductPartitionActions(
                _partitionActions);

        outputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
        adGroupCriterions = CampaignManagementExampleHelper.getAdGroupCriterionsByIds(
            null,
            adGroupId, 
            criterionType,
            null).getAdGroupCriterions();

        outputStatusMessage("The product partition group tree now has 9 nodes");
        printProductPartitions(adGroupCriterions);

        return applyPartitionActionsResponse;
    }

    // Deletes and updates branch and leaf criterion. 

    static void updateBranchAndLeafCriterion(long adGroupId, long rootId, long electronicsCriterionId) throws RemoteException, Exception
    {
        _partitionActions.getAdGroupCriterionActions().clear();

        AdGroupCriterion electronics = new BiddableAdGroupCriterion();
        electronics.setId(electronicsCriterionId);
        electronics.setAdGroupId(adGroupId);
        addPartitionAction(electronics, ItemAction.DELETE);

        BiddableAdGroupCriterion parent = new BiddableAdGroupCriterion();
        parent.setId(rootId);

        ProductCondition electronicsSubdivisionCondition = new ProductCondition();
        electronicsSubdivisionCondition.setAttribute("Electronics");
        electronicsSubdivisionCondition.setOperand("CategoryL1");

        AdGroupCriterion electronicsSubdivision = addPartition(
                        adGroupId,
                        parent, 
                        electronicsSubdivisionCondition, 
                        ProductPartitionType.SUBDIVISION, 
                        null, 
                        false);

        ProductCondition brandCCondition = new ProductCondition();
        brandCCondition.setAttribute("Brand C");
        brandCCondition.setOperand("Brand");

        AdGroupCriterion brandC = addPartition(
                        adGroupId,
                        electronicsSubdivision, 
                        brandCCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        ProductCondition brandDCondition = new ProductCondition();
        brandDCondition.setAttribute("Brand D");
        brandDCondition.setOperand("Brand");

        AdGroupCriterion brandD = addPartition(
                        adGroupId,
                        electronicsSubdivision, 
                        brandDCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        ProductCondition otherElectronicBrandsCondition = new ProductCondition();
        otherElectronicBrandsCondition.setAttribute(null);
        otherElectronicBrandsCondition.setOperand("Brand");

        AdGroupCriterion otherElectronicBrands = addPartition(
                        adGroupId,
                        electronicsSubdivision, 
                        otherElectronicBrandsCondition, 
                        ProductPartitionType.UNIT, 
                        getFixedBid(0.35), 
                        false);

        outputStatusMessage("-----\nApplyProductPartitionActions:");
        outputStatusMessage("Updating the electronics partition...");
        ApplyProductPartitionActionsResponse applyPartitionActionsResponse = CampaignManagementExampleHelper.applyProductPartitionActions(
                _partitionActions);

        ArrayList<AdGroupCriterionType> criterionType = new ArrayList<AdGroupCriterionType>();
        criterionType.add(AdGroupCriterionType.PRODUCT_PARTITION);
        outputStatusMessage("-----\nGetAdGroupCriterionsByIds:");
        ArrayOfAdGroupCriterion adGroupCriterions = CampaignManagementExampleHelper.getAdGroupCriterionsByIds(
            null,
            adGroupId, 
            criterionType,
            null).getAdGroupCriterions();

        outputStatusMessage("The product partition group tree now has 12 nodes");
        printProductPartitions(adGroupCriterions);
    }     

    // Get the root criterion node.

    static AdGroupCriterion getRootNode(ArrayOfAdGroupCriterion adGroupCriterions)
    {
        AdGroupCriterion rootNode = null;

        for (AdGroupCriterion adGroupCriterion : adGroupCriterions.getAdGroupCriterions())
        {
                if (((ProductPartition)adGroupCriterion.getCriterion()).getParentCriterionId() == null)
                {
                        rootNode = adGroupCriterion;
                        break;
                }
        }

        return rootNode;
    }

    // Gets a fixed bid object with the specified bid amount.

    static FixedBid getFixedBid(final double bidAmount)
    {
        FixedBid fixedBid = new FixedBid();
        fixedBid.setAmount(bidAmount);

        return fixedBid;
    }

    // Adds a criterion action to the list of actions.

    static void addPartitionAction(final AdGroupCriterion CRITERION, final ItemAction ITEM_ACTION)
    {
        AdGroupCriterionAction partitionAction = new AdGroupCriterionAction();
        partitionAction.setAction(ITEM_ACTION);
        partitionAction.setAdGroupCriterion(CRITERION);

        // _partitionActions is a global variable.

        _partitionActions.getAdGroupCriterionActions().add(partitionAction);
    }

    // Adds either a negative or biddable partition criterion. 

    static AdGroupCriterion addPartition(
                long adGroupId,
                AdGroupCriterion parent, 
                ProductCondition condition, 
                ProductPartitionType partitionType, 
                FixedBid bid, 
                Boolean isNegative)
    {
        AdGroupCriterion adGroupCriterion = null;

        if (isNegative)
        {
                adGroupCriterion = new NegativeAdGroupCriterion();
        }
        else
        {
                adGroupCriterion = new BiddableAdGroupCriterion();
                ((BiddableAdGroupCriterion)adGroupCriterion).setCriterionBid(bid);
        }

        adGroupCriterion.setAdGroupId(adGroupId);

        ProductPartition criterion = new ProductPartition();
        criterion.setCondition(condition);
        criterion.setParentCriterionId((parent != null) ? parent.getId() : null); 

        if (partitionType == ProductPartitionType.SUBDIVISION)
        {
                criterion.setPartitionType(ProductPartitionType.SUBDIVISION);  // Branch
                adGroupCriterion.setId(_referenceId--);
        }
        else
        {
                criterion.setPartitionType(ProductPartitionType.UNIT);  // Leaf
        }

        adGroupCriterion.setCriterion(criterion);

        addPartitionAction(adGroupCriterion, ItemAction.ADD);

        return adGroupCriterion;
    }

    // Generates the ad group's partition tree that we then print.

    static void printProductPartitions(ArrayOfAdGroupCriterion adGroupCriterions)
    {
        Map<Long, ArrayList<AdGroupCriterion>> childBranches = new HashMap<Long, ArrayList<AdGroupCriterion>>();
        AdGroupCriterion treeRoot = null;

        for (AdGroupCriterion adGroupCriterion : adGroupCriterions.getAdGroupCriterions())
        {
                ProductPartition partition = (ProductPartition)adGroupCriterion.getCriterion();
                childBranches.put(adGroupCriterion.getId(), new ArrayList<AdGroupCriterion>());

                if (partition.getParentCriterionId() != null)
                {
                        childBranches.get(partition.getParentCriterionId()).add(adGroupCriterion);
                }
                else
                {
                        treeRoot = adGroupCriterion;
                }
        }

        printProductPartitionTree(treeRoot, childBranches, 0);
    }

    // Print the partition tree.

    static void printProductPartitionTree(
                AdGroupCriterion node,
                Map<Long, ArrayList<AdGroupCriterion>> childBranches,
                int treeLevel)
    {
        ProductPartition criterion = (ProductPartition)node.getCriterion();

        outputStatusMessage(String.format("%" + ((treeLevel > 0) ? treeLevel * 4 : "") + "s%s\n",
                        "",
                        criterion.getPartitionType()));

        outputStatusMessage(String.format("%" + ((treeLevel > 0) ? treeLevel * 4 : "") + "s%s%d\n",
                        "",
                        "ParentCriterionId: ", 
                        criterion.getParentCriterionId()));

        outputStatusMessage(String.format("%" + ((treeLevel > 0) ? treeLevel * 4 : "") + "s%s%d\n",
                        "",
                        "Id: ", 
                        node.getId()));

        if (criterion.getPartitionType() == ProductPartitionType.UNIT)
        {
            if (node instanceof BiddableAdGroupCriterion)
            {
                outputStatusMessage(String.format("%" + ((treeLevel > 0) ? treeLevel * 4 : "") + "s%s%.2f\n",
                                "",
                                "Bid amount: ", 
                                ((FixedBid)((BiddableAdGroupCriterion)node).getCriterionBid()).getAmount()));

            }
            else
            {
                if (node instanceof NegativeAdGroupCriterion)
                {
                    outputStatusMessage(String.format("%" + treeLevel * 4 + "s%s\n",
                                        "",
                                        "Not bidding on this condition"));
                }
            }
        }

        String nullAttribute = (criterion.getParentCriterionId() != null) ? "(All Others)" : "(Tree Root)";

        outputStatusMessage(String.format("%" + ((treeLevel > 0) ? treeLevel * 4 : "") + "s%s%s\n",
                        "",
                        "Attribute: ", 
                        (criterion.getCondition().getAttribute() == null) ? nullAttribute : criterion.getCondition().getAttribute()));

        outputStatusMessage(String.format("%" + ((treeLevel > 0) ? treeLevel * 4 : "") + "s%s%s\n",
                        "",
                        "Condition: ", 
                        criterion.getCondition().getOperand()));

        for (AdGroupCriterion childNode : childBranches.get(node.getId()))
        {
                printProductPartitionTree(childNode, childBranches, treeLevel + 1);
        }
    }
}

<?php

namespace Microsoft\BingAds\Samples\V13;

// For more information about installing and using the Bing Ads PHP SDK, 
// see https://go.microsoft.com/fwlink/?linkid=838593.

require_once __DIR__ . "/../vendor/autoload.php";

include __DIR__ . "/AuthHelper.php";
include __DIR__ . "/CampaignManagementExampleHelper.php";

use SoapVar;
use SoapFault;
use Exception;

// Specify the Microsoft\BingAds\V13\CampaignManagement classes that will be used.
use Microsoft\BingAds\V13\CampaignManagement\Campaign;
use Microsoft\BingAds\V13\CampaignManagement\BudgetLimitType;
use Microsoft\BingAds\V13\CampaignManagement\BMCStore;
use Microsoft\BingAds\V13\CampaignManagement\ShoppingSetting;
use Microsoft\BingAds\V13\CampaignManagement\CampaignType;
use Microsoft\BingAds\V13\CampaignManagement\AdGroup;
use Microsoft\BingAds\V13\CampaignManagement\Date;
use Microsoft\BingAds\V13\CampaignManagement\ProductAd;
use Microsoft\BingAds\V13\CampaignManagement\CampaignCriterionType;
use Microsoft\BingAds\V13\CampaignManagement\ProductScope;
use Microsoft\BingAds\V13\CampaignManagement\BiddableCampaignCriterion;
use Microsoft\BingAds\V13\CampaignManagement\ProductCondition;
use Microsoft\BingAds\V13\CampaignManagement\ProductPartitionType;
use Microsoft\BingAds\V13\CampaignManagement\AdGroupCriterionType;
use Microsoft\BingAds\V13\CampaignManagement\BiddableAdGroupCriterion;
use Microsoft\BingAds\V13\CampaignManagement\ItemAction;
use Microsoft\BingAds\V13\CampaignManagement\AdGroupCriterionAction;
use Microsoft\BingAds\V13\CampaignManagement\NegativeAdGroupCriterion;
use Microsoft\BingAds\V13\CampaignManagement\ProductPartition;
use Microsoft\BingAds\V13\CampaignManagement\FixedBid;
use Microsoft\BingAds\V13\CampaignManagement\Bid;
use Microsoft\BingAds\V13\CampaignManagement\BatchErrorCollection;

// Specify the Microsoft\BingAds\Auth classes that will be used.
use Microsoft\BingAds\Auth\ServiceClient;
use Microsoft\BingAds\Auth\ServiceClientType;

// Specify the Microsoft\BingAds\Samples classes that will be used.
use Microsoft\BingAds\Samples\V13\AuthHelper;
use Microsoft\BingAds\Samples\V13\CampaignManagementExampleHelper;

$PartitionActions = array(); // AdGroupCriterionAction array
$ReferenceId = -1;
$ids = null;

try
{
    // Authenticate user credentials and set the account ID for the sample.  
    AuthHelper::Authenticate();

    // Get a list of all Bing Merchant Center stores associated with your CustomerId.

    $stores= CampaignManagementExampleHelper::GetBMCStoresByCustomerId(
        false
    )->BMCStores;
    
    if (!isset($stores->BMCStore))
    {
        printf(
            "CustomerId %d does not have any registered BMC stores.\r\n", 
            $GLOBALS['AuthorizationData']->CustomerId
        );
        return;
    }
    
    print("BMCStores:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfBMCStore($stores);
    
    // Create a Shopping campaign with product conditions.
    
    $campaigns = array();   
    $campaign = new Campaign();
    $campaign->CampaignType = CampaignType::Shopping;
    $campaign->BudgetType = BudgetLimitType::DailyBudgetStandard;
    $campaign->DailyBudget = 50.00;
    $campaign->Languages = array("All");
    $campaign->Name = "Women's Shoes " . $_SERVER['REQUEST_TIME'];
    $settings = array();
    $shoppingSetting = new ShoppingSetting();
    $shoppingSetting->Priority = 0;
    $shoppingSetting->SalesCountryCode = "US";
    $shoppingSetting->StoreId = $stores->BMCStore[0]->Id;	
    $encodedSetting = new SoapVar(
        $shoppingSetting, 
        SOAP_ENC_OBJECT, 
        'ShoppingSetting', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    $settings[] = $encodedSetting;
    $campaign->Settings = $settings;
    $campaign->TimeZone = "PacificTimeUSCanadaTijuana";
    $campaigns[] = $campaign;
    
    print("-----\r\nAddCampaigns:\r\n");
    $addCampaignsResponse = CampaignManagementExampleHelper::AddCampaigns(
        $GLOBALS['AuthorizationData']->AccountId, 
        $campaigns
    );
    $campaignIds = $addCampaignsResponse->CampaignIds;
    print("CampaignIds:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfLong($campaignIds);
    print("PartialErrors:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfBatchError($addCampaignsResponse->PartialErrors);
    
    // Optionally, you can create a ProductScope criterion that will be associated with your Bing Shopping campaign. 
    // You'll also be able to add more specific product conditions for each ad group.
    
    AddCampaignCriterion($campaignIds->long[0]);

    // Create the ad group that will have the product partitions.

    $adGroups = array();
    $adGroup = new AdGroup();
    $adGroup->CpcBid = new Bid();
    $adGroup->CpcBid->Amount = 0.09;
    date_default_timezone_set('UTC');
    $endDate = new Date();
    $endDate->Day = 31;
    $endDate->Month = 12;
    $endDate->Year = date("Y");
    $adGroup->EndDate = $endDate;
    $adGroup->Name = "Women's Red Shoe Sale";    
    $adGroup->StartDate = null;    
    $adGroups[] = $adGroup;
 
    print("-----\r\nAddAdGroups:\r\n");
    $addAdGroupsResponse = CampaignManagementExampleHelper::AddAdGroups(
        $campaignIds->long[0], 
        $adGroups,
        null
    );
    $adGroupIds = $addAdGroupsResponse->AdGroupIds;
    print("AdGroupIds:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfLong($adGroupIds);
    print("PartialErrors:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfBatchError($addAdGroupsResponse->PartialErrors);
        
    // Create a product ad. You must add at least one product ad to the ad group. 
    // The product ad identifier can be used for reporting analytics.
    // Use Merchant Promotions if you want tags to appear at the bottom of your product ad 
    // as "special offer" links, helping to increase customer engagement. For details
    // on Merchant Promotions see https://help.bingads.microsoft.com/#apex/3/en/56805/0.

    $ads = array();
    $ad = new ProductAd();
    $encodedAd = new SoapVar(
        $ad, 
        SOAP_ENC_OBJECT, 
        'ProductAd', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    $ads[] = $encodedAd;
    
    print("-----\r\nAddAds:\r\n");
    $addAdsResponse = CampaignManagementExampleHelper::AddAds(
        $adGroupIds->long[0], 
        $ads
    );
    print("AdIds:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfLong($addAdsResponse->AdIds);
    print("PartialErrors:\r\n");
    CampaignManagementExampleHelper::OutputArrayOfBatchError($addAdsResponse->PartialErrors);
    
    // Add and update the ad group product groups. 
    
    AddAndUpdateAdGroupCriterion(
        $GLOBALS['AuthorizationData']->AccountId, 
        $PartitionActions, 
        $adGroupIds->long[0]);
    
    $applyPartitionActionsResponse = AddBranchAndLeafCriterion(
        $GLOBALS['AuthorizationData']->AccountId, 
        $PartitionActions, 
        $adGroupIds->long[0]);
    
    $rootId = $applyPartitionActionsResponse->AdGroupCriterionIds->long[1];
    $electronicsCriterionId = $applyPartitionActionsResponse->AdGroupCriterionIds->long[8];
    
    UpdateBranchAndLeafCriterion(
        $PartitionActions, 
        $GLOBALS['AuthorizationData']->AccountId, 
        $adGroupIds->long[0], 
        $rootId, 
        $electronicsCriterionId);
     
    // Delete the campaign and everything it contains e.g., ad groups and ads.

    print("-----\r\nDeleteCampaigns:\r\n");
    CampaignManagementExampleHelper::DeleteCampaigns(
        $GLOBALS['AuthorizationData']->AccountId, 
        array($campaignIds->long[0])
    );
    printf("Deleted Campaign Id %s\r\n", $campaignIds->long[0]);
}
catch (SoapFault $e)
{
    printf("-----\r\nFault Code: %s\r\nFault String: %s\r\nFault Detail: \r\n", $e->faultcode, $e->faultstring);
    var_dump($e->detail);
    print "-----\r\nLast SOAP request/response:\r\n";
    print $GLOBALS['Proxy']->GetWsdl() . "\r\n";
    print $GLOBALS['Proxy']->GetService()->__getLastRequest()."\r\n";
    print $GLOBALS['Proxy']->GetService()->__getLastResponse()."\r\n";
}
catch (Exception $e)
{
    // Ignore fault exceptions that we already caught.
    if ($e->getPrevious())
    { ; }
    else
    {
        print $e->getCode()." ".$e->getMessage()."\r\n";
        print $e->getTraceAsString()."\r\n";
    }
}

// Add criterion to the campaign. The criterion is used to limit the campaign to a subset of
// your product catalog. For example, you can limit the catalog by brand, category, or product
// type. The campaign may be associated with only one ProductScope, and the ProductScope
// may contain a list of up to 7 ProductConditions. Each ad group may also specify
// more specific product conditions.
 
function AddCampaignCriterion($campaignId)
{
    $campaignCriterions = array();
    
    $criterion = new BiddableCampaignCriterion();
    $criterion->CampaignId = $campaignId;

    // CriterionBid is not applicable for ProductScope
    $criterion->CriterionBid = null;  
    
    $productConditions = array();
    
    $condition = new ProductCondition();
    $condition->Operand = "Condition";
    $condition->Attribute = "New";
    $productConditions[] = $condition;
    
    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = "Contoso";
    $productConditions[] = $condition;

    $scope = new ProductScope();
    $scope->Conditions = $productConditions;

    $encodedScope = new SoapVar(
        $scope, 
        SOAP_ENC_OBJECT, 
        'ProductScope', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    
    $criterion->Criterion = $encodedScope;

    $encodedCriterion = new SoapVar(
        $criterion, 
        SOAP_ENC_OBJECT, 
        'BiddableCampaignCriterion', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );

    $campaignCriterions[] = $encodedCriterion;

    $addCampaignCriterionsResponse = CampaignManagementExampleHelper::AddCampaignCriterions(
        $campaignCriterions,
        CampaignCriterionType::ProductScope);

    CampaignManagementExampleHelper::OutputArrayOfLong($addCampaignCriterionsResponse->CampaignCriterionIds);
    if(isset($addCampaignCriterionsResponse->NestedPartialErrors)){
        CampaignManagementExampleHelper::OutputArrayOfBatchErrorCollection($addCampaignCriterionsResponse->NestedPartialErrors);
    }
}


// Add a criterion to the ad group and then update it.
 
function AddAndUpdateAdGroupCriterion($accountId, &$actions, $adGroupId)
{
    // Add a biddable criterion as the root.

    $condition = new ProductCondition();
    $condition->Operand = "All";
    $condition->Attribute = null;
    
    $root = AddPartition(
        $adGroupId,
        null,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    printf("Applying a biddable criterion as the root...\r\n");
    $applyPartitionActionsResponse = CampaignManagementExampleHelper::ApplyProductPartitionActions(
        $actions
    );
    CampaignManagementExampleHelper::OutputArrayOfLong($applyPartitionActionsResponse->AdGroupCriterionIds);
    CampaignManagementExampleHelper::OutputArrayOfBatchError($applyPartitionActionsResponse->PartialErrors);

    $adGroupCriterions = CampaignManagementExampleHelper::GetAdGroupCriterionsByIds(
        null,
        $adGroupId,
        AdGroupCriterionType::ProductPartition,
        null
    )->AdGroupCriterions;
     
    printf("Outputing the ad group's product partition; contains only the tree root node\r\n");
    OutputProductPartitions($adGroupCriterions);
     
    // Update the bid of the root node that we just added.
     
    $updatedRoot = new BiddableAdGroupCriterion();
    $updatedRoot->Id = $applyPartitionActionsResponse->AdGroupCriterionIds->long[0];
    $updatedRoot->AdGroupId = $adGroupId;
    $updatedRoot->CriterionBid = GetFixedBid(0.40);

    $encodedUpdateRoot = new SoapVar(
        $updatedRoot, 
        SOAP_ENC_OBJECT, 
        'BiddableAdGroupCriterion', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    
    $actions = array();  // clear
     
    AddPartitionAction($encodedUpdateRoot, ItemAction::Update, $actions);
     
    printf("Updating the bid for the tree root node...\r\n");
    
    $applyPartitionActionsResponse = CampaignManagementExampleHelper::ApplyProductPartitionActions(
        $actions
    );
     
    $adGroupCriterions = CampaignManagementExampleHelper::GetAdGroupCriterionsByIds(
        null,
        $adGroupId,
        AdGroupCriterionType::ProductPartition,
        null
    )->AdGroupCriterions;
     
    printf("Updated the bid for the tree root node\r\n");
    OutputProductPartitions($adGroupCriterions);
}

// Add a criterion to the ad group and then update it.
 
function AddBranchAndLeafCriterion($accountId, &$actions, $adGroupId)
{
    $actions = array();  // clear
     
    $adGroupCriterions = CampaignManagementExampleHelper::GetAdGroupCriterionsByIds(
        null,
        $adGroupId,
        AdGroupCriterionType::ProductPartition,
        null
    )->AdGroupCriterions;
     
    $existingRoot = GetRootNode($adGroupCriterions);

    if (isset($existingRoot))
    {
        $nodeToDelete = new BiddableAdGroupCriterion();
        $nodeToDelete->Id = $existingRoot->Id;
        $nodeToDelete->AdGroupId = $existingRoot->AdGroupId;
        
        $encodedNodeToDelete = new SoapVar(
            $nodeToDelete, 
            SOAP_ENC_OBJECT, 
            'BiddableAdGroupCriterion', 
            $GLOBALS['CampaignManagementProxy']->GetNamespace()
        );
        
        AddPartitionAction($encodedNodeToDelete, ItemAction::Delete, $actions);
    }

    $condition = new ProductCondition();
    $condition->Operand = "All";
    $condition->Attribute = null;
    
    $root = AddPartition(
        $adGroupId,
        null,
        $condition,
        ProductPartitionType::Subdivision,
        null,
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "CategoryL1";
    $condition->Attribute = "Animals & Pet Supplies";
     
    $animalsSubdivision = AddPartition(
        $adGroupId,
        $root,
        $condition,
        ProductPartitionType::Subdivision,
        null,
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "CategoryL2";
    $condition->Attribute = "Pet Supplies";
    
    $petSuppliesSubdivision = AddPartition(
        $adGroupId,
        $animalsSubdivision,
        $condition,
        ProductPartitionType::Subdivision,
        null,
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = "Brand A";
    
    $brandA = AddPartition(
        $adGroupId,
        $petSuppliesSubdivision,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = "Brand B";
    
    $brandB = AddPartition(
        $adGroupId,
        $petSuppliesSubdivision,
        $condition,
        ProductPartitionType::Unit,
        null,
        true,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = null;
    
    $otherBrands = AddPartition(
        $adGroupId,
        $petSuppliesSubdivision,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "CategoryL2";
    $condition->Attribute = null;
    
    $otherPetSupplies = AddPartition(
        $adGroupId,
        $animalsSubdivision,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "CategoryL1";
    $condition->Attribute = "Electronics";
    
    $electronics = AddPartition(
        $adGroupId,
        $root,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "CategoryL1";
    $condition->Attribute = null;
    
    $otherCategoryL1 = AddPartition(
        $adGroupId,
        $root,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);
    
    printf("Applying product partitions to the ad group...\r\n");
    $applyPartitionActionsResponse = CampaignManagementExampleHelper::ApplyProductPartitionActions(
        $actions
    );

    $adGroupCriterions = CampaignManagementExampleHelper::GetAdGroupCriterionsByIds(
        null,
        $adGroupId,
        AdGroupCriterionType::ProductPartition,
        null
    )->AdGroupCriterions;

    printf("The product partition group tree now has 9 nodes\r\n");
    OutputProductPartitions($adGroupCriterions);

    return $applyPartitionActionsResponse;
}


// Deletes and updates branch and leaf criterion.
 
function UpdateBranchAndLeafCriterion(&$actions, $accountId, $adGroupId, $rootId, $electronicsCriterionId)
{
    $actions = array(); // clear;

    $electronics = new BiddableAdGroupCriterion();
    $electronics->Id = $electronicsCriterionId;
    $electronics->AdGroupId = $adGroupId;
    $encodedNodeToDelete = new SoapVar(
        $electronics, 
        SOAP_ENC_OBJECT, 
        'BiddableAdGroupCriterion', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    
    AddPartitionAction($encodedNodeToDelete, ItemAction::Delete, $actions);
     
    $parent = new BiddableAdGroupCriterion();
    $parent->Id = $rootId;
    $encodedParent = new SoapVar(
        $parent, 
        SOAP_ENC_OBJECT, 
        'BiddableAdGroupCriterion', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    
    $condition = new ProductCondition();
    $condition->Operand = "CategoryL1";
    $condition->Attribute = "Electronics";
    
    $electronicsSubdivision = AddPartition(
        $adGroupId,
        $encodedParent,
        $condition,
        ProductPartitionType::Subdivision,
        null,
        false,
        $actions);
     
    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = "Brand C";
    
    $brandC = AddPartition(
        $adGroupId,
        $electronicsSubdivision,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);
     
    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = "Brand D";
    
    $brandD = AddPartition(
        $adGroupId,
        $electronicsSubdivision,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    $condition = new ProductCondition();
    $condition->Operand = "Brand";
    $condition->Attribute = null;
    
    $otherElectronicBrands = AddPartition(
        $adGroupId,
        $electronicsSubdivision,
        $condition,
        ProductPartitionType::Unit,
        GetFixedBid(0.35),
        false,
        $actions);

    printf("\nUpdating the electronics partition...\n");
    $applyPartitionActionsResponse = CampaignManagementExampleHelper::ApplyProductPartitionActions(
        $actions
    );
     
    $adGroupCriterions = CampaignManagementExampleHelper::GetAdGroupCriterionsByIds(
        null,
        $adGroupId,
        AdGroupCriterionType::ProductPartition,
        null
    )->AdGroupCriterions;
     
    printf("\nThe product partition group tree now has 12 nodes\n");
    OutputProductPartitions($adGroupCriterions);
}


// Get the root criterion node.
 
function GetRootNode($adGroupCriterions)
{
    $rootNode = null;

    foreach ($adGroupCriterions->AdGroupCriterion as $adGroupCriterion)
    {
        if (empty($adGroupCriterion->Criterion->ParentCriterionId))
        {
            $rootNode = $adGroupCriterion;
            break;
        }
    }

    return $rootNode;
}

// Gets a fixed bid object with the specified bid amount.
 
function GetFixedBid($bidAmount)
{
    $fixedBid = new FixedBid();
    $fixedBid->Amount = $bidAmount;

    $encodedFixedBid = new SoapVar(
        $fixedBid, 
        SOAP_ENC_OBJECT, 
        'FixedBid', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    
    return $encodedFixedBid;
}

// Adds a criterion action to the list of actions.
 
function AddPartitionAction($criterion, $itemAction, &$actions)
{
    $partitionAction = new AdGroupCriterionAction();
    $partitionAction->Action = $itemAction; 
    $partitionAction->AdGroupCriterion = $criterion;

    $actions[] = $partitionAction;
}

// Adds either a negative or biddable partition criterion.
 
function AddPartition(
        $adGroupId,
        $parent,  		// AdGroupCriterion 
        $condition,  	// ProductCondition 
        $partitionType, // ProductPartitionType 
        $bid, 			// FixedBid 
        $isNegative,
        &$actions)
{
    global $ReferenceId;
    
    $adGroupCriterion = null;

    if ($isNegative)
    {
        $adGroupCriterion = new NegativeAdGroupCriterion();
    }
    else
    {
        $adGroupCriterion = new BiddableAdGroupCriterion();
        $adGroupCriterion->CriterionBid = $bid;
    }

    $adGroupCriterion->AdGroupId = $adGroupId;
    
    // Parent is encoded, so dereference enc_value.
    
    $criterion = new ProductPartition();
    $criterion->Condition = $condition;
    $criterion->ParentCriterionId = (($parent != null) ? $parent->enc_value->Id : null);

    if ($partitionType === ProductPartitionType::Subdivision)
    {
        $criterion->PartitionType = ProductPartitionType::Subdivision;  // Branch
        $adGroupCriterion->Id = $ReferenceId--;
    }
    else
    {
        $criterion->PartitionType = ProductPartitionType::Unit;  // Leaf
    }

    $encodedCriterion = new SoapVar(
        $criterion, 
        SOAP_ENC_OBJECT, 
        'ProductPartition', 
        $GLOBALS['CampaignManagementProxy']->GetNamespace()
    );
    
    $adGroupCriterion->Criterion = $encodedCriterion;
    
    if ($isNegative)
    {
        $encodedAdGroupCriterion = new SoapVar(
            $adGroupCriterion, 
            SOAP_ENC_OBJECT, 
            'NegativeAdGroupCriterion', 
            $GLOBALS['CampaignManagementProxy']->GetNamespace()
        );
    }
    else
    {
        $encodedAdGroupCriterion = new SoapVar(
            $adGroupCriterion, 
            SOAP_ENC_OBJECT, 
            'BiddableAdGroupCriterion', 
            $GLOBALS['CampaignManagementProxy']->GetNamespace()
        );
    }	
    
    AddPartitionAction($encodedAdGroupCriterion, ItemAction::Add, $actions);

    return $encodedAdGroupCriterion;
}

// Generates the ad group's partition tree that we then print.
 
function OutputProductPartitions($adGroupCriterions)
{
    $childBranches = array(); // Hash map (Long, List(AdGroupCriterion));
    $treeRoot = null;

    foreach ($adGroupCriterions->AdGroupCriterion as $adGroupCriterion)
    {
        $partition = $adGroupCriterion->Criterion;
        $childBranches[$adGroupCriterion->Id] = array();

        if (!empty($partition->ParentCriterionId))
        {
            $childBranches[$partition->ParentCriterionId][] = $adGroupCriterion;
        }
        else
        {
            $treeRoot = $adGroupCriterion;
        }
    }

    OutputProductPartitionTree($treeRoot, $childBranches, 0);
}


// Output the partition tree.
 
function OutputProductPartitionTree(
        $node,
        $childBranches,  // hash map (Long, List(AdGroupCriterion)) 
        $treeLevel)
{
    $criterion = $node->Criterion;  // ProductPartition 

    printf("%" . (($treeLevel > 0) ? $treeLevel * 4 : "") . "s%s\n",
            "",
            $criterion->PartitionType);

    printf("%" . (($treeLevel > 0) ? $treeLevel * 4 : "") . "s%s%s\n",
            "",
            "ParentCriterionId: ",
            $criterion->ParentCriterionId);

    printf("%" . (($treeLevel > 0) ? $treeLevel * 4 : "") . "s%s%s\n",
            "",
            "Id: ",
            $node->Id);

    if ($criterion->PartitionType === ProductPartitionType::Unit)
    {
        if ($node->Type === "BiddableAdGroupCriterion") //instanceof BiddableAdGroupCriterion)
        {
            printf("%" . (($treeLevel > 0) ? $treeLevel * 4 : "") . "s%s%.2f\n",
                    "",
                    "Bid amount: ",
                    $node->CriterionBid->Amount);  // ((FixedBid)((BiddableAdGroupCriterion)

        }
        else
        {
            if ($node->Type === "NegativeAdGroupCriterion")  // node instanceof NegativeAdGroupCriterion
            {
                printf("%" . $treeLevel * 4 . "s%s\n",
                        "",
                        "Not bidding on this condition");
            }
        }
    }

    $nullAttribute = (!empty($criterion->ParentCriterionId)) ? "(All Others)" : "(Tree Root)";

    printf("%" . (($treeLevel > 0) ? $treeLevel * 4 : "") . "s%s%s\n",
            "",
            "Attribute: ",
            (empty($criterion->Condition->Attribute)) ?
            $nullAttribute : $criterion->Condition->Attribute);

    printf("%" . (($treeLevel > 0) ? $treeLevel * 4 : "") . "s%s%s\r\n",
            "",
            "Condition: ",
            $criterion->Condition->Operand);

    foreach ($childBranches[$node->Id] as $childNode)  // AdGroupCriterion 
    {
        OutputProductPartitionTree($childNode, $childBranches, $treeLevel + 1);
    }
}
from auth_helper import *
from campaignmanagement_example_helper import *

# You must provide credentials in auth_helper.py.

def main(authorization_data):

    try:
        # Get a list of all Bing Merchant Center stores associated with your CustomerId.

        output_status_message("-----\nGetBMCStoresByCustomerId:")
        stores=campaign_service.GetBMCStoresByCustomerId()['BMCStore']
        if stores is None:
            output_status_message(
                "You do not have any BMC stores registered for CustomerId {0}.".format(authorization_data.customer_id)
            )
            sys.exit(0)

        # Create a Shopping campaign with product conditions.

        campaigns=campaign_service.factory.create('ArrayOfCampaign')
        campaign=set_elements_to_none(campaign_service.factory.create('Campaign'))
        campaign.BudgetType='DailyBudgetStandard'
        campaign.CampaignType=['Shopping']
        campaign.DailyBudget=50
        languages=campaign_service.factory.create('ns3:ArrayOfstring')
        languages.string.append('All')
        campaign.Languages=languages
        campaign.Name="Women's Shoes " + strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
        settings=campaign_service.factory.create('ArrayOfSetting')
        setting=set_elements_to_none(campaign_service.factory.create('ShoppingSetting'))
        setting.Priority=0
        setting.SalesCountryCode ='US'
        setting.StoreId=stores[0].Id
        settings.Setting.append(setting)
        campaign.Settings=settings
        campaign.TimeZone='PacificTimeUSCanadaTijuana'
        campaigns.Campaign.append(campaign)

        output_status_message("-----\nAddCampaigns:")
        add_campaigns_response=campaign_service.AddCampaigns(
            AccountId=authorization_data.account_id,
            Campaigns=campaigns
        )
        campaign_ids={
            'long': add_campaigns_response.CampaignIds['long'] if add_campaigns_response.CampaignIds['long'] else None
        }
        output_status_message("CampaignIds:")
        output_array_of_long(campaign_ids)
        output_status_message("PartialErrors:")
        output_array_of_batcherror(add_campaigns_response.PartialErrors)
                
        # Optionally, you can create a ProductScope criterion that will be associated with your Microsoft Shopping campaign. 
        # You'll also be able to add more specific product conditions for each ad group.

        campaign_criterions=campaign_service.factory.create('ArrayOfCampaignCriterion')
        campaign_criterion=set_elements_to_none(campaign_service.factory.create('BiddableCampaignCriterion'))
        product_scope=set_elements_to_none(campaign_service.factory.create('ProductScope'))
        conditions=campaign_service.factory.create('ArrayOfProductCondition')
        condition_new=campaign_service.factory.create('ProductCondition')
        condition_new.Operand='Condition'
        condition_new.Attribute='New'
        conditions.ProductCondition.append(condition_new)
        condition_custom_label_0=campaign_service.factory.create('ProductCondition')
        condition_custom_label_0.Operand='CustomLabel0'
        condition_custom_label_0.Attribute='MerchantDefinedCustomLabel'
        conditions.ProductCondition.append(condition_custom_label_0)
        product_scope.Conditions=conditions
        campaign_criterion.CampaignId=campaign_ids['long'][0]
        campaign_criterion.CriterionBid=None # Not applicable for product scope
        campaign_criterion.Criterion=product_scope
        campaign_criterions.CampaignCriterion.append(campaign_criterion)

        output_status_message("-----\nAddCampaignCriterions:")
        add_campaign_criterions_response = campaign_service.AddCampaignCriterions(
            CampaignCriterions=campaign_criterions,
            CriterionType='ProductScope'
        )
        campaign_criterion_ids={
            'long': add_campaign_criterions_response.CampaignCriterionIds['long'] 
            if add_campaign_criterions_response.CampaignCriterionIds['long'] else None
        }
        output_status_message("CampaignCriterionIds:")
        output_array_of_long(campaign_criterion_ids)
        output_status_message("NestedPartialErrors:")
        output_array_of_batcherrorcollection(add_campaign_criterions_response.NestedPartialErrors) 
        
        # Create the ad group that will have the product partitions.

        ad_groups=campaign_service.factory.create('ArrayOfAdGroup')
        ad_group=set_elements_to_none(campaign_service.factory.create('AdGroup'))
        ad_group.Name="Women's Red Shoe Sale"
        end_date=campaign_service.factory.create('Date')
        end_date.Day=31
        end_date.Month=12
        current_time=gmtime()
        end_date.Year=current_time.tm_year + 1
        ad_group.EndDate=end_date
        cpc_bid=campaign_service.factory.create('Bid')
        cpc_bid.Amount=0.09
        ad_group.CpcBid=cpc_bid
        ad_groups.AdGroup.append(ad_group)

        output_status_message("-----\nAddAdGroups:")
        add_ad_groups_response=campaign_service.AddAdGroups(
            CampaignId=campaign_ids['long'][0],
            AdGroups=ad_groups,
            ReturnInheritedBidStrategyTypes=False
        )
        ad_group_ids={
            'long': add_ad_groups_response.AdGroupIds['long'] if add_ad_groups_response.AdGroupIds['long'] else None
        }
        output_status_message("AdGroupIds:")
        output_array_of_long(ad_group_ids)
        output_status_message("PartialErrors:")
        output_array_of_batcherror(add_ad_groups_response.PartialErrors)

        # Bid all products

        helper=PartitionActionHelper(ad_group_ids['long'][0])
        
        root_condition=campaign_service.factory.create('ProductCondition')
        root_condition.Operand='All'
        root_condition.Attribute=None

        root=helper.add_unit(
            None,
            root_condition,
            0.35,
            False
        )

        output_status_message("-----\nApplyProductPartitionActions:")
        output_status_message("Applying only the root as a Unit with a bid...")
        apply_product_partition_actions_response=campaign_service.ApplyProductPartitionActions(
            CriterionActions=helper.partition_actions
        )

        output_status_message("-----\nGetAdGroupCriterionsByIds:")
        ad_group_criterions=campaign_service.GetAdGroupCriterionsByIds(
            AdGroupId=ad_group_ids['long'][0],
            AdGroupCriterionIds=None,
            CriterionType='ProductPartition'
        )

        output_status_message("The ad group's product partition only has a tree root node: \n")
        output_product_partitions(ad_group_criterions)

        # Let's update the bid of the root Unit we just added.

        updated_root=set_elements_to_none(campaign_service.factory.create('BiddableAdGroupCriterion'))
        fixed_bid=set_elements_to_none(campaign_service.factory.create('FixedBid'))
        fixed_bid.Amount=0.45
        updated_root.Id=apply_product_partition_actions_response.AdGroupCriterionIds['long'][0]
        updated_root.CriterionBid=fixed_bid
                
        helper=PartitionActionHelper(ad_group_ids['long'][0])
        helper.update_partition(updated_root)

        output_status_message("-----\nApplyProductPartitionActions:")
        output_status_message("Updating the bid for the tree root node...")
        campaign_service.ApplyProductPartitionActions(
            CriterionActions=helper.partition_actions
        )

        output_status_message("-----\nGetAdGroupCriterionsByIds:")
        ad_group_criterions=campaign_service.GetAdGroupCriterionsByIds(
            AdGroupId=ad_group_ids['long'][0],
            AdGroupCriterionIds=None,
            CriterionType='ProductPartition'
        )

        output_status_message("Updated the bid for the tree root node: \n")
        output_product_partitions(ad_group_criterions)
        
        # Initialize and overwrite any existing tree root, and build a product partition group tree structure in multiple steps. 
        # You could build the entire tree in a single call since there are less than 20,000 nodes; however, 
        # we will build it in steps to demonstrate how to use the results from bulk upload to update the tree. 
        
        helper=PartitionActionHelper(ad_group_ids['long'][0])

        #Check whether a root node exists already.

        output_status_message("-----\nGetAdGroupCriterionsByIds:")
        ad_group_criterions=campaign_service.GetAdGroupCriterionsByIds(
            AdGroupId=ad_group_ids['long'][0],
            AdGroupCriterionIds=None,
            CriterionType='ProductPartition'
        )

        existing_root=get_root_node(ad_group_criterions)
        if existing_root is not None:
            helper.delete_partition(existing_root)

        root_condition=campaign_service.factory.create('ProductCondition')
        root_condition.Operand='All'
        root_condition.Attribute=None
        root=helper.add_subdivision(
            None, 
            root_condition
        )
        
        #The direct children of any node must have the same Operand. 
        #For this example we will use CategoryL1 nodes as children of the root. 
        #For a list of valid CategoryL1 through CategoryL5 values, see the Bing Category Taxonomy:
        #https://go.microsoft.com/fwlink?LinkId=507666
        
        animals_condition=campaign_service.factory.create('ProductCondition')
        animals_condition.Operand='CategoryL1'
        animals_condition.Attribute='Animals & Pet Supplies'
        animals_subdivision=helper.add_subdivision(
            root,
            animals_condition
        )
        
        #If you use a CategoryL2 node, it must be a descendant (child or later) of a CategoryL1 node. 
        #In other words you cannot have a CategoryL2 node as parent of a CategoryL1 node. 
        #For this example we will a CategoryL2 node as child of the CategoryL1 Animals & Pet Supplies node. 
        
        pet_supplies_condition=campaign_service.factory.create('ProductCondition')
        pet_supplies_condition.Operand='CategoryL2'
        pet_supplies_condition.Attribute='Pet Supplies'
        pet_supplies_subdivision=helper.add_subdivision(
            animals_subdivision,
            pet_supplies_condition
        )

        brand_a_condition=campaign_service.factory.create('ProductCondition')
        brand_a_condition.Operand='Brand'
        brand_a_condition.Attribute='Brand A'
        brand_a=helper.add_unit(
            pet_supplies_subdivision,
            brand_a_condition,
            0.35,
            False
        )
        
        #If you won't bid on Brand B, set the helper method's bidAmount to '0' and isNegative to True. 
        #The helper method will create a NegativeAdGroupCriterion and apply the condition.
        
        brand_b_condition=campaign_service.factory.create('ProductCondition')
        brand_b_condition.Operand='Brand'
        brand_b_condition.Attribute='Brand B'
        brand_b=helper.add_unit(
            pet_supplies_subdivision,
            brand_b_condition,
            0,
            True
        )

        other_brands_condition=campaign_service.factory.create('ProductCondition')
        other_brands_condition.Operand='Brand'
        other_brands_condition.Attribute=None
        other_brands=helper.add_unit(
            pet_supplies_subdivision,
            other_brands_condition,
            0.35,
            False
        )

        other_pet_supplies_condition=campaign_service.factory.create('ProductCondition')
        other_pet_supplies_condition.Operand='CategoryL2'
        other_pet_supplies_condition.Attribute=None
        other_pet_supplies=helper.add_unit(
            animals_subdivision,
            other_pet_supplies_condition,
            0.35,
            False
        )

        electronics_condition=campaign_service.factory.create('ProductCondition')
        electronics_condition.Operand='CategoryL1'
        electronics_condition.Attribute='Electronics'
        electronics=helper.add_unit(
            root,
            electronics_condition,
            0.35,
            False
        )

        other_categoryL1_condition=campaign_service.factory.create('ProductCondition')
        other_categoryL1_condition.Operand='CategoryL1'
        other_categoryL1_condition.Attribute=None
        other_categoryL1=helper.add_unit(
            root,
            other_categoryL1_condition,
            0.35,
            False
        )

        output_status_message("-----\nApplyProductPartitionActions:")
        output_status_message("Applying product partitions to the ad group...")
        apply_product_partition_actions_response=campaign_service.ApplyProductPartitionActions(
            CriterionActions=helper.partition_actions
        )

        # To retrieve product partitions after they have been applied, call GetAdGroupCriterionsByIds. 
        # The product partition with ParentCriterionId set to null is the root node.

        output_status_message("-----\nGetAdGroupCriterionsByIds:")
        ad_group_criterions=campaign_service.GetAdGroupCriterionsByIds(
            AdGroupId=ad_group_ids['long'][0],
            AdGroupCriterionIds=None,
            CriterionType='ProductPartition'
        )
        
        #The product partition group tree now has 9 nodes. 
                 
        #All other (Root Node)
        #|
        #+-- Animals & Pet Supplies (CategoryL1)
        #|    |
        #|    +-- Pet Supplies (CategoryL2)
        #|    |    |
        #|    |    +-- Brand A
        #|    |    |    
        #|    |    +-- Brand B
        #|    |    |    
        #|    |    +-- All other (Brand)
        #|    |         
        #|    +-- All other (CategoryL2)
        #|        
        #+-- Electronics (CategoryL1)
        #|   
        #+-- All other (CategoryL1)

        output_status_message("The product partition group tree now has 9 nodes: \n")
        output_product_partitions(ad_group_criterions)
                
        #Let's replace the Electronics (CategoryL1) node created above with an Electronics (CategoryL1) node that 
        #has children i.e. Brand C (Brand), Brand D (Brand), and All other (Brand) as follows: 
                 
        #Electronics (CategoryL1)
        #|
        #+-- Brand C (Brand)
        #|
        #+-- Brand D (Brand)
        #|
        #+-- All other (Brand)

        helper=PartitionActionHelper(ad_group_ids['long'][0])
        
        #To replace a node we must know its Id and its ParentCriterionId. In this case the parent of the node 
        #we are replacing is All other (Root Node), and was created at Index 1 of the previous ApplyProductPartitionActions call. 
        #The node that we are replacing is Electronics (CategoryL1), and was created at Index 8. 
        
        root_id=apply_product_partition_actions_response.AdGroupCriterionIds['long'][1]
        electronics.Id=apply_product_partition_actions_response.AdGroupCriterionIds['long'][8]
        helper.delete_partition(electronics)

        parent=set_elements_to_none(campaign_service.factory.create('BiddableAdGroupCriterion'))
        parent.Id=root_id

        electronics_subdivision_condition=campaign_service.factory.create('ProductCondition')
        electronics_subdivision_condition.Operand='CategoryL1'
        electronics_subdivision_condition.Attribute='Electronics'
        electronics_subdivision=helper.add_subdivision(
            parent,
            electronics_subdivision_condition
        )

        brand_c_condition=campaign_service.factory.create('ProductCondition')
        brand_c_condition.Operand='Brand'
        brand_c_condition.Attribute='Brand C'
        brand_c=helper.add_unit(
            electronics_subdivision,
            brand_c_condition,
            0.35,
            False
        )

        brand_d_condition=campaign_service.factory.create('ProductCondition')
        brand_d_condition.Operand='Brand'
        brand_d_condition.Attribute='Brand D'
        brand_d=helper.add_unit(
            electronics_subdivision,
            brand_d_condition,
            0.35,
            False
        )

        other_electronics_brands_condition=campaign_service.factory.create('ProductCondition')
        other_electronics_brands_condition.Operand='Brand'
        other_electronics_brands_condition.Attribute=None
        other_electronics_brands=helper.add_unit(
            electronics_subdivision,
            other_electronics_brands_condition,
            0.35,
            False
        )

        output_status_message("-----\nApplyProductPartitionActions:")
        output_status_message(
            "Updating the product partition group to refine Electronics (CategoryL1) with 3 child nodes..."
        )
        apply_product_partition_actions_response=campaign_service.ApplyProductPartitionActions(
            CriterionActions=helper.partition_actions
        )
                
        output_status_message("-----\nGetAdGroupCriterionsByIds:")
        ad_group_criterions=campaign_service.GetAdGroupCriterionsByIds(
            AdGroupId=ad_group_ids['long'][0],
            AdGroupCriterionIds=None,
            CriterionType='ProductPartition'
        )
        
        #The product partition group tree now has 12 nodes, including the children of Electronics (CategoryL1):
                 
        #All other (Root Node)
        #|
        #+-- Animals & Pet Supplies (CategoryL1)
        #|    |
        #|    +-- Pet Supplies (CategoryL2)
        #|    |    |
        #|    |    +-- Brand A
        #|    |    |    
        #|    |    +-- Brand B
        #|    |    |    
        #|    |    +-- All other (Brand)
        #|    |         
        #|    +-- All other (CategoryL2)
        #|        
        #+-- Electronics (CategoryL1)
        #|    |
        #|    +-- Brand C (Brand)
        #|    |
        #|    +-- Brand D (Brand)
        #|    |
        #|    +-- All other (Brand)
        #|   
        #+-- All other (CategoryL1)        

        output_status_message(
            "The product partition group tree now has 12 nodes, including the children of Electronics (CategoryL1): \n"
        )
        output_product_partitions(ad_group_criterions)
        
        #Create a product ad. You must add at least one product ad to the ad group. 
        #The product ad identifier can be used for reporting analytics.
        #Use Merchant Promotions if you want tags to appear at the bottom of your product ad 
        #as "special offer" links, helping to increase customer engagement. For details
        #on Merchant Promotions see https://help.bingads.microsoft.com/#apex/3/en/56805/0.
        
        ads=campaign_service.factory.create('ArrayOfAd')
        product_ad=set_elements_to_none(campaign_service.factory.create('ProductAd'))
        product_ad.Type='Product'
        product_ad.Status=None
        product_ad.EditorialStatus=None
        ads.Ad.append(product_ad)

        output_status_message("-----\nAddAds:")
        add_ads_response=campaign_service.AddAds(
            AdGroupId=ad_group_ids['long'][0],
            Ads=ads
        )
        ad_ids={
            'long': add_ads_response.AdIds['long'] if add_ads_response.AdIds['long'] else None
        }
        output_status_message("AdIds:")
        output_array_of_long(ad_ids)
        output_status_message("PartialErrors:")
        output_array_of_batcherror(add_ads_response.PartialErrors)
        
        # Delete the campaign and everything it contains e.g., ad groups and ads.

        output_status_message("-----\nDeleteCampaigns:")
        campaign_service.DeleteCampaigns(
            AccountId=authorization_data.account_id,
            CampaignIds=campaign_ids
        )
        output_status_message("Deleted Campaign Id {0}".format(campaign_ids['long'][0]))

    except WebFault as ex:
        output_webfault_errors(ex)
    except Exception as ex:
        output_status_message(ex)

def get_root_node(ad_group_criterions):
    """
    Returns the root node of a tree. This operation assumes that a complete 
    product partition tree is provided for one ad group. The node that has
    null ParentCriterionId is the root node.

    :param ad_group_criterions: The ad group criterions that contain the product partition tree.
    :type ad_group_criterions: ArrayOfAdGroupCriterion
    :return: The ad group criterion that was added to the list of PartitionActions.
    :rtype: AdGroupCriterion

    """

    root_node=None
    for ad_group_criterion in ad_group_criterions['AdGroupCriterion']:
        if ad_group_criterion.Criterion.ParentCriterionId is None:
            root_node=ad_group_criterion
            break

    return root_node

def output_product_partitions(ad_group_criterions):
    """
    Outputs the list of AdGroupCriterion, formatted as a tree. 
    Each AdGroupCriterion must be either a BiddableAdGroupCriterion or NegativeAdGroupCriterion. 
    To ensure the complete tree is represented, you should first call GetAdGroupCriterionsByIds 
    where CriterionTypeFilter is ProductPartition, and pass the returned list of AdGroupCriterion to this method. 

    :param ad_group_criterions: The list of ad group criterions to output formatted as a tree.
    :type ad_group_criterions: ArrayOfAdGroupCriterion

    """

    # Set up the tree for output

    child_branches={}
    tree_root=None

    for ad_group_criterion in ad_group_criterions['AdGroupCriterion']:
        partition=ad_group_criterion.Criterion
        child_branches[ad_group_criterion.Id]=campaign_service.factory.create('ArrayOfAdGroupCriterion')

        # The product partition with ParentCriterionId set to null is the root node.
        if partition.ParentCriterionId is not None:
            child_branches[partition.ParentCriterionId].AdGroupCriterion.append(ad_group_criterion)
        else:
            tree_root=ad_group_criterion

    # Outputs the tree root node and any children recursively
    output_product_partition_tree(tree_root, child_branches, 0)

def output_product_partition_tree(node, child_branches, tree_level):
    """
    Outputs the details of the specified product partition node, 
    and passes any children to itself recursively.

    :param node: The node to output, whether a Subdivision or Unit.
    :type node: AdGroupCriterion
    :param child_branches: The child branches or nodes if any exist.
    :type child_branches: dict{long, ArrayOfAdGroupCriterion}
    :param tree_level: The number of descendents from the tree root node. 
     Used by this operation to format the tree structure output.
    :type tree_level: int

    """

    pad=''
    for i in range(0, tree_level):
        pad=pad + '\t'
    output_status_message("{0}{1}".format(
        pad,
        node.Criterion.PartitionType)
    )

    output_status_message("{0}ParentCriterionId: {1}".format(
        pad,
        node.Criterion.ParentCriterionId)
    )

    output_status_message("{0}Id: {1}".format(
        pad,
        node.Id)
    )

    if node.Criterion.PartitionType == 'Unit':
        if node.Type == 'BiddableAdGroupCriterion':
            output_status_message("{0}Bid Amount: {1}".format(
                pad,
                node.CriterionBid.Amount)
            )
        elif node.Type == 'NegativeAdGroupCriterion':
            output_status_message("{0}Not Bidding on this Condition".format(
                pad)
            )
           

    null_attribute="(All other)" if node.Criterion.ParentCriterionId is not None else "(Tree Root)"
    output_status_message("{0}Attribute: {1}".format(
        pad,
        null_attribute if node.Criterion.Condition.Attribute is None else node.Criterion.Condition.Attribute)
    )

    output_status_message("{0}Operand: {1}\n".format(
        pad,
        node.Criterion.Condition.Operand)
    )

    for child_node in child_branches[node.Id]['AdGroupCriterion']:
        output_product_partition_tree(child_node, child_branches, tree_level + 1)

class PartitionActionHelper:
    """ 
    Helper class used to maintain a list of product partition actions for an ad group.
    The list of partition actions can be passed to the Bing Ads ApplyProductPartitionActions service operation.
    """

    def __init__(self,
                 ad_group_id):
        """ 
        Initialize an instance of this class.

        :param ad_group_id: Each criterion is associated with the same ad group.
        :type ad_group_id: long
        
        """

        self._ad_group_id=ad_group_id
        self._reference_id=-1
        self._partition_actions=campaign_service.factory.create('ArrayOfAdGroupCriterionAction')

    @property
    def partition_actions(self):
        """ 
        The list of partition actions that can be passed to the Bing Ads ApplyProductPartitionActions service operation.

        :rtype: ArrayOfAdGroupCriterionAction
        """

        return self._partition_actions

    def add_subdivision(self, parent, condition):
        """ 
        Sets the Add action for a new BiddableAdGroupCriterion corresponding to the specified ProductCondition, 
        and adds it to the helper's list of AdGroupCriterionAction.

        :param parent: The parent of the product partition subdivision that you want to add.
        :type parent: AdGroupCriterion
        :param condition: The condition or product filter for the new product partition.
        :type condition: ProductCondition
        :return: The ad group criterion that was added to the list of PartitionActions.
        :rtype: AdGroupCriterion
        """

        biddable_ad_group_criterion=set_elements_to_none(campaign_service.factory.create('BiddableAdGroupCriterion'))
        product_partition=set_elements_to_none(campaign_service.factory.create('ProductPartition'))
        # If the root node is a unit, it would not have a parent
        product_partition.ParentCriterionId=parent.Id if parent is not None else None
        product_partition.Condition=condition
        product_partition.PartitionType='Subdivision'
        biddable_ad_group_criterion.Criterion=product_partition
        biddable_ad_group_criterion.CriterionBid=None
        biddable_ad_group_criterion.AdGroupId=self._ad_group_id
        biddable_ad_group_criterion.Status=None
        if hasattr(biddable_ad_group_criterion, 'EditorialStatus'):
            biddable_ad_group_criterion.EditorialStatus=None
        biddable_ad_group_criterion.Id=self._reference_id
        self._reference_id=self._reference_id
        self._reference_id-=1

        partition_action=set_elements_to_none(campaign_service.factory.create('AdGroupCriterionAction'))
        partition_action.Action='Add'
        partition_action.AdGroupCriterion=biddable_ad_group_criterion
        self._partition_actions.AdGroupCriterionAction.append(partition_action)

        return biddable_ad_group_criterion

    def add_unit(self, parent, condition, bid_amount, is_negative=False):
        """ 
        Sets the Add action for a new AdGroupCriterion corresponding to the specified ProductCondition, 
        and adds it to the helper's list of AdGroupCriterionAction.

        :param parent: The parent of the product partition unit that you want to add.
        :type parent: AdGroupCriterion
        :param condition: The condition or product filter for the new product partition.
        :type condition: ProductCondition
        :param bid_amount: The bid amount for the new product partition.
        :type bid_amount: double
        :param is_negative: (Optional) Indicates whether or not to add a NegativeAdGroupCriterion. 
         The default value is False, in which case a BiddableAdGroupCriterion will be added.
        :type is_negative: bool
        :return: The ad group criterion that was added to the list of PartitionActions.
        :rtype: AdGroupCriterion
        """

        ad_group_criterion=None
        if is_negative:
            ad_group_criterion=set_elements_to_none(campaign_service.factory.create('NegativeAdGroupCriterion'))
        else:
            ad_group_criterion=set_elements_to_none(campaign_service.factory.create('BiddableAdGroupCriterion'))
            fixed_bid=set_elements_to_none(campaign_service.factory.create('FixedBid'))
            fixed_bid.Amount=bid_amount
            ad_group_criterion.CriterionBid=fixed_bid
            
        ad_group_criterion.AdGroupId=self._ad_group_id
        if hasattr(ad_group_criterion, 'EditorialStatus'):
            ad_group_criterion.EditorialStatus=None
        ad_group_criterion.Status=None

        product_partition=set_elements_to_none(campaign_service.factory.create('ProductPartition'))
        # If the root node is a unit, it would not have a parent
        product_partition.ParentCriterionId=parent.Id if parent is not None else None
        product_partition.Condition=condition
        product_partition.PartitionType='Unit'
        ad_group_criterion.Criterion=product_partition=product_partition

        partition_action=set_elements_to_none(campaign_service.factory.create('AdGroupCriterionAction'))
        partition_action.Action='Add'
        partition_action.AdGroupCriterion=ad_group_criterion
        self._partition_actions.AdGroupCriterionAction.append(partition_action)

        return ad_group_criterion

    def delete_partition(self, ad_group_criterion):
        """ 
        Sets the Delete action for the specified AdGroupCriterion, 
        and adds it to the helper's list of AdGroupCriterionAction.

        :param ad_group_criterion: The ad group criterion whose product partition you want to delete.
        :type ad_group_criterion: AdGroupCriterion
        """

        ad_group_criterion.AdGroupId=self._ad_group_id

        partition_action=set_elements_to_none(campaign_service.factory.create('AdGroupCriterionAction'))
        partition_action.Action='Delete'
        partition_action.AdGroupCriterion=ad_group_criterion
        self._partition_actions.AdGroupCriterionAction.append(partition_action)

    def update_partition(self, biddable_ad_group_criterion):
        """ 
        Sets the Update action for the specified BiddableAdGroupCriterion, 
        and adds it to the helper's list of AdGroupCriterionAction. 
        You can only update the CriterionBid, DestinationUrl, Param1, Param2, and Param3 elements 
        of the BiddableAdGroupCriterion. 
        When working with product partitions, youu cannot update the Criterion (ProductPartition). 
        To update a ProductPartition, you must delete the existing node (delete_partition) and 
        add a new one (add_unit or add_subdivision) during the same call to ApplyProductPartitionActions.

        :param ad_group_criterion: The biddable ad group criterion to update.
        :type ad_group_criterion: BiddableAdGroupCriterion
        """

        biddable_ad_group_criterion.AdGroupId=self._ad_group_id

        partition_action=set_elements_to_none(campaign_service.factory.create('AdGroupCriterionAction'))
        partition_action.Action='Update'
        partition_action.AdGroupCriterion=biddable_ad_group_criterion
        self._partition_actions.AdGroupCriterionAction.append(partition_action)

# Main execution
if __name__ == '__main__':

    print("Loading the web service client proxies...")
    
    authorization_data=AuthorizationData(
        account_id=None,
        customer_id=None,
        developer_token=DEVELOPER_TOKEN,
        authentication=None,
    )

    campaign_service=ServiceClient(
        service='CampaignManagementService', 
        version=13,
        authorization_data=authorization_data, 
        environment=ENVIRONMENT,
    )

    authenticate(authorization_data)
        
    main(authorization_data)

See Also

Get Started with the Bing Ads API