Merchant API code sample to insert product reviews asynchronously.

### Java

    // Copyright 2023 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.reviews.v1;

    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.protobuf.Timestamp;
    import com.google.shopping.merchant.reviews.v1.InsertProductReviewRequest;
    import com.google.shopping.merchant.reviews.v1.ProductReview;
    import com.google.shopping.merchant.reviews.v1.ProductReviewAttributes;
    import com.google.shopping.merchant.reviews.v1.ProductReviewAttributes.ReviewLink;
    import com.google.shopping.merchant.reviews.v1.ProductReviewAttributes.ReviewLink.Type;
    import com.google.shopping.merchant.reviews.v1.ProductReviewsServiceClient;
    import com.google.shopping.merchant.reviews.v1.ProductReviewsServiceSettings;
    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 multiple product reviews asynchronously. */
    public class InsertProductReviewsAsyncSample {

      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();
      }

      // Returns a product review with a random ID.
      private static ProductReview createProductReview(String accountId) {
        // MAKE SURE YOU PASS AN ACTUAL PRODUCT REVIEW ID HERE.
        String productReviewId = generateRandomString();

        ProductReviewAttributes attributes =
            ProductReviewAttributes.newBuilder()
                .setTitle("Would not recommend!")
                .setContent("Not fantastic.")
                .setMinRating(1)
                .setMaxRating(5)
                .setRating(2)
                .setReviewTime(Timestamp.newBuilder().setSeconds(123456789).build())
                .addProductLinks("exampleproducturl.com")
                .setReviewLink(
                    ReviewLink.newBuilder()
                        .setLink("examplereviewurl.com")
                        // The review page contains only this single review.
                        .setType(Type.SINGLETON)
                        .build())
                .addGtins("9780007350896")
                .addGtins("9780007350897")
                .build();

        return ProductReview.newBuilder()
            .setProductReviewId(productReviewId)
            .setProductReviewAttributes(attributes)
            .build();
      }

      public static void asyncInsertProductReviews(String accountId, String dataSourceId)
          throws Exception {
        GoogleCredentials credential = new Authenticator().authenticate();

        ProductReviewsServiceSettings productReviewsServiceSettings =
            ProductReviewsServiceSettings.newBuilder()
                .setCredentialsProvider(FixedCredentialsProvider.create(credential))
                .build();

        try (ProductReviewsServiceClient productReviewsServiceClient =
            ProductReviewsServiceClient.create(productReviewsServiceSettings)) {

          // Arbitrarily creates five product reviews with random IDs.
          List<InsertProductReviewRequest> requests = new ArrayList<>();
          for (int i = 0; i < 5; i++) {
            InsertProductReviewRequest request =
                InsertProductReviewRequest.newBuilder()
                    .setParent(String.format("accounts/%s", accountId))
                    .setProductReview(createProductReview(accountId))
                    // Must be a product reviews data source. In other words, a data source whose "type"
                    // is ProductReviewDataSource.
                    .setDataSource(String.format("accounts/%s/dataSources/%s", accountId, dataSourceId))
                    .build();
            requests.add(request);
          }

          // Inserts the product reviews.
          List<ApiFuture<ProductReview>> futures =
              requests.stream()
                  .map(
                      request ->
                          productReviewsServiceClient.insertProductReviewCallable().futureCall(request))
                  .collect(Collectors.toList());

          // Creates callback to handle the responses when all are ready.
          ApiFuture<List<ProductReview>> responses = ApiFutures.allAsList(futures);
          ApiFutures.addCallback(
              responses,
              new ApiFutureCallback<List<ProductReview>>() {
                @Override
                public void onSuccess(List<ProductReview> results) {
                  System.out.println("Inserted product reviews 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();
        asyncInsertProductReviews(config.getAccountId().toString(), "YOUR_DATA_SOURCE_ID");
      }
    }

### PHP

    <?php
    /**
     * 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.
     */

    require_once __DIR__ . '/../../../vendor/autoload.php';
    require_once __DIR__ . '/../../Authentication/Authentication.php';
    require_once __DIR__ . '/../../Authentication/Config.php';
    use GuzzleHttp\Promise\Utils;
    use Google\Protobuf\Timestamp;
    use Google\Shopping\Merchant\Reviews\V1alpha\Client\ProductReviewsServiceClient;
    use Google\Shopping\Merchant\Reviews\V1alpha\InsertProductReviewRequest;
    use Google\Shopping\Merchant\Reviews\V1alpha\ProductReview;
    use Google\Shopping\Merchant\Reviews\V1alpha\ProductReviewAttributes;
    use Google\Shopping\Merchant\Reviews\V1alpha\ProductReviewAttributes\ReviewLink;
    use Google\Shopping\Merchant\Reviews\V1alpha\ProductReviewAttributes\ReviewLink\Type;


    /**
     * This class demonstrates how to insert multiple product reviews asynchronously.
     */
    class InsertProductReviewsAsyncSample
    {
        private const DATA_SOURCE_ID = '<DATA_SOURCE_ID>';

        /**
         * Generates a random string of 8 alphanumeric characters.
         * @return string A random string.
         */
        private static function generateRandomString(): string
        {
            $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
            $randomString = '';
            $length = 8;
            for ($i = 0; $i < $length; $i++) {
                $randomString .= $characters[random_int(0, strlen($characters) - 1)];
            }
            return $randomString;
        }

        /**
         * Creates a sample ProductReview object with a random ID.
         * @return ProductReview A sample ProductReview object.
         */
        private static function createProductReview(): ProductReview
        {
            // MAKE SURE YOU PASS AN ACTUAL PRODUCT REVIEW ID HERE if not generating randomly.
            $productReviewId = self::generateRandomString();

            $attributes = (new ProductReviewAttributes())
                ->setTitle('Would not recommend!')
                ->setContent('Not fantastic.')
                ->setMinRating(1)
                ->setMaxRating(5)
                ->setRating(2)
                ->setReviewTime(new Timestamp(['seconds' => 123456789]))
                ->setProductLinks(['exampleproducturl.com'])
                ->setReviewLink(
                    (new ReviewLink())
                        ->setLink('examplereviewurl.com')
                        // The review page contains only this single review.
                        ->setType(Type::SINGLETON)
                )
                ->setGtins(['9780007350896', '9780007350897']);

            return (new ProductReview())
                ->setProductReviewId($productReviewId)
                ->setAttributes($attributes);
        }

        /**
         * Inserts multiple product reviews into your Merchant Center account asynchronously.
         *
         * @param array $config The configuration data for authentication and account ID.
         * @param string $dataSourceId The ID of the data source for the reviews.
         */
        public static function asyncInsertProductReviewsSample(array $config, string $dataSourceId): void
        {
            // Gets the OAuth credentials to make the request.
            $credentials = Authentication::useServiceAccountOrTokenFile();

            // Creates options config containing credentials for the client to use.
            $options = ['credentials' => $credentials];

            // Creates a client.
            $productReviewsServiceClient = new ProductReviewsServiceClient($options);

            $parent = sprintf('accounts/%s', $config['accountId']);
            $dataSource = sprintf('accounts/%s/dataSources/%s', $config['accountId'], $dataSourceId);

            $insertedReviews = [];
            $errors = [];
            $promises = [];
            // Insert 5 product reviews.
            for ($i = 0; $i < 5; $i++) {
                try {
                    $productReview = self::createProductReview();
                    $productReviewId = $productReview->getProductReviewId();
                    $request = (new InsertProductReviewRequest())
                        ->setParent($parent)
                        ->setProductReview($productReview)
                        ->setDataSource($dataSource);

                    printf("Dispatching insert request for Product Review ID: %s%s", $productReviewId, PHP_EOL);

                    // The async API returns a promise object.
                    // Store the promise, keyed by a unique identifier for this review
                    // This helps match responses/errors back to the original request.
                    $promises[$productReviewId] = $productReviewsServiceClient->insertProductReviewAsync($request);

                } catch (Exception $e) {
                    printf(
                        "Error preparing/dispatching product reviews %s",
                        $e->getMessage(),
                        PHP_EOL
                    );
                }
            }

            if (empty($promises)) {
                echo "No review insert requests were dispatched." . PHP_EOL;
            } else {
                echo "All review insert requests dispatched. Waiting for responses..." . PHP_EOL;

                // Wait for all the promises to settle (either fulfilled or rejected)
                // Utils::settle() returns an array of results, each with 'state' and 'value' or 'reason'
                $results = Utils::settle($promises)->wait();

                $insertedReviewsResponses = [];
                $errors = [];

                foreach ($results as $productReviewId => $result) {

                    if ($result['state'] === 'fulfilled') {
                        $response = $result['value']; // This is the actual response object from the API
                        printf("Successfully inserted product review ID: %s%s", $productReviewId, PHP_EOL);
                        $insertedReviewsResponses[] = $response;
                    } elseif ($result['state'] === 'rejected') {
                        $reason = $result['reason']; // This is the exception object
                        printf(
                            "Error inserting product review Id: %s. %s",
                            $productReviewId,
                            $reason->getMessage(),
                            PHP_EOL
                        );
                        $errors[] = ['reviewId' => $productReviewId, 'reason' => $reason];
                    }
                }

                // Now $insertedReviewsResponses contains actual successful response objects
                // And $errors contains details about the failed requests.
                printf("Processing complete. Successful inserts: %d, Errors: %d%s", count($insertedReviewsResponses), count($errors), PHP_EOL);
            }
        }

        /**
         * Helper to execute the sample.
         */
        public function callSample(): void
        {
            $config = Config::generateConfig();
            self::asyncInsertProductReviewsSample($config, self::DATA_SOURCE_ID);
        }
    }

    $sample = new InsertProductReviewsAsyncSample();
    $sample->callSample();