From bb6ea908f9d45be19a381a886743207ef9b017ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:31:59 +0000 Subject: [PATCH 1/5] Initial plan From 5b4be0b2835a00d5cee042ff0c820ae63cd83e7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:41:59 +0000 Subject: [PATCH 2/5] Add new actions to Snapchat Conversions API destination Co-authored-by: varadarajan-tw <109586712+varadarajan-tw@users.noreply.github.com> --- .../snap-conversions-api/index.ts | 60 ++- .../snap-conversions-api/shared-fields.ts | 5 + .../syncUserData/generated-types.ts | 438 ++++++++++++++++++ .../syncUserData/index.ts | 16 + .../trackAppEvent/generated-types.ts | 438 ++++++++++++++++++ .../trackAppEvent/index.ts | 16 + .../trackEvent/generated-types.ts | 438 ++++++++++++++++++ .../snap-conversions-api/trackEvent/index.ts | 16 + .../trackPurchase/generated-types.ts | 438 ++++++++++++++++++ .../trackPurchase/index.ts | 16 + 10 files changed, 1873 insertions(+), 8 deletions(-) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/index.ts index f5376d842b3..574768b7e6d 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/index.ts @@ -2,6 +2,10 @@ import { defaultValues, DestinationDefinition } from '@segment/actions-core' import type { Settings } from './generated-types' import reportConversionEvent from './reportConversionEvent' +import trackEvent from './trackEvent' +import trackAppEvent from './trackAppEvent' +import trackPurchase from './trackPurchase' +import syncUserData from './syncUserData' const DEFAULT_VALS = { ...defaultValues(reportConversionEvent.fields) @@ -14,13 +18,6 @@ interface RefreshTokenResponse { } const presets: DestinationDefinition['presets'] = [ - { - name: 'Snap Browser Plugin', - subscribe: 'type = "track" or type = "identify" or type = "group" or type = "page" or type = "alias"', - partnerAction: 'snapPlugin', - mapping: {}, - type: 'automatic' - }, { name: 'Add Billing', subscribe: 'event = "Payment Info Entered"', @@ -174,6 +171,49 @@ const presets: DestinationDefinition['presets'] = [ ...DEFAULT_VALS }, type: 'automatic' + }, + // New presets for specific actions + { + name: 'Track Standard Events', + subscribe: 'type = "track"', + partnerAction: 'trackEvent', + mapping: { + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Track App Events', + subscribe: 'type = "track" and context.device.type != null', + partnerAction: 'trackAppEvent', + mapping: { + action_source: 'app', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Track Purchase Events', + subscribe: 'type = "track" and event = "Order Completed"', + partnerAction: 'trackPurchase', + mapping: { + event_name: 'PURCHASE', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Sync User Data', + subscribe: 'type = "identify"', + partnerAction: 'syncUserData', + mapping: { + event_name: 'UPDATE_PROFILE', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' } ] @@ -227,7 +267,11 @@ const destination: DestinationDefinition = { }, presets, actions: { - reportConversionEvent + reportConversionEvent, + trackEvent, + trackAppEvent, + trackPurchase, + syncUserData } } diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts b/packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts new file mode 100644 index 00000000000..db017feef39 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts @@ -0,0 +1,5 @@ +// Re-export the v3 fields from the existing reportConversionEvent action +import snap_capi_input_fields_v3 from './reportConversionEvent/snap-capi-input-fields-v3' + +export { snap_capi_input_fields_v3 } +export type { Payload } from './reportConversionEvent/generated-types' \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/generated-types.ts new file mode 100644 index 00000000000..bdff545ddfd --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/generated-types.ts @@ -0,0 +1,438 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The conversion event type. For custom events, you must use one of the predefined event types (i.e. CUSTOM_EVENT_1). Please refer to the possible event types in [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters). + */ + event_name?: string + /** + * If you are reporting events via more than one method (Snap Pixel, App Ads Kit, Conversions API) you should use the same event_id across all methods. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Adds Kit events. + */ + event_id?: string + /** + * The Epoch timestamp for when the conversion happened. The timestamp cannot be more than 7 days in the past. + */ + event_time?: string + /** + * This field allows you to specify where your conversions occurred. + */ + action_source?: string + /** + * These parameters are a set of identifiers Snapchat can use for targeted attribution. You must provide at least one of the following parameters in your request. + */ + user_data?: { + /** + * Any unique ID from the advertiser, such as loyalty membership IDs, user IDs, and external cookie IDs. You can send one or more external IDs for a given event. + */ + externalId?: string[] + /** + * An email address in lowercase. + */ + email?: string + /** + * A phone number. Include only digits with country code, area code, and number. Remove symbols, letters, and any leading zeros. In addition, always include the country code, even if all of the data is from the same country, as the country code is used for matching. + */ + phone?: string + /** + * Gender in lowercase. Either f or m. + */ + gender?: string + /** + * A date of birth given as year, month, and day. Example: 19971226 for December 26, 1997. + */ + dateOfBirth?: string + /** + * A last name in lowercase. + */ + lastName?: string + /** + * A first name in lowercase. + */ + firstName?: string + /** + * A city in lowercase without spaces or punctuation. Example: menlopark. + */ + city?: string + /** + * A two-letter state code in lowercase. Example: ca. + */ + state?: string + /** + * A five-digit zip code for United States. For other locations, follow each country`s standards. + */ + zip?: string + /** + * A two-letter country code in lowercase. + */ + country?: string + /** + * The IP address of the browser corresponding to the event. + */ + client_ip_address?: string + /** + * The user agent for the browser corresponding to the event. This is required if action source is “website”. + */ + client_user_agent?: string + /** + * The subscription ID for the user in this transaction. + */ + subscriptionID?: string + /** + * This is the identifier associated with your Snapchat Lead Ad. + */ + leadID?: number + /** + * Mobile ad identifier (IDFA or AAID) of the user who triggered the conversion event. Segment will normalize and hash this value before sending to Snapchat. [Snapchat requires](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters) that every payload contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields. Also see [Segment documentation](https://segment.com/docs/connections/destinations/catalog/actions-snap-conversions/#required-parameters-and-hashing). + */ + madid?: string + /** + * The ID value stored in the landing page URL's `&ScCid=` query parameter. Using this ID improves ad measurement performance. We also encourage advertisers who are using `click_id` to pass the full url in the `page_url` field. For more details, please refer to [Sending a Click ID](#sending-a-click-id) + */ + sc_click_id?: string + /** + * Unique user ID cookie. If you are using the Pixel SDK, you can access a cookie1 by looking at the _scid value. + */ + sc_cookie1?: string + /** + * IDFV of the user’s device. Segment will normalize and hash this value before sending to Snapchat. + */ + idfv?: string + } + /** + * These fields support sending app events to Snapchat through the Conversions API. + */ + app_data?: { + /** + * *Required for app events* + * Use this field to specify ATT permission on an iOS 14.5+ device. Set to 0 for disabled or 1 for enabled. + */ + advertiser_tracking_enabled?: boolean + /** + * *Required for app events* + * A person can choose to enable ad tracking on an app level. Your SDK should allow an app developer to put an opt-out setting into their app. Use this field to specify the person's choice. Use 0 for disabled, 1 for enabled. + */ + application_tracking_enabled?: boolean + /** + * *Required for app events* Example: 'i2'. + */ + version?: string + /** + * Example: 'com.snapchat.sdk.samples.hello'. + */ + packageName?: string + /** + * Example: '1.0'. + */ + shortVersion?: string + /** + * Example: '1.0 long'. + */ + longVersion?: string + /** + * Example: '13.4.1'. + */ + osVersion?: string + /** + * Example: 'iPhone5,1'. + */ + deviceName?: string + /** + * Example: 'En_US'. + */ + locale?: string + /** + * Example: 'PST'. + */ + timezone?: string + /** + * Example: 'AT&T'. + */ + carrier?: string + /** + * Example: '1080'. + */ + width?: string + /** + * Example: '1920'. + */ + height?: string + /** + * Example: '2.0'. + */ + density?: string + /** + * Example: '8'. + */ + cpuCores?: string + /** + * Example: '64'. + */ + storageSize?: string + /** + * Example: '32'. + */ + freeStorage?: string + /** + * Example: 'USA/New York'. + */ + deviceTimezone?: string + } + /** + * The custom data object can be used to pass custom properties. + */ + custom_data?: { + /** + * Currency for the value specified as ISO 4217 code. + */ + currency?: string + /** + * The number of items when checkout was initiated. + */ + num_items?: number + /** + * Order ID tied to the conversion event. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Ads Kit events. + */ + order_id?: string + /** + * The text string that was searched for. + */ + search_string?: string + /** + * A string indicating the sign up method. + */ + sign_up_method?: string + /** + * Total value of the purchase. This should be a single number. Can be overriden using the 'Track Purchase Value Per Product' field. + */ + value?: number + /** + * The desired hotel check-in date in the hotel's time-zone. Accepted formats are YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD and YYYY-MM-DDThh:mm:ssTZD + */ + checkin_date?: string + /** + * End date of travel + */ + travel_end?: string + /** + * Start date of travel + */ + travel_start?: string + /** + * The suggested destinations + */ + suggested_destinations?: string + /** + * The destination airport. Make sure to use the IATA code of the airport + */ + destination_airport?: string + /** + * The country based on the location the user intends to visit + */ + country?: string + /** + * The city based on the location the user intends to visit + */ + city?: string + /** + * This could be the state, district, or region of interest to the user + */ + region?: string + /** + * The neighborhood the user is interested in + */ + neighborhood?: string + /** + * The starting date and time for travel + */ + departing_departure_date?: string + /** + * The arrival date and time at the destination for the travel + */ + departing_arrival_date?: string + /** + * The number of adults staying + */ + num_adults?: number + /** + * The official IATA code of origin airport + */ + origin_airport?: string + /** + * The starting date and time of the return journey + */ + returning_departure_date?: string + /** + * The date and time when the return journey is complete + */ + returning_arrival_date?: string + /** + * The number of children staying + */ + num_children?: number + /** + * This represents the hotels score relative to other hotels to an advertiser + */ + hotel_score?: string + /** + * The postal /zip code + */ + postal_code?: string + /** + * The number of infants staying + */ + num_infants?: number + /** + * Any preferred neighborhoods for the stay + */ + preferred_neighborhoods?: string + /** + * The minimum and maximum hotel star rating supplied as a tuple. This is what the user would use for filtering hotels + */ + preferred_star_ratings?: string + /** + * The suggested hotels + */ + suggested_hotels?: string + } + /** + * The Data Processing Options to send to Snapchat. If set to true, Segment will send an array to Snapchat indicating events should be processed with Limited Data Use (LDU) restrictions. + */ + data_processing_options?: boolean + /** + * A country that you want to associate to the Data Processing Options. Accepted values are 1, for the United States of America, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_country?: number + /** + * A state that you want to associate to the Data Processing Options. Accepted values are 1000, for California, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_state?: number + /** + * The URL of the web page where the event took place. + */ + event_source_url?: string + /** + * Use this field to send details of mulitple products / items. This field overrides individual 'Item ID', 'Item Category' and 'Brand' fields. Note: total purchase value is tracked using the 'Price' field + */ + products?: { + /** + * Identfier for the item. International Article Number (EAN) when applicable, or other product or category identifier. + */ + item_id?: string + /** + * Category of the item. This field accepts a string. + */ + item_category?: string + /** + * Brand associated with the item. This field accepts a string. + */ + brand?: string + }[] + /** + * [Deprecated] Use Products field. + */ + brands?: string[] + /** + * Deprecated. Use User Data sc_click_id field. + */ + click_id?: string + /** + * Deprecated. Use Event ID field. + */ + client_dedup_id?: string + /** + * Deprecated. Use Custom Data currency field. + */ + currency?: string + /** + * Deprecated. No longer supported. + */ + description?: string + /** + * Deprecated. Use App Data deviceName field. + */ + device_model?: string + /** + * Deprecated. Use User Data email field. + */ + email?: string + /** + * Deprecated. Use Action Source field. + */ + event_conversion_type?: string + /** + * Deprecated. No longer supported. + */ + event_tag?: string + /** + * Deprecated. Use Event Name field. + */ + event_type?: string + /** + * Deprecated. Use User Data idfv field. + */ + idfv?: string + /** + * Deprecated. Use User Data client_ip_address field. + */ + ip_address?: string + /** + * Deprecated. Use products field. + */ + item_category?: string + /** + * Deprecated. Use products field. + */ + item_ids?: string + /** + * Deprecated. No longer supported. + */ + level?: string + /** + * Deprecated. Use User Data madid field. + */ + mobile_ad_id?: string + /** + * Deprecated. Use Custom Data num_items field. + */ + number_items?: string + /** + * Deprecated. Use App Data version field. + */ + os_version?: string + /** + * Deprecated. Use Event Source URL field. + */ + page_url?: string + /** + * Deprecated. Use User Data phone field. + */ + phone_number?: string + /** + * Deprecated. Use Custom Data value field. + */ + price?: number + /** + * Deprecated. Use Custom Data search_string field. + */ + search_string?: string + /** + * Deprecated. Use Custom Data sign_up_method field. + */ + sign_up_method?: string + /** + * Deprecated. Use Event Timestamp field. + */ + timestamp?: string + /** + * Deprecated. Use Custom Data order_id field. + */ + transaction_id?: string + /** + * Deprecated. Use User Data client_user_agent field. + */ + user_agent?: string + /** + * Deprecated. Use User Data sc_cookie1 field. + */ + uuid_c1?: string +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts new file mode 100644 index 00000000000..623ae3f21e5 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts @@ -0,0 +1,16 @@ +import { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' + +const action: ActionDefinition = { + title: 'Sync User Data', + description: + 'Synchronize user data without tracking events to Snapchat Conversions API. Used for updating user profiles and enhancing audience data for better ad targeting.', + defaultSubscription: 'type = "identify"', + fields: snap_capi_input_fields_v3, + perform +} + +export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/generated-types.ts new file mode 100644 index 00000000000..bdff545ddfd --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/generated-types.ts @@ -0,0 +1,438 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The conversion event type. For custom events, you must use one of the predefined event types (i.e. CUSTOM_EVENT_1). Please refer to the possible event types in [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters). + */ + event_name?: string + /** + * If you are reporting events via more than one method (Snap Pixel, App Ads Kit, Conversions API) you should use the same event_id across all methods. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Adds Kit events. + */ + event_id?: string + /** + * The Epoch timestamp for when the conversion happened. The timestamp cannot be more than 7 days in the past. + */ + event_time?: string + /** + * This field allows you to specify where your conversions occurred. + */ + action_source?: string + /** + * These parameters are a set of identifiers Snapchat can use for targeted attribution. You must provide at least one of the following parameters in your request. + */ + user_data?: { + /** + * Any unique ID from the advertiser, such as loyalty membership IDs, user IDs, and external cookie IDs. You can send one or more external IDs for a given event. + */ + externalId?: string[] + /** + * An email address in lowercase. + */ + email?: string + /** + * A phone number. Include only digits with country code, area code, and number. Remove symbols, letters, and any leading zeros. In addition, always include the country code, even if all of the data is from the same country, as the country code is used for matching. + */ + phone?: string + /** + * Gender in lowercase. Either f or m. + */ + gender?: string + /** + * A date of birth given as year, month, and day. Example: 19971226 for December 26, 1997. + */ + dateOfBirth?: string + /** + * A last name in lowercase. + */ + lastName?: string + /** + * A first name in lowercase. + */ + firstName?: string + /** + * A city in lowercase without spaces or punctuation. Example: menlopark. + */ + city?: string + /** + * A two-letter state code in lowercase. Example: ca. + */ + state?: string + /** + * A five-digit zip code for United States. For other locations, follow each country`s standards. + */ + zip?: string + /** + * A two-letter country code in lowercase. + */ + country?: string + /** + * The IP address of the browser corresponding to the event. + */ + client_ip_address?: string + /** + * The user agent for the browser corresponding to the event. This is required if action source is “website”. + */ + client_user_agent?: string + /** + * The subscription ID for the user in this transaction. + */ + subscriptionID?: string + /** + * This is the identifier associated with your Snapchat Lead Ad. + */ + leadID?: number + /** + * Mobile ad identifier (IDFA or AAID) of the user who triggered the conversion event. Segment will normalize and hash this value before sending to Snapchat. [Snapchat requires](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters) that every payload contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields. Also see [Segment documentation](https://segment.com/docs/connections/destinations/catalog/actions-snap-conversions/#required-parameters-and-hashing). + */ + madid?: string + /** + * The ID value stored in the landing page URL's `&ScCid=` query parameter. Using this ID improves ad measurement performance. We also encourage advertisers who are using `click_id` to pass the full url in the `page_url` field. For more details, please refer to [Sending a Click ID](#sending-a-click-id) + */ + sc_click_id?: string + /** + * Unique user ID cookie. If you are using the Pixel SDK, you can access a cookie1 by looking at the _scid value. + */ + sc_cookie1?: string + /** + * IDFV of the user’s device. Segment will normalize and hash this value before sending to Snapchat. + */ + idfv?: string + } + /** + * These fields support sending app events to Snapchat through the Conversions API. + */ + app_data?: { + /** + * *Required for app events* + * Use this field to specify ATT permission on an iOS 14.5+ device. Set to 0 for disabled or 1 for enabled. + */ + advertiser_tracking_enabled?: boolean + /** + * *Required for app events* + * A person can choose to enable ad tracking on an app level. Your SDK should allow an app developer to put an opt-out setting into their app. Use this field to specify the person's choice. Use 0 for disabled, 1 for enabled. + */ + application_tracking_enabled?: boolean + /** + * *Required for app events* Example: 'i2'. + */ + version?: string + /** + * Example: 'com.snapchat.sdk.samples.hello'. + */ + packageName?: string + /** + * Example: '1.0'. + */ + shortVersion?: string + /** + * Example: '1.0 long'. + */ + longVersion?: string + /** + * Example: '13.4.1'. + */ + osVersion?: string + /** + * Example: 'iPhone5,1'. + */ + deviceName?: string + /** + * Example: 'En_US'. + */ + locale?: string + /** + * Example: 'PST'. + */ + timezone?: string + /** + * Example: 'AT&T'. + */ + carrier?: string + /** + * Example: '1080'. + */ + width?: string + /** + * Example: '1920'. + */ + height?: string + /** + * Example: '2.0'. + */ + density?: string + /** + * Example: '8'. + */ + cpuCores?: string + /** + * Example: '64'. + */ + storageSize?: string + /** + * Example: '32'. + */ + freeStorage?: string + /** + * Example: 'USA/New York'. + */ + deviceTimezone?: string + } + /** + * The custom data object can be used to pass custom properties. + */ + custom_data?: { + /** + * Currency for the value specified as ISO 4217 code. + */ + currency?: string + /** + * The number of items when checkout was initiated. + */ + num_items?: number + /** + * Order ID tied to the conversion event. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Ads Kit events. + */ + order_id?: string + /** + * The text string that was searched for. + */ + search_string?: string + /** + * A string indicating the sign up method. + */ + sign_up_method?: string + /** + * Total value of the purchase. This should be a single number. Can be overriden using the 'Track Purchase Value Per Product' field. + */ + value?: number + /** + * The desired hotel check-in date in the hotel's time-zone. Accepted formats are YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD and YYYY-MM-DDThh:mm:ssTZD + */ + checkin_date?: string + /** + * End date of travel + */ + travel_end?: string + /** + * Start date of travel + */ + travel_start?: string + /** + * The suggested destinations + */ + suggested_destinations?: string + /** + * The destination airport. Make sure to use the IATA code of the airport + */ + destination_airport?: string + /** + * The country based on the location the user intends to visit + */ + country?: string + /** + * The city based on the location the user intends to visit + */ + city?: string + /** + * This could be the state, district, or region of interest to the user + */ + region?: string + /** + * The neighborhood the user is interested in + */ + neighborhood?: string + /** + * The starting date and time for travel + */ + departing_departure_date?: string + /** + * The arrival date and time at the destination for the travel + */ + departing_arrival_date?: string + /** + * The number of adults staying + */ + num_adults?: number + /** + * The official IATA code of origin airport + */ + origin_airport?: string + /** + * The starting date and time of the return journey + */ + returning_departure_date?: string + /** + * The date and time when the return journey is complete + */ + returning_arrival_date?: string + /** + * The number of children staying + */ + num_children?: number + /** + * This represents the hotels score relative to other hotels to an advertiser + */ + hotel_score?: string + /** + * The postal /zip code + */ + postal_code?: string + /** + * The number of infants staying + */ + num_infants?: number + /** + * Any preferred neighborhoods for the stay + */ + preferred_neighborhoods?: string + /** + * The minimum and maximum hotel star rating supplied as a tuple. This is what the user would use for filtering hotels + */ + preferred_star_ratings?: string + /** + * The suggested hotels + */ + suggested_hotels?: string + } + /** + * The Data Processing Options to send to Snapchat. If set to true, Segment will send an array to Snapchat indicating events should be processed with Limited Data Use (LDU) restrictions. + */ + data_processing_options?: boolean + /** + * A country that you want to associate to the Data Processing Options. Accepted values are 1, for the United States of America, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_country?: number + /** + * A state that you want to associate to the Data Processing Options. Accepted values are 1000, for California, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_state?: number + /** + * The URL of the web page where the event took place. + */ + event_source_url?: string + /** + * Use this field to send details of mulitple products / items. This field overrides individual 'Item ID', 'Item Category' and 'Brand' fields. Note: total purchase value is tracked using the 'Price' field + */ + products?: { + /** + * Identfier for the item. International Article Number (EAN) when applicable, or other product or category identifier. + */ + item_id?: string + /** + * Category of the item. This field accepts a string. + */ + item_category?: string + /** + * Brand associated with the item. This field accepts a string. + */ + brand?: string + }[] + /** + * [Deprecated] Use Products field. + */ + brands?: string[] + /** + * Deprecated. Use User Data sc_click_id field. + */ + click_id?: string + /** + * Deprecated. Use Event ID field. + */ + client_dedup_id?: string + /** + * Deprecated. Use Custom Data currency field. + */ + currency?: string + /** + * Deprecated. No longer supported. + */ + description?: string + /** + * Deprecated. Use App Data deviceName field. + */ + device_model?: string + /** + * Deprecated. Use User Data email field. + */ + email?: string + /** + * Deprecated. Use Action Source field. + */ + event_conversion_type?: string + /** + * Deprecated. No longer supported. + */ + event_tag?: string + /** + * Deprecated. Use Event Name field. + */ + event_type?: string + /** + * Deprecated. Use User Data idfv field. + */ + idfv?: string + /** + * Deprecated. Use User Data client_ip_address field. + */ + ip_address?: string + /** + * Deprecated. Use products field. + */ + item_category?: string + /** + * Deprecated. Use products field. + */ + item_ids?: string + /** + * Deprecated. No longer supported. + */ + level?: string + /** + * Deprecated. Use User Data madid field. + */ + mobile_ad_id?: string + /** + * Deprecated. Use Custom Data num_items field. + */ + number_items?: string + /** + * Deprecated. Use App Data version field. + */ + os_version?: string + /** + * Deprecated. Use Event Source URL field. + */ + page_url?: string + /** + * Deprecated. Use User Data phone field. + */ + phone_number?: string + /** + * Deprecated. Use Custom Data value field. + */ + price?: number + /** + * Deprecated. Use Custom Data search_string field. + */ + search_string?: string + /** + * Deprecated. Use Custom Data sign_up_method field. + */ + sign_up_method?: string + /** + * Deprecated. Use Event Timestamp field. + */ + timestamp?: string + /** + * Deprecated. Use Custom Data order_id field. + */ + transaction_id?: string + /** + * Deprecated. Use User Data client_user_agent field. + */ + user_agent?: string + /** + * Deprecated. Use User Data sc_cookie1 field. + */ + uuid_c1?: string +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts new file mode 100644 index 00000000000..161e5fd248a --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts @@ -0,0 +1,16 @@ +import { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' + +const action: ActionDefinition = { + title: 'Track App Event', + description: + 'Track app-specific events to Snapchat Conversions API. Designed for mobile app conversion tracking with enhanced app data and device information.', + defaultSubscription: 'type = "track" and context.device.type != null', + fields: snap_capi_input_fields_v3, + perform +} + +export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/generated-types.ts new file mode 100644 index 00000000000..bdff545ddfd --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/generated-types.ts @@ -0,0 +1,438 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The conversion event type. For custom events, you must use one of the predefined event types (i.e. CUSTOM_EVENT_1). Please refer to the possible event types in [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters). + */ + event_name?: string + /** + * If you are reporting events via more than one method (Snap Pixel, App Ads Kit, Conversions API) you should use the same event_id across all methods. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Adds Kit events. + */ + event_id?: string + /** + * The Epoch timestamp for when the conversion happened. The timestamp cannot be more than 7 days in the past. + */ + event_time?: string + /** + * This field allows you to specify where your conversions occurred. + */ + action_source?: string + /** + * These parameters are a set of identifiers Snapchat can use for targeted attribution. You must provide at least one of the following parameters in your request. + */ + user_data?: { + /** + * Any unique ID from the advertiser, such as loyalty membership IDs, user IDs, and external cookie IDs. You can send one or more external IDs for a given event. + */ + externalId?: string[] + /** + * An email address in lowercase. + */ + email?: string + /** + * A phone number. Include only digits with country code, area code, and number. Remove symbols, letters, and any leading zeros. In addition, always include the country code, even if all of the data is from the same country, as the country code is used for matching. + */ + phone?: string + /** + * Gender in lowercase. Either f or m. + */ + gender?: string + /** + * A date of birth given as year, month, and day. Example: 19971226 for December 26, 1997. + */ + dateOfBirth?: string + /** + * A last name in lowercase. + */ + lastName?: string + /** + * A first name in lowercase. + */ + firstName?: string + /** + * A city in lowercase without spaces or punctuation. Example: menlopark. + */ + city?: string + /** + * A two-letter state code in lowercase. Example: ca. + */ + state?: string + /** + * A five-digit zip code for United States. For other locations, follow each country`s standards. + */ + zip?: string + /** + * A two-letter country code in lowercase. + */ + country?: string + /** + * The IP address of the browser corresponding to the event. + */ + client_ip_address?: string + /** + * The user agent for the browser corresponding to the event. This is required if action source is “website”. + */ + client_user_agent?: string + /** + * The subscription ID for the user in this transaction. + */ + subscriptionID?: string + /** + * This is the identifier associated with your Snapchat Lead Ad. + */ + leadID?: number + /** + * Mobile ad identifier (IDFA or AAID) of the user who triggered the conversion event. Segment will normalize and hash this value before sending to Snapchat. [Snapchat requires](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters) that every payload contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields. Also see [Segment documentation](https://segment.com/docs/connections/destinations/catalog/actions-snap-conversions/#required-parameters-and-hashing). + */ + madid?: string + /** + * The ID value stored in the landing page URL's `&ScCid=` query parameter. Using this ID improves ad measurement performance. We also encourage advertisers who are using `click_id` to pass the full url in the `page_url` field. For more details, please refer to [Sending a Click ID](#sending-a-click-id) + */ + sc_click_id?: string + /** + * Unique user ID cookie. If you are using the Pixel SDK, you can access a cookie1 by looking at the _scid value. + */ + sc_cookie1?: string + /** + * IDFV of the user’s device. Segment will normalize and hash this value before sending to Snapchat. + */ + idfv?: string + } + /** + * These fields support sending app events to Snapchat through the Conversions API. + */ + app_data?: { + /** + * *Required for app events* + * Use this field to specify ATT permission on an iOS 14.5+ device. Set to 0 for disabled or 1 for enabled. + */ + advertiser_tracking_enabled?: boolean + /** + * *Required for app events* + * A person can choose to enable ad tracking on an app level. Your SDK should allow an app developer to put an opt-out setting into their app. Use this field to specify the person's choice. Use 0 for disabled, 1 for enabled. + */ + application_tracking_enabled?: boolean + /** + * *Required for app events* Example: 'i2'. + */ + version?: string + /** + * Example: 'com.snapchat.sdk.samples.hello'. + */ + packageName?: string + /** + * Example: '1.0'. + */ + shortVersion?: string + /** + * Example: '1.0 long'. + */ + longVersion?: string + /** + * Example: '13.4.1'. + */ + osVersion?: string + /** + * Example: 'iPhone5,1'. + */ + deviceName?: string + /** + * Example: 'En_US'. + */ + locale?: string + /** + * Example: 'PST'. + */ + timezone?: string + /** + * Example: 'AT&T'. + */ + carrier?: string + /** + * Example: '1080'. + */ + width?: string + /** + * Example: '1920'. + */ + height?: string + /** + * Example: '2.0'. + */ + density?: string + /** + * Example: '8'. + */ + cpuCores?: string + /** + * Example: '64'. + */ + storageSize?: string + /** + * Example: '32'. + */ + freeStorage?: string + /** + * Example: 'USA/New York'. + */ + deviceTimezone?: string + } + /** + * The custom data object can be used to pass custom properties. + */ + custom_data?: { + /** + * Currency for the value specified as ISO 4217 code. + */ + currency?: string + /** + * The number of items when checkout was initiated. + */ + num_items?: number + /** + * Order ID tied to the conversion event. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Ads Kit events. + */ + order_id?: string + /** + * The text string that was searched for. + */ + search_string?: string + /** + * A string indicating the sign up method. + */ + sign_up_method?: string + /** + * Total value of the purchase. This should be a single number. Can be overriden using the 'Track Purchase Value Per Product' field. + */ + value?: number + /** + * The desired hotel check-in date in the hotel's time-zone. Accepted formats are YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD and YYYY-MM-DDThh:mm:ssTZD + */ + checkin_date?: string + /** + * End date of travel + */ + travel_end?: string + /** + * Start date of travel + */ + travel_start?: string + /** + * The suggested destinations + */ + suggested_destinations?: string + /** + * The destination airport. Make sure to use the IATA code of the airport + */ + destination_airport?: string + /** + * The country based on the location the user intends to visit + */ + country?: string + /** + * The city based on the location the user intends to visit + */ + city?: string + /** + * This could be the state, district, or region of interest to the user + */ + region?: string + /** + * The neighborhood the user is interested in + */ + neighborhood?: string + /** + * The starting date and time for travel + */ + departing_departure_date?: string + /** + * The arrival date and time at the destination for the travel + */ + departing_arrival_date?: string + /** + * The number of adults staying + */ + num_adults?: number + /** + * The official IATA code of origin airport + */ + origin_airport?: string + /** + * The starting date and time of the return journey + */ + returning_departure_date?: string + /** + * The date and time when the return journey is complete + */ + returning_arrival_date?: string + /** + * The number of children staying + */ + num_children?: number + /** + * This represents the hotels score relative to other hotels to an advertiser + */ + hotel_score?: string + /** + * The postal /zip code + */ + postal_code?: string + /** + * The number of infants staying + */ + num_infants?: number + /** + * Any preferred neighborhoods for the stay + */ + preferred_neighborhoods?: string + /** + * The minimum and maximum hotel star rating supplied as a tuple. This is what the user would use for filtering hotels + */ + preferred_star_ratings?: string + /** + * The suggested hotels + */ + suggested_hotels?: string + } + /** + * The Data Processing Options to send to Snapchat. If set to true, Segment will send an array to Snapchat indicating events should be processed with Limited Data Use (LDU) restrictions. + */ + data_processing_options?: boolean + /** + * A country that you want to associate to the Data Processing Options. Accepted values are 1, for the United States of America, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_country?: number + /** + * A state that you want to associate to the Data Processing Options. Accepted values are 1000, for California, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_state?: number + /** + * The URL of the web page where the event took place. + */ + event_source_url?: string + /** + * Use this field to send details of mulitple products / items. This field overrides individual 'Item ID', 'Item Category' and 'Brand' fields. Note: total purchase value is tracked using the 'Price' field + */ + products?: { + /** + * Identfier for the item. International Article Number (EAN) when applicable, or other product or category identifier. + */ + item_id?: string + /** + * Category of the item. This field accepts a string. + */ + item_category?: string + /** + * Brand associated with the item. This field accepts a string. + */ + brand?: string + }[] + /** + * [Deprecated] Use Products field. + */ + brands?: string[] + /** + * Deprecated. Use User Data sc_click_id field. + */ + click_id?: string + /** + * Deprecated. Use Event ID field. + */ + client_dedup_id?: string + /** + * Deprecated. Use Custom Data currency field. + */ + currency?: string + /** + * Deprecated. No longer supported. + */ + description?: string + /** + * Deprecated. Use App Data deviceName field. + */ + device_model?: string + /** + * Deprecated. Use User Data email field. + */ + email?: string + /** + * Deprecated. Use Action Source field. + */ + event_conversion_type?: string + /** + * Deprecated. No longer supported. + */ + event_tag?: string + /** + * Deprecated. Use Event Name field. + */ + event_type?: string + /** + * Deprecated. Use User Data idfv field. + */ + idfv?: string + /** + * Deprecated. Use User Data client_ip_address field. + */ + ip_address?: string + /** + * Deprecated. Use products field. + */ + item_category?: string + /** + * Deprecated. Use products field. + */ + item_ids?: string + /** + * Deprecated. No longer supported. + */ + level?: string + /** + * Deprecated. Use User Data madid field. + */ + mobile_ad_id?: string + /** + * Deprecated. Use Custom Data num_items field. + */ + number_items?: string + /** + * Deprecated. Use App Data version field. + */ + os_version?: string + /** + * Deprecated. Use Event Source URL field. + */ + page_url?: string + /** + * Deprecated. Use User Data phone field. + */ + phone_number?: string + /** + * Deprecated. Use Custom Data value field. + */ + price?: number + /** + * Deprecated. Use Custom Data search_string field. + */ + search_string?: string + /** + * Deprecated. Use Custom Data sign_up_method field. + */ + sign_up_method?: string + /** + * Deprecated. Use Event Timestamp field. + */ + timestamp?: string + /** + * Deprecated. Use Custom Data order_id field. + */ + transaction_id?: string + /** + * Deprecated. Use User Data client_user_agent field. + */ + user_agent?: string + /** + * Deprecated. Use User Data sc_cookie1 field. + */ + uuid_c1?: string +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts new file mode 100644 index 00000000000..71ba69e16ad --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts @@ -0,0 +1,16 @@ +import { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' + +const action: ActionDefinition = { + title: 'Track Event', + description: + 'Track standard conversion events to Snapchat Conversions API. Automatically maps Segment track events to appropriate Snapchat event types for conversion tracking and optimization.', + defaultSubscription: 'type = "track"', + fields: snap_capi_input_fields_v3, + perform +} + +export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/generated-types.ts new file mode 100644 index 00000000000..bdff545ddfd --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/generated-types.ts @@ -0,0 +1,438 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The conversion event type. For custom events, you must use one of the predefined event types (i.e. CUSTOM_EVENT_1). Please refer to the possible event types in [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters). + */ + event_name?: string + /** + * If you are reporting events via more than one method (Snap Pixel, App Ads Kit, Conversions API) you should use the same event_id across all methods. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Adds Kit events. + */ + event_id?: string + /** + * The Epoch timestamp for when the conversion happened. The timestamp cannot be more than 7 days in the past. + */ + event_time?: string + /** + * This field allows you to specify where your conversions occurred. + */ + action_source?: string + /** + * These parameters are a set of identifiers Snapchat can use for targeted attribution. You must provide at least one of the following parameters in your request. + */ + user_data?: { + /** + * Any unique ID from the advertiser, such as loyalty membership IDs, user IDs, and external cookie IDs. You can send one or more external IDs for a given event. + */ + externalId?: string[] + /** + * An email address in lowercase. + */ + email?: string + /** + * A phone number. Include only digits with country code, area code, and number. Remove symbols, letters, and any leading zeros. In addition, always include the country code, even if all of the data is from the same country, as the country code is used for matching. + */ + phone?: string + /** + * Gender in lowercase. Either f or m. + */ + gender?: string + /** + * A date of birth given as year, month, and day. Example: 19971226 for December 26, 1997. + */ + dateOfBirth?: string + /** + * A last name in lowercase. + */ + lastName?: string + /** + * A first name in lowercase. + */ + firstName?: string + /** + * A city in lowercase without spaces or punctuation. Example: menlopark. + */ + city?: string + /** + * A two-letter state code in lowercase. Example: ca. + */ + state?: string + /** + * A five-digit zip code for United States. For other locations, follow each country`s standards. + */ + zip?: string + /** + * A two-letter country code in lowercase. + */ + country?: string + /** + * The IP address of the browser corresponding to the event. + */ + client_ip_address?: string + /** + * The user agent for the browser corresponding to the event. This is required if action source is “website”. + */ + client_user_agent?: string + /** + * The subscription ID for the user in this transaction. + */ + subscriptionID?: string + /** + * This is the identifier associated with your Snapchat Lead Ad. + */ + leadID?: number + /** + * Mobile ad identifier (IDFA or AAID) of the user who triggered the conversion event. Segment will normalize and hash this value before sending to Snapchat. [Snapchat requires](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters) that every payload contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields. Also see [Segment documentation](https://segment.com/docs/connections/destinations/catalog/actions-snap-conversions/#required-parameters-and-hashing). + */ + madid?: string + /** + * The ID value stored in the landing page URL's `&ScCid=` query parameter. Using this ID improves ad measurement performance. We also encourage advertisers who are using `click_id` to pass the full url in the `page_url` field. For more details, please refer to [Sending a Click ID](#sending-a-click-id) + */ + sc_click_id?: string + /** + * Unique user ID cookie. If you are using the Pixel SDK, you can access a cookie1 by looking at the _scid value. + */ + sc_cookie1?: string + /** + * IDFV of the user’s device. Segment will normalize and hash this value before sending to Snapchat. + */ + idfv?: string + } + /** + * These fields support sending app events to Snapchat through the Conversions API. + */ + app_data?: { + /** + * *Required for app events* + * Use this field to specify ATT permission on an iOS 14.5+ device. Set to 0 for disabled or 1 for enabled. + */ + advertiser_tracking_enabled?: boolean + /** + * *Required for app events* + * A person can choose to enable ad tracking on an app level. Your SDK should allow an app developer to put an opt-out setting into their app. Use this field to specify the person's choice. Use 0 for disabled, 1 for enabled. + */ + application_tracking_enabled?: boolean + /** + * *Required for app events* Example: 'i2'. + */ + version?: string + /** + * Example: 'com.snapchat.sdk.samples.hello'. + */ + packageName?: string + /** + * Example: '1.0'. + */ + shortVersion?: string + /** + * Example: '1.0 long'. + */ + longVersion?: string + /** + * Example: '13.4.1'. + */ + osVersion?: string + /** + * Example: 'iPhone5,1'. + */ + deviceName?: string + /** + * Example: 'En_US'. + */ + locale?: string + /** + * Example: 'PST'. + */ + timezone?: string + /** + * Example: 'AT&T'. + */ + carrier?: string + /** + * Example: '1080'. + */ + width?: string + /** + * Example: '1920'. + */ + height?: string + /** + * Example: '2.0'. + */ + density?: string + /** + * Example: '8'. + */ + cpuCores?: string + /** + * Example: '64'. + */ + storageSize?: string + /** + * Example: '32'. + */ + freeStorage?: string + /** + * Example: 'USA/New York'. + */ + deviceTimezone?: string + } + /** + * The custom data object can be used to pass custom properties. + */ + custom_data?: { + /** + * Currency for the value specified as ISO 4217 code. + */ + currency?: string + /** + * The number of items when checkout was initiated. + */ + num_items?: number + /** + * Order ID tied to the conversion event. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Ads Kit events. + */ + order_id?: string + /** + * The text string that was searched for. + */ + search_string?: string + /** + * A string indicating the sign up method. + */ + sign_up_method?: string + /** + * Total value of the purchase. This should be a single number. Can be overriden using the 'Track Purchase Value Per Product' field. + */ + value?: number + /** + * The desired hotel check-in date in the hotel's time-zone. Accepted formats are YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD and YYYY-MM-DDThh:mm:ssTZD + */ + checkin_date?: string + /** + * End date of travel + */ + travel_end?: string + /** + * Start date of travel + */ + travel_start?: string + /** + * The suggested destinations + */ + suggested_destinations?: string + /** + * The destination airport. Make sure to use the IATA code of the airport + */ + destination_airport?: string + /** + * The country based on the location the user intends to visit + */ + country?: string + /** + * The city based on the location the user intends to visit + */ + city?: string + /** + * This could be the state, district, or region of interest to the user + */ + region?: string + /** + * The neighborhood the user is interested in + */ + neighborhood?: string + /** + * The starting date and time for travel + */ + departing_departure_date?: string + /** + * The arrival date and time at the destination for the travel + */ + departing_arrival_date?: string + /** + * The number of adults staying + */ + num_adults?: number + /** + * The official IATA code of origin airport + */ + origin_airport?: string + /** + * The starting date and time of the return journey + */ + returning_departure_date?: string + /** + * The date and time when the return journey is complete + */ + returning_arrival_date?: string + /** + * The number of children staying + */ + num_children?: number + /** + * This represents the hotels score relative to other hotels to an advertiser + */ + hotel_score?: string + /** + * The postal /zip code + */ + postal_code?: string + /** + * The number of infants staying + */ + num_infants?: number + /** + * Any preferred neighborhoods for the stay + */ + preferred_neighborhoods?: string + /** + * The minimum and maximum hotel star rating supplied as a tuple. This is what the user would use for filtering hotels + */ + preferred_star_ratings?: string + /** + * The suggested hotels + */ + suggested_hotels?: string + } + /** + * The Data Processing Options to send to Snapchat. If set to true, Segment will send an array to Snapchat indicating events should be processed with Limited Data Use (LDU) restrictions. + */ + data_processing_options?: boolean + /** + * A country that you want to associate to the Data Processing Options. Accepted values are 1, for the United States of America, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_country?: number + /** + * A state that you want to associate to the Data Processing Options. Accepted values are 1000, for California, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_state?: number + /** + * The URL of the web page where the event took place. + */ + event_source_url?: string + /** + * Use this field to send details of mulitple products / items. This field overrides individual 'Item ID', 'Item Category' and 'Brand' fields. Note: total purchase value is tracked using the 'Price' field + */ + products?: { + /** + * Identfier for the item. International Article Number (EAN) when applicable, or other product or category identifier. + */ + item_id?: string + /** + * Category of the item. This field accepts a string. + */ + item_category?: string + /** + * Brand associated with the item. This field accepts a string. + */ + brand?: string + }[] + /** + * [Deprecated] Use Products field. + */ + brands?: string[] + /** + * Deprecated. Use User Data sc_click_id field. + */ + click_id?: string + /** + * Deprecated. Use Event ID field. + */ + client_dedup_id?: string + /** + * Deprecated. Use Custom Data currency field. + */ + currency?: string + /** + * Deprecated. No longer supported. + */ + description?: string + /** + * Deprecated. Use App Data deviceName field. + */ + device_model?: string + /** + * Deprecated. Use User Data email field. + */ + email?: string + /** + * Deprecated. Use Action Source field. + */ + event_conversion_type?: string + /** + * Deprecated. No longer supported. + */ + event_tag?: string + /** + * Deprecated. Use Event Name field. + */ + event_type?: string + /** + * Deprecated. Use User Data idfv field. + */ + idfv?: string + /** + * Deprecated. Use User Data client_ip_address field. + */ + ip_address?: string + /** + * Deprecated. Use products field. + */ + item_category?: string + /** + * Deprecated. Use products field. + */ + item_ids?: string + /** + * Deprecated. No longer supported. + */ + level?: string + /** + * Deprecated. Use User Data madid field. + */ + mobile_ad_id?: string + /** + * Deprecated. Use Custom Data num_items field. + */ + number_items?: string + /** + * Deprecated. Use App Data version field. + */ + os_version?: string + /** + * Deprecated. Use Event Source URL field. + */ + page_url?: string + /** + * Deprecated. Use User Data phone field. + */ + phone_number?: string + /** + * Deprecated. Use Custom Data value field. + */ + price?: number + /** + * Deprecated. Use Custom Data search_string field. + */ + search_string?: string + /** + * Deprecated. Use Custom Data sign_up_method field. + */ + sign_up_method?: string + /** + * Deprecated. Use Event Timestamp field. + */ + timestamp?: string + /** + * Deprecated. Use Custom Data order_id field. + */ + transaction_id?: string + /** + * Deprecated. Use User Data client_user_agent field. + */ + user_agent?: string + /** + * Deprecated. Use User Data sc_cookie1 field. + */ + uuid_c1?: string +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts new file mode 100644 index 00000000000..9a4ffb87f91 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts @@ -0,0 +1,16 @@ +import { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' + +const action: ActionDefinition = { + title: 'Track Purchase', + description: + 'Track purchase/conversion events with enhanced ecommerce data to Snapchat Conversions API. Optimized for Order Completed events with detailed product and transaction information.', + defaultSubscription: 'type = "track" and event = "Order Completed"', + fields: snap_capi_input_fields_v3, + perform +} + +export default action \ No newline at end of file From 21722fd029749767718a53eb9751927f71b28bcd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:46:51 +0000 Subject: [PATCH 3/5] Complete Snapchat Conversions API v3 implementation with batching and tests Co-authored-by: varadarajan-tw <109586712+varadarajan-tw@users.noreply.github.com> --- .../_tests_/syncUserData.test.ts | 49 +++++++++++++ .../_tests_/trackEvent.test.ts | 73 +++++++++++++++++++ .../_tests_/trackPurchase.test.ts | 60 +++++++++++++++ .../reportConversionEvent/index.ts | 5 +- .../reportConversionEvent/snap-capi-v3.ts | 40 +++++++++- .../snap-conversions-api/shared-fields.ts | 5 -- .../syncUserData/index.ts | 14 +++- .../trackAppEvent/index.ts | 13 +++- .../snap-conversions-api/trackEvent/index.ts | 13 +++- .../trackPurchase/index.ts | 14 +++- 10 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/_tests_/syncUserData.test.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackPurchase.test.ts delete mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/syncUserData.test.ts b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/syncUserData.test.ts new file mode 100644 index 00000000000..c172dafb921 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/syncUserData.test.ts @@ -0,0 +1,49 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Definition from '../index' + +const testDestination = createTestIntegration(Definition) + +const testIdentifyEvent = createTestEvent({ + timestamp: '2022-05-12T15:21:15.449Z', + messageId: 'test-message-identify', + type: 'identify', + userId: 'user123', + traits: { + email: 'test@example.com', + phone: '+15551234567', + firstName: 'John', + lastName: 'Doe', + address: { + city: 'San Francisco', + state: 'CA', + postalCode: '94105', + country: 'US' + } + } +}) + +describe('Snap Conversions API - Sync User Data', () => { + it('should sync user data with UPDATE_PROFILE event name', async () => { + nock('https://tr.snapchat.com') + .post('/v3/pixel123/events') + .query({ access_token: 'access123' }) + .reply(200, { status: 'success' }) + + const responses = await testDestination.testAction('syncUserData', { + event: testIdentifyEvent, + settings: { + pixel_id: 'pixel123', + snap_app_id: 'app123' + }, + useDefaultMappings: true, + auth: { + accessToken: 'access123', + refreshToken: 'refresh123' + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + }) +}) \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts new file mode 100644 index 00000000000..1818dd8ff4e --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts @@ -0,0 +1,73 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Definition from '../index' + +const testDestination = createTestIntegration(Definition) + +const testEvent = createTestEvent({ + timestamp: '2022-05-12T15:21:15.449Z', + messageId: 'test-message-trackEvent', + event: 'Product Viewed', + type: 'track', + properties: { + email: 'test@example.com', + phone: '+15551234567', + product_id: 'test_product_123', + value: 25.99, + currency: 'USD' + }, + context: { + ip: '127.0.0.1', + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } +}) + +describe('Snap Conversions API - Track Event', () => { + it('should track standard events with default mappings', async () => { + nock('https://tr.snapchat.com') + .post('/v3/pixel123/events') + .query({ access_token: 'access123' }) + .reply(200, { status: 'success' }) + + const responses = await testDestination.testAction('trackEvent', { + event: testEvent, + settings: { + pixel_id: 'pixel123', + snap_app_id: 'app123' + }, + useDefaultMappings: true, + auth: { + accessToken: 'access123', + refreshToken: 'refresh123' + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + }) + + it('should batch multiple events', async () => { + const batchEvents = [testEvent, { ...testEvent, messageId: 'test-message-2' }] + + nock('https://tr.snapchat.com') + .post('/v3/pixel123/events') + .query({ access_token: 'access123' }) + .reply(200, { status: 'success' }) + + const responses = await testDestination.testBatchAction('trackEvent', { + events: batchEvents, + settings: { + pixel_id: 'pixel123', + snap_app_id: 'app123' + }, + useDefaultMappings: true, + auth: { + accessToken: 'access123', + refreshToken: 'refresh123' + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + }) +}) \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackPurchase.test.ts b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackPurchase.test.ts new file mode 100644 index 00000000000..44e45824de0 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackPurchase.test.ts @@ -0,0 +1,60 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Definition from '../index' + +const testDestination = createTestIntegration(Definition) + +const testPurchaseEvent = createTestEvent({ + timestamp: '2022-05-12T15:21:15.449Z', + messageId: 'test-message-purchase', + event: 'Order Completed', + type: 'track', + properties: { + email: 'test@example.com', + phone: '+15551234567', + order_id: 'order_123', + revenue: 99.99, + currency: 'USD', + products: [ + { + product_id: 'product_1', + category: 'Electronics', + brand: 'TestBrand', + price: 49.99, + quantity: 1 + }, + { + product_id: 'product_2', + category: 'Electronics', + brand: 'TestBrand', + price: 50.00, + quantity: 1 + } + ] + } +}) + +describe('Snap Conversions API - Track Purchase', () => { + it('should track purchase events with PURCHASE event name', async () => { + nock('https://tr.snapchat.com') + .post('/v3/pixel123/events') + .query({ access_token: 'access123' }) + .reply(200, { status: 'success' }) + + const responses = await testDestination.testAction('trackPurchase', { + event: testPurchaseEvent, + settings: { + pixel_id: 'pixel123', + snap_app_id: 'app123' + }, + useDefaultMappings: true, + auth: { + accessToken: 'access123', + refreshToken: 'refresh123' + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + }) +}) \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts index 78d54f8d358..5e80ab00f7f 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts @@ -3,7 +3,7 @@ import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_deprecated from './snap-capi-input-fields-deprecated' import snap_capi_input_fields_v3 from './snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform } from './snap-capi-v3' +import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from './snap-capi-v3' const action: ActionDefinition = { title: 'Report Conversion Event', @@ -15,7 +15,8 @@ const action: ActionDefinition = { ...snap_capi_input_fields_v3, ...snap_capi_input_fields_deprecated }, - perform + perform, + performBatch } export default action diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts index 50bc73cd0ba..ce491af8fd5 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts @@ -1,4 +1,4 @@ -import { ExecuteInput, ModifiedResponse, RequestClient } from '@segment/actions-core' +import { ExecuteInput, ModifiedResponse, RequestClient, IntegrationError } from '@segment/actions-core' import { Payload } from './generated-types' import { Settings } from '../generated-types' import { @@ -751,3 +751,41 @@ export const performSnapCAPIv3 = async ( } }) } + +export const performSnapCAPIv3Batch = async ( + request: RequestClient, + data: ExecuteInput +): Promise> => { + const { payload, settings } = data + + if (!Array.isArray(payload) || payload.length === 0) { + throw new IntegrationError('Batch payload must be a non-empty array', 'Invalid Input', 400) + } + + // Snapchat supports up to 2,000 events per batch + if (payload.length > 2000) { + throw new IntegrationError('Batch size cannot exceed 2,000 events', 'Invalid Input', 400) + } + + const payloadDataArray = payload.map((event) => buildPayloadData(event, settings)) + + // Validate all events and settings + payloadDataArray.forEach((payloadData) => { + validatePayload(payloadData) + validateSettingsConfig(settings, payloadData.action_source) + }) + + const authToken = emptyStringToUndefined(data.auth?.accessToken) + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined(authToken, 'Missing valid auth token') + + // Use the action_source from the first event to determine the URL + // All events in a batch should have the same action_source + const url = buildRequestURL(settings, payloadDataArray[0].action_source, authToken) + + return request(url, { + method: 'post', + json: { + data: payloadDataArray + } + }) +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts b/packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts deleted file mode 100644 index db017feef39..00000000000 --- a/packages/destination-actions/src/destinations/snap-conversions-api/shared-fields.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Re-export the v3 fields from the existing reportConversionEvent action -import snap_capi_input_fields_v3 from './reportConversionEvent/snap-capi-input-fields-v3' - -export { snap_capi_input_fields_v3 } -export type { Payload } from './reportConversionEvent/generated-types' \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts index 623ae3f21e5..0660cf44e46 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition } from '@segment/actions-core' +import { ActionDefinition, defaultValues } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Sync User Data', @@ -10,7 +10,15 @@ const action: ActionDefinition = { 'Synchronize user data without tracking events to Snapchat Conversions API. Used for updating user profiles and enhancing audience data for better ad targeting.', defaultSubscription: 'type = "identify"', fields: snap_capi_input_fields_v3, - perform + perform, + performBatch, + default: { + event_name: { '@literal': 'UPDATE_PROFILE' }, + action_source: { '@literal': 'website' }, + event_time: { '@path': '$.timestamp' }, + event_id: { '@path': '$.messageId' }, + ...defaultValues(snap_capi_input_fields_v3) + } } export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts index 161e5fd248a..d23a018670e 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition } from '@segment/actions-core' +import { ActionDefinition, defaultValues } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Track App Event', @@ -10,7 +10,14 @@ const action: ActionDefinition = { 'Track app-specific events to Snapchat Conversions API. Designed for mobile app conversion tracking with enhanced app data and device information.', defaultSubscription: 'type = "track" and context.device.type != null', fields: snap_capi_input_fields_v3, - perform + perform, + performBatch, + default: { + action_source: { '@literal': 'app' }, + event_time: { '@path': '$.timestamp' }, + event_id: { '@path': '$.messageId' }, + ...defaultValues(snap_capi_input_fields_v3) + } } export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts index 71ba69e16ad..e9a6532bd31 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition } from '@segment/actions-core' +import { ActionDefinition, defaultValues } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Track Event', @@ -10,7 +10,14 @@ const action: ActionDefinition = { 'Track standard conversion events to Snapchat Conversions API. Automatically maps Segment track events to appropriate Snapchat event types for conversion tracking and optimization.', defaultSubscription: 'type = "track"', fields: snap_capi_input_fields_v3, - perform + perform, + performBatch, + default: { + action_source: { '@literal': 'website' }, + event_time: { '@path': '$.timestamp' }, + event_id: { '@path': '$.messageId' }, + ...defaultValues(snap_capi_input_fields_v3) + } } export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts index 9a4ffb87f91..f8665608e14 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition } from '@segment/actions-core' +import { ActionDefinition, defaultValues } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Track Purchase', @@ -10,7 +10,15 @@ const action: ActionDefinition = { 'Track purchase/conversion events with enhanced ecommerce data to Snapchat Conversions API. Optimized for Order Completed events with detailed product and transaction information.', defaultSubscription: 'type = "track" and event = "Order Completed"', fields: snap_capi_input_fields_v3, - perform + perform, + performBatch, + default: { + event_name: { '@literal': 'PURCHASE' }, + action_source: { '@literal': 'website' }, + event_time: { '@path': '$.timestamp' }, + event_id: { '@path': '$.messageId' }, + ...defaultValues(snap_capi_input_fields_v3) + } } export default action \ No newline at end of file From 8cf80a3f505051e8e1c64471c68e2d400e410d6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:49:01 +0000 Subject: [PATCH 4/5] Add intelligent event name mapping and enhanced trackEvent functionality Co-authored-by: varadarajan-tw <109586712+varadarajan-tw@users.noreply.github.com> --- .../_tests_/eventMapping.test.ts | 46 +++++++++++++++++++ .../_tests_/trackEvent.test.ts | 28 +++++++++++ .../snap-conversions-api/eventMapping.ts | 31 +++++++++++++ .../snap-conversions-api/trackEvent/index.ts | 2 +- .../trackEvent/perform.ts | 37 +++++++++++++++ 5 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/_tests_/eventMapping.test.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/eventMapping.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/perform.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/eventMapping.test.ts b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/eventMapping.test.ts new file mode 100644 index 00000000000..c091102eedc --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/eventMapping.test.ts @@ -0,0 +1,46 @@ +import { mapEventName, EVENT_NAME_MAPPING } from '../eventMapping' + +describe('Event Name Mapping', () => { + it('should map known Segment events to Snapchat event names', () => { + expect(mapEventName('Product Added')).toBe('ADD_CART') + expect(mapEventName('Product Viewed')).toBe('VIEW_CONTENT') + expect(mapEventName('Order Completed')).toBe('PURCHASE') + expect(mapEventName('Checkout Started')).toBe('START_CHECKOUT') + expect(mapEventName('Signed Up')).toBe('SIGN_UP') + expect(mapEventName('Login')).toBe('LOGIN') + expect(mapEventName('Signed In')).toBe('LOGIN') + }) + + it('should return original event name for unknown events', () => { + expect(mapEventName('Custom Event')).toBe('Custom Event') + expect(mapEventName('CUSTOM_EVENT_1')).toBe('CUSTOM_EVENT_1') + }) + + it('should handle undefined event names', () => { + expect(mapEventName(undefined)).toBeUndefined() + }) + + it('should include all required mappings from specification', () => { + // Verify all mappings from the issue specification are included + const requiredMappings = { + 'Product Added': 'ADD_CART', + 'Product Added to Wishlist': 'ADD_TO_WISHLIST', + 'Checkout Started': 'START_CHECKOUT', + 'Order Completed': 'PURCHASE', + 'Product Viewed': 'VIEW_CONTENT', + 'Products Searched': 'SEARCH', + 'Signed Up': 'SIGN_UP', + 'Login': 'LOGIN', + 'Page Viewed': 'PAGE_VIEW', + 'Application Installed': 'APP_INSTALL', + 'Application Opened': 'APP_OPEN', + 'Product List Viewed': 'LIST_VIEW', + 'Shared': 'SHARE', + 'Payment Info Entered': 'ADD_BILLING' + } + + Object.entries(requiredMappings).forEach(([segmentEvent, snapchatEvent]) => { + expect(EVENT_NAME_MAPPING[segmentEvent]).toBe(snapchatEvent) + }) + }) +}) \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts index 1818dd8ff4e..b0e87faac6e 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts @@ -46,6 +46,34 @@ describe('Snap Conversions API - Track Event', () => { expect(responses[0].status).toBe(200) }) + it('should automatically map segment event names to snapchat event names', async () => { + const productViewedEvent = createTestEvent({ + ...testEvent, + event: 'Product Viewed' + }) + + nock('https://tr.snapchat.com') + .post('/v3/pixel123/events') + .query({ access_token: 'access123' }) + .reply(200, { status: 'success' }) + + const responses = await testDestination.testAction('trackEvent', { + event: productViewedEvent, + settings: { + pixel_id: 'pixel123', + snap_app_id: 'app123' + }, + useDefaultMappings: true, + auth: { + accessToken: 'access123', + refreshToken: 'refresh123' + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(200) + }) + it('should batch multiple events', async () => { const batchEvents = [testEvent, { ...testEvent, messageId: 'test-message-2' }] diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/eventMapping.ts b/packages/destination-actions/src/destinations/snap-conversions-api/eventMapping.ts new file mode 100644 index 00000000000..0c154301e2d --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/eventMapping.ts @@ -0,0 +1,31 @@ +// Event name mapping from Segment events to Snapchat event names +export const EVENT_NAME_MAPPING: Record = { + 'Product Added': 'ADD_CART', + 'Product Added to Wishlist': 'ADD_TO_WISHLIST', + 'Checkout Started': 'START_CHECKOUT', + 'Order Completed': 'PURCHASE', + 'Product Viewed': 'VIEW_CONTENT', + 'Products Searched': 'SEARCH', + 'Signed Up': 'SIGN_UP', + 'Login': 'LOGIN', + 'Signed In': 'LOGIN', + 'Page Viewed': 'PAGE_VIEW', + 'Application Installed': 'APP_INSTALL', + 'Application Opened': 'APP_OPEN', + 'Product List Viewed': 'LIST_VIEW', + 'Shared': 'SHARE', + 'Product Shared': 'SHARE', + 'Payment Info Entered': 'ADD_BILLING' +} + +/** + * Maps a Segment event name to the corresponding Snapchat event name + * Returns the original event name if no mapping is found + */ +export function mapEventName(segmentEventName: string | undefined): string | undefined { + if (!segmentEventName) { + return undefined + } + + return EVENT_NAME_MAPPING[segmentEventName] || segmentEventName +} \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts index e9a6532bd31..3fd6ce00ce0 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts @@ -2,7 +2,7 @@ import { ActionDefinition, defaultValues } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' +import { performTrackEvent as perform, performTrackEventBatch as performBatch } from './perform' const action: ActionDefinition = { title: 'Track Event', diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/perform.ts b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/perform.ts new file mode 100644 index 00000000000..885a1236c44 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/perform.ts @@ -0,0 +1,37 @@ +import { ExecuteInput, ModifiedResponse, RequestClient, IntegrationError } from '@segment/actions-core' +import { Payload } from './generated-types' +import { Settings } from '../generated-types' +import { performSnapCAPIv3, performSnapCAPIv3Batch } from '../reportConversionEvent/snap-capi-v3' +import { mapEventName } from '../eventMapping' + +export const performTrackEvent = async ( + request: RequestClient, + data: ExecuteInput +): Promise> => { + // Map the event name if it's a standard Segment event + const mappedPayload = { + ...data.payload, + event_name: data.payload.event_name || mapEventName(data.event?.event) + } + + return performSnapCAPIv3(request, { + ...data, + payload: mappedPayload + }) +} + +export const performTrackEventBatch = async ( + request: RequestClient, + data: ExecuteInput +): Promise> => { + // Map event names for all events in the batch + const mappedPayloads = data.payload.map((payload, index) => ({ + ...payload, + event_name: payload.event_name || mapEventName(data.events?.[index]?.event) + })) + + return performSnapCAPIv3Batch(request, { + ...data, + payload: mappedPayloads + }) +} \ No newline at end of file From 818255e649034f196c278ef89019e91032b4a2e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:17:36 +0000 Subject: [PATCH 5/5] Create new snap-conversions-v3 destination as requested in feedback Co-authored-by: varadarajan-tw <109586712+varadarajan-tw@users.noreply.github.com> --- .../src/destinations/index.ts | 1 + .../snap-conversions-api/index.ts | 60 +- .../reportConversionEvent/index.ts | 5 +- .../reportConversionEvent/snap-capi-v3.ts | 40 +- .../_tests_/eventMapping.test.ts | 0 .../snap-conversions-v3/_tests_/index.test.ts | 841 ++++++++++++++++++ .../_tests_/syncUserData.test.ts | 0 .../_tests_/trackEvent.test.ts | 28 - .../_tests_/trackPurchase.test.ts | 0 .../eventMapping.ts | 0 .../snap-conversions-v3/generated-types.ts | 16 + .../destinations/snap-conversions-v3/index.ts | 278 ++++++ .../reportConversionEvent}/generated-types.ts | 0 .../reportConversionEvent/index.ts | 21 + .../snap-capi-input-fields-deprecated.ts | 203 +++++ .../snap-capi-input-fields-v3.ts | 682 ++++++++++++++ .../reportConversionEvent/snap-capi-v3.ts | 791 ++++++++++++++++ .../snap-conversions-api-request.ts | 0 .../reportConversionEvent/utils.ts | 80 ++ .../snap-conversions-v3/shared-fields.ts | 5 + .../syncUserData}/generated-types.ts | 0 .../syncUserData/index.ts | 14 +- .../trackAppEvent}/generated-types.ts | 0 .../trackAppEvent/index.ts | 13 +- .../trackEvent}/generated-types.ts | 0 .../trackEvent/index.ts | 13 +- .../trackEvent/perform.ts | 0 .../trackPurchase/generated-types.ts | 438 +++++++++ .../trackPurchase/index.ts | 14 +- 29 files changed, 3379 insertions(+), 164 deletions(-) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/_tests_/eventMapping.test.ts (100%) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/index.test.ts rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/_tests_/syncUserData.test.ts (100%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/_tests_/trackEvent.test.ts (72%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/_tests_/trackPurchase.test.ts (100%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/eventMapping.ts (100%) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/index.ts rename packages/destination-actions/src/destinations/{snap-conversions-api/syncUserData => snap-conversions-v3/reportConversionEvent}/generated-types.ts (100%) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/index.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-deprecated.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-v3.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-v3.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-conversions-api-request.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/utils.ts create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/shared-fields.ts rename packages/destination-actions/src/destinations/{snap-conversions-api/trackAppEvent => snap-conversions-v3/syncUserData}/generated-types.ts (100%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/syncUserData/index.ts (54%) rename packages/destination-actions/src/destinations/{snap-conversions-api/trackEvent => snap-conversions-v3/trackAppEvent}/generated-types.ts (100%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/trackAppEvent/index.ts (58%) rename packages/destination-actions/src/destinations/{snap-conversions-api/trackPurchase => snap-conversions-v3/trackEvent}/generated-types.ts (100%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/trackEvent/index.ts (59%) rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/trackEvent/perform.ts (100%) create mode 100644 packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/generated-types.ts rename packages/destination-actions/src/destinations/{snap-conversions-api => snap-conversions-v3}/trackPurchase/index.ts (56%) diff --git a/packages/destination-actions/src/destinations/index.ts b/packages/destination-actions/src/destinations/index.ts index 6c3273805bd..34ea3ba6356 100644 --- a/packages/destination-actions/src/destinations/index.ts +++ b/packages/destination-actions/src/destinations/index.ts @@ -60,6 +60,7 @@ register('61957755c4d820be968457de', './salesforce') register('62e30bad99f1bfb98ee8ce08', './salesforce-marketing-cloud') register('5f7dd8e302173ff732db5cc4', './slack') register('6261a8b6cb4caa70e19116e8', './snap-conversions-api') +register('000000000000000000000000', './snap-conversions-v3') register('6234b137d3b6404a64f2a0f0', './talon-one') register('615cae349d109d6b7496a131', './tiktok-conversions') register('63d2e550fb90f1632ed8820a', './tiktok-audiences') diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/index.ts index 574768b7e6d..f5376d842b3 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/index.ts @@ -2,10 +2,6 @@ import { defaultValues, DestinationDefinition } from '@segment/actions-core' import type { Settings } from './generated-types' import reportConversionEvent from './reportConversionEvent' -import trackEvent from './trackEvent' -import trackAppEvent from './trackAppEvent' -import trackPurchase from './trackPurchase' -import syncUserData from './syncUserData' const DEFAULT_VALS = { ...defaultValues(reportConversionEvent.fields) @@ -18,6 +14,13 @@ interface RefreshTokenResponse { } const presets: DestinationDefinition['presets'] = [ + { + name: 'Snap Browser Plugin', + subscribe: 'type = "track" or type = "identify" or type = "group" or type = "page" or type = "alias"', + partnerAction: 'snapPlugin', + mapping: {}, + type: 'automatic' + }, { name: 'Add Billing', subscribe: 'event = "Payment Info Entered"', @@ -171,49 +174,6 @@ const presets: DestinationDefinition['presets'] = [ ...DEFAULT_VALS }, type: 'automatic' - }, - // New presets for specific actions - { - name: 'Track Standard Events', - subscribe: 'type = "track"', - partnerAction: 'trackEvent', - mapping: { - action_source: 'website', - ...DEFAULT_VALS - }, - type: 'automatic' - }, - { - name: 'Track App Events', - subscribe: 'type = "track" and context.device.type != null', - partnerAction: 'trackAppEvent', - mapping: { - action_source: 'app', - ...DEFAULT_VALS - }, - type: 'automatic' - }, - { - name: 'Track Purchase Events', - subscribe: 'type = "track" and event = "Order Completed"', - partnerAction: 'trackPurchase', - mapping: { - event_name: 'PURCHASE', - action_source: 'website', - ...DEFAULT_VALS - }, - type: 'automatic' - }, - { - name: 'Sync User Data', - subscribe: 'type = "identify"', - partnerAction: 'syncUserData', - mapping: { - event_name: 'UPDATE_PROFILE', - action_source: 'website', - ...DEFAULT_VALS - }, - type: 'automatic' } ] @@ -267,11 +227,7 @@ const destination: DestinationDefinition = { }, presets, actions: { - reportConversionEvent, - trackEvent, - trackAppEvent, - trackPurchase, - syncUserData + reportConversionEvent } } diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts index 5e80ab00f7f..78d54f8d358 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/index.ts @@ -3,7 +3,7 @@ import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_deprecated from './snap-capi-input-fields-deprecated' import snap_capi_input_fields_v3 from './snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from './snap-capi-v3' +import { performSnapCAPIv3 as perform } from './snap-capi-v3' const action: ActionDefinition = { title: 'Report Conversion Event', @@ -15,8 +15,7 @@ const action: ActionDefinition = { ...snap_capi_input_fields_v3, ...snap_capi_input_fields_deprecated }, - perform, - performBatch + perform } export default action diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts index ce491af8fd5..50bc73cd0ba 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-api/reportConversionEvent/snap-capi-v3.ts @@ -1,4 +1,4 @@ -import { ExecuteInput, ModifiedResponse, RequestClient, IntegrationError } from '@segment/actions-core' +import { ExecuteInput, ModifiedResponse, RequestClient } from '@segment/actions-core' import { Payload } from './generated-types' import { Settings } from '../generated-types' import { @@ -751,41 +751,3 @@ export const performSnapCAPIv3 = async ( } }) } - -export const performSnapCAPIv3Batch = async ( - request: RequestClient, - data: ExecuteInput -): Promise> => { - const { payload, settings } = data - - if (!Array.isArray(payload) || payload.length === 0) { - throw new IntegrationError('Batch payload must be a non-empty array', 'Invalid Input', 400) - } - - // Snapchat supports up to 2,000 events per batch - if (payload.length > 2000) { - throw new IntegrationError('Batch size cannot exceed 2,000 events', 'Invalid Input', 400) - } - - const payloadDataArray = payload.map((event) => buildPayloadData(event, settings)) - - // Validate all events and settings - payloadDataArray.forEach((payloadData) => { - validatePayload(payloadData) - validateSettingsConfig(settings, payloadData.action_source) - }) - - const authToken = emptyStringToUndefined(data.auth?.accessToken) - raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined(authToken, 'Missing valid auth token') - - // Use the action_source from the first event to determine the URL - // All events in a batch should have the same action_source - const url = buildRequestURL(settings, payloadDataArray[0].action_source, authToken) - - return request(url, { - method: 'post', - json: { - data: payloadDataArray - } - }) -} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/eventMapping.test.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/eventMapping.test.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/_tests_/eventMapping.test.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/eventMapping.test.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/index.test.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/index.test.ts new file mode 100644 index 00000000000..fd49b81b6d5 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/index.test.ts @@ -0,0 +1,841 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Definition from '../index' +import { smartHash } from '../reportConversionEvent/utils' + +const testDestination = createTestIntegration(Definition) + +const testEvent = createTestEvent({ + timestamp: '2022-05-12T15:21:15.449Z', + messageId: 'test-message-rv4t40s898k', + event: 'PURCHASE', + type: 'track', + properties: { + email: ' Test123@gmail.com ', + phone: '+44 844 412 4653', + event_tag: 'back-to-school', + number_items: 10, + revenue: '15', + currency: 'USD', + level: 3 + }, + integrations: { + ['Snap Conversions Api']: { + click_id: 'cliasdfck_id', + uuid_c1: 'sadfjklsdf;lksjd' + } as any + } +}) + +type InputData = T extends ( + arg1: any, + arg2: infer U, + ...args: any[] +) => any + ? U + : never + +const reportConversionEvent = async (inputData: InputData): Promise<{ url: string; data: any }> => { + const event = createTestEvent(testEvent) + const accessToken = 'access123' + const refreshToken = 'refresh123' + + const responses = await testDestination.testAction('reportConversionEvent', { + event, + settings: { + snap_app_id: 'test123', + pixel_id: 'pixel123', + app_id: '123' + }, + useDefaultMappings: true, + auth: { + accessToken, + refreshToken + }, + ...inputData + }) + + return { url: responses[0].url, data: JSON.parse(responses[0].options.body as string).data[0] } +} + +beforeEach(() => { + nock.cleanAll() // Clear all Nock interceptors and filters + nock(/.*/).post(/.*/).reply(200) +}) + +describe('Snap Conversions API ', () => { + describe('ReportConversionEvent', () => { + it('should use products array over number_items, product_id and category fields', async () => { + const { data } = await reportConversionEvent({ + event: { + ...testEvent, + properties: { + email: 'test123@gmail.com', + phone: '+44 844 412 4653', + event_tag: 'back-to-school', + quantity: 10, + revenue: '15', + currency: 'USD', + level: 3, + products: [ + { product_id: '123', category: 'games', brand: 'Hasbro' }, + { product_id: '456', category: 'games', brand: 'Mattel' } + ] + }, + context: {} + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + const { integration, event_name, event_time, user_data, custom_data, action_source, app_data } = data + const { em, ph } = user_data + const { brands, content_category, content_ids, currency, num_items, value } = custom_data + + expect(integration).toBe('segment') + expect(event_name).toBe('PURCHASE') + expect(event_time).toBe(1652368875449) + + expect(em[0]).toBe('cc779c04191c2e736d89e45c11339c8382832bcaf70383f7df94e3d08ba7a6d9') + expect(ph[0]).toBe('dc008fda46e2e64002cf2f82a4906236282d431c4f75e5b60bfe79fc48546383') + expect(currency).toBe('USD') + expect(value).toBe(15) + expect(action_source).toBe('website') + // app_data is only defined when action_source is app + expect(app_data).toBeUndefined() + + expect(brands).toEqual(['Hasbro', 'Mattel']) + expect(content_category).toEqual(['games', 'games']) + expect(content_ids).toEqual(['123', '456']) + expect(num_items).toBe(10) + }) + + it('should handle a basic event', async () => { + const { data } = await reportConversionEvent({ + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + const { integration, event_name, event_source_url, event_time, user_data, custom_data, action_source, app_data } = + data + const { client_ip_address, client_user_agent, em, ph } = user_data + const { currency, value } = custom_data + + expect(integration).toBe('segment') + expect(event_name).toBe('PURCHASE') + expect(event_source_url).toBe('https://segment.com/academy/') + expect(event_time).toBe(1652368875449) + expect(client_ip_address).toBe('8.8.8.8') + expect(client_user_agent).toBe( + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + ) + expect(em[0]).toBe('cc779c04191c2e736d89e45c11339c8382832bcaf70383f7df94e3d08ba7a6d9') + expect(ph[0]).toBe('dc008fda46e2e64002cf2f82a4906236282d431c4f75e5b60bfe79fc48546383') + expect(currency).toBe('USD') + expect(value).toBe(15) + expect(action_source).toBe('website') + // app_data is only defined when action_source is app + expect(app_data).toBeUndefined() + }) + + it('should fail web event without pixel_id', async () => { + await expect( + reportConversionEvent({ + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + }, + settings: { + snap_app_id: 'test123' + } + }) + ).rejects.toThrowError('If event conversion type is "WEB" then Pixel ID must be defined') + }) + + it('should fail app event without snap_app_id', async () => { + await expect( + reportConversionEvent({ + settings: {}, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'MOBILE_APP' + } + }) + ).rejects.toThrowError('If event conversion type is "MOBILE_APP" then Snap App ID must be defined') + }) + + it('should handle an offline event conversion type', async () => { + const { data } = await reportConversionEvent({ + mapping: { + event_type: 'SAVE', + event_conversion_type: 'OFFLINE' + } + }) + + const { integration, event_name, event_source_url, event_time, user_data, custom_data, action_source, app_data } = + data + const { client_ip_address, client_user_agent, em, ph } = user_data + const { currency, value } = custom_data + + expect(integration).toBe('segment') + expect(event_name).toBe('SAVE') + expect(event_source_url).toBe('https://segment.com/academy/') + expect(event_time).toBe(1652368875449) + expect(client_ip_address).toBe('8.8.8.8') + expect(client_user_agent).toBe( + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + ) + expect(em[0]).toBe('cc779c04191c2e736d89e45c11339c8382832bcaf70383f7df94e3d08ba7a6d9') + expect(ph[0]).toBe('dc008fda46e2e64002cf2f82a4906236282d431c4f75e5b60bfe79fc48546383') + expect(currency).toBe('USD') + expect(value).toBe(15) + expect(action_source).toBe('OFFLINE') + + // App data is only defined for app events + expect(app_data).toBeUndefined() + }) + + it('should handle a mobile app event conversion type', async () => { + const { data } = await reportConversionEvent({ + mapping: { + device_model: 'iPhone12,1', + os_version: '17.2', + event_type: 'SAVE', + event_conversion_type: 'MOBILE_APP' + } + }) + + const { integration, event_name, event_source_url, event_time, user_data, custom_data, action_source, app_data } = + data + const { client_ip_address, client_user_agent, em, ph } = user_data + const { currency, value } = custom_data + const { extinfo, advertiser_tracking_enabled } = app_data + + expect(integration).toBe('segment') + expect(event_name).toBe('SAVE') + expect(event_source_url).toBe('https://segment.com/academy/') + expect(event_time).toBe(1652368875449) + expect(client_ip_address).toBe('8.8.8.8') + expect(client_user_agent).toBe( + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + ) + expect(em[0]).toBe('cc779c04191c2e736d89e45c11339c8382832bcaf70383f7df94e3d08ba7a6d9') + expect(ph[0]).toBe('dc008fda46e2e64002cf2f82a4906236282d431c4f75e5b60bfe79fc48546383') + expect(currency).toBe('USD') + expect(value).toBe(15) + expect(action_source).toBe('app') + expect(extinfo).toEqual([ + 'i2', + '', + '', + '', + '17.2', + 'iPhone12,1', + 'en-US', + '', + '', + '', + '', + '', + '', + '', + '', + 'Europe/Amsterdam' + ]) + expect(advertiser_tracking_enabled).toBe(0) + }) + + it('should fail invalid currency', async () => { + await expect( + reportConversionEvent({ + event: { + ...testEvent, + properties: { + currency: 'Galleon' + } + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + ).rejects.toThrowError('GALLEON is not a valid currency code.') + }) + + it('should fail missing event conversion type', async () => { + await expect( + reportConversionEvent({ + mapping: { + event_type: 'PURCHASE' + } + }) + ).rejects.toThrowError("The root value is missing the required field 'action_source'.") + }) + + it('should handle a custom event', async () => { + const { data } = await reportConversionEvent({ + event: { + ...testEvent, + event: 'CUSTOM_EVENT_5' + }, + mapping: { + event_type: { '@path': '$.event' }, + event_conversion_type: 'MOBILE_APP' + } + }) + + const { integration, event_name, event_source_url, event_time, user_data, custom_data, action_source, app_data } = + data + const { client_ip_address, client_user_agent, em, ph } = user_data + const { currency, value } = custom_data + const { app_id, advertiser_tracking_enabled } = app_data + + expect(integration).toBe('segment') + expect(event_name).toBe('CUSTOM_EVENT_5') + expect(event_source_url).toBe('https://segment.com/academy/') + expect(event_time).toBe(1652368875449) + expect(client_ip_address).toBe('8.8.8.8') + expect(client_user_agent).toBe( + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + ) + expect(em[0]).toBe('cc779c04191c2e736d89e45c11339c8382832bcaf70383f7df94e3d08ba7a6d9') + expect(ph[0]).toBe('dc008fda46e2e64002cf2f82a4906236282d431c4f75e5b60bfe79fc48546383') + expect(currency).toBe('USD') + expect(value).toBe(15) + expect(action_source).toBe('app') + expect(app_id).toBe('123') + expect(advertiser_tracking_enabled).toBe(0) + }) + + it('should fail event missing all Snap identifiers', async () => { + await expect( + reportConversionEvent({ + event: { + ...testEvent, + properties: {}, + context: {} + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + ).rejects.toThrowError( + 'Payload must contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields' + ) + }) + + it('should handle event with email as only Snap identifier', async () => { + const { data } = await reportConversionEvent({ + event: { + ...testEvent, + properties: { + email: 'test123@gmail.com' + }, + context: {} + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + const { integration, event_name, event_time, user_data, action_source } = data + const { em, ph } = user_data + + expect(integration).toBe('segment') + expect(event_name).toBe('PURCHASE') + expect(event_time).toBe(1652368875449) + expect(em[0]).toBe('cc779c04191c2e736d89e45c11339c8382832bcaf70383f7df94e3d08ba7a6d9') + expect(ph).toBeUndefined() + expect(action_source).toBe('website') + }) + + it('should handle event with phone as only Snap identifier', async () => { + const { data } = await reportConversionEvent({ + event: { + ...testEvent, + properties: { + phone: '+44 844 412 4653' + }, + context: {} + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + const { integration, event_name, event_time, user_data, action_source } = data + const { ph } = user_data + + expect(integration).toBe('segment') + expect(event_name).toBe('PURCHASE') + expect(event_time).toBe(1652368875449) + expect(ph[0]).toBe('dc008fda46e2e64002cf2f82a4906236282d431c4f75e5b60bfe79fc48546383') + expect(action_source).toBe('website') + }) + + it('should handle event with advertising_id as only Snap identifier', async () => { + const advertisingId = '87a7def4-b6e9-4bf7-91b6-66372842007a' + + const { data } = await reportConversionEvent({ + event: { + ...testEvent, + properties: {}, + context: { + device: { + advertisingId + } + } + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + const { integration, event_name, event_time, user_data, action_source } = data + const { madid } = user_data + + expect(integration).toBe('segment') + expect(event_name).toBe('PURCHASE') + expect(event_time).toBe(1652368875449) + expect(madid).toBe(advertisingId) + expect(action_source).toBe('website') + }) + + it('should handle event with ip and user_agent as only Snap identifiers', async () => { + const { data } = await reportConversionEvent({ + event: { + ...testEvent, + properties: {} + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + const { integration, event_name, event_time, user_data, action_source } = data + const { client_ip_address, client_user_agent } = user_data + + expect(integration).toBe('segment') + expect(event_name).toBe('PURCHASE') + expect(event_time).toBe(1652368875449) + expect(client_ip_address).toBe('8.8.8.8') + expect(client_user_agent).toBe( + 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1' + ) + expect(action_source).toBe('website') + }) + + it('should always use the pixel id in settings for web events', async () => { + const { url } = await reportConversionEvent({ + event: { + ...testEvent, + properties: {} + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + expect(url).toBe('https://tr.snapchat.com/v3/pixel123/events?access_token=access123') + }) + + it('should trim a pixel id with leading or trailing whitespace', async () => { + const { url } = await reportConversionEvent({ + event: { + ...testEvent, + properties: {} + }, + settings: { + pixel_id: ' pixel123 ' + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB' + } + }) + + expect(url).toBe('https://tr.snapchat.com/v3/pixel123/events?access_token=access123') + }) + + it('should exclude number_items that is not a valid integer', async () => { + const { url, data } = await reportConversionEvent({ + event: { + ...testEvent, + properties: {} + }, + settings: { + pixel_id: ' pixel123 ' + }, + auth: { + accessToken: ' access123 ', + refreshToken: '' + }, + mapping: { + event_type: 'PURCHASE', + event_conversion_type: 'WEB', + number_items: 'six' + } + }) + + expect(url).toBe('https://tr.snapchat.com/v3/pixel123/events?access_token=access123') + + const { custom_data } = data + + expect(custom_data).toBeUndefined() + }) + + it('supports all the v3 fields', async () => { + const { + data: { + action_source, + app_data, + custom_data, + data_processing_options, + data_processing_options_country, + data_processing_options_state, + event_id, + event_name, + event_source_url, + event_time, + user_data + } + } = await reportConversionEvent({ + event: { + ...testEvent, + properties: { + currency: 'USD', + email: ' Test123@gmail.com ', + phone: '+44 844 412 4653', + birthday: '19970216', + gender: 'Male', + last_name: 'McTest', + first_name: 'Tester', + address: { + city: 'A Fake City', + state: 'Not Even A State', + country: 'Bolivia', + postalCode: '1234' + }, + query: 'test-query', + order_id: 'A1234', + revenue: 1000 + }, + context: { + ...testEvent.context, + app: { + namespace: 'com.segment.app', + version: '1.0.0' + }, + device: { + adTrackingEnabled: true + }, + screen: { + width: '1920', + height: '1080', + density: '2.0' + }, + os: { + version: '19.5' + } + } + }, + mapping: { + event_name: 'PURCHASE', + action_source: 'app', + data_processing_options: true, + data_processing_options_country: 1, + data_processing_options_state: 1000 + } + }) + + expect(action_source).toEqual('app') + expect(event_name).toEqual('PURCHASE') + expect(event_id).toEqual(testEvent.messageId) + expect(event_source_url).toEqual(testEvent.context?.page?.url ?? '') + expect(event_time).toEqual(Date.parse(testEvent.timestamp as string)) + + expect(data_processing_options).toEqual(['LDU']) + expect(data_processing_options_country).toEqual(1) + expect(data_processing_options_state).toEqual(1000) + + expect(app_data.application_tracking_enabled).toEqual(1) + expect(app_data.extinfo).toEqual([ + 'i2', + 'com.segment.app', + '', + '1.0.0', + '19.5', + '', + 'en-US', + '', + '', + '1920', + '1080', + '2.0', + '', + '', + '', + 'Europe/Amsterdam' + ]) + + expect(custom_data.currency).toEqual('USD') + expect(custom_data.order_id).toEqual('A1234') + expect(custom_data.search_string).toEqual('test-query') + expect(custom_data.value).toEqual(1000) + + expect(user_data.external_id[0]).toEqual(smartHash(testEvent.userId ?? '')) + expect(user_data.em[0]).toEqual(smartHash('test123@gmail.com')) + expect(user_data.ph[0]).toEqual(smartHash('448444124653')) + expect(user_data.db).toEqual(smartHash('19970216')) + expect(user_data.fn).toEqual(smartHash('tester')) + expect(user_data.ln).toEqual(smartHash('mctest')) + expect(user_data.ge).toEqual(smartHash('m')) + expect(user_data.ct).toEqual(smartHash('afakecity')) + expect(user_data.st).toEqual(smartHash('notevenastate')) + expect(user_data.country).toEqual(smartHash('bo')) + expect(user_data.zp).toEqual(smartHash('1234')) + expect(user_data.client_ip_address).toBe(testEvent.context?.ip) + expect(user_data.client_user_agent).toBe(testEvent.context?.userAgent) + expect(user_data.idfv).toBe(testEvent.context?.device?.id) + expect(user_data.madid).toBe(testEvent.context?.device?.advertisingId) + expect(user_data.sc_click_id).toEqual((testEvent.integrations?.['Snap Conversions Api'] as any)?.click_id) + expect(user_data.sc_cookie1).toEqual((testEvent.integrations?.['Snap Conversions Api'] as any)?.uuid_c1) + }) + + it('should detect hashed values', async () => { + const { + data: { + action_source, + app_data, + custom_data, + data_processing_options, + data_processing_options_country, + data_processing_options_state, + event_id, + event_name, + event_source_url, + event_time, + user_data + } + } = await reportConversionEvent({ + event: { + ...testEvent, + properties: { + currency: 'USD', + email: '388c735eec8225c4ad7a507944dd0a975296baea383198aa87177f29af2c6f69', + phone: 'd74dcef7e1dba5aed01b62bc932ab1a7848ed9a590ca029aa958a1bf82b6993d', + birthday: '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', + gender: 'eef2eae2699d81c58d176a9a58d4bf183df2acb6844b9eebf1cc60ae460ec50d', + last_name: 'fd53ef835b15485572a6e82cf470dcb41fd218ae5751ab7531c956a2a6bcd3c7', + first_name: '5f39b51ae9a4dacbb8d9538229d726bfb7e1a03633e37d64598c32989a8c1277', + address: { + city: 'dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb', + state: 'dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb', + country: 'dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb', + postalCode: 'dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb' + }, + query: 'test-query', + order_id: 'A1234', + revenue: 1000 + }, + context: { + ...testEvent.context, + app: { + namespace: 'com.segment.app', + version: '1.0.0' + }, + device: { + adTrackingEnabled: true + }, + screen: { + width: '1920', + height: '1080', + density: '2.0' + }, + os: { + version: '19.5' + } + } + }, + mapping: { + event_name: 'PURCHASE', + action_source: 'app', + data_processing_options: true, + data_processing_options_country: 1, + data_processing_options_state: 1000 + } + }) + + expect(action_source).toEqual('app') + expect(event_name).toEqual('PURCHASE') + expect(event_id).toEqual(testEvent.messageId) + expect(event_source_url).toEqual(testEvent.context?.page?.url ?? '') + expect(event_time).toEqual(Date.parse(testEvent.timestamp as string)) + + expect(data_processing_options).toEqual(['LDU']) + expect(data_processing_options_country).toEqual(1) + expect(data_processing_options_state).toEqual(1000) + + expect(app_data.application_tracking_enabled).toEqual(1) + expect(app_data.extinfo).toEqual([ + 'i2', + 'com.segment.app', + '', + '1.0.0', + '19.5', + '', + 'en-US', + '', + '', + '1920', + '1080', + '2.0', + '', + '', + '', + 'Europe/Amsterdam' + ]) + + expect(custom_data.currency).toEqual('USD') + expect(custom_data.order_id).toEqual('A1234') + expect(custom_data.search_string).toEqual('test-query') + expect(custom_data.value).toEqual(1000) + + expect(user_data.external_id[0]).toEqual(smartHash(testEvent.userId ?? '')) + expect(user_data.em[0]).toEqual('388c735eec8225c4ad7a507944dd0a975296baea383198aa87177f29af2c6f69') + expect(user_data.ph[0]).toEqual('d74dcef7e1dba5aed01b62bc932ab1a7848ed9a590ca029aa958a1bf82b6993d') + expect(user_data.db).toEqual('6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b') + expect(user_data.fn).toEqual('5f39b51ae9a4dacbb8d9538229d726bfb7e1a03633e37d64598c32989a8c1277') + expect(user_data.ln).toEqual('fd53ef835b15485572a6e82cf470dcb41fd218ae5751ab7531c956a2a6bcd3c7') + expect(user_data.ge).toEqual('eef2eae2699d81c58d176a9a58d4bf183df2acb6844b9eebf1cc60ae460ec50d') + expect(user_data.ct).toEqual('dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb') + expect(user_data.st).toEqual('dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb') + expect(user_data.country).toEqual('dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb') + expect(user_data.zp).toEqual('dc968997b2904bcdb46f90378701f86fb09b17274579e9d75601cdaaecd0a5fb') + expect(user_data.client_ip_address).toBe(testEvent.context?.ip) + expect(user_data.client_user_agent).toBe(testEvent.context?.userAgent) + expect(user_data.idfv).toBe(testEvent.context?.device?.id) + expect(user_data.madid).toBe(testEvent.context?.device?.advertisingId) + expect(user_data.sc_click_id).toEqual((testEvent.integrations?.['Snap Conversions Api'] as any)?.click_id) + expect(user_data.sc_cookie1).toEqual((testEvent.integrations?.['Snap Conversions Api'] as any)?.uuid_c1) + }) + + it('should not add empty values in final payload', async () => { + const { + data: { + action_source, + app_data, + custom_data, + data_processing_options, + data_processing_options_country, + data_processing_options_state, + event_id, + event_name, + event_source_url, + event_time, + user_data + } + } = await reportConversionEvent({ + event: { + ...testEvent, + properties: { + currency: 'USD', + email: '388c735eec8225c4ad7a507944dd0a975296baea383198aa87177f29af2c6f69', + phone: 'd74dcef7e1dba5aed01b62bc932ab1a7848ed9a590ca029aa958a1bf82b6993d', + query: 'test-query', + order_id: 'A1234', + revenue: 1000 + }, + context: { + ...testEvent.context, + app: { + namespace: 'com.segment.app', + version: '1.0.0' + }, + device: { + adTrackingEnabled: true + }, + screen: { + width: '1920', + height: '1080', + density: '2.0' + }, + os: { + version: '19.5' + } + } + }, + mapping: { + event_name: 'PURCHASE', + action_source: 'app', + data_processing_options: true, + data_processing_options_country: 1, + data_processing_options_state: 1000 + } + }) + + expect(action_source).toEqual('app') + expect(event_name).toEqual('PURCHASE') + expect(event_id).toEqual(testEvent.messageId) + expect(event_source_url).toEqual(testEvent.context?.page?.url ?? '') + expect(event_time).toEqual(Date.parse(testEvent.timestamp as string)) + + expect(data_processing_options).toEqual(['LDU']) + expect(data_processing_options_country).toEqual(1) + expect(data_processing_options_state).toEqual(1000) + + expect(app_data.application_tracking_enabled).toEqual(1) + expect(app_data.extinfo).toEqual([ + 'i2', + 'com.segment.app', + '', + '1.0.0', + '19.5', + '', + 'en-US', + '', + '', + '1920', + '1080', + '2.0', + '', + '', + '', + 'Europe/Amsterdam' + ]) + + expect(custom_data.currency).toEqual('USD') + expect(custom_data.order_id).toEqual('A1234') + expect(custom_data.search_string).toEqual('test-query') + expect(custom_data.value).toEqual(1000) + + expect(user_data.external_id[0]).toEqual(smartHash(testEvent.userId ?? '')) + expect(user_data.em[0]).toEqual('388c735eec8225c4ad7a507944dd0a975296baea383198aa87177f29af2c6f69') + expect(user_data.ph[0]).toEqual('d74dcef7e1dba5aed01b62bc932ab1a7848ed9a590ca029aa958a1bf82b6993d') + expect(user_data.fn).toEqual(undefined) + expect(user_data.ln).toEqual(undefined) + expect(user_data.ge).toEqual(undefined) + expect(user_data.db).toEqual(undefined) + expect(user_data.ct).toEqual(undefined) + expect(user_data.st).toEqual(undefined) + expect(user_data.country).toEqual(undefined) + expect(user_data.zp).toEqual(undefined) + expect(user_data.client_ip_address).toBe(testEvent.context?.ip) + expect(user_data.client_user_agent).toBe(testEvent.context?.userAgent) + expect(user_data.idfv).toBe(testEvent.context?.device?.id) + expect(user_data.madid).toBe(testEvent.context?.device?.advertisingId) + expect(user_data.sc_click_id).toEqual((testEvent.integrations?.['Snap Conversions Api'] as any)?.click_id) + expect(user_data.sc_cookie1).toEqual((testEvent.integrations?.['Snap Conversions Api'] as any)?.uuid_c1) + }) + }) +}) diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/syncUserData.test.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/syncUserData.test.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/_tests_/syncUserData.test.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/syncUserData.test.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/trackEvent.test.ts similarity index 72% rename from packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/trackEvent.test.ts index b0e87faac6e..1818dd8ff4e 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackEvent.test.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/trackEvent.test.ts @@ -46,34 +46,6 @@ describe('Snap Conversions API - Track Event', () => { expect(responses[0].status).toBe(200) }) - it('should automatically map segment event names to snapchat event names', async () => { - const productViewedEvent = createTestEvent({ - ...testEvent, - event: 'Product Viewed' - }) - - nock('https://tr.snapchat.com') - .post('/v3/pixel123/events') - .query({ access_token: 'access123' }) - .reply(200, { status: 'success' }) - - const responses = await testDestination.testAction('trackEvent', { - event: productViewedEvent, - settings: { - pixel_id: 'pixel123', - snap_app_id: 'app123' - }, - useDefaultMappings: true, - auth: { - accessToken: 'access123', - refreshToken: 'refresh123' - } - }) - - expect(responses.length).toBe(1) - expect(responses[0].status).toBe(200) - }) - it('should batch multiple events', async () => { const batchEvents = [testEvent, { ...testEvent, messageId: 'test-message-2' }] diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackPurchase.test.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/trackPurchase.test.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/_tests_/trackPurchase.test.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/_tests_/trackPurchase.test.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/eventMapping.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/eventMapping.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/eventMapping.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/eventMapping.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/generated-types.ts new file mode 100644 index 00000000000..aa83e29ba55 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/generated-types.ts @@ -0,0 +1,16 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Settings { + /** + * The Pixel ID for your Snapchat Ad Account. **Required for web and offline events**. + */ + pixel_id?: string + /** + * The Snap App ID associated with your app. This is a unique code generated in Snapchat Ads Manager and included in your MMP dashboard. **Required for app events**. + */ + snap_app_id?: string + /** + * The unique ID assigned for a given application. It should be numeric for iOS, and the human interpretable string for Android. **Required for app events**. + */ + app_id?: string +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/index.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/index.ts new file mode 100644 index 00000000000..b16ea237740 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/index.ts @@ -0,0 +1,278 @@ +import { defaultValues, DestinationDefinition } from '@segment/actions-core' +import type { Settings } from './generated-types' + +import reportConversionEvent from './reportConversionEvent' +import trackEvent from './trackEvent' +import trackAppEvent from './trackAppEvent' +import trackPurchase from './trackPurchase' +import syncUserData from './syncUserData' + +const DEFAULT_VALS = { + ...defaultValues(reportConversionEvent.fields) +} + +const ACCESS_TOKEN_URL = 'https://accounts.snapchat.com/login/oauth2/access_token' + +interface RefreshTokenResponse { + access_token: string +} + +const presets: DestinationDefinition['presets'] = [ + { + name: 'Add Billing', + subscribe: 'event = "Payment Info Entered"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'ADD_BILLING', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Add to Cart', + subscribe: 'event = "Product Added"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'ADD_CART', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Add to Wishlist', + subscribe: 'event = "Product Added to Wishlist"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'ADD_TO_WISHLIST', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'App Install', + subscribe: 'event = "Application Installed"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'APP_INSTALL', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'List View', + subscribe: 'event = "Product List Viewed"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'LIST_VIEW', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'App Open', + subscribe: 'event = "Application Opened"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'APP_OPEN', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Login', + subscribe: 'event = "Signed In"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'LOGIN', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Page View', + subscribe: 'type = "page"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'PAGE_VIEW', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Purchase', + subscribe: 'event = "Order Completed"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'PURCHASE', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Search', + subscribe: 'event = "Products Searched"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'SEARCH', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Share', + subscribe: 'event = "Product Shared"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'SHARE', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Sign Up', + subscribe: 'event = "Signed Up"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'SIGN_UP', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Start Checkout', + subscribe: 'event = "Checkout Started"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'START_CHECKOUT', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'View Content', + subscribe: 'event = "Product Viewed"', + partnerAction: 'reportConversionEvent', + mapping: { + event_name: 'VIEW_CONTENT', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + // New presets for specific actions + { + name: 'Track Standard Events', + subscribe: 'type = "track"', + partnerAction: 'trackEvent', + mapping: { + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Track App Events', + subscribe: 'type = "track" and context.device.type != null', + partnerAction: 'trackAppEvent', + mapping: { + action_source: 'app', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Track Purchase Events', + subscribe: 'type = "track" and event = "Order Completed"', + partnerAction: 'trackPurchase', + mapping: { + event_name: 'PURCHASE', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + }, + { + name: 'Sync User Data', + subscribe: 'type = "identify"', + partnerAction: 'syncUserData', + mapping: { + event_name: 'UPDATE_PROFILE', + action_source: 'website', + ...DEFAULT_VALS + }, + type: 'automatic' + } +] + +const destination: DestinationDefinition = { + name: 'Snap Conversions API v3', + slug: 'actions-snap-conversions-v3', + mode: 'cloud', + + authentication: { + scheme: 'oauth2', + fields: { + pixel_id: { + label: 'Pixel ID', + description: 'The Pixel ID for your Snapchat Ad Account. **Required for web and offline events**.', + type: 'string' + }, + snap_app_id: { + label: 'Snap App ID', + description: + 'The Snap App ID associated with your app. This is a unique code generated in Snapchat Ads Manager and included in your MMP dashboard. **Required for app events**.', + type: 'string' + }, + app_id: { + label: 'App ID', + description: + 'The unique ID assigned for a given application. It should be numeric for iOS, and the human interpretable string for Android. **Required for app events**.', + type: 'string' + } + }, + refreshAccessToken: async (request, { auth }) => { + // Return a request that refreshes the access_token if the API supports it + const res = await request(ACCESS_TOKEN_URL, { + method: 'POST', + body: new URLSearchParams({ + refresh_token: auth.refreshToken, + client_id: auth.clientId, + client_secret: auth.clientSecret, + grant_type: 'refresh_token' + }) + }) + + return { accessToken: res.data.access_token } + } + }, + extendRequest({ auth }) { + return { + headers: { + authorization: `Bearer ${auth?.accessToken}` + } + } + }, + presets, + actions: { + reportConversionEvent, + trackEvent, + trackAppEvent, + trackPurchase, + syncUserData + } +} + +export default destination diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/generated-types.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/generated-types.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/generated-types.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/index.ts new file mode 100644 index 00000000000..78d54f8d358 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/index.ts @@ -0,0 +1,21 @@ +import { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import snap_capi_input_fields_deprecated from './snap-capi-input-fields-deprecated' +import snap_capi_input_fields_v3 from './snap-capi-input-fields-v3' +import { performSnapCAPIv3 as perform } from './snap-capi-v3' + +const action: ActionDefinition = { + title: 'Report Conversion Event', + description: + 'Report events directly to Snapchat. Data shared can power Snap solutions such as custom audience targeting, campaign optimization, Dynamic Ads, and more.', + fields: { + // Add deprecatred v2 fields last so that they take precedence in case we have overlapping names + // This is safer for backwards compatibility + ...snap_capi_input_fields_v3, + ...snap_capi_input_fields_deprecated + }, + perform +} + +export default action diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-deprecated.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-deprecated.ts new file mode 100644 index 00000000000..1d3f57866f0 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-deprecated.ts @@ -0,0 +1,203 @@ +import { InputField } from '@segment/actions-core/destination-kit/types' + +const brands: InputField = { + label: '[Deprecated] Brand', + description: '[Deprecated] Use Products field.', + type: 'string', + multiple: true +} + +const click_id: InputField = { + label: '[Deprecated] Click ID', + description: 'Deprecated. Use User Data sc_click_id field.', + type: 'string' +} + +const client_dedup_id: InputField = { + label: '[Deprecated] Client Deduplication ID', + description: 'Deprecated. Use Event ID field.', + type: 'string' +} + +const currency: InputField = { + label: '[Deprecated] Currency', + description: 'Deprecated. Use Custom Data currency field.', + type: 'string' +} + +const description: InputField = { + label: '[Deprecated] Description', + description: 'Deprecated. No longer supported.', + type: 'string' +} + +const device_model: InputField = { + label: '[Deprecated] Device Model', + description: 'Deprecated. Use App Data deviceName field.', + type: 'string' +} + +const email: InputField = { + label: '[Deprecated] Email', + description: 'Deprecated. Use User Data email field.', + type: 'string', + category: 'hashedPII' +} + +const event_conversion_type: InputField = { + label: '[Deprecated] Event Conversion Type', + description: 'Deprecated. Use Action Source field.', + type: 'string', + choices: [ + { label: 'Offline', value: 'OFFLINE' }, + { label: 'Web', value: 'WEB' }, + { label: 'Mobile App', value: 'MOBILE_APP' } + ] +} + +const event_tag: InputField = { + label: '[Deprecated] Event Tag', + description: 'Deprecated. No longer supported.', + type: 'string' +} + +const event_type: InputField = { + label: '[Deprecated] Event Type', + description: 'Deprecated. Use Event Name field.', + type: 'string' +} + +const idfv: InputField = { + label: '[Deprecated] Identifier for Vendor', + description: 'Deprecated. Use User Data idfv field.', + type: 'string' +} + +const ip_address: InputField = { + label: '[Deprecated] IP Address', + description: 'Deprecated. Use User Data client_ip_address field.', + type: 'string' +} + +const item_category: InputField = { + label: '[Deprecated] Item Category', + description: 'Deprecated. Use products field.', + type: 'string' +} + +const item_ids: InputField = { + label: '[Deprecated] Item ID', + description: 'Deprecated. Use products field.', + type: 'string' +} + +const level: InputField = { + label: '[Deprecated] Level', + description: 'Deprecated. No longer supported.', + type: 'string' +} + +const mobile_ad_id: InputField = { + label: '[Deprecated] Mobile Ad Identifier', + description: 'Deprecated. Use User Data madid field.', + type: 'string' +} + +const number_items: InputField = { + label: '[Deprecated] Number of Items', + description: 'Deprecated. Use Custom Data num_items field.', + type: 'string' +} + +const os_version: InputField = { + label: '[Deprecated] OS Version', + description: 'Deprecated. Use App Data version field.', + type: 'string' +} + +const page_url: InputField = { + label: '[Deprecated] Page URL', + description: 'Deprecated. Use Event Source URL field.', + type: 'string' +} + +const phone_number: InputField = { + label: '[Deprecated] Phone Number', + description: 'Deprecated. Use User Data phone field.', + type: 'string', + category: 'hashedPII' +} + +const price: InputField = { + label: '[Deprecated] Price', + description: 'Deprecated. Use Custom Data value field.', + type: 'number' +} + +const search_string: InputField = { + label: '[Deprecated] Search String', + description: 'Deprecated. Use Custom Data search_string field.', + type: 'string' +} + +const sign_up_method: InputField = { + label: '[Deprecated] Sign Up Method', + description: 'Deprecated. Use Custom Data sign_up_method field.', + type: 'string' +} + +const timestamp: InputField = { + label: '[Deprecated] Event Timestamp', + description: 'Deprecated. Use Event Timestamp field.', + type: 'string' +} + +const transaction_id: InputField = { + label: '[Deprecated] Transaction ID', + description: 'Deprecated. Use Custom Data order_id field.', + type: 'string' +} + +const user_agent: InputField = { + label: '[Deprecated] User Agent', + description: 'Deprecated. Use User Data client_user_agent field.', + type: 'string' +} + +const uuid_c1: InputField = { + label: '[Deprecated] uuid_c1 Cookie', + description: 'Deprecated. Use User Data sc_cookie1 field.', + type: 'string' +} + +const snap_capi_input_fields_deprecated = { + brands, + click_id, + client_dedup_id, + currency, + description, + device_model, + email, + event_conversion_type, + event_tag, + event_type, + idfv, + ip_address, + item_category, + item_ids, + level, + mobile_ad_id, + number_items, + os_version, + page_url, + phone_number, + price, + search_string, + sign_up_method, + timestamp, + transaction_id, + user_agent, + uuid_c1 +} + +export default snap_capi_input_fields_deprecated diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-v3.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-v3.ts new file mode 100644 index 00000000000..f71e98d3d01 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-input-fields-v3.ts @@ -0,0 +1,682 @@ +import { InputField, Optional } from '@segment/actions-core/destination-kit/types' + +const action_source: InputField = { + label: 'Action Source', + description: 'This field allows you to specify where your conversions occurred.', + type: 'string', + choices: [ + { label: 'EMAIL', value: 'email' }, + { label: 'WEBSITE', value: 'website' }, + { label: 'APP', value: 'app' }, + { label: 'PHONE CALL', value: 'phone_call' }, + { label: 'CHAT', value: 'chat' }, + { label: 'PHYSICAL STORE', value: 'physical_store' }, + { label: 'SYSTEM GENERATED', value: 'system_generated' }, + { label: 'OTHER', value: 'other' } + ] +} + +const app_data: InputField = { + label: 'App Data', + description: `These fields support sending app events to Snapchat through the Conversions API.`, + type: 'object', + defaultObjectUI: 'keyvalue', + properties: { + advertiser_tracking_enabled: { + label: 'Advertiser Tracking Enabled', + description: `*Required for app events* + Use this field to specify ATT permission on an iOS 14.5+ device. Set to 0 for disabled or 1 for enabled.`, + type: 'boolean' + }, + application_tracking_enabled: { + label: 'Application Tracking Enabled', + type: 'boolean', + description: `*Required for app events* + A person can choose to enable ad tracking on an app level. Your SDK should allow an app developer to put an opt-out setting into their app. Use this field to specify the person's choice. Use 0 for disabled, 1 for enabled.` + }, + version: { + label: 'ExtInfo Version', + description: `*Required for app events* Example: 'i2'.`, + type: 'string', + choices: [ + { label: 'iOS', value: 'i2' }, + { label: 'Android', value: 'a2' } + ] + }, + packageName: { + label: 'Package Name', + description: `Example: 'com.snapchat.sdk.samples.hello'.`, + type: 'string' + }, + shortVersion: { + label: 'Short Version', + description: `Example: '1.0'.`, + type: 'string' + }, + longVersion: { + label: 'Long Version', + description: `Example: '1.0 long'.`, + type: 'string' + }, + osVersion: { + label: '*Required for app events* OS Version', + description: `Example: '13.4.1'.`, + type: 'string' + }, + deviceName: { + label: 'Device Model Name', + description: `Example: 'iPhone5,1'.`, + type: 'string' + }, + locale: { + label: 'Locale', + description: `Example: 'En_US'.`, + type: 'string' + }, + timezone: { + label: 'Timezone Abbreviation', + description: `Example: 'PST'.`, + type: 'string' + }, + carrier: { + label: 'Carrier Name', + description: `Example: 'AT&T'.`, + type: 'string' + }, + width: { + label: 'Screen Width', + description: `Example: '1080'.`, + type: 'string' + }, + height: { + label: 'Screen Height', + description: `Example: '1920'.`, + type: 'string' + }, + density: { + label: 'Screen Density', + description: `Example: '2.0'.`, + type: 'string' + }, + cpuCores: { + label: 'CPU Cores', + description: `Example: '8'.`, + type: 'string' + }, + storageSize: { + label: 'Storage Size in GBs', + description: `Example: '64'.`, + type: 'string' + }, + freeStorage: { + label: 'Free Storage in GBs', + description: `Example: '32'.`, + type: 'string' + }, + deviceTimezone: { + label: 'Device Timezone', + description: `Example: 'USA/New York'.`, + type: 'string' + } + }, + default: { + application_tracking_enabled: { + '@path': '$.context.device.adTrackingEnabled' + }, + packageName: { + '@path': '$.context.app.namespace' + }, + longVersion: { + '@path': '$.context.app.version' + }, + osVersion: { + '@path': '$.context.os.version' + }, + deviceName: { + '@path': '$.context.device.model' + }, + locale: { + '@path': '$.context.locale' + }, + carrier: { + '@path': '$.context.network.carrier' + }, + width: { + '@path': '$.context.screen.width' + }, + height: { + '@path': '$.context.screen.height' + }, + density: { + '@path': '$.context.screen.density' + }, + deviceTimezone: { + '@path': '$.context.timezone' + } + } +} + +const custom_data_travel_properties: Record> = { + checkin_date: { + label: 'Check-In Date', + description: `The desired hotel check-in date in the hotel's time-zone. Accepted formats are YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD and YYYY-MM-DDThh:mm:ssTZD`, + type: 'string' + }, + travel_end: { + label: 'Travel End', + description: `End date of travel`, + type: 'string' + }, + travel_start: { + label: 'Travel Start', + description: `Start date of travel`, + type: 'string' + }, + suggested_destinations: { + label: 'Suggests Destinations', + description: `The suggested destinations`, + type: 'string' + }, + destination_airport: { + label: 'Destination Airport', + description: `The destination airport. Make sure to use the IATA code of the airport`, + type: 'string' + }, + country: { + label: 'Country', + description: `The country based on the location the user intends to visit`, + type: 'string' + }, + city: { + label: 'City', + description: `The city based on the location the user intends to visit`, + type: 'string' + }, + region: { + label: 'Region', + description: `This could be the state, district, or region of interest to the user`, + type: 'string' + }, + neighborhood: { + label: 'Neighborhood', + description: `The neighborhood the user is interested in`, + type: 'string' + }, + departing_departure_date: { + label: 'Departing Departure Date', + description: `The starting date and time for travel`, + type: 'string' + }, + departing_arrival_date: { + label: 'Departing Arrival Date', + description: `The arrival date and time at the destination for the travel`, + type: 'string' + }, + num_adults: { + label: 'Num Adults', + description: `The number of adults staying`, + type: 'integer' + }, + origin_airport: { + label: 'Origin Airport', + description: `The official IATA code of origin airport`, + type: 'string' + }, + returning_departure_date: { + label: 'Returning Departure Date', + description: `The starting date and time of the return journey`, + type: 'string' + }, + returning_arrival_date: { + label: 'Returning Arrival Date', + description: `The date and time when the return journey is complete`, + type: 'string' + }, + num_children: { + label: 'Num Children', + description: `The number of children staying`, + type: 'integer' + }, + hotel_score: { + label: 'Hotel Score', + description: `This represents the hotels score relative to other hotels to an advertiser`, + type: 'string' + }, + postal_code: { + label: 'Postal Code', + description: `The postal /zip code`, + type: 'string' + }, + num_infants: { + label: 'Num Infants', + description: `The number of infants staying`, + type: 'integer' + }, + preferred_neighborhoods: { + label: 'Preferred Neighborhoods', + description: `Any preferred neighborhoods for the stay`, + type: 'string' + }, + preferred_star_ratings: { + label: 'Preferred Star Ratings', + description: `The minimum and maximum hotel star rating supplied as a tuple. This is what the user would use for filtering hotels`, + type: 'string' + }, + suggested_hotels: { + label: 'Suggested Hotels', + description: `The suggested hotels`, + type: 'string' + } +} + +const custom_data: InputField = { + label: 'Custom Data', + description: 'The custom data object can be used to pass custom properties.', + type: 'object', + defaultObjectUI: 'keyvalue', + properties: { + currency: { + label: 'Currency', + description: 'Currency for the value specified as ISO 4217 code.', + type: 'string' + }, + num_items: { + label: 'Number of Items', + description: 'The number of items when checkout was initiated.', + type: 'integer' + }, + order_id: { + label: 'Order ID', + description: + 'Order ID tied to the conversion event. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Ads Kit events.', + type: 'string' + }, + search_string: { + label: 'Search String', + description: 'The text string that was searched for.', + type: 'string' + }, + sign_up_method: { + label: 'Sign Up Method', + description: 'A string indicating the sign up method.', + type: 'string' + }, + value: { + label: 'Value', + description: + "Total value of the purchase. This should be a single number. Can be overriden using the 'Track Purchase Value Per Product' field.", + type: 'number' + }, + ...custom_data_travel_properties + }, + default: { + currency: { + '@path': '$.properties.currency' + }, + num_items: { + '@path': '$.properties.quantity' + }, + order_id: { + '@path': '$.properties.order_id' + }, + search_string: { + '@path': '$.properties.query' + }, + value: { + '@if': { + exists: { '@path': '$.properties.revenue' }, + then: { '@path': '$.properties.revenue' }, + else: { '@path': '$.properties.total' } + } + } + } +} + +const user_data: InputField = { + label: 'User Data', + description: + 'These parameters are a set of identifiers Snapchat can use for targeted attribution. You must provide at least one of the following parameters in your request.', + type: 'object', + defaultObjectUI: 'keyvalue', + properties: { + externalId: { + label: 'External ID', + description: + 'Any unique ID from the advertiser, such as loyalty membership IDs, user IDs, and external cookie IDs. You can send one or more external IDs for a given event.', + type: 'string', + multiple: true, // changed the type from string to array of strings. + category: 'hashedPII' + }, + email: { + label: 'Email', + description: 'An email address in lowercase.', + type: 'string', + category: 'hashedPII' + }, + phone: { + label: 'Phone', + description: + 'A phone number. Include only digits with country code, area code, and number. Remove symbols, letters, and any leading zeros. In addition, always include the country code, even if all of the data is from the same country, as the country code is used for matching.', + type: 'string', + category: 'hashedPII' + }, + gender: { + label: 'Gender', + description: 'Gender in lowercase. Either f or m.', + type: 'string', + category: 'hashedPII' + }, + dateOfBirth: { + label: 'Date of Birth', + description: 'A date of birth given as year, month, and day. Example: 19971226 for December 26, 1997.', + type: 'string', + category: 'hashedPII' + }, + lastName: { + label: 'Last Name', + description: 'A last name in lowercase.', + type: 'string', + category: 'hashedPII' + }, + firstName: { + label: 'First Name', + description: 'A first name in lowercase.', + type: 'string', + category: 'hashedPII' + }, + city: { + label: 'City', + description: 'A city in lowercase without spaces or punctuation. Example: menlopark.', + type: 'string', + category: 'hashedPII' + }, + state: { + label: 'State', + description: 'A two-letter state code in lowercase. Example: ca.', + type: 'string', + category: 'hashedPII' + }, + zip: { + label: 'Zip Code', + description: 'A five-digit zip code for United States. For other locations, follow each country`s standards.', + type: 'string', + category: 'hashedPII' + }, + country: { + label: 'Country', + description: 'A two-letter country code in lowercase.', + type: 'string', + category: 'hashedPII' + }, + client_ip_address: { + label: 'Client IP Address', + description: 'The IP address of the browser corresponding to the event.', + type: 'string' + }, + client_user_agent: { + label: 'Client User Agent', + description: + 'The user agent for the browser corresponding to the event. This is required if action source is “website”.', + type: 'string' + }, + subscriptionID: { + label: 'Subscription ID', + description: 'The subscription ID for the user in this transaction.', + type: 'string' + }, + leadID: { + label: 'Lead ID', + description: 'This is the identifier associated with your Snapchat Lead Ad.', + type: 'integer' + }, + madid: { + label: 'Mobile Ad Identifier', + description: + 'Mobile ad identifier (IDFA or AAID) of the user who triggered the conversion event. Segment will normalize and hash this value before sending to Snapchat. [Snapchat requires](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters) that every payload contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields. Also see [Segment documentation](https://segment.com/docs/connections/destinations/catalog/actions-snap-conversions/#required-parameters-and-hashing).', + type: 'string' + }, + sc_click_id: { + label: 'Click ID', + description: + "The ID value stored in the landing page URL's `&ScCid=` query parameter. Using this ID improves ad measurement performance. We also encourage advertisers who are using `click_id` to pass the full url in the `page_url` field. For more details, please refer to [Sending a Click ID](#sending-a-click-id)", + type: 'string' + }, + sc_cookie1: { + label: 'uuid_c1 Cookie', + description: + 'Unique user ID cookie. If you are using the Pixel SDK, you can access a cookie1 by looking at the _scid value.', + type: 'string' + }, + idfv: { + label: 'Identifier for Vendor', + description: 'IDFV of the user’s device. Segment will normalize and hash this value before sending to Snapchat.', + type: 'string' + } + }, + default: { + externalId: { + '@if': { + exists: { '@path': '$.userId' }, + then: { '@path': '$.userId' }, + else: { '@path': '$.anonymousId' } + } + }, + email: { + '@if': { + exists: { '@path': '$.properties.email' }, + then: { '@path': '$.properties.email' }, + else: { '@path': '$.traits.email' } + } + }, + phone: { + '@if': { + exists: { '@path': '$.properties.phone' }, + then: { '@path': '$.properties.phone' }, + else: { '@path': '$.traits.phone' } + } + }, + gender: { + '@if': { + exists: { '@path': '$.context.traits.gender' }, + then: { '@path': '$.context.traits.gender' }, + else: { '@path': '$.properties.gender' } + } + }, + dateOfBirth: { + '@if': { + exists: { '@path': '$.context.traits.birthday' }, + then: { '@path': '$.context.traits.birthday' }, + else: { '@path': '$.properties.birthday' } + } + }, + lastName: { + '@if': { + exists: { '@path': '$.context.traits.last_name' }, + then: { '@path': '$.context.traits.last_name' }, + else: { '@path': '$.properties.last_name' } + } + }, + firstName: { + '@if': { + exists: { '@path': '$.context.traits.first_name' }, + then: { '@path': '$.context.traits.first_name' }, + else: { '@path': '$.properties.first_name' } + } + }, + city: { + '@if': { + exists: { '@path': '$.context.traits.address.city' }, + then: { '@path': '$.context.traits.address.city' }, + else: { '@path': '$.properties.address.city' } + } + }, + state: { + '@if': { + exists: { '@path': '$.context.traits.address.state' }, + then: { '@path': '$.context.traits.address.state' }, + else: { '@path': '$.properties.address.state' } + } + }, + country: { + '@if': { + exists: { '@path': '$.context.traits.address.country' }, + then: { '@path': '$.context.traits.address.country' }, + else: { '@path': '$.properties.address.country' } + } + }, + zip: { + '@if': { + exists: { '@path': '$.context.traits.address.postalCode' }, + then: { '@path': '$.context.traits.address.postalCode' }, + else: { '@path': '$.properties.address.postalCode' } + } + }, + client_ip_address: { + '@path': '$.context.ip' + }, + client_user_agent: { + '@path': '$.context.userAgent' + }, + idfv: { + '@path': '$.context.device.id' + }, + madid: { + '@path': '$.context.device.advertisingId' + }, + sc_click_id: { + '@path': '$.integrations.Snap Conversions Api.click_id' + }, + sc_cookie1: { + '@path': '$.integrations.Snap Conversions Api.uuid_c1' + } + } +} + +const data_processing_options: InputField = { + label: 'Data Processing Options', + description: `The Data Processing Options to send to Snapchat. If set to true, Segment will send an array to Snapchat indicating events should be processed with Limited Data Use (LDU) restrictions.`, + type: 'boolean' +} + +const data_processing_options_country: InputField = { + label: 'Data Processing Country', + description: + 'A country that you want to associate to the Data Processing Options. Accepted values are 1, for the United States of America, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0.', + type: 'number', + choices: [ + { label: `Use Snapchat's Geolocation Logic`, value: 0 }, + { label: 'United States of America', value: 1 } + ] +} + +const data_processing_options_state: InputField = { + label: 'Data Processing State', + description: + 'A state that you want to associate to the Data Processing Options. Accepted values are 1000, for California, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0.', + type: 'number', + choices: [ + { label: "Use Snapchat's Geolocation Logic", value: 0 }, + { label: 'California', value: 1000 } + ] +} + +const event_id: InputField = { + label: 'Event ID', + description: + 'If you are reporting events via more than one method (Snap Pixel, App Ads Kit, Conversions API) you should use the same event_id across all methods. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Adds Kit events.', + type: 'string', + default: { + '@path': '$.messageId' + } +} + +const event_name: InputField = { + label: 'Event Name', + description: + 'The conversion event type. For custom events, you must use one of the predefined event types (i.e. CUSTOM_EVENT_1). Please refer to the possible event types in [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters).', + type: 'string' +} + +const event_source_url: InputField = { + label: 'Event Source URL', + description: 'The URL of the web page where the event took place.', + type: 'string', + default: { + '@path': '$.context.page.url' + } +} + +const event_time: InputField = { + label: 'Event Timestamp', + description: + 'The Epoch timestamp for when the conversion happened. The timestamp cannot be more than 7 days in the past.', + type: 'string', + default: { + '@path': '$.timestamp' + } +} + +// Ideally this would be a property in custom_data, but object fields cannot contain complex types. +const products: InputField = { + label: 'Products', + description: + "Use this field to send details of mulitple products / items. This field overrides individual 'Item ID', 'Item Category' and 'Brand' fields. Note: total purchase value is tracked using the 'Price' field", + type: 'object', + multiple: true, + additionalProperties: false, + properties: { + item_id: { + label: 'Item ID', + type: 'string', + description: + 'Identfier for the item. International Article Number (EAN) when applicable, or other product or category identifier.', + allowNull: false + }, + item_category: { + label: 'Category', + type: 'string', + description: 'Category of the item. This field accepts a string.', + allowNull: false + }, + brand: { + label: 'Brand', + type: 'string', + description: 'Brand associated with the item. This field accepts a string.', + allowNull: false + } + }, + default: { + '@arrayPath': [ + '$.properties.products', + { + item_id: { + '@path': 'product_id' + }, + item_category: { + '@path': 'category' + }, + brand: { + '@path': 'brand' + } + } + ] + } +} + +// The order here is important and impacts the UI for event testing. +const snap_capi_input_fields_v3 = { + event_name, + event_id, + event_time, + action_source, + user_data, + app_data, + custom_data, + data_processing_options, + data_processing_options_country, + data_processing_options_state, + event_source_url, + products +} + +export default snap_capi_input_fields_v3 diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-v3.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-v3.ts new file mode 100644 index 00000000000..ce491af8fd5 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-capi-v3.ts @@ -0,0 +1,791 @@ +import { ExecuteInput, ModifiedResponse, RequestClient, IntegrationError } from '@segment/actions-core' +import { Payload } from './generated-types' +import { Settings } from '../generated-types' +import { + box, + emptyObjectToUndefined, + isNullOrUndefined, + splitListValueToArray, + raiseMisconfiguredRequiredFieldErrorIf, + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined, + emptyStringToUndefined, + parseNumberSafe, + parseDateSafe, + smartHash +} from './utils' +import { processHashing } from '../../../lib/hashing-utils' + +const CURRENCY_ISO_4217_CODES = new Set([ + 'USD', + 'AED', + 'AUD', + 'BGN', + 'BRL', + 'CAD', + 'CHF', + 'CLP', + 'CNY', + 'COP', + 'CZK', + 'DKK', + 'EGP', + 'EUR', + 'GBP', + 'GIP', + 'HKD', + 'HRK', + 'HUF', + 'IDR', + 'ILS', + 'INR', + 'JPY', + 'KRW', + 'KWD', + 'KZT', + 'LBP', + 'MXN', + 'MYR', + 'NGN', + 'NOK', + 'NZD', + 'PEN', + 'PHP', + 'PKR', + 'PLN', + 'QAR', + 'RON', + 'RUB', + 'SAR', + 'SEK', + 'SGD', + 'THB', + 'TRY', + 'TWD', + 'TZS', + 'UAH', + 'VND', + 'ZAR', + 'ALL', + 'BHD', + 'DZD', + 'GHS', + 'IQD', + 'ISK', + 'JOD', + 'KES', + 'MAD', + 'OMR', + 'XOF' +]) + +const US_STATE_CODES = new Map([ + ['arizona', 'az'], + ['alabama', 'al'], + ['alaska', 'ak'], + ['arkansas', 'ar'], + ['california', 'ca'], + ['colorado', 'co'], + ['connecticut', 'ct'], + ['delaware', 'de'], + ['florida', 'fl'], + ['georgia', 'ga'], + ['hawaii', 'hi'], + ['idaho', 'id'], + ['illinois', 'il'], + ['indiana', 'in'], + ['iowa', 'ia'], + ['kansas', 'ks'], + ['kentucky', 'ky'], + ['louisiana', 'la'], + ['maine', 'me'], + ['maryland', 'md'], + ['massachusetts', 'ma'], + ['michigan', 'mi'], + ['minnesota', 'mn'], + ['mississippi', 'ms'], + ['missouri', 'mo'], + ['montana', 'mt'], + ['nebraska', 'ne'], + ['nevada', 'nv'], + ['newhampshire', 'nh'], + ['newjersey', 'nj'], + ['newmexico', 'nm'], + ['newyork', 'ny'], + ['northcarolina', 'nc'], + ['northdakota', 'nd'], + ['ohio', 'oh'], + ['oklahoma', 'ok'], + ['oregon', 'or'], + ['pennsylvania', 'pa'], + ['rhodeisland', 'ri'], + ['southcarolina', 'sc'], + ['southdakota', 'sd'], + ['tennessee', 'tn'], + ['texas', 'tx'], + ['utah', 'ut'], + ['vermont', 'vt'], + ['virginia', 'va'], + ['washington', 'wa'], + ['westvirginia', 'wv'], + ['wisconsin', 'wi'], + ['wyoming', 'wy'] +]) + +const COUNTRY_CODES = new Map([ + ['afghanistan', 'af'], + ['alandislands', 'ax'], + ['albania', 'al'], + ['algeria', 'dz'], + ['americansamoa', 'as'], + ['andorra', 'ad'], + ['angola', 'ao'], + ['anguilla', 'ai'], + ['antarctica', 'aq'], + ['antiguaandbarbuda', 'ag'], + ['argentina', 'ar'], + ['armenia', 'am'], + ['aruba', 'aw'], + ['australia', 'au'], + ['austria', 'at'], + ['azerbaijan', 'az'], + ['bahamas', 'bs'], + ['bahrain', 'bh'], + ['bangladesh', 'bd'], + ['barbados', 'bb'], + ['belarus', 'by'], + ['belgium', 'be'], + ['belize', 'bz'], + ['benin', 'bj'], + ['bermuda', 'bm'], + ['bhutan', 'bt'], + ['bolivia', 'bo'], + ['bosniaandherzegovina', 'ba'], + ['botswana', 'bw'], + ['bouvetisland', 'bv'], + ['brazil', 'br'], + ['britishindianoceanterritory', 'io'], + ['bruneidarussalam', 'bn'], + ['bulgaria', 'bg'], + ['burkinafaso', 'bf'], + ['burundi', 'bi'], + ['cambodia', 'kh'], + ['cameroon', 'cm'], + ['canada', 'ca'], + ['capeverde', 'cv'], + ['caymanislands', 'ky'], + ['centralafricanrepublic', 'cf'], + ['chad', 'td'], + ['chile', 'cl'], + ['china', 'cn'], + ['christmasisland', 'cx'], + ['cocos(keeling)islands', 'cc'], + ['colombia', 'co'], + ['comoros', 'km'], + ['congo', 'cg'], + ['congo,democraticrepublic', 'cd'], + ['cookislands', 'ck'], + ['costarica', 'cr'], + ["coted'ivoire", 'ci'], + ['croatia', 'hr'], + ['cuba', 'cu'], + ['cyprus', 'cy'], + ['czechrepublic', 'cz'], + ['denmark', 'dk'], + ['djibouti', 'dj'], + ['dominica', 'dm'], + ['dominicanrepublic', 'do'], + ['ecuador', 'ec'], + ['egypt', 'eg'], + ['elsalvador', 'sv'], + ['equatorialguinea', 'gq'], + ['eritrea', 'er'], + ['estonia', 'ee'], + ['ethiopia', 'et'], + ['falklandislands(malvinas)', 'fk'], + ['faroeislands', 'fo'], + ['fiji', 'fj'], + ['finland', 'fi'], + ['france', 'fr'], + ['frenchguiana', 'gf'], + ['frenchpolynesia', 'pf'], + ['frenchsouthernterritories', 'tf'], + ['gabon', 'ga'], + ['gambia', 'gm'], + ['georgia', 'ge'], + ['germany', 'de'], + ['ghana', 'gh'], + ['gibraltar', 'gi'], + ['greece', 'gr'], + ['greenland', 'gl'], + ['grenada', 'gd'], + ['guadeloupe', 'gp'], + ['guam', 'gu'], + ['guatemala', 'gt'], + ['guernsey', 'gg'], + ['guinea', 'gn'], + ['guinea-bissau', 'gw'], + ['guyana', 'gy'], + ['haiti', 'ht'], + ['heardisland&mcdonaldislands', 'hm'], + ['holysee(vaticancitystate)', 'va'], + ['honduras', 'hn'], + ['hongkong', 'hk'], + ['hungary', 'hu'], + ['iceland', 'is'], + ['india', 'in'], + ['indonesia', 'id'], + ['iran,islamicrepublicof', 'ir'], + ['iraq', 'iq'], + ['ireland', 'ie'], + ['isleofman', 'im'], + ['israel', 'il'], + ['italy', 'it'], + ['jamaica', 'jm'], + ['japan', 'jp'], + ['jersey', 'je'], + ['jordan', 'jo'], + ['kazakhstan', 'kz'], + ['kenya', 'ke'], + ['kiribati', 'ki'], + ['korea', 'kr'], + ['kuwait', 'kw'], + ['kyrgyzstan', 'kg'], + ["laopeople'sdemocraticrepublic", 'la'], + ['latvia', 'lv'], + ['lebanon', 'lb'], + ['lesotho', 'ls'], + ['liberia', 'lr'], + ['libyanarabjamahiriya', 'ly'], + ['liechtenstein', 'li'], + ['lithuania', 'lt'], + ['luxembourg', 'lu'], + ['macao', 'mo'], + ['macedonia', 'mk'], + ['madagascar', 'mg'], + ['malawi', 'mw'], + ['malaysia', 'my'], + ['maldives', 'mv'], + ['mali', 'ml'], + ['malta', 'mt'], + ['marshallislands', 'mh'], + ['martinique', 'mq'], + ['mauritania', 'mr'], + ['mauritius', 'mu'], + ['mayotte', 'yt'], + ['mexico', 'mx'], + ['micronesia,federatedstatesof', 'fm'], + ['moldova', 'md'], + ['monaco', 'mc'], + ['mongolia', 'mn'], + ['montenegro', 'me'], + ['montserrat', 'ms'], + ['morocco', 'ma'], + ['mozambique', 'mz'], + ['myanmar', 'mm'], + ['namibia', 'na'], + ['nauru', 'nr'], + ['nepal', 'np'], + ['netherlands', 'nl'], + ['netherlandsantilles', 'an'], + ['newcaledonia', 'nc'], + ['newzealand', 'nz'], + ['nicaragua', 'ni'], + ['niger', 'ne'], + ['nigeria', 'ng'], + ['niue', 'nu'], + ['norfolkisland', 'nf'], + ['northernmarianaislands', 'mp'], + ['norway', 'no'], + ['oman', 'om'], + ['pakistan', 'pk'], + ['palau', 'pw'], + ['palestinianterritory,occupied', 'ps'], + ['panama', 'pa'], + ['papuanewguinea', 'pg'], + ['paraguay', 'py'], + ['peru', 'pe'], + ['philippines', 'ph'], + ['pitcairn', 'pn'], + ['poland', 'pl'], + ['portugal', 'pt'], + ['puertorico', 'pr'], + ['qatar', 'qa'], + ['reunion', 're'], + ['romania', 'ro'], + ['russianfederation', 'ru'], + ['rwanda', 'rw'], + ['saintbarthelemy', 'bl'], + ['sainthelena', 'sh'], + ['saintkittsandnevis', 'kn'], + ['saintlucia', 'lc'], + ['saintmartin', 'mf'], + ['saintpierreandmiquelon', 'pm'], + ['saintvincentandgrenadines', 'vc'], + ['samoa', 'ws'], + ['sanmarino', 'sm'], + ['saotomeandprincipe', 'st'], + ['saudiarabia', 'sa'], + ['senegal', 'sn'], + ['serbia', 'rs'], + ['seychelles', 'sc'], + ['sierraleone', 'sl'], + ['singapore', 'sg'], + ['slovakia', 'sk'], + ['slovenia', 'si'], + ['solomonislands', 'sb'], + ['somalia', 'so'], + ['southafrica', 'za'], + ['southgeorgiaandsandwichisl.', 'gs'], + ['spain', 'es'], + ['srilanka', 'lk'], + ['sudan', 'sd'], + ['suriname', 'sr'], + ['svalbardandjanmayen', 'sj'], + ['swaziland', 'sz'], + ['sweden', 'se'], + ['switzerland', 'ch'], + ['syrianarabrepublic', 'sy'], + ['taiwan', 'tw'], + ['tajikistan', 'tj'], + ['tanzania', 'tz'], + ['thailand', 'th'], + ['timor-leste', 'tl'], + ['togo', 'tg'], + ['tokelau', 'tk'], + ['tonga', 'to'], + ['trinidadandtobago', 'tt'], + ['tunisia', 'tn'], + ['turkey', 'tr'], + ['turkmenistan', 'tm'], + ['turksandcaicosislands', 'tc'], + ['tuvalu', 'tv'], + ['uganda', 'ug'], + ['ukraine', 'ua'], + ['unitedarabemirates', 'ae'], + ['unitedkingdom', 'gb'], + ['unitedstates', 'us'], + ['unitedstatesoutlyingislands', 'um'], + ['uruguay', 'uy'], + ['uzbekistan', 'uz'], + ['vanuatu', 'vu'], + ['venezuela', 've'], + ['vietnam', 'vn'], + ['virginislands,british', 'vg'], + ['virginislands,u.s.', 'vi'], + ['wallisandfutuna', 'wf'], + ['westernsahara', 'eh'], + ['yemen', 'ye'], + ['zambia', 'zm'], + ['zimbabwe', 'zw'] +]) + +const iosAppIDRegex = new RegExp('^[0-9]+$') + +const buildAppData = (payload: Payload, settings: Settings) => { + const { app_data } = payload + const app_id = emptyStringToUndefined(settings.app_id) + + // Ideally advertisers on iOS 14.5+ would pass the ATT_STATUS from the device. + // However the field is required for app events, so hardcode the value to false (0) + // for any events sent that include app_data. + const advertiser_tracking_enabled = app_data?.advertiser_tracking_enabled ? 1 : 0 + + const appDataApplicationTrackingEnabled = app_data?.application_tracking_enabled ? 1 : 0 + const application_tracking_enabled = app_data != null ? appDataApplicationTrackingEnabled : undefined + + const appDataExtInfoVersion = app_data?.version + const appIDExtInfoVersion = iosAppIDRegex.test(app_id ?? '') ? 'i2' : 'a2' + const extInfoVersion = appDataExtInfoVersion ?? appIDExtInfoVersion + + // extinfo needs to be defined whenever app_data is included in the data payload + const extinfo = [ + extInfoVersion, // required per spec version must be a2 for Android, must be i2 for iOS + app_data?.packageName ?? '', + app_data?.shortVersion ?? '', + app_data?.longVersion ?? '', + app_data?.osVersion ?? payload.os_version ?? '', // os version + app_data?.deviceName ?? payload.device_model ?? '', // device model name + app_data?.locale ?? '', + app_data?.timezone ?? '', + app_data?.carrier ?? '', + app_data?.width ?? '', + app_data?.height ?? '', + app_data?.density ?? '', + app_data?.cpuCores ?? '', + app_data?.storageSize ?? '', + app_data?.freeStorage ?? '', + app_data?.deviceTimezone ?? '' + ] + + // Only set app data for app events + return { + app_id, + advertiser_tracking_enabled, + application_tracking_enabled, + extinfo + } +} + +const buildUserData = (payload: Payload) => { + const { user_data } = payload + // Removes all leading and trailing whitespace and converts all characters to lowercase. + const normalizedValue = (value: string) => value?.replace(/\s/g, '').toLowerCase() + const email = smartHash(user_data?.email ?? payload.email, normalizedValue) + + // Removes all non-numberic characters and leading zeros. + const normalizedPhoneNumber = (value: string) => value?.replace(/\D|^0+/g, '') + const phone_number = smartHash(user_data?.phone ?? payload.phone_number, normalizedPhoneNumber) + + // Converts all characters to lowercase + const madid = (user_data?.madid ?? payload.mobile_ad_id)?.toLowerCase() + + const normalizedGender = (value: string): string => { + const normalizedValue = value?.replace(/\s/g, '').toLowerCase() + return normalizedValue === 'male' ? 'm' : normalizedValue === 'female' ? 'f' : normalizedValue + } + const hashedGender = smartHash(user_data?.gender, normalizedGender) + + const hashedLastName = smartHash(user_data?.lastName, normalizedValue) + + const hashedFirstName = smartHash(user_data?.firstName, normalizedValue) + + const client_ip_address = user_data?.client_ip_address ?? payload.ip_address + const client_user_agent = user_data?.client_user_agent ?? payload.user_agent + + const hashedCity = smartHash(user_data?.city, normalizedValue) + + // checks if the full US state name is used instead of the two letter abbreviation + const normalizedState = (value: string): string => { + const normalizedValue = value?.replace(/\s/g, '').toLowerCase() + return US_STATE_CODES.get(normalizedValue ?? '') ?? normalizedValue + } + const hashedState = smartHash(user_data?.state, normalizedState) + + const hashedZip = smartHash(user_data?.zip, normalizedValue) + + const normalizedCountry = (value: string): string => { + const normalizedValue = value?.replace(/\s/g, '').toLowerCase() + return COUNTRY_CODES.get(normalizedValue ?? '') ?? normalizedValue + } + const hashedCountry = smartHash(user_data?.country, normalizedCountry) + + const external_id = user_data?.externalId?.map((id) => { + return processHashing(id, 'sha256', 'hex', normalizedValue) + }) + + const db = smartHash(user_data?.dateOfBirth) + const lead_id = user_data?.leadID + const subscription_id = user_data?.subscriptionID + + const idfv = user_data?.idfv ?? payload.idfv + + const sc_click_id = user_data?.sc_click_id ?? payload.click_id + const sc_cookie1 = user_data?.sc_cookie1 ?? payload.uuid_c1 + + return { + client_ip_address, + client_user_agent, + country: hashedCountry, + ct: hashedCity, + db, + em: box(email), + external_id, + fn: hashedFirstName, + ge: hashedGender, + idfv, + lead_id, + ln: hashedLastName, + madid, + ph: box(phone_number), + sc_click_id, + sc_cookie1, + st: hashedState, + subscription_id, + zp: hashedZip + } +} + +const buildCustomData = (payload: Payload) => { + const { custom_data } = payload + + const products = payload.products?.filter(({ item_id }) => item_id != null) + + const content_ids = products?.map(({ item_id }) => item_id ?? '') ?? splitListValueToArray(payload.item_ids ?? '') + + const content_category = + products?.map(({ item_category }) => item_category ?? '') ?? splitListValueToArray(payload.item_category ?? '') + + const brands = products?.map((product) => product.brand ?? '') ?? payload.brands + + const num_items = custom_data?.num_items ?? products?.length ?? parseNumberSafe(payload.number_items) + + const currency = emptyStringToUndefined(custom_data?.currency ?? payload.currency)?.toUpperCase() + const order_id = emptyStringToUndefined(custom_data?.order_id ?? payload.transaction_id) + const search_string = emptyStringToUndefined(custom_data?.search_string ?? payload.search_string) + const sign_up_method = emptyStringToUndefined(custom_data?.sign_up_method ?? payload.sign_up_method) + const value = custom_data?.value ?? payload.price + + const checkin_date = emptyStringToUndefined(custom_data?.checkin_date) + const travel_end = emptyStringToUndefined(custom_data?.travel_end) + const travel_start = emptyStringToUndefined(custom_data?.travel_start) + const suggested_destinations = emptyStringToUndefined(custom_data?.suggested_destinations) + const destination_airport = emptyStringToUndefined(custom_data?.destination_airport) + const country = emptyStringToUndefined(custom_data?.country) + const city = emptyStringToUndefined(custom_data?.city) + const region = emptyStringToUndefined(custom_data?.region) + const neighborhood = emptyStringToUndefined(custom_data?.neighborhood) + const departing_departure_date = emptyStringToUndefined(custom_data?.departing_departure_date) + const departing_arrival_date = emptyStringToUndefined(custom_data?.departing_arrival_date) + const num_adults = custom_data?.num_adults + const origin_airport = emptyStringToUndefined(custom_data?.origin_airport) + const returning_departure_date = emptyStringToUndefined(custom_data?.returning_departure_date) + const returning_arrival_date = emptyStringToUndefined(custom_data?.returning_arrival_date) + const num_children = custom_data?.num_children + const hotel_score = emptyStringToUndefined(custom_data?.hotel_score) + const postal_code = emptyStringToUndefined(custom_data?.postal_code) + const num_infants = custom_data?.num_infants + const preferred_neighborhoods = emptyStringToUndefined(custom_data?.preferred_neighborhoods) + const preferred_star_ratings = emptyStringToUndefined(custom_data?.preferred_star_ratings) + const suggested_hotels = emptyStringToUndefined(custom_data?.suggested_hotels) + + const dta_fields = { + checkin_date, + travel_end, + travel_start, + suggested_destinations, + destination_airport, + country, + city, + region, + neighborhood, + departing_departure_date, + departing_arrival_date, + num_adults, + origin_airport, + returning_departure_date, + returning_arrival_date, + num_children, + hotel_score, + postal_code, + num_infants, + preferred_neighborhoods, + preferred_star_ratings, + suggested_hotels + } + + return emptyObjectToUndefined({ + brands, + content_category, + content_ids, + currency, + num_items, + order_id, + search_string, + sign_up_method, + value, + ...dta_fields + }) +} + +const eventConversionTypeToActionSource: { [k in string]?: string } = { + WEB: 'website', + MOBILE_APP: 'app', + + // Use the snap event_conversion_type for offline events + OFFLINE: 'OFFLINE' +} + +const getSupportedActionSource = (action_source: string | undefined): string | undefined => { + const normalizedActionSource = emptyStringToUndefined(action_source) + + // Snap doesn't support all the defined action sources, so fall back to OFFLINE if specified. + return ['website', 'app'].indexOf(normalizedActionSource ?? '') > -1 + ? normalizedActionSource + : normalizedActionSource != null + ? 'OFFLINE' + : undefined +} + +const buildPayloadData = (payload: Payload, settings: Settings) => { + // event_conversion_type is a required parameter whose value is enforced as + // always OFFLINE, WEB, or MOBILE_APP, so in practice action_source will always have a value. + const action_source = + getSupportedActionSource(payload.action_source) ?? + eventConversionTypeToActionSource[payload.event_conversion_type ?? ''] + + // Snaps CAPI v3 supports the legacy v2 events so don't bother + // translating them + const event_name = payload.event_name ?? payload.event_type + const event_source_url = payload.event_source_url ?? payload.page_url + const event_id = emptyStringToUndefined(payload.event_id) ?? emptyStringToUndefined(payload.client_dedup_id) + + const payload_event_time = payload.event_time ?? payload.timestamp + // Handle the case where a number is passed instead of an ISO8601 timestamp + const event_time_number = parseNumberSafe(payload_event_time ?? '') + const event_time_date_time = parseDateSafe(payload_event_time ?? '') + const event_time = event_time_date_time ?? event_time_number + + const app_data = action_source === 'app' ? buildAppData(payload, settings) : undefined + const user_data = buildUserData(payload) + const custom_data = buildCustomData(payload) + + const data_processing_options = payload.data_processing_options ?? false ? ['LDU'] : undefined + + return { + integration: 'segment', + event_id, + event_name, + event_source_url, + event_time, + user_data, + custom_data, + action_source, + app_data, + data_processing_options, + data_processing_options_country: payload.data_processing_options_country, + data_processing_options_state: payload.data_processing_options_state + } +} + +const validateSettingsConfig = (settings: Settings, action_source: string | undefined) => { + const { snap_app_id, pixel_id } = settings + const snapAppID = emptyStringToUndefined(snap_app_id) + const snapPixelID = emptyStringToUndefined(pixel_id) + + raiseMisconfiguredRequiredFieldErrorIf( + action_source === 'OFFLINE' && isNullOrUndefined(snapPixelID), + 'If event conversion type is "OFFLINE" then Pixel ID must be defined' + ) + + raiseMisconfiguredRequiredFieldErrorIf( + action_source === 'app' && isNullOrUndefined(snapAppID), + 'If event conversion type is "MOBILE_APP" then Snap App ID must be defined' + ) + + raiseMisconfiguredRequiredFieldErrorIf( + action_source === 'website' && isNullOrUndefined(snapPixelID), + `If event conversion type is "WEB" then Pixel ID must be defined` + ) +} + +const buildRequestURL = (settings: Settings, action_source: string | undefined, authToken: string) => { + const { snap_app_id, pixel_id } = settings + + // Some configurations specify both a snapPixelID and a snapAppID. In these cases + // check the conversion type to ensure that the right id is selected and used. + const appOrPixelID = emptyStringToUndefined( + (() => { + switch (action_source) { + case 'website': + case 'OFFLINE': + return pixel_id + case 'app': + return snap_app_id + default: + return undefined + } + })() + ) + + return `https://tr.snapchat.com/v3/${appOrPixelID}/events?access_token=${authToken}` +} + +const validatePayload = (payload: ReturnType) => { + const { + action_source, + event_name, + event_time, + custom_data = {} as NonNullable, + user_data + } = payload + + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined( + action_source, + "The root value is missing the required field 'action_source'." + ) + + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined( + event_name, + "The root value is missing the required field 'event_name'." + ) + + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined( + event_time, + "The root value is missing the required field 'event_time'." + ) + + raiseMisconfiguredRequiredFieldErrorIf( + !isNullOrUndefined(custom_data.currency) && !CURRENCY_ISO_4217_CODES.has(custom_data.currency), + `${custom_data.currency} is not a valid currency code.` + ) + + raiseMisconfiguredRequiredFieldErrorIf( + isNullOrUndefined(user_data.em) && + isNullOrUndefined(user_data.ph) && + isNullOrUndefined(user_data.madid) && + (isNullOrUndefined(user_data.client_ip_address) || isNullOrUndefined(user_data.client_user_agent)), + `Payload must contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields` + ) +} + +export const performSnapCAPIv3 = async ( + request: RequestClient, + data: ExecuteInput +): Promise> => { + const { payload, settings } = data + + const payloadData = buildPayloadData(payload, settings) + + validatePayload(payloadData) + validateSettingsConfig(settings, payloadData.action_source) + + const authToken = emptyStringToUndefined(data.auth?.accessToken) + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined(authToken, 'Missing valid auth token') + + const url = buildRequestURL(settings, payloadData.action_source, authToken) + + return request(url, { + method: 'post', + json: { + data: [payloadData] + } + }) +} + +export const performSnapCAPIv3Batch = async ( + request: RequestClient, + data: ExecuteInput +): Promise> => { + const { payload, settings } = data + + if (!Array.isArray(payload) || payload.length === 0) { + throw new IntegrationError('Batch payload must be a non-empty array', 'Invalid Input', 400) + } + + // Snapchat supports up to 2,000 events per batch + if (payload.length > 2000) { + throw new IntegrationError('Batch size cannot exceed 2,000 events', 'Invalid Input', 400) + } + + const payloadDataArray = payload.map((event) => buildPayloadData(event, settings)) + + // Validate all events and settings + payloadDataArray.forEach((payloadData) => { + validatePayload(payloadData) + validateSettingsConfig(settings, payloadData.action_source) + }) + + const authToken = emptyStringToUndefined(data.auth?.accessToken) + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined(authToken, 'Missing valid auth token') + + // Use the action_source from the first event to determine the URL + // All events in a batch should have the same action_source + const url = buildRequestURL(settings, payloadDataArray[0].action_source, authToken) + + return request(url, { + method: 'post', + json: { + data: payloadDataArray + } + }) +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-conversions-api-request.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/snap-conversions-api-request.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/utils.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/utils.ts new file mode 100644 index 00000000000..4a48acce3e5 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/reportConversionEvent/utils.ts @@ -0,0 +1,80 @@ +import { IntegrationError } from '@segment/actions-core' +import { processHashing } from '../../../lib/hashing-utils' + +export const isNullOrUndefined = (v: T | null | undefined): v is null | undefined => v == null + +export const raiseMisconfiguredRequiredFieldErrorIf = (condition: boolean, message: string) => { + if (condition) { + throw new IntegrationError(message, 'Misconfigured required field', 400) + } +} + +// Use an interface to work around typescript limitation of using arrow functions for assertions +interface S { + raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined(v: T | undefined, message: string): asserts v is T +} + +export const raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined: S['raiseMisconfiguredRequiredFieldErrorIfNullOrUndefined'] = + (v: T | undefined, message: string): asserts v is T => + raiseMisconfiguredRequiredFieldErrorIf(isNullOrUndefined(v), message) + +export const box = (v: string | undefined): readonly string[] | undefined => + (v ?? '').length > 0 ? [v as string] : undefined + +export const emptyObjectToUndefined = (v: T) => { + const properties = Object.getOwnPropertyNames(v) + + if (properties.length === 0) { + return undefined + } + + for (const prop of properties) { + if (v[prop] !== undefined) { + return v + } + } + + return undefined +} + +export const splitListValueToArray = (input: string): readonly string[] | undefined => { + // Default to comma seperated values unless semi-colons are present + const separator = input.includes(';') ? ';' : ',' + + // split on the separator, remove whitespace and remove any empty values. + const result = input + .split(separator) + .map((x) => x.trim()) + .filter((x) => x != '') + + return result.length > 0 ? result : undefined +} + +export const emptyStringToUndefined = (v: string | undefined): string | undefined => { + const trimmed = v?.trim() + return (trimmed ?? '').length > 0 ? trimmed : undefined +} + +export const parseNumberSafe = (v: string | number | undefined): number | undefined => { + if (Number.isSafeInteger(v)) { + return v as number + } else if (v != null) { + const parsed = Number.parseInt(String(v) ?? '') + return Number.isSafeInteger(parsed) ? parsed : undefined + } + return undefined +} + +export const parseDateSafe = (v: string | undefined): number | undefined => { + const parsed = Date.parse(v ?? '') + return Number.isSafeInteger(parsed) ? parsed : undefined +} + +export const smartHash = ( + value: string | undefined, + cleaningFunction?: (value: string) => string +): string | undefined => { + if (value === undefined) return + + return processHashing(value, 'sha256', 'hex', cleaningFunction) +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/shared-fields.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/shared-fields.ts new file mode 100644 index 00000000000..db017feef39 --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/shared-fields.ts @@ -0,0 +1,5 @@ +// Re-export the v3 fields from the existing reportConversionEvent action +import snap_capi_input_fields_v3 from './reportConversionEvent/snap-capi-input-fields-v3' + +export { snap_capi_input_fields_v3 } +export type { Payload } from './reportConversionEvent/generated-types' \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/syncUserData/generated-types.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/generated-types.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/syncUserData/generated-types.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/syncUserData/index.ts similarity index 54% rename from packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/syncUserData/index.ts index 0660cf44e46..623ae3f21e5 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/syncUserData/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/syncUserData/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition, defaultValues } from '@segment/actions-core' +import { ActionDefinition } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Sync User Data', @@ -10,15 +10,7 @@ const action: ActionDefinition = { 'Synchronize user data without tracking events to Snapchat Conversions API. Used for updating user profiles and enhancing audience data for better ad targeting.', defaultSubscription: 'type = "identify"', fields: snap_capi_input_fields_v3, - perform, - performBatch, - default: { - event_name: { '@literal': 'UPDATE_PROFILE' }, - action_source: { '@literal': 'website' }, - event_time: { '@path': '$.timestamp' }, - event_id: { '@path': '$.messageId' }, - ...defaultValues(snap_capi_input_fields_v3) - } + perform } export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackAppEvent/generated-types.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/generated-types.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/trackAppEvent/generated-types.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackAppEvent/index.ts similarity index 58% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/trackAppEvent/index.ts index d23a018670e..161e5fd248a 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackAppEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/trackAppEvent/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition, defaultValues } from '@segment/actions-core' +import { ActionDefinition } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Track App Event', @@ -10,14 +10,7 @@ const action: ActionDefinition = { 'Track app-specific events to Snapchat Conversions API. Designed for mobile app conversion tracking with enhanced app data and device information.', defaultSubscription: 'type = "track" and context.device.type != null', fields: snap_capi_input_fields_v3, - perform, - performBatch, - default: { - action_source: { '@literal': 'app' }, - event_time: { '@path': '$.timestamp' }, - event_id: { '@path': '$.messageId' }, - ...defaultValues(snap_capi_input_fields_v3) - } + perform } export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/generated-types.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/generated-types.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/generated-types.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/index.ts similarity index 59% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/index.ts index 3fd6ce00ce0..71ba69e16ad 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition, defaultValues } from '@segment/actions-core' +import { ActionDefinition } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performTrackEvent as perform, performTrackEventBatch as performBatch } from './perform' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Track Event', @@ -10,14 +10,7 @@ const action: ActionDefinition = { 'Track standard conversion events to Snapchat Conversions API. Automatically maps Segment track events to appropriate Snapchat event types for conversion tracking and optimization.', defaultSubscription: 'type = "track"', fields: snap_capi_input_fields_v3, - perform, - performBatch, - default: { - action_source: { '@literal': 'website' }, - event_time: { '@path': '$.timestamp' }, - event_id: { '@path': '$.messageId' }, - ...defaultValues(snap_capi_input_fields_v3) - } + perform } export default action \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/perform.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/perform.ts similarity index 100% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackEvent/perform.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/trackEvent/perform.ts diff --git a/packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/generated-types.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/generated-types.ts new file mode 100644 index 00000000000..bdff545ddfd --- /dev/null +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/generated-types.ts @@ -0,0 +1,438 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The conversion event type. For custom events, you must use one of the predefined event types (i.e. CUSTOM_EVENT_1). Please refer to the possible event types in [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters). + */ + event_name?: string + /** + * If you are reporting events via more than one method (Snap Pixel, App Ads Kit, Conversions API) you should use the same event_id across all methods. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Adds Kit events. + */ + event_id?: string + /** + * The Epoch timestamp for when the conversion happened. The timestamp cannot be more than 7 days in the past. + */ + event_time?: string + /** + * This field allows you to specify where your conversions occurred. + */ + action_source?: string + /** + * These parameters are a set of identifiers Snapchat can use for targeted attribution. You must provide at least one of the following parameters in your request. + */ + user_data?: { + /** + * Any unique ID from the advertiser, such as loyalty membership IDs, user IDs, and external cookie IDs. You can send one or more external IDs for a given event. + */ + externalId?: string[] + /** + * An email address in lowercase. + */ + email?: string + /** + * A phone number. Include only digits with country code, area code, and number. Remove symbols, letters, and any leading zeros. In addition, always include the country code, even if all of the data is from the same country, as the country code is used for matching. + */ + phone?: string + /** + * Gender in lowercase. Either f or m. + */ + gender?: string + /** + * A date of birth given as year, month, and day. Example: 19971226 for December 26, 1997. + */ + dateOfBirth?: string + /** + * A last name in lowercase. + */ + lastName?: string + /** + * A first name in lowercase. + */ + firstName?: string + /** + * A city in lowercase without spaces or punctuation. Example: menlopark. + */ + city?: string + /** + * A two-letter state code in lowercase. Example: ca. + */ + state?: string + /** + * A five-digit zip code for United States. For other locations, follow each country`s standards. + */ + zip?: string + /** + * A two-letter country code in lowercase. + */ + country?: string + /** + * The IP address of the browser corresponding to the event. + */ + client_ip_address?: string + /** + * The user agent for the browser corresponding to the event. This is required if action source is “website”. + */ + client_user_agent?: string + /** + * The subscription ID for the user in this transaction. + */ + subscriptionID?: string + /** + * This is the identifier associated with your Snapchat Lead Ad. + */ + leadID?: number + /** + * Mobile ad identifier (IDFA or AAID) of the user who triggered the conversion event. Segment will normalize and hash this value before sending to Snapchat. [Snapchat requires](https://marketingapi.snapchat.com/docs/conversion.html#conversion-parameters) that every payload contain values for Email or Phone Number or Mobile Ad Identifier or both IP Address and User Agent fields. Also see [Segment documentation](https://segment.com/docs/connections/destinations/catalog/actions-snap-conversions/#required-parameters-and-hashing). + */ + madid?: string + /** + * The ID value stored in the landing page URL's `&ScCid=` query parameter. Using this ID improves ad measurement performance. We also encourage advertisers who are using `click_id` to pass the full url in the `page_url` field. For more details, please refer to [Sending a Click ID](#sending-a-click-id) + */ + sc_click_id?: string + /** + * Unique user ID cookie. If you are using the Pixel SDK, you can access a cookie1 by looking at the _scid value. + */ + sc_cookie1?: string + /** + * IDFV of the user’s device. Segment will normalize and hash this value before sending to Snapchat. + */ + idfv?: string + } + /** + * These fields support sending app events to Snapchat through the Conversions API. + */ + app_data?: { + /** + * *Required for app events* + * Use this field to specify ATT permission on an iOS 14.5+ device. Set to 0 for disabled or 1 for enabled. + */ + advertiser_tracking_enabled?: boolean + /** + * *Required for app events* + * A person can choose to enable ad tracking on an app level. Your SDK should allow an app developer to put an opt-out setting into their app. Use this field to specify the person's choice. Use 0 for disabled, 1 for enabled. + */ + application_tracking_enabled?: boolean + /** + * *Required for app events* Example: 'i2'. + */ + version?: string + /** + * Example: 'com.snapchat.sdk.samples.hello'. + */ + packageName?: string + /** + * Example: '1.0'. + */ + shortVersion?: string + /** + * Example: '1.0 long'. + */ + longVersion?: string + /** + * Example: '13.4.1'. + */ + osVersion?: string + /** + * Example: 'iPhone5,1'. + */ + deviceName?: string + /** + * Example: 'En_US'. + */ + locale?: string + /** + * Example: 'PST'. + */ + timezone?: string + /** + * Example: 'AT&T'. + */ + carrier?: string + /** + * Example: '1080'. + */ + width?: string + /** + * Example: '1920'. + */ + height?: string + /** + * Example: '2.0'. + */ + density?: string + /** + * Example: '8'. + */ + cpuCores?: string + /** + * Example: '64'. + */ + storageSize?: string + /** + * Example: '32'. + */ + freeStorage?: string + /** + * Example: 'USA/New York'. + */ + deviceTimezone?: string + } + /** + * The custom data object can be used to pass custom properties. + */ + custom_data?: { + /** + * Currency for the value specified as ISO 4217 code. + */ + currency?: string + /** + * The number of items when checkout was initiated. + */ + num_items?: number + /** + * Order ID tied to the conversion event. Please refer to the [Snapchat Marketing API docs](https://marketingapi.snapchat.com/docs/conversion.html#deduplication) for information on how this field is used for deduplication against Snap Pixel SDK and App Ads Kit events. + */ + order_id?: string + /** + * The text string that was searched for. + */ + search_string?: string + /** + * A string indicating the sign up method. + */ + sign_up_method?: string + /** + * Total value of the purchase. This should be a single number. Can be overriden using the 'Track Purchase Value Per Product' field. + */ + value?: number + /** + * The desired hotel check-in date in the hotel's time-zone. Accepted formats are YYYYMMDD, YYYY-MM-DD, YYYY-MM-DDThh:mmTZD and YYYY-MM-DDThh:mm:ssTZD + */ + checkin_date?: string + /** + * End date of travel + */ + travel_end?: string + /** + * Start date of travel + */ + travel_start?: string + /** + * The suggested destinations + */ + suggested_destinations?: string + /** + * The destination airport. Make sure to use the IATA code of the airport + */ + destination_airport?: string + /** + * The country based on the location the user intends to visit + */ + country?: string + /** + * The city based on the location the user intends to visit + */ + city?: string + /** + * This could be the state, district, or region of interest to the user + */ + region?: string + /** + * The neighborhood the user is interested in + */ + neighborhood?: string + /** + * The starting date and time for travel + */ + departing_departure_date?: string + /** + * The arrival date and time at the destination for the travel + */ + departing_arrival_date?: string + /** + * The number of adults staying + */ + num_adults?: number + /** + * The official IATA code of origin airport + */ + origin_airport?: string + /** + * The starting date and time of the return journey + */ + returning_departure_date?: string + /** + * The date and time when the return journey is complete + */ + returning_arrival_date?: string + /** + * The number of children staying + */ + num_children?: number + /** + * This represents the hotels score relative to other hotels to an advertiser + */ + hotel_score?: string + /** + * The postal /zip code + */ + postal_code?: string + /** + * The number of infants staying + */ + num_infants?: number + /** + * Any preferred neighborhoods for the stay + */ + preferred_neighborhoods?: string + /** + * The minimum and maximum hotel star rating supplied as a tuple. This is what the user would use for filtering hotels + */ + preferred_star_ratings?: string + /** + * The suggested hotels + */ + suggested_hotels?: string + } + /** + * The Data Processing Options to send to Snapchat. If set to true, Segment will send an array to Snapchat indicating events should be processed with Limited Data Use (LDU) restrictions. + */ + data_processing_options?: boolean + /** + * A country that you want to associate to the Data Processing Options. Accepted values are 1, for the United States of America, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_country?: number + /** + * A state that you want to associate to the Data Processing Options. Accepted values are 1000, for California, or 0, to request that Snapchat geolocates the event using IP address. This is required if Data Processing Options is set to true. If nothing is provided, Segment will send 0. + */ + data_processing_options_state?: number + /** + * The URL of the web page where the event took place. + */ + event_source_url?: string + /** + * Use this field to send details of mulitple products / items. This field overrides individual 'Item ID', 'Item Category' and 'Brand' fields. Note: total purchase value is tracked using the 'Price' field + */ + products?: { + /** + * Identfier for the item. International Article Number (EAN) when applicable, or other product or category identifier. + */ + item_id?: string + /** + * Category of the item. This field accepts a string. + */ + item_category?: string + /** + * Brand associated with the item. This field accepts a string. + */ + brand?: string + }[] + /** + * [Deprecated] Use Products field. + */ + brands?: string[] + /** + * Deprecated. Use User Data sc_click_id field. + */ + click_id?: string + /** + * Deprecated. Use Event ID field. + */ + client_dedup_id?: string + /** + * Deprecated. Use Custom Data currency field. + */ + currency?: string + /** + * Deprecated. No longer supported. + */ + description?: string + /** + * Deprecated. Use App Data deviceName field. + */ + device_model?: string + /** + * Deprecated. Use User Data email field. + */ + email?: string + /** + * Deprecated. Use Action Source field. + */ + event_conversion_type?: string + /** + * Deprecated. No longer supported. + */ + event_tag?: string + /** + * Deprecated. Use Event Name field. + */ + event_type?: string + /** + * Deprecated. Use User Data idfv field. + */ + idfv?: string + /** + * Deprecated. Use User Data client_ip_address field. + */ + ip_address?: string + /** + * Deprecated. Use products field. + */ + item_category?: string + /** + * Deprecated. Use products field. + */ + item_ids?: string + /** + * Deprecated. No longer supported. + */ + level?: string + /** + * Deprecated. Use User Data madid field. + */ + mobile_ad_id?: string + /** + * Deprecated. Use Custom Data num_items field. + */ + number_items?: string + /** + * Deprecated. Use App Data version field. + */ + os_version?: string + /** + * Deprecated. Use Event Source URL field. + */ + page_url?: string + /** + * Deprecated. Use User Data phone field. + */ + phone_number?: string + /** + * Deprecated. Use Custom Data value field. + */ + price?: number + /** + * Deprecated. Use Custom Data search_string field. + */ + search_string?: string + /** + * Deprecated. Use Custom Data sign_up_method field. + */ + sign_up_method?: string + /** + * Deprecated. Use Event Timestamp field. + */ + timestamp?: string + /** + * Deprecated. Use Custom Data order_id field. + */ + transaction_id?: string + /** + * Deprecated. Use User Data client_user_agent field. + */ + user_agent?: string + /** + * Deprecated. Use User Data sc_cookie1 field. + */ + uuid_c1?: string +} diff --git a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts b/packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/index.ts similarity index 56% rename from packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts rename to packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/index.ts index f8665608e14..9a4ffb87f91 100644 --- a/packages/destination-actions/src/destinations/snap-conversions-api/trackPurchase/index.ts +++ b/packages/destination-actions/src/destinations/snap-conversions-v3/trackPurchase/index.ts @@ -1,8 +1,8 @@ -import { ActionDefinition, defaultValues } from '@segment/actions-core' +import { ActionDefinition } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import snap_capi_input_fields_v3 from '../reportConversionEvent/snap-capi-input-fields-v3' -import { performSnapCAPIv3 as perform, performSnapCAPIv3Batch as performBatch } from '../reportConversionEvent/snap-capi-v3' +import { performSnapCAPIv3 as perform } from '../reportConversionEvent/snap-capi-v3' const action: ActionDefinition = { title: 'Track Purchase', @@ -10,15 +10,7 @@ const action: ActionDefinition = { 'Track purchase/conversion events with enhanced ecommerce data to Snapchat Conversions API. Optimized for Order Completed events with detailed product and transaction information.', defaultSubscription: 'type = "track" and event = "Order Completed"', fields: snap_capi_input_fields_v3, - perform, - performBatch, - default: { - event_name: { '@literal': 'PURCHASE' }, - action_source: { '@literal': 'website' }, - event_time: { '@path': '$.timestamp' }, - event_id: { '@path': '$.messageId' }, - ...defaultValues(snap_capi_input_fields_v3) - } + perform } export default action \ No newline at end of file