Skip to content

Commit 60b99f1

Browse files
authored
Adds examples for conversion enhancements and form data uploads (#486)
Also adds an `--orderId` parameter to UploadOfflineConversion since order ID is required for enhancements.
1 parent b994e0f commit 60b99f1

4 files changed

Lines changed: 561 additions & 4 deletions

File tree

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.ads.googleads.examples.remarketing;
16+
17+
import com.beust.jcommander.Parameter;
18+
import com.google.ads.googleads.examples.utils.ArgumentNames;
19+
import com.google.ads.googleads.examples.utils.CodeSampleParams;
20+
import com.google.ads.googleads.lib.GoogleAdsClient;
21+
import com.google.ads.googleads.v8.common.OfflineUserAddressInfo;
22+
import com.google.ads.googleads.v8.common.UserIdentifier;
23+
import com.google.ads.googleads.v8.enums.ConversionAdjustmentTypeEnum.ConversionAdjustmentType;
24+
import com.google.ads.googleads.v8.enums.UserIdentifierSourceEnum.UserIdentifierSource;
25+
import com.google.ads.googleads.v8.errors.GoogleAdsError;
26+
import com.google.ads.googleads.v8.errors.GoogleAdsException;
27+
import com.google.ads.googleads.v8.services.ConversionAdjustment;
28+
import com.google.ads.googleads.v8.services.ConversionAdjustmentResult;
29+
import com.google.ads.googleads.v8.services.ConversionAdjustmentUploadServiceClient;
30+
import com.google.ads.googleads.v8.services.RestatementValue;
31+
import com.google.ads.googleads.v8.services.UploadConversionAdjustmentsRequest;
32+
import com.google.ads.googleads.v8.services.UploadConversionAdjustmentsResponse;
33+
import com.google.ads.googleads.v8.utils.ResourceNames;
34+
import java.io.FileNotFoundException;
35+
import java.io.IOException;
36+
import java.io.UnsupportedEncodingException;
37+
import java.security.MessageDigest;
38+
import java.security.NoSuchAlgorithmException;
39+
40+
/**
41+
* Adjusts an existing conversion by supplying user identifiers so Google can enhance the conversion
42+
* value.
43+
*/
44+
public class UploadConversionEnhancement {
45+
private static class UploadConversionEnhancementParams extends CodeSampleParams {
46+
47+
@Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
48+
private long customerId;
49+
50+
@Parameter(names = ArgumentNames.CONVERSION_ACTION_ID, required = true)
51+
private long conversionActionId;
52+
53+
@Parameter(names = ArgumentNames.ORDER_ID, required = true)
54+
private String orderId;
55+
56+
@Parameter(names = ArgumentNames.USER_AGENT, required = false)
57+
private String userAgent;
58+
59+
@Parameter(names = ArgumentNames.RESTATEMENT_VALUE, required = false)
60+
private Double restatementValue;
61+
62+
@Parameter(
63+
names = ArgumentNames.CURRENCY_CODE,
64+
required = false,
65+
description = "The currency of the restatement value.")
66+
private String currencyCode;
67+
}
68+
69+
public static void main(String[] args)
70+
throws UnsupportedEncodingException, NoSuchAlgorithmException {
71+
UploadConversionEnhancementParams params = new UploadConversionEnhancementParams();
72+
if (!params.parseArguments(args)) {
73+
74+
// Either pass the required parameters for this example on the command line, or insert them
75+
// into the code here. See the parameter class definition above for descriptions.
76+
params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
77+
params.conversionActionId = Long.parseLong("INSERT_CONVERSION_ACTION_ID_HERE");
78+
params.orderId = "INSERT_ORDER_ID_HERE";
79+
80+
// Optional: Specify the user agent, restatement value and restatement currency code.
81+
params.userAgent = null;
82+
params.restatementValue = null;
83+
params.currencyCode = null;
84+
}
85+
86+
GoogleAdsClient googleAdsClient = null;
87+
try {
88+
googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
89+
} catch (FileNotFoundException fnfe) {
90+
System.err.printf(
91+
"Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
92+
System.exit(1);
93+
} catch (IOException ioe) {
94+
System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
95+
System.exit(1);
96+
}
97+
98+
try {
99+
new UploadConversionEnhancement()
100+
.runExample(
101+
googleAdsClient,
102+
params.customerId,
103+
params.conversionActionId,
104+
params.orderId,
105+
params.userAgent,
106+
params.restatementValue,
107+
params.currencyCode);
108+
} catch (GoogleAdsException gae) {
109+
// GoogleAdsException is the base class for most exceptions thrown by an API request.
110+
// Instances of this exception have a message and a GoogleAdsFailure that contains a
111+
// collection of GoogleAdsErrors that indicate the underlying causes of the
112+
// GoogleAdsException.
113+
System.err.printf(
114+
"Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
115+
gae.getRequestId());
116+
int i = 0;
117+
for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
118+
System.err.printf(" Error %d: %s%n", i++, googleAdsError);
119+
}
120+
System.exit(1);
121+
}
122+
}
123+
124+
/**
125+
* Runs the example.
126+
*
127+
* @param googleAdsClient the Google Ads API client.
128+
* @param customerId the client customer ID.
129+
* @param conversionActionId conversion action ID associated with this conversion.
130+
* @param orderId unique order ID (transaction ID) of the conversion.
131+
* @param userAgent the HTTP user agent of the conversion.
132+
* @param restatementValue the enhancement value.
133+
* @param restatementCurrencyCode the currency of the enhancement value.
134+
*/
135+
// [START upload_conversion_enhancement]
136+
private void runExample(
137+
GoogleAdsClient googleAdsClient,
138+
long customerId,
139+
long conversionActionId,
140+
String orderId,
141+
String userAgent,
142+
Double restatementValue,
143+
String restatementCurrencyCode)
144+
throws NoSuchAlgorithmException, UnsupportedEncodingException {
145+
// [START create_adjustment]
146+
// Creates a builder for constructing the enhancement adjustment.
147+
ConversionAdjustment.Builder enhancementBuilder =
148+
ConversionAdjustment.newBuilder()
149+
.setConversionAction(ResourceNames.conversionAction(customerId, conversionActionId))
150+
.setAdjustmentType(ConversionAdjustmentType.ENHANCEMENT)
151+
// Enhancements MUST use order ID instead of GCLID date/time pair.
152+
.setOrderId(orderId);
153+
154+
// Creates a SHA256 message digest for hashing user identifiers in a privacy-safe way, as
155+
// described at https://support.google.com/google-ads/answer/9888656.
156+
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
157+
158+
// Adds user identifiers, hashing where required.
159+
160+
// Creates a user identifier using sample values for the user address.
161+
UserIdentifier addressIdentifier =
162+
UserIdentifier.newBuilder()
163+
// User identifiers for conversion enhancements MUST use first party data.
164+
.setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
165+
.setAddressInfo(
166+
OfflineUserAddressInfo.newBuilder()
167+
.setHashedFirstName(normalizeAndHash(sha256Digest, "Joanna"))
168+
.setHashedLastName(normalizeAndHash(sha256Digest, "Smith"))
169+
.setHashedStreetAddress(
170+
normalizeAndHash(sha256Digest, "1600 Amphitheatre Pkwy"))
171+
.setCity("Mountain View")
172+
.setState("CA")
173+
.setPostalCode("94043")
174+
.setCountryCode("US"))
175+
.build();
176+
177+
// Creates a user identifier using the hashed email address.
178+
UserIdentifier emailIdentifier =
179+
UserIdentifier.newBuilder()
180+
.setUserIdentifierSource(UserIdentifierSource.FIRST_PARTY)
181+
// Uses the normalize and hash method specifically for email addresses.
182+
.setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, "joannasmith@example.com"))
183+
.build();
184+
185+
// Adds the user identifiers to the enhancement adjustment.
186+
enhancementBuilder.addUserIdentifiers(addressIdentifier).addUserIdentifiers(emailIdentifier);
187+
188+
// Sets optional fields where a value was provided.
189+
190+
if (userAgent != null) {
191+
// Sets the user agent. This should match the user agent of the request that sent the original
192+
// conversion so the conversion and its enhancement are either both attributed as same-device
193+
// or both attributed as cross-device.
194+
enhancementBuilder.setUserAgent(userAgent);
195+
}
196+
197+
if (restatementValue != null) {
198+
// Creates a builder to construct the restated conversion value.
199+
RestatementValue.Builder valueBuilder = enhancementBuilder.getRestatementValueBuilder();
200+
// Sets the new value of the conversion.
201+
valueBuilder.setAdjustedValue(restatementValue);
202+
// Sets the currency of the new value, if provided. Otherwise, the default currency from
203+
// the conversion action is used, and if that is not set then the account currency is used.
204+
if (restatementCurrencyCode != null) {
205+
valueBuilder.setCurrencyCode(restatementCurrencyCode);
206+
}
207+
}
208+
// [END create_adjustment]
209+
210+
// Creates the conversion upload service client.
211+
try (ConversionAdjustmentUploadServiceClient conversionUploadServiceClient =
212+
googleAdsClient.getLatestVersion().createConversionAdjustmentUploadServiceClient()) {
213+
// Uploads the enhancement adjustment. Partial failure should always be set to true.
214+
UploadConversionAdjustmentsResponse response =
215+
conversionUploadServiceClient.uploadConversionAdjustments(
216+
UploadConversionAdjustmentsRequest.newBuilder()
217+
.setCustomerId(Long.toString(customerId))
218+
.addConversionAdjustments(enhancementBuilder)
219+
// Enables partial failure (must be true).
220+
.setPartialFailure(true)
221+
.build());
222+
223+
// Prints any partial errors returned.
224+
if (response.hasPartialFailureError()) {
225+
System.out.printf(
226+
"Partial error encountered: '%s'.%n", response.getPartialFailureError().getMessage());
227+
} else {
228+
// Prints the result.
229+
ConversionAdjustmentResult result = response.getResults(0);
230+
System.out.printf(
231+
"Uploaded conversion adjustment of '%s' for order ID '%s'.%n",
232+
result.getConversionAction(), result.getOrderId());
233+
}
234+
}
235+
}
236+
// [END upload_conversion_enhancement]
237+
238+
/**
239+
* Returns the result of normalizing and then hashing the string using the provided digest.
240+
* Private customer data must be hashed during upload, as described at
241+
* https://support.google.com/google-ads/answer/7474263.
242+
*
243+
* @param digest the digest to use to hash the normalized string.
244+
* @param s the string to normalize and hash.
245+
*/
246+
// [START normalize_and_hash]
247+
private String normalizeAndHash(MessageDigest digest, String s)
248+
throws UnsupportedEncodingException {
249+
// Normalizes by removing leading and trailing whitespace and converting all characters to
250+
// lower case.
251+
String normalized = s.trim().toLowerCase();
252+
// Hashes the normalized string using the hashing algorithm.
253+
byte[] hash = digest.digest(normalized.getBytes("UTF-8"));
254+
StringBuilder result = new StringBuilder();
255+
for (byte b : hash) {
256+
result.append(String.format("%02x", b));
257+
}
258+
259+
return result.toString();
260+
}
261+
262+
/**
263+
* Returns the result of normalizing and hashing an email address. For this use case, Google Ads
264+
* requires removal of any '.' characters preceding {@code gmail.com} or {@code googlemail.com}.
265+
*
266+
* @param digest the digest to use to hash the normalized string.
267+
* @param emailAddress the email address to normalize and hash.
268+
*/
269+
private String normalizeAndHashEmailAddress(MessageDigest digest, String emailAddress)
270+
throws UnsupportedEncodingException {
271+
String normalizedEmail = emailAddress.toLowerCase();
272+
String[] emailParts = normalizedEmail.split("@");
273+
if (emailParts.length > 1 && emailParts[1].matches("^(gmail|googlemail)\\.com\\s*")) {
274+
// Removes any '.' characters from the portion of the email address before the domain if the
275+
// domain is gmail.com or googlemail.com.
276+
emailParts[0] = emailParts[0].replaceAll("\\.", "");
277+
normalizedEmail = String.format("%s@%s", emailParts[0], emailParts[1]);
278+
}
279+
return normalizeAndHash(digest, normalizedEmail);
280+
}
281+
// [END normalize_and_hash]
282+
}

0 commit comments

Comments
 (0)