Share via


Exemple de code de gestion des produits

Cet exemple montre comment obtenir, ajouter, mettre à jour et supprimer des produits dans le magasin spécifié.

Pour plus d’informations sur les classes liées aux produits utilisées par cet exemple, consultez Products and Product.

Cet exemple utilise le Kit de développement logiciel (SDK) Bing Ads pour l’authentification utilisateur.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using Microsoft.BingAds;
using Newtonsoft.Json;

namespace Products
{
    public class ManageProductExample
    {
        private const string BaseUri = "https://content.api.bingads.microsoft.com/shopping/v9.1";
        private const string BmcUri = BaseUri + "/bmc/{0}";
        private const string ClientState = "ClientStateGoesHere";
        
        private static string ClientId = "<CLIENTIDGOESHERE>";
        private static string DeveloperToken = "<DEVELOPERTOKENGOESHERE>";
        private static string RefreshToken = "<REFRESHTOKENGOESHERE>";
        private static string MerchantId = "<STOREIDGOESHERE>";

        private static OAuthDesktopMobileAuthCodeGrant OAuth;

        public static void Main()
        {
            try
            {
                OAuth = AuthenticateWithOAuth();

                // Get the default catalog.
                // Note that getting catalog information is an expensive call and should be limited. The example
                // includes this call simply to demonstrate using the call. If you don't specify a catalog, the
                // product is automatically written to the default catalog, so getting the catalog ID is only useful
                // if you're adding the product to a non-default catalog. 

                Catalog defaultCatalog = ListCatalogs().FirstOrDefault(cat => cat.IsDefault == true);

                // Add a product to the catalog
                Product testProduct = GetTestProduct("My Test Product ({0})");
                // If catalog id is not specified, the product is added to the default catalog.
                Product addedProduct = AddProduct(defaultCatalog.Id, testProduct);
                Console.WriteLine("*** Added product to catalog (catalog.Id=" + defaultCatalog.Id + ", product.Id=" + addedProduct.Id + ")***");
                Print(addedProduct);
                Console.WriteLine("*** / End of added product (catalog.Id=" + defaultCatalog.Id + ", product.Id=" + addedProduct.Id + ")***");
                Console.WriteLine();

                // Retrieve a product by id
                Product retrievedProduct = GetProduct(addedProduct.Id);
                Console.WriteLine("*** Retrieved product (product.Id=" + retrievedProduct.Id + ")***");
                Print(retrievedProduct);
                Console.WriteLine("*** / End retrieved product (product.Id=" + retrievedProduct.Id + ")***");
                Console.WriteLine();

                // List products
                Console.WriteLine("*** Listing products ***");
                foreach (Product product in ListProducts())
                {
                    Print(product);
                }
                Console.WriteLine("*** / End of listing products ***");
                Console.WriteLine();

                // Delete product
                Console.WriteLine("*** Deleting Product (" + addedProduct.Id + ") ***");
                DeleteProduct(addedProduct.Id);
                Console.WriteLine("*** / Deleting Product Done (" + addedProduct.Id + ") ***");
            }
            // Catch authentication exceptions
            catch (OAuthTokenRequestException ex)
            {
                OutputStatusMessage(string.Format("OAuthTokenRequestException Message:\n{0}", ex.Message));
                if (ex.Details != null)
                {
                    OutputStatusMessage(string.Format("OAuthTokenRequestException Details:\nError: {0}\nDescription: {1}",
                    ex.Details.Error, ex.Details.Description));
                }
            }
            catch (HttpRequestException ex)
            {
                OutputStatusMessage(ex.Message);
            }

            Console.Write("Press enter to exit");
            Console.ReadLine();
        }

        // List catalogs
        public static string CatalogsUri = BmcUri + "/catalogs";
        public static string CatalogUri = CatalogsUri + "/{1}";        
        public static IEnumerable<Catalog> ListCatalogs()
        {
            string url = string.Format(CatalogsUri, MerchantId);
            CatalogCollection catalogs = Request<CatalogCollection>("GET", url);
            return catalogs?.Catalogs ?? new List<Catalog>();
        }

        // List products
        public static string ListUri = BmcUri + "/products";
        public static string ListQueryString = "?max-results=2&alt=json"; // max-results set to 2 to test paging, this would be set to a higher value in a real world scenario based on your needs
        public static string ListQueryStringWithStartToken = "?max-results=2&alt=json&start-token={1}";
        public static IEnumerable<Product> ListProducts()
        {
            string url = string.Format(ListUri + ListQueryString, MerchantId);
            while (!string.IsNullOrEmpty(url))
            {
                ProductCollection productCollection = Request<ProductCollection>("GET", url);
                foreach (Product product in productCollection.Resources)
                {
                    yield return product;
                }

                if (!string.IsNullOrEmpty(productCollection.nextPageToken))
                {
                    url = string.Format(ListUri + ListQueryStringWithStartToken, MerchantId, productCollection.nextPageToken);
                }
                else
                {
                    url = null;
                }
            }
        }

        // Add
        public static string AddProductUri = BmcUri + "/products";
        public static string AddProductQueryString = "?bmc-catalog-id={1}&alt=json";
        public static Product AddProduct(ulong catalogId, Product product)
        {
            string url = string.Format(AddProductUri + AddProductQueryString, MerchantId, catalogId);
            return Request<Product>("POST", url, product);
        }

        // Get
        public static string GetUri = BmcUri + "/products/{1}";
        public static string GetQueryString = "?alt=json";
        public static Product GetProduct(string productId)
        {
            string url = string.Format(GetUri + GetQueryString, MerchantId, productId);
            return Request<Product>("GET", url);
        }

        // Delete
        public static string DeletetUri = BmcUri + "/products/{1}";
        public static string DeleteQueryString = "?alt=json";
        public static void DeleteProduct(string productId)
        {
            string url = string.Format(DeletetUri + DeleteQueryString, MerchantId, productId);
            ContentError conentError = Request<ContentError>("DELETE", url);
            if (conentError != null)
            {
                throw new Exception(conentError.Error.ToString());
            }
        }

        private static Product GetTestProduct(string titleFormat)
        {
            string id = Guid.NewGuid().ToString();
            return new Product()
            {
                Availability = "in stock",
                Channel = ProductChannel.Online.ToString(),
                Condition = ProductCondition.New.ToString(),
                ContentLanguage = "en",
                Link = "http://www.contoso.com/apperal/men/tshirts.htm",
                ImageLink = "http://www.contoso.com/pics/tees.jpg",
                OfferId = id,
                Price = new Price()
                {
                    Currency = "USD",
                    Value = (decimal)1205.00
                },
                TargetCountry = "US",
                Title = string.Format(titleFormat, id),
                IdentifierExists = false,
                ExpirationDate = DateTime.UtcNow.AddDays(45).ToString("O")
            };
        }

        /// <summary>
        /// Use reflection to output the properties of the specified
        /// value to the console
        /// </summary>
        /// <param name="value"></param>
        private static void Print(object value)
        {
            Type type = value.GetType();
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                Console.WriteLine("\t{0}: {1}", property.Name, property.GetValue(value));
            }
        }

        protected static T Request<T>(string method, string url, T instance = default(T))
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = method;
                request.Headers = new WebHeaderCollection
                {
                    { "AuthenticationToken", OAuth.OAuthTokens.AccessToken },
                    { "DeveloperToken", DeveloperToken }
                };
                request.ContentType = "application/json";

                if(instance != null)
                {
                    string json = JsonConvert.SerializeObject(instance);
                    request.ContentLength = json.Length;
                    using (Stream requestStream = request.GetRequestStream())
                    {
                        StreamWriter writer = new StreamWriter(requestStream);
                        writer.Write(json);
                        writer.Close();
                    }
                }

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                T result = default(T);
                using (Stream responseStream = response.GetResponseStream())
                {
                    var reader = new StreamReader(responseStream);
                    var jsonOut = reader.ReadToEnd();
                    reader.Close();
                    result = JsonConvert.DeserializeObject<T>(jsonOut);
                }

                return result;
            }
            catch (WebException e)
            {
                HandleWebException(e);
                return default(T);
            }
        }

        private static void HandleWebException(WebException webException)
        {
            Console.WriteLine("\n" + webException.Message);

            HttpWebResponse response = (HttpWebResponse)webException.Response;

            // If the request is bad, the API returns the errors in the 
            // body of the request. For cases where the path may be valid, but
            // the resource does not belong to the user, the API returns not found.

            if (HttpStatusCode.BadRequest == response.StatusCode ||
                HttpStatusCode.NotFound == response.StatusCode ||
                HttpStatusCode.InternalServerError == response.StatusCode)
            {
                using (Stream stream = response.GetResponseStream())
                {
                    StreamReader reader = new StreamReader(stream);
                    string json = reader.ReadToEnd();
                    reader.Close();

                    try
                    {
                        ContentError contentError = JsonConvert.DeserializeObject<ContentError>(json);

                        Console.WriteLine("HTTP status code: " + contentError.Error.Code);

                        foreach (Error error in contentError.Error.Errors)
                        {
                            Console.WriteLine("reason: {0}\nmessage: {1}\nlocation type: {2}\nlocation: {3}\n",
                                error.Reason, error.Message, error.LocationType, error.Location);
                        }
                    }
                    catch (Exception deserializeError)
                    {
                        // This case occurs when the path is not valid.

                        if (HttpStatusCode.NotFound == response.StatusCode)
                        {
                            Console.WriteLine("Path not found: " + response.ResponseUri);
                        }
                        else
                        {
                            Console.WriteLine(deserializeError.Message);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Write to the console by default.
        /// </summary>
        /// <param name="msg">The message sent to console output.</param>
        private static void OutputStatusMessage(String msg)
        {
            Console.WriteLine(msg);
        }

        private static OAuthDesktopMobileAuthCodeGrant AuthenticateWithOAuth()
        {
            var oAuthDesktopMobileAuthCodeGrant = new OAuthDesktopMobileAuthCodeGrant(ClientId);

            // It is recommended that you specify a non guessable 'state' request parameter to help prevent
            // cross site request forgery (CSRF). 
            oAuthDesktopMobileAuthCodeGrant.State = ClientState;

            string refreshToken;

            // If you have previously securely stored a refresh token, try to use it.
            if (GetRefreshToken(out refreshToken))
            {
                AuthorizeWithRefreshTokenAsync(oAuthDesktopMobileAuthCodeGrant, refreshToken).Wait();
            }
            else
            {
                // You must request user consent at least once through a web browser control. 
                // Call the GetAuthorizationEndpoint method of the OAuthDesktopMobileAuthCodeGrant instance that you created above.
                Console.WriteLine(string.Format(
                    "The Microsoft Advertising user must provide consent for your application to access their accounts.\n" +
                    "Open a new web browser and navigate to {0}.\n\n" +
                    "After the user has granted consent in the web browser for the application to access their accounts, " +
                    "please enter the response URI that includes the authorization 'code' parameter: \n", oAuthDesktopMobileAuthCodeGrant.GetAuthorizationEndpoint()));

                // Request access and refresh tokens using the URI that you provided manually during program execution.
                var responseUri = new Uri(Console.ReadLine());

                if (oAuthDesktopMobileAuthCodeGrant.State != ClientState)
                    throw new HttpRequestException("The OAuth response state does not match the client request state.");

                oAuthDesktopMobileAuthCodeGrant.RequestAccessAndRefreshTokensAsync(responseUri).Wait();
            }

            // It is important to save the most recent refresh token whenever new OAuth tokens are received. 
            // You will want to subscribe to the NewOAuthTokensReceived event handler. 
            // When calling Microsoft Advertising services with ServiceClient<TService>, BulkServiceManager, or ReportingServiceManager, 
            // each instance will refresh your access token automatically if they detect the AuthenticationTokenExpired (109) error code. 
            oAuthDesktopMobileAuthCodeGrant.NewOAuthTokensReceived +=
                    (sender, tokens) => SaveRefreshToken(tokens.NewRefreshToken);

            return oAuthDesktopMobileAuthCodeGrant;
        }

        private static bool GetRefreshToken(out string refreshToken)
        {
            var protectedToken = RefreshToken;

            if (string.IsNullOrEmpty(protectedToken))
            {
                refreshToken = null;
                return false;
            }

            try
            {
                refreshToken = protectedToken.Unprotect();
                return true;
            }
            catch (CryptographicException)
            {
                refreshToken = null;
                return false;
            }
            catch (FormatException)
            {
                refreshToken = null;
                return false;
            }
        }

        private static Task<OAuthTokens> AuthorizeWithRefreshTokenAsync(OAuthDesktopMobileAuthCodeGrant authentication, string refreshToken)
        {
            return authentication.RequestAccessAndRefreshTokensAsync(refreshToken);
        }

        private static void SaveRefreshToken(string newRefreshtoken)
        {
            if (newRefreshtoken != null)
            {
                Settings.Default["RefreshToken"] = newRefreshtoken.Protect();
                Settings.Default.Save();
            }
        }
    }

    /// <summary>
    /// This static class can be used to access and protect a string.
    /// </summary>
    public static class StringProtection
    {
        public static string Protect(this string sourceString)
        {
            var sourceBytes = Encoding.Unicode.GetBytes(sourceString);

            var encryptedBytes = ProtectedData.Protect(sourceBytes, null, DataProtectionScope.CurrentUser);

            return Convert.ToBase64String(encryptedBytes);
        }

        public static string Unprotect(this string protectedString)
        {
            var protectedBytes = Convert.FromBase64String(protectedString);

            var unprotectedBytes = ProtectedData.Unprotect(protectedBytes, null, DataProtectionScope.CurrentUser);

            return Encoding.Unicode.GetString(unprotectedBytes);
        }
    }

    // Data transfer objects
    public class Catalog
    {
        [JsonProperty("id", DefaultValueHandling = DefaultValueHandling.Ignore)]
        public ulong Id { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("market", DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string Market { get; set; }

        [JsonProperty("isPublishingEnabled")]
        public Boolean IsPublishingEnabled { get; set; }

        [JsonProperty("isDefault", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        public Boolean IsDefault { get; set; }
    }

    public class CatalogCollection
    {
        [JsonProperty("catalogs")]
        public List<Catalog> Catalogs { get; set; }
    }

    public class ContentError
    {
        [JsonProperty("error")]
        public ErrorCollection Error { get; set; }
    }

    public class Error
    {
        [JsonProperty("location")]
        public string Location { get; set; }

        [JsonProperty("locationType")]
        public string LocationType { get; set; }

        [JsonProperty("domain")]
        public string Domain { get; set; }

        [JsonProperty("message")]
        public string Message { get; set; }

        [JsonProperty("reason")]
        public string Reason { get; set; }

        public override string ToString()
        {
            return $"Location: {Location}, LocationType: {LocationType}, Domain: {Domain}, Message: {Message}, Reason: {Reason}";
        }
    }

    public class ErrorCollection
    {
        [JsonProperty("code")]
        public string Code { get; set; }

        [JsonProperty("errors")]
        public List<Error> Errors { get; set; }

        [JsonProperty("message")]
        public string Message { get; set; }

        public override string ToString()
        {
            return string.Format("{0}: {1}\r\n{2}", Code, Message, string.Join("\r\n\t", (Errors ?? new List<Error>()).Select(e => e.ToString())));
        }
    }

    public class Price
    {
        [JsonProperty("currency")]
        public string Currency { get; set; }

        [JsonProperty("value")]
        public decimal? Value { get; set; }
    }

    public class Product
    {
        public Product() { }

        [JsonProperty("additionalImageLinks")]
        public List<string> AdditionalImageLinks { get; set; }

        [JsonProperty("adult")]
        public bool? IsAdult { get; set; }

        [JsonProperty("adwordsGrouping")]
        public string AdwordsGrouping { get; set; }

        [JsonProperty("adwordsLabels")]
        public List<string> AdwordsLabels { get; set; }

        [JsonProperty("adwordsRedirect")]
        public string AdwordsRedirect { get; set; }

        [JsonProperty("ageGroup")]
        public string AgeGroup { get; set; }

        [JsonProperty("availability")]
        public string Availability { get; set; }

        [JsonProperty("availabilityDate")]
        public string AvailabilityDate { get; set; }

        [JsonProperty("brand")]
        public string Brand { get; set; }

        [JsonProperty("channel")]
        public string Channel { get; set; }

        [JsonProperty("color")]
        public string Color { get; set; }

        [JsonProperty("condition")]
        public string Condition { get; set; }


        [JsonProperty("contentLanguage")]
        public string ContentLanguage { get; set; }

        [JsonProperty("customAttributes")]
        public List<ProductCustomAttribute> CustomAttributes { get; set; }

        [JsonProperty("customGroups")]
        public List<ProductCustomGroup> CustomGroups { get; set; }

        [JsonProperty("customLabel0")]
        public string CustomLabel0 { get; set; }

        [JsonProperty("customLabel1")]
        public string CustomLabel1 { get; set; }

        [JsonProperty("customLabel2")]
        public string CustomLabel2 { get; set; }

        [JsonProperty("customLabel3")]
        public string CustomLabel3 { get; set; }

        [JsonProperty("customLabel4")]
        public string CustomLabel4 { get; set; }

        [JsonProperty("description")]
        public string Description { get; set; }

        [JsonProperty("destinations")]
        public List<ProductDestination> Destinations { get; set; }

        [JsonProperty("energyEfficiencyClass")]
        public string EnergyEfficiencyClass { get; set; }

        [JsonProperty("expirationDate")]
        public string ExpirationDate { get; set; }

        [JsonProperty("gender")]
        public string Gender { get; set; }

        [JsonProperty("googleProductCategory")]
        public string GoogleProductCategory { get; set; }

        [JsonProperty("gtin")]
        public string Gtin { get; set; }

        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("identifierExists")]
        public bool? IdentifierExists { get; set; }

        [JsonProperty("imageLink")]
        public string ImageLink { get; set; }

        [JsonProperty("isBundle")]
        public bool? IsBundle { get; set; }

        [JsonProperty("itemGroupId")]
        public string ItemGroupId { get; set; }

        [JsonProperty("kind")] ////For example: "kind":"content#product"
        public string Kind { get; set; }

        [JsonProperty("link")]
        public string Link { get; set; }

        [JsonProperty("material")]
        public string Material { get; set; }

        [JsonProperty("multipack")]
        public ulong? MerchantMultipackQuantity { get; set; }

        [JsonProperty("mobileLink")]
        public string MobileLink { get; set; }

        [JsonProperty("mpn")]
        public string Mpn { get; set; }

        [JsonProperty("offerId")]
        public string OfferId { get; set; }

        [JsonProperty("onlineOnly")]
        public bool? OnlineOnly { get; set; }

        [JsonProperty("pattern")]
        public string Pattern { get; set; }

        [JsonProperty("price")]
        public Price Price { get; set; }

        [JsonProperty("productType")]
        public string ProductType { get; set; }

        [JsonProperty("salePrice")]
        public Price SalePrice { get; set; }

        [JsonProperty("salePriceEffectiveDate")]
        public string SalePriceEffectiveDate { get; set; }

        [JsonProperty("shipping")]
        public List<ProductShipping> Shipping { get; set; }

        [JsonProperty("shippingWeight")]
        public ProductShippingWeight ShippingWeight { get; set; }

        [JsonProperty("shippingLabel")]
        public string ShippingLabel { get; set; }

        [JsonProperty("sizes")]
        public List<string> Sizes { get; set; }

        [JsonProperty("sizeSystem")]
        public string SizeSystem { get; set; }

        [JsonProperty("sizeType")]
        public string SizeType { get; set; }

        [JsonProperty("targetCountry")]
        public string TargetCountry { get; set; }

        [JsonProperty("taxes")]
        public List<ProductTax> Taxes { get; set; }

        [JsonProperty("title")]
        public string Title { get; set; }

        [JsonProperty("unitPricingBaseMeasure")]
        public UnitPricing UnitPricingBaseMeasure { get; set; }

        [JsonProperty("unitPricingMeasure")]
        public UnitPricing UnitPricingMeasure { get; set; }

        [JsonProperty("validatedDestinations")]
        public List<string> ValidatedDestinations { get; set; }
    }

    public enum ProductChannel
    {
        Online,
        Local
    }

    public class ProductCollection
    {
        [JsonProperty("kind")]  // Set to content#productsListResponse
        public string Kind { get; set; }

        [JsonProperty("nextPageToken")]
        public string nextPageToken { get; set; }

        [JsonProperty("resources")]
        public List<Product> Resources { get; set; }
    }

    public enum ProductCondition
    {
        New,
        Refurbished,
        Used
    }

    public class ProductCustomAttribute
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("unit")]
        public string Unit { get; set; }

        [JsonProperty("value")]
        public string Value { get; set; }
    }

    public class ProductCustomGroup
    {
        [JsonProperty("customGroups")]
        public List<ProductCustomAttribute> Attributes { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }
    }

    public class ProductDestination
    {
        [JsonProperty("destinationName")]
        public string DestinationName { get; set; }

        [JsonProperty("intention")]
        public string Intention { get; set; }
    }

    public class ProductShipping
    {
        [JsonProperty("country")]
        public string Country { get; set; }

        [JsonProperty("locationGroupName")]
        public string LocationGroupName { get; set; }

        [JsonProperty("locationId")]
        public long? LocationId { get; set; }

        [JsonProperty("postalCode")]
        public string PostalCode { get; set; }

        [JsonProperty("price")]
        public Price Price { get; set; }

        [JsonProperty("region")]
        public string Region { get; set; }

        [JsonProperty("service")]
        public string Service { get; set; }
    }

    public class ProductShippingWeight
    {
        [JsonProperty("unit")]
        public string Unit { get; set; }

        [JsonProperty("value")]
        public double? Value { get; set; }
    }

    public class ProductTax
    {
        [JsonProperty("country")]
        public string Country { get; set; }

        [JsonProperty("locationId")]
        public long? LocationId { get; set; }

        [JsonProperty("postalCode")]
        public string PostalCode { get; set; }

        [JsonProperty("rate")]
        public double? Rate { get; set; }

        [JsonProperty("region")]
        public string Region { get; set; }

        [JsonProperty("taxShip")]
        public bool? TaxShip { get; set; }
    }

    public class UnitPricing
    {
        [JsonProperty("unit")]
        public string Unit { get; set; }

        [JsonProperty("value")]
        public double Value { get; set; }
    }
// -/ end Data transfer objects
}

package com.microsoft.contentapi.examples;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

import com.microsoft.contentapi.examples.datatransferobjects.*;

public class ManageProductsExample {
    private static String BaseUri = "https://content.api.bingads.microsoft.com/shopping/v9.1";
    private static String BmcUri = BaseUri + "/bmc/%s";
    private static String ClientState = "ClientStateGoesHere";

    private static String ClientId = "<CLIENTIDGOESHERE>";
    private static String DeveloperToken = "<DEVELOPERTOKENGOESHERE>";
    private static String MerchantId = "<STOREIDGOESHERE>";

    private static String authenticationToken = "<AUTHENTICATIONTOKENGOESHERE>";

    public static void main(String args[]) throws Exception {
        try{
            // Get the default catalog
            Catalog defaultCatalog = retrieveDefaultCatalog();

            // Add a product to catalog
            Product testProduct = createTestProduct("My Test Product %s");
            // if catalog id is not specified the product will be added to the default catalog, here we are specifying the default explicitly
            Product addedProduct = AddProduct(defaultCatalog.getId(), testProduct);
            System.out.println("*** Added product to catalog (catalog.Id=" + defaultCatalog.getId() + ", product.Id=" + addedProduct.getId() + ")***");
            Print(addedProduct);
            System.out.println("*** / End of added product (catalog.Id=" + defaultCatalog.getId() + ", product.Id=" + addedProduct.getId() + ")***");
            System.out.println();

            // Retrieve a product by id
            Product retrievedProduct = GetProduct(addedProduct.getId());
            System.out.println("*** Retrieved product (product.Id=" + retrievedProduct.getId() + ")***");
            Print(retrievedProduct);
            System.out.println("*** / End retreived product (product.Id=" + retrievedProduct.getId() + ")***");
            System.out.println();

            // List products
            System.out.println("*** Listing products ***");
            for (Product product : ListProducts())
            {
                Print(product);
            }
            System.out.println("*** / End of listing products ***");
            System.out.println();

            // Delete product
            System.out.println("*** Deleting Product (" + addedProduct.getId() + ") ***");
            DeleteProduct(addedProduct.getId());
            System.out.println("*** / Deleting Product Done (" + addedProduct.getId() + ") ***");
        }catch(Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    public static Catalog retrieveDefaultCatalog() throws IOException {
        List<Catalog> catalogs = ListCatalogs();
        for(Catalog catalog : catalogs){
            if(catalog.getIsDefault() == true){
                return catalog;
            }
        }
        return null;
    }

    // List catalogs
    public static String CatalogsUri = BmcUri + "/catalogs";
    public static String CatalogUri = CatalogsUri + "/%s";
    public static List<Catalog> ListCatalogs() throws IOException {
        String url = String.format(CatalogsUri, MerchantId);
        CatalogCollection catalogs = Request("GET", url, null, CatalogCollection.class);
        return catalogs == null ? new LinkedList<Catalog>() : catalogs.getCatalogs();
    }

    // List products
    public static String ListUri = BmcUri + "/products";
    public static String ListQueryString = "?max-results=2&alt=json"; // max-results set to 2 to test paging, this would be set to a higher value in a real world scenario based on your needs
    public static String ListQueryStringWithStartToken = "?max-results=2&alt=json&start-token=%s";
    public static List<Product> ListProducts() throws IOException {
        String url = String.format(ListUri + ListQueryString, MerchantId);
        LinkedList results = new LinkedList();
        while(url != null){
            ProductCollection productCollection = Request("GET", url, null, ProductCollection.class);
            results.addAll(productCollection.getResources());

            if(productCollection.nextPageToken != null){
                url = String.format(ListUri + ListQueryStringWithStartToken, MerchantId, productCollection.nextPageToken);
            }else{
                url = null;
            }
        }
        return results;
    }

    // Add
    public static String AddProductUri = BmcUri + "/products";
    public static String AddProductQueryString = "?bmc-catalog-id=%s&alt=json";
    public static Product AddProduct(long catalogId, Product product) throws IOException {
        String url = String.format(AddProductUri + AddProductQueryString, MerchantId, catalogId);
        return Request("POST", url, product, Product.class);
    }

    // Get
    public static String GetUri = BmcUri + "/products/%s";
    public static String GetQueryString = "?alt=json";
    public static Product GetProduct(String productId) throws IOException {
        String url = String.format(GetUri + GetQueryString, MerchantId, productId);
        return Request("GET", url, null, Product.class);
    }

    // Delete
    public static String DeletetUri = BmcUri + "/products/%s";
    public static String DeleteQueryString = "?alt=json";
    public static void DeleteProduct(String productId) throws Exception {
        String url = String.format(DeletetUri + DeleteQueryString, MerchantId, productId);
        ContentError contentError = Request("DELETE", url, null, ContentError.class);
        if (contentError != null)
        {
            ErrorCollection error = contentError.getError();
            throw new Exception(error.toString());
        }
    }

    protected static Product createTestProduct(String titleFormat){
        String id = UUID.randomUUID().toString();
        Product product = new Product();
        product.setAvailability("in stock");
        product.setChannel("Online");
        product.setCondition("New");
        product.setContentLanguage("en");
        product.setLink("http://www.contoso.com/apperal/men/tshirts.htm");
        product.setImageLink("http://www.contoso.com/pics/tees.jpg");
        product.setOfferId(id);
        Price price = new Price();
        price.setCurrency("USD");
        price.setValue(1205.00);
        product.setPrice(price);
        product.setTargetCountry("US");
        product.setTitle(String.format(titleFormat, id));
        product.setIdentifierExists(false);

        Calendar c = Calendar.getInstance();
        c.setTime(new Date());
        c.add(Calendar.DATE, 45);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String expiry = dateFormat.format(c.getTime()) + "T17:00:00.0000000Z";
        product.setExpirationDate(expiry);

        return product;
    }

    /***
     * Use reflection to output the gettable properties of the
     * specified value
     * @param value
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    protected static void Print(Object value) throws InvocationTargetException, IllegalAccessException {
        Class<?> clazz = value.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for(Method method : methods){
            String methodName = method.getName();
            if(methodName.startsWith("get")){
                Object propertyValue = method.invoke(value);
                if(propertyValue != null){
                    System.out.println(methodName.substring(3, methodName.length()) + ": " + propertyValue.toString());
                }
            }
        }
    }

    protected static <T> T Request(String method, String uri, T instance, Class<T> clazz) throws IOException {
        T result = null;
        ObjectMapper jsonSerializer = new ObjectMapper();
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        BufferedReader reader = null;

        try{
            URL url = new URL(uri);
            connection = (HttpURLConnection)url.openConnection();
            connection.setRequestMethod(method);
            connection.setRequestProperty("AuthenticationToken", authenticationToken);
            connection.setRequestProperty("DeveloperToken", DeveloperToken);
            connection.setRequestProperty("Content-Type", "application/json");

            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);

            if(instance != null){
                String json = jsonSerializer.writeValueAsString(instance);
                OutputStreamWriter outputStream = new OutputStreamWriter(connection.getOutputStream());
                outputStream.write(json);
                outputStream.close();
            }

            int statusCode = connection.getResponseCode();
            if(statusCode >= HttpURLConnection.HTTP_BAD_REQUEST){
                inputStream = connection.getErrorStream();
            }else{
                inputStream = connection.getInputStream();
            }

            StringBuffer response = new StringBuffer();
            reader = new BufferedReader(new InputStreamReader(inputStream));
            char[] buffer = new char[1024];
            int length = 0;
            while((length = reader.read(buffer)) != -1){
                response.append(new String(buffer, 0, length));
            }
            if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
                if (statusCode < HttpURLConnection.HTTP_INTERNAL_ERROR) {
                    ContentError errors = jsonSerializer.readValue(response.toString(), ContentError.class);
                    throw new Exception(errors.getError().toString());
                }
                else {
                    throw new Exception(response.toString());
                }
            }
            String responseJson = response.toString();
            if(responseJson.length() > 0){
                result = jsonSerializer.readValue(responseJson, clazz);
            }
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }

            if (reader != null) {
                reader.close();
            }

            if (inputStream != null) {
                inputStream.close();
            }
        }

        return result;
    }
}

// Catalog.java
package com.microsoft.contentapi.examples.datatransferobjects;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class Catalog
{
    private long id;
    private String name;
    private String market;
    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean isPublishingEnabled;
    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean isDefault;

    public long getId() { return this.id; }
    public void setId(long value) { this.id = value; }

    public String getName() { return this.name; }
    public void setName(String value) { this.name = value; }

    public String getMarket() { return this.market; }
    public void setMarket(String value) { this.market = value; }

    public Boolean getIsPublishingEnabled() { return this.isPublishingEnabled; }
    public void setIsPublishingEnabled(Boolean value) { this.isPublishingEnabled = value; }

    public Boolean getIsDefault() { return this.isDefault; }
    public void setIsDefault(Boolean value) { this.isDefault = value; }
}

// CatalogCollection.java
package com.microsoft.contentapi.examples.datatransferobjects;

import java.util.List;

public class CatalogCollection
{
    private List<Catalog> catalogs;

    public List<Catalog> getCatalogs() { return this.catalogs; }
    public void setCatalogs(List<Catalog> value) { this.catalogs = value; }
}

// ContentError.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class ContentError
{
    private ErrorCollection error;

    public ErrorCollection getError() { return this.error; }
}

// Error.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class Error
{
    private String location;
    private String locationType;
    private String domain;
    private String message;
    private String reason;

    public String getLocation() { return this.location; }

    public String getLocationType() { return this.locationType; }

    public String getDomain() { return this.domain; }

    public String getMessage() { return this.message; }

    public String getReason() { return this.reason; }
}

// ErrorCollection.java
package com.microsoft.contentapi.examples.datatransferobjects;

import java.util.List;

public class ErrorCollection
{
    private List<Error> errors;

    public List<Error> getErrors() { return this.errors; }

    public String toString(){
        String result = "";
        for(int i = 0; i < errors.size(); i++){
            if(i != 0){
                result += ", ";
            }
            result += errors.get(i).getMessage();
        }
        return result;
    }
}

// Price.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class Price
{
    private String currency;
    private Double value;

    public String getCurrency() { return this.currency; }
    public void setCurrency(String value) { this.currency = value; }

    public Double getValue() { return this.value; }
    public void setValue(Double value) { this.value = value; }
}

// Product.java
package com.microsoft.contentapi.examples.datatransferobjects;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.util.List;

public class Product
{
    private List<String> additionalImageLinks;

    @JsonProperty("adult")
    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean isAdult;
    private String adwordsGrouping;
    private List<String> adwordsLabels;
    private String adwordsRedirect;
    private String ageGroup;
    private String availability;
    private String availabilityDate;
    private String brand;
    private String channel;
    private String color;
    private String condition;
    private String contentLanguage;
    private List<ProductCustomAttribute> customAttributes;
    private List<ProductCustomGroup> customGroups;
    private String customLabel0;
    private String customLabel1;
    private String customLabel2;
    private String customLabel3;
    private String customLabel4;
    private String description;
    private List<ProductDestination> destinations;
    private String energyEfficiencyClass;
    private String expirationDate;
    private String gender;
    private String googleProductCategory;
    private String gtin;
    private String id;

    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean identifierExists;
    private String imageLink;

    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean isBundle;
    private String itemGroupId;
    private String kind;  //For example: "kind":"content#product"
    private String link;
    private String material;

    @JsonProperty("multipack")
    private Long multipackQuantity;
    private String mobileLink;
    private String mpn;
    private String offerId;

    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean onlineOnly;
    private String pattern;
    private Price price;
    private String productType;
    private Price salePrice;
    private String salePriceEffectiveDate;
    private List<ProductShipping> shipping;
    private ProductShippingWeight shippingWeight;
    private String shippingLabel;
    private List<String> sizes;
    private String sizeSystem;
    private String sizeType;
    private String targetCountry;
    private List<ProductTax> taxes;
    private String title;
    private UnitPricing unitPricingBaseMeasure;
    private UnitPricing unitPricingMeasure;
    private List<String> validatedDestinations;



    public List<String> getAdditionalImageLinks() { return this.additionalImageLinks; }
    public void setAdditionalImageLinks(List<String> value) { this.additionalImageLinks = value; }

    public Boolean getIsAdult() { return this.isAdult; }
    public void setIsAdult(Boolean value) { this.isAdult = value; }

    public String getAdwordsGrouping() { return this.adwordsGrouping; }
    public void setAdwordsGrouping(String value) { this.adwordsGrouping = value; }

    public List<String> getAdwordsLabels() { return this.adwordsLabels; }
    public void setAdwordsLabels(List<String> value) { this.adwordsLabels = value; }

    public String getAdwordsRedirect() { return this.adwordsRedirect; }
    public void setAdwordsRedirect(String value) { this.adwordsRedirect = value; }

    public String getAgeGroup() { return this.ageGroup; }
    public void setAgeGroup(String value) { this.ageGroup = value; }

    public String getAvailability() { return this.availability; }
    public void setAvailability(String value) { this.availability = value; }

    public String getAvailabilityDate() { return this.availabilityDate; }
    public void setAvailabilityDate(String value) { this.availabilityDate = value; }

    public String getBrand() { return this.brand; }
    public void setBrand(String value) { this.brand = value; }

    public String getChannel() { return this.channel; }
    public void setChannel(String value) { this.channel = value; }

    public String getColor() { return this.color; }
    public void setColor(String value) { this.color = value; }

    public String getCondition() { return this.condition; }
    public void setCondition(String value) { this.condition = value; }

    public String getContentLanguage() { return this.contentLanguage; }
    public void setContentLanguage(String value) { this.contentLanguage = value; }

    public List<ProductCustomAttribute> getCustomAttributes() { return this.customAttributes; }
    public void setCustomAttributes(List<ProductCustomAttribute> value) { this.customAttributes = value; }

    public List<ProductCustomGroup> getCustomGroups() { return this.customGroups; }
    public void setCustomGroups(List<ProductCustomGroup> value) { this.customGroups = value; }

    public String getCustomLabel0() { return this.customLabel0; }
    public void setCustomLabel0(String value) { this.customLabel0 = value; }

    public String getCustomLabel1() { return this.customLabel1; }
    public void setCustomLabel1(String value) { this.customLabel1 = value; }

    public String getCustomLabel2() { return this.customLabel2; }
    public void setCustomLabel2(String value) { this.customLabel2 = value; }

    public String getCustomLabel3() { return this.customLabel3; }
    public void setCustomLabel3(String value) { this.customLabel3 = value; }

    public String getCustomLabel4() { return this.customLabel4; }
    public void setCustomLabel4(String value) { this.customLabel4 = value; }

    public String getDescription() { return this.description; }
    public void setDescription(String value) { this.description = value; }

    public List<ProductDestination> getDestinations() { return this.destinations; }
    public void setDestinations(List<ProductDestination> value) { this.destinations = value; }

    public String getEnergyEfficiencyClass() { return this.energyEfficiencyClass; }
    public void setEnergyEfficiencyClass(String value) { this.energyEfficiencyClass = value; }

    public String getExpirationDate() { return this.expirationDate; }
    public void setExpirationDate(String value) { this.expirationDate = value; }

    public String getGender() { return this.gender; }
    public void setGender(String value) { this.gender = value; }

    public String getGoogleProductCategory() { return this.googleProductCategory; }
    public void setGoogleProductCategory(String value) { this.googleProductCategory = value; }

    public String getGtin() { return this.gtin; }
    public void setGtin(String value) { this.gtin = value; }

    public String getId() { return this.id; }
    public void setId(String value) { this.id = value; }

    public Boolean getIdentifierExists() { return this.identifierExists; }
    public void setIdentifierExists(Boolean value) { this.identifierExists = value; }

    public String getImageLink() { return this.imageLink; }
    public void setImageLink(String value) { this.imageLink = value; }

    public Boolean getIsBundle() { return this.isBundle; }
    public void setIsBundle(Boolean value) { this.isBundle = value; }

    public String getItemGroupId() { return this.itemGroupId; }
    public void setItemGroupId(String value) { this.itemGroupId = value; }

    public String getKind() { return this.kind; }
    public void setKind(String value) { this.kind = value; }

    public String getLink() { return this.link; }
    public void setLink(String value) { this.link = value; }

    public String getMaterial() { return this.material; }
    public void setMaterial(String value) { this.material = value; }

    public Long getMultipackQuantity() { return this.multipackQuantity; }
    public void setMultipackQuantity(Long value) { this.multipackQuantity = value; }

    public String getMobileLink() { return this.mobileLink; }
    public void setMobileLink(String value) { this.mobileLink = value; }

    public String getMpn() { return this.mpn; }
    public void setMpn(String value) { this.mpn = value; }

    public String getOfferId() { return this.offerId; }
    public void setOfferId(String value) { this.offerId = value; }

    public Boolean getOnlineOnly() { return this.onlineOnly; }
    public void setOnlineOnly(Boolean value) { this.onlineOnly = value; }

    public String getPattern() { return this.pattern; }
    public void setPattern(String value) { this.pattern = value; }

    public Price getPrice() { return this.price; }
    public void setPrice(Price value) { this.price = value; }

    public String getProductType() { return this.productType; }
    public void setProductType(String value) { this.productType = value; }

    public Price getSalePrice() { return this.salePrice; }
    public void setSalePrice(Price value) { this.salePrice = value; }

    public String getSalePriceEffectiveDate() { return this.salePriceEffectiveDate; }
    public void setSalePriceEffectiveDate(String value) { this.salePriceEffectiveDate = value; }

    public List<ProductShipping> getShipping() { return this.shipping; }
    public void setShipping(List<ProductShipping> value) { this.shipping = value; }

    public ProductShippingWeight getShippingWeight() { return this.shippingWeight; }
    public void setShippingWeight(ProductShippingWeight value) { this.shippingWeight = value; }

    public String getShippingLabel() { return this.shippingLabel; }
    public void setShippingLabel(String value) { this.shippingLabel = value; }

    public List<String> getSizes() { return this.sizes; }
    public void setSizes(List<String> value) { this.sizes = value; }

    public String getSizeSystem() { return this.sizeSystem; }
    public void setSizeSystem(String value) { this.sizeSystem = value; }

    public String getSizeType() { return this.sizeType; }
    public void setSizeType(String value) { this.sizeType = value; }

    public String getTargetCountry() { return this.targetCountry; }
    public void setTargetCountry(String value) { this.targetCountry = value; }

    public List<ProductTax> getTaxes() { return this.taxes; }
    public void setTaxes(List<ProductTax> value) { this.taxes = value; }

    public String getTitle() { return this.title; }
    public void setTitle(String value) { this.title = value; }

    public UnitPricing getUnitPricingBaseMeasure() { return this.unitPricingBaseMeasure; }
    public void setUnitPricingBaseMeasure(UnitPricing value) { this.unitPricingBaseMeasure = value; }

    public UnitPricing getUnitPricingMeasure() { return this.unitPricingMeasure; }
    public void setUnitPricingMeasure(UnitPricing value) { this.unitPricingMeasure = value; }

    public List<String> getValidatedDestinations() { return this.validatedDestinations; }
    public void setValidatedDestinations(List<String> value) { this.validatedDestinations = value; }
}

// ProductCollection.java
package com.microsoft.contentapi.examples.datatransferobjects;

import java.util.List;

public class ProductCollection {
    public String kind;
    public String nextPageToken;
    public List<Product> resources;

    public List<Product> getResources(){
        return resources;
    }
    public void setResources(List<Product> value){
        resources = value;
    }
}

// ProductCustomAttribute.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class ProductCustomAttribute
{
    private String name;
    private String type;
    private String unit;
    private String value;

    public String getName() { return this.name; }
    public void setName(String value) { this.name = value; }

    public String getType() { return this.type; }
    public void setType(String value) { this.type = value; }

    public String getUnit() { return this.unit; }
    public void setUnit(String value) { this.unit = value; }

    public String getValue() { return this.value; }
    public void setValue(String value) { this.value = value; }
}

// ProductCustomGroup.java
package com.microsoft.contentapi.examples.datatransferobjects;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

public class ProductCustomGroup
{
    @JsonProperty("customGroups")
    private List<ProductCustomAttribute> attributes;
    private String name;

    public List<ProductCustomAttribute> getAttributes() { return this.attributes; }
    public void setAttributes(List<ProductCustomAttribute> value) { this.attributes = value; }

    public String getName() { return this.name; }
    public void setName(String value) { this.name = value; }
}

// ProductDestination.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class ProductDestination
{
    private String destinationName;
    private String intention;

    public String getDestinationName() { return this.destinationName; }
    public void setDestinationName(String value) { this.destinationName = value; }

    public String getIntention() { return this.intention; }
    public void setIntention(String value) { this.intention = value; }
}

// ProductShippingWeight.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class ProductShippingWeight
{
    private String unit;
    private Double value;

    public String getUnit() { return this.unit; }
    public void setUnit(String value) { this.unit = value; }

    public Double getValue() { return this.value; }
    public void setValue(Double value) { this.value = value; }
}

// ProductTax.java
package com.microsoft.contentapi.examples.datatransferobjects;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

public class ProductTax
{
    private String country;
    private Long locationId;
    private String postalCode;
    private Double rate;
    private String region;

    @JsonDeserialize(using=StringBooleanDeserializer.class)
    private Boolean taxShip;

    public String getCountry() { return this.country; }
    public void setCountry(String value) { this.country = value; }

    public Long getLocationId() { return this.locationId; }
    public void setLocationId(Long value) { this.locationId = value; }

    public String getPostalCode() { return this.postalCode; }
    public void setPostalCode(String value) { this.postalCode = value; }

    public Double getRate() { return this.rate; }
    public void setRate(Double value) { this.rate = value; }

    public String getRegion() { return this.region; }
    public void setRegion(String value) { this.region = value; }

    public Boolean getTaxShip() { return this.taxShip; }
    public void setTaxShip(Boolean value) { this.taxShip = value; }
}

// StringBooleanDeserializer.java
package com.microsoft.contentapi.examples.datatransferobjects;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;

public class StringBooleanDeserializer extends JsonDeserializer<Boolean> {

    public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        return !"False".equals(parser.getText());
    }
}

// UnitPricing.java
package com.microsoft.contentapi.examples.datatransferobjects;

public class UnitPricing
{
    private String unit;
    private Double value;

    public String getUnit() { return this.unit; }
    public void setUnit(String value) { this.unit = value; }

    public Double getValue() { return this.value; }
    public void setValue(Double value) { this.value = value; }
}

"""Content API Manage Products Example"""
import string
import random
from datetime import datetime, timedelta
import json
import requests

BASE_URI = 'https://content.api.bingads.microsoft.com/shopping/v9.1'
BMC_URI = BASE_URI + '/bmc/{0}'

CLIENT_ID = '<CLIENTIDGOESHERE>'
DEV_TOKEN = '<DEVELOPERTOKENGOESHERE>'
MERCHANT_ID = '<STOREIDGOESHERE>'

AUTHENTICATION_TOKEN = '<AUTHENTICATIONTOKENGOESHERE>'

AUTHENTICATION_HEADERS = {'DeveloperToken': DEV_TOKEN, 'AuthenticationToken': AUTHENTICATION_TOKEN}

def main():
    """The main entry point of this example"""

    try:
        # Get the default catalog
        default_catalog = retrieve_default_catalog()

        # Add a product to catalog
        test_product = create_test_product("My Test Product")
        # if catalog id is not specified the product will be added to the default catalog, here we are specifying the default explicitly
        added_product = add_product(default_catalog['id'], test_product)
        print("*** Added product to catalog (catalog.Id=" + str(default_catalog['id']) + ", product.Id=" + str(added_product['id']) + ")***")
        print_json(added_product)
        print("*** / End of added product (catalog.Id=" + str(default_catalog['id']) + ", product.Id=" + str(added_product['id']) + ")***")
        print()
        
        # Retrieve a product by id
        retrieved_product = get_product(added_product['id'])
        print("*** Retrieved product (product.Id=" + str(retrieved_product['id']) + ")***")
        print_json(retrieved_product)
        print("*** / End retrieved product (product.Id=" + str(retrieved_product['id']) + ")***")
        print()

        # List products
        print("*** Listing products ***")
        for product in list_products():
            print_json(product)
        print("*** / End listing producst ***")
        print()

        # Delete product
        print("*** Deleting product (" + str(added_product['id']) + ")***")
        delete_product(added_product['id'])
        print("*** / Deleting product Done (" + str(added_product['id']) + ")***")
    except Exception as ex:
        raise ex

def retrieve_default_catalog():
    """Retrieve the default catalog"""
    catalogs = list_catalogs()
    for catalog in catalogs:
        if catalog['isDefault']:
            return catalog
    return None

# List catalogs
CATALOGS_URI = BMC_URI + "/catalogs"
def list_catalogs():
    """list catalogs for the current merchant"""
    url = CATALOGS_URI.format(MERCHANT_ID)
    response = requests.get(url, headers=AUTHENTICATION_HEADERS)
    response.raise_for_status()
    return json.loads(response.text)['catalogs']

# List products
LIST_PRODUCTS_URI = BMC_URI + "/products"
# max-results set to 2 to test paging, this would be set to a
# higher value in a real world scenario based on your needs
LIST_PRODUCTS_QUERYSTRING = "?max-results=2&alt=json"
LIST_PRODUCST_START_TOKEN_QUERYSTRING = "?max-results=2&alt=json&start-token={1}"
def list_products(next_page_token=None):
    """List products"""    
    url = (LIST_PRODUCTS_URI + LIST_PRODUCTS_QUERYSTRING).format(MERCHANT_ID)
    results = []

    while url is not None:
        response = requests.get(url, headers=AUTHENTICATION_HEADERS)
        response.raise_for_status()
        products_response = json.loads(response.text)
        for product in products_response['resources']:
            results.append(product)
        if 'nextPageToken' in products_response:
            url = (LIST_PRODUCTS_URI + LIST_PRODUCST_START_TOKEN_QUERYSTRING).format(MERCHANT_ID, products_response['nextPageToken'])
        else:
            url = None
    return results   

ADD_PRODUCT_URI = BMC_URI + "/products"
ADD_PRODUCT_QUERY_STRING = "?bmc-catalog-id={1}&alt=json"
def add_product(catalog_id, product):
    """Add a product"""
    url = (ADD_PRODUCT_URI + ADD_PRODUCT_QUERY_STRING).format(MERCHANT_ID, catalog_id)
    response = requests.post(url, headers=AUTHENTICATION_HEADERS, data=json.dumps(product))
    response.raise_for_status()
    return json.loads(response.text)

GET_PRODUCT_URI = BMC_URI + "/products/{1}"
GET_PRODUCT_QUERY_STRING = "?alt=json"
def get_product(product_id):
    """Get an existing product"""
    url = (GET_PRODUCT_URI + GET_PRODUCT_QUERY_STRING).format(MERCHANT_ID, product_id)
    response = requests.get(url, headers=AUTHENTICATION_HEADERS)
    response.raise_for_status()
    return json.loads(response.text)

DELETE_PRODUCT_URI = BMC_URI + "/products/{1}"
DELETE_PRODUCT_QUERY_STRING = "?alt=json"
def delete_product(product_id):
    """Delete a product"""
    url = (DELETE_PRODUCT_URI + DELETE_PRODUCT_QUERY_STRING).format(MERCHANT_ID, product_id)
    response = requests.delete(url, headers=AUTHENTICATION_HEADERS)
    response.raise_for_status()

def print_json(obj):
    """Print the object as json"""
    print(json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': ')))

def create_test_product(title_prefix):
    """
    Create and return an in memory product for testing.
    You will want to set the values as appropriate for your purposes.
    """
    return {
        'offerId': 'YourUniqueId(' + random_string(16) + ')',
        'title': title_prefix + '(' + random_string(4) + ')',
        'availability': 'in stock',
        'channel': 'Online',
        'condition': 'New',
        'contentLanguage': 'en',
        'link': 'http://www.contoso.com/apperal/men/tshirts.htm',
        'imageLink': 'http://www.contoso.com/pics/tees.jpg',
        'price': {
            'currency': 'USD',
            'value': 1205.00
        },
        'targetCountry': 'US',
        'identifierExists': False,
        'expirationDate': (datetime.utcnow()+timedelta(days=45)).strftime("%Y-%m-%dT%H:%M:%SZ")
    }

def random_string(length=6):
    """Get a random string of characters of the specified length"""
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))

# Main execution
if __name__ == '__main__':
    main()

<?php
$clientId = '<CLIENTIDGOESHERE>';
$devToken = '<DEVELOPERTOKENGOESHERE>';
$merchantId = '<STOREIDGOESHERE>';
$authenticationToken = '<AUTHENTICATIONTOKENGOESHERE>';

$baseUri = 'https://content.api.bingads.microsoft.com/shopping/v9.1';
$bmcUri = $baseUri . '/bmc/%s';
$catalogsUri = $bmcUri . "/catalogs";
$catalogUri = $catalogsUri . "/%s";

$productsUri = $bmcUri . '/products';
$productUri = $productsUri . '/%s';

$defaultCatalog = retrieve_default_catalog();

# Add a product to a catalog
$testProduct = create_test_product('My Test Product');
$addedProduct = add_product($defaultCatalog['id'], $testProduct);
echo("*** Added product to catalog (catalog id = " . $defaultCatalog['id'] . ", product id = " . $addedProduct['id'] . ")***\r\n");
printObject($addedProduct);
echo("*** / End of added product to catalog (catalog id = " . $defaultCatalog['id'] . ", product id = " . $addedProduct['id'] . ")***\r\n");

# Retrieve a product
$retrievedProduct = get_product($addedProduct['id']);
echo("*** Retrieved product (product id = " . $retrievedProduct['id'] . ")***\r\n");
printObject($retrievedProduct);
echo("*** / End of retrieved product (product id = " . $retrievedProduct['id'] . ")***\r\n");

# List products
$products = list_products();
echo("*** Listing products ***\r\n");
foreach ($products as $index => $value) {
    printObject($value);
}
echo("*** / End of listing products ***\r\n");

echo("*** Deleting product (" . $addedProduct['id'] . ")***\r\n");
delete_product($addedProduct['id']);
echo("*** / Deleting product done (" . $addedProduct['id'] . ")***\r\n");

function retrieve_default_catalog(){
    $catalogs = list_catalogs();
    $count = count($catalogs);
    for ($i = 0; $i < $count; ++$i){
        $catalog = $catalogs[$i];
        if ($catalog['isDefault']) {
            return $catalog;
        }
    }
}

function list_catalogs(){
    global $merchantId, $catalogsUri;

    $url = sprintf($catalogsUri, $merchantId);
    $result = request('GET', $url);
    if ($result === FALSE) {  
        throw new Exception(var_dump($result));
    } else {
        $catalogs = json_decode($result, TRUE)["catalogs"];
        return $catalogs;
    }    
}

function list_products($nextPageToken = null){
    global $merchantId, $productsUri;
    # max-results set to 2 to test paging, this would be set to a
    # higher value in a real world scenario based on your needs.
    $productsQueryString = '?max-results=2&alt=json';
    $productsStartTokenQueryString = $productsQueryString . '&start-token=%s';

    $url = sprintf($productsUri . $productsQueryString, $merchantId);
    $results = array();
    while ($url !== null) {
        $result = request('GET', $url);
        if ($result === FALSE) {  
            throw new Exception(var_dump($result));
        }
        $productsResponse = json_decode($result, TRUE);
        
        foreach ($productsResponse['resources'] as $key => $value) {
            array_push($results, $value);
        }
        if ($productsResponse !== null && array_key_exists('nextPageToken', $productsResponse)) {
            $url = sprintf($productsUri . $productsStartTokenQueryString, $merchantId, $productsResponse['nextPageToken']);
        } else {
            $url = null;
        }
    }
    return $results;
}

function add_product($catalogId, $product){
    global $merchantId, $productsUri;
    
    $url = sprintf($productsUri . '?bmc-catalog-id=%s&alt=json', $merchantId, $catalogId);
    $result = request('POST', $url, $product);
    if ($result === FALSE) {  
        throw new Exception(var_dump($result));
    } 
    return json_decode($result, TRUE);    
}

function get_product($productId){    
    global $merchantId, $productUri;
    $url = sprintf($productUri . "?alt=json", $merchantId, $productId);
    $result = request('GET', $url);
    if ($result === FALSE) {  
        throw new Exception(var_dump($result));
    } 
    return json_decode($result, TRUE); 
}

function delete_product($productId){
    global $merchantId, $productUri;
    $url = sprintf($productUri . "?alt=json", $merchantId, $productId);
    $result = request('DELETE', $url);
    if ($result === FALSE) {  
        throw new Exception(var_dump($result));
    } 
}

function request($method, $url, $data = null){
    global $devToken, $authenticationToken;
    $options = array(
        'http' => array(
            'method'  => $method,
            'header'  => "AuthenticationToken: $authenticationToken\r\n" .
                        "DeveloperToken: $devToken\r\n" .
                        "Content-type: application/json\r\n"
        )
    );
    $json = null;
    if($data !== null){
        $json = json_encode($data);
        $options['http']['content'] = $json;
    }
    $context  = stream_context_create($options);
    echo("requesting ($method): $url\r\n");
    if($json !== null){
        echo("json data: $json\r\n");
    }
    return file_get_contents($url, FALSE, $context);
}

function create_test_product($titlePrefix){
    $expDate = date_add(new DateTime(), date_interval_create_from_date_string("45 days"));
    return array(
        'offerId' => "YourUniqueId(" . uniqid() . ")",
        'title' => "$titlePrefix (" . uniqid() . ")",
        'availability' => "in stock",
        'channel' => "Online",
        'condition' => "New",
        'contentLanguage' => "en",
        'link' => "http://www.contoso.com/apperal/men/tshirts.htm",
        'imageLink' => "http://www.contoso.com/pics/tees.jpg",
        'price' => array(
            'currency' => 'USD',
            'value' => 5.00,
        ),
        'targetCountry' => 'US',
        'identifierExists' => FALSE,
        'expirationDate' => $expDate->format("Y-m-d\TH:i:s\Z")
    );
}

function printObject($object){
    foreach($object as $prop => $value){
        echo("$prop: $value\r\n");
    }
}