Insert Product Input Asynchronously

Merchant API Code Sample to Insert Product Input Asynchronously

Java

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

package shopping.merchant.samples.products.v1beta;

import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutureCallback;
import com.google.api.core.ApiFutures;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.shopping.merchant.products.v1beta.Attributes;
import com.google.shopping.merchant.products.v1beta.InsertProductInputRequest;
import com.google.shopping.merchant.products.v1beta.ProductInput;
import com.google.shopping.merchant.products.v1beta.ProductInputsServiceClient;
import com.google.shopping.merchant.products.v1beta.ProductInputsServiceSettings;
import com.google.shopping.merchant.products.v1beta.Shipping;
import com.google.shopping.type.Channel.ChannelEnum;
import com.google.shopping.type.Price;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import shopping.merchant.samples.utils.Authenticator;
import shopping.merchant.samples.utils.Config;

/** This class demonstrates how to insert a product input */
public class InsertProductInputAsyncSample {

  private static String getParent(String accountId) {
    return String.format("accounts/%s", accountId);
  }

  private static String generateRandomString() {
    String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    Random random = new Random();
    StringBuilder sb = new StringBuilder(8);
    for (int i = 0; i < 8; i++) {
      sb.append(characters.charAt(random.nextInt(characters.length())));
    }
    return sb.toString();
  }

  private static ProductInput createRandomProduct() {
    Price price = Price.newBuilder().setAmountMicros(33_450_000).setCurrencyCode("USD").build();

    Shipping shipping =
        Shipping.newBuilder().setPrice(price).setCountry("GB").setService("1st class post").build();

    Shipping shipping2 =
        Shipping.newBuilder().setPrice(price).setCountry("FR").setService("1st class post").build();

    Attributes attributes =
        Attributes.newBuilder()
            .setTitle("A Tale of Two Cities")
            .setDescription("A classic novel about the French Revolution")
            .setLink("https://exampleWebsite.com/tale-of-two-cities.html")
            .setImageLink("https://exampleWebsite.com/tale-of-two-cities.jpg")
            .setAvailability("in stock")
            .setCondition("new")
            .setGoogleProductCategory("Media > Books")
            .addGtin("9780007350896")
            .addShipping(shipping)
            .addShipping(shipping2)
            .build();

    return ProductInput.newBuilder()
        .setChannel(ChannelEnum.ONLINE)
        .setContentLanguage("en")
        .setFeedLabel("CH")
        .setOfferId(generateRandomString())
        .setAttributes(attributes)
        .build();
  }

  public static void asyncInsertProductInput(Config config, String dataSource) throws Exception {

    // Obtains OAuth token based on the user's configuration.
    GoogleCredentials credential = new Authenticator().authenticate();

    // Creates service settings using the credentials retrieved above.
    ProductInputsServiceSettings productInputsServiceSettings =
        ProductInputsServiceSettings.newBuilder()
            .setCredentialsProvider(FixedCredentialsProvider.create(credential))
            .build();

    // Creates parent to identify where to insert the product.
    String parent = getParent(config.getAccountId().toString());

    // Calls the API and catches and prints any network failures/errors.
    try (ProductInputsServiceClient productInputsServiceClient =
        ProductInputsServiceClient.create(productInputsServiceSettings)) {

      // Creates five insert product input requests with random product IDs.
      List<InsertProductInputRequest> requests = new ArrayList<>(5);
      for (int i = 0; i < 5; i++) {
        InsertProductInputRequest request =
            InsertProductInputRequest.newBuilder()
                .setParent(parent)
                // You can only insert products into datasource types of Input "API" and "FILE", and
                // of Type "Primary" or "Supplemental."
                // This field takes the `name` field of the datasource.
                .setDataSource(dataSource)
                // If this product is already owned by another datasource, when re-inserting, the
                // new datasource will take ownership of the product.
                .setProductInput(createRandomProduct())
                .build();

        requests.add(request);
      }

      System.out.println("Sending insert product input requests");
      List<ApiFuture<ProductInput>> futures =
          requests.stream()
              .map(
                  request ->
                      productInputsServiceClient.insertProductInputCallable().futureCall(request))
              .collect(Collectors.toList());

      // Creates callback to handle the responses when all are ready.
      ApiFuture<List<ProductInput>> responses = ApiFutures.allAsList(futures);
      ApiFutures.addCallback(
          responses,
          new ApiFutureCallback<List<ProductInput>>() {
            @Override
            public void onSuccess(List<ProductInput> results) {
              System.out.println("Inserted products below");
              System.out.println(results);
            }

            @Override
            public void onFailure(Throwable throwable) {
              System.out.println(throwable);
            }
          },
          MoreExecutors.directExecutor());

    } catch (Exception e) {
      System.out.println(e);
    }
  }

  public static void main(String[] args) throws Exception {
    Config config = Config.load();
    // Identifies the data source that will own the product input.
    String dataSource = "accounts/" + config.getAccountId() + "/dataSources/{datasourceId}";

    asyncInsertProductInput(config, dataSource);
  }
}

Node.js

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

'use strict';
const fs = require('fs');
const authUtils = require('../../authentication/authenticate.js');
const {
  ProductInputsServiceClient,
} = require('@google-shopping/products').v1beta;

/**
 * This class demonstrates how to insert a product input asynchronously.
 */

/**
 * Helper function to generate a random string for offerId
 * @returns {string} A sample offerId.
 */
function generateRandomString() {
  const characters =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  const length = 8;
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * characters.length));
  }
  return result;
}

/**
 * Helper function to create a sample ProductInput object
 * @returns {!object} A sample ProductInput object.
 */
function createRandomProduct() {
  const price = {
    amount_micros: 33450000, // 33.45 USD
    currency_code: 'USD',
  };

  const shipping = {
    price: price,
    country: 'GB',
    service: '1st class post',
  };

  const shipping2 = {
    price: price,
    country: 'FR',
    service: '1st class post',
  };

  const attributes = {
    title: 'A Tale of Two Cities',
    description: 'A classic novel about the French Revolution',
    link: 'https://exampleWebsite.com/tale-of-two-cities.html',
    image_link: 'https://exampleWebsite.com/tale-of-two-cities.jpg',
    availability: 'in stock',
    condition: 'new',
    google_product_category: 'Media > Books',
    gtin: ['9780007350896'],
    shipping: [shipping, shipping2],
    // Price is nested within attributes in the ProductInput message
    price: price,
  };

  // Construct the ProductInput object
  const productInput = {
    channel: 'ONLINE', // Use the string representation for ChannelEnum
    contentLanguage: 'en',
    feedLabel: 'CH',
    offerId: generateRandomString(),
    attributes: attributes,
  };

  return productInput;
}

/**
 * Inserts multiple product inputs asynchronously.
 * @param {!object} config - Configuration object.
 * @param {string} dataSource - The data source name.
 */
async function asyncInsertProductInput(config, dataSource) {
  // Read merchant_id from the configuration file.
  const merchantInfo = JSON.parse(
    fs.readFileSync(config.merchantInfoFile, 'utf8')
  );
  const merchantId = merchantInfo.merchantId;

  // Construct the parent resource name string.
  const parent = `accounts/${merchantId}`;

  // Get OAuth2 credentials.
  const authClient = await authUtils.getOrGenerateUserCredentials();

  // Create client options with authentication.
  const options = {authClient: authClient};

  // Create the ProductInputsServiceClient.
  const productInputsServiceClient = new ProductInputsServiceClient(options);

  // Create five insert product input requests with random product details.
  const requests = [];
  for (let i = 0; i < 5; i++) {
    const request = {
      parent: parent,
      // You can only insert products into datasource types of Input "API" and "FILE", and
      // of Type "Primary" or "Supplemental."
      // This field takes the `name` field of the datasource, e.g.,
      // accounts/123/dataSources/456
      dataSource: dataSource,
      // If this product is already owned by another datasource, when re-inserting, the
      // new datasource will take ownership of the product.
      productInput: createRandomProduct(),
    };
    requests.push(request);
  }

  console.log('Sending insert product input requests...');

  // Create an array of promises by calling the insertProductInput method for each request.
  const insertPromises = requests.map(request =>
    productInputsServiceClient.insertProductInput(request)
  );

  // Wait for all insert operations to complete.
  // Promise.all returns an array of results, where each result is the response
  // from the corresponding insertProductInput call (which is the inserted ProductInput).
  // The response from insertProductInput is an array where the first element is the ProductInput.
  const results = await Promise.all(insertPromises);
  const insertedProducts = results.map(result => result[0]); // Extract ProductInput from each response array

  console.log('Inserted products below:');
  console.log(JSON.stringify(insertedProducts, null, 2));
}

/**
 * Main function to call the async insert product input method.
 */
async function main() {
  // Get configuration settings.
  const config = authUtils.getConfig();
  // Define the data source ID. Replace {datasourceId} with your actual data source ID.
  // The format is accounts/{account_id}/dataSources/{datasource_id}.
  const merchantInfo = JSON.parse(
    fs.readFileSync(config.merchantInfoFile, 'utf8')
  );
  const merchantId = merchantInfo.merchantId;
  const dataSource = `accounts/${merchantId}/dataSources/{datasourceId}`; // Replace {datasourceId}

  try {
    await asyncInsertProductInput(config, dataSource);
  } catch (error) {
    console.error(`An error occurred: ${error.message || error}`);
    // Log details if available (e.g., for gRPC errors)
    if (error.details) {
      console.error(`Details: ${error.details}`);
    }
  }
}

main();