diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java index f9a8b5f07c..97f725097a 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/AddCustomerMatchUserList.java @@ -81,8 +81,8 @@ * https://support.google.com/adspolicy/answer/6299717. *
  • It may take up to several hours for the list to be populated with members. *
  • Email addresses must be associated with a Google account. - *
  • For privacy purposes, the user list size will show as zero until the list has at least - * 100 members. After that, the size will be rounded to the two most significant digits. + *
  • For privacy purposes, the user list size will show as zero until the list has at least 100 + * members. After that, the size will be rounded to the two most significant digits. * */ public class AddCustomerMatchUserList { @@ -423,14 +423,14 @@ private List buildOfflineUserDataJobOperations() ImmutableMap.builder() .put("email", "dana@example.com") // Phone number to be converted to E.164 format, with a leading '+' as required. This - // includes whitespace that will be removed later. - .put("phone", "+1 800 5550101") + // includes whitespace, dashes, and parentheses that will be removed later. + .put("phone", "+1 (800) 555-0101") .build(); // The second user data has an email address, a mailing address, and a phone number. Map rawRecord2 = ImmutableMap.builder() - // Email address that includes a period (.) before the domain. - .put("email", "alex.2@example.com") + // Email address that includes a period (.) and plus (+) suffix before the Gmail domain. + .put("email", "alex.2+myalias@gmail.com") // Address that includes all four required elements: first name, last name, country // code, and postal code. .put("firstName", "Alex") @@ -438,7 +438,7 @@ private List buildOfflineUserDataJobOperations() .put("countryCode", "US") .put("postalCode", "94045") // Phone number to be converted to E.164 format, with a leading '+' as required. - .put("phone", "+1 800 5550102") + .put("phone", "+1 800-555-0102") .build(); // The third user data only has an email address. Map rawRecord3 = @@ -479,7 +479,7 @@ private List buildOfflineUserDataJobOperations() if (rawRecord.containsKey("email")) { UserIdentifier hashedEmailIdentifier = UserIdentifier.newBuilder() - .setHashedEmail(normalizeAndHash(sha256Digest, rawRecord.get("email"), true)) + .setHashedEmail(normalizeAndHashEmailAddress(sha256Digest, rawRecord.get("email"))) .build(); // Adds the hashed email identifier to the UserData object's list. userDataBuilder.addUserIdentifiers(hashedEmailIdentifier); @@ -489,7 +489,8 @@ private List buildOfflineUserDataJobOperations() if (rawRecord.containsKey("phone")) { UserIdentifier hashedPhoneNumberIdentifier = UserIdentifier.newBuilder() - .setHashedPhoneNumber(normalizeAndHash(sha256Digest, rawRecord.get("phone"), true)) + .setHashedPhoneNumber( + normalizeAndHashPhoneNumber(sha256Digest, rawRecord.get("phone"))) .build(); // Adds the hashed phone number identifier to the UserData object's list. userDataBuilder.addUserIdentifiers(hashedPhoneNumberIdentifier); @@ -578,6 +579,50 @@ private String normalizeAndHash(MessageDigest digest, String s, boolean trimInte return result.toString(); } + /** + * Returns the result of normalizing and hashing an email address. For this use case, Google Ads + * requires removal of any '.' characters or trailing '+' and characters that follow it from the + * username portion of the email address if the domain is {@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 { + // Removes all whitespace (leading, trailing, and intermediate) from the email address. + String normalizedEmail = emailAddress.toLowerCase().replaceAll("\\s+", ""); + String[] emailParts = normalizedEmail.split("@", 2); + if (emailParts.length == 2 && emailParts[1].matches("^(gmail|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].replaceAll("\\.", ""); + // Removes any '+' and all characters that follow it 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, true); + } + + /** + * Returns the result of normalizing and hashing a phone number. For this use case, Google Ads + * requires phone numbers to be in E.164 format. + * + * @param digest the digest to use to hash the normalized string. + * @param phoneNumber the phone number to normalize and hash. + */ + private String normalizeAndHashPhoneNumber(MessageDigest digest, String phoneNumber) + throws UnsupportedEncodingException { + // Removes non-digit characters and prepends a leading '+' sign. + String digitsOnly = phoneNumber.replaceAll("[^0-9]", ""); + String formattedPhone = "+" + digitsOnly; + if (!formattedPhone.matches("^\\+[1-9]\\d{6,14}$")) { + throw new IllegalArgumentException("Phone number must be in E.164 format: " + phoneNumber); + } + return normalizeAndHash(digest, formattedPhone, true); + } + /** * Retrieves, checks, and prints the status of the offline user data job. * diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForLeads.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForLeads.java index aaa87a4f0b..34f9d3c964 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForLeads.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForLeads.java @@ -86,18 +86,17 @@ private static class UploadEnhancedConversionsForLeadsParams extends CodeSampleP names = ArgumentNames.SESSION_ATTRIBUTES_ENCODED, required = false, description = - "A session attributes token. Only one of sessionAttributesEncoded or sessionAttributesMap" - + " should be passed.") + "A session attributes token. Only one of sessionAttributesEncoded or" + + " sessionAttributesMap should be passed.") private String sessionAttributesEncoded; @Parameter( names = ArgumentNames.SESSION_ATTRIBUTES_MAP, required = false, description = - "A " - + "space-delimited list of session attribute key value pairs. Each pair should be " - + "separated by an equal sign, for example: 'gad_campaignid=12345 gad_source=1'. Only " - + "one of sessionAttributesEncoded or sessionAttributesMap should be passed.") + "A space-delimited list of session attribute key value pairs. Each pair should be" + + " separated by an equal sign, for example: 'gad_campaignid=12345 gad_source=1'." + + " Only one of sessionAttributesEncoded or sessionAttributesMap should be passed.") private String sessionAttributesMap; } @@ -217,9 +216,9 @@ private void runExample( ImmutableMap.Builder rawRecordBuilder = ImmutableMap.builder() - .put("email", "alex.2@example.com") + .put("email", "alex.2+myalias@gmail.com") // Phone number to be converted to E.164 format, with a leading '+' as required. - .put("phone", "+1 800 5550102") + .put("phone", "+1 (800) 555-0102") // This example lets you put conversion details as arguments, but in reality you might // store this data alongside other user data, so we include it in this sample user // record. @@ -269,7 +268,7 @@ private void runExample( // Creates a user identifier using normalized and hashed phone info. UserIdentifier hashedPhoneNumberIdentifier = UserIdentifier.newBuilder() - .setHashedPhoneNumber(normalizeAndHash(sha256Digest, rawRecord.get("phone"))) + .setHashedPhoneNumber(normalizeAndHashPhoneNumber(sha256Digest, rawRecord.get("phone"))) .build(); // Adds the hashed phone number identifier to the UserData object's list. userIdentifiers.add(hashedPhoneNumberIdentifier); @@ -324,9 +323,10 @@ private void runExample( String[] parts = pair.split("=", 2); if (parts.length != 2) { throw new IllegalArgumentException( - "Failed to read the sessionAttributesMap. SessionAttributesMap must use a " - + "space-delimited list of session attribute key value pairs. Each pair should be" - + " separated by an equal sign, for example: 'gad_campaignid=12345 gad_source=1'"); + "Failed to read the sessionAttributesMap. SessionAttributesMap must use a" + + " space-delimited list of session attribute key value pairs. Each pair should" + + " be separated by an equal sign, for example: 'gad_campaignid=12345" + + " gad_source=1'"); } sessionAttributePairs.addKeyValuePairs( SessionAttributeKeyValuePair.newBuilder() @@ -409,22 +409,46 @@ private String normalizeAndHash(MessageDigest digest, String s) /** * 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}. + * requires removal of any '.' characters or trailing '+' and characters that follow it from the + * username portion of the email address if the domain is {@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 all whitespace (leading, trailing, and intermediate) from the email address. + String normalizedEmail = emailAddress.toLowerCase().replaceAll("\\s+", ""); + String[] emailParts = normalizedEmail.split("@", 2); + if (emailParts.length == 2 && emailParts[1].matches("^(gmail|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].replaceAll("\\.", ""); + // Removes any '+' and all characters that follow it 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); } + + /** + * Returns the result of normalizing and hashing a phone number. For this use case, Google Ads + * requires phone numbers to be in E.164 format. + * + * @param digest the digest to use to hash the normalized string. + * @param phoneNumber the phone number to normalize and hash. + */ + private String normalizeAndHashPhoneNumber(MessageDigest digest, String phoneNumber) + throws UnsupportedEncodingException { + // Removes non-digit characters and prepends a leading '+' sign. + String digitsOnly = phoneNumber.replaceAll("[^0-9]", ""); + String formattedPhone = "+" + digitsOnly; + if (!formattedPhone.matches("^\\+[1-9]\\d{6,14}$")) { + throw new IllegalArgumentException("Phone number must be in E.164 format: " + phoneNumber); + } + return normalizeAndHash(digest, formattedPhone); + } // [END normalize_and_hash] } diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForWeb.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForWeb.java index 329f8e872c..9aa0a325c9 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForWeb.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadEnhancedConversionsForWeb.java @@ -171,9 +171,8 @@ private void runExample( ImmutableMap.Builder rawRecordBuilder = ImmutableMap.builder() - .put("email", "alex.2@example.com") - // Email address that includes a period (.) before the Gmail domain. - .put("email", "alex.2@example.com") + // Email address that includes a period (.) and plus (+) suffix before the Gmail domain. + .put("email", "alex.2+myalias@gmail.com") // Address that includes all four required elements: first name, last name, country // code, and postal code. .put("firstName", "Alex") @@ -181,7 +180,7 @@ private void runExample( .put("countryCode", "US") .put("postalCode", "94045") // Phone number to be converted to E.164 format, with a leading '+' as required. - .put("phone", "+1 800 5550102") + .put("phone", "+1 800-555-0102") // This example lets you put conversion details as arguments, but in reality you might // store this data alongside other user data, so we include it in this sample user // record. @@ -222,7 +221,8 @@ private void runExample( if (rawRecord.containsKey("phone")) { UserIdentifier hashedPhoneNumberIdentifier = UserIdentifier.newBuilder() - .setHashedPhoneNumber(normalizeAndHash(sha256Digest, rawRecord.get("phone"), true)) + .setHashedPhoneNumber( + normalizeAndHashPhoneNumber(sha256Digest, rawRecord.get("phone"))) .build(); // Adds the hashed phone number identifier to the UserData object's list. userIdentifiers.add(hashedPhoneNumberIdentifier); @@ -363,22 +363,46 @@ private String normalizeAndHash(MessageDigest digest, String s, boolean trimInte /** * 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}. + * requires removal of any '.' characters or trailing '+' and characters that follow it from the + * username portion of the email address if the domain is {@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 all whitespace (leading, trailing, and intermediate) from the email address. + String normalizedEmail = emailAddress.toLowerCase().replaceAll("\\s+", ""); + String[] emailParts = normalizedEmail.split("@", 2); + if (emailParts.length == 2 && emailParts[1].matches("^(gmail|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].replaceAll("\\.", ""); + // Removes any '+' and all characters that follow it 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, true); } + + /** + * Returns the result of normalizing and hashing a phone number. For this use case, Google Ads + * requires phone numbers to be in E.164 format. + * + * @param digest the digest to use to hash the normalized string. + * @param phoneNumber the phone number to normalize and hash. + */ + private String normalizeAndHashPhoneNumber(MessageDigest digest, String phoneNumber) + throws UnsupportedEncodingException { + // Removes non-digit characters and prepends a leading '+' sign. + String digitsOnly = phoneNumber.replaceAll("[^0-9]", ""); + String formattedPhone = "+" + digitsOnly; + if (!formattedPhone.matches("^\\+[1-9]\\d{6,14}$")) { + throw new IllegalArgumentException("Phone number must be in E.164 format: " + phoneNumber); + } + return normalizeAndHash(digest, formattedPhone, true); + } // [END normalize_and_hash] } diff --git a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java index 61d495938f..5e6b0b2a4f 100644 --- a/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java +++ b/google-ads-examples/src/main/java/com/google/ads/googleads/examples/remarketing/UploadStoreSalesTransactions.java @@ -562,7 +562,7 @@ private List buildOfflineUserDataJobOperations( throw new RuntimeException("Missing SHA-256 algorithm implementation", e); } - // Create the first transaction for upload based on an email address and state. + // Create the first transaction for upload based on an email address, phone number, and state. UserData.Builder userDataWithEmailAddress = UserData.newBuilder() .addAllUserIdentifiers( @@ -570,7 +570,12 @@ private List buildOfflineUserDataJobOperations( UserIdentifier.newBuilder() .setHashedEmail( // Email addresses must be normalized and hashed. - normalizeAndHash(sha256Digest, "dana@example.com")) + normalizeAndHashEmailAddress(sha256Digest, "dana.2+myalias@gmail.com")) + .build(), + UserIdentifier.newBuilder() + .setHashedPhoneNumber( + // Phone numbers must be normalized and hashed. + normalizeAndHashPhoneNumber(sha256Digest, "+1 (800) 555-0101")) .build(), UserIdentifier.newBuilder() .setAddressInfo(OfflineUserAddressInfo.newBuilder().setState("NY")) @@ -675,6 +680,50 @@ private String normalizeAndHash(MessageDigest digest, String s) return result.toString(); } + /** + * Returns the result of normalizing and hashing an email address. For this use case, Google Ads + * requires removal of any '.' characters or trailing '+' and characters that follow it from the + * username portion of the email address if the domain is {@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 { + // Removes all whitespace (leading, trailing, and intermediate) from the email address. + String normalizedEmail = emailAddress.toLowerCase().replaceAll("\\s+", ""); + String[] emailParts = normalizedEmail.split("@", 2); + if (emailParts.length == 2 && emailParts[1].matches("^(gmail|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].replaceAll("\\.", ""); + // Removes any '+' and all characters that follow it 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); + } + + /** + * Returns the result of normalizing and hashing a phone number. For this use case, Google Ads + * requires phone numbers to be in E.164 format. + * + * @param digest the digest to use to hash the normalized string. + * @param phoneNumber the phone number to normalize and hash. + */ + private String normalizeAndHashPhoneNumber(MessageDigest digest, String phoneNumber) + throws UnsupportedEncodingException { + // Removes non-digit characters and prepends a leading '+' sign. + String digitsOnly = phoneNumber.replaceAll("[^0-9]", ""); + String formattedPhone = "+" + digitsOnly; + if (!formattedPhone.matches("^\\+[1-9]\\d{6,14}$")) { + throw new IllegalArgumentException("Phone number must be in E.164 format: " + phoneNumber); + } + return normalizeAndHash(digest, formattedPhone); + } + /** Retrieves, checks, and prints the status of the offline user data job. */ private void checkJobStatus( GoogleAdsClient googleAdsClient, long customerId, String offlineUserDataJobResourceName) {