Upload Peningkatan Konversi

Java

// Copyright 2021 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 com.google.ads.googleads.examples.remarketing;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v14.common.OfflineUserAddressInfo;
import com.google.ads.googleads.v14.common.UserIdentifier;
import com.google.ads.googleads.v14.enums.ConversionAdjustmentTypeEnum.ConversionAdjustmentType;
import com.google.ads.googleads.v14.enums.UserIdentifierSourceEnum.UserIdentifierSource;
import com.google.ads.googleads.v14.errors.GoogleAdsError;
import com.google.ads.googleads.v14.errors.GoogleAdsException;
import com.google.ads.googleads.v14.services.ConversionAdjustment;
import com.google.ads.googleads.v14.services.ConversionAdjustmentResult;
import com.google.ads.googleads.v14.services.ConversionAdjustmentUploadServiceClient;
import com.google.ads.googleads.v14.services.GclidDateTimePair;
import com.google.ads.googleads.v14.services.UploadConversionAdjustmentsRequest;
import com.google.ads.googleads.v14.services.UploadConversionAdjustmentsResponse;
import com.google.ads.googleads.v14.utils.ResourceNames;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Adjusts an existing conversion by supplying user identifiers so Google can enhance the conversion
 * value.
 */
public class UploadConversionEnhancement {
  private static class UploadConversionEnhancementParams extends CodeSampleParams {

    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private long customerId;

    @Parameter(names = ArgumentNames.CONVERSION_ACTION_ID, required = true)
    private long conversionActionId;

    @Parameter(names = ArgumentNames.ORDER_ID, required = true)
    private String orderId;

    @Parameter(
        names = ArgumentNames.CONVERSION_DATE_TIME,
        required = false,
        description =
            "The date time at which the conversion with the specified order ID occurred. "
                + "Must be after the click time, and must include the time zone offset. "
                + "The format is  'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'. "
                + "Setting this field is optional, but recommended.")
    private String conversionDateTime;

    @Parameter(names = ArgumentNames.USER_AGENT, required = false)
    private String userAgent;
  }

  public static void main(String[] args)
      throws UnsupportedEncodingException, NoSuchAlgorithmException {
    UploadConversionEnhancementParams params = new UploadConversionEnhancementParams();
    if (!params.parseArguments(args)) {

      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
      params.conversionActionId = Long.parseLong("INSERT_CONVERSION_ACTION_ID_HERE");
      params.orderId = "INSERT_ORDER_ID_HERE";

      // Optional: Specify the conversion date/time and user agent.
      params.conversionDateTime = null;
      params.userAgent = null;
    }

    GoogleAdsClient googleAdsClient = null;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      System.exit(1);
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      System.exit(1);
    }

    try {
      new UploadConversionEnhancement()
          .runExample(
              googleAdsClient,
              params.customerId,
              params.conversionActionId,
              params.orderId,
              params.conversionDateTime,
              params.userAgent);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
      System.exit(1);
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param conversionActionId conversion action ID associated with this conversion.
   * @param orderId unique order ID (transaction ID) of the conversion.
   * @param conversionDateTime
   * @param userAgent the HTTP user agent of the conversion.
   */
  private void runExample(
      GoogleAdsClient googleAdsClient,
      long customerId,
      long conversionActionId,
      String orderId,
      String conversionDateTime,
      String userAgent)
      throws NoSuchAlgorithmException, UnsupportedEncodingException {
    // Creates a builder for constructing the enhancement adjustment.
    ConversionAdjustment.Builder enhancementBuilder =
        ConversionAdjustment.newBuilder()
            .setConversionAction(ResourceNames.conversionAction(customerId, conversionActionId))
            .setAdjustmentType(ConversionAdjustmentType.ENHANCEMENT)
            // Enhancements MUST use order ID instead of GCLID date/time pair.
            .setOrderId(orderId);

    // Sets the conversion date and time if provided. Providing this value is optional but
    // recommended.
    if (conversionDateTime != null) {
      enhancementBuilder.setGclidDateTimePair(
          GclidDateTimePair.newBuilder().setConversionDateTime(conversionDateTime));
    }

    // Creates a SHA256 message digest for hashing user identifiers in a privacy-safe way, as
    // described at https://support.google.com/google-ads/answer/9888656.
    MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");

    // Adds user identifiers, hashing where required.

    // Creates a user identifier using sample values for the user address.
    UserIdentifier addressIdentifier =
        UserIdentifier.newBuilder()
            .setAddressInfo(
                OfflineUserAddressInfo.newBuilder()
                    .setHashedFirstName(normalizeAndHash(sha256Digest, "Dana"))
                    .setHashedLastName(normalizeAndHash(sha256Digest, "Quinn"))
                    .setHashedStreetAddress(
                        normalizeAndHash(sha256Digest, "1600 Amphitheatre Pkwy"))
                    .setCity("Mountain View")
                    .setState("CA")
                    .setPostalCode("94043")
                    .setCountryCode("US"))
            // Optional: Specifies the user identifier source.
            .setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
            .build();

    // Creates a user identifier using the hashed email address.
    UserIdentifier emailIdentifier =
        UserIdentifier.newBuilder()
            .setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
            // Uses the normalize and hash method specifically for email addresses.
            .setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, "dana@example.com"))
            .build();

    // Adds the user identifiers to the enhancement adjustment.
    enhancementBuilder.addUserIdentifiers(addressIdentifier).addUserIdentifiers(emailIdentifier);

    // Sets optional fields where a value was provided.

    if (userAgent != null) {
      // Sets the user agent. This should match the user agent of the request that sent the original
      // conversion so the conversion and its enhancement are either both attributed as same-device
      // or both attributed as cross-device.
      enhancementBuilder.setUserAgent(userAgent);
    }

    // Creates the conversion upload service client.
    try (ConversionAdjustmentUploadServiceClient conversionUploadServiceClient =
        googleAdsClient.getLatestVersion().createConversionAdjustmentUploadServiceClient()) {
      // Uploads the enhancement adjustment. Partial failure should always be set to true.
      UploadConversionAdjustmentsResponse response =
          conversionUploadServiceClient.uploadConversionAdjustments(
              UploadConversionAdjustmentsRequest.newBuilder()
                  .setCustomerId(Long.toString(customerId))
                  .addConversionAdjustments(enhancementBuilder)
                  // Enables partial failure (must be true).
                  .setPartialFailure(true)
                  .build());

      // Prints any partial errors returned.
      if (response.hasPartialFailureError()) {
        System.out.printf(
            "Partial error encountered: '%s'.%n", response.getPartialFailureError().getMessage());
      } else {
        // Prints the result.
        ConversionAdjustmentResult result = response.getResults(0);
        System.out.printf(
            "Uploaded conversion adjustment of '%s' for order ID '%s'.%n",
            result.getConversionAction(), result.getOrderId());
      }
    }
  }

  /**
   * Returns the result of normalizing and then hashing the string using the provided digest.
   * Private customer data must be hashed during upload, as described at
   * https://support.google.com/google-ads/answer/7474263.
   *
   * @param digest the digest to use to hash the normalized string.
   * @param s the string to normalize and hash.
   */
  private String normalizeAndHash(MessageDigest digest, String s)
      throws UnsupportedEncodingException {
    // Normalizes by removing leading and trailing whitespace and converting all characters to
    // lower case.
    String normalized = s.trim().toLowerCase();
    // Hashes the normalized string using the hashing algorithm.
    byte[] hash = digest.digest(normalized.getBytes("UTF-8"));
    StringBuilder result = new StringBuilder();
    for (byte b : hash) {
      result.append(String.format("%02x", b));
    }

    return result.toString();
  }

  /**
   * Returns the result of normalizing and hashing an email address. For this use case, Google Ads
   * requires removal of any '.' characters preceding {@code gmail.com} or {@code googlemail.com}.
   *
   * @param digest the digest to use to hash the normalized string.
   * @param emailAddress the email address to normalize and hash.
   */
  private String normalizeAndHashEmailAddress(MessageDigest digest, String emailAddress)
      throws UnsupportedEncodingException {
    String normalizedEmail = emailAddress.toLowerCase();
    String[] emailParts = normalizedEmail.split("@");
    if (emailParts.length > 1 && emailParts[1].matches("^(gmail|googlemail)\\.com\\s*")) {
      // Removes any '.' characters from the portion of the email address before the domain if the
      // domain is gmail.com or googlemail.com.
      emailParts[0] = emailParts[0].replaceAll("\\.", "");
      normalizedEmail = String.format("%s@%s", emailParts[0], emailParts[1]);
    }
    return normalizeAndHash(digest, normalizedEmail);
  }
}

      

C#

// Copyright 2021 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
//
//     http://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.

using CommandLine;
using Google.Ads.Gax.Examples;
using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V14.Common;
using Google.Ads.GoogleAds.V14.Errors;
using Google.Ads.GoogleAds.V14.Resources;
using Google.Ads.GoogleAds.V14.Services;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using static Google.Ads.GoogleAds.V14.Enums.ConversionAdjustmentTypeEnum.Types;
using static Google.Ads.GoogleAds.V14.Enums.UserIdentifierSourceEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V14
{
    /// <summary>
    /// This code example adjusts an existing conversion by supplying user identifiers so
    /// Google can enhance the conversion value.
    /// </summary>
    public class UploadConversionEnhancement : ExampleBase
    {
        /// <summary>
        /// Command line options for running the <see cref="UploadConversionEnhancement"/> example.
        /// </summary>
        public class Options : OptionsBase
        {
            /// <summary>
            /// The Google Ads customer ID for which the conversion action is added.
            /// </summary>
            [Option("customerId", Required = true, HelpText =
                "The Google Ads customer ID for which the conversion action is added.")]
            public long CustomerId { get; set; }

            /// <summary>
            /// ID of the conversion action for which adjustments are uploaded.
            /// </summary>
            [Option("conversionActionId", Required = true, HelpText =
                "ID of the conversion action for which adjustments are uploaded.")]
            public long ConversionActionId { get; set; }

            /// <summary>
            /// The unique order ID (transaction ID) of the conversion.
            /// </summary>
            [Option("orderId", Required = true, HelpText =
                "The unique order ID (transaction ID) of the conversion.")]
            public string OrderId { get; set; }

            /// <summary>
            /// The date time at which the conversion with the specified order ID occurred. Must
            /// be after the click time, and must include the time zone offset. The format is
            /// 'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'. Setting this
            /// field is optional, but recommended.
            /// </summary>
            [Option("conversionDateTime", Required = false, HelpText =
                "The date time at which the conversion with the specified order ID occurred. " +
                "Must be after the click time, and must include the time zone offset. The " +
                "format is 'yyyy-mm-dd hh:mm:ss+|-hh:mm', e.g. '2019-01-01 12:32:45-08:00'. " +
                "Setting this field is optional, but recommended.")]
            public string ConversionDateTime { get; set; }

            /// <summary>
            /// The HTTP user agent of the conversion.
            /// </summary>
            [Option("userAgent", Required = true, HelpText =
                "The HTTP user agent of the conversion.")]
            public string UserAgent { get; set; }
        }

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            Options options = ExampleUtilities.ParseCommandLine<Options>(args);

            UploadConversionEnhancement codeExample = new UploadConversionEnhancement();
            Console.WriteLine(codeExample.Description);

            codeExample.Run(new GoogleAdsClient(), options.CustomerId, options.ConversionActionId,
                options.OrderId, options.ConversionDateTime, options.UserAgent);
        }

        private static SHA256 digest = SHA256.Create();

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This code example adjusts an existing conversion by supplying user identifiers so " +
                "Google can enhance the conversion value.";

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the conversion
        /// enhancement is uploaded.</param>
        /// <param name="conversionActionId">ID of the conversion action for which adjustments are
        /// uploaded.</param>
        /// <param name="orderId">The unique order ID (transaction ID) of the conversion.</param>
        /// <param name="conversionDateTime">The date time at which the conversion with the
        /// specified order ID occurred.</param>
        /// <param name="userAgent">The HTTP user agent of the conversion.</param>
        public void Run(GoogleAdsClient client, long customerId, long conversionActionId,
            string orderId, string conversionDateTime, string userAgent)
        {
            // Get the ConversionAdjustmentUploadService.
            ConversionAdjustmentUploadServiceClient conversionAdjustmentUploadService =
                client.GetService(Services.V14.ConversionAdjustmentUploadService);

            // Creates the enhancement adjustment.
            ConversionAdjustment enhancement = new ConversionAdjustment()
            {
                ConversionAction = ResourceNames.ConversionAction(customerId, conversionActionId),
                AdjustmentType = ConversionAdjustmentType.Enhancement,

                // Enhancements MUST use order ID instead of GCLID date/time pair.
                OrderId = orderId
            };

            // Sets the conversion date and time if provided. Providing this value is optional but
            // recommended.
            if (string.IsNullOrEmpty(conversionDateTime))
            {
                enhancement.GclidDateTimePair = new GclidDateTimePair()
                {
                    ConversionDateTime = conversionDateTime
                };
            }

            // Adds user identifiers, hashing where required.

            // Creates a user identifier using sample values for the user address.
            UserIdentifier addressIdentifier = new UserIdentifier()
            {
                AddressInfo = new OfflineUserAddressInfo()
                {
                    HashedFirstName = NormalizeAndHash("Dana"),
                    HashedLastName = NormalizeAndHash("Quinn"),
                    HashedStreetAddress = NormalizeAndHash("1600 Amphitheatre Pkwy"),
                    City = "Mountain View",
                    State = "CA",
                    PostalCode = "94043",
                    CountryCode = "US"
                },
                // Optional: Specifies the user identifier source.
                UserIdentifierSource = UserIdentifierSource.FirstParty
            };

            // Creates a user identifier using the hashed email address.
            UserIdentifier emailIdentifier = new UserIdentifier()
            {
                UserIdentifierSource = UserIdentifierSource.FirstParty,
                // Uses the normalize and hash method specifically for email addresses.
                HashedEmail = NormalizeAndHashEmailAddress("dana@example.com")
            };

            // Adds the user identifiers to the enhancement adjustment.
            enhancement.UserIdentifiers.AddRange(new[] { addressIdentifier, emailIdentifier });

            // Sets optional fields where a value was provided.
            if (!string.IsNullOrEmpty(userAgent))
            {
                // Sets the user agent. This should match the user agent of the request that
                // sent the original conversion so the conversion and its enhancement are either
                // both attributed as same-device or both attributed as cross-device.
                enhancement.UserAgent = userAgent;
            }

            try
            {
                // Uploads the enhancement adjustment. Partial failure should always be set to true.
                UploadConversionAdjustmentsResponse response =
                    conversionAdjustmentUploadService.UploadConversionAdjustments(
                        new UploadConversionAdjustmentsRequest()
                        {
                            CustomerId = customerId.ToString(),
                            ConversionAdjustments = { enhancement },
                            // Enables partial failure (must be true).
                            PartialFailure = true,
                        });

                // Prints the status message if any partial failure error is returned.
                // Note: The details of each partial failure error are not printed here,
                // you can refer to the example HandlePartialFailure.cs to learn more.
                if (response.PartialFailureError != null)
                {
                    // Extracts the partial failure from the response status.
                    GoogleAdsFailure partialFailure = response.PartialFailure;
                    Console.WriteLine($"{partialFailure.Errors.Count} partial failure error(s) " +
                        $"occurred");
                }
                else
                {
                    // Prints the result.
                    ConversionAdjustmentResult result = response.Results[0];
                    Console.WriteLine($"Uploaded conversion adjustment of " +
                        $"'{result.ConversionAction}' for order ID '{result.OrderId}'.");
                }
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }
        }

        /// <summary>
        /// Normalizes the email address and hashes it. For this use case, Google Ads requires
        /// removal of any '.' characters preceding <code>gmail.com</code> or
        /// <code>googlemail.com</code>.
        /// </summary>
        /// <param name="emailAddress">The email address.</param>
        /// <returns>The hash code.</returns>
        private string NormalizeAndHashEmailAddress(string emailAddress)
        {
            string normalizedEmail = emailAddress.ToLower();
            string[] emailParts = normalizedEmail.Split('@');
            if (emailParts.Length > 1 && (emailParts[1] == "gmail.com" ||
                emailParts[1] == "googlemail.com"))
            {
                // Removes any '.' characters from the portion of the email address before
                // the domain if the domain is gmail.com or googlemail.com.
                emailParts[0] = emailParts[0].Replace(".", "");
                normalizedEmail = $"{emailParts[0]}@{emailParts[1]}";
            }
            return NormalizeAndHash(normalizedEmail);
        }

        /// <summary>
        /// Normalizes and hashes a string value.
        /// </summary>
        /// <param name="value">The value to normalize and hash.</param>
        /// <returns>The normalized and hashed value.</returns>
        private static string NormalizeAndHash(string value)
        {
            return ToSha256String(digest, ToNormalizedValue(value));
        }

        /// <summary>
        /// Hash a string value using SHA-256 hashing algorithm.
        /// </summary>
        /// <param name="digest">Provides the algorithm for SHA-256.</param>
        /// <param name="value">The string value (e.g. an email address) to hash.</param>
        /// <returns>The hashed value.</returns>
        private static string ToSha256String(SHA256 digest, string value)
        {
            byte[] digestBytes = digest.ComputeHash(Encoding.UTF8.GetBytes(value));
            // Convert the byte array into an unhyphenated hexadecimal string.
            return BitConverter.ToString(digestBytes).Replace("-", string.Empty);
        }

        /// <summary>
        /// Removes leading and trailing whitespace and converts all characters to
        /// lower case.
        /// </summary>
        /// <param name="value">The value to normalize.</param>
        /// <returns>The normalized value.</returns>
        private static string ToNormalizedValue(string value)
        {
            return value.Trim().ToLower();
        }
    }
}
      

PHP

<?php

/**
 * Copyright 2021 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.
 */

namespace Google\Ads\GoogleAds\Examples\Remarketing;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V14\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V14\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V14\GoogleAdsException;
use Google\Ads\GoogleAds\Util\V14\ResourceNames;
use Google\Ads\GoogleAds\V14\Common\OfflineUserAddressInfo;
use Google\Ads\GoogleAds\V14\Common\UserIdentifier;
use Google\Ads\GoogleAds\V14\Enums\ConversionAdjustmentTypeEnum\ConversionAdjustmentType;
use Google\Ads\GoogleAds\V14\Enums\UserIdentifierSourceEnum\UserIdentifierSource;
use Google\Ads\GoogleAds\V14\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V14\Services\ConversionAdjustment;
use Google\Ads\GoogleAds\V14\Services\ConversionAdjustmentResult;
use Google\Ads\GoogleAds\V14\Services\GclidDateTimePair;
use Google\Ads\GoogleAds\V14\Services\UploadConversionAdjustmentsRequest;
use Google\ApiCore\ApiException;

/**
 * This example adjusts an existing conversion by supplying user identifiers so Google can enhance
 * the conversion value.
 */
class UploadConversionEnhancement
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const CONVERSION_ACTION_ID = 'INSERT_CONVERSION_ACTION_ID_HERE';
    private const ORDER_ID = 'INSERT_ORDER_ID_HERE';

    // Optional parameters.

    // The date time at which the conversion with the specified order ID occurred.
    // Must be after the click time, and must include the time zone offset.
    // The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. '2019-01-01 12:32:45-08:00'.
    // Setting this field is optional, but recommended.
    private const CONVERSION_DATE_TIME = null;
    private const USER_AGENT = null;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CONVERSION_ACTION_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::ORDER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::CONVERSION_DATE_TIME => GetOpt::OPTIONAL_ARGUMENT,
            ArgumentNames::USER_AGENT => GetOpt::OPTIONAL_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())
            ->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            // We set this value to true to show how to use GAPIC v2 source code. You can remove the
            // below line if you wish to use the old-style source code. Note that in that case, you
            // probably need to modify some parts of the code below to make it work.
            // For more information, see
            // https://developers.devsite.corp.google.com/google-ads/api/docs/client-libs/php/gapic.
            ->usingGapicV2Source(true)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::CONVERSION_ACTION_ID] ?: self::CONVERSION_ACTION_ID,
                $options[ArgumentNames::ORDER_ID] ?: self::ORDER_ID,
                $options[ArgumentNames::CONVERSION_DATE_TIME] ?: self::CONVERSION_DATE_TIME,
                $options[ArgumentNames::USER_AGENT] ?: self::USER_AGENT
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param int $conversionActionId the ID of the conversion action associated with this
     *      conversion
     * @param string $orderId the unique order ID (transaction ID) of the conversion
     * @param string|null $conversionDateTime the date and time of the conversion.
     *      The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. “2019-01-01 12:32:45-08:00”
     * @param string|null $userAgent the HTTP user agent of the conversion
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $conversionActionId,
        string $orderId,
        ?string $conversionDateTime,
        ?string $userAgent
    ) {
        // Creates the conversion enhancement.
        $conversionAdjustment = new ConversionAdjustment([
            'conversion_action' =>
                ResourceNames::forConversionAction($customerId, $conversionActionId),
            'adjustment_type' => ConversionAdjustmentType::ENHANCEMENT,
            // Enhancements must use order ID instead of GCLID date/time pair.
            'order_id' => $orderId
        ]);

        // Uses the SHA-256 hash algorithm for hashing user identifiers in a privacy-safe way, as
        // described at https://support.google.com/google-ads/answer/9888656.
        $hashAlgorithm = "sha256";

        // Adds user identifiers, hashing where required.

        // Creates a user identifier using sample values for the user address.
        $addressIdentifier = new UserIdentifier([
            'address_info' => new OfflineUserAddressInfo([
                'hashed_first_name' => self::normalizeAndHash($hashAlgorithm, 'Dana'),
                'hashed_last_name' => self::normalizeAndHash($hashAlgorithm, 'Quinn'),
                'hashed_street_address' => self::normalizeAndHash(
                    $hashAlgorithm,
                    '1600 Amphitheatre Pkwy'
                ),
                'city' => 'Mountain View',
                'state' => 'CA',
                'postal_code' => '94043',
                'country_code' => 'US'
            ]),
            // Optional: Specifies the user identifier source.
            'user_identifier_source' => UserIdentifierSource::FIRST_PARTY
        ]);

        // Creates a user identifier using the hashed email address.
        $emailIdentifier = new UserIdentifier([
            // Uses the normalize and hash method specifically for email addresses.
            'hashed_email' => self::normalizeAndHashEmailAddress(
                $hashAlgorithm,
                'dana@example.com'
            ),
            // Optional: Specifies the user identifier source.
            'user_identifier_source' => UserIdentifierSource::FIRST_PARTY
        ]);

        // Adds the user identifiers to the enhancement adjustment.
        $conversionAdjustment->setUserIdentifiers([$addressIdentifier, $emailIdentifier]);

        // Sets optional fields where a value was provided.

        if ($conversionDateTime !== null) {
            // Sets the conversion date and time if provided. Providing this value is optional but
            // recommended.
            $conversionAdjustment->setGclidDateTimePair(new GclidDateTimePair([
                'conversion_date_time' => $conversionDateTime
            ]));
        }

        if ($userAgent !== null) {
            // Sets the user agent. This should match the user agent of the request that sent the
            // original conversion so the conversion and its enhancement are either both attributed
            // as same-device or both attributed as cross-device.
            $conversionAdjustment->setUserAgent($userAgent);
        }

        // Issues a request to upload the conversion enhancement.
        $conversionAdjustmentUploadServiceClient =
            $googleAdsClient->getConversionAdjustmentUploadServiceClient();
        $response = $conversionAdjustmentUploadServiceClient->uploadConversionAdjustments(
            // Enables partial failure (must be true).
            UploadConversionAdjustmentsRequest::build($customerId, [$conversionAdjustment], true)
        );

        // Prints the status message if any partial failure error is returned.
        // Note: The details of each partial failure error are not printed here, you can refer to
        // the example HandlePartialFailure.php to learn more.
        if ($response->hasPartialFailureError()) {
            printf(
                "Partial failures occurred: '%s'.%s",
                $response->getPartialFailureError()->getMessage(),
                PHP_EOL
            );
        } else {
            // Prints the result if exists.
            /** @var ConversionAdjustmentResult $uploadedConversionAdjustment */
            $uploadedConversionAdjustment = $response->getResults()[0];
            printf(
                "Uploaded conversion adjustment of '%s' for order ID '%s'.%s",
                $uploadedConversionAdjustment->getConversionAction(),
                $uploadedConversionAdjustment->getOrderId(),
                PHP_EOL
            );
        }
    }

    /**
     * Returns the result of normalizing and then hashing the string using the provided hash
     * algorithm. Private customer data must be hashed during upload, as described at
     * https://support.google.com/google-ads/answer/7474263.
     *
     * @param string $hashAlgorithm the hash algorithm to use
     * @param string $value the value to normalize and hash
     * @return string the normalized and hashed value
     */
    private static function normalizeAndHash(string $hashAlgorithm, string $value): string
    {
        return hash($hashAlgorithm, strtolower(trim($value)));
    }

    /**
     * Returns the result of normalizing and hashing an email address. For this use case, Google
     * Ads requires removal of any '.' characters preceding "gmail.com" or "googlemail.com".
     *
     * @param string $hashAlgorithm the hash algorithm to use
     * @param string $emailAddress the email address to normalize and hash
     * @return string the normalized and hashed email address
     */
    private static function normalizeAndHashEmailAddress(
        string $hashAlgorithm,
        string $emailAddress
    ): string {
        $normalizedEmail = strtolower($emailAddress);
        $emailParts = explode("@", $normalizedEmail);
        if (
            count($emailParts) > 1
            && preg_match('/^(gmail|googlemail)\.com\s*/', $emailParts[1])
        ) {
            // Removes any '.' characters from the portion of the email address before the domain
            // if the domain is gmail.com or googlemail.com.
            $emailParts[0] = str_replace(".", "", $emailParts[0]);
            $normalizedEmail = sprintf('%s@%s', $emailParts[0], $emailParts[1]);
        }
        return self::normalizeAndHash($hashAlgorithm, $normalizedEmail);
    }
}

UploadConversionEnhancement::main();

      

Python

#!/usr/bin/env python
# Copyright 2021 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.
"""Uploads a conversion using hashed email address instead of GCLID."""


import argparse
import hashlib
import re
import sys

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException


def main(
    client,
    customer_id,
    conversion_action_id,
    order_id,
    conversion_date_time,
    user_agent,
):
    """The main method that creates all necessary entities for the example.

    Args:
        client: An initialized GoogleAdsClient instance.
        customer_id: The client customer ID string.
        conversion_action_id: The ID of the conversion action to upload to.
        order_id: The unique ID (transaction ID) of the conversion.
        conversion_date_time: The date and time of the conversion.
        user_agent: The HTTP user agent of the conversion.
    """
    conversion_action_service = client.get_service("ConversionActionService")
    conversion_adjustment = client.get_type("ConversionAdjustment")
    conversion_adjustment.conversion_action = conversion_action_service.conversion_action_path(
        customer_id, conversion_action_id
    )
    conversion_adjustment.adjustment_type = (
        client.enums.ConversionAdjustmentTypeEnum.ENHANCEMENT
    )
    # Enhancements MUST use order ID instead of GCLID date/time pair.
    conversion_adjustment.order_id = order_id

    # Sets the conversion date and time if provided. Providing this value is
    # optional but recommended.
    if conversion_date_time:
        conversion_adjustment.gclid_date_time_pair.conversion_date_time = (
            conversion_date_time
        )

    # Creates a user identifier using sample values for the user address,
    # hashing where required.
    address_identifier = client.get_type("UserIdentifier")
    address_identifier.address_info.hashed_first_name = normalize_and_hash(
        "Dana"
    )
    address_identifier.address_info.hashed_last_name = normalize_and_hash(
        "Quinn"
    )
    address_identifier.address_info.hashed_street_address = normalize_and_hash(
        "1600 Amphitheatre Pkwy"
    )
    address_identifier.address_info.city = "Mountain View"
    address_identifier.address_info.state = "CA"
    address_identifier.address_info.postal_code = "94043"
    address_identifier.address_info.country_code = "US"
    # Optional: Specifies the user identifier source.
    address_identifier.user_identifier_source = (
        client.enums.UserIdentifierSourceEnum.FIRST_PARTY
    )

    # Creates a user identifier using the hashed email address.
    email_identifier = client.get_type("UserIdentifier")
    # Optional: Specifies the user identifier source.
    email_identifier.user_identifier_source = (
        client.enums.UserIdentifierSourceEnum.FIRST_PARTY
    )
    # Uses the normalize and hash method specifically for email addresses.
    email_identifier.hashed_email = normalize_and_hash_email_address(
        "dana@example.com"
    )

    # Adds both user identifiers to the conversion adjustment.
    conversion_adjustment.user_identifiers.extend(
        [address_identifier, email_identifier]
    )

    # Sets optional fields where a value was provided
    if user_agent:
        # Sets the user agent. This should match the user agent of the request
        # that sent the original conversion so the conversion and its
        # enhancement are either both attributed as same-device or both
        # attributed as cross-device.
        conversion_adjustment.user_agent = user_agent

    # Creates the conversion adjustment upload service client.
    conversion_adjustment_upload_service = client.get_service(
        "ConversionAdjustmentUploadService"
    )
    # Uploads the enhancement adjustment. Partial failure should always be set
    # to true.
    response = conversion_adjustment_upload_service.upload_conversion_adjustments(
        customer_id=customer_id,
        conversion_adjustments=[conversion_adjustment],
        # Enables partial failure (must be true).
        partial_failure=True,
    )

    # Prints any partial errors returned.
    if response.partial_failure_error:
        print(
            "Partial error encountered: "
            f"{response.partial_failure_error.message}"
        )

    # Prints the result.
    result = response.results[0]
    # Only prints valid results. If the click conversion failed then this
    # result will be returned as an empty message and will be falsy.
    if result:
        print(
            f"Uploaded conversion adjustment of {result.conversion_action} for "
            f"order ID {result,order_id}."
        )


def normalize_and_hash_email_address(email_address):
    """Returns the result of normalizing and hashing an email address.

    For this use case, Google Ads requires removal of any '.' characters
    preceding "gmail.com" or "googlemail.com"

    Args:
        email_address: An email address to normalize.

    Returns:
        A normalized (lowercase, removed whitespace) and SHA-265 hashed string.
    """
    normalized_email = email_address.lower()
    email_parts = normalized_email.split("@")
    # Checks whether the domain of the email address is either "gmail.com"
    # or "googlemail.com". If this regex does not match then this statement
    # will evaluate to None.
    is_gmail = re.match(r"^(gmail|googlemail)\.com$", email_parts[1])

    # Check that there are at least two segments and the second segment
    # matches the above regex expression validating the email domain name.
    if len(email_parts) > 1 and is_gmail:
        # Removes any '.' characters from the portion of the email address
        # before the domain if the domain is gmail.com or googlemail.com.
        email_parts[0] = email_parts[0].replace(".", "")
        normalized_email = "@".join(email_parts)

    return normalize_and_hash(normalized_email)


def normalize_and_hash(s):
    """Normalizes and hashes a string with SHA-256.

    Private customer data must be hashed during upload, as described at:
    https://support.google.com/google-ads/answer/7474263

    Args:
        s: The string to perform this operation on.

    Returns:
        A normalized (lowercase, removed whitespace) and SHA-256 hashed string.
    """
    return hashlib.sha256(s.strip().lower().encode()).hexdigest()


if __name__ == "__main__":
    # GoogleAdsClient will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    googleads_client = GoogleAdsClient.load_from_storage(version="v14")

    parser = argparse.ArgumentParser(
        description="Imports offline call conversion values for calls related "
        "to your ads."
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-a",
        "--conversion_action_id",
        type=str,
        required=True,
        help="The ID of the conversion action to upload to.",
    )
    parser.add_argument(
        "-o",
        "--order_id",
        type=str,
        required=True,
        help="the unique ID (transaction ID) of the conversion.",
    )
    parser.add_argument(
        "-d",
        "--conversion_date_time",
        type=str,
        help="The date time at which the conversion with the specified order "
        "ID occurred. Must be after the click time, and must include the time "
        "zone offset.  The format is 'yyyy-mm-dd hh:mm:ss+|-hh:mm', "
        "e.g. '2019-01-01 12:32:45-08:00'. Setting this field is optional, "
        "but recommended",
    )
    parser.add_argument(
        "-u",
        "--user_agent",
        type=str,
        help="The HTTP user agent of the conversion.",
    )
    args = parser.parse_args()

    try:
        main(
            googleads_client,
            args.customer_id,
            args.conversion_action_id,
            args.order_id,
            args.conversion_date_time,
            args.user_agent,
        )
    except GoogleAdsException as ex:
        print(
            f"Request with ID '{ex.request_id}'' failed with status "
            f"'{ex.error.code().name}' and includes the following errors:"
        )
        for error in ex.failure.errors:
            print(f"\tError with message '{error.message}'.")
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(f"\t\tOn field: {field_path_element.field_name}")
        sys.exit(1)

      

Ruby

#!/usr/bin/env ruby
#
# Copyright 2021 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.
#
# Adjusts an existing conversion by supplying user identifiers so Google can
# enhance the conversion value.

require 'optparse'
require 'google/ads/google_ads'
require 'digest'

def upload_conversion_enhancement(
  customer_id,
  conversion_action_id,
  order_id,
  conversion_date_time,
  user_agent
)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  enhancement = client.resource.conversion_adjustment do |ca|
    ca.conversion_action = client.path.conversion_action(customer_id, conversion_action_id)
    ca.adjustment_type = :ENHANCEMENT
    ca.order_id = order_id

    # Sets the conversion date and time if provided. Providing this value is
    # optional but recommended.
    unless conversion_date_time.nil?
      ca.gclid_date_time_pair = client.resource.gclid_date_time_pair do |pair|
        pair.conversion_date_time = conversion_date_time
      end
    end

    # Creates a user identifier using sample values for the user address.
    ca.user_identifiers << client.resource.user_identifier do |ui|
      ui.address_info = client.resource.offline_user_address_info do |info|
        # Certain fields must be hashed using SHA256 in order to handle
        # identifiers in a privacy-safe way, as described at
        # https://support.google.com/google-ads/answer/9888656.
        info.hashed_first_name = normalize_and_hash("Joanna")
        info.hashed_last_name = normalize_and_hash("Smith")
        info.hashed_street_address = normalize_and_hash("1600 Amphitheatre Pkwy")
        info.city = "Mountain View"
        info.state = "CA"
        info.postal_code = "94043"
        info.country_code = "US"
      end
      # Optional: Specifies the user identifier source.
      ui.user_identifier_source = :FIRST_PARTY
    end

    # Creates a user identifier using the hashed email address.
    ca.user_identifiers << client.resource.user_identifier do |ui|
      # Uses the normalize and hash method specifically for email addresses.
      ui.hashed_email = normalize_and_hash_email("dana@example.com")
      ui.user_identifier_source = :FIRST_PARTY
    end

    # Sets optional fields where a value was provided.
    unless user_agent.nil?
      # Sets the user agent. This should match the user agent of the request
      # that sent the original conversion so the conversion and its enhancement
      # are either both attributed as same-device or both attributed as
      # cross-device.
      ca.user_agent = user_agent
    end
  end

  response = client.service.conversion_adjustment_upload.upload_conversion_adjustments(
    customer_id: customer_id,
    conversion_adjustments: [enhancement],
    # Partial failure must be set to true.
    partial_failure: true,
  )

  if response.partial_failure_error
    puts "Partial failure encountered: #{response.partial_failure_error.message}."
  else
    result = response.results.first
    puts "Uploaded conversion adjustment of #{result.conversion_action} for "\
      "order ID #{result.order_id}."
  end
end

# Returns the result of normalizing and then hashing the string using the
# provided digest.  Private customer data must be hashed during upload, as
# described at https://support.google.com/google-ads/answer/7474263.
def normalize_and_hash(str)
  # Remove leading and trailing whitespace and ensure all letters are lowercase
  # before hasing.
  Digest::SHA256.hexdigest(str.strip.downcase)
end

# Returns the result of normalizing and hashing an email address. For this use
# case, Google Ads requires removal of any '.' characters preceding 'gmail.com'
# or 'googlemail.com'.
def normalize_and_hash_email(email)
  email_parts = email.downcase.split("@")
  # Removes any '.' characters from the portion of the email address before the
  # domain if the domain is gmail.com or googlemail.com.
  if email_parts.last =~ /^(gmail|googlemail)\.com\s*/
    email_parts[0] = email_parts[0].gsub('.', '')
  end
  normalize_and_hash(email_parts.join('@'))
end

if __FILE__ == $0
  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'
  options[:conversion_action_id] = 'INSERT_CONVERSION_ACTION_ID_HERE'
  options[:order_id] = 'INSERT_ORDER_ID_HERE'

  OptionParser.new do |opts|
    opts.banner = format('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.on('-c', '--conversion-action-id CONVERSION-ACTION-ID', String,
            'Conversion Action ID') do |v|
      options[:conversion_action_id] = v
    end

    opts.on('-o', '--order-id ORDER-ID', String, 'Order ID') do |v|
      options[:order_id] = v
    end

    opts.on('-d', '--conversion-date-time CONVERSION-DATE-TIME', String,
            'The date and time of the conversion (should be after click time).'\
            ' The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", '\
            'e.g. "2019-01-01 12:32:45-08:00".') do |v|
      options[:conversion_date_time] = v
    end

    opts.on('-u', '--user-agent USER-AGENT', String, 'User Agent') do |v|
      options[:user_agent] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    upload_conversion_enhancement(
      options.fetch(:customer_id).tr('-', ''),
      options.fetch(:conversion_action_id),
      options.fetch(:order_id),
      options[:conversion_date_time],
      options[:user_agent]
    )
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      error.location&.field_path_elements&.each do |field_path_element|
        STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED

        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
    raise
  end
end

      

Perl

#!/usr/bin/perl -w
#
# Copyright 2021, 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
#
#     http://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.
#
# Adjusts an existing conversion by supplying user identifiers so Google can
# enhance the conversion value.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::V14::Common::UserIdentifier;
use Google::Ads::GoogleAds::V14::Common::OfflineUserAddressInfo;
use Google::Ads::GoogleAds::V14::Enums::ConversionAdjustmentTypeEnum
  qw(ENHANCEMENT);
use Google::Ads::GoogleAds::V14::Enums::UserIdentifierSourceEnum
  qw(FIRST_PARTY);
use
  Google::Ads::GoogleAds::V14::Services::ConversionAdjustmentUploadService::ConversionAdjustment;
use
  Google::Ads::GoogleAds::V14::Services::ConversionAdjustmentUploadService::GclidDateTimePair;
use Google::Ads::GoogleAds::V14::Utils::ResourceNames;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd         qw(abs_path);
use Digest::SHA qw(sha256_hex);

# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
my $customer_id          = "INSERT_CUSTOMER_ID_HERE";
my $conversion_action_id = "INSERT_CONVERSION_ACTION_ID_HERE";
my $order_id             = "INSERT_ORDER_ID_HERE";
# Optional: Specify the conversion date/time and user agent.
my $conversion_date_time = undef;
my $user_agent           = undef;

sub upload_conversion_enhancement {
  my (
    $api_client, $customer_id,          $conversion_action_id,
    $order_id,   $conversion_date_time, $user_agent
  ) = @_;

  # Construct the enhancement adjustment.
  my $enhancement =
    Google::Ads::GoogleAds::V14::Services::ConversionAdjustmentUploadService::ConversionAdjustment
    ->new({
      conversionAction =>
        Google::Ads::GoogleAds::V14::Utils::ResourceNames::conversion_action(
        $customer_id, $conversion_action_id
        ),
      adjustmentType => ENHANCEMENT,
      # Enhancements MUST use order ID instead of GCLID date/time pair.
      orderId => $order_id
    });

  # Set the conversion date and time if provided. Providing this value is optional
  # but recommended.
  if (defined $conversion_date_time) {
    $enhancement->{gclidDateTimePair} =
      Google::Ads::GoogleAds::V14::Services::ConversionAdjustmentUploadService::GclidDateTimePair
      ->new({
        conversionDateTime => $conversion_date_time
      });
  }

  # Add user identifiers, hashing where required.

  # Create a user identifier using sample values for the user address.
  my $address_identifier =
    Google::Ads::GoogleAds::V14::Common::UserIdentifier->new({
      addressInfo =>
        Google::Ads::GoogleAds::V14::Common::OfflineUserAddressInfo->new({
          hashedFirstName     => normalize_and_hash("Dana"),
          hashedLastName      => normalize_and_hash("Quinn"),
          hashedStreetAddress => normalize_and_hash("1600 Amphitheatre Pkwy"),
          city                => "Mountain View",
          state               => "CA",
          postalCode          => "94043",
          countryCode         => "US"
        }
        ),
      # Optional: Specify the user identifier source.
      userIdentifierSource => FIRST_PARTY
    });

  # Create a user identifier using the hashed email address.
  my $email_identifier =
    Google::Ads::GoogleAds::V14::Common::UserIdentifier->new({
      userIdentifierSource => FIRST_PARTY,
      # Use the normalize and hash method specifically for email addresses.
      hashedEmail => normalize_and_hash_email_address('dana@example.com')});

  # Add the user identifiers to the enhancement adjustment.
  $enhancement->{userIdentifiers} = [$address_identifier, $email_identifier];

  # Set optional fields where a value was provided.

  if (defined $user_agent) {
    # Set the user agent. This should match the user agent of the request that
    # sent the original conversion so the conversion and its enhancement are
    # either both attributed as same-device or both attributed as cross-device.
    $enhancement->{userAgent} = $user_agent;
  }

  # Upload the enhancement adjustment. Partial failure should always be set to true.
  my $response =
    $api_client->ConversionAdjustmentUploadService()
    ->upload_conversion_adjustments({
      customerId            => $customer_id,
      conversionAdjustments => [$enhancement],
      # Enable partial failure (must be true).
      partialFailure => "true"
    });

  # Print any partial errors returned.
  if ($response->{partialFailureError}) {
    printf "Partial error encountered: '%s'.\n",
      $response->{partialFailureError}{message};
  } else {
    # Print the result.
    my $result = $response->{results}[0];
    printf "Uploaded conversion adjustment of '%s' for order ID '%s'.\n",
      $result->{conversionAction}, $result->{orderId};
  }

  return 1;
}

# Normalizes and hashes a string value.
# Private customer data must be hashed during upload, as described at
# https://support.google.com/google-ads/answer/7474263.
sub normalize_and_hash {
  my $value = shift;

  $value =~ s/^\s+|\s+$//g;
  return sha256_hex(lc $value);
}

# Returns the result of normalizing and hashing an email address. For this use
# case, Google Ads requires removal of any '.' characters preceding 'gmail.com'
# or 'googlemail.com'.
sub normalize_and_hash_email_address {
  my $email_address = shift;

  my $normalized_email = lc $email_address;
  my @email_parts      = split('@', $normalized_email);
  if (scalar @email_parts > 1
    && $email_parts[1] =~ /^(gmail|googlemail)\.com\s*/)
  {
    # Remove any '.' characters from the portion of the email address before the
    # domain if the domain is 'gmail.com' or 'googlemail.com'.
    $email_parts[0] =~ s/\.//g;
    $normalized_email = sprintf '%s@%s', $email_parts[0], $email_parts[1];
  }
  return normalize_and_hash($normalized_email);
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s"          => \$customer_id,
  "conversion_action_id=i" => \$conversion_action_id,
  "order_id=s"             => \$order_id,
  "conversion_date_time=s" => \$conversion_date_time,
  "user_agent=s"           => \$user_agent
);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2)
  if not check_params($customer_id, $conversion_action_id, $order_id);

# Call the example.
upload_conversion_enhancement($api_client, $customer_id =~ s/-//gr,
  $conversion_action_id, $order_id, $conversion_date_time, $user_agent);

=pod

=head1 NAME

upload_conversion_enhancement

=head1 DESCRIPTION

Adjusts an existing conversion by supplying user identifiers so Google can
enhance the conversion value.

=head1 SYNOPSIS

upload_conversion_enhancement.pl [options]

    -help                       Show the help message.
    -customer_id                The Google Ads customer ID.
    -conversion_action_id       The conversion action ID associated with this conversion.
    -order_id                   The unique order ID (transaction ID) of the conversion.
    -conversion_date_time       [optional] The date time at which the conversion with the specified order ID
                                occurred. Must be after the click time, and must include the time zone offset.
                                The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", e.g. "2019-01-01 12:32:45-08:00".
                                Setting this field is optional, but recommended.
    -user_agent                 [optional] The HTTP user agent of the conversion.

=cut