Prijsextensies updaten

Prijsextensies voor producten, merken of categorieën in Google Ads automatisch updaten wanneer prijzen van producten aangepast worden.

Start nu!
Prijsextensies updaten
Prijsextensies Get started!

Het toepassen van prijsextensies in Google Ads accounts van grotere ecommerce partijen is een tijdrovende klus. Wanneer prijzen in de shop aangepast worden, moet dit handmatig doorgevoerd worden in een prijsextensie. Of je kiest ervoor om dit middels een CSV-upload gedeeltelijk te automatiseren, maar dat betekent nog steeds dat je dagelijks een nieuwe sheet moet uploaden.

Met onderstaand script kan je bestaande prijsextensies gemakkelijk controleren. Wanneer prijzen in de shop veranderen, past het script de nieuwe prijs ook toe in de prijsextensie. Je kan het script gebruiken voor prijsextensies met productprijzen, categorieën en merken. Door een koppeling met Google Merchant Center (GMC), heeft het script altijd de meest actuele prijzen om toe te kunnen passen in de prijsextensies.

Producten, categorieën en merken

De toepassing van het script op productprijzen is eenvoudig; op basis van de product-URL wordt de laatste prijs in GMC gezocht. Wanneer een product een andere prijs heeft, wordt deze aangepast in de prijsextensie. Wanneer een product niet meer in GMC staat, wordt het product verwijderd uit de prijsextensie.

Wanneer je prijzen van merken in de shop in een prijsextensie zet, zoekt het script alle producten met dit merk en pakt dan de laagste prijs uit GMC. Hierdoor heb je altijd een actuele 'vanaf-prijs'. Zorg er voor dat de merknaam die in de titel van het prijsitem van de extensie staat, overeenkomt met de merknaam in GMC. Voor categorieën geldt hetzelfde als voor merken. Wanneer een categorie in de titel van een prijsitem overeenkomst met een veld uit GMC (dit veld kan je in het script zelf bepalen), zoekt het script de laagste prijs om in de prijsextensie op te nemen.

Wanneer een item uit een prijsextensie verwijderd moet worden, maar de prijsextensie heeft maar drie items, wordt er een einddatum op de prijsextensie ingesteld. Je ontvangt dan ook een mail dat er een prijsextensie is gepauzeerd, zodat je weet dat je een nieuwe moet aanmaken of in de bestaande prijsextensie een nieuw item moet toevoegen.

Zorg ervoor dat je dit script voor gebruik uitvoerig in voorbeeld-modus draait om te kijken welke wijzigingen gemaakt zouden worden. Controleer daarbij of de prijzen overeenkomen met Google Merchant Center.

Instellingen

Met de volgende instellingen kan je het script toepassen naar wens:

  • LOG: Geef aan of het script de tussenstappen moet rapporteren, door de waarde aan te passen naar 'true'.
  • MERCHANT_ID: Schakel de geavanceerde API 'Shopping Content' in en vul hier het Merchant Center ID in. Zorg ervoor dat de gebruiker die het script draait ook toegang heeft tot GMC.
  • INCLUDE_PRODUCT_TIERS: Zet op 'true' wanneer je prijsextensies met productprijzen wilt controleren, anders zet je deze op 'false'.
  • INCLUDE_BRANDS: Zet op 'true' wanneer je prijsextensies met merkprijzen wilt controleren, anders zet je deze op 'false'.
  • INCLUDE_CATEGORIES: Zet op 'true' wanneer je prijsextensies met categorieprijzen wilt controleren, anders zet je deze op 'false'.
  • CATEGORY_FEEDFIELD: Wanneer je prijsextensies met categorieprijzen wilt controleren, kan je hier aangeven welk veld in GMC de categorie bevat.
  • EMAIL_RECIPIENTS: Wanneer prijsextensies gepauzeerd worden, wordt er een mail verstuurd naar deze ontvanger(s).
  • EMAIL_SUBJECT: Geef het onderwerp van de email aan
  • EMAIL_CONTENT: Inhoud van de email, welke wordt aangevuld met meldingen uit het account.
The script
// Copyright 2019. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
//
// Created 15-08-2019
// Last update: 14-02-2019
//
// ABOUT THE SCRIPT
// This script will check all price-extensions for up-to-date prices
// When prices have changed, the script will update the extension.
//
////////////////////////////////////////////////////////////////////

var config = {
  
  LOG : true,    
  
  // Connect Merchant Center. Add user to MC that runs this script.
  // Also enable Advanced API 'Shopping content'.
  MERCHANT_ID : "123456789",
  
  INCLUDE_PRODUCT_TIERS : true,
  INCLUDE_BRANDS : true,
  INCLUDE_CATEGORIES : true,
  
  // Specify the field in Merchant Center that contains your product-category
  CATEGORY_FEEDFIELD : "productType",  
  
  // An email will be send when price extensions are paused
  // Add multiple emailaddresses separated by a comma.
  EMAIL_RECIPIENTS : "example@example.com",
  EMAIL_SUBJECT : "Update Price Extensions in " + AdsApp.currentAccount().getName(),
  EMAIL_CONTENT : "The following changes are made in price extensions:<br /><br />"
  
}

////////////////////////////////////////////////////////////////////

function main(){
  
  // Connect Merchant Center and collect active brands and products
  var products = connectMerchant();
  var newPriceItems = [];
  
  // Set builders
  var priceItemBuilder = AdsApp.extensions().newPriceItemBuilder();
  var priceBuilder = AdsApp.extensions().newPriceBuilder();  
  
  var priceIterator = AdsApp
    .extensions()
    .prices()
    .withCondition("Status = ENABLED")
    .withCondition("PolicyApprovalStatus IN ['APPROVED','APPROVED_LIMITED']")
    .get();

  while(priceIterator.hasNext()){
    var price = priceIterator.next();
    var priceItems = price.getPriceItems();
    
    var numItems = priceItems.length;
    
    // Productprices are checked, when the price extension includes products
    // and config is set to include product tiers
    if(config.INCLUDE_PRODUCT_TIERS === true && !price.getEndDate() && price.getPriceType() == "PRODUCT_TIERS"){

      for(var i = 0;i < priceItems.length;i++){
        var priceItem = priceItems[i];
        var currentPrice = checkPrice(products, priceItem.getFinalUrl());

        numItems = checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder);

      } // priceItemIterator
      
    } // PRODUCT_TIERS
    
    // Brandprices are checked, when the price extension includes brands
    // and config is set to include brand prices
    if(config.INCLUDE_BRANDS === true && !price.getEndDate() && price.getPriceType() == "BRANDS"){

      for(var i = 0;i < priceItems.length;i++){
        var priceItem = priceItems[i];
        var currentPrice = checkBrandPrice(products, priceItem.getHeader());
        
        numItems = checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder);

      } // priceItemIterator
      
    } // BRANDS
    
    // ProductCategories are checked, when the price extension includes categories
    // and config is set to include product categories
    if(config.INCLUDE_CATEGORIES === true && !price.getEndDate() && price.getPriceType() == "PRODUCT_CATEGORIES"){

      for(var i = 0;i < priceItems.length;i++){
        var priceItem = priceItems[i];
        var currentPrice = checkCategoryPrice(products, priceItem.getHeader());
        
        numItems = checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder);

      } // priceItemIterator
      
    } // PRODUCT_CATEGORIES
    
  } // priceIterator
  
  sendEmail();
  
  Logger.log("Thanks for using this custom script by Tibbe van Asten. Winning!");
  
} // function main

////////////////////////////////////////////////////////////////////

function connectMerchant(){
  
  if(config.MERCHANT == "123456789"){
    throw Error("Change the Merchant ID in the settings");
  }  
  
  var pageToken;
  var pageNum = 1;
  var maxResults = 250;
  var products = [];
  
  do {
    var productList = ShoppingContent.Products.list(config.MERCHANT_ID, {
      pageToken: pageToken,
      maxResults: maxResults
    });
    
    if (productList.resources) {
      for (var i = 0; i < productList.resources.length; i++) {
        
        // We'll only check for products that are in stock
        if(productList.resources[i]["availability"] == "in stock"){
          products.push(productList.resources[i]);
        }
      }
    }

    pageToken = productList.nextPageToken;
    pageNum++;
  } while (pageToken);
  
  return products;
  
} // function connectMerchant()

////////////////////////////////////////////////////////////////////

function checkPrice(products, url){

  var productPrice = 1000;
  
  for (var i = 1; i < products.length; i++) { 
    
    // When the link in the feed is the same as the price-item url, the price is retrieved
    if(products[i]["link"] == url) { 
    
      // When the saleprice is in the feed, this will be used. If not, the regular price is used.
      if(products[i]["salePrice"]) {
        var price = products[i]["salePrice"]["value"];
      } else {
        var price = products[i]["price"]["value"];
      }

      if(price < productPrice){ 
        productPrice = price; 
      }
      
    } // if statement url
    
  } // for statement

  if(productPrice != 1000){
    return productPrice;
  }
  
} // function checkPrice()

////////////////////////////////////////////////////////////////////

function checkBrandPrice(products, brand){
  
  var brandPrice = 1000;
  
  for (var i = 1; i < products.length; i++) { 
    
    // When the brand in the feed is the same as the price-item brand, the price is retrieved
    if(products[i]["brand"] == brand) { 

      // When the saleprice is in the feed, this will be used. If not, the regular price is used.
      if(products[i]["salePrice"]) {
        var price = parseFloat(products[i]["salePrice"]["value"]);
      } else {
        var price = parseFloat(products[i]["price"]["value"]);
      }
      
      if(price < brandPrice){ 
        brandPrice = price; 
      }
      
    } // if statement brand
    
  } // for statement

  if(brandPrice != 1000){
    return brandPrice;
  }
  
} // function checkBrandPrice()

////////////////////////////////////////////////////////////////////

function checkCategoryPrice(products, category){
  
  var categoryPrice = 1000;
  
  for (var i = 1; i < products.length; i++) { 
    
    // When the brand in the feed is the same as the price-item productType, the price is retrieved
    if(products[i][config.CATEGORY_FEEDFIELD] == category) { 

      // When the saleprice is in the feed, this will be used. If not, the regular price is used.
      if(products[i]["salePrice"]) {
        var price = parseFloat(products[i]["salePrice"]["value"]);
      } else {
        var price = parseFloat(products[i]["price"]["value"]);
      }   
      
      if(price < categoryPrice){
        categoryPrice = price;
      }
      
    } // if statement brand
    
  } // for statement
  
  if(categoryPrice != 1000){
    return categoryPrice;
  }
  
} // function checkCategoryPrice()

////////////////////////////////////////////////////////////////////

function checkPriceItem(currentPrice, price, priceItems, priceItem, numItems, priceItemBuilder){
  
  // If product is not found, the priceItem will be removed.
  // When price only exists of 3 items, the price will be removed
  if(currentPrice == null && numItems > 3){
    priceItem.remove();
    numItems--;

    // Summary changes in mail
    config.EMAIL_CONTENT += "Removed " + priceItem.getHeader() + " from price #" + price.getId() + "<br />";
    
      if(config.LOG === true){
        Logger.log("Removed " + priceItem.getHeader() + " from price #" + price.getId());
        Logger.log(" ");
      }
          
  } // remove priceItem, not in feed
  else if(currentPrice == null && numItems == 3){
    var date = new Date(Utilities.formatDate(new Date(), 'Europe/Amsterdam', 'yyyy/MM/dd HH:mm').toString());

    if(price.getEndDate() == null){
      price.setEndDate({year: date.getYear(), month: date.getMonth()+1, day: date.getDate()})
      price.setSchedules([{dayOfWeek: "MONDAY", startHour: 0, startMinute: 0, endHour: date.getHours(), endMinute: 0}]);

      if(config.LOG === true){
        Logger.log("Ended " + priceItem.getHeader() + " in #" + price.getId());
        Logger.log(" ");
      }
      
      // Summary changes in mail
      config.EMAIL_CONTENT += "Ended " + priceItem.getHeader() + " in #" + price.getId() + "<br />";  
    }    

  } // remove price, not in feed anymore

  // If price from priceItem is not the same as the one in the feed
  // we'll remove it.
  if(priceItem.getAmount() != currentPrice && currentPrice != null){
    Logger.log(priceItem.getHeader() + ". Current: " + priceItem.getAmount() + ", New: " + currentPrice);

    // Add dummy item
    var dummyPriceItemOperation = priceItemBuilder
    .withHeader("Dummy")
    .withDescription("Dummy")
    .withAmount(10)
    .withCurrencyCode(priceItem.getCurrencyCode())
    .withUnitType("NONE")
    .withFinalUrl("http://www.example.com/")
    .build();                        

    // Setting up new priceItem
    var newPriceItemOperation = priceItemBuilder
    .withHeader(priceItem.getHeader())
    .withDescription(priceItem.getDescription())
    .withAmount(parseFloat(currentPrice))
    .withUnitType(priceItem.getUnitType())
    .withCurrencyCode(priceItem.getCurrencyCode())
    .withFinalUrl(priceItem.getFinalUrl())
    .build();

    var dummyPriceItem = dummyPriceItemOperation.getResult();    
    var newPriceItem = newPriceItemOperation.getResult(); 

    // Adding dummyPriceitem and removing original
    price.addPriceItem(dummyPriceItem);
    priceItem.remove();
    price.addPriceItem(newPriceItem);    
    
      if(config.LOG === true){
        Logger.log("Renewed " + priceItem.getHeader());
        Logger.log(" ");
      }
    
    // Summary changes in mail
    config.EMAIL_CONTENT += "Renewed " + priceItem.getHeader() + "<br />";      

  } // Replace item with wrong price

  // Remove dummy priceItem
  var priceItems = price.getPriceItems()
  for(var y = 0;y < priceItems.length;y++){
    var priceItem = priceItems[y];
    if(priceItem.getHeader() == "Dummy"){
      priceItem.remove();
    }     
  } // Remove dummy
  
  return numItems;
  
} // function checkPriceItem()

////////////////////////////////////////////////////////////////////

function sendEmail() {
  
  if(config.EMAIL_CONTENT != "The following changes are made in price extensions:<br /><br />"){
    MailApp.sendEmail({
      to: config.EMAIL_RECIPIENTS,
      subject: config.EMAIL_SUBJECT,
      htmlBody: config.EMAIL_CONTENT});

    Logger.log(config.EMAIL_CONTENT);
  }
  
} // function sendEmail()
Show whole script!
The Experts
Tibbe van Asten Head of PPC @ Increase
Nils Rooijmans Water Cooler Topics
Martijn Kraan Freelance PPC Specialist
Bas Baudoin Teamlead SEA @ Happy Leads
How about you? JOIN US!
Sharing Knowledge
Caring

Kennis delen

Adsscripts.com staat voor het delen van kennis. In de huidige markt houden SEA-specialisten de kennis en ervaring graag voor zich. Wij zijn er van overtuigd dat het delen van kennis ervoor kan zorgen dat iedereen beter wordt in haar of zijn werk. Daarom lopen wij hier graag in voorop, door onze kennis over scripts te delen met iedereen.

Wil jij ook graag een bijdrage leveren? Wij staan open voor nieuwe ideeën en feedback op alles wat je op Adsscripts.com vindt.

Neem contact op

Training &
Workshop
Neem contact op!
Adsscripts Training & Workshop